वेब पर जीपीयू कंप्यूट का इस्तेमाल शुरू करना

इस पोस्ट में, एक्सपेरिमेंट के तौर पर शुरू किए गए WebGPU API के बारे में उदाहरण देकर बताया गया है. तो आप जीपीयू का इस्तेमाल करके डेटा-पैरलल कंप्यूटेशन से शुरू कर सकते हैं.

François Beaufort
François Beaufort

बैकग्राउंड

जैसा कि आपको पहले ही पता होगा कि ग्राफ़िक प्रोसेसिंग यूनिट (जीपीयू), एक इलेक्ट्रॉनिक एक कंप्यूटर का एक सबसिस्टम, जिसे मूल रूप से प्रोसेसिंग के लिए खास तौर पर बनाया गया था ग्राफ़िक्स. हालांकि, पिछले 10 सालों में, एआई के इस्तेमाल को लेकर आर्किटेक्चर की मदद से डेवलपर, न सिर्फ़ कई तरह के एल्गोरिदम लागू कर सकते हैं 3D ग्राफ़िक्स रेंडर करने के लिए जीपीयू. इन क्षमताओं को जीपीयू कंप्यूट कहा जाता है और जीपीयू का इस्तेमाल सामान्य पर्पज़ साइंटिफ़िक कंप्यूटिंग के लिए को-प्रोसेसर को जनरल पर्पज़ जीपीयू (GPGPU) प्रोग्रामिंग.

जीपीयू कंप्यूट ने हाल ही में मशीन लर्निंग में हुई बढ़ोतरी में अहम योगदान दिया है, क्योंकि कॉन्वलूशन न्यूरल नेटवर्क और अन्य मॉडल आर्किटेक्चर का इस्तेमाल किया जाता है. मौजूदा वेब प्लैटफ़ॉर्म के साथ जीपीयू कंप्यूट की सुविधाएं नहीं हैं, W3C का "वेब के लिए जीपीयू" समुदाय समूह ऐसे आधुनिक जीपीयू एपीआई दिखाने के लिए एक एपीआई डिज़ाइन कर रहा है जो ज़्यादातर ब्राउज़र पर उपलब्ध है मौजूदा डिवाइस. इस एपीआई को WebGPU कहा जाता है.

WebGPU, WebGL की तरह ही एक लो-लेवल एपीआई है. यह बेहद असरदार और बहुत शब्दों वाला है, जैसा कि आपको यह दिखेगा. लेकिन कोई बात नहीं. हम चाहते हैं कि परफ़ॉर्मेंस बेहतर हो.

इस लेख में, हमने WebGPU के जीपीयू Compute हिस्से पर फ़ोकस किया है. साथ ही, मैं गेम की सतह को स्क्रैच कर रहा हूं, ताकि आप मालिकाना हक है. इसके बारे में मुझे और जानकारी चाहिए और WebGPU रेंडरिंग (कैनवस, टेक्सचर, वगैरह.)

जीपीयू ऐक्सेस करें

WebGPU में जीपीयू को ऐक्सेस करना आसान है. navigator.gpu.requestAdapter() को कॉल किया जा रहा है यह JavaScript प्रॉमिस को दिखाता है, जो जीपीयू के साथ एसिंक्रोनस तरीके से रिज़ॉल्व हो जाता है अडैप्टर. इस अडैप्टर को ग्राफ़िक कार्ड की तरह समझें. इसे या तो इंटिग्रेट किया जा सकता है (सीपीयू वाले चिप में) या अलग (आम तौर पर, PCIe कार्ड जो कि परफ़ॉर्मेंट है, लेकिन ज़्यादा पावर का इस्तेमाल करता है).

जीपीयू अडैप्टर मिलने के बाद, प्रॉमिस पाने के लिए adapter.requestDevice() पर कॉल करें एक जीपीयू डिवाइस के साथ समाधान हो जाएगा, जिसका इस्तेमाल आपको कुछ जीपीयू कंप्यूटेशन के लिए करना होगा.

const adapter = await navigator.gpu.requestAdapter();
if (!adapter) { return; }
const device = await adapter.requestDevice();

दोनों फ़ंक्शन ऐसे विकल्प लेते हैं जिनसे आपको यह समझने में मदद मिलती है कि अडैप्टर (पावर प्राथमिकता) और डिवाइस (एक्सटेंशन, सीमाएं) को अनुमति देता है. इसे आसानी से समझने के लिए, हम इस लेख में डिफ़ॉल्ट विकल्पों का इस्तेमाल करेंगे.

