wgblas 0.1.2 → 1.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.
Files changed (64) hide show
  1. package/README.md +3 -0
  2. package/dist/wgblas.browser.js +1249 -37
  3. package/index.d.mts +9 -0
  4. package/index.mjs +9 -0
  5. package/package.json +47 -1
  6. package/src/classes/GpuMatrix.d.mts +98 -0
  7. package/src/classes/GpuMatrix.mjs +109 -0
  8. package/src/classes/GpuVector.d.mts +11 -7
  9. package/src/classes/GpuVector.mjs +31 -5
  10. package/src/dasum/dasum.d.mts +48 -0
  11. package/src/dasum/dasum.mjs +121 -0
  12. package/src/init.mjs +3 -1
  13. package/src/isamax/isamax.mjs +66 -67
  14. package/src/random/random.d.mts +37 -0
  15. package/src/random/random.mjs +17 -0
  16. package/src/sasum/sasum.mjs +55 -51
  17. package/src/saxpy/saxpy.mjs +48 -35
  18. package/src/scopy/scopy.mjs +43 -32
  19. package/src/sdot/sdot.mjs +60 -55
  20. package/src/sgemv/sgemv.d.mts +127 -0
  21. package/src/sgemv/sgemv.mjs +148 -0
  22. package/src/sger/sger.d.mts +111 -0
  23. package/src/sger/sger.mjs +136 -0
  24. package/src/shaders/browser-shaders.mjs +26 -0
  25. package/src/shaders/dasum.wgsl +98 -0
  26. package/src/shaders/f64add.wgsl +281 -0
  27. package/src/shaders/isamax.wgsl +32 -9
  28. package/src/shaders/reduction/sumF64.wgsl +49 -0
  29. package/src/shaders/sasum.wgsl +18 -4
  30. package/src/shaders/sdot.wgsl +18 -4
  31. package/src/shaders/sgemv_n.wgsl +75 -0
  32. package/src/shaders/sgemv_t.wgsl +65 -0
  33. package/src/shaders/sger.wgsl +48 -0
  34. package/src/shaders/snrm2.wgsl +22 -4
  35. package/src/shaders/ssymv.wgsl +69 -0
  36. package/src/shaders/ssyr.wgsl +60 -0
  37. package/src/shaders/ssyr2.wgsl +63 -0
  38. package/src/shaders/strmv.wgsl +103 -0
  39. package/src/shaders/strsv_apply_inverse.wgsl +47 -0
  40. package/src/shaders/strsv_invert_block.wgsl +109 -0
  41. package/src/shaders/strsv_update.wgsl +75 -0
  42. package/src/snrm2/snrm2.mjs +56 -52
  43. package/src/srot/srot.mjs +57 -41
  44. package/src/srotm/srotm.mjs +54 -38
  45. package/src/sscal/sscal.mjs +43 -32
  46. package/src/sswap/sswap.mjs +49 -34
  47. package/src/ssymv/ssymv.d.mts +117 -0
  48. package/src/ssymv/ssymv.mjs +135 -0
  49. package/src/ssyr/ssyr.d.mts +100 -0
  50. package/src/ssyr/ssyr.mjs +106 -0
  51. package/src/ssyr2/ssyr2.d.mts +112 -0
  52. package/src/ssyr2/ssyr2.mjs +130 -0
  53. package/src/strmv/strmv.d.mts +117 -0
  54. package/src/strmv/strmv.mjs +138 -0
  55. package/src/strsv/strsv.d.mts +106 -0
  56. package/src/strsv/strsv.mjs +207 -0
  57. package/src/util/benchmark.mjs +1 -1
  58. package/src/util/bindgroup.mjs +14 -10
  59. package/src/util/buffer.mjs +7 -2
  60. package/src/util/compute.mjs +41 -15
  61. package/src/util/f64pack.mjs +152 -0
  62. package/src/util/pipeline.mjs +32 -17
  63. package/src/util/result.mjs +8 -4
  64. package/src/util/workgroup.mjs +10 -10
