@danielsimonjr/mathts-functions 0.18.0 → 0.19.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.
@@ -7,11 +7,18 @@
7
7
  * storage buffers, and reads back once, so the transfer is amortized across the
8
8
  * whole chain.
9
9
  *
10
- * **Read this before reaching for the GPU:** for element-wise work the GPU is
11
- * *slower* than WASM (~1.9×, measured) *and* less precise (f32 vs f64). It earns
12
- * its place only where WASM cannot load. `fuseUnaryChainAsync` therefore tries
13
- * WASM first. The GPU wins decisively on **compute-bound** work see
14
- * `gpuMatmul`, where O(n³) arithmetic amortizes the O(n²) transfer.
10
+ * **Read this before reaching for the GPU:** for element-wise chains the GPU is
11
+ * the *fastest* tier (3.2–8.3× over WASM see the table on
12
+ * `fuseUnaryChainAsync`), but it computes in **f32** where every CPU tier is
13
+ * f64-exact. That is the whole trade, and `enableGpu()` is how a caller consents
14
+ * to it. `fuseUnaryChainAsync` therefore tries the GPU first, but only when the
15
+ * flag is on; with it off (the default) the GPU never runs.
16
+ *
17
+ * An earlier revision of this comment claimed the GPU was ~1.9× *slower* than
18
+ * WASM. That was an artifact of a `Float32Array.from()` in this very file — the
19
+ * generic `Array.from` path, which cost 433 ms at n=2²⁰ where the constructor
20
+ * costs 5.9 ms. Do not re-derive a tier ranking from a single tier's number; see
21
+ * `gpu-vs-wasm.browser.test.ts`, which measures all three in one run.
15
22
  *
16
23
  * Contract (mirrors the WASM `elementwiseChainDispatch`): a **never-throw**
17
24
  * best-effort fast path. It returns `null` — never rejects — whenever the GPU is