बफ़र मेमोरी में बदलाव करने की अनुमति दें

हम जीपीयू के लिए मेमोरी में डेटा लिखने के लिए, JavaScript का इस्तेमाल करने का तरीका देखते हैं. यह यह प्रोसेस आसान नहीं है, क्योंकि आधुनिक वेब में इस्तेमाल किए जाने वाले सैंडबॉक्सिंग मॉडल ब्राउज़र खोलें.

नीचे दिया उदाहरण दिखाता है कि बफ़र मेमोरी को ऐक्सेस करने लायक चार बाइट बनाने का तरीका क्या है का इस्तेमाल किया जा सकता है. यह device.createBuffer() को कॉल करता है, जो बफ़र और उसका इस्तेमाल. भले ही इस्तेमाल फ़्लैग GPUBufferUsage.MAP_WRITE यह हो इस कॉल के लिए ज़रूरी नहीं है, लेकिन आपको साफ़ तौर पर बताना होगा कि हमें इस बफ़र का इस्तेमाल करें. यह बनाने के दौरान मैप किया गया एक जीपीयू बफ़र ऑब्जेक्ट बनाता है. इसमें इसका धन्यवाद mappedAtCreation को 'सही है' पर सेट किया गया. तब इससे जुड़ा रॉ बाइनरी डेटा बफ़र, जीपीयू बफ़र मेथड को getMappedRange() कॉल करके, वापस लाया जा सकता है.

अगर आपने ArrayBuffer का इस्तेमाल करके पहले ही गेम खेला है, तो आपको बाइट लिखने के बारे में पता है; किसी TypedArray और वैल्यू को कॉपी करें.

// Get a GPU buffer in a mapped state and an arrayBuffer for writing.
const gpuBuffer = device.createBuffer({
  mappedAtCreation: true,
  size: 4,
  usage: GPUBufferUsage.MAP_WRITE
});
const arrayBuffer = gpuBuffer.getMappedRange();

// Write bytes to buffer.
new Uint8Array(arrayBuffer).set([0, 1, 2, 3]);

यहां पर, जीपीयू बफ़र को मैप किया जाता है, जिसका मतलब है कि इसका मालिकाना हक सीपीयू के पास है, और तो, इसे JavaScript से पढ़ें/लिखें में ऐक्सेस किया जा सकता है. जीपीयू इसे ऐक्सेस कर सके, को मैप नहीं करना होगा, जो gpuBuffer.unmap() को कॉल करने जितना आसान है.

ऐसी रेस स्थितियों को रोकने के लिए मैप किए गए/अनमैप किए गए का सिद्धांत ज़रूरी है जहां जीपीयू होने पर और सीपीयू ऐक्सेस मेमोरी, दोनों एक साथ आ सकते हैं.

बफ़र मेमोरी पढ़ें

आइए, अब देखते हैं कि जीपीयू बफ़र को किसी दूसरे जीपीयू बफ़र में कॉपी कैसे करें और फिर इसे पढ़ें.

हम पहले जीपीयू बफ़र में लिख रहे हैं और हम इसे एक सेकंड में कॉपी करना चाहते हैं जीपीयू बफ़र, इस्तेमाल से जुड़ा नया फ़्लैग GPUBufferUsage.COPY_SRC ज़रूरी है. दूसरा इस बार जीपीयू बफ़र को मैप नहीं की गई स्थिति में बनाया गया है. इसके साथ device.createBuffer(). इसका इस्तेमाल फ़्लैग GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ है, क्योंकि इसका इस्तेमाल पहले जीपीयू के डेस्टिनेशन के रूप में किया जाएगा जीपीयू कॉपी करने के निर्देश लागू होने के बाद, बफ़र करें और JavaScript में पढ़ें.

// Get a GPU buffer in a mapped state and an arrayBuffer for writing.
const gpuWriteBuffer = device.createBuffer({
  mappedAtCreation: true,
  size: 4,
  usage: GPUBufferUsage.MAP_WRITE | GPUBufferUsage.COPY_SRC
});
const arrayBuffer = gpuWriteBuffer.getMappedRange();

// Write bytes to buffer.
new Uint8Array(arrayBuffer).set([0, 1, 2, 3]);

