Repository navigation
GPU bangs chain their rounds on the device, three command buffers in flight - #1447
Merged
Merged
Conversation
Collaborator
Author
|
@totaland , can you tell me if this is enough for your use case? |
nicolas-abril
force-pushed
the
gpu-device-loop
branch
2 times, most recently
from
October 14, 2026 16:29
e1f203e to
b14de72
Compare
…ier and sizes the next round's passes, so one GPU submission holds up to 64 rounds Each round of a bang used to go back to the host: cube_run read the frontier, picked the passes, committed one command buffer and waited. A stage that forks after its joins is a new round, so a bang of K dependent stages paid K round trips (about 0.42 ms each on an M4). Now the pack, the last pass of a round, takes the next frontier into H[H_CURSOR + 1] and writes each pass's group count after it (turn_set). Every pass reads that frontier and returns when it has no work, as the host's choice did. On Metal each pass is an indirect dispatch, so a pass with no work launches no groups; CUDA launches it whole and it returns. cube_run submits 1, 2, 4 .. 64 rounds at a time. A bang of 40 dependent 256-leaf stages: 16.5 -> 9.8 ms on an M4. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…reads the device's frontier - cube_run doubles the rounds per submission up to 64 while a submission takes under 250 ms, and halves them when one takes more, so a bang of long rounds stays far from the GPU's command-buffer timeout. - The host reads the cursor once at the bang's start; after a submission it reads only the frontier the pack wrote, and no longer rewrites the pack's counts. The CPU path takes the cursor alone, and a bang leaves the cursor at 0. - Metal binds the pipeline, buffer and threadgroup memory once per encoder, not once per dispatch. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…by an event; the next bang starts at the rounds the last one took, and Metal skips the device-side frontier check
nicolas-abril
force-pushed
the
gpu-device-loop
branch
from
October 19, 2026 11:06
b14de72 to
69cf38e
Compare
…es, one event and pool per round; CUDA doubles from 1 each bang and checks its dispatch by slot; heap turn fields named
nicolas-abril
added a commit
to nicolas-abril/bend
that referenced
this pull request
Oct 19, 2026
bendlang#1438's tail-drop spine and bendlang#1447's device rounds come in as written. The constructor table takes bendlang#1438's Cid struct with this branch's nat flag (a foreign Nat answer checked as it comes in) as one more byte; lay_holds keys by ty_key. tests/io/nat_host_neg takes bendlang#1656's JS-only form. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
nicolas-abril
added a commit
to nicolas-abril/bend
that referenced
this pull request
Oct 20, 2026
Drops the fid_bangs, cid_hot and cid_nat macros, turn_fits and the CUDA bend_dev exit; shortens main_pure, turn_groups, turn_set, map_msb_u, map_bit and comments; io_run requeues through io_push. lay_holds keeps its memo: without it fan-out types compile 7-13x slower (a 200-type chain of 100-ctor types: 0.31 s vs 4.06 s).
nicolas-abril
added a commit
to nicolas-abril/bend
that referenced
this pull request
Oct 20, 2026
Drops the fid_bangs, cid_hot and cid_nat macros, turn_fits and the CUDA bend_dev exit; shortens main_pure, turn_groups, turn_set, map_msb_u, map_bit and comments; io_run requeues through io_push. lay_holds keeps its memo: without it fan-out types compile 7-13x slower (a 200-type chain of 100-ctor types: 0.31 s vs 4.06 s).
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
A bang runs in rounds. Every time a stage's joins finish and the next stage forks, that is a new round. On main each round goes back to the host:
cube_runreads the frontier (H_CURSOR), picks which passes to launch from its size, commits one command buffer and waits. So the GPU sits idle for a full host round trip on every dependent stage.#1431, #1435, #1436 and #1437 ran into this with a 4-bit matvec for LLM decoding (about 97 dependent stages per token). They worked around it with format-specific kernels and a
q4go/q4waitqueue. This PR removes the round trip in the runtime, so it works for any bang.Change
bend2/comp.tsonly.H_CURSORintoH[H_FRONT]and writes the group count for each of the five dispatches atH_GROUPS(turn_set). These are the same rulesgpu_runused to apply on the host (turn_groups). Atypedefcheck makes sure both fields fit beforeH_ROOT_DONE.GPU_DEEP). They are chained by one staticMTLEventwhose value keeps going up, and each has its own autorelease pool. The host stops queueing once it reads an empty frontier from the heap it was given, then waits for the buffers still in flight. Rounds queued after the frontier empties dispatch nothing. There is no batch size and no timing rule, and nothing carries over between bangs.atand returns at once whenturn_groups(H[H_FRONT], at)says there is no work. This replaces the inverted pass→slot formula.TURN_ROWS(at)is the one group-size rule, shared byturn_groupsand the CUDA launch.Measurements
PAR-GPU on the cluster, from
gates/perf.ts(one Mac mini per cell, the gate's own build and timing). Main is8f86f735, the PR is1ac32994. Each side ran the gate once. Outputs match on every bench.Gates
At
1ac32994, on top of648e4456: repo 54/54, ping 61/61, test 1655/1655 and perf 124/124 (test and perf run on the cluster).comp.tsis 64,885 ttok, under its 66,000 cap.Not tested
at, the launch usesTURN_ROWS(at), andgpu_passdoubles from 1 each bang.Notes
turn_groups(u32 g[5], indexed at runtime) made queens 8% and lexer 4% slower on the GPU, so it stays written as conditionals. The slot→pass table{0, 0, 1, 3, 2}exists only ingpu_run.🤖 Generated with Claude Code