@@ -0,0 +1,138 @@
1
+ import {
2
+ uploadBuffer,
3
+ createParamsBuffer,
4
+ stageReadback,
5
+ destroyBuffers,
6
+ } from "../util/buffer.mjs";
7
+ import { createBindGroup } from "../util/bindgroup.mjs";
8
+ import { runComputePass, submit } from "../util/compute.mjs";
9
+ import { extractResult } from "../util/result.mjs";
10
+ import { extractTimestamp } from "../util/benchmark.mjs";
11
+ import { getPipeline } from "../util/pipeline.mjs";
12
+ import { GpuVector } from "../classes/GpuVector.mjs";
13
+ import { GpuMatrix } from "../classes/GpuMatrix.mjs";
14
+
15
+ export async function strmv(device, uplo, trans, diag, n, A, lda, x, incx, y, incy, layout = "row-major") {
16
+ const xIsGpu = x instanceof GpuVector;
17
+ const yIsGpu = y instanceof GpuVector;
18
+ const AIsGpu = A instanceof GpuMatrix;
19
+ const isUnit = diag === "unit";
20
+
21
+ if (!(device instanceof GPUDevice))
22
+ throw new Error("device must be a GPUDevice.");
23
+ if (uplo !== "lower" && uplo !== "upper")
24
+ throw new Error("uplo must be 'lower' or 'upper'.");
25
+ if (trans !== "no-transpose" && trans !== "transpose")
26
+ throw new Error("trans must be 'no-transpose' or 'transpose'.");
27
+ if (!isUnit && diag !== "non-unit")
28
+ throw new Error("diag must be 'unit' or 'non-unit'.");
29
+ if (layout !== "row-major" && layout !== "column-major")
30
+ throw new Error("layout must be 'row-major' or 'column-major'.");
31
+ if (
32
+ !Number.isInteger(n) ||
33
+ !Number.isInteger(incx) ||
34
+ !Number.isInteger(incy) ||
35
+ !Number.isInteger(lda)
36
+ )
37
+ throw new Error("n, incx, incy, and lda must be integers.");
38
+ if (incx <= 0 || incy <= 0)
39
+ throw new Error("incx and incy must be positive.");
40
+ if (lda < n) throw new Error("lda must be >= n.");
41
+ if (!AIsGpu && !(A instanceof Float32Array))
42
+ throw new Error("A must be a Float32Array or GpuMatrix.");
43
+ if (!xIsGpu && !(x instanceof Float32Array))
44
+ throw new Error("x must be a Float32Array or GpuVector.");
45
+ if (!yIsGpu && !(y instanceof Float32Array))
46
+ throw new Error("y must be a Float32Array or GpuVector.");
47
+ if (xIsGpu !== yIsGpu)
48
+ throw new Error(
49
+ "x and y must be the same type (both Float32Array or both GpuVector).",
50
+ );
51
+ if (xIsGpu && x._buf === y._buf)
52
+ throw new Error(
53
+ "x and y must not reference the same GPU buffer when both are GpuVectors.",
54
+ );
55
+ if (xIsGpu && !AIsGpu)
56
+ throw new Error("A must be a GpuMatrix when x and y are GpuVectors.");
57
+ if (AIsGpu && yIsGpu && A._buf === y._buf)
58
+ throw new Error("A and y must not reference the same GPU buffer.");
59
+ if (AIsGpu && lda !== A.lda)
60
+ throw new Error("lda must match A.lda when A is a GpuMatrix.");
61
+ if (AIsGpu && (A.rows < n || A.cols < n))
62
+ throw new Error("A is too small for the given n.");
63
+ if (n < 0) throw new Error("n must be non-negative.");
64
+ if (n === 0) return yIsGpu ? {} : { y };
65
+
66
+ if (!AIsGpu && A.length < (n - 1) * lda + n)
67
+ throw new Error(
68
+ "A does not have enough elements for the given n and lda.",
69
+ );
70
+ if (x.length < (n - 1) * incx + 1)
71
+ throw new Error(
72
+ "x does not have enough elements for the given n and incx.",
73
+ );
74
+ if (y.length < (n - 1) * incy + 1)
75
+ throw new Error(
76
+ "y does not have enough elements for the given n and incy.",
77
+ );
78
+
79
+ // GpuMatrix's own layout wins over the argument; column-major A reinterpreted row-major is A^T, so flip both uplo and trans to reproduce the requested op(A).
80
+ const effLayout = AIsGpu ? A.layout : layout;
81
+ const isColMajor = effLayout === "column-major";
82
+ const isLower = isColMajor ? uplo === "upper" : uplo === "lower";
83
+ const isNoTrans = isColMajor ? trans === "transpose" : trans === "no-transpose";
84
+
85
+ const pipeline = await getPipeline(device, "strmv");
86
+
87
+ let ABuffer = null;
88
+ let xBuffer = null;
89
+ let yBuffer = null;
90
+ let paramsBuffer = null;
91
+
92
+ try {
93
+ ABuffer = AIsGpu ? A._buf : uploadBuffer(A, "strmv-A", false);
94
+ xBuffer = xIsGpu ? x._buf : uploadBuffer(x, "strmv-x", false);
95
+ yBuffer = yIsGpu ? y._buf : uploadBuffer(y, "strmv-y", true);
96
+ paramsBuffer = createParamsBuffer(
97
+ [
98
+ { value: n, type: "u32" },
99
+ { value: incx, type: "u32" },
100
+ { value: incy, type: "u32" },
101
+ { value: lda, type: "u32" },
102
+ { value: isNoTrans ? 0 : 1, type: "u32" },
103
+ { value: isLower ? 0 : 1, type: "u32" },
104
+ { value: isUnit ? 1 : 0, type: "u32" },
105
+ ],
106
+ "strmv-params",
107
+ );
108
+
109
+ const bindGroup = createBindGroup(pipeline.getBindGroupLayout(0), [
110
+ ABuffer,
111
+ xBuffer,
112
+ yBuffer,
113
+ paramsBuffer,
114
+ ]);
115
+
116
+ const wgCount = Math.min(n, device.limits.maxComputeWorkgroupsPerDimension);
117
+ const { commandEncoder, ts } = runComputePass(pipeline, bindGroup, wgCount);
118
+ const readBuffer = yIsGpu ? null : stageReadback(commandEncoder, yBuffer);
119
+
120
+ submit(commandEncoder);
121
+
122
+ const gpuTimeMs = await extractTimestamp(ts);
123
+
124
+ if (yIsGpu) {
125
+ if (gpuTimeMs !== undefined) return { gpuTimeMs };
126
+ return {};
127
+ }
128
+
129
+ const result = await extractResult(readBuffer, Float32Array);
130
+ if (gpuTimeMs !== undefined) return { y: result, gpuTimeMs };
131
+ return { y: result };
132
+ } finally {
133
+ if (!AIsGpu && ABuffer) destroyBuffers(ABuffer);
134
+ if (!xIsGpu && xBuffer) destroyBuffers(xBuffer);
135
+ if (!yIsGpu && yBuffer) destroyBuffers(yBuffer);
136
+ if (paramsBuffer) destroyBuffers(paramsBuffer);
137
+ }
138
+ }
@@ -0,0 +1,106 @@
1
+ import { GpuVector } from "../classes/GpuVector.mjs";
2
+ import { GpuMatrix } from "../classes/GpuMatrix.mjs";
3
+
4
+ /**
5
+ * Solves the triangular system op(A) * x = b for x, in place (x holds b on
6
+ * input, the solution on output).
7
+ *
8
+ * A is an n×n triangular matrix stored in row-major order. Only the triangle
9
+ * specified by `uplo` is referenced; the other triangle is not accessed.
10
+ *
11
+ * {@includeCode ../../examples/strsv/strsv.js}
12
+ *
13
+ * **Browser (standalone HTML):**
14
+ * {@includeCode ../../examples/strsv/web/strsv.html}
15
+ *
16
+ * @param device - GPUDevice from `init()`
17
+ * @param uplo - `'lower'` to use the lower triangle, `'upper'` to use the upper triangle
18
+ * @param trans - `'no-transpose'` to solve A*x=b, `'transpose'` to solve A^T*x=b
19
+ * @param diag - `'unit'` to treat the diagonal as all-ones (A's diagonal is not read), `'non-unit'` to read it
20
+ * @param n - order of the matrix A (number of rows and columns)
21
+ * @param A - Float32Array, row-major or column-major (see `layout`), at least (n-1)*lda+n elements
22
+ * @param lda - leading dimension of A (>= n either way — A is square)
23
+ * @param x - Float32Array holding b on input, the solution on output; length at least (n-1)*incx+1
24
+ * @param incx - stride for x (must be a positive integer)
25
+ * @param layout - storage layout of `A` (default: `'row-major'`); column-major
26
+ * flips both the stored triangle and the effective `trans` (the system
27
+ * being solved stays what you asked for either way)
28
+ * @see <a href="https://github.com/manit2004/wgblas/blob/main/src/strsv/strsv.mjs#L15">Source code: strsv.mjs (L15)</a>
29
+ * @category BLAS Level 2
30
+ */
31
+ export declare function strsv(
32
+ device: GPUDevice,
33
+ uplo: 'lower' | 'upper',
34
+ trans: 'no-transpose' | 'transpose',
35
+ diag: 'unit' | 'non-unit',
36
+ n: number,
37
+ A: Float32Array,
38
+ lda: number,
39
+ x: Float32Array,
40
+ incx: number,
41
+ layout?: 'row-major' | 'column-major',
42
+ ): Promise<{ x: Float32Array; gpuTimeMs?: number }>;
43
+
44
+ /**
45
+ * Solves the triangular system op(A) * x = b for x, in place.
46
+ *
47
+ * A is kept GPU-resident; x is a CPU Float32Array. `A`'s own `layout` (set at
48
+ * `GpuMatrix.from` time) determines the operation — there is no separate
49
+ * `layout` argument here.
50
+ *
51
+ * @param device - GPUDevice from `init()`
52
+ * @param uplo - `'lower'` to use the lower triangle, `'upper'` to use the upper triangle
53
+ * @param trans - `'no-transpose'` to solve A*x=b, `'transpose'` to solve A^T*x=b
54
+ * @param diag - `'unit'` to treat the diagonal as all-ones (A's diagonal is not read), `'non-unit'` to read it
55
+ * @param n - order of the matrix A
56
+ * @param A - GpuMatrix, GPU-resident
57
+ * @param lda - leading dimension of A (must equal A.lda)
58
+ * @param x - Float32Array holding b on input, the solution on output
59
+ * @param incx - stride for x (must be a positive integer)
60
+ * @see <a href="https://github.com/manit2004/wgblas/blob/main/src/strsv/strsv.mjs#L15">Source code: strsv.mjs (L15)</a>
61
+ * @category BLAS Level 2
62
+ */
63
+ export declare function strsv(
64
+ device: GPUDevice,
65
+ uplo: 'lower' | 'upper',
66
+ trans: 'no-transpose' | 'transpose',
67
+ diag: 'unit' | 'non-unit',
68
+ n: number,
69
+ A: GpuMatrix,
70
+ lda: number,
71
+ x: Float32Array,
72
+ incx: number,
73
+ ): Promise<{ x: Float32Array; gpuTimeMs?: number }>;
74
+
75
+ /**
76
+ * Solves the triangular system op(A) * x = b for x, in place.
77
+ *
78
+ * x is kept resident on the GPU (mutated in place). A must be a GpuMatrix;
79
+ * its own `layout` (set at `GpuMatrix.from` time) determines the operation —
80
+ * there is no separate `layout` argument here.
81
+ *
82
+ * {@includeCode ../../examples/strsv/gpuvec.strsv.js}
83
+ *
84
+ * @param device - GPUDevice from `init()`
85
+ * @param uplo - `'lower'` to use the lower triangle, `'upper'` to use the upper triangle
86
+ * @param trans - `'no-transpose'` to solve A*x=b, `'transpose'` to solve A^T*x=b
87
+ * @param diag - `'unit'` to treat the diagonal as all-ones (A's diagonal is not read), `'non-unit'` to read it
88
+ * @param n - order of the matrix A
89
+ * @param A - GpuMatrix, GPU-resident
90
+ * @param lda - leading dimension of A (must equal A.lda)
91
+ * @param x - GpuVector holding b on input, the solution on output (mutated in place)
92
+ * @param incx - stride for x (must be a positive integer)
93
+ * @see <a href="https://github.com/manit2004/wgblas/blob/main/src/strsv/strsv.mjs#L15">Source code: strsv.mjs (L15)</a>
94
+ * @category BLAS Level 2
95
+ */
96
+ export declare function strsv(
97
+ device: GPUDevice,
98
+ uplo: 'lower' | 'upper',
99
+ trans: 'no-transpose' | 'transpose',
100
+ diag: 'unit' | 'non-unit',
101
+ n: number,
102
+ A: GpuMatrix,
103
+ lda: number,
104
+ x: GpuVector,
105
+ incx: number,
106
+ ): Promise<{ gpuTimeMs?: number }>;
@@ -0,0 +1,207 @@
1
+ import {
2
+ uploadBuffer,
3
+ createParamsBuffer,
4
+ createStorageBuffer,
5
+ stageReadback,
6
+ destroyBuffers,
7
+ } from "../util/buffer.mjs";
8
+ import { createBindGroup } from "../util/bindgroup.mjs";
9
+ import { beginTimedEncoder, encodePass, submit } from "../util/compute.mjs";
10
+ import { extractResult } from "../util/result.mjs";
11
+ import { resolveTimestamp, extractTimestamp } from "../util/benchmark.mjs";
12
+ import { getPipeline } from "../util/pipeline.mjs";
13
+ import { GpuVector } from "../classes/GpuVector.mjs";
14
+ import { GpuMatrix } from "../classes/GpuMatrix.mjs";
15
+
16
+ // Blocked triangular solve via explicit block inversion (invert/apply/update passes) instead of barrier-per-row substitution.
17
+ const BLOCK_SIZE = 64;
18
+
19
+ // One shared buffer holds all blocks' params (offset blockIndex*stride) instead of one buffer per block — avoids the O(numBlocks) createBuffer/writeBuffer calls that dominated CPU time.
20
+ function packBlockParams(numBlocks, stride, fieldsPerBlock) {
21
+ const data = new ArrayBuffer(numBlocks * stride);
22
+ const view = new DataView(data);
23
+ for (let blockIndex = 0; blockIndex < numBlocks; blockIndex++) {
24
+ const fields = fieldsPerBlock(blockIndex);
25
+ const base = blockIndex * stride;
26
+ fields.forEach((value, i) => view.setUint32(base + i * 4, value, true));
27
+ }
28
+ return data;
29
+ }
30
+
31
+ function createSharedParamsBuffer(device, data, label) {
32
+ const buffer = device.createBuffer({
33
+ label,
34
+ size: data.byteLength,
35
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
36
+ });
37
+ device.queue.writeBuffer(buffer, 0, data);
38
+ return buffer;
39
+ }
40
+
41
+ export async function strsv(device, uplo, trans, diag, n, A, lda, x, incx, layout = "row-major") {
42
+ const xIsGpu = x instanceof GpuVector;
43
+ const AIsGpu = A instanceof GpuMatrix;
44
+ const isUnit = diag === "unit";
45
+
46
+ if (!(device instanceof GPUDevice))
47
+ throw new Error("device must be a GPUDevice.");
48
+ if (uplo !== "lower" && uplo !== "upper")
49
+ throw new Error("uplo must be 'lower' or 'upper'.");
50
+ if (trans !== "no-transpose" && trans !== "transpose")
51
+ throw new Error("trans must be 'no-transpose' or 'transpose'.");
52
+ if (!isUnit && diag !== "non-unit")
53
+ throw new Error("diag must be 'unit' or 'non-unit'.");
54
+ if (layout !== "row-major" && layout !== "column-major")
55
+ throw new Error("layout must be 'row-major' or 'column-major'.");
56
+ if (!Number.isInteger(n) || !Number.isInteger(incx) || !Number.isInteger(lda))
57
+ throw new Error("n, incx, and lda must be integers.");
58
+ if (incx <= 0) throw new Error("incx must be positive.");
59
+ if (lda < n) throw new Error("lda must be >= n.");
60
+ if (!AIsGpu && !(A instanceof Float32Array))
61
+ throw new Error("A must be a Float32Array or GpuMatrix.");
62
+ if (!xIsGpu && !(x instanceof Float32Array))
63
+ throw new Error("x must be a Float32Array or GpuVector.");
64
+ if (xIsGpu && !AIsGpu)
65
+ throw new Error("A must be a GpuMatrix when x is a GpuVector.");
66
+ if (AIsGpu && lda !== A.lda)
67
+ throw new Error("lda must match A.lda when A is a GpuMatrix.");
68
+ if (AIsGpu && (A.rows < n || A.cols < n))
69
+ throw new Error("A is too small for the given n.");
70
+ if (n < 0) throw new Error("n must be non-negative.");
71
+ if (n === 0) return xIsGpu ? {} : { x };
72
+
73
+ if (!AIsGpu && A.length < (n - 1) * lda + n)
74
+ throw new Error(
75
+ "A does not have enough elements for the given n and lda.",
76
+ );
77
+ if (x.length < (n - 1) * incx + 1)
78
+ throw new Error(
79
+ "x does not have enough elements for the given n and incx.",
80
+ );
81
+
82
+ // GpuMatrix's own layout wins over the argument; column-major A reinterpreted row-major is A^T, so flip both uplo and trans to reproduce the requested system.
83
+ const effLayout = AIsGpu ? A.layout : layout;
84
+ const isColMajor = effLayout === "column-major";
85
+ const isLower = isColMajor ? uplo === "upper" : uplo === "lower";
86
+ const isNoTrans = isColMajor ? trans === "transpose" : trans === "no-transpose";
87
+
88
+ const invertPipeline = await getPipeline(device, "strsv_invert_block");
89
+ const applyPipeline = await getPipeline(device, "strsv_apply_inverse");
90
+ const updatePipeline = await getPipeline(device, "strsv_update");
91
+
92
+ // Same forward/backward pairing the shaders use.
93
+ const forward = isNoTrans === isLower;
94
+ const blockStarts = [];
95
+ for (let s = 0; s < n; s += BLOCK_SIZE) blockStarts.push(s);
96
+ if (!forward) blockStarts.reverse();
97
+ const numBlocks = blockStarts.length;
98
+
99
+ const maxWg = device.limits.maxComputeWorkgroupsPerDimension;
100
+ const stride = device.limits.minUniformBufferOffsetAlignment;
101
+
102
+ let ABuffer = null;
103
+ let xBuffer = null;
104
+ let AinvBuffer = null;
105
+ let applyParamsBuffer = null;
106
+ let updateParamsBuffer = null;
107
+ let invertParams = null;
108
+
109
+ try {
110
+ ABuffer = AIsGpu ? A._buf : uploadBuffer(A, "strsv-A", false);
111
+ xBuffer = xIsGpu ? x._buf : uploadBuffer(x, "strsv-x", true);
112
+ // One BLOCK_SIZE x BLOCK_SIZE dense region per block (row-major), even
113
+ // though only a triangular half is ever nonzero — see strsv_invert_block.wgsl.
114
+ AinvBuffer = createStorageBuffer(
115
+ numBlocks * BLOCK_SIZE * BLOCK_SIZE * 4,
116
+ "strsv-Ainv",
117
+ );
118
+
119
+ // Every block's {blockStart, blockEnd} is fixed by its natural index
120
+ // regardless of traversal direction, so both packed buffers are indexed
121
+ // by blockIndex (0..numBlocks-1), not by loop position.
122
+ const applyData = packBlockParams(numBlocks, stride, (blockIndex) => {
123
+ const blockStart = blockIndex * BLOCK_SIZE;
124
+ const blockEnd = Math.min(blockStart + BLOCK_SIZE, n);
125
+ return [incx, blockIndex, blockStart, blockEnd];
126
+ });
127
+ applyParamsBuffer = createSharedParamsBuffer(device, applyData, "strsv-apply-params");
128
+
129
+ const updateData = packBlockParams(numBlocks, stride, (blockIndex) => {
130
+ const blockStart = blockIndex * BLOCK_SIZE;
131
+ const blockEnd = Math.min(blockStart + BLOCK_SIZE, n);
132
+ return [n, incx, lda, isNoTrans ? 0 : 1, isLower ? 0 : 1, blockStart, blockEnd];
133
+ });
134
+ updateParamsBuffer = createSharedParamsBuffer(device, updateData, "strsv-update-params");
135
+
136
+ const { commandEncoder, querySet } = beginTimedEncoder();
137
+
138
+ // Pre-pass: every block's inverse, fully parallel, one dispatch.
139
+ invertParams = createParamsBuffer(
140
+ [
141
+ { value: n, type: "u32" },
142
+ { value: lda, type: "u32" },
143
+ { value: isNoTrans ? 0 : 1, type: "u32" },
144
+ { value: isLower ? 0 : 1, type: "u32" },
145
+ { value: isUnit ? 1 : 0, type: "u32" },
146
+ ],
147
+ "strsv-invert-params",
148
+ );
149
+ const invertBindGroup = createBindGroup(invertPipeline.getBindGroupLayout(0), [
150
+ ABuffer, AinvBuffer, invertParams,
151
+ ]);
152
+ const invertDesc = querySet
153
+ ? { timestampWrites: { querySet, beginningOfPassWriteIndex: 0 } }
154
+ : undefined;
155
+ encodePass(commandEncoder, invertPipeline, invertBindGroup, { x: BLOCK_SIZE, y: numBlocks }, invertDesc);
156
+
157
+ for (let bi = 0; bi < blockStarts.length; bi++) {
158
+ const blockStart = blockStarts[bi];
159
+ const blockEnd = Math.min(blockStart + BLOCK_SIZE, n);
160
+ const blockIndex = blockStart / BLOCK_SIZE;
161
+ const isLastPass = bi === blockStarts.length - 1;
162
+ const paramsOffset = blockIndex * stride;
163
+
164
+ const applyBindGroup = createBindGroup(applyPipeline.getBindGroupLayout(0), [
165
+ AinvBuffer, xBuffer, { buffer: applyParamsBuffer, offset: paramsOffset, size: 16 },
166
+ ]);
167
+
168
+ const applyDesc = isLastPass && querySet
169
+ ? { timestampWrites: { querySet, endOfPassWriteIndex: 1 } }
170
+ : undefined;
171
+ encodePass(commandEncoder, applyPipeline, applyBindGroup, 1, applyDesc);
172
+
173
+ const remaining = forward ? n - blockEnd : blockStart;
174
+ if (remaining === 0) continue;
175
+
176
+ const updateBindGroup = createBindGroup(updatePipeline.getBindGroupLayout(0), [
177
+ ABuffer, xBuffer, { buffer: updateParamsBuffer, offset: paramsOffset, size: 32 },
178
+ ]);
179
+
180
+ const wgCount = Math.min(remaining, maxWg);
181
+ encodePass(commandEncoder, updatePipeline, updateBindGroup, wgCount);
182
+ }
183
+
184
+ const ts = resolveTimestamp(commandEncoder, querySet);
185
+ const readBuffer = xIsGpu ? null : stageReadback(commandEncoder, xBuffer);
186
+
187
+ submit(commandEncoder);
188
+
189
+ const gpuTimeMs = await extractTimestamp(ts);
190
+
191
+ if (xIsGpu) {
192
+ if (gpuTimeMs !== undefined) return { gpuTimeMs };
193
+ return {};
194
+ }
195
+
196
+ const result = await extractResult(readBuffer, Float32Array);
197
+ if (gpuTimeMs !== undefined) return { x: result, gpuTimeMs };
198
+ return { x: result };
199
+ } finally {
200
+ if (!AIsGpu && ABuffer) destroyBuffers(ABuffer);
201
+ if (!xIsGpu && xBuffer) destroyBuffers(xBuffer);
202
+ if (AinvBuffer) destroyBuffers(AinvBuffer);
203
+ if (applyParamsBuffer) destroyBuffers(applyParamsBuffer);
204
+ if (updateParamsBuffer) destroyBuffers(updateParamsBuffer);
205
+ if (invertParams) destroyBuffers(invertParams);
206
+ }
207
+ }
@@ -99,5 +99,5 @@ export async function extractTimestamp(ts) {
99
99
  tsReadBuffer.destroy();
100
100
  resolveBuffer.destroy(); // never mapped — no unmap() needed
101
101
  querySet.destroy();
102
- return Number(timestamps[1] - timestamps[0]) / 1e6;
102
+ return Math.max(0, Number(timestamps[1] - timestamps[0])) / 1e6;
103
103
  }