// Unmap buffer so that it can be used later for copy.
gpuWriteBuffer.unmap();

// Get a GPU buffer for reading in an unmapped state.
const gpuReadBuffer = device.createBuffer({
  size: 4,
  usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
});

जीपीयू एक इंडिपेंडेंट को-प्रोसेसर है, इसलिए सभी जीपीयू कमांड एक्ज़ीक्यूट किए जाते हैं एसिंक्रोनस रूप से. यही वजह है कि जीपीयू कमांड की सूची बनाई जाती है और भेजी जाती है बैच बनाता है. WebGPU में, जीपीयू कमांड एन्कोडर, device.createCommandEncoder()ऐसा JavaScript ऑब्जेक्ट है जो "बफ़र किया गया" कुछ समय के लिए जीपीयू को भेजे जाएंगे. बताए गए तरीके दूसरी ओर, GPUBuffer "अनबफ़र किए गए" होते हैं. इसका मतलब है कि वे अपने-आप काम करते हैं उसी समय उन्हें ट्रिगर किया जाएगा.

जीपीयू कमांड एन्कोडर मिलने के बाद, copyEncoder.copyBufferToBuffer() को कॉल करें जैसा कि नीचे दिखाया गया है. आखिर में, copyEncoder.finish() पर कॉल करके, कोड में बदलने के निर्देशों को पूरा करें और सबमिट करें वे जीपीयू डिवाइस कमांड क्यू में भेज दिए जाते हैं. हैंडलिंग की ज़िम्मेदारी सूची में है आर्ग्युमेंट के तौर पर, जीपीयू निर्देशों का इस्तेमाल करके, device.queue.submit() से सबमिट किए गए सबमिशन. यह आपके कलेक्शन में सेव किए गए सभी कमांड को, क्रम से लागू करेगा.

// Encode commands for copying buffer to buffer.
const copyEncoder = device.createCommandEncoder();
copyEncoder.copyBufferToBuffer(
  gpuWriteBuffer /* source buffer */,
  0 /* source offset */,
  gpuReadBuffer /* destination buffer */,
  0 /* destination offset */,
  4 /* size */
);

// Submit copy commands.
const copyCommands = copyEncoder.finish();
device.queue.submit([copyCommands]);

इस समय, जीपीयू सूची के निर्देश भेजे जा चुके हैं, लेकिन ज़रूरी नहीं है कि इन्हें चलाया जाए. दूसरे जीपीयू बफ़र को पढ़ने के लिए, gpuReadBuffer.mapAsync() को इसके साथ कॉल करें GPUMapMode.READ. यह प्रॉमिस देता है, जो जीपीयू बफ़र होने पर रिज़ॉल्व हो जाएगा मैप किया गया. इसके बाद, gpuReadBuffer.getMappedRange() के साथ मैप की गई रेंज पाएं सभी जीपीयू कमांड के बाद, इसमें वही वैल्यू होती हैं जो पहले जीपीयू बफ़र में थीं लागू किए गए हैं.

// Read buffer.
await gpuReadBuffer.mapAsync(GPUMapMode.READ);
const copyArrayBuffer = gpuReadBuffer.getMappedRange();
console.log(new Uint8Array(copyArrayBuffer));

आप इस नमूने को आज़मा सकते हैं.

कम शब्दों में कहें, तो बफ़र मेमोरी ऑपरेशन के बारे में आपको ये बातें ध्यान में रखनी चाहिए:

  • डिवाइस की सूची को सबमिट करने के लिए, जीपीयू बफ़र को मैप से हटाना होगा.
  • मैप किए जाने पर, जीपीयू बफ़र को JavaScript में पढ़ा और लिखा जा सकता है.
  • जीपीयू बफ़र तब मैप किए जाते हैं, जब mapAsync() और createBuffer() mappedAtCreation को 'सही' पर सेट किया गया है.

शेडर प्रोग्रामिंग

जीपीयू पर चल रहे ऐसे प्रोग्राम जो सिर्फ़ कंप्यूटेशन (हिसाब लगाना) करते हैं और ड्रॉ नहीं करते त्रिभुजों को कंप्यूट शेडर कहा जाता है. उन्हें एक साथ सैकड़ों और एक साथ काम करने वाले जीपीयू कोर (जो सीपीयू कोर से छोटे होते हैं) डेटा शामिल है. WebGPU में इनके इनपुट और आउटपुट बफ़र होते हैं.

