wgblas 2.0.0 → 2.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 (66) hide show
  1. package/README.md +18 -18
  2. package/dist/wgblas.browser.js +1273 -1239
  3. package/index.d.mts +38 -6
  4. package/package.json +2 -1
  5. package/src/classes/GpuMatrix.mjs +17 -10
  6. package/src/classes/GpuVector.mjs +28 -10
  7. package/src/dasum/dasum.d.mts +4 -4
  8. package/src/dasum/dasum.mjs +19 -17
  9. package/src/devdocs.mjs +13 -0
  10. package/src/idamax/idamax.d.mts +20 -2
  11. package/src/idamax/idamax.mjs +18 -16
  12. package/src/init.mjs +114 -56
  13. package/src/isamax/isamax.d.mts +20 -2
  14. package/src/isamax/isamax.mjs +16 -14
  15. package/src/random/random.d.mts +1 -0
  16. package/src/sasum/sasum.d.mts +2 -2
  17. package/src/sasum/sasum.mjs +15 -13
  18. package/src/saxpy/saxpy.d.mts +2 -2
  19. package/src/saxpy/saxpy.mjs +10 -8
  20. package/src/scopy/scopy.d.mts +2 -2
  21. package/src/scopy/scopy.mjs +10 -8
  22. package/src/sdot/sdot.d.mts +2 -2
  23. package/src/sdot/sdot.mjs +16 -14
  24. package/src/sgemm/sgemm.mjs +28 -15
  25. package/src/sgemmtr/sgemmtr.mjs +16 -15
  26. package/src/sgemv/sgemv.mjs +38 -26
  27. package/src/sger/sger.mjs +10 -8
  28. package/src/shaders/index.mjs +164 -14
  29. package/src/shaders/reduction/scaledSum.wgsl +65 -0
  30. package/src/shaders/sgemm_large.wgsl +107 -18
  31. package/src/shaders/sgemm_small.wgsl +115 -15
  32. package/src/shaders/sgemmtr_large.wgsl +4 -1
  33. package/src/shaders/sgemmtr_small.wgsl +4 -1
  34. package/src/shaders/sgemv_n.wgsl +3 -1
  35. package/src/shaders/sgemv_t.wgsl +3 -1
  36. package/src/shaders/snrm2.wgsl +72 -23
  37. package/src/shaders/ssymv.wgsl +3 -1
  38. package/src/snrm2/snrm2.d.mts +2 -2
  39. package/src/snrm2/snrm2.mjs +33 -21
  40. package/src/srot/srot.d.mts +2 -4
  41. package/src/srot/srot.mjs +11 -9
  42. package/src/srotm/srotm.d.mts +2 -4
  43. package/src/srotm/srotm.mjs +19 -10
  44. package/src/sscal/sscal.d.mts +3 -3
  45. package/src/sscal/sscal.mjs +12 -10
  46. package/src/sswap/sswap.d.mts +2 -2
  47. package/src/sswap/sswap.mjs +11 -9
  48. package/src/ssymm/ssymm.mjs +31 -22
  49. package/src/ssymv/ssymv.mjs +10 -8
  50. package/src/ssyr/ssyr.mjs +9 -7
  51. package/src/ssyr2/ssyr2.mjs +10 -8
  52. package/src/ssyr2k/ssyr2k.mjs +18 -17
  53. package/src/ssyrk/ssyrk.mjs +18 -17
  54. package/src/strmm/strmm.mjs +47 -32
  55. package/src/strmv/strmv.mjs +10 -8
  56. package/src/strsm/strsm.mjs +54 -36
  57. package/src/strsv/strsv.mjs +16 -12
  58. package/src/util/benchmark.mjs +4 -6
  59. package/src/util/bindgroup.mjs +1 -3
  60. package/src/util/buffer.mjs +113 -19
  61. package/src/util/compute.mjs +6 -9
  62. package/src/util/constants.mjs +57 -0
  63. package/src/util/device.mjs +34 -0
  64. package/src/util/pipeline.mjs +5 -6
  65. package/src/util/workgroup.mjs +55 -7
  66. package/src/shaders/browser-shaders.mjs +0 -81
package/src/init.mjs CHANGED
@@ -1,10 +1,27 @@
1
1
  import { benchmarkMode } from "./util/benchmark.mjs";
2
2
 
