Repository navigation
Conversation
A side step's admit and plan run on the GPU thread while the main thread may tear down other rows. The lane holds only recurrent pools, which take their lock in admit, plan and teardown alike, so the fence ordered nothing the pool didn't already. (The chain does fence its top-of-pass teardowns, readiness scans and ingests against N's admit; only its post-step removes run beside N+1's.) The fence's waits also ended inside the window where the main thread re-entered the lane's stream. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A side step its admit refused went back to wait but never failed a row that had failed preparing, which then hung. Both lanes now run the same tail: hand a refused step back to wait for room, then fail the blamed rows. The chain builds the rows its speculation holds, which the hand-back leaves alone, only on a refusal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The side lane was one special case: a chain plus one lane, for one node, holding nothing but a recurrent pool, with its stream decided in three places. A lane is now one placement unit, keyed by lane id everywhere: - Placement is config: `lane: <id>` on a node group (0, the chain, where unset) replaces `side_stream: true`, which now fails at startup, and every walk of the node runs on its lane, so `side_stream_walks` goes and a model declares nothing. `EngineManager.build` refuses what a lane can't serve, as before. - The engine owns each lane's stream, made once at load from the graph backend, and puts everything a lane issues on it (`Engine.on_lane`): capture, collate, the step, and the teardown of what its nodes hold, cleanup_request included. It switches the thread's current stream through the backend's per-call context, never by entering the stream object, so the main thread tearing down while the GPU thread runs the lane no longer raises. `Engine.set_side_stream`, `StepRunner.set_side_stream` and `_side_streams` go. - A lane holds the resources only its nodes declare, of any kind, decided from facts the resource reports (`Engine._lane_contexts`): declared by one lane's nodes on this worker, no dependency across lanes, no prefix cache, no state the main thread moves outside the step (`Resource.journals_resident_moves`) and no host rows. That replaces the recurrent-pool allowlist. The runner gets one map, resource -> lane context, for the stage partition and for every call into a lane's resource from outside its step (ingest, readiness, output copies, publishing, teardown), and the chain's clear_preplan leaves lane resources alone. - The worker runs a tuple of `Lane`s (`mstar/worker/lanes.py`), and the scheduler maps node -> lane. With the chain idle, the main loop waits for whichever lane lands first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rce with Every step waited on every node's latest pre-plan, because nodes can share a resource's slot buffers. That made a lane's codec window wait on the Talker's plan stream though the two share nothing. The rule is now the last-writer one: a step waits on its own node's pre-plan and on those of the nodes sharing a resource with it, directly or through what the resources depend on. Chain models whose nodes share nothing also drop the cross-node waits. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
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.
Not sure if we actually want this, might close
Stacked on #1175. Pending: a two-lane run on a real server (the config I tried doesn't load on the test box, for a reason unrelated to lanes).
What this does
A worker's GPU can run any number of lanes. Lane 0 is the main chain, and each other lane is a CUDA stream with one step in flight.
lane: <id>on a node group puts that node on a lane, and every walk of the node runs there. It replacesside_stream: true(which now fails at startup and points tolane:), and models no longer declareside_stream_walks. Nodes with different ids overlap; nodes with the same id take turns. The colocated Qwen3-TTS configs put the Codec onlane: 1.A lane can hold any resource only its own nodes declare, not just a recurrent pool. Whether a resource can live on a lane is decided from what the resource reports, not from its kind.
What it doesn't do:
lane: 1as Side lane admits a recurrent pool: Qwen3-TTS codec beside the chain again #1175 has it.So it fits light, stream-fed nodes (codecs, vocoders) beside a heavy chain, at high concurrency.
Fixes from Side lane admits a recurrent pool: Qwen3-TTS codec beside the chain again #1175: the teardown fence is gone, and teardown no longer re-enters the lane's stream from the main thread. A refused step on a lane now fails rows that failed preparing, where before they hung.
How it works
EngineManager.buildreads the node→lane map and refuses nodes a lane can't serve. The engine then makes one stream per lane at load. Everything a lane issues goes on that stream throughEngine.on_lane: capture, collating inputs, the step, and every call into its resources from outside the step (ingest, readiness, teardown).on_lanesets the thread's current stream with the runtime's per-call context, neverwith stream:, so the main thread and the GPU thread can both be on a lane at once.Engine._lane_contextsgives each lane the resources only its nodes declare. It refuses a resource that is shared across lanes or workers, depends on another lane's, keeps a prefix cache, has state the main thread moves outside the step, or stages host rows.StepRunnergets one map, resource → lane context. It uses the map to put those calls on the lane's stream and to leave the chain's staged pre-plan alone.Lanes (mstar/worker/lanes.py). The scheduler maps node → lane. Every lane shares the chain's submit, refused-step tail (_settle_step_rows) and postprocess. With the chain idle, the loop finishes whichever lane lands first.Tests
ruff check .test_lanes.py: two lanes; capture on the lane's stream; a lane taking a resource that isn't a recurrent pool and refusing shared, cross-lane-dependent, prefix-cached, host-row and evicting ones; every off-step call into a lane's resource running on its stream; the lane that lands first finishing first;side_streamrefused; a refused step failing its failed rowstest_preplan_error_paths.py: a step waits only on the pre-plans of nodes sharing a resourceqwen3tts.yaml(0.6B)qwen3tts_1p7b.yamlqwen3tts_base.yamlfails to load (qwen_ttsencoderupsamplesetter error) with or without lanes🤖 Generated with Claude Code