docs/design/pipeline-compilation.md
Approved API shape. Every implementation decision below is settled; agents build to this, deviations require a named reason in the final report.
const sweep = gpu.createKernel(function (u, src) { ... }, { constants: { hi }, output: [1024, 1024] });
const solve = gpu.createPipeline(function (u, q) {
for (let s = 0; s < this.constants.sweeps; s++) {
u = sweep(u, q);
}
return u;
}, { constants: { sweeps: 512 } });
const result = await solve(u0, q); // one launch, fences inside, one readback
gpu.createPipeline(fn, settings?); settings.constants only in v1.pipeline: true; intermediate residency is the
pipeline's business. Kernels may be shared between pipelines and direct use.this.constants inside the orchestration fn are trace-time facts. Changing
them = pipeline.setConstants({...}), which invalidates the plan and
re-traces on next call (kernels already treat settings this way).Math.random() during trace → throw (orchestration must be deterministic).{ steps: [ { kernel, argBindings[], outputBuffer } ], buffers: [...], results }
{ source: 'pipelineArg', index } | { source: 'step', step } |
{ source: 'literal', value }.u = sweep(u, q) in a loop — must compile
to two alternating buffers with ONE kernel. Liveness is static (plan is a
DAG after unrolling).Generic executor (every backend, correctness reference): execute steps
sequentially through the existing kernel machinery with pipeline: true
forced on inner kernel INSTANCES cloned/configured for pipeline use (do not
mutate the user's kernel settings observably); final results read back once.
On GL this is textures end-to-end; on cpu plain arrays; on webgpu buffer
handles. This executor ships for cpu/webgl/webgl2/headlessgl/webgpu in v1.
webasm fused executor (the point of the feature): all steps compile over
ONE wasm memory laid out [pipeline args | plan buffers]; passes run
back-to-back with intermediates never leaving wasm memory (no slice /
flattenTo between steps). Sync path first. Threaded path: workers execute
the whole plan with Atomics-based barriers between steps over the shared
memory (generation counter; no main-thread round trip per pass); falls back
to sync-fused when threads are unavailable, and to the generic executor for
anything the webasm backend cannot take (its usual degradation contract,
with fallbackReason).
Pipeline call semantics: arguments sampled at call time; concurrent calls to the same pipeline serialize on a tail like threaded kernels do.
pipeline.destroy() releases plan buffers/instances; gpu.destroy() reaches
pipelines like kernels.
webgpu fused executor ('fused-encoder', added after v1's generic-only lowering): every plan step compiles against persistent STORAGE buffers on the kernel's device — ping-pong as static alternating bind groups, per-step params uniforms created at compile. Per call: pipeline arguments and per-call seeds/scalars via queue.writeBuffer, EVERY step recorded as a compute pass into ONE command encoder, result buffers copied to MAP_READ staging in the same encoder, one queue.submit, one mapAsync readback. Math.random keeps the direct-call seeding contract (seed uniform per call). Anything unfusable (GPU-resident handle arguments, vec intermediates, argument drift the layout cannot absorb) degrades to the generic executor with a named fallbackReason.
this.check / mid-plan readback (reserved; design in README as future).toString() deferred.src/pipeline.js — tracer, handle, plan IR, generic executor, Pipeline class.src/gpu.js — createPipeline wiring; pipeline registry for destroy.src/backend/web-assembly/pipeline-executor.js — fused sync + threaded
barrier lowering (worker-pool changes as needed).src/backend/web-gpu/pipeline-executor.js — the fused-encoder lowering.test/features/pipeline/*.js — see testing section.src/index.d.ts declarations.pipeline.executorKind).gauntlet jacobi/heat rewritten via createPipeline must beat their current per-pass webasm numbers materially (target: ≥1.5× on heat threaded) and match checksums; numbers reported in the PR.