コードをメモ帳などのテキストエディタに貼り付け、ファイルを「index.html」などの拡張子が.htmlのファイルとして保存します。その後、保存したファイルをブラウザで開けば、コードが実行されます。
ショートストーリー: 「小さなGPUでの冒険」 GPU量子エンタングルメント
東京の繁華街に住む若いプログラマー、ケンジは、自分の小さなノートパソコンを使って驚くべき実験を行っていた。ケンジはコンピュータ科学に情熱を注ぎ、特にGPUを使った計算に興味を持っていた。今日は、自分の小さなGPUで「量子エンタングル状態」をシミュレートしようと決心していた。
「ビット列のすべての可能性をシミュレートしてみせる!」とケンジは意気込んでいた。彼は最近、量子ビットの概念に心を奪われ、ビットのすべての組み合わせが同時に重なり合う状態について学んでいた。この知識を基に、彼は自分のノートパソコンを使ってこのエンタングル状態を模倣するプログラムを書いた。
ケンジのノートパソコンには内蔵GPUが搭載されており、その性能を活かしてみたかった。彼はまず、ビット列を生成し、そのすべての組み合わせを計算するプログラムを書いた。これにより、ビットの状態がすべて重なり合っている様子を模擬することができる。
ケンジは画面を見つめながら、プログラムが開始されるのを待っていた。彼は少しの緊張と共に興奮も感じていた。プログラムが実行されると、ノートパソコンのGPUが大量のビットパターンを一気に計算し始めた。ケンジは自分の小さなGPUが、どれだけ高性能であるかを確かめたかった。
「もしうまくいけば、僕のGPUでも量子ビットのような状態を再現できるかもしれない」とケンジは思っていた。プログラムが完了するまでの間、彼は画面に映る進行状況を見守りながら、期待に胸を膨らませていた。
数分後、ケンジのノートパソコンの画面には結果が表示されていた。ビット列のすべての可能性がうまくシミュレートされており、エンタングル状態の模倣が成功していることがわかった。ケンジは自分の小さなGPUが大きな役割を果たしたことに感動し、達成感を感じていた。「すごい!僕のノートパソコンがこれほどの計算をこなせたなんて!」と喜びを表した。
ケンジは、この実験を通じて、自分の小さなGPUが意外にも強力であることを再確認し、科学と技術の広がりを実感した。彼は「明日はもっと複雑な問題に挑戦しよう」と心に決め、ベッドに入った。ケンジにとって、この実験は単なる計算の結果を超え、自分の夢に一歩近づくための貴重な経験となったのだった。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CPU vs GPU Bit Pattern Search</title>
<style>
canvas {
border: 1px solid black;
margin-top: 20px;
}
</style>
</head>
<body>
<h1>CPU vs GPU Bit Pattern Search</h1>
<div>
<button onclick="runComparison()">Run Comparison</button>
<canvas id="resultCanvas" width="800" height="600"></canvas>
</div>
<!-- TensorFlow.jsライブラリ -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@4.0.0"></script>
<script>
const canvas = document.getElementById('resultCanvas');
const ctx = canvas.getContext('2d');
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function printResult(text, x = 10, y = 20) {
ctx.fillText(text, x, y);
}
function generateBitPatterns(length) {
const numPatterns = Math.pow(2, length);
const patterns = [];
for (let i = 0; i < numPatterns; i++) {
patterns.push(i.toString(2).padStart(length, '0'));
}
return patterns;
}
async function findPatterns(bitLength, useGPU) {
const patterns = generateBitPatterns(bitLength);
const patternTensor = tf.tensor2d(patterns.map(p => p.split('').map(Number)), [patterns.length, bitLength]);
const start = performance.now();
// 簡単な計算プロセス: すべてのビットパターンの合計を計算する
await tf.tidy(() => {
const sum = patternTensor.sum().arraySync();
console.log(`Total Sum of Patterns: ${sum}`);
});
const end = performance.now();
return end - start;
}
async function runComparison() {
clearCanvas();
ctx.font = '16px Arial';
const bitLengths = [10, 12, 14, 16, 18];
let yPosition = 20;
for (const bitLength of bitLengths) {
// CPUでの計算時間
const cpuTime = await findPatterns(bitLength, false);
printResult(`Bit Length: ${bitLength} - CPU Time: ${cpuTime.toFixed(2)} ms`, 10, yPosition);
yPosition += 20;
// GPUでの計算時間
const gpuTime = await findPatterns(bitLength, true);
printResult(`Bit Length: ${bitLength} - GPU Time: ${gpuTime.toFixed(2)} ms`, 10, yPosition);
yPosition += 20;
}
}
// 初期メッセージをキャンバスに表示
ctx.font = '16px Arial';
printResult('Click "Run Comparison" to compare CPU and GPU times for different bit lengths.', 10, 20);
</script>
</body>
</html>
解説
ビットパターン生成 (generateBitPatterns 関数):
指定されたビット数に基づいて、すべてのビットパターンを生成します。
パターン検索 (findPatterns 関数):
指定されたビット数のパターンを生成し、TensorFlow.jsで計算します。
計算として、すべてのビットパターンの合計を求めます。
比較と表示 (runComparison 関数):
ビット数を10、12、14、16、18に設定し、それぞれのビット数でCPUとGPUの処理時間を計測します。
各ビット数に対する処理時間をキャンバスに表示します。
HTMLとCanvas:
「Run Comparison」ボタンをクリックすると、runComparison 関数が呼ばれ、各ビット数に対するCPUとGPUの処理時間が計測され、キャンバス上に表示されます。
ショートストーリー: 「プログラマと小さな自分だけのGPU」 GPUニューラルネットワークモデル
東京のとある小さなアパート。その一室に、ひとりのプログラマ、タクヤが住んでいた。彼の部屋は、モニターの明かりとキーボードの音だけが響く静かな空間だった。タクヤは毎晩遅くまでコードを書き続け、日々新しいアルゴリズムやモデルを試していた。
ある日、タクヤは最新のプロジェクトに取り組んでいた。彼は、CPUとGPUの処理速度を比較するための実験をしていた。通常の開発者は大型のデータセンターやクラウドを利用するが、タクヤは自分の手のひらサイズの小さなGPUデバイスにこだわりを持っていた。この小さなGPUは、彼が独自にカスタマイズしたもので、まるでパートナーのように信頼していた。
「さあ、今日も君の力を見せてくれ。」
タクヤはそう呟きながら、コードを実行する。まずは、シンプルなニューラルネットワークモデルを構築し、小さなデータセットを使ってCPUでの処理時間を測定した。モニターに表示された数字は悪くなかったが、タクヤは微かに眉をひそめた。
「悪くはないけど、やっぱりGPUの方が速いはずだ。」
次に彼は、小さなGPUにスイッチを切り替え、同じモデルを再度実行した。タクヤの心臓は少し高鳴った。この瞬間が好きだった。GPUが全力で計算を始め、わずか数秒で結果を返してくれるのだ。その瞬間、モニターに表示された数字は、CPUよりも明らかに短い時間を示していた。
「やっぱり、君はすごいな。」
タクヤは笑みを浮かべた。彼のGPUは、小さくてもそのパワーは本物だった。次に、彼はさらに大きなモデルでテストを行った。今回もCPUでの処理時間はそこそこだったが、GPUに切り替えると、まるで重い荷物を軽々と運び上げるように、スムーズに結果が出た。
「これで、クライアントも満足するだろう。」
タクヤはそう思いながら、結果をまとめた。だが、彼が本当に満足していたのは、プロジェクトの成功ではなく、自分だけの小さなGPUが期待以上の力を発揮してくれたことだった。
その夜、タクヤは珍しく早めに作業を終え、ベッドに横たわった。窓の外からは、東京の街の喧騒が微かに聞こえていたが、彼の心は静かだった。隣に置かれた小さなGPUが、まるで安らかな眠りについているように見えた。
「明日もまた、君の力を借りるよ。」
タクヤはそう言って目を閉じた。彼と彼の小さなGPUは、明日もまた、新しいコードを書き、より良い結果を目指して挑戦し続けるのだ。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CPU vs GPU Training Time Comparison</title>
<style>
canvas {
border: 1px solid black;
margin-top: 20px;
}
</style>
</head>
<body>
<h1>CPU vs GPU Training Time Comparison</h1>
<div>
<button onclick="runComparison()">Run Comparison</button>
<canvas id="resultCanvas" width="600" height="300"></canvas>
</div>
<!-- TensorFlow.jsライブラリ -->
<script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@4.0.0"></script>
<script>
const canvas = document.getElementById('resultCanvas');
const ctx = canvas.getContext('2d');
// キャンバスをクリアする関数
function clearCanvas() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
// 結果をキャンバスに描画する関数
function printResult(text, x = 10, y = 20) {
ctx.fillText(text, x, y);
}
// モデルをトレーニングする関数
async function trainModel(useGPU, layerSizes) {
// 簡単なデータセットを作成
const xs = tf.randomNormal([1000, 10]);
const ys = tf.randomNormal([1000, 1]);
// シンプルなニューラルネットワークを定義
const model = tf.sequential();
// 指定された層のサイズに基づいてモデルに層を追加
layerSizes.forEach((units, i) => {
model.add(tf.layers.dense({
units: units,
activation: 'relu',
inputShape: i === 0 ? [10] : undefined // 最初の層にはinputShapeを設定
}));
});
model.add(tf.layers.dense({units: 1})); // 出力層
// モデルのコンパイル
model.compile({optimizer: 'sgd', loss: 'meanSquaredError'});
// デバイス設定
const device = useGPU ? 'gpu' : 'cpu';
const start = performance.now();
// モデルをトレーニング
await tf.tidy(() => model.fit(xs, ys, {
epochs: 5,
batchSize: 32,
callbacks: {
onEpochEnd: (epoch, logs) => console.log(`Epoch ${epoch}: loss = ${logs.loss}`)
}
}));
const end = performance.now();
return end - start;
}
// CPUとGPUの比較を実行する関数
async function runComparison() {
clearCanvas();
ctx.font = '16px Arial';
// 小さいモデルサイズを定義(層の数とそれぞれの層のニューロン数)
const smallModelSize = [10, 10]; // 2層、各10ニューロン
// 大きいモデルサイズを定義
const largeModelSize = [50, 50, 50]; // 3層、各50ニューロン
// 小さいモデルでのCPUトレーニング時間
const smallCpuTime = await trainModel(false, smallModelSize);
printResult(`Small Model - CPU Training Time: ${smallCpuTime.toFixed(2)} ms`, 10, 40);
// 小さいモデルでのGPUトレーニング時間
const smallGpuTime = await trainModel(true, smallModelSize);
printResult(`Small Model - GPU Training Time: ${smallGpuTime.toFixed(2)} ms`, 10, 60);
// 大きいモデルでのCPUトレーニング時間
const largeCpuTime = await trainModel(false, largeModelSize);
printResult(`Large Model - CPU Training Time: ${largeCpuTime.toFixed(2)} ms`, 10, 100);
// 大きいモデルでのGPUトレーニング時間
const largeGpuTime = await trainModel(true, largeModelSize);
printResult(`Large Model - GPU Training Time: ${largeGpuTime.toFixed(2)} ms`, 10, 120);
}
// 初期メッセージをキャンバスに表示
ctx.font = '16px Arial';
printResult('Click "Run Comparison" to compare CPU and GPU training times.');
</script>
</body>
</html>
説明
モデルサイズの変更: モデルサイズを変えるために、layerSizesというパラメータを追加し、層ごとに異なる数のニューロンを指定しました。
smallModelSize: 2層のモデルで、各層に10ニューロンを持つ。
largeModelSize: 3層のモデルで、各層に50ニューロンを持つ。
結果の描画: 結果はキャンバスに描画され、CPUとGPUでのトレーニング時間が小さいモデルと大きいモデルのそれぞれについて表示されます。
参考。JavaScriptエンジンはブラウザごとに異なりますが、多くのモダンなJavaScriptエンジンは、JIT(Just-In-Time)コンパイルをサポートしています。JITコンパイルは、JavaScriptコードを実行時にネイティブコード(CPUが直接実行できる機械語)に変換する技術で、これによりコードの実行速度が大幅に向上します。
Google ChromeのJavaScriptエンジンであるV8は、非常に高度な最適化を行っています。これにより、例えばParticle Simulationのような計算集約型の処理でも、通常通りに書かれたJavaScriptコードが実行時に効率的に最適化されます。
V8エンジンの最適化について
V8エンジンは、JavaScriptコードのパフォーマンスを向上させるためにいくつかの重要な技術を用いています。以下はその一部です。
JITコンパイルとホットスポットの検出:
V8は、実行されるコードを監視し、特に頻繁に実行される部分(ホットスポット)を検出します。
ホットスポットと判断されたコードは、JITコンパイラによってネイティブコードに変換され、高速に実行されるようになります。
インライン化とデッドコード除去:
V8は、関数呼び出しをインライン化し、不要なコードを除去することで、ループや関数呼び出しのオーバーヘッドを削減します。これにより、パーティクルの軌道計算などがより効率的に行われます。
最適化コンパイラ:
V8には、コードをさらに最適化するための最適化コンパイラ(TurboFanなど)が含まれており、特定のパターンに基づいてコードをより効率的な形に再編成します。
ガベージコレクションの最適化:
メモリ管理もV8の重要な部分です。パーティクルシミュレーションのような動的なオブジェクト生成が頻繁に行われる環境では、ガベージコレクションがパフォーマンスに影響を与えることがありますが、V8はこれを効率的に処理するよう最適化されています。
Particle Simulationにおける最適化
パーティクルシミュレーションのようなケースでは、以下のような最適化が行われます。
頻繁なループの最適化: パーティクルの軌道計算などでループが多用される場合、V8はこれらのループを効率的に実行するためにインライン化やアンローリングを行います。
メモリ効率の向上: オブジェクトの生成と破棄が頻繁に行われる場合でも、V8のガベージコレクタが最適に動作し、メモリリークや不要なメモリ使用を防ぎます。
JITコンパイルの活用: シミュレーション中に頻繁に使用される計算式や関数がJITコンパイルされ、ネイティブコードに変換されて高速に実行されます。
結論
Google ChromeのV8エンジンは、非常に高度な最適化を行うため、通常通りにJavaScriptコードを書くだけで、そのコードが効率的に実行されることが期待できます。特にParticle Simulationのような計算負荷の高い処理でも、V8エンジンの最適化機能により、パフォーマンスが最大化されます。これは、パーティクルの軌道計算やシミュレーションロジックが最適に実行されることを意味します。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Particle System</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
<style>
body { margin: 0; }
canvas { display: block; }
</style>
</head>
<body>
<script>
let particles = [];
function setup() {
createCanvas(windowWidth, windowHeight);
noStroke();
}
function draw() {
background(0);
// 新しいパーティクルを追加
if (frameCount % 5 === 0) {
particles.push(new Particle(mouseX, mouseY));
}
// すべてのパーティクルを更新し、描画
for (let i = particles.length - 1; i >= 0; i--) {
particles[i].update();
particles[i].display();
// 画面外のパーティクルを削除
if (particles[i].isOutOfBounds()) {
particles.splice(i, 1);
}
}
}
class Particle {
constructor(x, y) {
this.x = x;
this.y = y;
this.xspeed = random(-2, 2);
this.yspeed = random(-2, 2);
this.lifespan = 255; // パーティクルの寿命
}
update() {
this.x += this.xspeed;
this.y += this.yspeed;
this.lifespan -= 2; // 寿命を減らす
}
display() {
fill(255, this.lifespan);
ellipse(this.x, this.y, 10);
}
isOutOfBounds() {
return this.lifespan <= 0 || this.x < 0 || this.x > width || this.y < 0 || this.y > height;
}
}
</script>
</body>
</html>
参考。TensorFlow.jsでは、多くの並列実行を可能にするメソッドが用意されています。これらのメソッドは、GPUを利用して計算を効率化し、大規模なデータ処理やトレーニングを迅速に行うのに役立ちます。以下は、TensorFlow.jsで用意されている主要な並列実行可能なメソッドの一覧です。
TensorFlow.jsの主要な並列実行メソッド
テンソルの演算:
tf.add(tensorA, tensorB): 2つのテンソルの要素ごとの加算。
tf.sub(tensorA, tensorB): 2つのテンソルの要素ごとの減算。
tf.mul(tensorA, tensorB): 2つのテンソルの要素ごとの乗算。
tf.div(tensorA, tensorB): 2つのテンソルの要素ごとの除算。
比較演算:
tf.equal(tensorA, tensorB): 2つのテンソルの要素ごとの等価比較。
tf.notEqual(tensorA, tensorB): 2つのテンソルの要素ごとの非等価比較。
tf.greater(tensorA, tensorB): 2つのテンソルの要素ごとの大なり比較。
tf.less(tensorA, tensorB): 2つのテンソルの要素ごとの小なり比較。
集約操作:
tf.sum(tensor, axis): 指定した軸に沿ってテンソルの合計を計算。
tf.mean(tensor, axis): 指定した軸に沿ってテンソルの平均を計算。
tf.max(tensor, axis): 指定した軸に沿ってテンソルの最大値を計算。
tf.min(tensor, axis): 指定した軸に沿ってテンソルの最小値を計算。
変換操作:
tf.transpose(tensor, perm): テンソルの次元を入れ替える。
tf.reshape(tensor, shape): テンソルの形状を変更する。
行列演算:
tf.matMul(tensorA, tensorB): 2つのテンソルの行列乗算。
tf.dot(tensorA, tensorB): 2つのテンソルのドット積(内積)。
その他の数学的操作:
tf.sqrt(tensor): テンソルの平方根を計算。
tf.exp(tensor): テンソルの指数関数を計算。
tf.log(tensor): テンソルの自然対数を計算。
tf.sin(tensor), tf.cos(tensor), tf.tan(tensor): 三角関数を計算。
TensorFlow.jsの関数の使い方
これらのメソッドは、TensorFlow.jsのテンソルに対して直感的に操作を行うためのものであり、内部でGPUを使用して並列処理を行うため、計算が高速に実行されます。使用方法は非常にシンプルで、テンソルを作成し、上記のメソッドを呼び出すだけで、GPUの力を活用した高速な計算を実現できます。
ショートストーリー: 「東京のプログラマと未知の計算世界」 はじめまして WebGPU
東京の夜は静かで、ビルの窓に反射するネオンの光が街を照らしていた。タクミは自宅のデスクに座り、コンピュータのモニターに集中していた。彼は一人のプログラマであり、その夜もまた新しい挑戦に取り組んでいた。テーマは「WebGPUを用いた行列計算」。タクミは、これまでよりも効率的な計算を実現するために、日々新しい技術に触れていた。
「さて、やってみようか。」
彼はキーボードを叩き、コードを実行する。WebGPUという新しい技術を駆使して、行列の積を計算するプログラムを開発していた。タクミの目の前には、4×4の行列がふたつ表示され、それぞれの要素がランダムに埋められていた。行列Aと行列B、それらを掛け合わせた結果が行列Cとして出力される。
タクミは、GPUがこの計算をどれだけ迅速に処理できるかを確認しようとしていた。従来のCPUでは、行列の積の計算は時間がかかることが多かったが、彼はこの新しい技術がそれをどれだけ改善できるかに興味を持っていた。
「行け!」
彼はプログラムを実行し、少し緊張しながら結果を待つ。数秒後、画面に表示されたのは計算結果の行列Cだった。だが、タクミの目を引いたのは、その横に表示された計算時間だった。
「たったの…0.02秒だって?!」
タクミは驚きを隠せなかった。たったの0.02秒。今までなら、このサイズの行列を計算するのにもっと時間がかかっていた。彼の心は興奮で高鳴っていた。小さな画面の向こう側で、彼のコードがどれだけ効率的に動いているかを感じることができた。
タクミは、これまでの努力が報われたように感じた。彼の手にした新しい技術は、まるで未知の領域への扉を開いたかのようだった。計算の世界は無限であり、その可能性を広げるために、彼は今後も挑戦を続けるだろう。
「次はもっと大きな行列で試してみようかな。」
タクミは自信に満ちた笑みを浮かべながら、新しいコードを書き始めた。東京の夜はまだ明けない。彼の挑戦もまた、終わることはなかった。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebGPU Matrix Multiplication</title>
</head>
<body>
<h1>WebGPU Matrix Multiplication</h1>
<pre id="output"></pre>
<script type="module">
async function initWebGPU() {
if (!navigator.gpu) {
console.log("WebGPU is not supported by your browser.");
return;
}
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const outputElement = document.getElementById('output');
const matrixSize = 4; // 4x4 matrix
const matrixA = new Float32Array(matrixSize * matrixSize);
const matrixB = new Float32Array(matrixSize * matrixSize);
const matrixC = new Float32Array(matrixSize * matrixSize);
// Initialize matrices A and B with some values
for (let i = 0; i < matrixSize * matrixSize; i++) {
matrixA[i] = Math.random();
matrixB[i] = Math.random();
}
const gpuBufferA = device.createBuffer({
size: matrixA.byteLength,
usage: GPUBufferUsage.STORAGE,
mappedAtCreation: true
});
new Float32Array(gpuBufferA.getMappedRange()).set(matrixA);
gpuBufferA.unmap();
const gpuBufferB = device.createBuffer({
size: matrixB.byteLength,
usage: GPUBufferUsage.STORAGE,
mappedAtCreation: true
});
new Float32Array(gpuBufferB.getMappedRange()).set(matrixB);
gpuBufferB.unmap();
const gpuBufferC = device.createBuffer({
size: matrixC.byteLength,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC
});
const gpuBufferRead = device.createBuffer({
size: matrixC.byteLength,
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ
});
const shaderCode = `
struct Matrix {
size : u32;
numbers: array<f32>;
};
@group(0) @binding(0) var<storage, read> matrixA : Matrix;
@group(0) @binding(1) var<storage, read> matrixB : Matrix;
@group(0) @binding(2) var<storage, read_write> matrixC : Matrix;
@compute @workgroup_size(1)
fn main(@builtin(global_invocation_id) global_id : vec3<u32>) {
let row = global_id.x;
let col = global_id.y;
let size = matrixA.size;
var sum = 0.0;
for (var i = 0u; i < size; i = i + 1u) {
sum = sum + matrixA.numbers[row * size + i] * matrixB.numbers[i * size + col];
}
matrixC.numbers[row * size + col] = sum;
}
`;
const shaderModule = device.createShaderModule({
code: shaderCode
});
const computePipeline = device.createComputePipeline({
layout: "auto",
compute: {
module: shaderModule,
entryPoint: "main"
}
});
const bindGroup = device.createBindGroup({
layout: computePipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer: gpuBufferA }},
{ binding: 1, resource: { buffer: gpuBufferB }},
{ binding: 2, resource: { buffer: gpuBufferC }}
]
});
const commandEncoder = device.createCommandEncoder();
const passEncoder = commandEncoder.beginComputePass();
passEncoder.setPipeline(computePipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.dispatchWorkgroups(matrixSize, matrixSize);
passEncoder.end();
commandEncoder.copyBufferToBuffer(gpuBufferC, 0, gpuBufferRead, 0, matrixC.byteLength);
const start = performance.now();
const commands = commandEncoder.finish();
device.queue.submit([commands]);
await gpuBufferRead.mapAsync(GPUMapMode.READ);
const arrayBuffer = gpuBufferRead.getMappedRange();
const result = new Float32Array(arrayBuffer);
const end = performance.now();
outputElement.textContent = `Matrix C (Result):\n${result}\nProcessing Time: ${(end - start).toFixed(2)} ms`;
gpuBufferRead.unmap();
}
initWebGPU();
</script>
</body>
</html>