@danielsimonjr/mathts-functions 0.17.2 → 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.
- package/dist/gpu/elementwise-gpu.d.ts +41 -28
- package/dist/gpu/elementwise-gpu.d.ts.map +1 -1
- package/dist/index.js +111 -76
- package/dist/typed/fused.d.ts +50 -23
- package/dist/typed/fused.d.ts.map +1 -1
- package/dist/typed/index.d.ts +1 -1
- package/dist/typed/index.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -2,52 +2,65 @@
|
|
|
2
2
|
* WebGPU fused element-wise chain.
|
|
3
3
|
*
|
|
4
4
|
* Why a *chain* and not a single op: a lone element-wise op on the GPU is pure
|
|
5
|
-
* transfer tax — upload n floats, do one flop each, read n floats back.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* is amortized across the whole chain.
|
|
5
|
+
* transfer tax — upload n floats, do one flop each, read n floats back. A
|
|
6
|
+
* **fused chain** uploads once, runs every op on-device by ping-ponging two
|
|
7
|
+
* storage buffers, and reads back once, so the transfer is amortized across the
|
|
8
|
+
* whole chain.
|
|
10
9
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
16
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
* WGSL expressions for each supported op, as a function of `x`.
|
|
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.
|
|
23
22
|
*
|
|
24
|
-
*
|
|
25
|
-
* `
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* Contract (mirrors the WASM `elementwiseChainDispatch`): a **never-throw**
|
|
24
|
+
* best-effort fast path. It returns `null` — never rejects — whenever the GPU is
|
|
25
|
+
* unavailable, not opted in, the input is too small, or the chain contains an op
|
|
26
|
+
* with no GPU kernel. The caller then falls through to the CPU tiers.
|
|
28
27
|
*/
|
|
28
|
+
import { type GPUContextOptions } from '@danielsimonjr/mathts-gpu';
|
|
29
29
|
declare const WGSL_OP_BODY: {
|
|
30
30
|
readonly abs: "return abs(x);";
|
|
31
31
|
readonly sin: "return sin(x);";
|
|
32
32
|
readonly cos: "return cos(x);";
|
|
33
33
|
readonly tan: "return tan(x);";
|
|
34
34
|
readonly exp: "return exp(x);";
|
|
35
|
-
readonly log: "return log(x);";
|
|
36
35
|
readonly atan: "return atan(x);";
|
|
37
36
|
readonly sinh: "return sinh(x);";
|
|
38
37
|
readonly tanh: "return tanh(x);";
|
|
39
|
-
readonly
|
|
40
|
-
readonly log2: "return
|
|
41
|
-
readonly log10: "return
|
|
42
|
-
readonly
|
|
43
|
-
readonly
|
|
44
|
-
readonly
|
|
38
|
+
readonly log: "return safe_log(x);";
|
|
39
|
+
readonly log2: "return safe_log(x) * 1.4426950408889634;";
|
|
40
|
+
readonly log10: "return safe_log(x) * 0.4342944819032518;";
|
|
41
|
+
readonly atanh: "return safe_atanh(x);";
|
|
42
|
+
readonly sec: "return safe_recip(cos(x));";
|
|
43
|
+
readonly csc: "return safe_recip(sin(x));";
|
|
44
|
+
readonly cot: "return safe_recip(tan(x));";
|
|
45
45
|
};
|
|
46
46
|
/** Ops that have a GPU kernel. A chain outside this set falls back. */
|
|
47
47
|
export type GpuElementwiseOp = keyof typeof WGSL_OP_BODY;
|
|
48
48
|
export declare const GPU_ELEMENTWISE_OPS: GpuElementwiseOp[];
|
|
49
49
|
/** Whether every op in the chain has a GPU kernel. */
|
|
50
50
|
export declare function isGpuChainSupported(ops: readonly string[]): ops is readonly GpuElementwiseOp[];
|
|
51
|
+
/** Drop the cached shaders/buffers (device loss, or between tests). */
|
|
52
|
+
export declare function resetGpuElementwise(): void;
|
|
53
|
+
/** Options for a GPU element-wise dispatch. */
|
|
54
|
+
export interface GpuChainOptions extends GPUContextOptions {
|
|
55
|
+
/**
|
|
56
|
+
* Per-call override of the global `enableGpu()` flag.
|
|
57
|
+
*
|
|
58
|
+
* The global flag is process-wide mutable state: any dependency that calls
|
|
59
|
+
* `enableGpu()` would otherwise change *your* call's behaviour. Passing `gpu`
|
|
60
|
+
* explicitly makes a call self-describing and immune to that.
|
|
61
|
+
*/
|
|
62
|
+
gpu?: boolean;
|
|
63
|
+
}
|
|
51
64
|
/**
|
|
52
65
|
* Run a fused element-wise chain on the GPU.
|
|
53
66
|
*
|
|
@@ -55,6 +68,6 @@ export declare function isGpuChainSupported(ops: readonly string[]): ops is read
|
|
|
55
68
|
* @param xs - input samples
|
|
56
69
|
* @returns the f32 results, or `null` to signal "fall back to another tier"
|
|
57
70
|
*/
|
|
58
|
-
export declare function elementwiseChainGpuDispatch(ops: readonly string[], xs: Float64Array | Float32Array, options?:
|
|
71
|
+
export declare function elementwiseChainGpuDispatch(ops: readonly string[], xs: Float64Array | Float32Array, options?: GpuChainOptions): Promise<Float32Array | null>;
|
|
59
72
|
export {};
|
|
60
73
|
//# sourceMappingURL=elementwise-gpu.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"elementwise-gpu.d.ts","sourceRoot":"","sources":["../../src/gpu/elementwise-gpu.ts"],"names":[],"mappings":"AAAA
|
|
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
|
@@ -396,6 +396,7 @@ __export(typed_exports, {
|
|
|
396
396
|
reduce: () => reduce,
|
|
397
397
|
reflectVector: () => reflectVector,
|
|
398
398
|
resample: () => resample,
|
|
399
|
+
resetGpuElementwise: () => resetGpuElementwise,
|
|
399
400
|
residue: () => residue,
|
|
400
401
|
resultant: () => resultant,
|
|
401
402
|
rightArithShift: () => rightArithShift,
|
|
@@ -7341,41 +7342,76 @@ var typedSpecial = {
|
|
|
7341
7342
|
// src/gpu/elementwise-gpu.ts
|
|
7342
7343
|
import {
|
|
7343
7344
|
getGpuDevice,
|
|
7345
|
+
getGlobalGPUContext,
|
|
7344
7346
|
isGpuEnabled,
|
|
7345
|
-
GPU_MIN_ELEMENTS
|
|
7347
|
+
GPU_MIN_ELEMENTS,
|
|
7348
|
+
ShaderManager,
|
|
7349
|
+
BufferPool
|
|
7346
7350
|
} from "@danielsimonjr/mathts-gpu";
|
|
7351
|
+
var WGSL_IEEE = `
|
|
7352
|
+
// NaN / \xB1Inf are supplied through the UNIFORM, not written as literals.
|
|
7353
|
+
//
|
|
7354
|
+
// WGSL const-folds \`bitcast<f32>(0x7fc00000u)\` even inside a function body and
|
|
7355
|
+
// then rejects the result: "value nan cannot be represented as 'f32'" \u2014 a const
|
|
7356
|
+
// expression may not BE NaN or Inf. Reading the bit pattern from \`params\` (a
|
|
7357
|
+
// runtime uniform) is not const-foldable, so the bitcast survives to runtime.
|
|
7358
|
+
// params = (n, nanBits, posInfBits, negInfBits).
|
|
7359
|
+
fn nan_f32() -> f32 { return bitcast<f32>(params.y); }
|
|
7360
|
+
fn pos_inf() -> f32 { return bitcast<f32>(params.z); }
|
|
7361
|
+
fn neg_inf() -> f32 { return bitcast<f32>(params.w); }
|
|
7362
|
+
|
|
7363
|
+
fn safe_log(x: f32) -> f32 {
|
|
7364
|
+
if (x < 0.0) { return nan_f32(); }
|
|
7365
|
+
if (x == 0.0) { return neg_inf(); }
|
|
7366
|
+
return log(x);
|
|
7367
|
+
}
|
|
7368
|
+
fn safe_atanh(x: f32) -> f32 {
|
|
7369
|
+
if (x > 1.0 || x < -1.0) { return nan_f32(); }
|
|
7370
|
+
if (x == 1.0) { return pos_inf(); }
|
|
7371
|
+
if (x == -1.0) { return neg_inf(); }
|
|
7372
|
+
return atanh(x);
|
|
7373
|
+
}
|
|
7374
|
+
// 1/d with IEEE division-by-zero semantics (JS: 1/0 = +Inf, 1/-0 = -Inf).
|
|
7375
|
+
fn safe_recip(d: f32) -> f32 {
|
|
7376
|
+
if (d == 0.0) {
|
|
7377
|
+
// Distinguish +0 from -0 by its sign bit, as IEEE division does.
|
|
7378
|
+
if ((bitcast<u32>(d) & 0x80000000u) != 0u) { return neg_inf(); }
|
|
7379
|
+
return pos_inf();
|
|
7380
|
+
}
|
|
7381
|
+
return 1.0 / d;
|
|
7382
|
+
}
|
|
7383
|
+
`;
|
|
7347
7384
|
var WGSL_OP_BODY = {
|
|
7348
7385
|
abs: "return abs(x);",
|
|
7349
7386
|
sin: "return sin(x);",
|
|
7350
7387
|
cos: "return cos(x);",
|
|
7351
7388
|
tan: "return tan(x);",
|
|
7352
7389
|
exp: "return exp(x);",
|
|
7353
|
-
log: "return log(x);",
|
|
7354
7390
|
atan: "return atan(x);",
|
|
7355
7391
|
sinh: "return sinh(x);",
|
|
7356
7392
|
tanh: "return tanh(x);",
|
|
7357
|
-
|
|
7358
|
-
|
|
7359
|
-
|
|
7360
|
-
|
|
7361
|
-
|
|
7362
|
-
//
|
|
7363
|
-
|
|
7364
|
-
|
|
7365
|
-
|
|
7366
|
-
|
|
7393
|
+
// Domain-guarded: WGSL says indeterminate outside the domain; JS does not.
|
|
7394
|
+
log: "return safe_log(x);",
|
|
7395
|
+
log2: "return safe_log(x) * 1.4426950408889634;",
|
|
7396
|
+
// ln(x) / ln(2)
|
|
7397
|
+
log10: "return safe_log(x) * 0.4342944819032518;",
|
|
7398
|
+
// ln(x) / ln(10)
|
|
7399
|
+
atanh: "return safe_atanh(x);",
|
|
7400
|
+
sec: "return safe_recip(cos(x));",
|
|
7401
|
+
csc: "return safe_recip(sin(x));",
|
|
7402
|
+
cot: "return safe_recip(tan(x));"
|
|
7403
|
+
// NOTE — `expm1`, `log1p` and `erfc` are DELIBERATELY ABSENT.
|
|
7367
7404
|
//
|
|
7368
|
-
//
|
|
7369
|
-
//
|
|
7370
|
-
//
|
|
7371
|
-
//
|
|
7372
|
-
//
|
|
7405
|
+
// WGSL has no builtin for any of them. For expm1/log1p the naive identities
|
|
7406
|
+
// (`exp(x)-1`, `log(1+x)`) are catastrophically wrong near zero in f32
|
|
7407
|
+
// (`1.0 + 1e-8` rounds to exactly 1.0f, so `log(1+x)` returns 0 where the true
|
|
7408
|
+
// value is 1e-8 — 100% relative error), and the standard Kahan-compensated
|
|
7409
|
+
// forms were implemented and MEASURED on real hardware: still 38% (expm1) and
|
|
7410
|
+
// 62% (log1p) max relative error, because the compensation needs an accurate
|
|
7411
|
+
// `log()` near 1.0 and the GPU's fast-math `log()` is not.
|
|
7373
7412
|
//
|
|
7374
|
-
// An f32 fast path
|
|
7375
|
-
//
|
|
7376
|
-
sec: "return 1.0 / cos(x);",
|
|
7377
|
-
csc: "return 1.0 / sin(x);",
|
|
7378
|
-
cot: "return 1.0 / tan(x);"
|
|
7413
|
+
// An f32 fast path may be less precise. It may not be WRONG. Chains containing
|
|
7414
|
+
// these fall back to the exact WASM/JS tiers.
|
|
7379
7415
|
};
|
|
7380
7416
|
var GPU_ELEMENTWISE_OPS = Object.keys(WGSL_OP_BODY);
|
|
7381
7417
|
function isGpuChainSupported(ops) {
|
|
@@ -7386,6 +7422,9 @@ function wgslFor(op) {
|
|
|
7386
7422
|
return `
|
|
7387
7423
|
@group(0) @binding(0) var<storage, read> inp: array<f32>;
|
|
7388
7424
|
@group(0) @binding(1) var<storage, read_write> outp: array<f32>;
|
|
7425
|
+
@group(0) @binding(2) var<uniform> params: vec4<u32>; // n, nanBits, +infBits, -infBits
|
|
7426
|
+
|
|
7427
|
+
${WGSL_IEEE}
|
|
7389
7428
|
|
|
7390
7429
|
fn apply(x: f32) -> f32 {
|
|
7391
7430
|
${WGSL_OP_BODY[op]}
|
|
@@ -7394,80 +7433,72 @@ function wgslFor(op) {
|
|
|
7394
7433
|
@compute @workgroup_size(${WORKGROUP_SIZE})
|
|
7395
7434
|
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
|
|
7396
7435
|
let i = gid.x;
|
|
7397
|
-
if (i >=
|
|
7436
|
+
if (i >= params.x) { return; }
|
|
7398
7437
|
outp[i] = apply(inp[i]);
|
|
7399
7438
|
}
|
|
7400
7439
|
`;
|
|
7401
7440
|
}
|
|
7402
|
-
var
|
|
7403
|
-
function
|
|
7404
|
-
|
|
7405
|
-
if (!
|
|
7406
|
-
|
|
7407
|
-
|
|
7408
|
-
|
|
7409
|
-
const
|
|
7410
|
-
|
|
7411
|
-
|
|
7412
|
-
|
|
7413
|
-
|
|
7414
|
-
|
|
7415
|
-
|
|
7416
|
-
|
|
7417
|
-
|
|
7418
|
-
|
|
7419
|
-
byOp.set(op, pipeline);
|
|
7420
|
-
return pipeline;
|
|
7441
|
+
var resources = null;
|
|
7442
|
+
async function getResources(options) {
|
|
7443
|
+
const device = await getGpuDevice(options);
|
|
7444
|
+
if (!device) return null;
|
|
7445
|
+
if (resources && resources.device === device) return resources;
|
|
7446
|
+
const ctx = getGlobalGPUContext();
|
|
7447
|
+
const shaders = new ShaderManager(ctx);
|
|
7448
|
+
for (const op of GPU_ELEMENTWISE_OPS) {
|
|
7449
|
+
shaders.registerShader(op, wgslFor(op));
|
|
7450
|
+
}
|
|
7451
|
+
shaders.precompileRegistered();
|
|
7452
|
+
resources = { device, shaders, pool: new BufferPool(ctx) };
|
|
7453
|
+
return resources;
|
|
7454
|
+
}
|
|
7455
|
+
function resetGpuElementwise() {
|
|
7456
|
+
resources?.pool.destroy();
|
|
7457
|
+
resources = null;
|
|
7421
7458
|
}
|
|
7422
7459
|
async function elementwiseChainGpuDispatch(ops, xs, options) {
|
|
7423
7460
|
const n = xs.length;
|
|
7424
|
-
|
|
7461
|
+
const enabled = options?.gpu ?? isGpuEnabled();
|
|
7462
|
+
if (!enabled) return null;
|
|
7425
7463
|
if (ops.length === 0) return null;
|
|
7426
7464
|
if (n < GPU_MIN_ELEMENTS) return null;
|
|
7427
7465
|
if (!isGpuChainSupported(ops)) return null;
|
|
7428
7466
|
const bytes = n * 4;
|
|
7429
7467
|
const workgroups = Math.ceil(n / WORKGROUP_SIZE);
|
|
7468
|
+
let res;
|
|
7430
7469
|
let bufA;
|
|
7431
7470
|
let bufB;
|
|
7432
7471
|
let staging;
|
|
7472
|
+
let params;
|
|
7433
7473
|
let scopePopped = false;
|
|
7434
|
-
let device;
|
|
7435
7474
|
try {
|
|
7436
|
-
const
|
|
7437
|
-
if (!
|
|
7438
|
-
|
|
7475
|
+
const r = await getResources(options);
|
|
7476
|
+
if (!r) return null;
|
|
7477
|
+
res = r;
|
|
7478
|
+
const { device, shaders, pool } = r;
|
|
7439
7479
|
const limits = device.limits;
|
|
7440
7480
|
if (workgroups > limits.maxComputeWorkgroupsPerDimension) return null;
|
|
7441
7481
|
if (bytes > limits.maxStorageBufferBindingSize) return null;
|
|
7442
7482
|
if (bytes > limits.maxBufferSize) return null;
|
|
7443
|
-
const input = xs instanceof Float32Array ? xs : Float32Array
|
|
7483
|
+
const input = xs instanceof Float32Array ? xs : new Float32Array(xs);
|
|
7444
7484
|
device.pushErrorScope("validation");
|
|
7445
|
-
bufA =
|
|
7446
|
-
|
|
7447
|
-
|
|
7448
|
-
|
|
7449
|
-
});
|
|
7450
|
-
bufB = device.createBuffer({
|
|
7451
|
-
size: bytes,
|
|
7452
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
|
|
7453
|
-
label: "chain-b"
|
|
7454
|
-
});
|
|
7455
|
-
staging = device.createBuffer({
|
|
7456
|
-
size: bytes,
|
|
7457
|
-
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
|
|
7458
|
-
label: "chain-staging"
|
|
7459
|
-
});
|
|
7485
|
+
bufA = pool.acquireStorageBuffer(bytes, "chain-a", true, true);
|
|
7486
|
+
bufB = pool.acquireStorageBuffer(bytes, "chain-b", true, true);
|
|
7487
|
+
staging = pool.acquireStagingBuffer(bytes, "chain-staging");
|
|
7488
|
+
params = pool.acquireUniformBuffer(16, "chain-params");
|
|
7460
7489
|
device.queue.writeBuffer(bufA, 0, input);
|
|
7490
|
+
device.queue.writeBuffer(params, 0, new Uint32Array([n, 2143289344, 2139095040, 4286578688]));
|
|
7461
7491
|
const encoder = device.createCommandEncoder({ label: "elementwise-chain" });
|
|
7462
7492
|
let src = bufA;
|
|
7463
7493
|
let dst2 = bufB;
|
|
7464
7494
|
for (const op of ops) {
|
|
7465
|
-
const pipeline =
|
|
7495
|
+
const pipeline = shaders.getRegisteredPipeline(op);
|
|
7466
7496
|
const bindGroup = device.createBindGroup({
|
|
7467
7497
|
layout: pipeline.getBindGroupLayout(0),
|
|
7468
7498
|
entries: [
|
|
7469
7499
|
{ binding: 0, resource: { buffer: src } },
|
|
7470
|
-
{ binding: 1, resource: { buffer: dst2 } }
|
|
7500
|
+
{ binding: 1, resource: { buffer: dst2 } },
|
|
7501
|
+
{ binding: 2, resource: { buffer: params } }
|
|
7471
7502
|
]
|
|
7472
7503
|
});
|
|
7473
7504
|
const pass = encoder.beginComputePass({ label: `chain:${op}` });
|
|
@@ -7482,22 +7513,25 @@ async function elementwiseChainGpuDispatch(ops, xs, options) {
|
|
|
7482
7513
|
const validationError = await device.popErrorScope();
|
|
7483
7514
|
scopePopped = true;
|
|
7484
7515
|
if (validationError) return null;
|
|
7485
|
-
await staging.mapAsync(GPUMapMode.READ);
|
|
7486
|
-
const out = new Float32Array(staging.getMappedRange().slice(0));
|
|
7516
|
+
await staging.mapAsync(GPUMapMode.READ, 0, bytes);
|
|
7517
|
+
const out = new Float32Array(staging.getMappedRange(0, bytes).slice(0));
|
|
7487
7518
|
staging.unmap();
|
|
7488
7519
|
return out;
|
|
7489
7520
|
} catch {
|
|
7490
7521
|
return null;
|
|
7491
7522
|
} finally {
|
|
7492
|
-
if (
|
|
7523
|
+
if (res && !scopePopped) {
|
|
7493
7524
|
try {
|
|
7494
|
-
await device.popErrorScope();
|
|
7525
|
+
await res.device.popErrorScope();
|
|
7495
7526
|
} catch {
|
|
7496
7527
|
}
|
|
7497
7528
|
}
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7529
|
+
if (res) {
|
|
7530
|
+
if (bufA) res.pool.release(bufA);
|
|
7531
|
+
if (bufB) res.pool.release(bufB);
|
|
7532
|
+
if (staging) res.pool.release(staging);
|
|
7533
|
+
if (params) res.pool.release(params);
|
|
7534
|
+
}
|
|
7501
7535
|
}
|
|
7502
7536
|
}
|
|
7503
7537
|
|
|
@@ -7528,18 +7562,18 @@ function fuseUnaryChain(ops, xs) {
|
|
|
7528
7562
|
return jsChain(ops, xs);
|
|
7529
7563
|
}
|
|
7530
7564
|
function jsChain(ops, xs) {
|
|
7531
|
-
const out = Float64Array
|
|
7565
|
+
const out = new Float64Array(xs);
|
|
7532
7566
|
for (const op of ops) {
|
|
7533
7567
|
const f = SCALAR[op];
|
|
7534
7568
|
for (let i = 0; i < out.length; i++) out[i] = f(out[i]);
|
|
7535
7569
|
}
|
|
7536
7570
|
return out;
|
|
7537
7571
|
}
|
|
7538
|
-
async function fuseUnaryChainAsync(ops, xs) {
|
|
7572
|
+
async function fuseUnaryChainAsync(ops, xs, options) {
|
|
7573
|
+
const gpu = await elementwiseChainGpuDispatch(ops, xs, options);
|
|
7574
|
+
if (gpu) return new Float64Array(gpu);
|
|
7539
7575
|
const wasm = elementwiseChainDispatch(ops, xs);
|
|
7540
7576
|
if (wasm) return wasm;
|
|
7541
|
-
const gpu = await elementwiseChainGpuDispatch(ops, xs);
|
|
7542
|
-
if (gpu) return gpu;
|
|
7543
7577
|
return jsChain(ops, xs);
|
|
7544
7578
|
}
|
|
7545
7579
|
|
|
@@ -45593,6 +45627,7 @@ export {
|
|
|
45593
45627
|
reflectVector,
|
|
45594
45628
|
replacer,
|
|
45595
45629
|
resample,
|
|
45630
|
+
resetGpuElementwise,
|
|
45596
45631
|
reshape2 as reshape,
|
|
45597
45632
|
residue,
|
|
45598
45633
|
resize2 as resize,
|
package/dist/typed/fused.d.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* array is below threshold, so the result is always correct.
|
|
14
14
|
*/
|
|
15
15
|
import { type WasmElementwiseOp } from '../wasm/elementwise/wasm-bridge.js';
|
|
16
|
+
import { type GpuChainOptions } from '../gpu/elementwise-gpu.js';
|
|
16
17
|
/**
|
|
17
18
|
* Apply `ops` left-to-right over `xs` (i.e. `ops[last](…ops[0](x))`), fused in
|
|
18
19
|
* WASM when possible. Returns a new `Float64Array`; never mutates `xs`.
|
|
@@ -25,35 +26,61 @@ export declare function fuseUnaryChain(ops: WasmElementwiseOp[], xs: Float64Arra
|
|
|
25
26
|
* `fuseUnaryChain`, because a GPU dispatch is inherently asynchronous and
|
|
26
27
|
* `fuseUnaryChain`'s synchronous signature is public API.
|
|
27
28
|
*
|
|
28
|
-
* Tiers, in order: **
|
|
29
|
+
* Tiers, in order: **GPU (f32, opt-in) → WASM (f64) → JS (f64)**.
|
|
29
30
|
*
|
|
30
|
-
* ### Why
|
|
31
|
+
* ### Why the GPU is tried first — but ONLY when explicitly enabled
|
|
31
32
|
*
|
|
32
|
-
*
|
|
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`:
|
|
33
36
|
*
|
|
34
|
-
* | n | JS
|
|
35
|
-
* | --------- |
|
|
36
|
-
* | 65,536 |
|
|
37
|
-
* | 262,144 |
|
|
38
|
-
* | 1,048,576 |
|
|
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×** |
|
|
39
42
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
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.
|
|
45
48
|
*
|
|
46
|
-
*
|
|
47
|
-
* the JS scalar pass ~2–2.5×. It engages only when the caller opted in via
|
|
48
|
-
* `enableGpu()`, WASM declined, a device exists, the array clears
|
|
49
|
-
* `GPU_MIN_ELEMENTS`, and every op has a GPU kernel.
|
|
49
|
+
* ### Provenance of these numbers (read before changing the order)
|
|
50
50
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
51
|
+
* This ordering has been wrong twice, both times from a benchmark measuring
|
|
52
|
+
* something other than what it claimed:
|
|
53
53
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
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.)
|
|
72
|
+
*
|
|
73
|
+
* **Precision.** Always returns a `Float64Array`. When the GPU tier runs, the
|
|
74
|
+
* *values* carry f32 precision (~7 significant digits) even though the container
|
|
75
|
+
* is f64 — the GPU cannot compute in f64 at all.
|
|
76
|
+
*
|
|
77
|
+
* It previously returned `Float64Array | Float32Array` to encode which path ran.
|
|
78
|
+
* That union was a footgun: `.map` / `.filter` / `.set` on it are TS2349 errors,
|
|
79
|
+
* so **every** caller had to narrow with `instanceof` before touching the result,
|
|
80
|
+
* and `new Float64Array(r.buffer)` silently produced garbage when the f32 branch
|
|
81
|
+
* hit. A narrowing tax on 100% of callers, for a branch most never take, is a bad
|
|
82
|
+
* trade. Callers who specifically want the raw f32 buffer can call
|
|
83
|
+
* `elementwiseChainGpuDispatch` directly — it is exported for exactly that.
|
|
57
84
|
*/
|
|
58
|
-
export declare function fuseUnaryChainAsync(ops: WasmElementwiseOp[], xs: Float64Array): Promise<Float64Array
|
|
85
|
+
export declare function fuseUnaryChainAsync(ops: WasmElementwiseOp[], xs: Float64Array, options?: GpuChainOptions): Promise<Float64Array>;
|
|
59
86
|
//# sourceMappingURL=fused.d.ts.map
|
|
@@ -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;
|
|
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/dist/typed/index.d.ts
CHANGED
|
@@ -47,7 +47,7 @@ export * from './matrix-ops.js';
|
|
|
47
47
|
export { cond } from './matrix-ops.js';
|
|
48
48
|
export * from './gpu.js';
|
|
49
49
|
export { enableGpu, disableGpu, isGpuEnabled, GPU_MIN_ELEMENTS } from '@danielsimonjr/mathts-gpu';
|
|
50
|
-
export { elementwiseChainGpuDispatch, isGpuChainSupported, GPU_ELEMENTWISE_OPS, type GpuElementwiseOp, } from '../gpu/elementwise-gpu.js';
|
|
50
|
+
export { elementwiseChainGpuDispatch, isGpuChainSupported, resetGpuElementwise, GPU_ELEMENTWISE_OPS, type GpuElementwiseOp, type GpuChainOptions, } from '../gpu/elementwise-gpu.js';
|
|
51
51
|
export * from './relational.js';
|
|
52
52
|
export { typedRelational } from './relational.js';
|
|
53
53
|
export * from './string.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/typed/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAOH,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGlD,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGlD,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAK5C,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGpC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGxD,cAAc,eAAe,CAAC;AAG9B,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,cAAc,kBAAkB,CAAC;AAGjC,cAAc,oBAAoB,CAAC;AAGnC,cAAc,cAAc,CAAC;AAG7B,cAAc,oBAAoB,CAAC;AAGnC,cAAc,YAAY,CAAC;AAG3B,cAAc,mBAAmB,CAAC;AAGlC,cAAc,iBAAiB,CAAC;AAWhC,cAAc,iBAAiB,CAAC;AAGhC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAGvC,cAAc,UAAU,CAAC;AAIzB,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClG,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,EACnB,mBAAmB,EACnB,KAAK,gBAAgB,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/typed/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAOH,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGlD,cAAc,mBAAmB,CAAC;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAGtD,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGlD,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAK5C,cAAc,UAAU,CAAC;AACzB,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AAGpC,cAAc,cAAc,CAAC;AAC7B,cAAc,YAAY,CAAC;AAC3B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,cAAc,oBAAoB,CAAC;AACnC,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAGxD,cAAc,eAAe,CAAC;AAG9B,cAAc,cAAc,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,cAAc,kBAAkB,CAAC;AAGjC,cAAc,oBAAoB,CAAC;AAGnC,cAAc,cAAc,CAAC;AAG7B,cAAc,oBAAoB,CAAC;AAGnC,cAAc,YAAY,CAAC;AAG3B,cAAc,mBAAmB,CAAC;AAGlC,cAAc,iBAAiB,CAAC;AAWhC,cAAc,iBAAiB,CAAC;AAGhC,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAGvC,cAAc,UAAU,CAAC;AAIzB,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC;AAClG,OAAO,EACL,2BAA2B,EAC3B,mBAAmB,EACnB,mBAAmB,EACnB,mBAAmB,EACnB,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,2BAA2B,CAAC;AAInC,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGlD,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAI1C,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGpD,cAAc,WAAW,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAsCtC;;;;;;GAMG;AACH,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8B1B,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danielsimonjr/mathts-functions",
|
|
3
|
-
"version": "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.
|
|
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",
|