Skip to content

patcher: fused voice block 17.0 -> 13.2 ns/voice-sample; regenerate SDK for engine.vocoder - #186

Merged
0thernet merged 3 commits into
mainfrom
patch-perf2
Oct 10, 2026
Merged

0thernet merged 3 commits into
mainfrom
patch-perf2

Conversation

@0thernet

Copy link
Copy Markdown
Member

Second pass on the patch runner (after #184), plus the SDK regen main needs.

Speed (bench/perf/patch-runner.ts, 16 voices x 12 nodes, 48 kHz, exclusive lease, 15 runs)

build median ns/voice-sample p95 fused/interp
before #184 (972404b) 27.4 28.3 1.0
main (9c7935e, #184) 16.9 17.7 0.61
this PR 13.2 13.6 0.47

Design target about 12.3; prototype interpreter 12.6, fused prototype 9.0.

What changed (src/audio/patch/fuse.ts, run.ts)

  • Intermediates one fused loop writes and reads stay in locals, not voice memory.
  • Fan-ins read straight from global memory; voice sums are added inside the fused loop on plain blocks (no steal fade or silence tracking). The runner's copy loops are skipped for those.
  • Nodes calling Math.sin/cos/tan/pow get their own loop so the call does not spill the shared loop's values.
  • Fused blocks are built per sample rate: the rate and constant controls become literals, and block constants computed from literals are hoisted out of the block function.
  • Block-constant voice ports (pitch, note, velocity) are read as values.

Bit-identical

Patch golden, PATCH_EXPECTED and every existing golden pass without re-pinning. The fuse differential test (random patches, fused vs interpreted bytes) now also runs one program at 48k/44.1k/48k/22.05k to cover the per-rate cache.

Gate (bench/perf/gate.test.ts)

SDK regen

#183 (generator) and #184 (engine.vocoder) landed together, so core/sdk/patch-nodes.gen.ts and the spliced v1.ts block were missing the vocoder and patch-nodes-gen fails on main. This regenerates them with bun scripts/gen-patch-sdk.ts. The 11 four-ways failures on main come from lane 4; #185 fixes them.

🤖 Generated with Claude Code

@vercel

vercel Bot commented Oct 10, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
dawg Ignored Ignored Preview Oct 10, 2026 10:49pm UTC

Request Review

0thernet and others added 3 commits October 10, 2026 18:32
- Hold intermediates a single fused loop writes and reads in locals
  instead of voice memory.
- Read fan-ins straight from the global memory and add the voice sums
  inside the fused loop when the block is plain (no steal fade, no
  silence tracking), dropping the runner's copy loops.
- Give nodes calling Math.sin/cos/tan/pow their own loop so the call
  does not spill every value of the shared loop.
- Build the fused block per sample rate: the rate and constant controls
  become literals, and block constants computed from literals move out
  of the block function.
- Read block-constant voice ports (pitch, note, velocity) as values.
- Perf gate: 24 -> 18 calibration units, fused/interpreter 0.8 -> 0.56.

Renders stay bit-identical (patch golden, PATCH_EXPECTED, fuse
differential test, now also across sample rates).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hoisting them out as closure constants made the fused block about 8x
slower on x64 JavaScriptCore (CI: 138 calibration units, 5.3x the
interpreter) and gained nothing on arm64. The gate now prints what it
measured.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@0thernet
0thernet merged commit ed590a4 into main Oct 10, 2026
14 checks passed
@0thernet
0thernet deleted the patch-perf2 branch October 10, 2026 23:06
@0thernet 0thernet mentioned this pull request Oct 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant