GPU

खास जानकारी

MediaPipe, जीपीयू कंप्यूट और रेंडरिंग के लिए कैलकुलेटर नोड के साथ काम करता है. साथ ही, यह कई जीपीयू नोड को मिलाने के साथ-साथ उन्हें सीपीयू आधारित कैलकुलेटर नोड के साथ मिक्स करने की सुविधा देता है. मोबाइल प्लैटफ़ॉर्म पर कई जीपीयू एपीआई मौजूद हैं (उदाहरण के लिए, OpenGL ES, मेटल, और Vulkan). MediaPipe एक क्रॉस-एपीआई जीपीयू ऐब्स्ट्रैक्शन ऑफ़र करने की कोशिश नहीं करता. अलग-अलग नोड को अलग-अलग एपीआई का इस्तेमाल करके लिखा जा सकता है, जिससे वे ज़रूरत पड़ने पर प्लैटफ़ॉर्म की खास सुविधाओं का फ़ायदा ले सकते हैं.

मोबाइल प्लैटफ़ॉर्म पर अच्छी परफ़ॉर्मेंस के लिए, जीपीयू से जुड़ी सहायता ज़रूरी है. खास तौर पर, रीयल-टाइम वीडियो के लिए. MediaPipe की मदद से डेवलपर, जीपीयू के साथ काम करने वाले ऐसे कैलकुलेटर लिख सकते हैं जो इन चीज़ों के लिए जीपीयू के इस्तेमाल की सुविधा देते हैं:

  • बैच प्रोसेसिंग ही नहीं, बल्कि डिवाइस पर रीयल-टाइम प्रोसेसिंग भी
  • सिर्फ़ विश्लेषण ही नहीं, बल्कि वीडियो रेंडरिंग और इफ़ेक्ट भी

MediaPipe में जीपीयू से जुड़ी सहायता के लिए डिज़ाइन के सिद्धांत नीचे दिए गए हैं

  • जीपीयू पर आधारित कैलकुलेटर, ग्राफ़ में कहीं भी मौजूद होने चाहिए. साथ ही, ऑन-स्क्रीन रेंडरिंग के लिए इनका इस्तेमाल करना ज़रूरी नहीं है.
  • एक जीपीयू-आधारित कैलकुलेटर से दूसरे में फ़्रेम डेटा को तेज़ी से ट्रांसफ़र किया जाना चाहिए. साथ ही, डेटा कॉपी करने के लिए ज़्यादा पैसे नहीं चुकाने चाहिए.
  • सीपीयू और जीपीयू के बीच फ़्रेम का डेटा उतना ही ट्रांसफ़र होना चाहिए जितना कि प्लैटफ़ॉर्म ने अनुमति दी है.
  • अलग-अलग प्लैटफ़ॉर्म पर बेहतरीन परफ़ॉर्मेंस के लिए अलग-अलग तकनीकों की ज़रूरत हो सकती है. इसलिए, एपीआई को पर्दे के पीछे की गतिविधियों को लागू करने के तरीके में बदलाव करने की अनुमति देनी चाहिए.
  • कैलकुलेटर के काम करने के तरीके के हिसाब से, जीपीयू का इस्तेमाल अपने हिसाब से किया जा सकता है. साथ ही, ज़रूरत पड़ने पर इसे सीपीयू से जोड़कर भी इस्तेमाल किया जा सकता है.

OpenGL ES सहायता

MediaPipe पर Android/Linux पर OpenGL ES वर्शन 3.2 तक और iOS पर ES 3.0 तक के वर्शन काम करते हैं. इसके अलावा, MediaPipe, iOS पर मेटल का भी काम करता है.

मशीन लर्निंग अनुमान कैलकुलेटर और ग्राफ़ चलाने के लिए, (Android/Linux सिस्टम पर) OpenGL ES 3.1 या इसके बाद के वर्शन की ज़रूरत होती है.