3
- let _device = null;
4
- let _adapter = null;
5
- let _gpu = null; // eslint-disable-line no-unused-vars
6
- let _benchmarkEnabled = false;
7
-
3
+ // One WebGPU instance for the whole process, never released. A GPUAdapter
4
+ // yields at most one working device, so a second device needs a second
5
+ // adapter — but creating a second *instance* aborts Dawn during native
6
+ // teardown (std::system_error), whether the instances overlap or are made one
7
+ // after another. So the instance is built once and every adapter comes from
8
+ // it; cleanup() releases devices, not this.
9
+ let _gpu = null;
10
+ let _dumpShaders = false; // instance-level Dawn toggle, fixed when _gpu is made
11
+
12
+ // Resolved-options key -> GPUDevice. init() returns the cached device for a
13
+ // given option set and creates one per distinct set, so a process can drive
14
+ // several GPUs at once (e.g. discrete via "high-performance", integrated via
15
+ // "low-power").
16
+ const _devices = new Map();
17
+ // GPUDevice -> { adapter, benchmark, options }. Benchmark support is a
18
+ // property of the device (its requiredFeatures), not of the library.
19
+ const _meta = new WeakMap();
20
+ // The device from the first init(); what getDevice() returns for callers that
21
+ // never mention one (GpuVector.from(data), GpuMatrix.from(data, ...)).
22
+ let _primary = null;
23
+
24
+ const optionsKey = ({ powerPreference, benchmark }) => `${powerPreference}::${benchmark}`;
8
25
 
9
26
  // ── Public API ───────────────────────────────────────────────────────────────
10
27
 
