הפוסט הזה בוחן את ה-WebGPU API הניסיוני באמצעות דוגמאות ו להתחיל לבצע חישובים מקבילים של נתונים באמצעות ה-GPU.
רקע
כפי שאולי כבר ידוע לך, יחידת העיבוד הגרפי (GPU) היא במחשב שהתמחה במקור בעיבוד גרפי. עם זאת, ב-10 השנים האחרונות, התפתחה שמאפשרת למפתחים להטמיע סוגים רבים של אלגוריתמים לעבד גרפיקה תלת-ממדית, תוך ניצול הארכיטקטורה הייחודית של GPU. היכולות האלה נקראות GPU Compute, והשימוש ב-GPU מעבד עזר למחשוב מדעי לשימוש כללי נקרא 'לשימוש כללי'. תכנות GPU (GPGPU).
GPU Compute תרם באופן משמעותי לפריחה האחרונה של למידת המכונה, רשתות נוירונים קונבולציה ומודלים אחרים יכולים לנצל כדי לפעול בצורה יעילה יותר במעבדי GPU. עם פלטפורמת האינטרנט הנוכחית ל-W3C אין יכולות מחשוב של GPU, ו-"GPU for the Web" קבוצת קהילה היא תכנון ממשק API שיחשוף את ממשקי ה-API המודרניים של GPU הזמינים ברוב מכשירים נוכחיים. ה-API הזה נקרא WebGPU.
WebGPU הוא ממשק API ברמה נמוכה, כמו WebGL. היא עוצמתית מאוד ודי מילונית, שתראו. אבל זה בסדר. מה שאנחנו מחפשים הוא ביצועים.
במאמר הזה אתמקד בחלק של GPU Compute ב-WebGPU, האמת היא שאנחנו רק מרקדים, כדי שתוכלו להתחיל לשחק שלו. אני אתעמק בנושא ואסביר על עיבוד של WebGPU (לוח הציור, טקסטורה, וכו') במאמרים הבאים.
גישה ל-GPU
הגישה ל-GPU קלה ב-WebGPU. אנחנו מתקשרים אל navigator.gpu.requestAdapter()
מחזירה הבטחת JavaScript שתפתור באופן אסינכרוני עם GPU
עם מתאם בלבד. המתאם הזה הוא כרטיס המסך. אפשר לשלב ביניהם
(באותו צ'יפ כמו המעבד) או דיסקרטי (בדרך כלל כרטיס PCIe גדול יותר
אך צורכת יותר חשמל).
אחרי שמוצאים את מתאם ה-GPU, צריך להתקשר אל adapter.requestDevice()
כדי לקבל הבטחה
שתפתרו את הבעיה במכשיר GPU שבו תשתמשו כדי לבצע חישובי GPU.
const adapter = await navigator.gpu.requestAdapter();
if (!adapter) { return; }
const device = await adapter.requestDevice();
שתי הפונקציות משתמשות באפשרויות שמאפשרות לפרט את מתאם המתח (העדפת חשמל) ואת המכשיר (תוספים, מגבלות) הרצויים. עבור כדי לפשט את הפשטות, נשתמש באפשרויות ברירת המחדל במאמר הזה.
כתיבת זיכרון מאגר נתונים זמני
בואו נראה איך להשתמש ב-JavaScript כדי לכתוב נתונים בזיכרון עבור ה-GPU. הזה התהליך אינו פשוט בגלל המודל 'ארגז חול' שמשמש בדפדפנים אחרים.
הדוגמה הבאה מראה איך לכתוב ארבעה בייטים במאגר נתונים זמני ונגיש
מה-GPU. הפונקציה קוראת לפונקציה device.createBuffer()
, שגודלה
מאגר הנתונים הזמני והשימוש בו. למרות שדגל השימוש GPUBufferUsage.MAP_WRITE
הוא
לא נדרשת לשיחה הספציפית הזו, בואו נבהיר שאנחנו רוצים לכתוב
למאגר הנתונים הזמני. התוצאה היא אובייקט של מאגר נתונים זמני של GPU שממופה בזמן היצירה,
mappedAtCreation
מוגדר כ-True. לאחר מכן, מאגר הנתונים הבינאריים הגולמי המשויך יכול
מאוחזר על ידי קריאה למאגר הנתונים הזמני של ה-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 ואנחנו רוצים להעתיק אותו ל
מאגר נתונים זמני של GPU, נדרש דגל שימוש חדש GPUBufferUsage.COPY_SRC
. השנייה
מאגר הנתונים הזמני של ה-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, צריך לבצע קריאה ל-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
שמוגדרת כ-True נקראת.
תכנות Shader
תוכנות שרצות ב-GPU שמבצעות רק חישובים (ולא שרטטו משולשים) נקראים תוכנות הצללה למחשוב. הן מבוצעות במקביל על ידי מאות של ליבות GPU (קטנות מליבות המעבד (CPU)) שפועלות יחד כדי לעבד . הקלט והפלט שלהם משמשים במאגרי נתונים זמניים ב-WebGPU.
כדי להמחיש את השימוש בתוכנות הצללה למחשוב ב-WebGPU, נשחק עם מטריצות אתיקה של כפל, אלגוריתם נפוץ בלמידת מכונה שמתואר בהמשך.
בקצרה, זה מה שנעשה:
- יוצרים שלושה מאגרי נתונים זמניים של GPU (שניים כדי להכפיל את המטריצות ואחת בשביל מטריצת התוצאות)
- תיאור הקלט והפלט של תוכנת ההצללה למחשוב
- הידור של הקוד של תוכנת ההצללה (shader) המחשוב
- הגדרת צינור עיבוד נתונים של מחשוב
- שליחה באצווה של פקודות מקודדות ל-GPU
- קריאת מאגר הנתונים הזמני של מטריצת התוצאות של ה-GPU
יצירת מאגרי GPU
כדי לשמור על פשטות, המטריצות מיוצגות כרשימה של צפים ומספרי נקודות. הרכיב הראשון הוא מספר השורות, והרכיב השני מספר העמודות, והשאר הוא המספרים בפועל של המטריצה.
שלושת מאגרי הנתונים הזמניים של ה-GPU הם מאגרי אחסון זמניים, כי אנחנו צריכים לאחסן ולאחזר נתונים
את תוכנת ההצללה למחשוב. זו הסיבה לכך שהסימונים לשימוש במאגר הנתונים הזמני של GPU כוללים
GPUBufferUsage.STORAGE
לכולם. דגל השימוש במטריצת התוצאות כולל גם
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
}
}
]
});
חישוב של קוד של תוכנת ההצללה (shader)
קוד ההצללה למחשוב להכפלת מטריצות כתוב ב-WGSL,
WebGPU Shader Language, שניתן לתרגום באופן טריוויאלי ל-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()
.
נדרשים שני ארגומנטים: פריסת קבוצת הקישור שיצרנו קודם, ו
שלב המגדיר את נקודת הכניסה של תוכנת ההצללה (shader) שלנו (פונקציית WGSL main
)
והמודול של תוכנת ההצללה (shader) המחשוב בפועל שנוצר באמצעות device.createShaderModule()
.
const computePipeline = device.createComputePipeline({
layout: device.createPipelineLayout({
bindGroupLayouts: [bindGroupLayout]
}),
compute: {
module: shaderModule,
entryPoint: "main"
}
});
שליחת פקודות
אחרי יצירת קבוצת קישור באמצעות שלושת מאגרי ה-GPU והמחשוב שלנו עם פריסת קבוצה של קשרים, זה הזמן להשתמש בהם.
בואו נתחיל מקודד של מעבר מחשוב שניתן לתכנות
commandEncoder.beginComputePass()
נשתמש בזה כדי לקודד פקודות GPU
שתבצעו את ההכפלה של המטריצה. מגדירים את צינור עיבוד הנתונים באמצעות
passEncoder.setPipeline(computePipeline)
וקבוצת הקישור שלו באינדקס 0 עם
passEncoder.setBindGroup(0, bindGroup)
. האינדקס 0 תואם
עיטור group(0)
בקוד WGSL.
עכשיו נדבר על האופן שבו תוכנת ההצללה למחשוב תרוץ על ה-GPU. שלנו
הוא להפעיל את התוכנית הזו במקביל לכל תא במטריצת התוצאות,
שלב אחרי שלב. למשל, כשמטריצת תוצאות בגודל 16 על 32 צריכה לקודד
פקודת הביצוע, ב-@workgroup_size(8, 8)
, נקרא
passEncoder.dispatchWorkgroups(2, 4)
או passEncoder.dispatchWorkgroups(16 / 8, 32 / 8)
.
הארגומנט הראשון 'x' הוא המאפיין הראשון, השני, 'y' הוא המאפיין השני,
והפקודה האחרונה היא 'z' הוא המאפיין השלישי שברירת המחדל שלו היא 1, כי אנחנו לא צריכים אותו כאן.
בעולם המחשוב של GPU, קידוד פקודה להפעלת פונקציית ליבה בקבוצת נתונים נקרא 'שליחת נתונים'.
גודל הרשת של קבוצת העבודה בתוכנת ההצללה (shader) המחשוב שלנו הוא (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();
כדי לסיים את המקודד של מעבר המחשוב, צריך לבצע קריאה ל-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()
.
בקוד שלנו, התוצאה שמתועדת במסוף 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 להרצה שלה על מעבד (CPU)? כדי לגלות זאת, כתבתי את התוכנית שתוארה לגבי מעבד (CPU). וככל שאפשר בתרשים שלמטה, השימוש בעוצמה המלאה של ה-GPU נראה כמו בחירה מובנת מאליה כשהגודל של המטריצות גדול מ-256 על 256.
המאמר הזה היה רק תחילת המסע שלי עם התנסות ב-WebGPU. יש עוד ציפייה בקרוב נוסיף עוד ניתוחים מעמיקים לגבי GPU Compute והסבר על תהליך העיבוד. (בד ציור, טקסטורה, דגול) פועל ב-WebGPU.