Repository navigation
fix(cpu): deal a slow grow's forks to idle threads (#1475) - #1479
Merged
Merged
Conversation
…rks over the rows, so a call before each fork no longer keeps a fork tree on a few threads (bendlang#1475) A grow turn gives each row to one thread, and the first turn has only the rows the root's fork reached: 2 for a binary tree at 16 threads. A row grows by running each ring's head up to its next fork, so a tree that does real work before each fork ran its top levels on one or two threads. bendlang#1475's 256-leaf tree got 3.5x from 16 threads instead of 14x. row_grow now notes when it starts. In a turn with fewer rows than threads, a grow still growing after 1 ms makes one last pass that deals its forks through H_CURSOR to row g % CUBE_T, and cube_run repeats the grow turn over the rows the cursor reached until no grow deals. Once a turn has as many rows as threads, every thread has a row and no grow deals, so a deep, fine-grained tree keeps its old schedule. No bench deals: the longest grow in any bench is 0.275 ms (tree-bitonic). io_tick moves up to the Row section. comp.ts: 63,761 -> 63,944 ttok.
Lorenzobattistela
force-pushed
the
cpu-late-fork-spread
branch
from
October 12, 2026 14:20
f08af8e to
65a7b94
Compare
Lorenzobattistela
approved these changes
Oct 12, 2026
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.
Fixes #1475.
One commit,
65a7b947, inrow_growandcube_run. The token cut this PR first carried (a7e927ed) is already on main as75d6f66d, so it was dropped and the fix was rebased onto main.comp.tsis 63,972 ttok, under the 64,000 cap.The cause
A grow turn gives each row to one thread. The first turn has only the rows the root's fork reached: 2 for a binary tree. A row grows by running each ring's head up to its next fork. So when a tree does real work before each fork, its top levels ran on one or two threads while the rest waited at the turn's barrier. The drain that follows runs each task's subtree whole, so it can't make that back.
What changes
row_grownotes when it starts. In a turn with fewer rows than threads, a grow that is still growing after 1 ms makes one last pass. That pass deals its forks throughH_CURSORto rowg % CUBE_Tinstead of within its own row.cube_runrepeats the grow turn over the rows the cursor reached until no grow deals, then drains as before. The row bound still covers every task, because a dealt taskglands in a row belowg. The Cube comment states this.corpus_evalnever reaches the cube, androws < pool_sizeis never true.monk_stepand the device kernel don't change.row_growandpool_*are host-only (#if !DEVICE).#1475's tree
./spread 8 20000000 K --threads T --gpu off: 256 leaves of 20M steps each. M3 Max (12P + 4E), median of 3 runs, main (2343aec) vs this branch. Every K prints the same output on both builds.At 4 threads with K = 20M it is 4.14 → 2.82 s (ideal ≈ 2.75 s). The 1-thread times are the same on both builds.
Deep trees with little work before each fork
These are why the gate exists. Same program, 16 threads, median of 3, time and RSS. "Ungated" is this fix without the
rows < pool_sizecondition.Benches (local A/B)
All 17 runtime benches, main vs branch, run alternately, 3 runs each on this Mac:
--threads 16 --gpu off) and PAR-GPU (--gpu on)./usr/bin/time's 10 ms resolution. For example, queens' main build alone ranged 0.69–1.01 s.What I ran, and what I couldn't
gates/test.ts's shard script, run locally on this Mac in 4 shards (check, interpret, JS under bun, C), passes 1598 / 1600. The two failures aregfx_clicksandgfx_window. They expect "no display", but this Mac has a desktop session, so a window opens. main's build gives the same output here.gates/repo.tspasses forcomp.ts. With ttok 1.0 it flagsbendtt.lean(64,192), which this PR doesn't touch; main counts the same.Checked after the rebase (65a7b94 on main c25ee78)
./spread 8 20000000 20000000 --threads 14 --gpu off: main 2.79 s, this branch 1.05 s. 1 thread: 9.28 s vs 9.27 s.rows < pool_sizegate also keeps the ring from overflowing: with the gate removed, 8 of the 17 benches fail with ERR_RING at 16 threads.Not fixed here
If the work before each fork is a non-flat fork-free call (one that isn't tail-recursive, so it isn't inlined as a
spin_N), this PR doesn't help. Example: the issue'sspinwith its result XORed by 1 after the recursive call. The program gets about 2x at 16 threads on main and on this branch, and that holds even at K = 0. The issue's tail-recursivespindoesn't have this problem. I'll look at it separately.