wgblas 0.1.2 → 1.0.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/README.md +3 -0
- package/dist/wgblas.browser.js +1078 -37
- package/index.d.mts +6 -0
- package/index.mjs +6 -0
- package/package.json +32 -1
- package/src/classes/GpuMatrix.d.mts +85 -0
- package/src/classes/GpuMatrix.mjs +91 -0
- package/src/classes/GpuVector.d.mts +11 -7
- package/src/classes/GpuVector.mjs +31 -5
- package/src/dasum/dasum.d.mts +48 -0
- package/src/dasum/dasum.mjs +121 -0
- package/src/init.mjs +3 -1
- package/src/isamax/isamax.mjs +66 -67
- package/src/random/random.d.mts +37 -0
- package/src/random/random.mjs +17 -0
- package/src/sasum/sasum.mjs +55 -51
- package/src/saxpy/saxpy.mjs +48 -35
- package/src/scopy/scopy.mjs +43 -32
- package/src/sdot/sdot.mjs +60 -55
- package/src/sgemv/sgemv.d.mts +118 -0
- package/src/sgemv/sgemv.mjs +141 -0
- package/src/shaders/browser-shaders.mjs +20 -0
- package/src/shaders/dasum.wgsl +98 -0
- package/src/shaders/f64add.wgsl +281 -0
- package/src/shaders/isamax.wgsl +32 -9
- package/src/shaders/reduction/sumF64.wgsl +49 -0
- package/src/shaders/sasum.wgsl +18 -4
- package/src/shaders/sdot.wgsl +18 -4
- package/src/shaders/sgemv_n.wgsl +75 -0
- package/src/shaders/sgemv_t.wgsl +65 -0
- package/src/shaders/snrm2.wgsl +22 -4
- package/src/shaders/ssymv.wgsl +69 -0
- package/src/shaders/strmv.wgsl +103 -0
- package/src/shaders/strsv_apply_inverse.wgsl +47 -0
- package/src/shaders/strsv_invert_block.wgsl +109 -0
- package/src/shaders/strsv_update.wgsl +75 -0
- package/src/snrm2/snrm2.mjs +56 -52
- package/src/srot/srot.mjs +57 -41
- package/src/srotm/srotm.mjs +54 -38
- package/src/sscal/sscal.mjs +43 -32
- package/src/sswap/sswap.mjs +49 -34
- package/src/ssymv/ssymv.d.mts +109 -0
- package/src/ssymv/ssymv.mjs +130 -0
- package/src/strmv/strmv.d.mts +109 -0
- package/src/strmv/strmv.mjs +132 -0
- package/src/strsv/strsv.d.mts +98 -0
- package/src/strsv/strsv.mjs +212 -0
- package/src/util/benchmark.mjs +1 -1
- package/src/util/bindgroup.mjs +14 -10
- package/src/util/buffer.mjs +7 -2
- package/src/util/compute.mjs +41 -15
- package/src/util/f64pack.mjs +152 -0
- package/src/util/pipeline.mjs +32 -17
- package/src/util/result.mjs +8 -4
- package/src/util/workgroup.mjs +10 -10
package/index.d.mts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* @module docs
|
|
3
3
|
*/
|
|
4
4
|
export { GpuVector } from "./src/classes/GpuVector.mjs";
|
|
5
|
+
export { GpuMatrix } from "./src/classes/GpuMatrix.mjs";
|
|
5
6
|
export {
|
|
6
7
|
randomFloat32Array,
|
|
7
8
|
randomFloat64Array,
|
|
@@ -12,10 +13,15 @@ export { saxpy } from "./src/saxpy/saxpy.mjs";
|
|
|
12
13
|
export { scopy } from "./src/scopy/scopy.mjs";
|
|
13
14
|
export { sdot } from "./src/sdot/sdot.mjs";
|
|
14
15
|
export { sasum } from "./src/sasum/sasum.mjs";
|
|
16
|
+
export { dasum } from "./src/dasum/dasum.mjs";
|
|
15
17
|
export { snrm2 } from "./src/snrm2/snrm2.mjs";
|
|
16
18
|
export { isamax } from "./src/isamax/isamax.mjs";
|
|
17
19
|
export { srot } from "./src/srot/srot.mjs";
|
|
18
20
|
export { srotm } from "./src/srotm/srotm.mjs";
|
|
21
|
+
export { sgemv } from "./src/sgemv/sgemv.mjs";
|
|
22
|
+
export { ssymv } from "./src/ssymv/ssymv.mjs";
|
|
23
|
+
export { strmv } from "./src/strmv/strmv.mjs";
|
|
24
|
+
export { strsv } from "./src/strsv/strsv.mjs";
|
|
19
25
|
|
|
20
26
|
/**
|
|
21
27
|
* Initializes the WebGPU device.
|
package/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { init, cleanup, gpuName } from "./src/init.mjs";
|
|
2
2
|
export { GpuVector } from "./src/classes/GpuVector.mjs";
|
|
3
|
+
export { GpuMatrix } from "./src/classes/GpuMatrix.mjs";
|
|
3
4
|
export {
|
|
4
5
|
randomFloat32Array,
|
|
5
6
|
randomFloat64Array,
|
|
@@ -10,7 +11,12 @@ export { saxpy } from "./src/saxpy/saxpy.mjs";
|
|
|
10
11
|
export { scopy } from "./src/scopy/scopy.mjs";
|
|
11
12
|
export { sdot } from "./src/sdot/sdot.mjs";
|
|
12
13
|
export { sasum } from "./src/sasum/sasum.mjs";
|
|
14
|
+
export { dasum } from "./src/dasum/dasum.mjs";
|
|
13
15
|
export { snrm2 } from "./src/snrm2/snrm2.mjs";
|
|
14
16
|
export { isamax } from "./src/isamax/isamax.mjs";
|
|
15
17
|
export { srot } from "./src/srot/srot.mjs";
|
|
16
18
|
export { srotm } from "./src/srotm/srotm.mjs";
|
|
19
|
+
export { sgemv } from "./src/sgemv/sgemv.mjs";
|
|
20
|
+
export { ssymv } from "./src/ssymv/ssymv.mjs";
|
|
21
|
+
export { strmv } from "./src/strmv/strmv.mjs";
|
|
22
|
+
export { strsv } from "./src/strsv/strsv.mjs";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wgblas",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "BLAS on WebGPU",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.mjs",
|
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
"import": "./src/classes/GpuVector.mjs",
|
|
16
16
|
"types": "./src/classes/GpuVector.d.mts"
|
|
17
17
|
},
|
|
18
|
+
"./classes/GpuMatrix": {
|
|
19
|
+
"import": "./src/classes/GpuMatrix.mjs",
|
|
20
|
+
"types": "./src/classes/GpuMatrix.d.mts"
|
|
21
|
+
},
|
|
18
22
|
"./random": {
|
|
19
23
|
"import": "./src/random/random.mjs",
|
|
20
24
|
"types": "./src/random/random.d.mts"
|
|
@@ -43,6 +47,10 @@
|
|
|
43
47
|
"import": "./src/sasum/sasum.mjs",
|
|
44
48
|
"types": "./src/sasum/sasum.d.mts"
|
|
45
49
|
},
|
|
50
|
+
"./dasum": {
|
|
51
|
+
"import": "./src/dasum/dasum.mjs",
|
|
52
|
+
"types": "./src/dasum/dasum.d.mts"
|
|
53
|
+
},
|
|
46
54
|
"./snrm2": {
|
|
47
55
|
"import": "./src/snrm2/snrm2.mjs",
|
|
48
56
|
"types": "./src/snrm2/snrm2.d.mts"
|
|
@@ -58,6 +66,22 @@
|
|
|
58
66
|
"./srotm": {
|
|
59
67
|
"import": "./src/srotm/srotm.mjs",
|
|
60
68
|
"types": "./src/srotm/srotm.d.mts"
|
|
69
|
+
},
|
|
70
|
+
"./sgemv": {
|
|
71
|
+
"import": "./src/sgemv/sgemv.mjs",
|
|
72
|
+
"types": "./src/sgemv/sgemv.d.mts"
|
|
73
|
+
},
|
|
74
|
+
"./ssymv": {
|
|
75
|
+
"import": "./src/ssymv/ssymv.mjs",
|
|
76
|
+
"types": "./src/ssymv/ssymv.d.mts"
|
|
77
|
+
},
|
|
78
|
+
"./strmv": {
|
|
79
|
+
"import": "./src/strmv/strmv.mjs",
|
|
80
|
+
"types": "./src/strmv/strmv.d.mts"
|
|
81
|
+
},
|
|
82
|
+
"./strsv": {
|
|
83
|
+
"import": "./src/strsv/strsv.mjs",
|
|
84
|
+
"types": "./src/strsv/strsv.d.mts"
|
|
61
85
|
}
|
|
62
86
|
},
|
|
63
87
|
"files": [
|
|
@@ -89,17 +113,24 @@
|
|
|
89
113
|
"webgpu": "^0.4.0"
|
|
90
114
|
},
|
|
91
115
|
"devDependencies": {
|
|
116
|
+
"@commitlint/cli": "^21.2.1",
|
|
117
|
+
"@commitlint/config-conventional": "^21.2.0",
|
|
92
118
|
"@eslint/js": "^10.0.1",
|
|
119
|
+
"@stdlib/blas-base-dasum": "^0.4.1",
|
|
93
120
|
"@stdlib/blas-base-isamax": "^0.1.1",
|
|
94
121
|
"@stdlib/blas-base-sasum": "^0.3.1",
|
|
95
122
|
"@stdlib/blas-base-saxpy": "^0.3.1",
|
|
96
123
|
"@stdlib/blas-base-scopy": "^0.3.1",
|
|
97
124
|
"@stdlib/blas-base-sdot": "^0.3.1",
|
|
125
|
+
"@stdlib/blas-base-sgemv": "^0.1.1",
|
|
98
126
|
"@stdlib/blas-base-snrm2": "^0.3.1",
|
|
99
127
|
"@stdlib/blas-base-srot": "^0.2.1",
|
|
100
128
|
"@stdlib/blas-base-srotm": "^0.2.1",
|
|
101
129
|
"@stdlib/blas-base-sscal": "^0.3.1",
|
|
102
130
|
"@stdlib/blas-base-sswap": "^0.3.1",
|
|
131
|
+
"@stdlib/blas-base-ssymv": "^0.1.1",
|
|
132
|
+
"@stdlib/blas-base-strmv": "^0.1.0",
|
|
133
|
+
"@stdlib/blas-base-strsv": "^0.1.1",
|
|
103
134
|
"@stdlib/random-array-uniform": "^0.2.2",
|
|
104
135
|
"@stdlib/random-base-uniform": "^0.2.3",
|
|
105
136
|
"esbuild": "^0.28.1",
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Represents a row-major Float32Array matrix stored in GPU memory.
|
|
3
|
+
*
|
|
4
|
+
* The buffer holds `rows * lda` elements. `lda` (leading dimension) is the
|
|
5
|
+
* number of floats between the start of consecutive rows — must be >= `cols`.
|
|
6
|
+
* When `lda === cols` the matrix is dense with no padding.
|
|
7
|
+
*
|
|
8
|
+
* @see <a href="https://github.com/manit2004/wgblas/blob/main/src/classes/GpuMatrix.mjs#L7">Source code: GpuMatrix.mjs (L7)</a>
|
|
9
|
+
* @see [MDN: GPUBuffer](https://developer.mozilla.org/en-US/docs/Web/API/GPUBuffer)
|
|
10
|
+
* @category Classes
|
|
11
|
+
*/
|
|
12
|
+
export declare class GpuMatrix {
|
|
13
|
+
private constructor();
|
|
14
|
+
|
|
15
|
+
/** @internal */
|
|
16
|
+
readonly _buf: GPUBuffer;
|
|
17
|
+
|
|
18
|
+
/** Number of rows. */
|
|
19
|
+
readonly rows: number;
|
|
20
|
+
|
|
21
|
+
/** Number of columns. */
|
|
22
|
+
readonly cols: number;
|
|
23
|
+
|
|
24
|
+
/** Leading dimension — stride between row starts (>= cols). */
|
|
25
|
+
readonly lda: number;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Uploads a row-major Float32Array or Float64Array matrix to GPU memory. A
|
|
29
|
+
* Float64Array is packed as two f32s per element (WGSL has no f64 type)
|
|
30
|
+
* and stored across two GPU buffers internally; `read()` reassembles the
|
|
31
|
+
* original doubles.
|
|
32
|
+
*
|
|
33
|
+
* `lda` defaults to `cols` (dense, no padding between rows).
|
|
34
|
+
* `data` must have at least `rows * lda` elements.
|
|
35
|
+
*
|
|
36
|
+
* @param data - matrix data in row-major order
|
|
37
|
+
* @param rows - number of rows
|
|
38
|
+
* @param cols - number of columns
|
|
39
|
+
* @param lda - leading dimension (default: cols)
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```js
|
|
43
|
+
* import { init, GpuMatrix } from "wgblas";
|
|
44
|
+
*
|
|
45
|
+
* await init();
|
|
46
|
+
* // 2×3 matrix: [[1,2,3],[4,5,6]]
|
|
47
|
+
* const mat = GpuMatrix.from(new Float32Array([1,2,3,4,5,6]), 2, 3);
|
|
48
|
+
* console.log(mat.rows, mat.cols, mat.lda); // 2 3 3
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
static from(data: Float32Array | Float64Array, rows: number, cols: number, lda?: number): GpuMatrix;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Downloads the matrix from GPU memory and returns a dense row-major
|
|
55
|
+
* array of shape `rows × cols` — a `Float32Array`, or a `Float64Array` if
|
|
56
|
+
* this matrix was created from one. If `lda > cols`, the leading-dimension
|
|
57
|
+
* padding is stripped so the returned array is always tightly packed.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```js
|
|
61
|
+
* import { init, GpuMatrix } from "wgblas";
|
|
62
|
+
*
|
|
63
|
+
* await init();
|
|
64
|
+
* const mat = GpuMatrix.from(new Float32Array([1,2,3,4,5,6]), 2, 3);
|
|
65
|
+
* const data = await mat.read();
|
|
66
|
+
* console.log(data); // Float32Array [1, 2, 3, 4, 5, 6]
|
|
67
|
+
* ```
|
|
68
|
+
*/
|
|
69
|
+
read(): Promise<Float32Array | Float64Array>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Destroys the underlying GPU buffer. Call when the matrix is no longer
|
|
73
|
+
* needed to free GPU memory.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```js
|
|
77
|
+
* import { init, GpuMatrix } from "wgblas";
|
|
78
|
+
*
|
|
79
|
+
* await init();
|
|
80
|
+
* const mat = GpuMatrix.from(new Float32Array([1,2,3,4,5,6]), 2, 3);
|
|
81
|
+
* mat.destroy();
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
destroy(): void;
|
|
85
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { getDevice } from "../init.mjs";
|
|
2
|
+
import { uploadBuffer, stageReadback } from "../util/buffer.mjs";
|
|
3
|
+
import { extractResult } from "../util/result.mjs";
|
|
4
|
+
import { packF64, unpackF64 } from "../util/f64pack.mjs";
|
|
5
|
+
|
|
6
|
+
export class GpuMatrix {
|
|
7
|
+
constructor(buffer, rows, cols, lda, auxBuffer = null) {
|
|
8
|
+
this._buf = buffer;
|
|
9
|
+
// Non-null only for Float64Array-backed matrices — see GpuVector for why
|
|
10
|
+
// (packF64 splits each element into a "main"/_buf f32 and "aux"/_auxBuf
|
|
11
|
+
// raw u32 — never a Float32Array, see f64pack.mjs).
|
|
12
|
+
this._auxBuf = auxBuffer;
|
|
13
|
+
this.rows = rows;
|
|
14
|
+
this.cols = cols;
|
|
15
|
+
this.lda = lda;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Uploads a row-major Float32Array or Float64Array matrix to GPU memory.
|
|
20
|
+
* lda defaults to cols (dense, no padding between rows).
|
|
21
|
+
* data must have at least rows * lda elements.
|
|
22
|
+
*/
|
|
23
|
+
static from(data, rows, cols, lda = cols) {
|
|
24
|
+
if (!(data instanceof Float32Array) && !(data instanceof Float64Array))
|
|
25
|
+
throw new Error("GpuMatrix.from expects a Float32Array or Float64Array.");
|
|
26
|
+
if (!Number.isInteger(rows) || rows <= 0)
|
|
27
|
+
throw new Error("rows must be a positive integer.");
|
|
28
|
+
if (!Number.isInteger(cols) || cols <= 0)
|
|
29
|
+
throw new Error("cols must be a positive integer.");
|
|
30
|
+
if (!Number.isInteger(lda) || lda < cols)
|
|
31
|
+
throw new Error("lda must be an integer >= cols.");
|
|
32
|
+
if (data.length < rows * lda)
|
|
33
|
+
throw new Error(
|
|
34
|
+
"data does not have enough elements for the given rows and lda.",
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
if (data instanceof Float64Array) {
|
|
38
|
+
const n = rows * lda;
|
|
39
|
+
const main = new Float32Array(n);
|
|
40
|
+
const aux = new Uint32Array(n);
|
|
41
|
+
for (let i = 0; i < n; i++) {
|
|
42
|
+
const packed = packF64(data[i]);
|
|
43
|
+
main[i] = packed[0];
|
|
44
|
+
aux[i] = packed[1];
|
|
45
|
+
}
|
|
46
|
+
const mainBuf = uploadBuffer(main, "gpu-matrix-f64-main", true);
|
|
47
|
+
const auxBuf = uploadBuffer(aux, "gpu-matrix-f64-aux", true);
|
|
48
|
+
return new GpuMatrix(mainBuf, rows, cols, lda, auxBuf);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const buf = uploadBuffer(data.subarray(0, rows * lda), "gpu-matrix", true);
|
|
52
|
+
return new GpuMatrix(buf, rows, cols, lda);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async read() {
|
|
56
|
+
const device = getDevice();
|
|
57
|
+
const enc = device.createCommandEncoder();
|
|
58
|
+
const rb = stageReadback(enc, this._buf);
|
|
59
|
+
device.queue.submit([enc.finish()]);
|
|
60
|
+
|
|
61
|
+
if (this._auxBuf) {
|
|
62
|
+
const encAux = device.createCommandEncoder();
|
|
63
|
+
const rbAux = stageReadback(encAux, this._auxBuf);
|
|
64
|
+
device.queue.submit([encAux.finish()]);
|
|
65
|
+
|
|
66
|
+
const [main, aux] = await Promise.all([
|
|
67
|
+
extractResult(rb, Float32Array),
|
|
68
|
+
extractResult(rbAux, Uint32Array),
|
|
69
|
+
]);
|
|
70
|
+
const raw = new Float64Array(this.rows * this.lda);
|
|
71
|
+
for (let i = 0; i < raw.length; i++) raw[i] = unpackF64(main[i], aux[i]);
|
|
72
|
+
if (this.lda === this.cols) return raw;
|
|
73
|
+
const out = new Float64Array(this.rows * this.cols);
|
|
74
|
+
for (let r = 0; r < this.rows; r++)
|
|
75
|
+
out.set(raw.subarray(r * this.lda, r * this.lda + this.cols), r * this.cols);
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const raw = await extractResult(rb, Float32Array);
|
|
80
|
+
if (this.lda === this.cols) return raw;
|
|
81
|
+
const out = new Float32Array(this.rows * this.cols);
|
|
82
|
+
for (let r = 0; r < this.rows; r++)
|
|
83
|
+
out.set(raw.subarray(r * this.lda, r * this.lda + this.cols), r * this.cols);
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
destroy() {
|
|
88
|
+
this._buf.destroy();
|
|
89
|
+
if (this._auxBuf) this._auxBuf.destroy();
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -12,12 +12,13 @@ export declare class GpuVector {
|
|
|
12
12
|
/** Number of elements in the vector. */
|
|
13
13
|
readonly length: number;
|
|
14
14
|
|
|
15
|
-
// TODO: widen to Float32ArrayConstructor | Float64ArrayConstructor when Float64 support is added
|
|
16
15
|
/** Typed array constructor used when reading data back from the GPU. */
|
|
17
|
-
readonly dtype: Float32ArrayConstructor;
|
|
16
|
+
readonly dtype: Float32ArrayConstructor | Float64ArrayConstructor;
|
|
18
17
|
|
|
19
18
|
/**
|
|
20
|
-
* Uploads a Float32Array to GPU memory.
|
|
19
|
+
* Uploads a Float32Array or Float64Array to GPU memory. A Float64Array is
|
|
20
|
+
* packed as two f32s per element (WGSL has no f64 type) and stored across
|
|
21
|
+
* two GPU buffers internally; `read()` reassembles the original doubles.
|
|
21
22
|
*
|
|
22
23
|
* @param data - input vector data
|
|
23
24
|
* @returns GpuVector backed by a GPU buffer
|
|
@@ -29,15 +30,18 @@ export declare class GpuVector {
|
|
|
29
30
|
* await init();
|
|
30
31
|
* const vec = GpuVector.from(new Float32Array([1, 2, 3, 4]));
|
|
31
32
|
* console.log("length:", vec.length, "dtype:", vec.dtype.name);
|
|
33
|
+
*
|
|
34
|
+
* const dvec = GpuVector.from(new Float64Array([1.1, 2.2, 3.3]));
|
|
35
|
+
* console.log("dtype:", dvec.dtype.name); // Float64Array
|
|
32
36
|
* ```
|
|
33
37
|
*/
|
|
34
|
-
static from(data: Float32Array): GpuVector;
|
|
38
|
+
static from(data: Float32Array | Float64Array): GpuVector;
|
|
35
39
|
|
|
36
|
-
// TODO: return type will widen to Promise<Float32Array | Float64Array> when Float64 support is added
|
|
37
40
|
/**
|
|
38
41
|
* Reads the vector data back from GPU memory.
|
|
39
42
|
*
|
|
40
|
-
* @returns vector data as a Float32Array
|
|
43
|
+
* @returns vector data as a Float32Array, or a Float64Array if this vector
|
|
44
|
+
* was created from one
|
|
41
45
|
*
|
|
42
46
|
* @example
|
|
43
47
|
* ```js
|
|
@@ -49,7 +53,7 @@ export declare class GpuVector {
|
|
|
49
53
|
* console.log(data);
|
|
50
54
|
* ```
|
|
51
55
|
*/
|
|
52
|
-
read(): Promise<Float32Array>;
|
|
56
|
+
read(): Promise<Float32Array | Float64Array>;
|
|
53
57
|
|
|
54
58
|
/**
|
|
55
59
|
* Destroys the underlying GPU buffer. Call when the vector is no longer needed
|
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
import { getDevice } from "../init.mjs";
|
|
2
2
|
import { uploadBuffer, stageReadback } from "../util/buffer.mjs";
|
|
3
3
|
import { extractResult } from "../util/result.mjs";
|
|
4
|
-
|
|
5
|
-
//TODO: Need to update this when we support Float64Array.
|
|
4
|
+
import { packF64, unpackF64 } from "../util/f64pack.mjs";
|
|
6
5
|
|
|
7
6
|
export class GpuVector {
|
|
8
|
-
constructor(buffer, length, dtype = Float32Array) {
|
|
7
|
+
constructor(buffer, length, dtype = Float32Array, auxBuffer = null) {
|
|
9
8
|
this._buf = buffer;
|
|
9
|
+
this._auxBuf = auxBuffer;
|
|
10
10
|
this.length = length;
|
|
11
11
|
this.dtype = dtype;
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
static from(data) {
|
|
15
|
+
if (data instanceof Float64Array) {
|
|
16
|
+
const main = new Float32Array(data.length);
|
|
17
|
+
const aux = new Uint32Array(data.length);
|
|
18
|
+
for (let i = 0; i < data.length; i++) {
|
|
19
|
+
const packed = packF64(data[i]);
|
|
20
|
+
main[i] = packed[0];
|
|
21
|
+
aux[i] = packed[1];
|
|
22
|
+
}
|
|
23
|
+
const mainBuf = uploadBuffer(main, "gpu-vector-f64-main", true);
|
|
24
|
+
const auxBuf = uploadBuffer(aux, "gpu-vector-f64-aux", true);
|
|
25
|
+
return new GpuVector(mainBuf, data.length, Float64Array, auxBuf);
|
|
26
|
+
}
|
|
15
27
|
if (!(data instanceof Float32Array)) {
|
|
16
|
-
throw new Error("GpuVector.from expects a Float32Array.");
|
|
28
|
+
throw new Error("GpuVector.from expects a Float32Array or Float64Array.");
|
|
17
29
|
}
|
|
18
30
|
const buf = uploadBuffer(data, "gpu-vector", true);
|
|
19
31
|
return new GpuVector(buf, data.length, data.constructor);
|
|
@@ -24,10 +36,24 @@ export class GpuVector {
|
|
|
24
36
|
const enc = device.createCommandEncoder();
|
|
25
37
|
const rb = stageReadback(enc, this._buf);
|
|
26
38
|
device.queue.submit([enc.finish()]);
|
|
27
|
-
|
|
39
|
+
|
|
40
|
+
if (!this._auxBuf) return extractResult(rb, this.dtype);
|
|
41
|
+
|
|
42
|
+
const encAux = device.createCommandEncoder();
|
|
43
|
+
const rbAux = stageReadback(encAux, this._auxBuf);
|
|
44
|
+
device.queue.submit([encAux.finish()]);
|
|
45
|
+
|
|
46
|
+
const [main, aux] = await Promise.all([
|
|
47
|
+
extractResult(rb, Float32Array),
|
|
48
|
+
extractResult(rbAux, Uint32Array),
|
|
49
|
+
]);
|
|
50
|
+
const out = new Float64Array(this.length);
|
|
51
|
+
for (let i = 0; i < this.length; i++) out[i] = unpackF64(main[i], aux[i]);
|
|
52
|
+
return out;
|
|
28
53
|
}
|
|
29
54
|
|
|
30
55
|
destroy() {
|
|
31
56
|
this._buf.destroy();
|
|
57
|
+
if (this._auxBuf) this._auxBuf.destroy();
|
|
32
58
|
}
|
|
33
59
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { GpuVector } from "../classes/GpuVector.mjs";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Computes the sum of absolute values of a vector of doubles in double
|
|
5
|
+
* precision: result = sum(|x[i]|). Each element of `x` is packed into a
|
|
6
|
+
* [main, aux] f32 pair (see `packF64`/`GpuVector`) since WGSL has no f64
|
|
7
|
+
* type; accumulation is done with f64add.wgsl's IEEE-754 binary64 addition.
|
|
8
|
+
*
|
|
9
|
+
* {@includeCode ../../examples/dasum/dasum.js}
|
|
10
|
+
*
|
|
11
|
+
* **Browser (standalone HTML):**
|
|
12
|
+
* {@includeCode ../../examples/dasum/web/dasum.html}
|
|
13
|
+
*
|
|
14
|
+
* @param device - GPUDevice from `init()`
|
|
15
|
+
* @param n - number of elements (must be a positive integer)
|
|
16
|
+
* @param x - Float64Array input vector
|
|
17
|
+
* @param incx - stride for x (must be a positive integer)
|
|
18
|
+
* @returns absolute sum scalar — always a CPU readback, even for GpuVector inputs
|
|
19
|
+
* @see <a href="https://github.com/manit2004/wgblas/blob/main/src/dasum/dasum.mjs#L18">Source code: dasum.mjs (L18)</a>
|
|
20
|
+
* @category BLAS Level 1
|
|
21
|
+
*/
|
|
22
|
+
export declare function dasum(
|
|
23
|
+
device: GPUDevice,
|
|
24
|
+
n: number,
|
|
25
|
+
x: Float64Array,
|
|
26
|
+
incx: number,
|
|
27
|
+
): Promise<{ asum: number } | { asum: number; gpuTimeMs: number }>;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Computes the sum of absolute values of a vector of doubles in double
|
|
31
|
+
* precision: result = sum(|x[i]|).
|
|
32
|
+
*
|
|
33
|
+
* {@includeCode ../../examples/dasum/gpuvec.dasum.js}
|
|
34
|
+
*
|
|
35
|
+
* @param device - GPUDevice from `init()`
|
|
36
|
+
* @param n - number of elements (must be a positive integer)
|
|
37
|
+
* @param x - Float64Array-backed GpuVector input vector
|
|
38
|
+
* @param incx - stride for x (must be a positive integer)
|
|
39
|
+
* @returns absolute sum scalar — always a CPU readback, even for GpuVector inputs
|
|
40
|
+
* @see <a href="https://github.com/manit2004/wgblas/blob/main/src/dasum/dasum.mjs#L18">Source code: dasum.mjs (L18)</a>
|
|
41
|
+
* @category BLAS Level 1
|
|
42
|
+
*/
|
|
43
|
+
export declare function dasum(
|
|
44
|
+
device: GPUDevice,
|
|
45
|
+
n: number,
|
|
46
|
+
x: GpuVector,
|
|
47
|
+
incx: number,
|
|
48
|
+
): Promise<{ asum: number } | { asum: number; gpuTimeMs: number }>;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createStorageBuffer,
|
|
3
|
+
createParamsBuffer,
|
|
4
|
+
createResultBuffer,
|
|
5
|
+
stageReadback,
|
|
6
|
+
destroyBuffers,
|
|
7
|
+
} from "../util/buffer.mjs";
|
|
8
|
+
import { createBindGroup } from "../util/bindgroup.mjs";
|
|
9
|
+
import { runComputePass, submit } from "../util/compute.mjs";
|
|
10
|
+
import { extractTimestamp } from "../util/benchmark.mjs";
|
|
11
|
+
import { extractResult } from "../util/result.mjs";
|
|
12
|
+
import { getPipeline } from "../util/pipeline.mjs";
|
|
13
|
+
import { unpackF64 } from "../util/f64pack.mjs";
|
|
14
|
+
import { GpuVector } from "../classes/GpuVector.mjs";
|
|
15
|
+
|
|
16
|
+
const WGS = 64; // workgroup size
|
|
17
|
+
|
|
18
|
+
export async function dasum(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 Float64Array))
|
|
27
|
+
throw new Error("x must be a Float64Array or GpuVector.");
|
|
28
|
+
if (xIsGpu && x.dtype !== Float64Array)
|
|
29
|
+
throw new Error("x must be a Float64Array-backed GpuVector.");
|
|
30
|
+
if (n <= 0) return { asum: 0 };
|
|
31
|
+
if (x.length < (n - 1) * incx + 1)
|
|
32
|
+
throw new Error(
|
|
33
|
+
"x does not have enough elements for the given n and incx.",
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
// dasum.wgsl/reduction/sumF64.wgsl are each concatenated with f64add.wgsl
|
|
37
|
+
// (reusing its decode/encode/computeSum/Packed — WGSL has no #include).
|
|
38
|
+
// f64add.wgsl declares no bindings/entry point of its own, so each
|
|
39
|
+
// concatenated module has exactly one @compute entry — omitting entryPoint
|
|
40
|
+
// here lets getPipeline auto-detect it, the stable path (see pipeline.mjs).
|
|
41
|
+
const pipelineMain = await getPipeline(device, ["f64add", "dasum"]);
|
|
42
|
+
const pipelineReduce = await getPipeline(device, ["f64add", "reduction/sumF64"]);
|
|
43
|
+
|
|
44
|
+
let xVec = null;
|
|
45
|
+
let partialsMainBuffer = null;
|
|
46
|
+
let partialsAuxBuffer = null;
|
|
47
|
+
let resultMainBuffer = null;
|
|
48
|
+
let resultAuxBuffer = null;
|
|
49
|
+
let paramsBuffer = null;
|
|
50
|
+
let readMainBuffer = null;
|
|
51
|
+
let readAuxBuffer = null;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
xVec = xIsGpu ? x : GpuVector.from(x);
|
|
55
|
+
partialsMainBuffer = createStorageBuffer(2 * WGS * 4, "dasum-partialsMain"); // 2*WGS partial sums, main halves
|
|
56
|
+
partialsAuxBuffer = createStorageBuffer(2 * WGS * 4, "dasum-partialsAux"); // 2*WGS partial sums, aux halves (raw u32 bits)
|
|
57
|
+
resultMainBuffer = createResultBuffer(4, "dasum-result-main"); // final main half
|
|
58
|
+
resultAuxBuffer = createResultBuffer(4, "dasum-result-aux"); // final aux half (raw u32 bits)
|
|
59
|
+
paramsBuffer = createParamsBuffer(
|
|
60
|
+
[
|
|
61
|
+
{ value: n, type: "u32" },
|
|
62
|
+
{ value: incx, type: "u32" },
|
|
63
|
+
],
|
|
64
|
+
"dasum-params",
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
const bgMain = createBindGroup(
|
|
68
|
+
pipelineMain.getBindGroupLayout(0),
|
|
69
|
+
[xVec._buf, xVec._auxBuf, partialsMainBuffer, partialsAuxBuffer, paramsBuffer],
|
|
70
|
+
);
|
|
71
|
+
const { commandEncoder: enc1, ts: ts1 } = runComputePass(
|
|
72
|
+
pipelineMain,
|
|
73
|
+
bgMain,
|
|
74
|
+
2 * WGS,
|
|
75
|
+
); // dispatch 2*WGS workgroups
|
|
76
|
+
|
|
77
|
+
submit(enc1);
|
|
78
|
+
|
|
79
|
+
const bgReduce = createBindGroup(
|
|
80
|
+
pipelineReduce.getBindGroupLayout(0),
|
|
81
|
+
[partialsMainBuffer, partialsAuxBuffer, resultMainBuffer, resultAuxBuffer],
|
|
82
|
+
);
|
|
83
|
+
const { commandEncoder: enc2, ts: ts2 } = runComputePass(
|
|
84
|
+
pipelineReduce,
|
|
85
|
+
bgReduce,
|
|
86
|
+
1,
|
|
87
|
+
); // dispatch 1 workgroup to reduce the partial sums to a single result
|
|
88
|
+
readMainBuffer = stageReadback(enc2, resultMainBuffer);
|
|
89
|
+
readAuxBuffer = stageReadback(enc2, resultAuxBuffer);
|
|
90
|
+
|
|
91
|
+
submit(enc2);
|
|
92
|
+
|
|
93
|
+
const mainPromise = extractResult(readMainBuffer, Float32Array);
|
|
94
|
+
const auxPromise = extractResult(readAuxBuffer, Uint32Array);
|
|
95
|
+
readMainBuffer = null; // ownership transferred — extractResult's own finally destroys it
|
|
96
|
+
readAuxBuffer = null;
|
|
97
|
+
|
|
98
|
+
const [gpuTime1, gpuTime2, mainArr, auxArr] = await Promise.all([
|
|
99
|
+
extractTimestamp(ts1),
|
|
100
|
+
extractTimestamp(ts2),
|
|
101
|
+
mainPromise,
|
|
102
|
+
auxPromise,
|
|
103
|
+
]);
|
|
104
|
+
|
|
105
|
+
// asum is always a scalar readback — both paths return { asum }
|
|
106
|
+
const asum = unpackF64(mainArr[0], auxArr[0]);
|
|
107
|
+
if (gpuTime1 !== undefined && gpuTime2 !== undefined)
|
|
108
|
+
return { asum, gpuTimeMs: gpuTime1 + gpuTime2 };
|
|
109
|
+
return { asum };
|
|
110
|
+
} finally {
|
|
111
|
+
if (!xIsGpu && xVec) xVec.destroy();
|
|
112
|
+
if (partialsMainBuffer) destroyBuffers(partialsMainBuffer);
|
|
113
|
+
if (partialsAuxBuffer) destroyBuffers(partialsAuxBuffer);
|
|
114
|
+
if (resultMainBuffer) destroyBuffers(resultMainBuffer);
|
|
115
|
+
if (resultAuxBuffer) destroyBuffers(resultAuxBuffer);
|
|
116
|
+
if (paramsBuffer) destroyBuffers(paramsBuffer);
|
|
117
|
+
// Only reached if submit(enc2) threw before ownership was transferred above.
|
|
118
|
+
if (readMainBuffer) destroyBuffers(readMainBuffer);
|
|
119
|
+
if (readAuxBuffer) destroyBuffers(readAuxBuffer);
|
|
120
|
+
}
|
|
121
|
+
}
|
package/src/init.mjs
CHANGED
|
@@ -41,7 +41,9 @@ export async function init({
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
_benchmarkEnabled = benchmark;
|
|
44
|
-
|
|
44
|
+
const bmConfig = benchmarkMode(_adapter, benchmark);
|
|
45
|
+
const features = [...(bmConfig.requiredFeatures ?? [])];
|
|
46
|
+
_device = await _adapter.requestDevice({ requiredFeatures: features });
|
|
45
47
|
// Fires for any GPU error not caught by a pushErrorScope/popErrorScope pair — surfaces silent GPU failures to the console.
|
|
46
48
|
// See: https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/uncapturederror_event
|
|
47
49
|
_device.addEventListener("uncapturederror", (e) => {
|