@@ -2,21 +2,25 @@
2
2
  import { getDevice } from "../init.mjs";
3
3
 
4
4
  /**
5
- * Creates a `GPUBindGroup` by mapping each buffer to sequential binding indices (0, 1, 2 …).
6
- * `resultBuffer` is appended last so its binding index follows all input buffers.
7
- * The order of `buffers` must match the `@binding` indices declared in the shader.
5
+ * Creates a `GPUBindGroup` by mapping each buffer to sequential binding indices
6
+ * (startBinding, startBinding + 1, …). The order of `buffers` must match the `@binding`s
7
+ * declared in the shader.
8
8
  * @param {GPUBindGroupLayout} layout - from `pipeline.getBindGroupLayout(0)`; the layout is derived automatically from the shader because `loadShader` uses `layout: "auto"`
9
- * @param {GPUBuffer[]} buffers - input and intermediate buffers in binding order
10
- * @param {GPUBuffer|null} [resultBuffer=null] - appended as the final binding if provided
9
+ * @param {(GPUBuffer|{buffer: GPUBuffer, offset: number, size: number})[]} buffers - input and
10
+ * intermediate buffers in binding order; a plain `GPUBuffer` binds the whole buffer, or pass
11
+ * `{buffer, offset, size}` to bind a sub-range — e.g. many small per-call param structs packed
12
+ * into one shared buffer instead of allocating a separate tiny buffer per call
13
+ * @param {number} [startBinding=0] - binding index of `buffers[0]`; nonzero only when a shader's
14
+ * own bindings don't start at 0 — e.g. a module built by concatenating two `.wgsl` files where
15
+ * the second file's bindings continue after the first's own bindings (if any)
11
16
  * @returns {GPUBindGroup}
12
17
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createBindGroup GPUDevice.createBindGroup()}
13
18
  */