WebGPU में कंप्यूट शेडर के इस्तेमाल का उदाहरण देने के लिए, हम मैट्रिक्स के साथ चलाएंगे गुणा, मशीन लर्निंग में एक सामान्य एल्गोरिदम नीचे दिया गया है.

मैट्रिक्स गुणा का डायग्राम
मैट्रिक्स मल्टीप्लिकेशन डायग्राम

संक्षेप में, यहां बताया गया है कि हम क्या करने जा रहे हैं:

  1. तीन जीपीयू बफ़र बनाएं (मैट्रिक्स के गुणा करने के लिए दो और दो जीपीयू के लिए एक नतीजे का मैट्रिक्स)
  2. कंप्यूट शेडर के लिए इनपुट और आउटपुट के बारे में बताएं
  3. कंप्यूट शेडर कोड को कंपाइल करें
  4. कंप्यूट पाइपलाइन सेट अप करना
  5. कोड में बदले गए निर्देशों को बैच में, जीपीयू पर सबमिट करें
  6. नतीजे मैट्रिक्स जीपीयू बफ़र का नतीजा पढ़ें

जीपीयू बफ़र बनाना

सरलता के लिए, आव्यूहों को फ़्लोटिंग की सूची के रूप में दिखाया जाएगा पॉइंट नंबर. पहला एलिमेंट पंक्तियों की संख्या है और दूसरा एलिमेंट कॉलम की संख्या, और बाकी आव्यूह की असल संख्या होती है.

JavaScript में मैट्रिक्स को आसान तरीके से दिखाया गया है और गणितीय संकेतन (नोटेशन) में इसके बराबर की सामग्री दी गई है
JavaScript में, मैट्रिक्स को आसान तरीके से दिखाया गया है और मैथमैटिक नोटेशन में इसके बराबर की कोई वैल्यू दी गई है

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

const adapter = await navigator.gpu.requestAdapter();
if (!adapter) { return; }
const device = await adapter.requestDevice();


// First Matrix

const firstMatrix = new Float32Array([
  2 /* rows */, 4 /* columns */,
  1, 2, 3, 4,
  5, 6, 7, 8
]);

const gpuBufferFirstMatrix = device.createBuffer({
  mappedAtCreation: true,
  size: firstMatrix.byteLength,
  usage: GPUBufferUsage.STORAGE,
});
const arrayBufferFirstMatrix = gpuBufferFirstMatrix.getMappedRange();
new Float32Array(arrayBufferFirstMatrix).set(firstMatrix);
gpuBufferFirstMatrix.unmap();


// Second Matrix

const secondMatrix = new Float32Array([
  4 /* rows */, 2 /* columns */,
  1, 2,
  3, 4,
  5, 6,
  7, 8
]);

const gpuBufferSecondMatrix = device.createBuffer({
  mappedAtCreation: true,
  size: secondMatrix.byteLength,
  usage: GPUBufferUsage.STORAGE,
});
const arrayBufferSecondMatrix = gpuBufferSecondMatrix.getMappedRange();
new Float32Array(arrayBufferSecondMatrix).set(secondMatrix);
gpuBufferSecondMatrix.unmap();


// Result Matrix

const resultMatrixBufferSize = Float32Array.BYTES_PER_ELEMENT * (2 + firstMatrix[0] * secondMatrix[1]);
const resultMatrixBuffer = device.createBuffer({
  size: resultMatrixBufferSize,
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
});

ग्रुप लेआउट और बाइंड ग्रुप

बाइंड ग्रुप लेआउट और बाइंड ग्रुप का सिद्धांत, खास तौर पर WebGPU के लिए है. बाइंड ग्रुप लेआउट, किसी शेडर से उम्मीद के मुताबिक इनपुट/आउटपुट इंटरफ़ेस को तय करता है. बाइंड ग्रुप, किसी शेडर के असल इनपुट/आउटपुट डेटा को दिखाता है.

नीचे दिए गए उदाहरण में, बाइंड ग्रुप लेआउट के लिए रीड ओनली स्टोरेज बफ़र की ज़रूरत है कंप्यूट शेडर के लिए, नंबर वाली एंट्री बाइंडिंग 0, 1, और 2 पर स्टोरेज बफ़र. दूसरी ओर, इस बाइंड ग्रुप लेआउट के लिए तय किया गया बाइंड ग्रुप, असोसिएट हो जाता है एंट्री के लिए जीपीयू बफ़र: बाइंडिंग 0 के लिए gpuBufferFirstMatrix, gpuBufferSecondMatrix से बाइंडिंग 1 और resultMatrixBuffer से लिंक करने वाला 2.

