Bài đăng này giới thiệu về API WebGPU thử nghiệm thông qua các ví dụ và thông tin trợ giúp bạn bắt đầu thực hiện các phép tính song song dữ liệu bằng GPU.
Thông tin khái quát
Như bạn đã biết, Bộ xử lý đồ hoạ (GPU) là một bộ xử lý điện tử hệ thống con trong một máy tính ban đầu chuyên xử lý đồ hoạ. Tuy nhiên, trong 10 năm qua, nền tảng này đã phát triển theo hướng 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ỉ hiển thị đồ hoạ 3D trong khi vẫn tận dụng được kiến trúc độc đáo của GPU. Những khả năng này được gọi là Điện toán GPU và sử dụng GPU làm bộ đồng xử lý cho điện toán khoa học đa năng được gọi là bộ đồng xử lý Lập trình GPU (GPGPU).
Đ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 để chạy hiệu quả hơn trên GPU. Với Nền tảng web hiện tại thiếu khả năng Điện toán GPU, "GPU cho Web" của W3C Nhóm cộng đồng đang thiết kế một API để hiển thị các API GPU hiện đại có sẵn trên hầu hết thiết bị hiện tại. API này được gọi là WebGPU.
WebGPU là một API cấp thấp, chẳng hạn như WebGL. Công cụ này rất mạnh mẽ và chi tiết, bạn sẽ thấy. Nhưng không sao cả. Điều chúng tôi mong đợi là hiệu suất.
Trong bài viết này, tôi sẽ tập trung vào phần Điện toán GPU của WebGPU và để Thật lòng mà nói, tôi chỉ đang sơ sài để bạn có thể bắt đầu chơi trên của bạn. Tôi sẽ đi sâu hơn vào 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
Trong WebGPU, bạn có thể dễ dàng truy cập vào GPU. Đang gọi cho navigator.gpu.requestAdapter()
trả về lời hứa JavaScript sẽ phân giải không đồng bộ với GPU
bộ chuyển đổi. Hãy xem bộ điều hợp này giống như một thẻ đồ hoạ. Bạn có thể tích hợp giải pháp này
(trên cùng một chip với CPU) hoặc rời rạc (thường là thẻ PCIe có
hiệu quả cao nhưng tốn nhiều năng lượng hơn).
Sau khi bạn có bộ điều hợp GPU, hãy gọi adapter.requestDevice()
để nhận lời hứa
sẽ phân giải bằng thiết bị GPU mà bạn sẽ sử dụng để tính toán GPU.
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) { return; }
const device = await adapter.requestDevice();
Cả hai chức năng này đều có các tuỳ chọn cho phép bạn xác định cụ thể về loại bộ sạc (lựa chọn ưu tiên về nguồn điện) và thiết bị (tiện ích, giới hạn) mà bạn muốn. Đối với để đơn giản hoá, chúng tôi 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. Chiến dịch này không đơn giản vì mô hình hộp cát dùng trong môi trường web hiện đại trình duyệt.
Ví dụ bên dưới cho bạn thấy cách ghi 4 byte vào bộ nhớ vùng đệm có thể truy cập
từ GPU. Phương thức này gọi device.createBuffer()
để lấy kích thước của
và việc sử dụng bộ đệm đó. Mặc dù cờ sử dụng GPUBufferUsage.MAP_WRITE
là
không bắt buộc đối với cuộc gọi này, hãy thể hiện rõ ràng rằng chúng ta muốn viết
vào bộ đệm này. Điều này dẫn đến một đối tượng vùng đệm GPU được ánh xạ khi tạo nhờ vào
Đã đặt mappedAtCreation
thành true. Sau đó, bộ đệ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 là quen thuộc nếu bạn đã chơi với ArrayBuffer
; sử dụng
TypedArray
rồi 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à
dữ liệu đó có thể truy cập được bằng cách đọc/ghi qua JavaScript. Để GPU có thể truy cập vào dữ liệu đó,
phải chưa được ánh xạ, việc này đơn giản như gọi gpuBuffer.unmap()
.
Cần có khái niệm về việc được ánh xạ/chưa được ánh xạ để ngăn tình huống tương tranh trong đó GPU và bộ nhớ truy cập CPU cùng một lúc.
Đọc bộ nhớ đệm
Bây giờ, hãy xem cách sao chép một vùng đệm GPU sang một vùng đệm GPU khác rồi đọc lại vùng đệm đó.
Vì chúng ta đang viết vào vùng đệm GPU đầu tiên và muốn sao chép nó vào một giây
Vùng đệm GPU, bắt buộc phải có một cờ sử dụng mới GPUBufferUsage.COPY_SRC
. Thứ hai
Lần này vùng đệm GPU được tạo ở trạng thái chưa được liên kết bằng
device.createBuffer()
. Cờ sử dụng của lớp này là GPUBufferUsage.COPY_DST |
GPUBufferUsage.MAP_READ
vì nó sẽ được dùng làm đích đến của GPU đầu tiên
vùng đệm và đọ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 của GPU đều được thực thi
một cách không đồng bộ. Đây là lý do có một danh sách các lệnh GPU được tạo và gửi đi
theo lô khi cần. Trong WebGPU, bộ mã hoá lệnh GPU được trả về bởi
device.createCommandEncoder()
là đối tượng JavaScript tạo một loạt
"đã lưu vào bộ đệm" các lệnh được gửi đến GPU vào một thời điểm nào đó. Các phương thức trên
Mặt khác, GPUBuffer
"không được lưu vào bộ đệm", nghĩa là thực thi nguyên tử
vào thời điểm chúng được gọi.
Sau khi bạn có bộ mã hoá lệnh GPU, hãy gọi copyEncoder.copyBufferToBuffer()
như minh hoạ bên dưới để thêm lệnh này vào hàng đợi lệnh để thực thi sau.
Cuối cùng, hãy hoàn tất các lệnh mã hoá bằng cách gọi copyEncoder.finish()
rồi gửi
chúng vào hàng đợi lệnh của thiết bị GPU. Hàng đợi chịu trách nhiệm xử lý
lượt gửi được thực hiện 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 hàng đợi GPU đã được gửi nhưng chưa chắc đã thực thi.
Để đọc vùng đệm GPU thứ hai, hãy gọi gpuReadBuffer.mapAsync()
bằng
GPUMapMode.READ
Hàm này trả về một lời hứa sẽ phân giải khi vùng đệm GPU được
được ánh xạ. Sau đó, hãy lấy dải ô được ánh xạ bằng gpuReadBuffer.getMappedRange()
mà
chứa cùng giá trị với vùng đệm GPU đầu tiên sau khi tất cả các lệnh GPU được xếp 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ề hoạt động của bộ nhớ đệm:
- Vùng đệm GPU phải không được liên kết thì mớ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.
- Vùng đệm GPU được liên kết khi
mapAsync()
vàcreateBuffer()
vớimappedAtCreation
được đặt thành true sẽ được gọi.
Lập trình đổ bóng
Các chương trình chạy trên GPU chỉ thực hiện phép tính (và không vẽ) tam giác) được gọi là chương trình đổ bóng điện toán. Các yêu cầu này được thực thi song song hàng trăm lõi GPU (nhỏ hơn lõi CPU) hoạt động cùng nhau để xử lý . Đầu vào và đầu ra của chúng là vùng đệm trong WebGPU.
Để minh hoạ việc sử dụng chương trình đổ bóng điện toán trong WebGPU, chúng ta sẽ thử nghiệm ma trận phép nhân, một thuật toán phổ biến trong công nghệ học máy được minh hoạ dưới đây.
Tóm lại, sau đây là những việc chúng ta sẽ làm:
- Tạo ba vùng đệm GPU (2 vùng cho ma trận nhân và một vùng đệm cho ma trận 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á cho GPU
- Đọc bộ đệm GPU ma trận kết quả
Tạo vùng đệm GPU
Để cho đơn giản, ma trận sẽ được biểu diễn dưới dạng danh sách các hàm biến số điểm. Phần tử đầu tiên là số lượng hàng, phần tử thứ hai là số lượng hàng 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 đường liên kết đó. Cờ sử dụng ma trận kết quả cũng có
GPUBufferUsage.COPY_SRC
vì tệp này sẽ được sao chép vào một vùng đệm khác cho
đọc sau khi tất cả các lệnh hàng đợi GPU đã đượ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à nhóm liên kết
Khái niệm về bố cục nhóm liên kết và nhóm liên kết là dành riêng cho WebGPU. Ràng buộc A bố cục nhóm 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 biểu thị dữ liệu đầu vào/đầu ra thực tế của 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 có hai vùng đệm lưu trữ chỉ đọc tại
các liên kết mục nhập được đánh số 0
, 1
và vùng đệm lưu trữ tại 2
dành 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, liên kết
Vùng đệm GPU cho các mục nhập: gpuBufferFirstMatrix
đến 0
liên kết,
gpuBufferSecondMatrix
với liên kết 1
và resultMatrixBuffer
với liên kết
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
}
}
]
});
Tính toán mã chương trình đổ bóng
Mã đổ bóng điện toán để nhân ma trận được viết bằng WGSL,
Ngôn ngữ trong chương trình đổ bóng WebGPU, có thể dịch dễ dàng sang SPIR-V. Không có
bên dưới là 3 vùng đệm lưu trữ đã xác định
cùng với var<storage>
. Chương trình sẽ sử dụng firstMatrix
và secondMatrix
làm
đầu vào và resultMatrix
làm đầu ra.
Lưu ý rằng mỗi vùng đệm lưu trữ 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à các 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ả thao tác điện toán
mà chúng tôi sẽ thực hiện. Hãy tạo lớp này bằng cách gọi device.createComputePipeline()
.
Có hai đối số: bố cục nhóm liên kết mà chúng ta đã tạo trước đó và một điện toán
giai đoạn xác định điểm truy cập của chương trình đổ bóng điện toán (hàm WGSL main
)
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 thực thể nhóm liên kết bằng 3 vùng đệm GPU và một điện toán quy trình có bố cục nhóm liên kết, đã đến lúc sử dụng chúng.
Hãy bắt đầu một bộ mã hoá thẻ tính toán có thể lập trình bằng
commandEncoder.beginComputePass()
. Chúng ta 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 nhóm này tại chỉ mục 0 với
passEncoder.setBindGroup(0, bindGroup)
Chỉ số 0 tương ứng với
Trang trí group(0)
trong mã WGSL.
Bây giờ, hãy cùng nói về cách chương trình đổ bóng điện toán này sẽ chạy trên GPU.
mục tiêu là thực thi chương trình này song song với từng ô của ma trận kết quả,
từng bước. Chẳng hạn, đối với ma trận kết quả có kích thước 16 x 32, để 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à chiều đầu tiên, chiều thứ hai là "y" là phương diện thứ hai,
và đồng hồ hẹn giờ mới nhất là "z" là phương diện thứ ba mặc định là 1 vì chúng tôi không cần ở đây.
Trong thế giới điện toán GPU, việc mã hoá một lệnh để thực thi một 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 WGSL
. Do đó, "x" và "y" lần lượt 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
chậm nhất là 8 giờ. Cùng với đó, giờ đây chúng ta có thể gửi một lệnh gọi điện toán bằng
passEncoder.dispatchWorkgroups(firstMatrix[0] / 8, secondMatrix[1] / 8)
. Chiến lược phát hành đĩa đơn
số lượng lưới nhóm công việc cần chạy là các đối số dispatchWorkgroups()
.
Như trong hình 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)
sẽ được dùng để biết kết quả nào
để 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
Vùng đệm GPU dùng làm đích đến sao chép vùng đệm ma trận kết quả bằng
copyBufferToBuffer
. Cuối cùng, hãy hoàn tất các lệnh mã hoá bằng
copyEncoder.finish()
rồi gửi các thiết bị đó đến hàng đợi thiết bị GPU bằng cách gọi
device.queue.submit()
bằng 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ả dễ dàng như gọi gpuReadBuffer.mapAsync()
bằng
GPUMapMode.READ
và đang chờ giải quyết lời hứa trả lại hàng. Thông báo này cho biết
vùng đệm GPU hiện đã được ánh xạ. Tại thời điểm này, có thể lấy được bản đồ
dải ô bằng gpuReadBuffer.getMappedRange()
.
Trong đoạn mã của chúng ta, kết quả được ghi lại trong bảng điều khiển JavaScript của Công cụ cho nhà phát triển 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ể thử nghiệm với đoạn nhạc.
Mẹo cuối cùng
Một cách để giúp mã của bạn dễ đọc hơn là sử dụng tiện ích
Phương thức getBindGroupLayout
của quy trình tính toán để suy ra nhóm liên kết
qua mô-đun chương trình đổ bóng. Với thủ thuật này, bạn không cần phải tạo
bố cục nhóm liên kết tuỳ chỉnh và chỉ định bố cục quy trình trong tính toán của bạn
như bạn có thể thấy dưới đây.
Hình minh hoạ getBindGroupLayout
cho mẫu trước đó có sẵn.
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 GPU như thế nào CPU? Để 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ể hãy xem trong biểu đồ bên dưới, việc sử dụng toàn bộ sức mạnh của GPU có vẻ 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 256 x 256.
Bài viết này chỉ là bước khởi đầu trong hành trình khám phá WebGPU của tôi. Kỳ vọng nhiều hơn các bài viết sẽ sớm cung cấp thông tin chuyên sâu hơn về Điện toán GPU và cách kết xuất hình ảnh (canvas, texture, sampler) hoạt động trong WebGPU.