MediaPipe, ग्राफ़ को एक से ज़्यादा GL कॉन्टेक्स्ट में OpenGL चलाने की अनुमति देता है. उदाहरण के लिए, यह उन ग्राफ़ में बहुत काम का हो सकता है जिनमें दिए गए जीपीयू रेंडरिंग पाथ (जैसे, 10 FPS पर) को ज़्यादा तेज़ जीपीयू रेंडरिंग पाथ के साथ जोड़ा जाता है (जैसे, 30 FPS पर): क्योंकि एक जीएल कॉन्टेक्स्ट, एक क्रम में चलने वाली कमांड लाइन से मेल खाता है. इसलिए, दोनों टास्क के लिए एक जैसा कॉन्टेक्स्ट इस्तेमाल करने से फ़्रेम रेट कम हो जाएगा.

MediaPipe की एक चुनौती यह है कि वह अलग-अलग कॉन्टेक्स्ट को समझकर, आपस में बातचीत कर पाए. उदाहरण के तौर पर, ऐसा इनपुट वीडियो ऐसा होता है जिसे रेंडरिंग और अनुमान पाथ, दोनों के लिए भेजा जाता है. वहीं, रेंडरिंग के पास अनुमान से सबसे नए आउटपुट का ऐक्सेस होना ज़रूरी है.

OpenGL के कॉन्टेक्स्ट को, एक ही समय में कई थ्रेड से ऐक्सेस नहीं किया जा सकता. इसके अलावा, एक ही थ्रेड पर ऐक्टिव जीएल कॉन्टेक्स्ट को स्विच करने से, कुछ Android डिवाइसों पर ऐसा करने में देरी हो सकती है. इसलिए, हमारा तरीका है कि हर कॉन्टेक्स्ट के लिए एक खास थ्रेड हो. हर थ्रेड, जीएल कमांड जारी करती है और उसके कॉन्टेक्स्ट पर एक सीरियल कमांड सूची बनाती है. इसके बाद, जीपीयू एक ही क्रम में काम करता है.

जीपीयू कैलकुलेटर का लाइफ़

इस सेक्शन में, आधार क्लास GlSimpleCalculator से लिए गए जीपीयू कैलकुलेटर की प्रोसेस के तरीके की बुनियादी बनावट के बारे में बताया गया है. जीपीयू कैलकुलेटर LuminanceCalculator को उदाहरण के तौर पर दिखाया गया है. GlSimpleCalculator::Process ने LuminanceCalculator::GlRender तरीके को कॉल किया है.

// 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 जीपीयू के साथ काम करने के लिए ये विकल्प:

  • इमेज डेटा को दिखाने के लिए, हमारे पास GpuBuffer नाम का जीपीयू डेटा टाइप है. इसे जीपीयू के इस्तेमाल के हिसाब से ऑप्टिमाइज़ किया गया है. इस डेटा टाइप का कॉन्टेंट, ओपेक और प्लैटफ़ॉर्म के हिसाब से तय होता है.
  • यह कंपोज़िशन के हिसाब से लो-लेवल का एपीआई होता है. इसमें कोई भी कैलकुलेटर जो जीपीयू का इस्तेमाल करता है और GlCalculatorHelper क्लास के इंस्टेंस बनाता है और उसका मालिक होता है. यह क्लास OpenGL के कॉन्टेक्स्ट को मैनेज करने, इनपुट और आउटपुट के लिए टेक्सचर सेट अप करने वगैरह के लिए, Platform-agnostic API की सुविधा देती है.
  • सब-क्लास पर आधारित हाई-लेवल एपीआई, जिसमें आसान कैलकुलेटर GlSimpleCalculator से इमेज फ़िल्टर की सब-क्लास लागू करते हैं. साथ ही, उन्हें खास OpenGL कोड की मदद से, कुछ वर्चुअल तरीकों को ही बदलना होता है, जबकि सुपर क्लास सभी प्लंबिंग का ध्यान रखती है.
  • जिस डेटा को जीपीयू पर आधारित सभी कैलकुलेटर के बीच शेयर करने की ज़रूरत होती है उसे बाहरी इनपुट के तौर पर दिया जाता है. इसे ग्राफ़ सेवा के तौर पर लागू किया जाता है और इसे GlCalculatorHelper क्लास मैनेज करती है.
  • कैलकुलेटर-खास हेल्पर और शेयर की गई ग्राफ़ सेवा का कॉम्बिनेशन, हमें जीपीयू संसाधन को मैनेज करने में काफ़ी सहूलियत मिलती है: हम हर कैलकुलेटर के लिए अलग संदर्भ रख सकते हैं, कोई एक संदर्भ शेयर कर सकते हैं, कोई लॉक या अन्य सिंक्रोनाइज़ेशन प्रिमिटिव शेयर कर सकते हैं. -- और ये सभी चीज़ें हेल्पर मैनेज करता है और अलग-अलग कैलकुलेटर से छिपा होता है.