const bindGroupLayout = device.createBindGroupLayout({
  entries: [
    {
      binding: 0,
      visibility: GPUShaderStage.COMPUTE,
      buffer: {
        type: "read-only-storage"
      }
    },
    {
      binding: 1,
      visibility: GPUShaderStage.COMPUTE,
      buffer: {
        type: "read-only-storage"
      }
    },
    {
      binding: 2,
      visibility: GPUShaderStage.COMPUTE,
      buffer: {
        type: "storage"
      }
    }
  ]
});

const bindGroup = device.createBindGroup({
  layout: bindGroupLayout,
  entries: [
    {
      binding: 0,
      resource: {
        buffer: gpuBufferFirstMatrix
      }
    },
    {
      binding: 1,
      resource: {
        buffer: gpuBufferSecondMatrix
      }
    },
    {
      binding: 2,
      resource: {
        buffer: resultMatrixBuffer
      }
    }
  ]
});

कंप्यूट शेडर कोड

आव्यूहों को गुणा करने के लिए कंप्यूट शेडर कोड WGSL में लिखा गया है, WebGPU शेडर लैंग्वेज, जिसका SPIR-V में अनुवाद करना बहुत आसान है. इसके बिना इसके बारे में विस्तार से जानने के लिए, आपको नीचे दिए गए तीन स्टोरेज बफ़र की जानकारी मिलेगी var<storage> के साथ. प्रोग्राम firstMatrix और secondMatrix का इस्तेमाल इस तौर पर करेगा इनपुट और resultMatrix को आउटपुट के तौर पर शामिल करें.

ध्यान दें कि हर स्टोरेज बफ़र में binding सजावट का इस्तेमाल किया गया है, जो यही इंडेक्स, बाइंड ग्रुप लेआउट और बाइंड ग्रुप में बताया गया है.

const shaderModule = device.createShaderModule({
  code: `
    struct Matrix {
      size : vec2f,
      numbers: array<f32>,
    }

    @group(0) @binding(0) var<storage, read> firstMatrix : Matrix;
    @group(0) @binding(1) var<storage, read> secondMatrix : Matrix;
    @group(0) @binding(2) var<storage, read_write> resultMatrix : Matrix;

    @compute @workgroup_size(8, 8)
    fn main(@builtin(global_invocation_id) global_id : vec3u) {
      // Guard against out-of-bounds work group sizes
      if (global_id.x >= u32(firstMatrix.size.x) || global_id.y >= u32(secondMatrix.size.y)) {
        return;
      }

      resultMatrix.size = vec2(firstMatrix.size.x, secondMatrix.size.y);

      let resultCell = vec2(global_id.x, global_id.y);
      var result = 0.0;
      for (var i = 0u; i < u32(firstMatrix.size.y); i = i + 1u) {
        let a = i + resultCell.x * u32(firstMatrix.size.y);
        let b = resultCell.y + i * u32(secondMatrix.size.y);
        result = result + firstMatrix.numbers[a] * secondMatrix.numbers[b];
      }

      let index = resultCell.y + resultCell.x * u32(secondMatrix.size.y);
      resultMatrix.numbers[index] = result;
    }
  `
});

पाइपलाइन सेटअप करना

कंप्यूट पाइपलाइन वह ऑब्जेक्ट है जो असल में कंप्यूट ऑपरेशन की जानकारी देता है प्रदर्शन करने जा रहे हैं. device.createComputePipeline() पर कॉल करके इसे बनाएं. इसमें दो आर्ग्युमेंट इस्तेमाल किए जाते हैं: पहले बनाया गया बाइंड ग्रुप लेआउट और दूसरा कंप्यूट हमारे कंप्यूट शेडर के एंट्री पॉइंट (main WGSL फ़ंक्शन) को तय करने वाला स्टेज और device.createShaderModule() की मदद से बनाया गया असल कंप्यूट शेडर मॉड्यूल भी देखें.

const computePipeline = device.createComputePipeline({
  layout: device.createPipelineLayout({
    bindGroupLayouts: [bindGroupLayout]
  }),
  compute: {
    module: shaderModule,
    entryPoint: "main"
  }
});

