/** * Op-fusion public API (Tier 3 of the WASM pairing gap plan). * * Applies a *chain* of unary elementwise ops with the array resident in WASM * memory across the whole chain, so the JS↔wasm transfer is paid **once**, not * once per op. This is the structural lever the per-op benchmarks pointed to: * a single op barely beats (or loses to) JS once the copy is counted, but a * chain of K ops amortizes that one copy over K kernels. * * fuseUnaryChain(['sin', 'exp'], xs) // = exp(sin(x)), one copy in + one out * * Falls back to a sequential JS scalar pass when WASM is unavailable or the * array is below threshold, so the result is always correct. */ import { type WasmElementwiseOp } from '../wasm/elementwise/wasm-bridge.js'; import { type GpuChainOptions, type GpuReduceOp } from '../gpu/elementwise-gpu.js'; /** * Apply `ops` left-to-right over `xs` (i.e. `ops[last](…ops[0](x))`), fused in * WASM when possible. Returns a new `Float64Array`; never mutates `xs`. */ export declare function fuseUnaryChain(ops: WasmElementwiseOp[], xs: Float64Array): Float64Array; /** * Async sibling of {@link fuseUnaryChain} that may run the chain on the **GPU**. * * This exists as a separate, `async` entry point rather than changing * `fuseUnaryChain`, because a GPU dispatch is inherently asynchronous and * `fuseUnaryChain`'s synchronous signature is public API. * * Tiers, in order: **GPU (f32, opt-in) → WASM (f64) → JS (f64)**. * * ### Why the GPU is tried first — but ONLY when explicitly enabled * * Chain `sin→exp→tanh→cos`, 5 reps. ONE run, 2026-07-13, Chrome on an NVIDIA * Pascal adapter — the same run quoted in the CHANGELOG and the reference docs, so * the three tables agree. Pinned by `gpu-vs-wasm.browser.test.ts`: * * | n | JS | WASM | GPU | GPU vs WASM | * | --------- | ------ | ------ | ---------- | ----------- | * | 65,536 | 44 ms | 17 ms | **5.2 ms** | **3.2×** | * | 262,144 | 185 ms | 63 ms | **7.5 ms** | **8.3×** | * | 1,048,576 | 711 ms | 256 ms | **35 ms** | **7.2×** | * * The GPU is the fastest tier by a wide margin. It is nevertheless **last-resort * by default**, because it computes in f32 while every other tier is f64-exact. * `enableGpu()` is how a caller consents to that trade: precision for speed. With * the flag off — the default — this function is exactly WASM → JS and returns * bit-identical f64 results, so opting out costs nothing. * * ### Provenance of these numbers (read before changing the order) * * This ordering has been wrong twice, both times from a benchmark measuring * something other than what it claimed: * * 1. GPU-first was first adopted on a "2.3-2.9x faster than JS" result. That * baseline was pure JS only because a *separate* bug meant WASM never loaded * in browsers. Fixing WASM revealed it beat the GPU, so the order was flipped * to WASM-first. * 2. That flip was also wrong. The GPU figure it rested on was inflated by * `Float32Array.from(f64array)` in the dispatch — the generic `Array.from` * path, which runs ToNumber per element. Naming the denominators, because they * differ: the *conversion* alone was 73x slower (433 ms vs 5.9 ms at n=2^20), * which made the *end-to-end dispatch* 12.2x slower (439.80 ms -> 36.06 ms). * With that fixed, the GPU wins outright, as above. * * The lesson both times: a tier's number is only as good as the tier it is * compared against. Re-measure ALL THREE tiers in one run before touching this * order — `gpu-vs-wasm.browser.test.ts` does exactly that and fails loudly if the * ranking changes. * * (The GPU also wins decisively for compute-bound work like a large matmul — see * `gpuMatmul`. It is not merely a memory-bound-work story.) * * **Precision.** Always returns a `Float64Array`. When the GPU tier runs, the * *values* carry f32 precision (~7 significant digits) even though the container * is f64 — the GPU cannot compute in f64 at all. * * It previously returned `Float64Array | Float32Array` to encode which path ran. * That union was a footgun: `.map` / `.filter` / `.set` on it are TS2349 errors, * so **every** caller had to narrow with `instanceof` before touching the result, * and `new Float64Array(r.buffer)` silently produced garbage when the f32 branch * hit. A narrowing tax on 100% of callers, for a branch most never take, is a bad * trade. Callers who specifically want the raw f32 buffer can call * `elementwiseChainGpuDispatch` directly — it is exported for exactly that. */ export declare function fuseUnaryChainAsync(ops: WasmElementwiseOp[], xs: Float64Array, options?: GpuChainOptions): Promise; /** * Apply `ops` and then reduce to a single number — `sum(exp(sin(x)))` and friends. * * Tiers: **GPU (f32, opt-in) → WASM chain + JS reduce → JS chain + JS reduce.** * * When the GPU tier runs, the reduction happens **on the device**, so only n/256 * floats cross the bus instead of n. That is the whole reason this function exists. * Measured end-to-end for `sum(exp(sin(x)))` on an NVIDIA Pascal adapter: **1.35-1.7x** * faster than `fuseUnaryChainAsync(...)` followed by a JS loop, and **2.6-3.8x** faster * than the CPU tier. (See `elementwiseChainReduceGpuDispatch` for the full table and * for why the ratio shrinks as n grows.) * * Reach for it only when you want the **scalar**. If you also need the transformed * array, use `fuseUnaryChainAsync` — you have to pay the n-float readback anyway, and * summing it in JS afterwards costs almost nothing on top. * * Precision follows the tier that ran: f32 (~7 significant digits) on the GPU, exact * f64 on WASM/JS. `enableGpu()` is the consent; with the flag off this is a pure f64 * computation. */ export declare function fuseUnaryChainReduceAsync(ops: WasmElementwiseOp[], xs: Float64Array, reduce: GpuReduceOp, options?: GpuChainOptions): Promise; //# sourceMappingURL=fused.d.ts.map