GpuBuffer से ImageFrame कन्वर्टर

हम GpuBufferToImageFrameCalculator और ImageFrameToGpuBufferCalculator नाम के दो कैलकुलेटर उपलब्ध कराते हैं. ये कैलकुलेटर ImageFrame और GpuBuffer के बीच बदलते हैं. इससे, जीपीयू और सीपीयू कैलकुलेटर बनाने वाले ग्राफ़ बन जाते हैं. ये iOS और Android, दोनों पर काम करते हैं

जब भी मुमकिन होता है, ये कैलकुलेटर बिना किसी कॉपी के सीपीयू और जीपीयू के बीच डेटा शेयर करने के लिए, प्लैटफ़ॉर्म के हिसाब से काम करने वाले फ़ंक्शन का इस्तेमाल करते हैं.

नीचे दिया गया डायग्राम, मोबाइल ऐप्लिकेशन में डेटा फ़्लो को दिखाता है. यह कैमरे से वीडियो कैप्चर करता है, उसे MediaPipe ग्राफ़ के ज़रिए चलाता है, और आउटपुट को रीयल-टाइम में स्क्रीन पर रेंडर करता है. डैश वाली लाइन से पता चलता है कि MediaPipe ग्राफ़ के अंदर कौनसे हिस्से सही हैं. यह ऐप्लिकेशन, OpenCV का इस्तेमाल करके सीपीयू पर Canny एज-डिटेक्शन फ़िल्टर चलाता है. साथ ही, जीपीयू का इस्तेमाल करके, इसे ओरिजनल वीडियो पर ओवरले करता है.

जीपीयू कैलकुलेटर कैसे इंटरैक्ट करते हैं

कैमरे से लिए गए वीडियो फ़्रेम, ग्राफ़ में GpuBuffer पैकेट के तौर पर डाले जाते हैं. इनपुट स्ट्रीम को साथ-साथ दो कैलकुलेटर से ऐक्सेस किया जाता है. GpuBufferToImageFrameCalculator बफ़र को ImageFrame में बदल देता है, जिसे इसके बाद ग्रेस्केल कन्वर्टर और कैनी फ़िल्टर के ज़रिए भेजा जाता है (OpenCV और सीपीयू पर चलने, दोनों के आधार पर). इसका आउटपुट फिर से GpuBuffer में बदल जाता है. कई इनपुट वाले जीपीयू कैलकुलेटर, GlOverlayCalculator, मूल GpuBuffer और एज डिटेक्टर से आने वाले, दोनों को इनपुट के तौर पर इस्तेमाल करते हैं. साथ ही, शेडर का इस्तेमाल करके उन्हें ओवरले करते हैं. इसके बाद, आउटपुट को कॉलबैक कैलकुलेटर का इस्तेमाल करके ऐप्लिकेशन पर वापस भेजा जाता है और ऐप्लिकेशन, OpenGL का इस्तेमाल करके स्क्रीन पर इमेज को रेंडर करता है.