निर्देशों को सबमिट किया जा रहा है

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

प्रोग्राम किया जा सकने वाला कंप्यूट पास एन्कोडर, इसके साथ शुरू करें commandEncoder.beginComputePass(). हम इसका इस्तेमाल जीपीयू कमांड को कोड में बदलने के लिए करेंगे जो मैट्रिक्स गुणा करेगा. इसकी पाइपलाइन सेट करें इंडेक्स 0 पर passEncoder.setPipeline(computePipeline) और इसका बाइंड ग्रुप passEncoder.setBindGroup(0, bindGroup). इंडेक्स 0, WGSL कोड में group(0) सजावट.

अब हम बात करते हैं कि जीपीयू पर यह कंप्यूट शेडर कैसे काम करेगा. हमारे इसका मकसद नतीजों के मैट्रिक्स के हर सेल के लिए इस प्रोग्राम को साथ-साथ लागू करना है, सिलसिलेवार तरीके से. उदाहरण के लिए, 16 x 32 साइज़ वाले मैट्रिक्स को कोड में बदलने के लिए चलाने का निर्देश देते हुए, @workgroup_size(8, 8) पर passEncoder.dispatchWorkgroups(2, 4) या passEncoder.dispatchWorkgroups(16 / 8, 32 / 8). पहला आर्ग्युमेंट "x" पहला डाइमेंशन है, दूसरा "y" दूसरा डाइमेंशन है. और सबसे नया "z" हो तीसरा डाइमेंशन 1 है, क्योंकि हमें यहां इसकी ज़रूरत नहीं है. जीपीयू कंप्यूट की दुनिया में, डेटा के किसी सेट पर कर्नेल फ़ंक्शन को एक्ज़ीक्यूट करने के लिए, किसी निर्देश को कोड में बदलने के तरीके को डिस्पैच करना कहा जाता है.

नतीजों वाले हर मैट्रिक्स सेल को साथ-साथ चलाया जाता है
नतीजे वाले हर मैट्रिक्स सेल को साथ-साथ चलाया जाता है

हमारे WGSL में हमारे कंप्यूट शेडर के लिए वर्कग्रुप ग्रिड का साइज़ (8, 8) है कोड. इस वजह से, "x" और "y" जो कि पहला मैट्रिक्स और दूसरे मैट्रिक्स के कॉलम की संख्या को भाग दिया जाएगा 8 तक. इसकी मदद से, अब हम कंप्यूट कॉल डिस्पैच कर सकते हैं. passEncoder.dispatchWorkgroups(firstMatrix[0] / 8, secondMatrix[1] / 8). कॉन्टेंट बनाने चलाने के लिए वर्कग्रुप ग्रिड की संख्या dispatchWorkgroups() है.

जैसा कि ऊपर दी गई ड्रॉइंग में दिखाया गया है, हर शेडर के पास builtin(global_invocation_id) ऑब्जेक्ट का इस्तेमाल, यह जानने के लिए किया जाएगा कि कौनसा नतीजा गणना करने के लिए मैट्रिक्स सेल.

const commandEncoder = device.createCommandEncoder();

const passEncoder = commandEncoder.beginComputePass();
passEncoder.setPipeline(computePipeline);
passEncoder.setBindGroup(0, bindGroup);
const workgroupCountX = Math.ceil(firstMatrix[0] / 8);
const workgroupCountY = Math.ceil(secondMatrix[1] / 8);
passEncoder.dispatchWorkgroups(workgroupCountX, workgroupCountY);
passEncoder.end();

कंप्यूट पास एन्कोडर को बंद करने के लिए, passEncoder.end() पर कॉल करें. इसके बाद, गंतव्य के रूप में उपयोग करने के लिए जीपीयू बफ़र, जिसके साथ परिणाम मैट्रिक्स बफ़र का इस्तेमाल किया जाता है copyBufferToBuffer. अंत में, एन्कोडिंग कमांड को इसके साथ समाप्त करें copyEncoder.finish() और उन्हें कॉल करके जीपीयू डिवाइस की सूची में सबमिट करें जीपीयू कमांड से device.queue.submit().

// Get a GPU buffer for reading in an unmapped state.
const gpuReadBuffer = device.createBuffer({
  size: resultMatrixBufferSize,
  usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
});

