GPU

개요

MediaPipe는 GPU 컴퓨팅 및 렌더링을 위한 계산기 노드를 지원하며 여러 GPU 노드를 결합할 수 있고 CPU 기반 계산기 노드와 혼합할 수 있습니다. 모바일 플랫폼에는 여러 GPU API가 있습니다 (예: OpenGL ES, Metal, Vulkan). MediaPipe는 단일 교차 API GPU 추상화를 제공하려고 시도하지 않습니다. 개별 노드는 다양한 API를 사용하여 작성될 수 있으므로 필요할 때 플랫폼별 기능을 활용할 수 있습니다.

GPU 지원은 모바일 플랫폼에서 좋은 성능을 발휘하는 데 필수적이며, 특히 실시간 동영상의 경우 더욱 그렇습니다. MediaPipe를 사용하면 개발자는 다음 작업에 GPU 사용을 지원하는 GPU 호환 계산기를 작성할 수 있습니다.

  • 일괄 처리가 아닌 기기 내 실시간 처리
  • 단순한 분석이 아닌 동영상 렌더링 및 효과

다음은 MediaPipe의 GPU 지원을 위한 설계 원칙입니다.

  • GPU 기반 계산기는 그래프의 아무 곳에서나 사용할 수 있어야 하며 화면 렌더링에 반드시 사용할 필요는 없습니다.
  • 한 GPU 기반 계산기에서 다른 GPU 기반 계산기로 프레임 데이터를 신속하게 전송해야 하며 값비싼 복사 작업이 발생하지 않아야 합니다.
  • CPU와 GPU 간의 프레임 데이터 전송은 플랫폼에서 허용하는 한 최대한 효율적이어야 합니다.
  • 최상의 성능을 얻기 위해 플랫폼마다 필요한 기술이 다를 수 있으므로 API는 백그라운드에서 구현되는 방식의 유연성을 허용해야 합니다.
  • 계산기는 작업의 전부 또는 일부에 GPU를 최대한 유연하게 사용하고 필요한 경우 CPU와 결합해야 합니다.

OpenGL ES 지원

MediaPipe는 Android/Linux에서 최대 3.2 버전의 OpenGL ES와 ES 3.0까지 OpenGL ES를 지원합니다. (iOS) 또한 MediaPipe는 iOS의 Metal도 지원합니다.

실행하려면 OpenGL ES 3.1 이상 (Android/Linux 시스템) 필요 머신러닝 추론 계산기와 그래프입니다.

MediaPipe를 사용하면 그래프가 여러 GL 컨텍스트에서 OpenGL을 실행할 수 있습니다. 예를 들어 이 더 느린 GPU 추론 경로 (예: FPS)에서 더 빠른 GPU 렌더링 경로 사용 (예: 30FPS): 단일 GL 컨텍스트 이후 하나의 순차 명령 대기열에 해당하며, 두 대기열 모두에 동일한 컨텍스트를 사용합니다. 렌더링 프레임 속도가 줄어듭니다.

MediaPipe가 여러 컨텍스트를 사용하여 해결할 수 있는 한 가지 문제는 소통할 수 있습니다 예시 시나리오는 모두 전송되므로 렌더링되어야 하며 최신 출력에 액세스할 수 있습니다

OpenGL 컨텍스트는 동시에 여러 스레드에서 액세스할 수 없습니다. 또한, 동일한 스레드에서 활성 GL 컨텍스트를 전환하면 실행 속도가 느려질 수 있습니다. 일부 Android 기기. 따라서 우리의 접근 방식은 있습니다 각 스레드는 GL 명령을 발행하여 직렬 명령 대기열을 구축함 GPU에서 비동기식으로 실행됩니다.

GPU 계산기 수명

이 섹션에서는 GPU 프로세스 메서드의 기본 구조를 보여줍니다. 기본 클래스 GlSimpleCalculator에서 파생된 계산기 GPU 계산기 다음은 LuminanceCalculator를 보여주는 예입니다. 메서드 LuminanceCalculator::GlRenderGlSimpleCalculator::Process에서 호출됩니다.

// Converts RGB images into luminance images, still stored in RGB format.
// See GlSimpleCalculator for inputs, outputs and input side packets.
class LuminanceCalculator : public GlSimpleCalculator {
 public:
  absl::Status GlSetup() override;
  absl::Status GlRender(const GlTexture& src,
                        const GlTexture& dst) override;
  absl::Status GlTeardown() override;

 private:
  GLuint program_ = 0;
  GLint frame_;
};
REGISTER_CALCULATOR(LuminanceCalculator);