@@ -1 +1 @@
1
- {"version":3,"file":"elementwise-gpu.d.ts","sourceRoot":"","sources":["../../src/gpu/elementwise-gpu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAOL,KAAK,iBAAiB,EACvB,MAAM,2BAA2B,CAAC;AA+CnC,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;CAgCR,CAAC;AAEX,uEAAuE;AACvE,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,YAAY,CAAC;AAEzD,eAAO,MAAM,mBAAmB,EAAgC,gBAAgB,EAAE,CAAC;AAEnF,sDAAsD;AACtD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,GAAG,IAAI,SAAS,gBAAgB,EAAE,CAE9F;AAsED,uEAAuE;AACvE,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C;AAED,+CAA+C;AAC/C,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,SAAS,MAAM,EAAE,EACtB,EAAE,EAAE,YAAY,GAAG,YAAY,EAC/B,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAuH9B"}
1
+ {"version":3,"file":"elementwise-gpu.d.ts","sourceRoot":"","sources":["../../src/gpu/elementwise-gpu.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAOL,KAAK,iBAAiB,EACvB,MAAM,2BAA2B,CAAC;AA+CnC,QAAA,MAAM,YAAY;;;;;;;;;;;;;;;;CAgCR,CAAC;AAEX,uEAAuE;AACvE,MAAM,MAAM,gBAAgB,GAAG,MAAM,OAAO,YAAY,CAAC;AAEzD,eAAO,MAAM,mBAAmB,EAAgC,gBAAgB,EAAE,CAAC;AAEnF,sDAAsD;AACtD,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,SAAS,MAAM,EAAE,GAAG,GAAG,IAAI,SAAS,gBAAgB,EAAE,CAE9F;AAsED,uEAAuE;AACvE,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C;AAED,+CAA+C;AAC/C,MAAM,WAAW,eAAgB,SAAQ,iBAAiB;IACxD;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED;;;;;;GAMG;AACH,wBAAsB,2BAA2B,CAC/C,GAAG,EAAE,SAAS,MAAM,EAAE,EACtB,EAAE,EAAE,YAAY,GAAG,YAAY,EAC/B,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CA6H9B"}
package/dist/index.js CHANGED
@@ -7480,7 +7480,7 @@ async function elementwiseChainGpuDispatch(ops, xs, options) {
7480
7480
  if (workgroups > limits.maxComputeWorkgroupsPerDimension) return null;
7481
7481
  if (bytes > limits.maxStorageBufferBindingSize) return null;
7482
7482
  if (bytes > limits.maxBufferSize) return null;
7483
- const input = xs instanceof Float32Array ? xs : Float32Array.from(xs);
7483
+ const input = xs instanceof Float32Array ? xs : new Float32Array(xs);
7484
7484
  device.pushErrorScope("validation");
7485
7485
  bufA = pool.acquireStorageBuffer(bytes, "chain-a", true, true);
7486
7486
  bufB = pool.acquireStorageBuffer(bytes, "chain-b", true, true);
@@ -7562,7 +7562,7 @@ function fuseUnaryChain(ops, xs) {
7562
7562
  return jsChain(ops, xs);
7563
7563
  }
7564
7564
  function jsChain(ops, xs) {
7565
- const out = Float64Array.from(xs);
7565
+ const out = new Float64Array(xs);
7566
7566
  for (const op of ops) {
7567
7567
  const f = SCALAR[op];
7568
7568
  for (let i = 0; i < out.length; i++) out[i] = f(out[i]);
@@ -7570,10 +7570,10 @@ function jsChain(ops, xs) {
7570
7570
  return out;
7571
7571
  }
7572
7572
  async function fuseUnaryChainAsync(ops, xs, options) {
7573
+ const gpu = await elementwiseChainGpuDispatch(ops, xs, options);
7574
+ if (gpu) return new Float64Array(gpu);
7573
7575
  const wasm = elementwiseChainDispatch(ops, xs);
7574
7576
  if (wasm) return wasm;
7575
- const gpu = await elementwiseChainGpuDispatch(ops, xs, options);
7576
- if (gpu) return Float64Array.from(gpu);
7577
7577
  return jsChain(ops, xs);
7578
7578
  }
7579
7579
 
@@ -26,31 +26,49 @@ export declare function fuseUnaryChain(ops: WasmElementwiseOp[], xs: Float64Arra
26
26
  * `fuseUnaryChain`, because a GPU dispatch is inherently asynchronous and
27
27
  * `fuseUnaryChain`'s synchronous signature is public API.
28
28
  *
29
- * Tiers, in order: **WASM (f64) → GPU (f32) → JS (f64)**.
29
+ * Tiers, in order: **GPU (f32, opt-in) → WASM (f64) → JS (f64)**.
30
30
  *
31
- * ### Why WASM is tried BEFORE the GPU
31
+ * ### Why the GPU is tried first but ONLY when explicitly enabled
32
32
  *
33
- * Measured in Chrome on an NVIDIA Pascal adapter, chain `sin→exp→tanh→cos`:
33
+ * Chain `sin→exp→tanh→cos`, 5 reps. ONE run, 2026-07-13, Chrome on an NVIDIA
34
+ * Pascal adapter — the same run quoted in the CHANGELOG and the reference docs, so
35
+ * the three tables agree. Pinned by `gpu-vs-wasm.browser.test.ts`:
34
36
  *
35
- * | n | JS | WASM | GPU | GPU vs WASM |
36
- * | --------- | ------- | ------- | ------- | ----------- |
37
- * | 65,536 | 90 ms | 16 ms | 28 ms | **0.57×** |
38
- * | 262,144 | 400 ms | 60 ms | 103 ms | **0.59×** |
39
- * | 1,048,576 | 1613 ms | 250 ms | 457 ms | **0.55×** |
37
+ * | n | JS | WASM | GPU | GPU vs WASM |
38
+ * | --------- | ------ | ------ | ---------- | ----------- |
39
+ * | 65,536 | 44 ms | 17 ms | **5.2 ms** | **3.2×** |
40
+ * | 262,144 | 185 ms | 63 ms | **7.5 ms** | **8.3×** |
41
+ * | 1,048,576 | 711 ms | 256 ms | **35 ms** | **7.2×** |
40
42
  *
41
- * **WASM is ~1.8× faster than the GPU and it is f64-exact while the GPU is
42
- * f32.** For element-wise chains the GPU is therefore both slower *and* less
43
- * precise, so it must never pre-empt WASM. (An earlier revision had GPU first;
44
- * that only looked like a win because a separate bug meant WASM never loaded in
45
- * browsers, making the baseline pure JS.)
43
+ * The GPU is the fastest tier by a wide margin. It is nevertheless **last-resort
44
+ * by default**, because it computes in f32 while every other tier is f64-exact.
45
+ * `enableGpu()` is how a caller consents to that trade: precision for speed. With
46
+ * the flag off the default this function is exactly WASM JS and returns
47
+ * bit-identical f64 results, so opting out costs nothing.
46
48
  *
47
- * The GPU still earns its place where WASM is **unavailable** — there it beats
48
- * the JS scalar pass ~2–2.5×. It engages only when the caller opted in via
49
- * `enableGpu()`, WASM declined, a device exists, the array clears
50
- * `GPU_MIN_ELEMENTS`, and every op has a GPU kernel.
49
+ * ### Provenance of these numbers (read before changing the order)
51
50
  *
52
- * (The GPU *does* win decisively for compute-bound work like a large matmul —
53
- * see `gpuMatmul`. This ordering is specific to memory-bound element-wise work.)
51
+ * This ordering has been wrong twice, both times from a benchmark measuring
52
+ * something other than what it claimed:
53
+ *
54
+ * 1. GPU-first was first adopted on a "2.3-2.9x faster than JS" result. That
55
+ * baseline was pure JS only because a *separate* bug meant WASM never loaded
56
+ * in browsers. Fixing WASM revealed it beat the GPU, so the order was flipped
57
+ * to WASM-first.
58
+ * 2. That flip was also wrong. The GPU figure it rested on was inflated by
59
+ * `Float32Array.from(f64array)` in the dispatch — the generic `Array.from`
60
+ * path, which runs ToNumber per element. Naming the denominators, because they
61
+ * differ: the *conversion* alone was 73x slower (433 ms vs 5.9 ms at n=2^20),
62
+ * which made the *end-to-end dispatch* 12.2x slower (439.80 ms -> 36.06 ms).
63
+ * With that fixed, the GPU wins outright, as above.
64
+ *
65
+ * The lesson both times: a tier's number is only as good as the tier it is
66
+ * compared against. Re-measure ALL THREE tiers in one run before touching this
67
+ * order — `gpu-vs-wasm.browser.test.ts` does exactly that and fails loudly if the
68
+ * ranking changes.
69
+ *
70
+ * (The GPU also wins decisively for compute-bound work like a large matmul — see
71
+ * `gpuMatmul`. It is not merely a memory-bound-work story.)
54
72
  *
55
73
  * **Precision.** Always returns a `Float64Array`. When the GPU tier runs, the
56
74
  * *values* carry f32 precision (~7 significant digits) even though the container
@@ -1 +1 @@
1
- {"version":3,"file":"fused.d.ts","sourceRoot":"","sources":["../../src/typed/fused.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAA+B,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAyB9F;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,iBAAiB,EAAE,EAAE,EAAE,EAAE,YAAY,GAAG,YAAY,CAIvF;AAYD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,iBAAiB,EAAE,EACxB,EAAE,EAAE,YAAY,EAChB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,YAAY,CAAC,CAUvB"}
1
+ {"version":3,"file":"fused.d.ts","sourceRoot":"","sources":["../../src/typed/fused.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAEL,KAAK,iBAAiB,EACvB,MAAM,oCAAoC,CAAC;AAC5C,OAAO,EAA+B,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAyB9F;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,iBAAiB,EAAE,EAAE,EAAE,EAAE,YAAY,GAAG,YAAY,CAIvF;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8DG;AACH,wBAAsB,mBAAmB,CACvC,GAAG,EAAE,iBAAiB,EAAE,EACxB,EAAE,EAAE,YAAY,EAChB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,YAAY,CAAC,CAgBvB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danielsimonjr/mathts-functions",
3
- "version": "0.18.0",
3
+ "version": "0.19.0",
4
4
  "description": "Mathematical functions for MathTS - arithmetic, algebra, trigonometry, statistics, and more",
5
5
  "author": "Daniel Simon Jr.",
6
6
  "license": "MIT",
@@ -37,7 +37,7 @@
37
37
  "@danielsimonjr/mathts-core": "^0.6.0",
38
38
  "@danielsimonjr/mathts-expression": "^0.6.0",
39
39
  "@danielsimonjr/mathts-gpu": "^0.1.1",
40
- "@danielsimonjr/mathts-matrix": "^0.3.2",
40
+ "@danielsimonjr/mathts-matrix": "^0.4.0",
41
41
  "@danielsimonjr/mathts-parallel": "^0.3.4",
42
42
  "bignumber.js": "^9.1.2",
43
43
  "complex.js": "^2.2.5",