14
- export function createBindGroup(layout, buffers, resultBuffer = null) {
19
+ export function createBindGroup(layout, buffers, startBinding = 0) {
15
20
  const device = getDevice();
16
- const allBuffers = resultBuffer ? [...buffers, resultBuffer] : [...buffers];
17
- const entries = allBuffers.map((buffer, i) => ({
18
- binding: i,
19
- resource: { buffer },
21
+ const entries = buffers.map((buffer, i) => ({
22
+ binding: startBinding + i,
23
+ resource: buffer instanceof GPUBuffer ? { buffer } : buffer,
20
24
  }));
21
25
  return device.createBindGroup({ layout, entries });
22
26
  }
@@ -12,7 +12,11 @@ export function destroyBuffers(...buffers) {
12
12
 
13
13
  /**
14
14
  * Creates a GPU storage buffer and uploads `data` into it via mapped-at-creation.
15
- * @param {Float32Array} data
15
+ * The mapped view is constructed from `data`'s own typed-array constructor, so
16
+ * bits are copied as-is regardless of element type (e.g. a Uint32Array's raw
17
+ * bit patterns are preserved — critical for dasum's aux half, which must
18
+ * never pass through a Float32Array view and risk NaN-bit-pattern canonicalization).
19
+ * @param {Float32Array|Uint32Array|Int32Array} data
16
20
  * @param {string} [label] - debug label visible in browser DevTools GPU inspection
17
21
  * @param {boolean} [readback=false] - add `COPY_SRC` so the buffer can be copied to a readback buffer
18
22
  * @throws {Error} if `data.byteLength` exceeds the device's `maxStorageBufferBindingSize`
@@ -45,7 +49,8 @@ export function uploadBuffer(data, label = "blas-input", readback = false) {
45
49
  mappedAtCreation: true,
46
50
  });
47
51
 
48
- const mappedArray = new Float32Array(buffer.getMappedRange());
52
+ const ViewCtor = data.constructor;
53
+ const mappedArray = new ViewCtor(buffer.getMappedRange());
49
54
  mappedArray.set(data);
50
55
  buffer.unmap();
51
56
 
@@ -2,6 +2,9 @@
2
2
  import { getDevice } from "../init.mjs";
3
3
  import { beginTimestamp, resolveTimestamp } from "./benchmark.mjs";
4
4
 
5
+ // Anchors the pass encoder to its command encoder to prevent premature GC.
6
+ const _passEncoders = new WeakMap();
7
+
5
8
  /**
6
9
  * Finalises `commandEncoder` into a command buffer and submits it to the GPU queue.
7
10
  * @param {GPUCommandEncoder} commandEncoder
@@ -13,24 +16,33 @@ export function submit(commandEncoder) {
13
16
  }
14
17
 
15
18
  /**
16
- * Encodes and submits a single compute pass: sets the pipeline and bind group,
17
- * dispatches workgroups, and optionally wraps the pass in GPU timestamp queries.
18
- * @param {GPUComputePipeline} pipeline
19
- * @param {GPUBindGroup} bindGroup
20
- * @param {number | { x: number, y: number }} workgroups - workgroup count; number for 1D dispatch, `{x, y}` for 2D
21
- * @returns {{ commandEncoder: GPUCommandEncoder, ts: any }} encoded commands and timestamp handle
19
+ * Starts a new command encoder together with its GPU timestamp query (if
20
+ * benchmarking is enabled) — the pairing every routine needs before encoding
21
+ * its first pass, whether that's `runComputePass`'s single pass or a
22
+ * multi-pass routine (e.g. strsv's blocked solve) encoding several by hand.
23
+ * @returns {{ commandEncoder: GPUCommandEncoder, querySet: GPUQuerySet|null, passDescriptor: object|undefined }}
22
24
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createCommandEncoder GPUDevice.createCommandEncoder()}
23
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/beginComputePass GPUCommandEncoder.beginComputePass()}
24
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/setPipeline GPUComputePassEncoder.setPipeline()}
25
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/setBindGroup GPUComputePassEncoder.setBindGroup()}
26
- * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/GPUComputePassEncoder/dispatchWorkgroups GPUComputePassEncoder.dispatchWorkgroups()}
27
25
  */
28
- export function runComputePass(pipeline, bindGroup, workgroups) {
26
+ export function beginTimedEncoder() {
29
27
  const device = getDevice();
30
-
31
28
  const { querySet, passDescriptor } = beginTimestamp();
32
-
33
29
  const commandEncoder = device.createCommandEncoder();
30
+ return { commandEncoder, querySet, passDescriptor };
31
+ }
32
+
33
+ /**
34
+ * Encodes one compute pass (set pipeline, set bind group, dispatch, end) onto an
35
+ * existing command encoder — the shared building block behind `runComputePass`,
36
+ * also used directly by routines that need several passes on one encoder (e.g.
37
+ * strsv's blocked solve, where each pass depends on the previous one completing).
38
+ * @param {GPUCommandEncoder} commandEncoder
39
+ * @param {GPUComputePipeline} pipeline
40
+ * @param {GPUBindGroup} bindGroup
41
+ * @param {number | { x: number, y: number }} workgroups - workgroup count; number for 1D dispatch, `{x, y}` for 2D
42
+ * @param {object} [passDescriptor] - passed to `beginComputePass`, e.g. for timestamp writes
43
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/GPUCommandEncoder/beginComputePass GPUCommandEncoder.beginComputePass()}
44
+ */
45
+ export function encodePass(commandEncoder, pipeline, bindGroup, workgroups, passDescriptor) {
34
46
  const passEncoder = commandEncoder.beginComputePass(passDescriptor);
35
47
 
36
48
  passEncoder.setPipeline(pipeline);
@@ -44,9 +56,23 @@ export function runComputePass(pipeline, bindGroup, workgroups) {
44
56
 
45
57
  passEncoder.end();
46
58
 
47
- const ts = resolveTimestamp(commandEncoder, querySet);
59
+ _passEncoders.set(commandEncoder, passEncoder);
60
+ }
48
61
 
49
- commandEncoder._passEncoder = passEncoder; // anchor — GC'd passEncoder may crash native encoder
62
+ /**
63
+ * Encodes and submits a single compute pass: sets the pipeline and bind group,
64
+ * dispatches workgroups, and optionally wraps the pass in GPU timestamp queries.
65
+ * @param {GPUComputePipeline} pipeline
66
+ * @param {GPUBindGroup} bindGroup
67
+ * @param {number | { x: number, y: number }} workgroups - workgroup count; number for 1D dispatch, `{x, y}` for 2D
68
+ * @returns {{ commandEncoder: GPUCommandEncoder, ts: any }} encoded commands and timestamp handle
69
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/createCommandEncoder GPUDevice.createCommandEncoder()}
70
+ */
71
+ export function runComputePass(pipeline, bindGroup, workgroups) {
72
+ const { commandEncoder, querySet, passDescriptor } = beginTimedEncoder();
73
+ encodePass(commandEncoder, pipeline, bindGroup, workgroups, passDescriptor);
74
+
75
+ const ts = resolveTimestamp(commandEncoder, querySet);
50
76
 
51
77
  return { commandEncoder, ts };
52
78
  }