Bài đăng này khám phá API WebGPU thử nghiệm thông qua các ví dụ và giúp bạn bắt đầu thực hiện các phép tính song song với dữ liệu bằng GPU.
Thông tin khái quát
Như bạn có thể biết, Đơn vị xử lý đồ hoạ (GPU) là một hệ thống con điện tử trong máy tính, ban đầu được chuyên dùng để xử lý đồ hoạ. Tuy nhiên, trong 10 năm qua, API này đã phát triển theo hướng kiến trúc linh hoạt hơn, cho phép nhà phát triển triển khai nhiều loại thuật toán, không chỉ kết xuất đồ hoạ 3D, đồng thời tận dụng kiến trúc độc đáo của GPU. Các chức năng này được gọi là GPU Compute (Tính toán GPU) và việc sử dụng GPU làm bộ xử lý đồng thời cho mục đích tính toán khoa học chung được gọi là lập trình GPU (GPGPU) chung.
Điện toán GPU đã đóng góp đáng kể vào sự bùng nổ của công nghệ học máy trong thời gian gần đây, vì mạng nơron tích chập và các mô hình khác có thể tận dụng cấu trúc này để chạy hiệu quả hơn trên GPU. Vì Nền tảng web hiện tại thiếu tính năng Điện toán GPU, nên Nhóm cộng đồng "GPU cho web" của W3C đang thiết kế một API để hiển thị các API GPU hiện đại có trên hầu hết các thiết bị hiện tại. API này được gọi là WebGPU.
WebGPU là một API cấp thấp, giống như WebGL. Hàm này rất mạnh mẽ và khá dài dòng, như bạn sẽ thấy. Nhưng không sao cả. Chúng ta đang tìm kiếm hiệu suất.
Trong bài viết này, tôi sẽ tập trung vào phần GPU Compute của WebGPU và thành thật mà nói, tôi chỉ mới tìm hiểu sơ bộ để bạn có thể bắt đầu tự chơi. Tôi sẽ tìm hiểu sâu hơn và đề cập đến khả năng kết xuất WebGPU (canvas, texture, v.v.) trong các bài viết sắp tới.
Truy cập vào GPU
Bạn có thể dễ dàng truy cập vào GPU trong WebGPU. Việc gọi navigator.gpu.requestAdapter()
sẽ trả về một lời hứa JavaScript sẽ phân giải không đồng bộ với bộ chuyển đổi GPU. Hãy xem bộ điều hợp này giống như thẻ đồ hoạ. GPU có thể được tích hợp (trên cùng một khối với CPU) hoặc riêng biệt (thường là thẻ PCIe có hiệu suất cao hơn nhưng tiêu thụ nhiều điện năng hơn).
Sau khi bạn có bộ chuyển đổi GPU, hãy gọi adapter.requestDevice()
để nhận một lời hứa sẽ phân giải bằng một thiết bị GPU mà bạn sẽ sử dụng để thực hiện một số phép tính GPU.
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) { return; }
const device = await adapter.requestDevice();
Cả hai hàm này đều có các tuỳ chọn cho phép bạn chỉ định cụ thể loại sạc (lựa chọn ưu tiên về nguồn) và thiết bị (phần mở rộng, giới hạn) mà bạn muốn. Để đơn giản hoá, chúng ta sẽ sử dụng các tuỳ chọn mặc định trong bài viết này.
Ghi bộ nhớ đệm
Hãy xem cách sử dụng JavaScript để ghi dữ liệu vào bộ nhớ cho GPU. Quy trình này không đơn giản do mô hình hộp cát được sử dụng trong các trình duyệt web hiện đại.
Ví dụ bên dưới cho bạn biết cách ghi 4 byte vào bộ nhớ đệm có thể truy cập từ GPU. Hàm này gọi device.createBuffer()
để lấy kích thước của vùng đệm và mức sử dụng vùng đệm đó. Mặc dù cờ sử dụng GPUBufferUsage.MAP_WRITE
không bắt buộc đối với lệnh gọi cụ thể này, nhưng hãy nêu rõ rằng chúng ta muốn ghi vào vùng đệm này. Điều này dẫn đến việc đối tượng vùng đệm GPU được liên kết khi tạo nhờ mappedAtCreation
được đặt thành true. Sau đó, vùng đệm dữ liệu nhị phân thô được liên kết có thể được truy xuất bằng cách gọi phương thức vùng đệm GPU getMappedRange()
.
Việc ghi byte sẽ quen thuộc nếu bạn đã chơi với ArrayBuffer
; hãy sử dụng TypedArray
và sao chép các giá trị vào đó.
// 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]);
Tại thời điểm này, vùng đệm GPU được ánh xạ, có nghĩa là vùng đệm này thuộc sở hữu của CPU và có thể truy cập được khi đọc/ghi qua JavaScript. Để GPU có thể truy cập vào GPU, bạn phải huỷ liên kết GPU, việc này đơn giản như việc gọi gpuBuffer.unmap()
.
Bạn cần có khái niệm ánh xạ/không ánh xạ để ngăn chặn các điều kiện tương tranh trong đó GPU và CPU truy cập vào bộ nhớ cùng một lúc.
Đọc bộ nhớ đệm
Bây giờ, hãy xem cách sao chép vùng đệm GPU vào vùng đệm GPU khác và đọc lại vùng đệm đó.
Vì chúng ta đang ghi vào vùng đệm GPU đầu tiên và muốn sao chép vùng đệm đó vào vùng đệm GPU thứ hai, nên cần có một cờ sử dụng mới GPUBufferUsage.COPY_SRC
. Vùng đệm GPU thứ hai được tạo ở trạng thái chưa liên kết lần này bằng device.createBuffer()
. Cờ sử dụng của lớp này là GPUBufferUsage.COPY_DST |
GPUBufferUsage.MAP_READ
vì sẽ được dùng làm đích đến của vùng đệm GPU đầu tiên và được đọc trong JavaScript sau khi các lệnh sao chép GPU được thực thi.
// 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
});
Vì GPU là một bộ đồng xử lý độc lập, nên tất cả các lệnh GPU đều được thực thi không đồng bộ. Đó là lý do tại sao có một danh sách các lệnh GPU được tạo và gửi theo lô khi cần. Trong WebGPU, bộ mã hoá lệnh GPU do device.createCommandEncoder()
trả về là đối tượng JavaScript có chức năng tạo một loạt lệnh "đã lưu vào bộ đệm" và sẽ được gửi đến GPU vào một thời điểm nào đó. Mặt khác, các phương thức trên GPUBuffer
là "không được lưu vào vùng đệm", nghĩa là các phương thức này thực thi một cách nguyên tử tại thời điểm được gọi.
Sau khi bạn đã có bộ mã hoá lệnh GPU, hãy gọi copyEncoder.copyBufferToBuffer()
như trình bày dưới đây để thêm lệnh này vào hàng đợi lệnh nhằm thực thi sau.
Cuối cùng, hãy hoàn tất việc mã hoá các lệnh bằng cách gọi copyEncoder.finish()
và gửi các lệnh đó vào hàng đợi lệnh của thiết bị GPU. Hàng đợi chịu trách nhiệm xử lý các nội dung gửi qua device.queue.submit()
với các lệnh GPU làm đối số.
Thao tác này sẽ thực thi theo thứ tự tất cả các lệnh được lưu trữ trong mảng.
// 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]);
Tại thời điểm này, các lệnh trong hàng đợi GPU đã được gửi nhưng chưa chắc đã được thực thi.
Để đọc vùng đệm GPU thứ hai, hãy gọi gpuReadBuffer.mapAsync()
bằng GPUMapMode.READ
. Phương thức này trả về một lời hứa sẽ giải quyết khi vùng đệm GPU được ánh xạ. Sau đó, lấy dải ô được ánh xạ bằng gpuReadBuffer.getMappedRange()
chứa các giá trị giống như vùng đệm GPU đầu tiên sau khi tất cả các lệnh GPU trong hàng đợi đã được thực thi.
// Read buffer.
await gpuReadBuffer.mapAsync(GPUMapMode.READ);
const copyArrayBuffer = gpuReadBuffer.getMappedRange();
console.log(new Uint8Array(copyArrayBuffer));
Bạn có thể dùng thử mẫu này.
Tóm lại, sau đây là những điều bạn cần nhớ về các thao tác bộ nhớ đệm:
- Vùng đệm GPU phải không được ánh xạ lại để có thể dùng trong quá trình gửi hàng đợi thiết bị.
- Khi được ánh xạ, vùng đệm GPU có thể được đọc và ghi bằng JavaScript.
- Các vùng đệm GPU được liên kết khi
mapAsync()
vàcreateBuffer()
vớimappedAtCreation
được đặt thành true được gọi.
Lập trình chương trình đổ bóng
Các chương trình chạy trên GPU chỉ thực hiện các phép tính toán (và không vẽ tam giác) được gọi là chương trình đổ bóng điện toán. Các lệnh này được thực thi song song bởi hàng trăm lõi GPU (nhỏ hơn lõi CPU) hoạt động cùng nhau để xử lý dữ liệu. Đầu vào và đầu ra của chúng là vùng đệm trong WebGPU.
Để minh hoạ cách sử dụng chương trình đổ bóng điện toán trong WebGPU, chúng ta sẽ thử nghiệm với phép nhân ma trận, một thuật toán phổ biến trong học máy được minh hoạ bên dưới.
Tóm lại, chúng ta sẽ làm những việc sau:
- Tạo ba vùng đệm GPU (hai vùng đệm cho các ma trận nhân và một vùng đệm cho ma trận kết quả)
- Mô tả dữ liệu đầu vào và đầu ra cho chương trình đổ bóng điện toán
- Biên dịch mã chương trình đổ bóng điện toán
- Thiết lập quy trình điện toán
- Gửi hàng loạt các lệnh đã mã hoá đến GPU
- Đọc bộ đệm GPU ma trận kết quả
Tạo vùng đệm GPU
Để đơn giản, ma trận sẽ được biểu diễn dưới dạng danh sách các số dấu phẩy động. Phần tử đầu tiên là số hàng, phần tử thứ hai là số cột và phần còn lại là số thực của ma trận.
Ba vùng đệm GPU là vùng đệm lưu trữ vì chúng ta cần lưu trữ và truy xuất dữ liệu trong chương trình đổ bóng điện toán. Điều này giải thích tại sao cờ sử dụng vùng đệm GPU bao gồm GPUBufferUsage.STORAGE
cho tất cả các cờ này. Cờ sử dụng ma trận kết quả cũng có GPUBufferUsage.COPY_SRC
vì cờ này sẽ được sao chép vào một vùng đệm khác để đọc sau khi tất cả lệnh hàng đợi GPU đều được thực thi.
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
});
Liên kết bố cục nhóm và liên kết nhóm
Các khái niệm về bố cục liên kết nhóm và liên kết nhóm là dành riêng cho WebGPU. Bố cục nhóm liên kết xác định giao diện đầu vào/đầu ra mà chương trình đổ bóng dự kiến, trong khi nhóm liên kết đại diện cho dữ liệu đầu vào/đầu ra thực tế cho chương trình đổ bóng.
Trong ví dụ bên dưới, bố cục nhóm liên kết dự kiến sẽ có hai vùng đệm lưu trữ chỉ có thể đọc tại các liên kết mục được đánh số 0
, 1
và một vùng đệm lưu trữ tại 2
cho chương trình đổ bóng điện toán.
Mặt khác, nhóm liên kết được xác định cho bố cục nhóm liên kết này sẽ liên kết các vùng đệm GPU với các mục nhập: gpuBufferFirstMatrix
với liên kết 0
, gpuBufferSecondMatrix
với liên kết 1
và resultMatrixBuffer
với liên kết 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
}
}
]
});
Mã chương trình đổ bóng điện toán
Mã chương trình đổ bóng điện toán để nhân ma trận được viết bằng WGSL, ngôn ngữ chương trình đổ bóng WebGPU, có thể dễ dàng dịch sang SPIR-V. Không cần đi sâu vào chi tiết, bạn sẽ thấy bên dưới là 3 vùng đệm bộ nhớ được xác định bằng var<storage>
. Chương trình sẽ sử dụng firstMatrix
và secondMatrix
làm dữ liệu đầu vào và resultMatrix
làm dữ liệu đầu ra.
Xin lưu ý rằng mỗi vùng đệm bộ nhớ có một phần trang trí binding
được sử dụng tương ứng với cùng một chỉ mục được xác định trong bố cục nhóm liên kết và nhóm liên kết được khai báo ở trên.
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;
}
`
});
Thiết lập quy trình
Quy trình điện toán là đối tượng thực sự mô tả toán tử điện toán mà chúng ta sắp thực hiện. Hãy tạo tệp bằng cách gọi device.createComputePipeline()
.
Cần có 2 đối số: bố cục nhóm liên kết mà chúng ta đã tạo trước đó và giai đoạn tính toán xác định điểm truy cập của chương trình đổ bóng điện toán (hàm main
WGSL) và mô-đun chương trình đổ bóng điện toán thực tế được tạo bằng device.createShaderModule()
.
const computePipeline = device.createComputePipeline({
layout: device.createPipelineLayout({
bindGroupLayouts: [bindGroupLayout]
}),
compute: {
module: shaderModule,
entryPoint: "main"
}
});
Gửi lệnh
Sau khi tạo bản sao một nhóm liên kết bằng ba vùng đệm GPU và một quy trình điện toán có bố cục nhóm liên kết, đã đến lúc sử dụng các nhóm liên kết đó.
Hãy bắt đầu một bộ mã hoá thẻ thông hành điện toán có thể lập trình bằng commandEncoder.beginComputePass()
. Chúng ta sẽ sử dụng mã này để mã hoá các lệnh GPU sẽ thực hiện phép nhân ma trận. Thiết lập quy trình bằng passEncoder.setPipeline(computePipeline)
và nhóm liên kết của quy trình đó tại chỉ mục 0 bằng passEncoder.setBindGroup(0, bindGroup)
. Chỉ mục 0 tương ứng với trang trí group(0)
trong mã WGSL.
Bây giờ, hãy cùng tìm hiểu cách chương trình đổ bóng điện toán này sẽ chạy trên GPU. Mục tiêu của chúng ta là thực thi song song chương trình này cho từng ô của ma trận kết quả, từng bước một. Ví dụ: đối với ma trận kết quả có kích thước 16x32, để mã hoá lệnh thực thi, trên @workgroup_size(8, 8)
, chúng ta sẽ gọi passEncoder.dispatchWorkgroups(2, 4)
hoặc passEncoder.dispatchWorkgroups(16 / 8, 32 / 8)
.
Đối số đầu tiên "x" là phương diện đầu tiên, đối số thứ hai "y" là phương diện thứ hai và đối số mới nhất "z" là phương diện thứ ba mặc định là 1 vì chúng ta không cần phương diện này ở đây.
Trong thế giới điện toán GPU, việc mã hoá một lệnh để thực thi hàm hạt nhân trên một tập dữ liệu được gọi là điều phối.
Kích thước của lưới nhóm công việc cho chương trình đổ bóng điện toán là (8, 8)
trong mã WGSL. Do đó, "x" và "y" tương ứng là số hàng của ma trận đầu tiên và số cột của ma trận thứ hai sẽ được chia cho 8. Nhờ đó, giờ đây, chúng ta có thể điều phối lệnh gọi điện toán bằng passEncoder.dispatchWorkgroups(firstMatrix[0] / 8, secondMatrix[1] / 8)
. Số lượng lưới nhóm công việc cần chạy là đối số dispatchWorkgroups()
.
Như trong bản vẽ ở trên, mỗi chương trình đổ bóng sẽ có quyền truy cập vào một đối tượng builtin(global_invocation_id)
duy nhất. Đối tượng này sẽ được dùng để biết ô ma trận kết quả nào cần tính toán.
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();
Để kết thúc bộ mã hoá thẻ điện toán, hãy gọi passEncoder.end()
. Sau đó, hãy tạo một bộ nhớ đệm GPU để dùng làm đích đến sao chép bộ nhớ đệm ma trận kết quả bằng copyBufferToBuffer
. Cuối cùng, hãy hoàn tất việc mã hoá các lệnh bằng copyEncoder.finish()
và gửi các lệnh đó vào hàng đợi thiết bị GPU bằng cách gọi device.queue.submit()
với các lệnh 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]);
Đọc ma trận kết quả
Việc đọc ma trận kết quả cũng dễ dàng như gọi gpuReadBuffer.mapAsync()
bằng GPUMapMode.READ
và chờ lời hứa trả về được phân giải, cho biết vùng đệm GPU hiện đã được liên kết. Tại thời điểm này, bạn có thể lấy dải ô được liên kết bằng gpuReadBuffer.getMappedRange()
.
Trong mã của chúng ta, kết quả được ghi lại trong bảng điều khiển JavaScript của DevTools là "2, 2, 50, 60, 114, 140".
// Read buffer.
await gpuReadBuffer.mapAsync(GPUMapMode.READ);
const arrayBuffer = gpuReadBuffer.getMappedRange();
console.log(new Float32Array(arrayBuffer));
Xin chúc mừng! Bạn đã hoàn tất. Bạn có thể chơi với đoạn nhạc đó.
Mẹo cuối cùng
Một cách để giúp mã dễ đọc hơn là sử dụng phương thức getBindGroupLayout
tiện lợi của quy trình điện toán để xác định bố cục nhóm liên kết từ mô-đun chương trình đổ bóng. Thủ thuật này giúp bạn không cần tạo bố cục nhóm liên kết tuỳ chỉnh và chỉ định bố cục quy trình trong quy trình điện toán như bạn có thể thấy bên dưới.
Bạn có thể xem hình minh hoạ getBindGroupLayout
cho mẫu trước.
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: [
Kết quả về hiệu suất
Vậy việc chạy phép nhân ma trận trên GPU so với việc chạy trên CPU có gì khác biệt? Để tìm hiểu, tôi đã viết chương trình vừa được mô tả cho một CPU. Và như bạn có thể thấy trong biểu đồ bên dưới, việc sử dụng toàn bộ sức mạnh của GPU có vẻ như là một lựa chọn hiển nhiên khi kích thước của ma trận lớn hơn 256x256.
Bài viết này chỉ là bước khởi đầu của hành trình khám phá WebGPU. Chúng tôi sẽ sớm cung cấp thêm các bài viết chuyên sâu về GPU Compute và cách hoạt động của tính năng kết xuất (canvas, hoạ tiết, bộ lấy mẫu) trong WebGPU.