wgblas 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +161 -0
- package/dist/wgblas.browser.js +422 -0
- package/index.d.mts +94 -0
- package/index.mjs +16 -0
- package/package.json +127 -0
- package/src/classes/GpuVector.d.mts +69 -0
- package/src/classes/GpuVector.mjs +33 -0
- package/src/devdocs.mjs +81 -0
- package/src/index.mjs +4 -0
- package/src/init.mjs +105 -0
- package/src/isamax/isamax.d.mts +46 -0
- package/src/isamax/isamax.mjs +115 -0
- package/src/random/random.d.mts +61 -0
- package/src/random/random.mjs +11 -0
- package/src/sasum/sasum.d.mts +44 -0
- package/src/sasum/sasum.mjs +98 -0
- package/src/saxpy/saxpy.d.mts +54 -0
- package/src/saxpy/saxpy.mjs +90 -0
- package/src/scopy/scopy.d.mts +50 -0
- package/src/scopy/scopy.mjs +87 -0
- package/src/sdot/sdot.d.mts +52 -0
- package/src/sdot/sdot.mjs +118 -0
- package/src/shaders/browser-shaders.mjs +27 -0
- package/src/shaders/index.mjs +27 -0
- package/src/snrm2/snrm2.d.mts +44 -0
- package/src/snrm2/snrm2.mjs +100 -0
- package/src/srot/srot.d.mts +62 -0
- package/src/srot/srot.mjs +95 -0
- package/src/srotm/srotm.d.mts +60 -0
- package/src/srotm/srotm.mjs +94 -0
- package/src/sscal/sscal.d.mts +46 -0
- package/src/sscal/sscal.mjs +71 -0
- package/src/sswap/sswap.d.mts +50 -0
- package/src/sswap/sswap.mjs +90 -0
- package/src/util/benchmark.mjs +103 -0
- package/src/util/bindgroup.mjs +22 -0
- package/src/util/buffer.mjs +160 -0
- package/src/util/compute.mjs +52 -0
- package/src/util/index.mjs +12 -0
- package/src/util/pipeline.mjs +82 -0
- package/src/util/result.mjs +19 -0
- package/src/util/workgroup.mjs +31 -0
package/package.json
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "wgblas",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "BLAS on WebGPU",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.mjs",
|
|
7
|
+
"types": "./index.d.mts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./index.mjs",
|
|
11
|
+
"types": "./index.d.mts"
|
|
12
|
+
},
|
|
13
|
+
"./browser": "./dist/wgblas.browser.js",
|
|
14
|
+
"./classes/GpuVector": {
|
|
15
|
+
"import": "./src/classes/GpuVector.mjs",
|
|
16
|
+
"types": "./src/classes/GpuVector.d.mts"
|
|
17
|
+
},
|
|
18
|
+
"./random": {
|
|
19
|
+
"import": "./src/random/random.mjs",
|
|
20
|
+
"types": "./src/random/random.d.mts"
|
|
21
|
+
},
|
|
22
|
+
"./sscal": {
|
|
23
|
+
"import": "./src/sscal/sscal.mjs",
|
|
24
|
+
"types": "./src/sscal/sscal.d.mts"
|
|
25
|
+
},
|
|
26
|
+
"./sswap": {
|
|
27
|
+
"import": "./src/sswap/sswap.mjs",
|
|
28
|
+
"types": "./src/sswap/sswap.d.mts"
|
|
29
|
+
},
|
|
30
|
+
"./saxpy": {
|
|
31
|
+
"import": "./src/saxpy/saxpy.mjs",
|
|
32
|
+
"types": "./src/saxpy/saxpy.d.mts"
|
|
33
|
+
},
|
|
34
|
+
"./scopy": {
|
|
35
|
+
"import": "./src/scopy/scopy.mjs",
|
|
36
|
+
"types": "./src/scopy/scopy.d.mts"
|
|
37
|
+
},
|
|
38
|
+
"./sdot": {
|
|
39
|
+
"import": "./src/sdot/sdot.mjs",
|
|
40
|
+
"types": "./src/sdot/sdot.d.mts"
|
|
41
|
+
},
|
|
42
|
+
"./sasum": {
|
|
43
|
+
"import": "./src/sasum/sasum.mjs",
|
|
44
|
+
"types": "./src/sasum/sasum.d.mts"
|
|
45
|
+
},
|
|
46
|
+
"./snrm2": {
|
|
47
|
+
"import": "./src/snrm2/snrm2.mjs",
|
|
48
|
+
"types": "./src/snrm2/snrm2.d.mts"
|
|
49
|
+
},
|
|
50
|
+
"./isamax": {
|
|
51
|
+
"import": "./src/isamax/isamax.mjs",
|
|
52
|
+
"types": "./src/isamax/isamax.d.mts"
|
|
53
|
+
},
|
|
54
|
+
"./srot": {
|
|
55
|
+
"import": "./src/srot/srot.mjs",
|
|
56
|
+
"types": "./src/srot/srot.d.mts"
|
|
57
|
+
},
|
|
58
|
+
"./srotm": {
|
|
59
|
+
"import": "./src/srotm/srotm.mjs",
|
|
60
|
+
"types": "./src/srotm/srotm.d.mts"
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
"files": [
|
|
64
|
+
"index.mjs",
|
|
65
|
+
"index.d.mts",
|
|
66
|
+
"src/**/*.mjs",
|
|
67
|
+
"src/**/*.d.mts",
|
|
68
|
+
"dist/wgblas.browser.js"
|
|
69
|
+
],
|
|
70
|
+
"repository": {
|
|
71
|
+
"type": "git",
|
|
72
|
+
"url": "git+https://github.com/manit2004/wgblas.git"
|
|
73
|
+
},
|
|
74
|
+
"keywords": [
|
|
75
|
+
"BLAS",
|
|
76
|
+
"WebGPU"
|
|
77
|
+
],
|
|
78
|
+
"author": "Manit Roy",
|
|
79
|
+
"license": "Apache-2.0",
|
|
80
|
+
"engines": {
|
|
81
|
+
"node": ">=22.22.2"
|
|
82
|
+
},
|
|
83
|
+
"bugs": {
|
|
84
|
+
"url": "https://github.com/manit2004/wgblas/issues"
|
|
85
|
+
},
|
|
86
|
+
"homepage": "https://github.com/manit2004/wgblas#readme",
|
|
87
|
+
"dependencies": {
|
|
88
|
+
"webgpu": "^0.4.0"
|
|
89
|
+
},
|
|
90
|
+
"devDependencies": {
|
|
91
|
+
"@eslint/js": "^10.0.1",
|
|
92
|
+
"@stdlib/blas-base-isamax": "^0.1.1",
|
|
93
|
+
"@stdlib/blas-base-sasum": "^0.3.1",
|
|
94
|
+
"@stdlib/blas-base-saxpy": "^0.3.1",
|
|
95
|
+
"@stdlib/blas-base-scopy": "^0.3.1",
|
|
96
|
+
"@stdlib/blas-base-sdot": "^0.3.1",
|
|
97
|
+
"@stdlib/blas-base-snrm2": "^0.3.1",
|
|
98
|
+
"@stdlib/blas-base-srot": "^0.2.1",
|
|
99
|
+
"@stdlib/blas-base-srotm": "^0.2.1",
|
|
100
|
+
"@stdlib/blas-base-sscal": "^0.3.1",
|
|
101
|
+
"@stdlib/blas-base-sswap": "^0.3.1",
|
|
102
|
+
"@stdlib/random-array-uniform": "^0.2.2",
|
|
103
|
+
"@stdlib/random-base-uniform": "^0.2.3",
|
|
104
|
+
"esbuild": "^0.28.1",
|
|
105
|
+
"eslint": "^10.6.0",
|
|
106
|
+
"fast-check": "^4.8.0",
|
|
107
|
+
"globals": "^17.7.0",
|
|
108
|
+
"husky": "^9.1.7",
|
|
109
|
+
"lint-staged": "^17.0.8",
|
|
110
|
+
"prettier": "^3.9.4",
|
|
111
|
+
"typedoc": "^0.28.19",
|
|
112
|
+
"typescript": "^6.0.3",
|
|
113
|
+
"vite": "^8.0.16"
|
|
114
|
+
},
|
|
115
|
+
"scripts": {
|
|
116
|
+
"prepare": "husky",
|
|
117
|
+
"build:browser": "node scripts/build-browser.mjs",
|
|
118
|
+
"docs": "npx typedoc && npm run build:browser",
|
|
119
|
+
"prepublishOnly": "npm run build:browser"
|
|
120
|
+
},
|
|
121
|
+
"lint-staged": {
|
|
122
|
+
"*.{js,mjs,cjs}": [
|
|
123
|
+
"prettier --write",
|
|
124
|
+
"eslint"
|
|
125
|
+
]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a Float32Array stored in GPU memory.
|
|
3
|
+
*
|
|
4
|
+
* @see <a href="https://github.com/manit2004/wgblas/blob/main/src/classes/GpuVector.mjs#L7">Source code: GpuVector.mjs (L7)</a>
|
|
5
|
+
* @see [MDN: GPUBuffer](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer)
|
|
6
|
+
* @category Classes
|
|
7
|
+
*/
|
|
8
|
+
export declare class GpuVector {
|
|
9
|
+
/** @internal */
|
|
10
|
+
readonly _buf: GPUBuffer;
|
|
11
|
+
|
|
12
|
+
/** Number of elements in the vector. */
|
|
13
|
+
readonly length: number;
|
|
14
|
+
|
|
15
|
+
// TODO: widen to Float32ArrayConstructor | Float64ArrayConstructor when Float64 support is added
|
|
16
|
+
/** Typed array constructor used when reading data back from the GPU. */
|
|
17
|
+
readonly dtype: Float32ArrayConstructor;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Uploads a Float32Array to GPU memory.
|
|
21
|
+
*
|
|
22
|
+
* @param data - input vector data
|
|
23
|
+
* @returns GpuVector backed by a GPU buffer
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```js
|
|
27
|
+
* import { init, GpuVector } from "wgblas";
|
|
28
|
+
*
|
|
29
|
+
* await init();
|
|
30
|
+
* const vec = GpuVector.from(new Float32Array([1, 2, 3, 4]));
|
|
31
|
+
* console.log("length:", vec.length, "dtype:", vec.dtype.name);
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
static from(data: Float32Array): GpuVector;
|
|
35
|
+
|
|
36
|
+
// TODO: return type will widen to Promise<Float32Array | Float64Array> when Float64 support is added
|
|
37
|
+
/**
|
|
38
|
+
* Reads the vector data back from GPU memory.
|
|
39
|
+
*
|
|
40
|
+
* @returns vector data as a Float32Array
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```js
|
|
44
|
+
* import { init, GpuVector } from "wgblas";
|
|
45
|
+
*
|
|
46
|
+
* await init();
|
|
47
|
+
* const vec = GpuVector.from(new Float32Array([1, 2, 3, 4]));
|
|
48
|
+
* const data = await vec.read();
|
|
49
|
+
* console.log(data);
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
read(): Promise<Float32Array>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Destroys the underlying GPU buffer. Call when the vector is no longer needed
|
|
56
|
+
* to free GPU memory — especially important in long-running programs.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```js
|
|
60
|
+
* import { init, GpuVector } from "wgblas";
|
|
61
|
+
*
|
|
62
|
+
* await init();
|
|
63
|
+
* const vec = GpuVector.from(new Float32Array([1, 2, 3, 4]));
|
|
64
|
+
* vec.destroy();
|
|
65
|
+
* console.log("GPU buffer released");
|
|
66
|
+
* ```
|
|
67
|
+
*/
|
|
68
|
+
destroy(): void;
|
|
69
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getDevice } from "../init.mjs";
|
|
2
|
+
import { uploadBuffer, stageReadback } from "../util/buffer.mjs";
|
|
3
|
+
import { extractResult } from "../util/result.mjs";
|
|
4
|
+
|
|
5
|
+
//TODO: Need to update this when we support Float64Array.
|
|
6
|
+
|
|
7
|
+
export class GpuVector {
|
|
8
|
+
constructor(buffer, length, dtype = Float32Array) {
|
|
9
|
+
this._buf = buffer;
|
|
10
|
+
this.length = length;
|
|
11
|
+
this.dtype = dtype;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static from(data) {
|
|
15
|
+
if (!(data instanceof Float32Array)) {
|
|
16
|
+
throw new Error("GpuVector.from expects a Float32Array.");
|
|
17
|
+
}
|
|
18
|
+
const buf = uploadBuffer(data, "gpu-vector", true);
|
|
19
|
+
return new GpuVector(buf, data.length, data.constructor);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async read() {
|
|
23
|
+
const device = getDevice();
|
|
24
|
+
const enc = device.createCommandEncoder();
|
|
25
|
+
const rb = stageReadback(enc, this._buf);
|
|
26
|
+
device.queue.submit([enc.finish()]);
|
|
27
|
+
return extractResult(rb, this.dtype);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
destroy() {
|
|
31
|
+
this._buf.destroy();
|
|
32
|
+
}
|
|
33
|
+
}
|
package/src/devdocs.mjs
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A repo tour for contributors, learners, and anyone curious how a GPU BLAS
|
|
3
|
+
* library is built. Each section mirrors a major folder or system in the
|
|
4
|
+
* repository and explains the reasoning behind it — not just what the code
|
|
5
|
+
* does, but why it is shaped the way it is.
|
|
6
|
+
*
|
|
7
|
+
* ## What is BLAS?
|
|
8
|
+
*
|
|
9
|
+
* BLAS (Basic Linear Algebra Subprograms) is a standard API for vector and
|
|
10
|
+
* matrix operations — dot products, norms, scalar multiplies, matrix-vector
|
|
11
|
+
* products. It is the computational backbone of numerical computing, machine
|
|
12
|
+
* learning frameworks, and scientific software. The reference spec lives at
|
|
13
|
+
* [netlib.org/blas](https://www.netlib.org/blas/).
|
|
14
|
+
*
|
|
15
|
+
* GPUs are well-suited for BLAS: operations like saxpy (y = αx + y) are
|
|
16
|
+
* embarrassingly parallel — each element is independent — so thousands of GPU
|
|
17
|
+
* threads can work simultaneously. NVIDIA's
|
|
18
|
+
* [cuBLAS](https://docs.nvidia.com/cuda/cublas/) is the canonical example;
|
|
19
|
+
* this library applies the same idea to the browser via WebGPU.
|
|
20
|
+
*
|
|
21
|
+
* ## What is WebGPU?
|
|
22
|
+
*
|
|
23
|
+
* WebGPU is the modern browser GPU API, shipping in Chrome 113+ and Firefox
|
|
24
|
+
* nightly. It lets JavaScript programs upload data to the GPU, run compute
|
|
25
|
+
* programs on it, and read results back — without plugins or native code.
|
|
26
|
+
*
|
|
27
|
+
* > **Note:** WGSL does not support `f64` natively, so this library currently
|
|
28
|
+
* > implements single-precision (`f32`) routines only.
|
|
29
|
+
*
|
|
30
|
+
* ## What is a compute shader?
|
|
31
|
+
*
|
|
32
|
+
* Each routine runs a **compute shader** written in WGSL (a GPU program that
|
|
33
|
+
* runs thousands of instances in parallel, each processing one element). WebGPU
|
|
34
|
+
* has vertex and fragment shaders for graphics too — compute shaders are
|
|
35
|
+
* specifically for general computation. This library only uses compute shaders,
|
|
36
|
+
* so we call them simply "shaders" throughout.
|
|
37
|
+
*
|
|
38
|
+
* ## New to GPU programming?
|
|
39
|
+
*
|
|
40
|
+
* Read these two chapters from the CUDA Programming Guide — the mental models
|
|
41
|
+
* transfer directly to WebGPU; only the terminology changes. Then come back here.
|
|
42
|
+
*
|
|
43
|
+
* - [Introduction](https://docs.nvidia.com/cuda/cuda-programming-guide/01-introduction/introduction.html) —
|
|
44
|
+
* why GPUs exist, GPU vs CPU design philosophy
|
|
45
|
+
* - [Programming Model](https://docs.nvidia.com/cuda/cuda-programming-guide/01-introduction/programming-model.html) —
|
|
46
|
+
* threads, blocks, grids, warps, memory hierarchy
|
|
47
|
+
*
|
|
48
|
+
* ### CUDA → WebGPU terminology
|
|
49
|
+
*
|
|
50
|
+
* | CUDA | WebGPU |
|
|
51
|
+
* |---|---|
|
|
52
|
+
* | Thread | Invocation |
|
|
53
|
+
* | Thread block | Workgroup |
|
|
54
|
+
* | Grid | Dispatch |
|
|
55
|
+
* | `__global__` function | `@compute` shader |
|
|
56
|
+
* | `threadIdx.x` | `local_invocation_id` |
|
|
57
|
+
* | `blockIdx.x` | `workgroup_id` |
|
|
58
|
+
* | `blockDim.x` | `@workgroup_size(N)` |
|
|
59
|
+
* | Shared memory | `var<workgroup>` |
|
|
60
|
+
* | Global memory | Storage buffer |
|
|
61
|
+
* | `cudaMemcpy` host→device | `device.queue.writeBuffer` |
|
|
62
|
+
* | `cudaMemcpy` device→host | `mapAsync` + `getMappedRange` |
|
|
63
|
+
*
|
|
64
|
+
* ## WebGPU resources
|
|
65
|
+
*
|
|
66
|
+
* - [WebGPU Fundamentals](https://webgpufundamentals.org/webgpu/lessons/webgpu-fundamentals.html#a-run-computations-on-the-gpu) —
|
|
67
|
+
* running computations on the GPU
|
|
68
|
+
* - [WebGPU Compute Shaders](https://webgpufundamentals.org/webgpu/lessons/webgpu-compute-shaders.html)
|
|
69
|
+
* - [GPU Compute with WebGPU](https://developer.chrome.com/docs/capabilities/web-apis/gpu-compute?hl=en) —
|
|
70
|
+
* Chrome's practical guide to compute pipelines
|
|
71
|
+
* - [Chrome WebGPU overview](https://developer.chrome.com/docs/web-platform/webgpu/overview?hl=en)
|
|
72
|
+
*
|
|
73
|
+
* ### Further reading
|
|
74
|
+
*
|
|
75
|
+
* - [Chrome "New in WebGPU" series](https://developer.chrome.com/tags/new-in-webgpu) —
|
|
76
|
+
* feature-by-feature changelog with code examples
|
|
77
|
+
* - [MDN WebGPU API](https://developer.mozilla.org/en-US/docs/Web/API/WebGPU_API)
|
|
78
|
+
* - [W3C WebGPU spec](https://www.w3.org/TR/webgpu/)
|
|
79
|
+
*
|
|
80
|
+
* @module devdocs
|
|
81
|
+
*/
|
package/src/index.mjs
ADDED
package/src/init.mjs
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { benchmarkMode } from "./util/benchmark.mjs";
|
|
2
|
+
|
|
3
|
+
let _device = null;
|
|
4
|
+
let _adapter = null;
|
|
5
|
+
let _gpu = null; // eslint-disable-line no-unused-vars
|
|
6
|
+
let _benchmarkEnabled = false;
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
10
|
+
|
|
11
|
+
export async function init({
|
|
12
|
+
powerPreference = "high-performance",
|
|
13
|
+
benchmark = false,
|
|
14
|
+
} = {}) {
|
|
15
|
+
if (_device) {
|
|
16
|
+
return _device;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let gpu;
|
|
20
|
+
// Browser exposes WebGPU natively via navigator.gpu.
|
|
21
|
+
// Node.js has no navigator, so we polyfill using the "webgpu" npm package which also
|
|
22
|
+
// injects WebGPU globals (GPUBufferUsage, GPUShaderStage, etc.) into globalThis.
|
|
23
|
+
if (typeof window === "undefined") {
|
|
24
|
+
const { create, globals } = await import("webgpu");
|
|
25
|
+
Object.assign(globalThis, globals);
|
|
26
|
+
gpu = create([]);
|
|
27
|
+
_gpu = gpu;
|
|
28
|
+
} else {
|
|
29
|
+
gpu = navigator.gpu;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!gpu) {
|
|
33
|
+
throw new Error("WebGPU not supported in this environment.");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_adapter =
|
|
37
|
+
(await gpu.requestAdapter({ powerPreference })) ??
|
|
38
|
+
(await gpu.requestAdapter());
|
|
39
|
+
if (!_adapter) {
|
|
40
|
+
throw new Error("No WebGPU adapter found.");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_benchmarkEnabled = benchmark;
|
|
44
|
+
_device = await _adapter.requestDevice(benchmarkMode(_adapter, benchmark));
|
|
45
|
+
// Fires for any GPU error not caught by a pushErrorScope/popErrorScope pair — surfaces silent GPU failures to the console.
|
|
46
|
+
// See: https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/uncapturederror_event
|
|
47
|
+
_device.addEventListener("uncapturederror", (e) => {
|
|
48
|
+
console.error("Uncaptured GPU error:", e.error.message);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
return _device;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function cleanup() {
|
|
55
|
+
if (_device) {
|
|
56
|
+
_device.destroy();
|
|
57
|
+
_device = null;
|
|
58
|
+
}
|
|
59
|
+
_adapter = null;
|
|
60
|
+
_gpu = null;
|
|
61
|
+
_benchmarkEnabled = false;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function gpuName() {
|
|
65
|
+
if (!_adapter) {
|
|
66
|
+
throw new Error("WebGPU adapter not initialized — call init() first.");
|
|
67
|
+
}
|
|
68
|
+
const { device, description } = _adapter.info;
|
|
69
|
+
return {
|
|
70
|
+
description: description || "unknown",
|
|
71
|
+
device: device || "unknown",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── Library internals (not part of the public API) ───────────────────────────
|
|
76
|
+
|
|
77
|
+
/** @returns {boolean} whether benchmark mode was enabled in the last `init()` call */
|
|
78
|
+
export function isBenchmarkEnabled() {
|
|
79
|
+
return _benchmarkEnabled;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Returns the active `GPUDevice`. Throws if `init()` has not been called.
|
|
84
|
+
* @returns {GPUDevice}
|
|
85
|
+
* @throws {Error} if the device is not initialized
|
|
86
|
+
*/
|
|
87
|
+
export function getDevice() {
|
|
88
|
+
if (!_device) {
|
|
89
|
+
throw new Error("WebGPU device not initialized — call init() first.");
|
|
90
|
+
}
|
|
91
|
+
return _device;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns the active `GPUAdapter`. Throws if `init()` has not been called.
|
|
96
|
+
* @returns {GPUAdapter}
|
|
97
|
+
* @throws {Error} if the adapter is not initialized
|
|
98
|
+
*/
|
|
99
|
+
export function getAdapter() {
|
|
100
|
+
if (!_adapter) {
|
|
101
|
+
throw new Error("WebGPU adapter not initialized — call init() first.");
|
|
102
|
+
}
|
|
103
|
+
return _adapter;
|
|
104
|
+
}
|
|
105
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { GpuVector } from "../classes/GpuVector.mjs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns the 0-based index of the element with the largest absolute value.
|
|
5
|
+
* Ties are broken in favour of the lower index, matching CBLAS behaviour.
|
|
6
|
+
*
|
|
7
|
+
* {@includeCode ../../examples/isamax/isamax.js}
|
|
8
|
+
*
|
|
9
|
+
* **Browser (standalone HTML):**
|
|
10
|
+
* {@includeCode ../../examples/isamax/web/isamax.html}
|
|
11
|
+
*
|
|
12
|
+
* @param device - GPUDevice from `init()`
|
|
13
|
+
* @param n - number of elements (must be a positive integer)
|
|
14
|
+
* @param x - Float32Array input vector
|
|
15
|
+
* @param incx - stride for x (must be a positive integer)
|
|
16
|
+
* @returns 0-based index of max |x[i]|
|
|
17
|
+
* @see <a href="https://github.com/manit2004/wgblas/blob/main/src/isamax/isamax.mjs#L18">Source code: isamax.mjs (L18)</a>
|
|
18
|
+
* @category BLAS Level 1
|
|
19
|
+
*/
|
|
20
|
+
export declare function isamax(
|
|
21
|
+
device: GPUDevice,
|
|
22
|
+
n: number,
|
|
23
|
+
x: Float32Array,
|
|
24
|
+
incx: number,
|
|
25
|
+
): Promise<{ index: number } | { index: number; gpuTimeMs: number }>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Returns the 0-based index of the element with the largest absolute value.
|
|
29
|
+
* Ties are broken in favour of the lower index, matching CBLAS behaviour.
|
|
30
|
+
*
|
|
31
|
+
* {@includeCode ../../examples/isamax/gpuvec.isamax.js}
|
|
32
|
+
*
|
|
33
|
+
* @param device - GPUDevice from `init()`
|
|
34
|
+
* @param n - number of elements (must be a positive integer)
|
|
35
|
+
* @param x - GpuVector input vector
|
|
36
|
+
* @param incx - stride for x (must be a positive integer)
|
|
37
|
+
* @returns 0-based index of max |x[i]|
|
|
38
|
+
* @see <a href="https://github.com/manit2004/wgblas/blob/main/src/isamax/isamax.mjs#L18">Source code: isamax.mjs (L18)</a>
|
|
39
|
+
* @category BLAS Level 1
|
|
40
|
+
*/
|
|
41
|
+
export declare function isamax(
|
|
42
|
+
device: GPUDevice,
|
|
43
|
+
n: number,
|
|
44
|
+
x: GpuVector,
|
|
45
|
+
incx: number,
|
|
46
|
+
): Promise<{ index: number } | { index: number; gpuTimeMs: number }>;
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import {
|
|
2
|
+
uploadBuffer,
|
|
3
|
+
createStorageBuffer,
|
|
4
|
+
createParamsBuffer,
|
|
5
|
+
createResultBuffer,
|
|
6
|
+
stageReadback,
|
|
7
|
+
destroyBuffers,
|
|
8
|
+
} from "../util/buffer.mjs";
|
|
9
|
+
import { createBindGroup } from "../util/bindgroup.mjs";
|
|
10
|
+
import { runComputePass, submit } from "../util/compute.mjs";
|
|
11
|
+
import { extractTimestamp } from "../util/benchmark.mjs";
|
|
12
|
+
import { extractResult } from "../util/result.mjs";
|
|
13
|
+
import { getPipeline } from "../util/pipeline.mjs";
|
|
14
|
+
import { GpuVector } from "../classes/GpuVector.mjs";
|
|
15
|
+
|
|
16
|
+
const WGS = 64;
|
|
17
|
+
|
|
18
|
+
export async function isamax(device, n, x, incx) {
|
|
19
|
+
const xIsGpu = x instanceof GpuVector;
|
|
20
|
+
|
|
21
|
+
if (!(device instanceof GPUDevice))
|
|
22
|
+
throw new Error("device must be a GPUDevice.");
|
|
23
|
+
if (!Number.isInteger(n) || !Number.isInteger(incx))
|
|
24
|
+
throw new Error("n and incx must be integers.");
|
|
25
|
+
if (incx <= 0) throw new Error("incx must be positive.");
|
|
26
|
+
if (!xIsGpu && !(x instanceof Float32Array))
|
|
27
|
+
throw new Error("x must be a Float32Array or GpuVector.");
|
|
28
|
+
if (n <= 0) return { index: 0 };
|
|
29
|
+
if (x.length < (n - 1) * incx + 1)
|
|
30
|
+
throw new Error(
|
|
31
|
+
"x does not have enough elements for the given n and incx.",
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
const pipelineMain = await getPipeline(device, "isamax");
|
|
35
|
+
const pipelineReduce = await getPipeline(device, "reduction/argmax");
|
|
36
|
+
|
|
37
|
+
const xBuffer = xIsGpu ? x._buf : uploadBuffer(x, "isamax-x", false);
|
|
38
|
+
const partialsValBuffer = createStorageBuffer(
|
|
39
|
+
2 * WGS * 4,
|
|
40
|
+
"isamax-partials-val",
|
|
41
|
+
); //to hold 2*WGS partial max values of f32
|
|
42
|
+
const partialsIdxBuffer = createStorageBuffer(
|
|
43
|
+
2 * WGS * 4,
|
|
44
|
+
"isamax-partials-idx",
|
|
45
|
+
); //to hold 2*WGS partial max indices of u32
|
|
46
|
+
const resultBuffer = createResultBuffer(4, "isamax-result"); // u32 index
|
|
47
|
+
const paramsBuffer = createParamsBuffer(
|
|
48
|
+
[
|
|
49
|
+
{ value: n, type: "u32" },
|
|
50
|
+
{ value: incx, type: "u32" },
|
|
51
|
+
],
|
|
52
|
+
"isamax-params",
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const bgMain = createBindGroup(pipelineMain.getBindGroupLayout(0), [
|
|
56
|
+
xBuffer,
|
|
57
|
+
partialsValBuffer,
|
|
58
|
+
partialsIdxBuffer,
|
|
59
|
+
paramsBuffer,
|
|
60
|
+
]);
|
|
61
|
+
const { commandEncoder: enc1, ts: ts1 } = runComputePass(
|
|
62
|
+
pipelineMain,
|
|
63
|
+
bgMain,
|
|
64
|
+
2 * WGS,
|
|
65
|
+
); //dispatch 2*WGS workgroups
|
|
66
|
+
|
|
67
|
+
submit(enc1);
|
|
68
|
+
|
|
69
|
+
const bgReduce = createBindGroup(pipelineReduce.getBindGroupLayout(0), [
|
|
70
|
+
partialsValBuffer,
|
|
71
|
+
partialsIdxBuffer,
|
|
72
|
+
resultBuffer,
|
|
73
|
+
]);
|
|
74
|
+
const { commandEncoder: enc2, ts: ts2 } = runComputePass(
|
|
75
|
+
pipelineReduce,
|
|
76
|
+
bgReduce,
|
|
77
|
+
1,
|
|
78
|
+
); // dispatch 1 workgroup to reduce the partial sums to a single result
|
|
79
|
+
const readBuffer = stageReadback(enc2, resultBuffer);
|
|
80
|
+
|
|
81
|
+
submit(enc2);
|
|
82
|
+
|
|
83
|
+
const [gpuTime1, gpuTime2, idxArr] = await Promise.all([
|
|
84
|
+
extractTimestamp(ts1),
|
|
85
|
+
extractTimestamp(ts2),
|
|
86
|
+
extractResult(readBuffer, Uint32Array),
|
|
87
|
+
]);
|
|
88
|
+
|
|
89
|
+
const index = idxArr[0];
|
|
90
|
+
|
|
91
|
+
if (xIsGpu) {
|
|
92
|
+
destroyBuffers(
|
|
93
|
+
partialsValBuffer,
|
|
94
|
+
partialsIdxBuffer,
|
|
95
|
+
resultBuffer,
|
|
96
|
+
paramsBuffer,
|
|
97
|
+
readBuffer,
|
|
98
|
+
);
|
|
99
|
+
if (gpuTime1 !== undefined && gpuTime2 !== undefined)
|
|
100
|
+
return { index, gpuTimeMs: gpuTime1 + gpuTime2 };
|
|
101
|
+
return { index };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
destroyBuffers(
|
|
105
|
+
xBuffer,
|
|
106
|
+
partialsValBuffer,
|
|
107
|
+
partialsIdxBuffer,
|
|
108
|
+
resultBuffer,
|
|
109
|
+
paramsBuffer,
|
|
110
|
+
readBuffer,
|
|
111
|
+
);
|
|
112
|
+
if (gpuTime1 !== undefined && gpuTime2 !== undefined)
|
|
113
|
+
return { index, gpuTimeMs: gpuTime1 + gpuTime2 };
|
|
114
|
+
return { index };
|
|
115
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a Float32Array of n random values uniformly sampled from [low, high).
|
|
3
|
+
*
|
|
4
|
+
* @param n - number of elements
|
|
5
|
+
* @param low - lower bound (default: -1)
|
|
6
|
+
* @param high - upper bound (default: 1)
|
|
7
|
+
*
|
|
8
|
+
* @example Default range [-1, 1)
|
|
9
|
+
* ```js
|
|
10
|
+
* import { randomFloat32Array } from "wgblas";
|
|
11
|
+
*
|
|
12
|
+
* const x = randomFloat32Array(4);
|
|
13
|
+
* console.log(x); // Float32Array [ -0.42, 0.81, -0.07, 0.55 ]
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @example Custom range [0, 10)
|
|
17
|
+
* ```js
|
|
18
|
+
* import { randomFloat32Array } from "wgblas";
|
|
19
|
+
*
|
|
20
|
+
* const x = randomFloat32Array(4, 0, 10);
|
|
21
|
+
* console.log(x);
|
|
22
|
+
* ```
|
|
23
|
+
* @see <a href="https://github.com/manit2004/wgblas/blob/main/src/random/random.mjs#L1">Source code: random.mjs (L1)</a>
|
|
24
|
+
* @category Utilities
|
|
25
|
+
*/
|
|
26
|
+
export declare function randomFloat32Array(
|
|
27
|
+
n: number,
|
|
28
|
+
low?: number,
|
|
29
|
+
high?: number,
|
|
30
|
+
): Float32Array;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Returns a Float64Array of n random values uniformly sampled from [low, high).
|
|
34
|
+
*
|
|
35
|
+
* @param n - number of elements
|
|
36
|
+
* @param low - lower bound (default: -1)
|
|
37
|
+
* @param high - upper bound (default: 1)
|
|
38
|
+
*
|
|
39
|
+
* @example Default range [-1, 1)
|
|
40
|
+
* ```js
|
|
41
|
+
* import { randomFloat64Array } from "wgblas";
|
|
42
|
+
*
|
|
43
|
+
* const x = randomFloat64Array(4);
|
|
44
|
+
* console.log(x); // Float64Array [ -0.42, 0.81, -0.07, 0.55 ]
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example Custom range [0, 10)
|
|
48
|
+
* ```js
|
|
49
|
+
* import { randomFloat64Array } from "wgblas";
|
|
50
|
+
*
|
|
51
|
+
* const x = randomFloat64Array(4, 0, 10);
|
|
52
|
+
* console.log(x);
|
|
53
|
+
* ```
|
|
54
|
+
* @see <a href="https://github.com/manit2004/wgblas/blob/main/src/random/random.mjs#L7">Source code: random.mjs (L7)</a>
|
|
55
|
+
* @category Utilities
|
|
56
|
+
*/
|
|
57
|
+
export declare function randomFloat64Array(
|
|
58
|
+
n: number,
|
|
59
|
+
low?: number,
|
|
60
|
+
high?: number,
|
|
61
|
+
): Float64Array;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function randomFloat32Array(n, low = -1, high = 1) {
|
|
2
|
+
const x = new Float32Array(n);
|
|
3
|
+
for (let i = 0; i < n; i++) x[i] = low + Math.random() * (high - low);
|
|
4
|
+
return x;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function randomFloat64Array(n, low = -1, high = 1) {
|
|
8
|
+
const x = new Float64Array(n);
|
|
9
|
+
for (let i = 0; i < n; i++) x[i] = low + Math.random() * (high - low);
|
|
10
|
+
return x;
|
|
11
|
+
}
|