absl::Status LuminanceCalculator::GlRender(const GlTexture& src,
                                           const GlTexture& dst) {
  static const GLfloat square_vertices[] = {
      -1.0f, -1.0f,  // bottom left
      1.0f,  -1.0f,  // bottom right
      -1.0f, 1.0f,   // top left
      1.0f,  1.0f,   // top right
  };
  static const GLfloat texture_vertices[] = {
      0.0f, 0.0f,  // bottom left
      1.0f, 0.0f,  // bottom right
      0.0f, 1.0f,  // top left
      1.0f, 1.0f,  // top right
  };

  // program
  glUseProgram(program_);
  glUniform1i(frame_, 1);

  // vertex storage
  GLuint vbo[2];
  glGenBuffers(2, vbo);
  GLuint vao;
  glGenVertexArrays(1, &vao);
  glBindVertexArray(vao);

  // vbo 0
  glBindBuffer(GL_ARRAY_BUFFER, vbo[0]);
  glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(GLfloat), square_vertices,
               GL_STATIC_DRAW);
  glEnableVertexAttribArray(ATTRIB_VERTEX);
  glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, 0, 0, nullptr);

  // vbo 1
  glBindBuffer(GL_ARRAY_BUFFER, vbo[1]);
  glBufferData(GL_ARRAY_BUFFER, 4 * 2 * sizeof(GLfloat), texture_vertices,
               GL_STATIC_DRAW);
  glEnableVertexAttribArray(ATTRIB_TEXTURE_POSITION);
  glVertexAttribPointer(ATTRIB_TEXTURE_POSITION, 2, GL_FLOAT, 0, 0, nullptr);

  // draw
  glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);

  // cleanup
  glDisableVertexAttribArray(ATTRIB_VERTEX);
  glDisableVertexAttribArray(ATTRIB_TEXTURE_POSITION);
  glBindBuffer(GL_ARRAY_BUFFER, 0);
  glBindVertexArray(0);
  glDeleteVertexArrays(1, &vao);
  glDeleteBuffers(2, vbo);

  return absl::OkStatus();
}

위에서 언급한 설계 원칙은 다음과 같은 설계를 가능하게 했습니다. MediaPipe GPU 지원을 위한 선택사항:

  • 이미지 데이터를 나타내는 GPU 데이터 유형인 GpuBuffer가 있으며, 이 유형은 GPU 사용에 최적화되어 있습니다. 이 데이터 유형의 정확한 콘텐츠는 불투명하고 플랫폼에 따라 다릅니다.
  • 합성 기반의 하위 수준 API로, GPU를 활용하려는 모든 계산기가 GlCalculatorHelper 클래스의 인스턴스를 만들고 소유합니다. 이 클래스는 OpenGL 컨텍스트 관리, 입력 및 출력용 텍스처 설정 등을 위해 플랫폼에 구애받지 않는 API를 제공합니다.
  • 서브클래스에 기반한 상위 수준 API로, 이미지 필터를 구현하는 간단한 계산기가 GlSimpleCalculator의 서브클래스를 구현하고 특정 OpenGL 코드로 몇 개의 가상 메서드만 재정의하면 되지만 슈퍼클래스가 모든 배관 작업을 처리합니다.
  • 모든 GPU 기반 계산기에서 공유해야 하는 데이터는 그래프 서비스로 구현되고 GlCalculatorHelper 클래스에서 관리되는 외부 입력으로 제공됩니다.
  • 계산기별 도우미와 공유 그래프 서비스의 조합을 사용하면 GPU 리소스를 매우 유연하게 관리할 수 있습니다. 계산기별로 별도의 컨텍스트를 사용하고 단일 컨텍스트를 공유하고 잠금 또는 기타 동기화 프리미티브를 공유하는 등 이 모든 것이 도우미에 의해 관리되고 개별 계산기에서 숨겨집니다.

GpuBuffer에서 ImageFrame으로 변환기

Google에서는 GpuBufferToImageFrameCalculatorImageFrameToGpuBufferCalculator라는 두 가지 계산기를 제공합니다. 이 계산기는 ImageFrameGpuBuffer 간에 변환되므로 GPU와 CPU 계산기를 결합한 그래프를 구성할 수 있습니다. iOS와 Android 모두에서 지원됩니다.

가능한 경우, 이러한 계산기는 플랫폼별 기능을 사용하여 복사하지 않고 CPU와 GPU 간에 데이터를 공유합니다.

아래 다이어그램은 카메라에서 동영상을 캡처하고 MediaPipe 그래프를 통해 실행하고 화면에 출력을 실시간으로 렌더링하는 모바일 애플리케이션의 데이터 흐름을 보여줍니다. 점선은 MediaPipe 그래프 내에 어느 부분이 적절한지 나타냅니다. 이 애플리케이션은 OpenCV를 사용하여 CPU에서 Canny 에지 감지 필터를 실행하고 GPU를 사용하여 원본 동영상 위에 오버레이합니다.

GPU 계산기의 상호작용 방식

카메라의 동영상 프레임은 GpuBuffer 패킷으로 그래프에 제공됩니다. 이 두 개의 계산기가 동시에 액세스하여 입력 스트림에 액세스합니다. GpuBufferToImageFrameCalculator는 버퍼를 ImageFrame로 변환합니다. 그런 다음 그레이 스케일 변환기와 캐니 필터 (모두 기반 CPU에서 실행), 그 출력은 다시 GpuBuffer. 다중 입력 GPU 계산기인 GlOverlayCalculator는 원본 GpuBuffer와 에지 감지기에서 나오는 GpuBuffer를 모두 입력합니다. 셰이더를 사용하여 오버레이합니다. 그런 다음 출력이 애플리케이션에 대해 계산되며 애플리케이션이 이미지를 렌더링합니다. 화면에 표시할 수 있습니다.