// Encode commands for copying buffer to buffer.
commandEncoder.copyBufferToBuffer(
  resultMatrixBuffer /* source buffer */,
  0 /* source offset */,
  gpuReadBuffer /* destination buffer */,
  0 /* destination offset */,
  resultMatrixBufferSize /* size */
);

// Submit GPU commands.
const gpuCommands = commandEncoder.finish();
device.queue.submit([gpuCommands]);

नतीजे का मैट्रिक्स पढ़ें

परिणाम मैट्रिक्स को पढ़ना gpuReadBuffer.mapAsync() को कॉल करने जितना आसान है GPUMapMode.READ और वापस आने वाले प्रॉमिस के समाधान का इंतज़ार किया जा रहा है. इससे पता चलता है कि जीपीयू बफ़र को अब मैप किया गया है. इसके बाद, मैप किया गया gpuReadBuffer.getMappedRange() की रेंज.

आव्यूह (मैट्रिक्स) को गुणा करने का नतीजा
मैट्रिक्स को गुणा करने का नतीजा

हमारे कोड में, DevTools JavaScript कंसोल में लॉग इन करने पर मिला नतीजा "2, 2, 50, 60, 114, 140".

// Read buffer.
await gpuReadBuffer.mapAsync(GPUMapMode.READ);
const arrayBuffer = gpuReadBuffer.getMappedRange();
console.log(new Float32Array(arrayBuffer));

बधाई हो! आपने कर लिया. आप सैंपल के साथ वीडियो देख सकते हैं.

एक आख़िरी तरकी

अपने कोड को पढ़ने में आसान बनाने का एक तरीक़ा है कि आप आसान बाइंड ग्रुप का अनुमान लगाने के लिए, कंप्यूट पाइपलाइन का getBindGroupLayout तरीका शेडर मॉड्यूल से लिया गया लेआउट. यह ट्रिक कस्टम बाइंड ग्रुप लेआउट और अपने कंप्यूट में पाइपलाइन लेआउट की जानकारी देना दिया गया है.

पिछले सैंपल के लिए getBindGroupLayout का इलस्ट्रेशन उपलब्ध है.

 const computePipeline = device.createComputePipeline({
-  layout: device.createPipelineLayout({
-    bindGroupLayouts: [bindGroupLayout]
-  }),
   compute: {
-// Bind group layout and bind group
- const bindGroupLayout = device.createBindGroupLayout({
-   entries: [
-     {
-       binding: 0,
-       visibility: GPUShaderStage.COMPUTE,
-       buffer: {
-         type: "read-only-storage"
-       }
-     },
-     {
-       binding: 1,
-       visibility: GPUShaderStage.COMPUTE,
-       buffer: {
-         type: "read-only-storage"
-       }
-     },
-     {
-       binding: 2,
-       visibility: GPUShaderStage.COMPUTE,
-       buffer: {
-         type: "storage"
-       }
-     }
-   ]
- });
+// Bind group
  const bindGroup = device.createBindGroup({
-  layout: bindGroupLayout,
+  layout: computePipeline.getBindGroupLayout(0 /* index */),
   entries: [

परफ़ॉर्मेंस के नतीजे

इसलिए, किसी जीपीयू पर चलने वाला मैट्रिक्स गुणन, किसी जीपीयू पर चलने की तुलना में कैसा होता है सीपीयू? यह पता लगाने के लिए, मैंने वह प्रोग्राम लिखा है जिसके बारे में अभी-अभी सीपीयू के बारे में बताया गया है. और जैसे भी आप कर सकते हैं नीचे दिए गए ग्राफ़ में देखें, तो जीपीयू की पूरी ताकत का इस्तेमाल करना एक आसान विकल्प लगता है जब मैट्रिक्स का साइज़ 256 x 256 से ज़्यादा हो.

जीपीयू बनाम सीपीयू बेंचमार्क
जीपीयू बनाम सीपीयू बेंचमार्क

यह लेख WebGPU के बारे में ज़्यादा जानने के मेरे सफ़र की शुरुआत भर है. और उम्मीद करें जल्द ही, जीपीयू Compute के बारे में ज़्यादा जानकारी दी जाएगी. साथ ही, रेंडरिंग के तरीके के बारे में भी बताया जाएगा (कैनवस, टेक्स्चर, सैंपलर) WebGPU में काम करता है.