โพสต์นี้จะอธิบายข้อมูลเกี่ยวกับ WebGPU API เวอร์ชันทดลองผ่านตัวอย่างและช่วย คุณเริ่มต้นคำนวณข้อมูลแบบขนานโดยใช้ GPU
ข้อมูลเบื้องต้น
ตามที่คุณอาจทราบแล้ว หน่วยประมวลผลกราฟิก (GPU) เป็น ระบบย่อยภายในคอมพิวเตอร์ที่ก่อนหน้านี้มีความเชี่ยวชาญสำหรับการประมวลผล กราฟิก อย่างไรก็ตาม ในช่วง 10 ปีที่ผ่านมา แพลตฟอร์มนี้ได้มีการพัฒนาไปสู่ ช่วยให้นักพัฒนาซอฟต์แวร์สามารถติดตั้งใช้งานอัลกอริทึมได้หลายประเภท ไม่ใช่แค่ แสดงผลกราฟิก 3 มิติ และใช้ประโยชน์จากสถาปัตยกรรมที่มี GPU ความสามารถเหล่านี้เรียกว่าการประมวลผล GPU และใช้ GPU โปรเซสเซอร์ร่วมสำหรับการประมวลผลทางวิทยาศาสตร์ที่มีวัตถุประสงค์ทั่วไปเรียกว่า "วัตถุประสงค์ทั่วไป" การเขียนโปรแกรม GPU (GPGPU)
GPU Compute มีส่วนอย่างมากต่อการเติบโตของแมชชีนเลิร์นนิงเมื่อเร็วๆ นี้ เนื่องจากโครงข่ายประสาทแบบคอนโวลูชัน (Convolution) และโมเดลอื่นๆ สามารถใช้ประโยชน์จาก เพื่อให้ทำงานได้อย่างมีประสิทธิภาพบน GPU ด้วยแพลตฟอร์มเว็บปัจจุบัน ขาดความสามารถของ GPU Compute, "GPU สำหรับเว็บ" ของ W3C กลุ่มชุมชน กำลังออกแบบ API เพื่อเผยให้เห็นถึง GPU API ที่ทันสมัยซึ่งใช้งานได้ อุปกรณ์ปัจจุบัน API นี้มีชื่อว่า WebGPU
WebGPU คือ API ระดับต่ำ เช่น WebGL เครื่องมือนี้มีประสิทธิภาพมากและมีรายละเอียดเพียงพอ ก็จะเห็น แต่ไม่เป็นไร สิ่งที่เราต้องการคือประสิทธิภาพ
ในบทความนี้ ผมจะโฟกัสที่ส่วน GPU Compute ของ WebGPU และเพื่อเป็นการ บอกตรงๆ ว่าฉันเพียงแค่ต้องการให้คุณลองเล่นบน ของตัวเอง ฉันจะเจาะลึกมากขึ้นเกี่ยวกับการแสดงภาพ WebGPU (Canvas, พื้นผิว ฯลฯ) ในบทความต่อไป
เข้าถึง GPU
การเข้าถึง GPU สามารถทำได้ง่ายใน WebGPU กำลังโทรหา navigator.gpu.requestAdapter()
แสดงผลสัญญา JavaScript ที่จะแปลง GPU เป็นแบบไม่พร้อมกัน
อะแดปเตอร์ ให้คิดว่าอะแดปเตอร์นี้เป็นการ์ดกราฟิก โดยจะผสานรวมก็ได้
(บนชิปเดียวกันกับ CPU) หรือแยกกัน (โดยปกติจะเป็นการ์ด PCI ที่มีราคามากกว่า
แต่มีประสิทธิภาพสูงกว่า)
เมื่อมีอะแดปเตอร์ GPU แล้ว ให้โทรหา adapter.requestDevice()
เพื่อรับคำมั่นสัญญา
ที่จะแก้ปัญหาด้วยอุปกรณ์ GPU ซึ่งคุณจะใช้ในการคำนวณ GPU
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) { return; }
const device = await adapter.requestDevice();
ทั้งสองฟังก์ชันมีตัวเลือกที่ช่วยให้คุณเจาะจงเกี่ยวกับ อะแดปเตอร์ (ค่ากำหนดพลังงาน) และอุปกรณ์ (ส่วนขยาย ขีดจำกัด) ที่คุณต้องการ สำหรับ เพื่อให้เข้าใจง่าย เราจะใช้ตัวเลือกเริ่มต้นในบทความนี้
เขียนหน่วยความจำบัฟเฟอร์
มาดูวิธีใช้ JavaScript เพื่อเขียนข้อมูลลงในหน่วยความจำสำหรับ GPU กัน ช่วงเวลานี้ ขั้นตอนนี้ไม่ตรงไปตรงมาเนื่องจากโมเดลแซนด์บ็อกซ์ที่ใช้ในเว็บสมัยใหม่ เบราว์เซอร์
ตัวอย่างด้านล่างแสดงวิธีเขียน 4 ไบต์เพื่อบัฟเฟอร์ที่เข้าถึงได้
จาก GPU จะเรียก device.createBuffer()
ซึ่งจะปรับขนาดของ
และการใช้งานบัฟเฟอร์ แม้ว่าแฟล็กการใช้งาน GPUBufferUsage.MAP_WRITE
จะเป็น
สำหรับการเรียกครั้งนี้ โปรดอธิบายให้ชัดเจนว่าเราต้องการเขียน
ลงในบัฟเฟอร์นี้ ซึ่งส่งผลให้ออบเจ็กต์บัฟเฟอร์ GPU ที่แมปขณะสร้างด้วย
ตั้งค่า mappedAtCreation
เป็น "จริง" จากนั้นบัฟเฟอร์ข้อมูลไบนารีข้อมูลดิบที่เกี่ยวข้องสามารถ
ให้ดึงได้โดยการเรียกเมธอดบัฟเฟอร์ GPU 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]);
ณ จุดนี้ ระบบจะแมปบัฟเฟอร์ GPU ซึ่งหมายความว่า CPU จะเป็นเจ้าของและ
สามารถเข้าถึงได้ในการอ่าน/เขียนจาก JavaScript เพื่อให้ GPU สามารถเข้าถึงได้
จะต้องยกเลิกการแมป ซึ่งง่ายพอๆ กับการเรียก gpuBuffer.unmap()
จำเป็นต้องมีแนวคิดการแมป/ไม่ได้แมปเพื่อป้องกันเงื่อนไขการแข่งขันที่ GPU และหน่วยความจำเข้าถึง CPU พร้อมกัน
อ่านหน่วยความจำของบัฟเฟอร์
ตอนนี้มาดูวิธีคัดลอกบัฟเฟอร์ GPU ไปยังบัฟเฟอร์ GPU อื่นและอ่านต่อ
เนื่องจากเรากำลังเขียนข้อมูลในบัฟเฟอร์ GPU แรกและต้องการคัดลอกไปยังวินาทีที่ 2
บัฟเฟอร์ GPU ต้องมี Flag การใช้งานใหม่ GPUBufferUsage.COPY_SRC
องค์ประกอบที่ 2
ขณะนี้บัฟเฟอร์ GPU สร้างขึ้นในสถานะที่ไม่ได้แมปด้วย
device.createBuffer()
แฟล็กการใช้งานของโมเดลคือ GPUBufferUsage.COPY_DST |
GPUBufferUsage.MAP_READ
เนื่องจากจะใช้เป็นปลายทางของ GPU แรก
บัฟเฟอร์และอ่านใน JavaScript เมื่อดำเนินการตามคำสั่งคัดลอก GPU แล้ว
// 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
});
เนื่องจาก GPU เป็นโปรเซสเซอร์ร่วมอิสระ จึงมีการดำเนินการคำสั่ง GPU ทั้งหมด
แบบไม่พร้อมกัน นี่คือเหตุผลที่มีรายการคำสั่ง GPU ที่สร้างและส่งเข้ามา
กลุ่มได้เมื่อต้องการ ใน WebGPU ตัวเข้ารหัสคำสั่ง GPU แสดงผลโดย
device.createCommandEncoder()
คือออบเจ็กต์ JavaScript ที่สร้างชุดของ
"บัฟเฟอร์" ซึ่งจะส่งไปยัง GPU ณ จุดใดจุดหนึ่ง เมธอดใน
ในทางกลับกัน GPUBuffer
นั้น "ไม่ได้รับการบัฟเฟอร์" ซึ่งหมายความว่าจะประมวลผลแบบอะตอม
ในเวลาที่มีการโทร
เมื่อคุณมีโปรแกรมเปลี่ยนไฟล์คำสั่ง GPU แล้วให้เรียกใช้ copyEncoder.copyBufferToBuffer()
ตามที่แสดงด้านล่างเพื่อเพิ่มคำสั่งนี้ลงในคิวคำสั่งสำหรับการดำเนินการในภายหลัง
ขั้นตอนสุดท้าย สิ้นสุดคำสั่งการเข้ารหัสโดยเรียกใช้ copyEncoder.finish()
แล้วส่ง
ลงในคิวคำสั่งของอุปกรณ์ GPU คิวมีหน้าที่จัดการ
การส่งที่เสร็จสิ้นผ่าน device.queue.submit()
โดยมีคำสั่ง GPU เป็นอาร์กิวเมนต์
ซึ่งจะเรียกใช้คำสั่งทั้งหมดที่จัดเก็บไว้ในอาร์เรย์ตามลำดับ
// 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]);
ณ จุดนี้ มีการส่งคำสั่งคิว GPU ไปแล้ว แต่ไม่จำเป็นต้องดำเนินการ
หากต้องการอ่านบัฟเฟอร์ GPU ที่ 2 ให้เรียกใช้ gpuReadBuffer.mapAsync()
ด้วย
GPUMapMode.READ
เครื่องมือนี้ส่งคืนสัญญาที่จะได้รับการแก้ไขเมื่อบัฟเฟอร์ GPU
ทำแผนที่แล้ว จากนั้นดูช่วงที่แมปด้วย gpuReadBuffer.getMappedRange()
ซึ่ง
มีค่าเดียวกันกับบัฟเฟอร์ GPU แรกเมื่อคำสั่ง GPU อยู่ในคิวทั้งหมด
ได้ดำเนินการแล้ว
// Read buffer.
await gpuReadBuffer.mapAsync(GPUMapMode.READ);
const copyArrayBuffer = gpuReadBuffer.getMappedRange();
console.log(new Uint8Array(copyArrayBuffer));
กล่าวโดยสรุปคือสิ่งที่ต้องจดจำเกี่ยวกับการดำเนินการหน่วยความจำบัฟเฟอร์:
- คุณต้องยกเลิกการแมปบัฟเฟอร์ GPU เพื่อนำไปใช้ในการส่งคิวอุปกรณ์
- เมื่อแมปแล้ว จะอ่านและเขียนบัฟเฟอร์ GPU ด้วย JavaScript ได้
- ระบบจะแมปบัฟเฟอร์ GPU เมื่อ
mapAsync()
และcreateBuffer()
กับ ระบบจะเรียกใช้mappedAtCreation
ที่ตั้งค่าเป็น "จริง"
การเขียนโปรแกรม Shader
โปรแกรมที่ทำงานบน GPU ซึ่งประมวลผลเฉพาะข้อมูล (และไม่ต้องวาด) สามเหลี่ยม) เรียกว่าเครื่องมือเงาการประมวลผล การดำเนินการดังกล่าวพร้อมกันหลายร้อยคน ของแกน GPU (ซึ่งเล็กกว่าแกน CPU) ที่ทำงานร่วมกันเพื่อขัดขวาง อินพุตและเอาต์พุตเป็นบัฟเฟอร์ใน WebGPU
เพื่อแสดงให้เห็นถึงการใช้ตัวปรับแสงเงาการประมวลผลใน WebGPU เราจะลองใช้เมทริกซ์ ซึ่งเป็นอัลกอริทึมที่พบบ่อยในแมชชีนเลิร์นนิงตามที่แสดงด้านล่าง
![แผนภาพการคูณเมทริกซ์](https://developer.chrome.com/static/docs/capabilities/web-apis/gpu-compute/image/matrix-multiplication-dia-18ebcce8f6445.jpeg?hl=th)
กล่าวโดยสรุปคือ สิ่งที่เราจะทำมีดังต่อไปนี้
- สร้างบัฟเฟอร์ GPU 3 รายการ (2 รายการสำหรับเมทริกซ์สำหรับคูณและอีก 1 รายการสำหรับ เมทริกซ์ผลลัพธ์)
- อธิบายอินพุตและเอาต์พุตสำหรับตัวปรับแสงเงาประมวลผล
- คอมไพล์โค้ดตัวปรับแสงประมวลผล
- ตั้งค่าไปป์ไลน์การประมวลผล
- ส่งคำสั่งที่เข้ารหัสเป็นกลุ่มไปยัง GPU
- อ่านบัฟเฟอร์ GPU ของเมทริกซ์ผลลัพธ์
การสร้างบัฟเฟอร์ GPU
เพื่อให้เข้าใจง่าย เมทริกซ์จะแสดงเป็นรายการแบบลอย หมายเลขคะแนน องค์ประกอบแรกคือจำนวนแถว องค์ประกอบที่ 2 จำนวนคอลัมน์ และส่วนที่เหลือคือจำนวนจริงของเมทริกซ์
![การนำเสนอเมทริกซ์แบบง่ายๆ ใน JavaScript และสัญลักษณ์ทางคณิตศาสตร์ที่เทียบเท่า](https://developer.chrome.com/static/docs/capabilities/web-apis/gpu-compute/image/simple-representation-a-a54c48f6c0ddd.jpeg?hl=th)
บัฟเฟอร์ GPU ทั้ง 3 รายการเป็นบัฟเฟอร์พื้นที่เก็บข้อมูลเนื่องจากเราต้องจัดเก็บและดึงข้อมูล
ตัวปรับแสงเงาการประมวลผล สิ่งนี้อธิบายว่าทำไมแฟล็กการใช้บัฟเฟอร์ GPU มี
GPUBufferUsage.STORAGE
สำหรับทุกคน นอกจากนี้ Flag การใช้งานเมทริกซ์ผลลัพธ์ยังมี
GPUBufferUsage.COPY_SRC
เนื่องจากระบบจะคัดลอกไปยังบัฟเฟอร์อื่นสำหรับ
การอ่านเมื่อเรียกใช้คำสั่งของคิว GPU ทั้งหมดแล้ว
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
สำหรับตัวปรับเงาการประมวลผล
ในทางกลับกัน กลุ่มการเชื่อมโยงที่กำหนดสำหรับเลย์เอาต์ของกลุ่มการเชื่อมโยงนี้ การเชื่อมโยง
บัฟเฟอร์ GPU ไปยังรายการ: gpuBufferFirstMatrix
กับการเชื่อมโยง 0
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 Shader Language ซึ่งแปลได้เล็กน้อยเป็นภาษา SPIR-V ไม่มี
คุณจะดูรายละเอียดได้ที่ด้านล่างระหว่างบัฟเฟอร์พื้นที่เก็บข้อมูลทั้ง 3 รายการที่ระบุ
กับ 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()
ต้องใช้อาร์กิวเมนต์ 2 ตัว ได้แก่ เลย์เอาต์ของกลุ่มการเชื่อมโยงที่เราสร้างไว้ก่อนหน้านี้ และการประมวลผล
ขั้นตอนที่กำหนดจุดแรกเข้าของเครื่องมือปรับแสงเงาการประมวลผล (ฟังก์ชัน main
WGSL)
และโมดูลตัวปรับแสงเงาประมวลผลจริงที่สร้างขึ้นด้วย device.createShaderModule()
const computePipeline = device.createComputePipeline({
layout: device.createPipelineLayout({
bindGroupLayouts: [bindGroupLayout]
}),
compute: {
module: shaderModule,
entryPoint: "main"
}
});
การส่งคำสั่ง
หลังจากสร้างการเชื่อมโยงกลุ่มด้วยบัฟเฟอร์ GPU ทั้ง 3 รายการและการประมวลผล ไปป์ไลน์ที่มีเลย์เอาต์แบบเชื่อมโยงกับกลุ่ม ตอนนี้ก็ถึงเวลาใช้งานแล้ว
มาเริ่มโปรแกรมเปลี่ยนไฟล์สำหรับ Compute Pass ที่สามารถตั้งโปรแกรมได้ด้วย
commandEncoder.beginComputePass()
เราจะใช้สิ่งนี้เพื่อเข้ารหัสคำสั่ง GPU
ที่จะใช้คูณเมทริกซ์ ตั้งค่าไปป์ไลน์ด้วย
passEncoder.setPipeline(computePipeline)
และกลุ่มการเชื่อมโยงที่ดัชนี 0 กับ
passEncoder.setBindGroup(0, bindGroup)
ดัชนี 0 สอดคล้องกับ
การตกแต่ง group(0)
ในรหัส WGSL
ต่อไป เรามาดูวิธีที่ตัวปรับแสงเงาประมวลผลนี้ทำงานบน GPU กัน
คือการเรียกใช้โปรแกรมนี้แบบคู่ขนานกับเมทริกซ์ผลลัพธ์แต่ละเซลล์
ทีละขั้นตอน เช่น สำหรับเมทริกซ์ผลลัพธ์ขนาด 16 x 32 เพื่อเข้ารหัส
คำสั่งในการดำเนินการ ใน @workgroup_size(8, 8)
เราจะเรียก
passEncoder.dispatchWorkgroups(2, 4)
หรือpassEncoder.dispatchWorkgroups(16 / 8, 32 / 8)
อาร์กิวเมนต์แรก "x" มิติข้อมูลแรกคือมิติข้อมูลแรก มิติข้อมูลที่สองเป็น "y" คือมิติข้อมูลที่ 2
และอันล่าสุดคือ "z" คือมิติข้อมูลที่สามที่มีค่าเริ่มต้นเป็น 1 เนื่องจากเราไม่ต้องการข้อมูลนี้
ในโลกการประมวลผล GPU การเข้ารหัสคำสั่งเพื่อเรียกใช้ฟังก์ชันเคอร์เนลในชุดข้อมูลเรียกว่าการจ่ายงาน
![การดำเนินการแบบขนานสำหรับเซลล์เมทริกซ์ผลลัพธ์แต่ละเซลล์](https://developer.chrome.com/static/docs/capabilities/web-apis/gpu-compute/image/execution-parallel-each-6e443814da856.jpeg?hl=th)
ขนาดของตารางกริดกลุ่มงานสำหรับตัวปรับแสงเงาการประมวลผลคือ (8, 8)
ใน WGSL
โค้ด ด้วยเหตุนี้ "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();
หากต้องการเลิกใช้โปรแกรมเปลี่ยนไฟล์สำหรับ Compute Pass โปรดโทรหา passEncoder.end()
จากนั้นสร้าง
บัฟเฟอร์ GPU ที่จะใช้เป็นปลายทางในการคัดลอกบัฟเฟอร์เมทริกซ์ผลลัพธ์
copyBufferToBuffer
สุดท้าย จบคำสั่งการเข้ารหัสด้วย
copyEncoder.finish()
และส่งไปยังคิวอุปกรณ์ GPU โดยการเรียกใช้
device.queue.submit()
ด้วยคำสั่ง GPU
// 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
และกำลังรอสัญญาที่จะกลับมาแก้ไขซึ่งแสดงไว้ว่า
แมปบัฟเฟอร์ GPU แล้ว ณ จุดนี้ คุณสามารถที่จะทำแผนที่
ด้วย gpuReadBuffer.getMappedRange()
![ผลการคูณเมทริกซ์](https://developer.chrome.com/static/docs/capabilities/web-apis/gpu-compute/image/matrix-multiplication-res-c04b7fceb1ce8.jpeg?hl=th)
ในโค้ดของเรา ผลลัพธ์ที่บันทึกในคอนโซล 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: [
ผลการสืบค้นประสิทธิภาพ
แล้วการเรียกใช้การคูณเมทริกซ์บน GPU เป็นอย่างไรเมื่อเทียบกับการเรียกใช้การคูณเมทริกซ์บน GPU CPU เพื่อหาคำตอบ ฉันจึงเขียนโปรแกรมที่อธิบายสำหรับ CPU และอย่างที่คุณ ในกราฟด้านล่าง การใช้ GPU อย่างเต็มประสิทธิภาพดูเหมือนจะเป็นทางเลือกที่ชัดเจน เมื่อขนาดของเมทริกซ์มากกว่า 256 x 256
![การเปรียบเทียบ GPU เทียบกับ CPU](https://developer.chrome.com/static/docs/capabilities/web-apis/gpu-compute/image/gpu-vs-cpu-benchmark-391d7651d455d.jpeg?hl=th)
บทความนี้เป็นเพียงจุดเริ่มต้นในเส้นทางการสำรวจ WebGPU ของฉัน คาดว่าจะมีมากขึ้น เร็วๆ นี้ จะมีข้อมูลเชิงลึกเพิ่มเติมเกี่ยวกับ GPU Compute และวิธีการแสดงผล (Canvas, texture, Sampler) ทำงานใน WebGPU ได้