wgblas 2.2.0 → 2.3.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 +5 -2
- package/dist/wgblas.browser.js +1092 -58
- package/index.d.mts +7 -0
- package/index.mjs +7 -0
- package/package.json +40 -4
- package/src/dgemv/dgemv.d.mts +92 -0
- package/src/dgemv/dgemv.mjs +247 -0
- package/src/dger/dger.d.mts +80 -0
- package/src/dger/dger.mjs +213 -0
- package/src/dsymv/dsymv.d.mts +84 -0
- package/src/dsymv/dsymv.mjs +227 -0
- package/src/dsyr/dsyr.d.mts +73 -0
- package/src/dsyr/dsyr.mjs +171 -0
- package/src/dsyr2/dsyr2.d.mts +81 -0
- package/src/dsyr2/dsyr2.mjs +214 -0
- package/src/dtrmv/dtrmv.d.mts +84 -0
- package/src/dtrmv/dtrmv.mjs +219 -0
- package/src/dtrsv/dtrsv.d.mts +79 -0
- package/src/dtrsv/dtrsv.mjs +331 -0
- package/src/shaders/dgemv_n.wgsl +112 -0
- package/src/shaders/dgemv_t.wgsl +102 -0
- package/src/shaders/dger.wgsl +77 -0
- package/src/shaders/dsymv.wgsl +125 -0
- package/src/shaders/dsyr.wgsl +84 -0
- package/src/shaders/dsyr2.wgsl +95 -0
- package/src/shaders/dtrmv.wgsl +111 -0
- package/src/shaders/dtrsv_apply_inverse.wgsl +60 -0
- package/src/shaders/dtrsv_invert_block.wgsl +148 -0
- package/src/shaders/dtrsv_update.wgsl +111 -0
- package/src/shaders/f64/utils/multiply.wgsl +10 -1
- package/src/shaders/index.mjs +63 -0
|
@@ -0,0 +1,219 @@
|
|
|
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
|
+
import { splitDoubleDouble, mergeDoubleDouble } from "../util/f64.mjs";
|
|
15
|
+
import { requireGpuDevice, requireSameDevice } from "../util/device.mjs";
|
|
16
|
+
|
|
17
|
+
// dtrmv: y := op(A) * x, double-double (Dekker) f64 emulation of strmv — A,
|
|
18
|
+
// x, and y are each split into an f32 (hi, lo) pair; WGSL has no f64 type.
|
|
19
|
+
export async function dtrmv(
|
|
20
|
+
device,
|
|
21
|
+
uplo,
|
|
22
|
+
trans,
|
|
23
|
+
diag,
|
|
24
|
+
n,
|
|
25
|
+
A,
|
|
26
|
+
lda,
|
|
27
|
+
x,
|
|
28
|
+
incx,
|
|
29
|
+
y,
|
|
30
|
+
incy,
|
|
31
|
+
layout = "row-major",
|
|
32
|
+
) {
|
|
33
|
+
const xIsGpu = x instanceof GpuVector;
|
|
34
|
+
const yIsGpu = y instanceof GpuVector;
|
|
35
|
+
const AIsGpu = A instanceof GpuMatrix;
|
|
36
|
+
const isUnit = diag === "unit";
|
|
37
|
+
|
|
38
|
+
requireGpuDevice(device);
|
|
39
|
+
requireSameDevice(device, "dtrmv", { A, x, y });
|
|
40
|
+
if (uplo !== "lower" && uplo !== "upper")
|
|
41
|
+
throw new Error("uplo must be 'lower' or 'upper'.");
|
|
42
|
+
if (trans !== "no-transpose" && trans !== "transpose")
|
|
43
|
+
throw new Error("trans must be 'no-transpose' or 'transpose'.");
|
|
44
|
+
if (!isUnit && diag !== "non-unit")
|
|
45
|
+
throw new Error("diag must be 'unit' or 'non-unit'.");
|
|
46
|
+
if (layout !== "row-major" && layout !== "column-major")
|
|
47
|
+
throw new Error("layout must be 'row-major' or 'column-major'.");
|
|
48
|
+
if (
|
|
49
|
+
!Number.isInteger(n) ||
|
|
50
|
+
!Number.isInteger(incx) ||
|
|
51
|
+
!Number.isInteger(incy) ||
|
|
52
|
+
!Number.isInteger(lda)
|
|
53
|
+
)
|
|
54
|
+
throw new Error("n, incx, incy, and lda must be integers.");
|
|
55
|
+
if (incx <= 0 || incy <= 0)
|
|
56
|
+
throw new Error("incx and incy must be positive.");
|
|
57
|
+
if (lda < n) throw new Error("lda must be >= n.");
|
|
58
|
+
if (!AIsGpu && !(A instanceof Float64Array))
|
|
59
|
+
throw new Error("A must be a Float64Array or GpuMatrix.");
|
|
60
|
+
if (AIsGpu && A.dtype !== Float64Array)
|
|
61
|
+
throw new Error("A must be a Float64Array-backed GpuMatrix.");
|
|
62
|
+
if (!xIsGpu && !(x instanceof Float64Array))
|
|
63
|
+
throw new Error("x must be a Float64Array or GpuVector.");
|
|
64
|
+
if (!yIsGpu && !(y instanceof Float64Array))
|
|
65
|
+
throw new Error("y must be a Float64Array or GpuVector.");
|
|
66
|
+
if (xIsGpu && x.dtype !== Float64Array)
|
|
67
|
+
throw new Error("x must be a Float64Array-backed GpuVector.");
|
|
68
|
+
if (yIsGpu && y.dtype !== Float64Array)
|
|
69
|
+
throw new Error("y must be a Float64Array-backed GpuVector.");
|
|
70
|
+
if (xIsGpu !== yIsGpu)
|
|
71
|
+
throw new Error(
|
|
72
|
+
"x and y must be the same type (both Float64Array or both GpuVector).",
|
|
73
|
+
);
|
|
74
|
+
if (xIsGpu && x._buf === y._buf)
|
|
75
|
+
throw new Error(
|
|
76
|
+
"x and y must not reference the same GPU buffer when both are GpuVectors.",
|
|
77
|
+
);
|
|
78
|
+
if (xIsGpu && !AIsGpu)
|
|
79
|
+
throw new Error("A must be a GpuMatrix when x and y are GpuVectors.");
|
|
80
|
+
if (AIsGpu && !xIsGpu)
|
|
81
|
+
throw new Error("x and y must be GpuVectors when A is a GpuMatrix.");
|
|
82
|
+
if (AIsGpu && yIsGpu && A._buf === y._buf)
|
|
83
|
+
throw new Error("A and y must not reference the same GPU buffer.");
|
|
84
|
+
if (AIsGpu && lda !== A.lda)
|
|
85
|
+
throw new Error("lda must match A.lda when A is a GpuMatrix.");
|
|
86
|
+
if (AIsGpu && (A.rows < n || A.cols < n))
|
|
87
|
+
throw new Error("A is too small for the given n.");
|
|
88
|
+
if (n < 0) throw new Error("n must be non-negative.");
|
|
89
|
+
if (n === 0) return yIsGpu ? {} : { y };
|
|
90
|
+
|
|
91
|
+
if (!AIsGpu && A.length < (n - 1) * lda + n)
|
|
92
|
+
throw new Error("A does not have enough elements for the given n and lda.");
|
|
93
|
+
if (x.length < (n - 1) * incx + 1)
|
|
94
|
+
throw new Error(
|
|
95
|
+
"x does not have enough elements for the given n and incx.",
|
|
96
|
+
);
|
|
97
|
+
if (y.length < (n - 1) * incy + 1)
|
|
98
|
+
throw new Error(
|
|
99
|
+
"y does not have enough elements for the given n and incy.",
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// 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).
|
|
103
|
+
const effLayout = AIsGpu ? A.layout : layout;
|
|
104
|
+
const isColMajor = effLayout === "column-major";
|
|
105
|
+
const isLower = isColMajor ? uplo === "upper" : uplo === "lower";
|
|
106
|
+
const isNoTrans = isColMajor
|
|
107
|
+
? trans === "transpose"
|
|
108
|
+
: trans === "no-transpose";
|
|
109
|
+
|
|
110
|
+
const f64Deps = ["f64/dekker", "f64/utils/add", "f64/utils/multiply"];
|
|
111
|
+
const pipeline = await getPipeline(
|
|
112
|
+
device,
|
|
113
|
+
[...f64Deps, "dtrmv"],
|
|
114
|
+
"dtrmv_main",
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
let AHiBuffer = null;
|
|
118
|
+
let ALoBuffer = null;
|
|
119
|
+
let xHiBuffer = null;
|
|
120
|
+
let xLoBuffer = null;
|
|
121
|
+
let yHiBuffer = null;
|
|
122
|
+
let yLoBuffer = null;
|
|
123
|
+
let paramsBuffer = null;
|
|
124
|
+
let readHiBuffer = null;
|
|
125
|
+
let readLoBuffer = null;
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
if (AIsGpu) {
|
|
129
|
+
AHiBuffer = A._buf;
|
|
130
|
+
ALoBuffer = A._loBuf;
|
|
131
|
+
} else {
|
|
132
|
+
const ASplit = splitDoubleDouble(A);
|
|
133
|
+
AHiBuffer = uploadBuffer(device, ASplit.hi, "dtrmv-AHi", false);
|
|
134
|
+
ALoBuffer = uploadBuffer(device, ASplit.lo, "dtrmv-ALo", false);
|
|
135
|
+
}
|
|
136
|
+
if (xIsGpu) {
|
|
137
|
+
xHiBuffer = x._buf;
|
|
138
|
+
xLoBuffer = x._loBuf;
|
|
139
|
+
} else {
|
|
140
|
+
const xSplit = splitDoubleDouble(x);
|
|
141
|
+
xHiBuffer = uploadBuffer(device, xSplit.hi, "dtrmv-xHi", false);
|
|
142
|
+
xLoBuffer = uploadBuffer(device, xSplit.lo, "dtrmv-xLo", false);
|
|
143
|
+
}
|
|
144
|
+
if (yIsGpu) {
|
|
145
|
+
yHiBuffer = y._buf;
|
|
146
|
+
yLoBuffer = y._loBuf;
|
|
147
|
+
} else {
|
|
148
|
+
const ySplit = splitDoubleDouble(y);
|
|
149
|
+
yHiBuffer = uploadBuffer(device, ySplit.hi, "dtrmv-yHi", true);
|
|
150
|
+
yLoBuffer = uploadBuffer(device, ySplit.lo, "dtrmv-yLo", true);
|
|
151
|
+
}
|
|
152
|
+
paramsBuffer = createParamsBuffer(
|
|
153
|
+
device,
|
|
154
|
+
[
|
|
155
|
+
{ value: n, type: "u32" },
|
|
156
|
+
{ value: incx, type: "u32" },
|
|
157
|
+
{ value: incy, type: "u32" },
|
|
158
|
+
{ value: lda, type: "u32" },
|
|
159
|
+
{ value: isNoTrans ? 0 : 1, type: "u32" },
|
|
160
|
+
{ value: isLower ? 0 : 1, type: "u32" },
|
|
161
|
+
{ value: isUnit ? 1 : 0, type: "u32" },
|
|
162
|
+
],
|
|
163
|
+
"dtrmv-params",
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const bindGroup = createBindGroup(device, pipeline.getBindGroupLayout(0), [
|
|
167
|
+
AHiBuffer,
|
|
168
|
+
ALoBuffer,
|
|
169
|
+
xHiBuffer,
|
|
170
|
+
xLoBuffer,
|
|
171
|
+
yHiBuffer,
|
|
172
|
+
yLoBuffer,
|
|
173
|
+
paramsBuffer,
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
// One workgroup per row of A; clamped to device limit — the shader's
|
|
177
|
+
// grid-stride loop handles remaining rows when n > dispatch count.
|
|
178
|
+
const wgCount = Math.min(n, device.limits.maxComputeWorkgroupsPerDimension);
|
|
179
|
+
const { commandEncoder, ts } = runComputePass(
|
|
180
|
+
device,
|
|
181
|
+
pipeline,
|
|
182
|
+
bindGroup,
|
|
183
|
+
wgCount,
|
|
184
|
+
);
|
|
185
|
+
readHiBuffer = yIsGpu
|
|
186
|
+
? null
|
|
187
|
+
: stageReadback(device, commandEncoder, yHiBuffer);
|
|
188
|
+
readLoBuffer = yIsGpu
|
|
189
|
+
? null
|
|
190
|
+
: stageReadback(device, commandEncoder, yLoBuffer);
|
|
191
|
+
|
|
192
|
+
submit(device, commandEncoder);
|
|
193
|
+
|
|
194
|
+
const gpuTimeMs = await extractTimestamp(ts);
|
|
195
|
+
|
|
196
|
+
if (yIsGpu) {
|
|
197
|
+
if (gpuTimeMs !== undefined) return { gpuTimeMs };
|
|
198
|
+
return {};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const hi = await extractResult(readHiBuffer, Float32Array);
|
|
202
|
+
readHiBuffer = null;
|
|
203
|
+
const lo = await extractResult(readLoBuffer, Float32Array);
|
|
204
|
+
readLoBuffer = null;
|
|
205
|
+
const result = mergeDoubleDouble(hi, lo);
|
|
206
|
+
if (gpuTimeMs !== undefined) return { y: result, gpuTimeMs };
|
|
207
|
+
return { y: result };
|
|
208
|
+
} finally {
|
|
209
|
+
if (!AIsGpu && AHiBuffer) destroyBuffers(AHiBuffer);
|
|
210
|
+
if (!AIsGpu && ALoBuffer) destroyBuffers(ALoBuffer);
|
|
211
|
+
if (!xIsGpu && xHiBuffer) destroyBuffers(xHiBuffer);
|
|
212
|
+
if (!xIsGpu && xLoBuffer) destroyBuffers(xLoBuffer);
|
|
213
|
+
if (!yIsGpu && yHiBuffer) destroyBuffers(yHiBuffer);
|
|
214
|
+
if (!yIsGpu && yLoBuffer) destroyBuffers(yLoBuffer);
|
|
215
|
+
if (paramsBuffer) destroyBuffers(paramsBuffer);
|
|
216
|
+
if (readHiBuffer) destroyBuffers(readHiBuffer);
|
|
217
|
+
if (readLoBuffer) destroyBuffers(readLoBuffer);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { GpuVector } from "../classes/GpuVector.mjs";
|
|
2
|
+
import { GpuMatrix } from "../classes/GpuMatrix.mjs";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Solves the triangular system for $x$, in place — x holds b on input, the
|
|
6
|
+
* solution on output, in double precision (double-double emulation — WGSL
|
|
7
|
+
* has no native f64 type):
|
|
8
|
+
* $$\mathrm{op}(A) x = b$$
|
|
9
|
+
*
|
|
10
|
+
* A is an n×n triangular matrix stored in row-major order. Only the triangle
|
|
11
|
+
* specified by `uplo` is referenced; the other triangle is not accessed.
|
|
12
|
+
*
|
|
13
|
+
* {@includeCode ../../examples/dtrsv/dtrsv.js}
|
|
14
|
+
*
|
|
15
|
+
* **Browser (standalone HTML):**
|
|
16
|
+
* {@includeCode ../../examples/dtrsv/web/dtrsv.html}
|
|
17
|
+
*
|
|
18
|
+
* @param device - GPUDevice from `init()`
|
|
19
|
+
* @param uplo - `'lower'` to use the lower triangle, `'upper'` to use the upper triangle
|
|
20
|
+
* @param trans - `'no-transpose'` to solve A*x=b, `'transpose'` to solve A^T*x=b
|
|
21
|
+
* @param diag - `'unit'` to treat the diagonal as all-ones (A's diagonal is not read), `'non-unit'` to read it
|
|
22
|
+
* @param n - order of the matrix A (number of rows and columns)
|
|
23
|
+
* @param A - Float64Array, row-major or column-major (see `layout`), at least (n-1)*lda+n elements
|
|
24
|
+
* @param lda - leading dimension of A (>= n either way — A is square)
|
|
25
|
+
* @param x - Float64Array holding b on input, the solution on output; length at least (n-1)*incx+1
|
|
26
|
+
* @param incx - stride for x (must be a positive integer)
|
|
27
|
+
* @param layout - storage layout of `A` (default: `'row-major'`); column-major
|
|
28
|
+
* flips both the stored triangle and the effective `trans` (the system
|
|
29
|
+
* being solved stays what you asked for either way)
|
|
30
|
+
* @see <a href="https://github.com/manit2004/wgblas/blob/main/src/dtrsv/dtrsv.mjs#L48">Source code: dtrsv.mjs (L48)</a>
|
|
31
|
+
* @category BLAS Level 2
|
|
32
|
+
*/
|
|
33
|
+
export declare function dtrsv(
|
|
34
|
+
device: GPUDevice,
|
|
35
|
+
uplo: 'lower' | 'upper',
|
|
36
|
+
trans: 'no-transpose' | 'transpose',
|
|
37
|
+
diag: 'unit' | 'non-unit',
|
|
38
|
+
n: number,
|
|
39
|
+
A: Float64Array,
|
|
40
|
+
lda: number,
|
|
41
|
+
x: Float64Array,
|
|
42
|
+
incx: number,
|
|
43
|
+
layout?: 'row-major' | 'column-major',
|
|
44
|
+
): Promise<{ x: Float64Array; gpuTimeMs?: number }>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Solves the triangular system for $x$, in place, in double precision
|
|
48
|
+
* (double-double emulation):
|
|
49
|
+
* $$\mathrm{op}(A) x = b$$
|
|
50
|
+
*
|
|
51
|
+
* x is kept resident on the GPU (mutated in place). A must be a GpuMatrix
|
|
52
|
+
* (Float64Array-backed); its own `layout` (set at `GpuMatrix.from` time)
|
|
53
|
+
* determines the operation — there is no separate `layout` argument here.
|
|
54
|
+
*
|
|
55
|
+
* {@includeCode ../../examples/dtrsv/gpu.dtrsv.js}
|
|
56
|
+
*
|
|
57
|
+
* @param device - GPUDevice from `init()`
|
|
58
|
+
* @param uplo - `'lower'` to use the lower triangle, `'upper'` to use the upper triangle
|
|
59
|
+
* @param trans - `'no-transpose'` to solve A*x=b, `'transpose'` to solve A^T*x=b
|
|
60
|
+
* @param diag - `'unit'` to treat the diagonal as all-ones (A's diagonal is not read), `'non-unit'` to read it
|
|
61
|
+
* @param n - order of the matrix A
|
|
62
|
+
* @param A - GpuMatrix (Float64Array-backed), GPU-resident
|
|
63
|
+
* @param lda - leading dimension of A (must equal A.lda)
|
|
64
|
+
* @param x - GpuVector (Float64Array-backed) holding b on input, the solution on output (mutated in place)
|
|
65
|
+
* @param incx - stride for x (must be a positive integer)
|
|
66
|
+
* @see <a href="https://github.com/manit2004/wgblas/blob/main/src/dtrsv/dtrsv.mjs#L48">Source code: dtrsv.mjs (L48)</a>
|
|
67
|
+
* @category BLAS Level 2
|
|
68
|
+
*/
|
|
69
|
+
export declare function dtrsv(
|
|
70
|
+
device: GPUDevice,
|
|
71
|
+
uplo: 'lower' | 'upper',
|
|
72
|
+
trans: 'no-transpose' | 'transpose',
|
|
73
|
+
diag: 'unit' | 'non-unit',
|
|
74
|
+
n: number,
|
|
75
|
+
A: GpuMatrix,
|
|
76
|
+
lda: number,
|
|
77
|
+
x: GpuVector,
|
|
78
|
+
incx: number,
|
|
79
|
+
): Promise<{ gpuTimeMs?: number }>;
|
|
@@ -0,0 +1,331 @@
|
|
|
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
|
+
import { BLOCK_SIZE } from "../util/constants.mjs";
|
|
16
|
+
import { splitDoubleDouble, mergeDoubleDouble } from "../util/f64.mjs";
|
|
17
|
+
import { requireGpuDevice, requireSameDevice } from "../util/device.mjs";
|
|
18
|
+
|
|
19
|
+
// Blocked triangular solve via explicit block inversion (invert/apply/update
|
|
20
|
+
// passes), double-double (Dekker) f64 emulation of strsv — A, Ainv, and x
|
|
21
|
+
// are each split into an f32 (hi, lo) pair; WGSL has no f64 type.
|
|
22
|
+
|
|
23
|
+
// 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.
|
|
24
|
+
function packBlockParams(numBlocks, stride, fieldsPerBlock) {
|
|
25
|
+
const data = new ArrayBuffer(numBlocks * stride);
|
|
26
|
+
const view = new DataView(data);
|
|
27
|
+
for (let blockIndex = 0; blockIndex < numBlocks; blockIndex++) {
|
|
28
|
+
const fields = fieldsPerBlock(blockIndex);
|
|
29
|
+
const base = blockIndex * stride;
|
|
30
|
+
fields.forEach((value, i) => view.setUint32(base + i * 4, value, true));
|
|
31
|
+
}
|
|
32
|
+
return data;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function createSharedParamsBuffer(device, data, label) {
|
|
36
|
+
const buffer = device.createBuffer({
|
|
37
|
+
label,
|
|
38
|
+
size: data.byteLength,
|
|
39
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
40
|
+
});
|
|
41
|
+
device.queue.writeBuffer(buffer, 0, data);
|
|
42
|
+
return buffer;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function dtrsv(
|
|
46
|
+
device,
|
|
47
|
+
uplo,
|
|
48
|
+
trans,
|
|
49
|
+
diag,
|
|
50
|
+
n,
|
|
51
|
+
A,
|
|
52
|
+
lda,
|
|
53
|
+
x,
|
|
54
|
+
incx,
|
|
55
|
+
layout = "row-major",
|
|
56
|
+
) {
|
|
57
|
+
const xIsGpu = x instanceof GpuVector;
|
|
58
|
+
const AIsGpu = A instanceof GpuMatrix;
|
|
59
|
+
const isUnit = diag === "unit";
|
|
60
|
+
|
|
61
|
+
requireGpuDevice(device);
|
|
62
|
+
requireSameDevice(device, "dtrsv", { A, x });
|
|
63
|
+
if (uplo !== "lower" && uplo !== "upper")
|
|
64
|
+
throw new Error("uplo must be 'lower' or 'upper'.");
|
|
65
|
+
if (trans !== "no-transpose" && trans !== "transpose")
|
|
66
|
+
throw new Error("trans must be 'no-transpose' or 'transpose'.");
|
|
67
|
+
if (!isUnit && diag !== "non-unit")
|
|
68
|
+
throw new Error("diag must be 'unit' or 'non-unit'.");
|
|
69
|
+
if (layout !== "row-major" && layout !== "column-major")
|
|
70
|
+
throw new Error("layout must be 'row-major' or 'column-major'.");
|
|
71
|
+
if (!Number.isInteger(n) || !Number.isInteger(incx) || !Number.isInteger(lda))
|
|
72
|
+
throw new Error("n, incx, and lda must be integers.");
|
|
73
|
+
if (incx <= 0) throw new Error("incx must be positive.");
|
|
74
|
+
if (lda < n) throw new Error("lda must be >= n.");
|
|
75
|
+
if (!AIsGpu && !(A instanceof Float64Array))
|
|
76
|
+
throw new Error("A must be a Float64Array or GpuMatrix.");
|
|
77
|
+
if (AIsGpu && A.dtype !== Float64Array)
|
|
78
|
+
throw new Error("A must be a Float64Array-backed GpuMatrix.");
|
|
79
|
+
if (!xIsGpu && !(x instanceof Float64Array))
|
|
80
|
+
throw new Error("x must be a Float64Array or GpuVector.");
|
|
81
|
+
if (xIsGpu && x.dtype !== Float64Array)
|
|
82
|
+
throw new Error("x must be a Float64Array-backed GpuVector.");
|
|
83
|
+
if (xIsGpu && !AIsGpu)
|
|
84
|
+
throw new Error("A must be a GpuMatrix when x is a GpuVector.");
|
|
85
|
+
if (AIsGpu && !xIsGpu)
|
|
86
|
+
throw new Error("x must be a GpuVector when A is a GpuMatrix.");
|
|
87
|
+
if (AIsGpu && xIsGpu && A._buf === x._buf)
|
|
88
|
+
throw new Error("A and x must not reference the same GPU buffer.");
|
|
89
|
+
if (AIsGpu && lda !== A.lda)
|
|
90
|
+
throw new Error("lda must match A.lda when A is a GpuMatrix.");
|
|
91
|
+
if (AIsGpu && (A.rows < n || A.cols < n))
|
|
92
|
+
throw new Error("A is too small for the given n.");
|
|
93
|
+
if (n < 0) throw new Error("n must be non-negative.");
|
|
94
|
+
if (n === 0) return xIsGpu ? {} : { x };
|
|
95
|
+
|
|
96
|
+
if (!AIsGpu && A.length < (n - 1) * lda + n)
|
|
97
|
+
throw new Error("A does not have enough elements for the given n and lda.");
|
|
98
|
+
if (x.length < (n - 1) * incx + 1)
|
|
99
|
+
throw new Error(
|
|
100
|
+
"x does not have enough elements for the given n and incx.",
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
// 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.
|
|
104
|
+
const effLayout = AIsGpu ? A.layout : layout;
|
|
105
|
+
const isColMajor = effLayout === "column-major";
|
|
106
|
+
const isLower = isColMajor ? uplo === "upper" : uplo === "lower";
|
|
107
|
+
const isNoTrans = isColMajor
|
|
108
|
+
? trans === "transpose"
|
|
109
|
+
: trans === "no-transpose";
|
|
110
|
+
|
|
111
|
+
const f64Deps = [
|
|
112
|
+
"f64/dekker",
|
|
113
|
+
"f64/utils/add",
|
|
114
|
+
"f64/utils/multiply",
|
|
115
|
+
"f64/utils/divide",
|
|
116
|
+
];
|
|
117
|
+
const invertPipeline = await getPipeline(
|
|
118
|
+
device,
|
|
119
|
+
[...f64Deps, "dtrsv_invert_block"],
|
|
120
|
+
"dtrsv_invert_block_main",
|
|
121
|
+
);
|
|
122
|
+
const applyPipeline = await getPipeline(
|
|
123
|
+
device,
|
|
124
|
+
[...f64Deps, "dtrsv_apply_inverse"],
|
|
125
|
+
"dtrsv_apply_inverse_main",
|
|
126
|
+
);
|
|
127
|
+
const updatePipeline = await getPipeline(
|
|
128
|
+
device,
|
|
129
|
+
[...f64Deps, "dtrsv_update"],
|
|
130
|
+
"dtrsv_update_main",
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// Same forward/backward pairing the shaders use.
|
|
134
|
+
const forward = isNoTrans === isLower;
|
|
135
|
+
const blockStarts = [];
|
|
136
|
+
for (let s = 0; s < n; s += BLOCK_SIZE) blockStarts.push(s);
|
|
137
|
+
if (!forward) blockStarts.reverse();
|
|
138
|
+
const numBlocks = blockStarts.length;
|
|
139
|
+
|
|
140
|
+
const maxWg = device.limits.maxComputeWorkgroupsPerDimension;
|
|
141
|
+
const stride = device.limits.minUniformBufferOffsetAlignment;
|
|
142
|
+
|
|
143
|
+
let AHiBuffer = null;
|
|
144
|
+
let ALoBuffer = null;
|
|
145
|
+
let xHiBuffer = null;
|
|
146
|
+
let xLoBuffer = null;
|
|
147
|
+
let AinvHiBuffer = null;
|
|
148
|
+
let AinvLoBuffer = null;
|
|
149
|
+
let applyParamsBuffer = null;
|
|
150
|
+
let updateParamsBuffer = null;
|
|
151
|
+
let invertParams = null;
|
|
152
|
+
let readHiBuffer = null;
|
|
153
|
+
let readLoBuffer = null;
|
|
154
|
+
|
|
155
|
+
try {
|
|
156
|
+
if (AIsGpu) {
|
|
157
|
+
AHiBuffer = A._buf;
|
|
158
|
+
ALoBuffer = A._loBuf;
|
|
159
|
+
} else {
|
|
160
|
+
const ASplit = splitDoubleDouble(A);
|
|
161
|
+
AHiBuffer = uploadBuffer(device, ASplit.hi, "dtrsv-AHi", false);
|
|
162
|
+
ALoBuffer = uploadBuffer(device, ASplit.lo, "dtrsv-ALo", false);
|
|
163
|
+
}
|
|
164
|
+
if (xIsGpu) {
|
|
165
|
+
xHiBuffer = x._buf;
|
|
166
|
+
xLoBuffer = x._loBuf;
|
|
167
|
+
} else {
|
|
168
|
+
const xSplit = splitDoubleDouble(x);
|
|
169
|
+
xHiBuffer = uploadBuffer(device, xSplit.hi, "dtrsv-xHi", true);
|
|
170
|
+
xLoBuffer = uploadBuffer(device, xSplit.lo, "dtrsv-xLo", true);
|
|
171
|
+
}
|
|
172
|
+
// One BLOCK_SIZE x BLOCK_SIZE dense region per block (row-major), even
|
|
173
|
+
// though only a triangular half is ever nonzero — see
|
|
174
|
+
// dtrsv_invert_block.wgsl. Hi/Lo are two separate f32-element buffers,
|
|
175
|
+
// same element count each as the f32 original's single Ainv buffer.
|
|
176
|
+
AinvHiBuffer = createStorageBuffer(
|
|
177
|
+
device,
|
|
178
|
+
numBlocks * BLOCK_SIZE * BLOCK_SIZE * 4,
|
|
179
|
+
"dtrsv-AinvHi",
|
|
180
|
+
);
|
|
181
|
+
AinvLoBuffer = createStorageBuffer(
|
|
182
|
+
device,
|
|
183
|
+
numBlocks * BLOCK_SIZE * BLOCK_SIZE * 4,
|
|
184
|
+
"dtrsv-AinvLo",
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
// Every block's {blockStart, blockEnd} is fixed by its natural index
|
|
188
|
+
// regardless of traversal direction, so both packed buffers are indexed
|
|
189
|
+
// by blockIndex (0..numBlocks-1), not by loop position.
|
|
190
|
+
const applyData = packBlockParams(numBlocks, stride, (blockIndex) => {
|
|
191
|
+
const blockStart = blockIndex * BLOCK_SIZE;
|
|
192
|
+
const blockEnd = Math.min(blockStart + BLOCK_SIZE, n);
|
|
193
|
+
return [incx, blockIndex, blockStart, blockEnd];
|
|
194
|
+
});
|
|
195
|
+
applyParamsBuffer = createSharedParamsBuffer(
|
|
196
|
+
device,
|
|
197
|
+
applyData,
|
|
198
|
+
"dtrsv-apply-params",
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
const updateData = packBlockParams(numBlocks, stride, (blockIndex) => {
|
|
202
|
+
const blockStart = blockIndex * BLOCK_SIZE;
|
|
203
|
+
const blockEnd = Math.min(blockStart + BLOCK_SIZE, n);
|
|
204
|
+
return [
|
|
205
|
+
n,
|
|
206
|
+
incx,
|
|
207
|
+
lda,
|
|
208
|
+
isNoTrans ? 0 : 1,
|
|
209
|
+
isLower ? 0 : 1,
|
|
210
|
+
blockStart,
|
|
211
|
+
blockEnd,
|
|
212
|
+
];
|
|
213
|
+
});
|
|
214
|
+
updateParamsBuffer = createSharedParamsBuffer(
|
|
215
|
+
device,
|
|
216
|
+
updateData,
|
|
217
|
+
"dtrsv-update-params",
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const { commandEncoder, querySet } = beginTimedEncoder(device);
|
|
221
|
+
|
|
222
|
+
// Pre-pass: every block's inverse, fully parallel, one dispatch.
|
|
223
|
+
invertParams = createParamsBuffer(
|
|
224
|
+
device,
|
|
225
|
+
[
|
|
226
|
+
{ value: n, type: "u32" },
|
|
227
|
+
{ value: lda, type: "u32" },
|
|
228
|
+
{ value: isNoTrans ? 0 : 1, type: "u32" },
|
|
229
|
+
{ value: isLower ? 0 : 1, type: "u32" },
|
|
230
|
+
{ value: isUnit ? 1 : 0, type: "u32" },
|
|
231
|
+
],
|
|
232
|
+
"dtrsv-invert-params",
|
|
233
|
+
);
|
|
234
|
+
const invertBindGroup = createBindGroup(
|
|
235
|
+
device,
|
|
236
|
+
invertPipeline.getBindGroupLayout(0),
|
|
237
|
+
[AHiBuffer, ALoBuffer, AinvHiBuffer, AinvLoBuffer, invertParams],
|
|
238
|
+
);
|
|
239
|
+
const invertDesc = querySet
|
|
240
|
+
? { timestampWrites: { querySet, beginningOfPassWriteIndex: 0 } }
|
|
241
|
+
: undefined;
|
|
242
|
+
encodePass(
|
|
243
|
+
commandEncoder,
|
|
244
|
+
invertPipeline,
|
|
245
|
+
invertBindGroup,
|
|
246
|
+
{ x: BLOCK_SIZE, y: numBlocks },
|
|
247
|
+
invertDesc,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
for (let bi = 0; bi < blockStarts.length; bi++) {
|
|
251
|
+
const blockStart = blockStarts[bi];
|
|
252
|
+
const blockEnd = Math.min(blockStart + BLOCK_SIZE, n);
|
|
253
|
+
const blockIndex = blockStart / BLOCK_SIZE;
|
|
254
|
+
const isLastPass = bi === blockStarts.length - 1;
|
|
255
|
+
const paramsOffset = blockIndex * stride;
|
|
256
|
+
|
|
257
|
+
const applyBindGroup = createBindGroup(
|
|
258
|
+
device,
|
|
259
|
+
applyPipeline.getBindGroupLayout(0),
|
|
260
|
+
[
|
|
261
|
+
AinvHiBuffer,
|
|
262
|
+
AinvLoBuffer,
|
|
263
|
+
xHiBuffer,
|
|
264
|
+
xLoBuffer,
|
|
265
|
+
{ buffer: applyParamsBuffer, offset: paramsOffset, size: 16 },
|
|
266
|
+
],
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const applyDesc =
|
|
270
|
+
isLastPass && querySet
|
|
271
|
+
? { timestampWrites: { querySet, endOfPassWriteIndex: 1 } }
|
|
272
|
+
: undefined;
|
|
273
|
+
encodePass(commandEncoder, applyPipeline, applyBindGroup, 1, applyDesc);
|
|
274
|
+
|
|
275
|
+
const remaining = forward ? n - blockEnd : blockStart;
|
|
276
|
+
if (remaining === 0) continue;
|
|
277
|
+
|
|
278
|
+
const updateBindGroup = createBindGroup(
|
|
279
|
+
device,
|
|
280
|
+
updatePipeline.getBindGroupLayout(0),
|
|
281
|
+
[
|
|
282
|
+
AHiBuffer,
|
|
283
|
+
ALoBuffer,
|
|
284
|
+
xHiBuffer,
|
|
285
|
+
xLoBuffer,
|
|
286
|
+
{ buffer: updateParamsBuffer, offset: paramsOffset, size: 32 },
|
|
287
|
+
],
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const wgCount = Math.min(remaining, maxWg);
|
|
291
|
+
encodePass(commandEncoder, updatePipeline, updateBindGroup, wgCount);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const ts = resolveTimestamp(device, commandEncoder, querySet);
|
|
295
|
+
readHiBuffer = xIsGpu
|
|
296
|
+
? null
|
|
297
|
+
: stageReadback(device, commandEncoder, xHiBuffer);
|
|
298
|
+
readLoBuffer = xIsGpu
|
|
299
|
+
? null
|
|
300
|
+
: stageReadback(device, commandEncoder, xLoBuffer);
|
|
301
|
+
|
|
302
|
+
submit(device, commandEncoder);
|
|
303
|
+
|
|
304
|
+
const gpuTimeMs = await extractTimestamp(ts);
|
|
305
|
+
|
|
306
|
+
if (xIsGpu) {
|
|
307
|
+
if (gpuTimeMs !== undefined) return { gpuTimeMs };
|
|
308
|
+
return {};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const hi = await extractResult(readHiBuffer, Float32Array);
|
|
312
|
+
readHiBuffer = null;
|
|
313
|
+
const lo = await extractResult(readLoBuffer, Float32Array);
|
|
314
|
+
readLoBuffer = null;
|
|
315
|
+
const result = mergeDoubleDouble(hi, lo);
|
|
316
|
+
if (gpuTimeMs !== undefined) return { x: result, gpuTimeMs };
|
|
317
|
+
return { x: result };
|
|
318
|
+
} finally {
|
|
319
|
+
if (!AIsGpu && AHiBuffer) destroyBuffers(AHiBuffer);
|
|
320
|
+
if (!AIsGpu && ALoBuffer) destroyBuffers(ALoBuffer);
|
|
321
|
+
if (!xIsGpu && xHiBuffer) destroyBuffers(xHiBuffer);
|
|
322
|
+
if (!xIsGpu && xLoBuffer) destroyBuffers(xLoBuffer);
|
|
323
|
+
if (AinvHiBuffer) destroyBuffers(AinvHiBuffer);
|
|
324
|
+
if (AinvLoBuffer) destroyBuffers(AinvLoBuffer);
|
|
325
|
+
if (applyParamsBuffer) destroyBuffers(applyParamsBuffer);
|
|
326
|
+
if (updateParamsBuffer) destroyBuffers(updateParamsBuffer);
|
|
327
|
+
if (invertParams) destroyBuffers(invertParams);
|
|
328
|
+
if (readHiBuffer) destroyBuffers(readHiBuffer);
|
|
329
|
+
if (readLoBuffer) destroyBuffers(readLoBuffer);
|
|
330
|
+
}
|
|
331
|
+
}
|