@@ -13,103 +30,144 @@ export async function init({
13
30
  benchmark = false,
14
31
  dumpShaders = false,
15
32
  } = {}) {
16
- if (_device) {
17
- return _device;
18
- }
33
+ const options = { powerPreference, benchmark, dumpShaders };
34
+ const key = optionsKey(options);
35
+
36
+ // Same options: idempotent, hand back the device already built for them.
37
+ const cached = _devices.get(key);
38
+ if (cached) return cached;
19
39
 
20
- let gpu;
21
40
  // Browser exposes WebGPU natively via navigator.gpu.
22
41
  // Node.js has no navigator, so we polyfill using the "webgpu" npm package which also
23
42
  // injects WebGPU globals (GPUBufferUsage, GPUShaderStage, etc.) into globalThis.
24
- if (typeof window === "undefined") {
25
- const { create, globals } = await import("webgpu");
26
- Object.assign(globalThis, globals);
27
- // dumpShaders forwards Dawn's own debug toggle — prints each pipeline's
28
- // WGSL and compiled backend IR to stderr. Node-only; see index.d.mts.
29
- const toggles = dumpShaders
30
- ? ["enable-dawn-features=dump_shaders,disable_symbol_renaming"]
31
- : [];
32
- gpu = create(toggles);
33
- _gpu = gpu;
34
- } else {
35
- if (dumpShaders)
36
- console.warn("dumpShaders has no effect in the browser — see init()'s docs.");
37
- gpu = navigator.gpu;
43
+ if (!_gpu) {
44
+ if (typeof window === "undefined") {
45
+ const { create, globals } = await import("webgpu");
46
+ Object.assign(globalThis, globals);
47
+ // dumpShaders forwards Dawn's own debug toggle — prints each pipeline's
48
+ // WGSL and compiled backend IR to stderr. Node-only; see index.d.mts.
49
+ const toggles = dumpShaders
50
+ ? ["enable-dawn-features=dump_shaders,disable_symbol_renaming"]
51
+ : [];
52
+ _gpu = create(toggles);
53
+ _dumpShaders = dumpShaders;
54
+ } else {
55
+ if (dumpShaders)
56
+ console.warn("dumpShaders has no effect in the browser — see init()'s docs.");
57
+ _gpu = navigator.gpu;
58
+ }
59
+ } else if (dumpShaders !== _dumpShaders && typeof window === "undefined") {
60
+ // Unlike powerPreference and benchmark, dumpShaders is a toggle on the Dawn
61
+ // instance rather than the device, and the instance is shared, so a later
62
+ // init() cannot change it.
63
+ console.warn(
64
+ `dumpShaders: ${dumpShaders} was requested, but the WebGPU instance was already created with ` +
65
+ `dumpShaders: ${_dumpShaders}. The first init() call fixes this for the process.`,
66
+ );
38
67
  }
39
68
 
40
- if (!gpu) {
69
+ if (!_gpu) {
41
70
  throw new Error("WebGPU not supported in this environment.");
42
71
  }
43
72
 
44
- _adapter =
45
- (await gpu.requestAdapter({ powerPreference })) ??
46
- (await gpu.requestAdapter());
47
- if (!_adapter) {
73
+ // A fresh adapter per device: requesting a device consumes its adapter, so
74
+ // reusing one would hand back an already-lost device.
75
+ const adapter =
76
+ (await _gpu.requestAdapter({ powerPreference })) ??
77
+ (await _gpu.requestAdapter());
78
+ if (!adapter) {
48
79
  throw new Error("No WebGPU adapter found.");
49
80
  }
50
81
 
51
- _benchmarkEnabled = benchmark;
52
- const bmConfig = benchmarkMode(_adapter, benchmark);
82
+ const bmConfig = benchmarkMode(adapter, benchmark);
53
83
  const features = [...(bmConfig.requiredFeatures ?? [])];
54
- _device = await _adapter.requestDevice({ requiredFeatures: features });
84
+ const device = await adapter.requestDevice({ requiredFeatures: features });
55
85
  // Fires for any GPU error not caught by a pushErrorScope/popErrorScope pair — surfaces silent GPU failures to the console.
56
86
  // See: https://developer.mozilla.org/en-US/docs/Web/API/GPUDevice/uncapturederror_event
57
- _device.addEventListener("uncapturederror", (e) => {
87
+ device.addEventListener("uncapturederror", (e) => {
58
88
  console.error("Uncaptured GPU error:", e.error.message);
59
89
  });
60
90
 
61
- return _device;
91
+ // benchmarkMode() drops the feature when the adapter can't do timestamp
92
+ // queries, so record what was actually granted rather than what was asked
93
+ // for — otherwise beginTimestamp() would build a query set on a device that
94
+ // never requested the feature.
95
+ const benchmarkGranted = features.includes("timestamp-query");
96
+ _meta.set(device, { adapter, benchmark: benchmarkGranted, options });
97
+ _devices.set(key, device);
98
+ if (!_primary) _primary = device;
99
+
100
+ return device;
62
101
  }
63
102
 
64
- export function cleanup() {
65
- if (_device) {
66
- _device.destroy();
67
- _device = null;
103
+ export function cleanup(device) {
104
+ if (device === undefined) {
105
+ for (const d of _devices.values()) d.destroy();
106
+ _devices.clear();
107
+ _primary = null;
108
+ return;
68
109
  }
69
- _adapter = null;
70
- _gpu = null;
71
- _benchmarkEnabled = false;
110
+
111
+ // Releasing one device of several. Unknown or already-released devices are a
112
+ // no-op so teardown paths can call this unguarded.
113
+ const meta = _meta.get(device);
114
+ if (!meta) return;
115
+ _devices.delete(optionsKey(meta.options));
116
+ _meta.delete(device);
117
+ device.destroy();
118
+
119
+ // getDevice() must keep answering while any device is left, so promote a
120
+ // survivor when the primary is the one being released.
121
+ if (_primary === device) _primary = _devices.values().next().value ?? null;
72
122
  }
73
123
 
74
- export function gpuName() {
75
- if (!_adapter) {
124
+ export function gpuName(device = _primary) {
125
+ const meta = device && _meta.get(device);
126
+ if (!meta) {
76
127
  throw new Error("WebGPU adapter not initialized — call init() first.");
77
128
  }
78
- const { device, description } = _adapter.info;
129
+ const { device: deviceName, description } = meta.adapter.info;
79
130
  return {
80
131
  description: description || "unknown",
81
- device: device || "unknown",
132
+ device: deviceName || "unknown",
82
133
  };
83
134
  }
84
135
 
85
136
  // ── Library internals (not part of the public API) ───────────────────────────
86
137
 
87
- /** @returns {boolean} whether benchmark mode was enabled in the last `init()` call */
88
- export function isBenchmarkEnabled() {
89
- return _benchmarkEnabled;
138
+ /**
139
+ * Whether benchmark mode is active for `device` — i.e. it was created with
140
+ * `benchmark: true` *and* its adapter actually supports timestamp queries.
141
+ * @param {GPUDevice} [device] - defaults to the first-initialized device
142
+ * @returns {boolean}
143
+ */
144
+ export function isBenchmarkEnabled(device = _primary) {
145
+ return _meta.get(device)?.benchmark ?? false;
90
146
  }
91
147
 
92
148
  /**
93
- * Returns the active `GPUDevice`. Throws if `init()` has not been called.
149
+ * Returns the device from the first `init()` call — the default for callers
150
+ * that don't name one. Throws if `init()` has not been called.
94
151
  * @returns {GPUDevice}
95
- * @throws {Error} if the device is not initialized
152
+ * @throws {Error} if no device is initialized
96
153
  */
97
154
  export function getDevice() {
98
- if (!_device) {
155
+ if (!_primary) {
99
156
  throw new Error("WebGPU device not initialized — call init() first.");
100
157
  }
101
- return _device;
158
+ return _primary;
102
159
  }
103
160
 
104
161
  /**
105
- * Returns the active `GPUAdapter`. Throws if `init()` has not been called.
162
+ * Returns the `GPUAdapter` backing `device`. Throws if it isn't initialized.
163
+ * @param {GPUDevice} [device] - defaults to the first-initialized device
106
164
  * @returns {GPUAdapter}
107
165
  * @throws {Error} if the adapter is not initialized
108
166
  */
109
- export function getAdapter() {
110
- if (!_adapter) {
167
+ export function getAdapter(device = _primary) {
168
+ const meta = device && _meta.get(device);
169
+ if (!meta) {
111
170
  throw new Error("WebGPU adapter not initialized — call init() first.");
112
171
  }
113
- return _adapter;
172
+ return meta.adapter;
114
173
  }
115
-
@@ -1,9 +1,18 @@
1
1
  import { GpuVector } from "../classes/GpuVector.mjs";
2
2
 
3
3
  /**
4
- * Returns the 0-based index of the element with the largest absolute value.
4
+ * Returns the 0-based index of the element with the largest absolute value:
5
+ * $$\text{index} = \arg\max_{i} |x_i|$$
5
6
  * Ties are broken in favour of the lower index, matching CBLAS behaviour.
6
7
  *
8
+ * **NaN handling.** The search compares with `>`, which is false for NaN, so
9
+ * NaN elements are skipped rather than selected: a vector of all NaN returns
10
+ * `0`. This matches CBLAS except when `x[0]` itself is NaN — CBLAS seeds its
11
+ * running maximum from `x[0]`, and since no comparison against NaN succeeds it
12
+ * returns `0` however large the later elements are, whereas this returns the
13
+ * index of the largest non-NaN element. `+-Infinity` compares normally and is
14
+ * selected as the maximum.
15
+ *
7
16
  * {@includeCode ../../examples/isamax/isamax.js}
8
17
  *
9
18
  * **Browser (standalone HTML):**
@@ -25,9 +34,18 @@ export declare function isamax(
25
34
  ): Promise<{ index: number } | { index: number; gpuTimeMs: number }>;
26
35
 
27
36
  /**
28
- * Returns the 0-based index of the element with the largest absolute value.
37
+ * Returns the 0-based index of the element with the largest absolute value:
38
+ * $$\text{index} = \arg\max_{i} |x_i|$$
29
39
  * Ties are broken in favour of the lower index, matching CBLAS behaviour.
30
40
  *
41
+ * **NaN handling.** The search compares with `>`, which is false for NaN, so
42
+ * NaN elements are skipped rather than selected: a vector of all NaN returns
43
+ * `0`. This matches CBLAS except when `x[0]` itself is NaN — CBLAS seeds its
44
+ * running maximum from `x[0]`, and since no comparison against NaN succeeds it
45
+ * returns `0` however large the later elements are, whereas this returns the
46
+ * index of the largest non-NaN element. `+-Infinity` compares normally and is
47
+ * selected as the maximum.
48
+ *
31
49
  * {@includeCode ../../examples/isamax/gpu.isamax.js}
32
50
  *
33
51
  * @param device - GPUDevice from `init()`
@@ -12,14 +12,16 @@ import { extractTimestamp } from "../util/benchmark.mjs";
12
12
  import { extractResult } from "../util/result.mjs";
13
13
  import { getPipeline } from "../util/pipeline.mjs";
14
14
  import { GpuVector } from "../classes/GpuVector.mjs";
15
+ import { WGS } from "../util/constants.mjs";
16
+ import { requireSameDevice } from "../util/device.mjs";
15
17
 
16
- const WGS = 64;
17
18
 
18
19
  export async function isamax(device, n, x, incx) {
19
20
  const xIsGpu = x instanceof GpuVector;
20
21
 
21
22
  if (!(device instanceof GPUDevice))
22
23
  throw new Error("device must be a GPUDevice.");
24
+ requireSameDevice(device, "isamax", { x });
23
25
  if (!Number.isInteger(n) || !Number.isInteger(incx))
24
26
  throw new Error("n and incx must be integers.");
25
27
  if (incx <= 0) throw new Error("incx must be positive.");
@@ -42,17 +44,17 @@ export async function isamax(device, n, x, incx) {
42
44
  let readBuffer = null;
43
45
 
44
46
  try {
45
- xBuffer = xIsGpu ? x._buf : uploadBuffer(x, "isamax-x", false);
46
- partialsValBuffer = createStorageBuffer(
47
+ xBuffer = xIsGpu ? x._buf : uploadBuffer(device, x, "isamax-x", false);
48
+ partialsValBuffer = createStorageBuffer(device,
47
49
  2 * WGS * 4,
48
50
  "isamax-partials-val",
49
51
  ); //to hold 2*WGS partial max values of f32
50
- partialsIdxBuffer = createStorageBuffer(
52
+ partialsIdxBuffer = createStorageBuffer(device,
51
53
  2 * WGS * 4,
52
54
  "isamax-partials-idx",
53
55
  ); //to hold 2*WGS partial max indices of u32
54
- resultBuffer = createResultBuffer(4, "isamax-result"); // u32 index
55
- paramsBuffer = createParamsBuffer(
56
+ resultBuffer = createResultBuffer(device, 4, "isamax-result"); // u32 index
57
+ paramsBuffer = createParamsBuffer(device,
56
58
  [
57
59
  { value: n, type: "u32" },
58
60
  { value: incx, type: "u32" },
@@ -60,33 +62,33 @@ export async function isamax(device, n, x, incx) {
60
62
  "isamax-params",
61
63
  );
62
64
 
63
- const bgMain = createBindGroup(pipelineMain.getBindGroupLayout(0), [
65
+ const bgMain = createBindGroup(device, pipelineMain.getBindGroupLayout(0), [
64
66
  xBuffer,
65
67
  partialsValBuffer,
66
68
  partialsIdxBuffer,
67
69
  paramsBuffer,
68
70
  ]);
69
- const { commandEncoder: enc1, ts: ts1 } = runComputePass(
71
+ const { commandEncoder: enc1, ts: ts1 } = runComputePass(device,
70
72
  pipelineMain,
71
73
  bgMain,
72
74
  2 * WGS,
73
75
  ); //dispatch 2*WGS workgroups
74
76
 
75
- submit(enc1);
77
+ submit(device, enc1);
76
78
 
77
- const bgReduce = createBindGroup(pipelineReduce.getBindGroupLayout(0), [
79
+ const bgReduce = createBindGroup(device, pipelineReduce.getBindGroupLayout(0), [
78
80
  partialsValBuffer,
79
81
  partialsIdxBuffer,
80
82
  resultBuffer,
81
83
  ]);
82
- const { commandEncoder: enc2, ts: ts2 } = runComputePass(
84
+ const { commandEncoder: enc2, ts: ts2 } = runComputePass(device,
83
85
  pipelineReduce,
84
86
  bgReduce,
85
87
  1,
86
88
  ); // dispatch 1 workgroup to reduce the partial sums to a single result
87
- readBuffer = stageReadback(enc2, resultBuffer);
89
+ readBuffer = stageReadback(device, enc2, resultBuffer);
88
90
 
89
- submit(enc2);
91
+ submit(device, enc2);
90
92
 
91
93
  const resultPromise = extractResult(readBuffer, Uint32Array);
92
94
  readBuffer = null; // ownership transferred — extractResult's own finally destroys it
@@ -108,7 +110,7 @@ export async function isamax(device, n, x, incx) {
108
110
  if (partialsIdxBuffer) destroyBuffers(partialsIdxBuffer);
109
111
  if (resultBuffer) destroyBuffers(resultBuffer);
110
112
  if (paramsBuffer) destroyBuffers(paramsBuffer);
111
- // Only reached if submit(enc2) threw before ownership was transferred above.
113
+ // Only reached if submit(device, enc2) threw before ownership was transferred above.
112
114
  if (readBuffer) destroyBuffers(readBuffer);
113
115
  }
114
116
  }
@@ -83,6 +83,7 @@ export declare function randomFloat64Array(
83
83
  *
84
84
  * const n = 4, lda = n;
85
85
  * const A = randomTriangularFloat32Array(n, lda, "lower");
86
+ * console.log(A);
86
87
  * ```
87
88
  * @see <a href="https://github.com/manit2004/wgblas/blob/main/src/random/random.mjs#L13">Source code: random.mjs (L13)</a>
88
89
  * @category Utilities
@@ -1,7 +1,7 @@
1
1
  import { GpuVector } from "../classes/GpuVector.mjs";
2
2
 
3
3
  /**
4
- * Computes the sum of absolute values of a vector: result = sum(|x[i]|)
4
+ * Computes the sum of absolute values of a vector: $$\text{result} = \sum_{i} |x_i|$$
5
5
  *
6
6
  * {@includeCode ../../examples/sasum/sasum.js}
7
7
  *
@@ -24,7 +24,7 @@ export declare function sasum(
24
24
  ): Promise<{ asum: number } | { asum: number; gpuTimeMs: number }>;
25
25
 
26
26
  /**
27
- * Computes the sum of absolute values of a vector: result = sum(|x[i]|)
27
+ * Computes the sum of absolute values of a vector: $$\text{result} = \sum_{i} |x_i|$$
28
28
  *
29
29
  * {@includeCode ../../examples/sasum/gpu.sasum.js}
30
30
  *
@@ -12,14 +12,16 @@ import { extractTimestamp } from "../util/benchmark.mjs";
12
12
  import { extractResult } from "../util/result.mjs";
13
13
  import { getPipeline } from "../util/pipeline.mjs";
14
14
  import { GpuVector } from "../classes/GpuVector.mjs";
15
+ import { WGS } from "../util/constants.mjs";
16
+ import { requireSameDevice } from "../util/device.mjs";
15
17
 
16
- const WGS = 64; // workgroup size
17
18
 
18
19
  export async function sasum(device, n, x, incx) {
19
20
  const xIsGpu = x instanceof GpuVector;
20
21
 
21
22
  if (!(device instanceof GPUDevice))
22
23
  throw new Error("device must be a GPUDevice.");
24
+ requireSameDevice(device, "sasum", { x });
23
25
  if (!Number.isInteger(n) || !Number.isInteger(incx))
24
26
  throw new Error("n and incx must be integers.");
25
27
  if (incx <= 0) throw new Error("incx must be positive.");
@@ -41,10 +43,10 @@ export async function sasum(device, n, x, incx) {
41
43
  let readBuffer = null;
42
44
 
43
45
  try {
44
- xBuffer = xIsGpu ? x._buf : uploadBuffer(x, "sasum-x", false);
45
- partialsBuffer = createStorageBuffer(2 * WGS * 4, "sasum-partials"); // 2*WGS partial sums of f32
46
- resultBuffer = createResultBuffer(4, "sasum-result"); // final f32 scalar
47
- paramsBuffer = createParamsBuffer(
46
+ xBuffer = xIsGpu ? x._buf : uploadBuffer(device, x, "sasum-x", false);
47
+ partialsBuffer = createStorageBuffer(device, 2 * WGS * 4, "sasum-partials"); // 2*WGS partial sums of f32
48
+ resultBuffer = createResultBuffer(device, 4, "sasum-result"); // final f32 scalar
49
+ paramsBuffer = createParamsBuffer(device,
48
50
  [
49
51
  { value: n, type: "u32" },
50
52
  { value: incx, type: "u32" },
@@ -52,31 +54,31 @@ export async function sasum(device, n, x, incx) {
52
54
  "sasum-params",
53
55
  );
54
56
 
55
- const bgMain = createBindGroup(pipelineMain.getBindGroupLayout(0), [
57
+ const bgMain = createBindGroup(device, pipelineMain.getBindGroupLayout(0), [
56
58
  xBuffer,
57
59
  partialsBuffer,
58
60
  paramsBuffer,
59
61
  ]);
60
- const { commandEncoder: enc1, ts: ts1 } = runComputePass(
62
+ const { commandEncoder: enc1, ts: ts1 } = runComputePass(device,
61
63
  pipelineMain,
62
64
  bgMain,
63
65
  2 * WGS,
64
66
  ); // dispatch 2*WGS workgroups
65
67
 
66
- submit(enc1);
68
+ submit(device, enc1);
67
69
 
68
- const bgReduce = createBindGroup(pipelineReduce.getBindGroupLayout(0), [
70
+ const bgReduce = createBindGroup(device, pipelineReduce.getBindGroupLayout(0), [
69
71
  partialsBuffer,
70
72
  resultBuffer,
71
73
  ]);
72
- const { commandEncoder: enc2, ts: ts2 } = runComputePass(
74
+ const { commandEncoder: enc2, ts: ts2 } = runComputePass(device,
73
75
  pipelineReduce,
74
76
  bgReduce,
75
77
  1,
76
78
  ); // dispatch 1 workgroup to reduce the partial sums to a single result
77
- readBuffer = stageReadback(enc2, resultBuffer);
79
+ readBuffer = stageReadback(device, enc2, resultBuffer);
78
80
 
79
- submit(enc2);
81
+ submit(device, enc2);
80
82
 
81
83
  const resultPromise = extractResult(readBuffer, Float32Array);
82
84
  readBuffer = null; // ownership transferred — extractResult's own finally destroys it
@@ -96,7 +98,7 @@ export async function sasum(device, n, x, incx) {
96
98
  if (partialsBuffer) destroyBuffers(partialsBuffer);
97
99
  if (resultBuffer) destroyBuffers(resultBuffer);
98
100
  if (paramsBuffer) destroyBuffers(paramsBuffer);
99
- // Only reached if submit(enc2) threw before ownership was transferred above.
101
+ // Only reached if submit(device, enc2) threw before ownership was transferred above.
100
102
  if (readBuffer) destroyBuffers(readBuffer);
101
103
  }
102
104
  }
@@ -1,7 +1,7 @@
1
1
  import { GpuVector } from "../classes/GpuVector.mjs";
2
2
 
3
3
  /**
4
- * Performs the operation y = alpha * x + y
4
+ * Performs the operation $$y \leftarrow \alpha x + y$$
5
5
  *
6
6
  * {@includeCode ../../examples/saxpy/saxpy.js}
7
7
  *
@@ -29,7 +29,7 @@ export declare function saxpy(
29
29
  ): Promise<{ y: Float32Array } | { y: Float32Array; gpuTimeMs: number }>;
30
30
 
31
31
  /**
32
- * Performs the operation y = alpha * x + y
32
+ * Performs the operation $$y \leftarrow \alpha x + y$$
33
33
  *
34
34
  * {@includeCode ../../examples/saxpy/gpu.saxpy.js}
35
35
  *
@@ -11,6 +11,7 @@ import { extractTimestamp } from "../util/benchmark.mjs";
11
11
  import { getPipeline } from "../util/pipeline.mjs";
12
12
  import { calcWorkgroups } from "../util/workgroup.mjs";
13
13
  import { GpuVector } from "../classes/GpuVector.mjs";
14
+ import { requireSameDevice } from "../util/device.mjs";
14
15
 
15
16
  export async function saxpy(device, n, alpha, x, incx, y, incy) {
16
17
  const xIsGpu = x instanceof GpuVector;
@@ -18,6 +19,7 @@ export async function saxpy(device, n, alpha, x, incx, y, incy) {
18
19
 
19
20
  if (!(device instanceof GPUDevice))
20
21
  throw new Error("device must be a GPUDevice.");
22
+ requireSameDevice(device, "saxpy", { x, y });
21
23
  if (
22
24
  !Number.isInteger(n) ||
23
25
  !Number.isInteger(incx) ||
@@ -56,9 +58,9 @@ export async function saxpy(device, n, alpha, x, incx, y, incy) {
56
58
  let readBuffer = null;
57
59
 
58
60
  try {
59
- xBuffer = xIsGpu ? x._buf : uploadBuffer(x, "saxpy-x", false);
60
- yBuffer = yIsGpu ? y._buf : uploadBuffer(y, "saxpy-y", true);
61
- paramsBuffer = createParamsBuffer(
61
+ xBuffer = xIsGpu ? x._buf : uploadBuffer(device, x, "saxpy-x", false);
62
+ yBuffer = yIsGpu ? y._buf : uploadBuffer(device, y, "saxpy-y", true);
63
+ paramsBuffer = createParamsBuffer(device,
62
64
  [
63
65
  { value: n, type: "u32" },
64
66
  { value: alpha, type: "f32" },
@@ -68,19 +70,19 @@ export async function saxpy(device, n, alpha, x, incx, y, incy) {
68
70
  "saxpy-params",
69
71
  );
70
72
 
71
- const bindGroup = createBindGroup(pipeline.getBindGroupLayout(0), [
73
+ const bindGroup = createBindGroup(device, pipeline.getBindGroupLayout(0), [
72
74
  xBuffer,
73
75
  yBuffer,
74
76
  paramsBuffer,
75
77
  ]);
76
- const { commandEncoder, ts } = runComputePass(
78
+ const { commandEncoder, ts } = runComputePass(device,
77
79
  pipeline,
78
80
  bindGroup,
79
- calcWorkgroups(n),
81
+ calcWorkgroups(device, n),
80
82
  );
81
- readBuffer = yIsGpu ? null : stageReadback(commandEncoder, yBuffer);
83
+ readBuffer = yIsGpu ? null : stageReadback(device, commandEncoder, yBuffer);
82
84
 
83
- submit(commandEncoder);
85
+ submit(device, commandEncoder);
84
86
 
85
87
  const gpuTimeMs = await extractTimestamp(ts);
86
88
 
@@ -1,7 +1,7 @@
1
1
  import { GpuVector } from "../classes/GpuVector.mjs";
2
2
 
3
3
  /**
4
- * Performs the operation y = x
4
+ * Performs the operation $$y \leftarrow x$$
5
5
  *
6
6
  * {@includeCode ../../examples/scopy/scopy.js}
7
7
  *
@@ -27,7 +27,7 @@ export declare function scopy(
27
27
  ): Promise<{ y: Float32Array } | { y: Float32Array; gpuTimeMs: number }>;
28
28
 
29
29
  /**
30
- * Performs the operation y = x
30
+ * Performs the operation $$y \leftarrow x$$
31
31
  *
32
32
  * {@includeCode ../../examples/scopy/gpu.scopy.js}
33
33
  *
@@ -11,6 +11,7 @@ import { extractTimestamp } from "../util/benchmark.mjs";
11
11
  import { getPipeline } from "../util/pipeline.mjs";
12
12
  import { calcWorkgroups } from "../util/workgroup.mjs";
13
13
  import { GpuVector } from "../classes/GpuVector.mjs";
14
+ import { requireSameDevice } from "../util/device.mjs";
14
15
 
15
16
  export async function scopy(device, n, x, incx, y, incy) {
16
17
  const xIsGpu = x instanceof GpuVector;
@@ -18,6 +19,7 @@ export async function scopy(device, n, x, incx, y, incy) {
18
19
 
19
20
  if (!(device instanceof GPUDevice))
20
21
  throw new Error("device must be a GPUDevice.");
22
+ requireSameDevice(device, "scopy", { x, y });
21
23
  if (
22
24
  !Number.isInteger(n) ||
23
25
  !Number.isInteger(incx) ||
@@ -52,9 +54,9 @@ export async function scopy(device, n, x, incx, y, incy) {
52
54
  let readBuffer = null;
53
55
 
54
56
  try {
55
- xBuffer = xIsGpu ? x._buf : uploadBuffer(x, "scopy-x", false);
56
- yBuffer = yIsGpu ? y._buf : uploadBuffer(y, "scopy-y", true);
57
- paramsBuffer = createParamsBuffer(
57
+ xBuffer = xIsGpu ? x._buf : uploadBuffer(device, x, "scopy-x", false);
58
+ yBuffer = yIsGpu ? y._buf : uploadBuffer(device, y, "scopy-y", true);
59
+ paramsBuffer = createParamsBuffer(device,
58
60
  [
59
61
  { value: n, type: "u32" },
60
62
  { value: incx, type: "u32" },
@@ -63,19 +65,19 @@ export async function scopy(device, n, x, incx, y, incy) {
63
65
  "scopy-params",
64
66
  );
65
67
 
66
- const bindGroup = createBindGroup(pipeline.getBindGroupLayout(0), [
68
+ const bindGroup = createBindGroup(device, pipeline.getBindGroupLayout(0), [
67
69
  xBuffer,
68
70
  yBuffer,
69
71
  paramsBuffer,
70
72
  ]);
71
- const { commandEncoder, ts } = runComputePass(
73
+ const { commandEncoder, ts } = runComputePass(device,
72
74
  pipeline,
73
75
  bindGroup,
74
- calcWorkgroups(n),
76
+ calcWorkgroups(device, n),
75
77
  );
76
- readBuffer = yIsGpu ? null : stageReadback(commandEncoder, yBuffer);
78
+ readBuffer = yIsGpu ? null : stageReadback(device, commandEncoder, yBuffer);
77
79
 
78
- submit(commandEncoder);
80
+ submit(device, commandEncoder);
79
81
 
80
82
  const gpuTimeMs = await extractTimestamp(ts);
81
83
 
@@ -1,7 +1,7 @@
1
1
  import { GpuVector } from "../classes/GpuVector.mjs";
2
2
 
3
3
  /**
4
- * Computes the dot product of two vectors: result = sum(x[i] * y[i])
4
+ * Computes the dot product of two vectors: $$\text{result} = \sum_{i} x_i y_i$$
5
5
  *
6
6
  * {@includeCode ../../examples/sdot/sdot.js}
7
7
  *
@@ -28,7 +28,7 @@ export declare function sdot(
28
28
  ): Promise<{ dot: number } | { dot: number; gpuTimeMs: number }>;
29
29
 
30
30
  /**
31
- * Computes the dot product of two vectors: result = sum(x[i] * y[i])
31
+ * Computes the dot product of two vectors: $$\text{result} = \sum_{i} x_i y_i$$
32
32
  *
33
33
  * {@includeCode ../../examples/sdot/gpu.sdot.js}
34
34
  *