Repository navigation
Performance 1/7: shared engine, conductor and component changes - #858
Conversation
… perf push The parts of the overnight perf push (draft #853) outside the per-model folders, so the speech/language and generation model changes stack on top: - Engine: ragged attention `shard_heads` (CFG-parallel nodes keep every head) and opt-in `compile_op` (`run` traces as the `mstar::ragged_attention` custom op); managers carry their resource key. `RaggedAttentionCallable` is deleted: callers bind the label with `functools.partial(resource.run, label=...)`. Piecewise `one_row_per_step` leases a packed region at batch size 1. - Submodule base: `TokenStop` declaration on `ARNodeSubmodule` (gives `check_stop` / `check_stop_batched`), `loopback_input_ids`, cached `get_device`. - Conductor: least-loaded data-parallel replica pick (`_worker_load`). - API server: warmup `concurrency` (int or list) and one copy per DP replica; `api_switch_interval_s` config. - Components: `rms_norm` / `add_rms_norm` helpers, `TopKRouter` `fused_softmax_topk` through the fused-MoE `router_topk` (now with a softmax mode), Qwen3 text encoder caches one rotary table and takes `padded`, `image_output_format`, `to_device_nonblocking`, threaded video decode, `DenoiseLoopSubmodule.ragged_for` via `resource.run`. - fused_moe H200 tile configs for E=128. - LTX-2.5: the one call site of the deleted `RaggedAttentionCallable`. - Docs (serving, adding_models), diffusion-scaffold skill, waypoint benchmark log parsing, and the tests for all of the above. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
39ee03e to
df1af58
Compare
Conflicts: - mstar/engine/resources/attn/ragged/flashinfer.py: main (#863) moved the manager machinery into ragged/base.py's _RaggedManagerBase for the FA3 dense backend. Took main's layout and moved this branch's resource-keyed mstar::ragged_attention op, the _MANAGERS registry and the compile_op `run` into _RaggedManagerBase, so both backends get compile_op; the FlashInfer managers just pass `key` through. - test/modular/test_conductor_tick_exception.py: imports; kept main's `re` and this branch's `Counter`. Converged on main: - mstar/utils/h2d.py: main (#890) added to_device_nonblocking too; kept main's and deleted this branch's duplicate (same signature; main's pins only for CUDA where this branch asked DeviceCapability). - mstar/model/components/token_step.py: main's TokenStepNode (#890) carried its own stop checks, the same logic as this branch's TokenStop. It now declares `token_stop` and inherits ARNodeSubmodule's checks; its _stops, check_stop and check_stop_batched are deleted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The replica pick summed live requests per worker across every node group, so a group sharing a worker with another (a DP2 group on ranks 0 and 1 beside a single-replica group on rank 0) saw the other group's requests as its own load and kept sending new requests to one replica. Each request now records its replica per group (RequestData.dp_replicas), and the load is counted on those keys. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The flag was applied at the engine's early lease only, while the runner's run and can_run derived the batch size from the request ids or segment lengths they were passed, so a caller had to repeat real_bs=1 and the first request id by hand or replay against a bucket the lease never promised. The runner now resolves a one_row_per_step region as one row in _resolve (lease, can_run, run) and trims run's request ids to the first, so the lease and the replay agree by construction and the engine passes the step's batch size. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ate (P3-3) With generated_key set, the stop check read the count through request_state, which creates a state for a request it doesn't have one for; a check that runs after the request's state was dropped left a fresh one behind for a finished request. It reads request_states directly now. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mixin derived token_stop from stop_token_ids through a cached_property, while the docs and every other AR node set token_stop in __init__. Canary and Canary-Qwen now set it in __init__, the mixin drops stop_token_ids and the property, and the module docstring no longer claims the stop checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The module-level key -> manager map the mstar::ragged_attention op reads was never purged, so it kept every ragged manager (and its plan wrappers and workspaces) alive past the engine that built it. A WeakValueDictionary drops an entry with the engine's last reference; there is no resource teardown hook to deregister from, and the op can only name a manager by key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-interval rows (R1.2, R1.3, P3-4) adding_models.rst documents one_row_per_step (and that the whole step's segments then count against max_segments_per_request), shard_heads and compile_op, says the latter two are the model's declaration rather than YAML overrides, and names RaggedAttentionConfig where it said "the config". serving.rst says warmup sends each entry once per DP replica and that concurrency copies can, not will, share a step, and no longer claims the Qwen3.5 configs set api_switch_interval_s (they do in #859, not here). The ragged config comments stop naming stacked-PR adopters. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…source (R1.7, P3-6) The helper only reads the sampler's slot masters and the step's routed ids, so it belongs to the resource that owns those masters rather than to every AR submodule. Its comment no longer claims the row-index copy never blocks: off CUDA, to_device_nonblocking is a plain copy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ne (P3-5, P3-6) TopKRouter's fused_softmax_topk docstring says bf16 logits tie often, so an opting-in model should count how often its outputs diverge; the router test's docstring describes the tests it holds; mstar/utils/device.py matches main. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nity (R1.1) VIDEO_DECODE_THREADS was min(8, os.cpu_count()) fixed at import: it counted the host's CPUs rather than the ones a container's cpuset gives the process, and ignored preprocess_torch_threads, the API server's preprocessing budget. media_limits.video_decode_threads() now bounds a decode by torch's intra-op pool (which that knob sets), by len(os.sched_getaffinity(0)) where the platform has it, and by 8. V-JEPA2's own loader asks it too. Qwen3.5 decodes through transformers' load_video, which takes no thread count; that stays a follow-up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Whisper decoder is the third EncoderDecoderNode, missed by the previous commit: with the mixin no longer deriving token_stop from stop_token_ids, it had no stop declaration and its stop checks fell back to the base, which never stops (test_whisper_decoder_node caught it). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…_ids (R2.a) The decode branch of preprocess kept its own copy of the routed-row index_copy_, whose row index was a blocking torch.tensor(..., device=). The sampler's helper does the same copy with the index staged through pinned memory. The test's fake sampler binds the real helper, so the routed-row case now exercises it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ler names (R2.b) Canary-Qwen built its TokenStop from the module constants while Canary and Whisper used self.DECODE_LOOP; all three now take both the loop and the sampler from the class attributes, so a subclass that renames them stops on the right loop. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… CPU quota (R2.c) video_decode_threads reads sched_getaffinity, so it honours a cpuset but not a CFS quota (a container --cpus limit). The docstring and the adding_models note now say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A one_row_per_step config refuses capture sizes above 1, so the test runner now captures only (1, 256). can_run(3, ...) then passes only because the runner resolves the step as one row; with the old per-call batch size it finds no bucket. Also drops a stray third blank line in test_conductor_replica_assignment.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… docs paragraph Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main brings #849 (Host overhead 2/N): the API server hands the data worker one InlineTokens item per result_tokens frame, Model.text_output / postprocess_inline map inline token ids straight to bytes (raw token bytes for Qwen3.5 and Whisper), and the Rust frontend relays chunks in batches (APIServer.relay_chunks_to, ChunkSink). One conflict, the typing import in mstar/api_server/entrypoint.py: both sides' names (NamedTuple for DataParallelReplicas and _SinkBatch, Protocol for ChunkSink). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The row still said the knob would be re-measured once #849 lands. It has landed and changed the message-thread and data-worker path the knob tunes, so the numbers are from before that change and the re-measure is still pending. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
It was there to document the removed splits_heads attribute; what is left repeats the RaggedAttentionConfig row. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Head sizing now equals main's, so the six cases pinned nothing this branch changes. The shard_over_sp=False test stays; the helper loses its recurrent branch, which only the deleted test used. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main already sizes a shard_over_sp=False ragged attention over the TP group, so the test pinned nothing this branch changes. The file's coverage goes back to main's plus the manager registry test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Brings in #850 (worker speculates as columns, fused route+send, skips unread publishes) and #974 (only expert parallelism syncs MoE routing). #850's fused route+send (route_and_send_inline) builds its route input from the host column as plain i64s, where this branch's inline values are per-row optional so a withheld output's row carries None. The Rust body now builds Some per row, and the worker sends a step whose stop check withheld an output through the general path, which gives that row None: the fused call reads every row's token off the column and would send it inline. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflict in test/modular/test_ragged_attention_cpu.py: main's mixed-wrapper assertions (a sink row on each side of the prompt rows, the tensor-core decode half on the FA2 paged prefill kernel) replace this branch's copy of the old ones; this branch's op-registry test is kept beside them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: - conductor.py: imports; Collection is gone with main's last use of it, Counter stays for the replica load. - audio_features.py: imports; both main's stft_as_real and this branch's DeviceCapability are used. - nemotron_duplex/submodules.py, components/conformer.py: main moved the duplex encoder to components/fastconformer_stream.py and deleted conformer.py. Its device_counts helper is gone on both sides; the stream's two count copies now hand to_device_nonblocking the host list directly, as this branch's other call sites do. Main moved the PNG thread count onto the CPU plan, so video_decode_threads follows the plan's unpinned CPUs too rather than usable_cpus(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gaurav-Shah05
left a comment
There was a problem hiding this comment.
On H100s I ran main (9f84f18cd) against this PR with Qwen3.5-0.8B, closed loop:
| main | #858 | ||
|---|---|---|---|
| 2 replicas, 32 requests | 10.8k tok/s | 12.8k tok/s | 1.18x |
| 2 replicas, 64 requests | 17.4k tok/s | 19.0k tok/s | 1.09x |
| 2 replicas, TTFT p50 at 32 / 64 requests | 18.1 / 27.0 ms | 18.1 / 27.1 ms | |
| 1 GPU, 64 requests | 17.5k tok/s | 17.6k tok/s | 1.01x |
The replicated rows use replies of 16 to 512 tokens, drawn per request, and your least-loaded gain reproduces there with TTFT unchanged. The 1-GPU row has the stop-path changes alone, and they're neutral, as you found.
The description says GLM-5.2's and Command A+'s caps become max_output_tokens - 1, but both were already max(1, n - 1) on main, so neither budget changes.
lgtm overall and my comments are only minor.
| # first step (``ARNodeSubmodule.ended_at_prefill``), which withholds its | ||
| # ``buffer`` (``StopCheck.withheld``), so the loop's client edge must | ||
| # carry ``buffer`` | ||
| prefill_walks: frozenset[str] = frozenset() |
There was a problem hiding this comment.
With prefill_walks, a stop id sampled at prefill keeps the request alive for one more decode step. This token is then withheld from both the inline frame and the tensor path.
#794 ends the same request at the prefill by returning END_PARTITION, which the conductor already turns into request done, so I think prefill_walks should mean that walk's row returns END_PARTITION.
ended_at_prefill and the withheld inline row in the worker and both graph runtimes can go then.
There was a problem hiding this comment.
| torch.set_num_threads(int(threads)) | ||
|
|
||
|
|
||
| def _set_switch_interval(config: dict) -> None: |
There was a problem hiding this comment.
| files: list[WarmupMedia] = field(default_factory=list) | ||
| output_modalities: list[str] = field(default_factory=lambda: ["text"]) | ||
| model_kwargs: dict | None = None | ||
| concurrency: tuple[int, ...] = (1,) |
There was a problem hiding this comment.
Is there any config in this stack that sets concurrency? If not its parsing, docs and tests cover a dead path. It would be needed when a config needs a batched warmup.
There was a problem hiding this comment.
None does, so it's gone in a25694a; ids are now warmup-..
| "warmup request %d/%d failed after %.1f s: %s", | ||
| index + 1, len(specs), time.perf_counter() - t0, | ||
| getattr(exc, "detail", exc), | ||
| if replicas.fewest < replicas.most: |
There was a problem hiding this comment.
Each warmup copy reaches its own replica only because the least-loaded pick spreads concurrent requests. #920 already had to turn the prefix cache off on a replicated deployment's warmup copies to keep them apart. What do you think about the conductor placing warmup copy i on replica i of each group, so every replica warms under any routing policy?
| @classmethod | ||
| def build(cls, spec: KVSpec, info: EngineResourceInfo): | ||
| # before the cache is allocated: it is sized off the head counts | ||
| spec.config.shard(info.head_shards) |
There was a problem hiding this comment.
With the shard moved from __init__ to build, the tests that construct KVManager directly with a two-rank group no longer shard, so test_cfg_tp_publish.py's TP2 managers each hold both KV heads.
Those helpers should go through KVManager.build, or shard the config themselves, so the test still moves one rank's shard.
There was a problem hiding this comment.
Done in 641e06c; the TP2 helper shards its config the way build does.
| # ``mstar::ragged_attention`` op, rather than breaking there per layer. For | ||
| # a compiled, uncaptured region whose fusions around attention pay; off by | ||
| # default, since tracing a whole transformer as one graph per capture | ||
| # bucket costs startup time and capture memory (Z-Image: +2.6 GiB). |
There was a problem hiding this comment.
Does anything in the stack turns compile_op on for Z-Image?
Conflicts: RouteInput / RouteArg keep this branch's optional inline values beside main's per-row log-probs (Python and Rust alike); the delivery parity test runs this branch's None inline row with main's log-prob script; Qwen3-ASR's imports take main's encoder move plus TokenStop. Main's Qwen3-Omni ragged-backend stand-in takes the manager key this branch passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A case with a pinned CPU fails if video_decode_threads goes back to usable_cpus(); the MSTAR_STEP_THREAD_CORE row names FFmpeg's per-decode threads among what the pins shrink. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Its gain was measured before #849 and #939 changed the message-thread and data-worker path, and the re-measure it was waiting on never ran. The serving-config key, its validation, docs rows and test go; workers keep main's MSTAR_PY_SWITCH_INTERVAL_SEC unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
KVManager shards its config in build, so the test's direct constructor shards it the same way and each rank moves its own shard. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No config sets it, so its parsing, docs and tests covered a dead path. Each entry still goes out once per data-parallel replica, now as warmup-<spec>.<copy>. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
TokenStop.prefill_walks rows now return #794's END_PARTITION (a stop id, or a one-token budget), with last_prefill_flag for a prompt prefilled over several passes; ended_at_prefill and the None inline value in the worker and both graph runtimes are deleted. Qwen3.5, Qwen3-ASR, Canary and Canary-Qwen declare their prefill walks in place of #794's per-model checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
#794's cap-versus-stop-check agreement tests for Canary and Canary-Qwen assumed a stop check that counts max_tokens; TokenStop leaves the budget to the loop's cap, so they pin the cap instead. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…LM-5.2 test comment names the loop cap Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This is the shared slice of the perf stack: the engine, conductor, worker and component changes that the model slices (#859, #860) build on, and a few that pay on their own. Data-parallel requests went to a random replica, warmup reached one replica at batch size 1, and several pieces had a copy per model: the AR stop check, pinned host-to-device staging, head-shard sizing in every resource builder. This PR routes each request to the least-loaded replica, warms every replica, gives AR submodules one stop path (
TokenStop), one pinned H2D helper and one head-shard count, and adds the engine pieces the model slices need: a one-row-per-step piecewise lease, ragged attention as a custom op, a softmax top-k MoE router kernel, a capturable Qwen3 text encoder and threaded request video decode. Against main, least-loaded routing gains up to +45% (Wan2.2, 2 replicas, c=4) and threaded decode +28% (V-JEPA2, c=4); on Qwen3.5 the stop-path changes are neutral.Routing and warmup. The conductor sends a request to the replica of each node group that serves the fewest live requests (ties at random). Load is counted per (node group, replica), so groups that share a worker don't skew each other's picks.
WorkerGraph.replica_ranksis the one place a worker graph's replicas come from, read by the conductor (its instance table included), warmup, the parallel config and placement validation. Each warmup entry is sent once per replica, as concurrent copies. When node groups have different replica counts, the entry first runs once alone, so a group with fewer replicas still compiles its batch-1 shape. Ids arewarmup-<spec>.<copy>andwarmup-<spec>.solo; a failing copy lets its siblings finish, then the first failure is re-raised. Spreading is best effort: copies over the admission cap queue and can land on a replica that is already warm. Stop path. An AR submodule setsself.token_stop = TokenStop(...)in__init__and getscheck_stopandcheck_stop_batchedfrom one host copy of the step's tokens; one per-row verdict (_token_stops) holds the walk test, andHostRows.first_valuesis a single row-map path. Whisper, Canary, Canary-Qwen, Qwen3-ASR (main'sTokenStepNode) and Qwen3.5's decode go through it, Qwen3.5's own copy is gone, and the engine no longer passesstep_walk.TokenStop.prefill_walkscovers a token sampled at prefill, before the loop exists: a stop id there, or a one-token budget, returns main'sEND_PARTITION(#794), so the conductor ends the partition instead of starting decode.last_prefill_flagnames the step-metadata flag of the pass whose token is the reply's, for Qwen3.5's prompt prefilled over several passes. Qwen3.5, Qwen3-ASR, Canary, Canary-Qwen, GLM-5.2 and Command A+ declare their prefill walks, so #794's per-model prefill checks are gone again for the one declaration; Whisper declares none, since an align walk can follow its decode. The token budget is the loop cap alone (get_loop_caps,max(1, max_output_tokens - 1)since #794): unlike main's stop checks,TokenStopdoesn't also countmax_tokens. Head sharding.EngineResourceInfo.head_shardsis how many ways a node's ranks split a resource's heads: all tp × sp ranks of its lockstep group (1 without one). Every builder (KV, cross, ragged, linear attention, recurrent state) sizes by it rather than reading the group itself. The one per-resource exception is main'sRaggedAttentionConfig(shard_over_sp=False), which splits a ragged attention's heads over TP only, decided in_build_raggedalone; Wan2.2 in #860 uses it to keep every head on each SP rank. Engine pieces for the model slices.one_row_per_steplets a packedlease_before_stepregion (a vision tower) lease, check and replay a whole step's requests as one captured row; the runner applies it in its own bucket lookup, the step's segments all count againstmax_segments_per_request, and it is refused at config time where it means nothing (#859's Qwen3.5 vision and Omnilingual ASR use it). WithRaggedAttentionConfig(compile_op=True), the resource'sruntraces asmstar::ragged_attention, so a compiled region keeps one graph across attention; the op body calls the manager's_run, and the op's manager registry holds managers weakly. It is off by default because Z-Image with the op took +2.6 GiB of capture memory and a slower startup, and only Wan2.2 (#860) turns it on.RaggedAttentionCallableis gone: callers bind the label onresource.run.TopKRouter(fused_softmax_topk=True)routes a softmax router through the Triton top-k kernel, passed to the MoE block throughrouter=; ties go to the lower expert id and bf16 logits tie often, so a model opts in after counting divergences (Qwen3-Omni in #859). Tuned tile tables cover Qwen3-Omni's two expert shapes on H200. The Qwen3 text encoder keeps one rotary table per device at the longest length, sliced per prompt, and a caller that knows whether the batch is padded passespadded, skipping a host sync; this makes klein's and Z-Image's encoders capturable (#860).rms_norm/add_rms_norm(function forms, for norms whose weights live elsewhere) andimage_output_format(lets a model checkoutput_formatat intake) change no behaviour on their own. Host work.to_device_nonblockingasks the device's pin-memory capability instead of checking for CUDA, and takes a sequence plusdtype, built straight into pinned memory in one allocation; main's tensor path is kept, and an already-pinned contiguous source is copied from in place. The sampler's keep-mask, wav2vec2's frame lengths, and the Nemotron duplex, Parakeet, FastConformer and audio-feature callers use it instead of their own copies.PinnedStager.to_deviceis deleted, and its callers, the DSA page tables and GLM-5.2's seq_lens, useto_device_nonblocking(this edits #387's GLM-5.2 and DSA files). torchcodec decodes throughModel.load_video(and Cosmos3 and V-JEPA2) usemin(8, usable_cpus())FFmpeg frame threads, read once per process; main'susable_cpus()reads the CPU affinity and the cgroup quota, and the count is not tied to the API server's torch pool, which main sizes down from the CPU quota. Qwen3.5 (transformers' loader) and Qwen3-Omni don't go through it. Ride-alongs:benchmark/waypoint/benchmark_streaming.pyandtest/waypoint/serve_rollout.pymatch the server's internal ids (with their#nsuffix) to client ids, andtest_submodule_device_cachechecks that a compiled forward reading main's cached device does not recompile.Hardware: Eras p5en H200s, torch 2.12.1+cu132, FlashInfer 0.7.2.post1,
MSTAR_COMPILE_LEVEL=1, SHM arena, Rust graph runtime and control mesh unless a row says otherwise. The per-change gains are push-1 A/Bs against main on the model each change was built for, 2–4 rounds on fresh servers. The "atad5f644d3" rows are against mainfadeb2759(ed31270d1differs only in a test docstring): one GPU for Qwen3.5-0.8B and Whisper large-v3, two for Qwen3.5-9B TP2 (GPUs 0 and 1 of a shared 4-GPU slice), four for Command A+ TP4 (command_a_plus_tp4.yamlwith its KV pool at 2048 pages, the size the file gives for 141 GB GPUs); throughput there isbenchmark.runner, closed loop, a fresh Inductor cache per boot, rounds A B B A A B (plus 3 in reverse order at 0.8B c=64), output tok/s per boot, failed=0 everywhere.test_conductor_replica_assignment(alternation, lockstep instances balanced whole, groups sharing a worker balance independently).test_startup_warmup(every replica reached, the solo run, a failing copy).test_piecewise_can_lease: a real runner leases and replays the step as one row; refused for batched or non-leasing regions.test_ragged_attention(CUDA: graph kept, callable built inside a compiled region, rebuilt resource);test_ragged_attention_cpu(a dropped manager leaves the registry).head_shardsTokenStop, stop path (8f3de9809vs37ad4f58f)worker_phasesprofileevent_sync~0.09 ms);check_stop+9 µs/step.TokenStop, ASR (8f3de9809)0d242a90a), not on this PR's own head: WER on 200 LibriSpeech clips, stack vs main: Whisper 1.72 vs 1.74%, Canary 5.52 vs 5.52%, Qwen3-ASR 1.79 vs 1.84%; at c=32, 196–198 of 200 transcripts identical. Withignore_eos, Whisper and Canary returnmax_output_tokens+ 1 tokens on both arms, as before this PR (Qwen3-ASR's count is #859's).TokenStop, token counts atad5f644d3max_tokens1/2/4/16, both graph runtimesTokenStop, streaming atad5f644d3TokenStop, Whisper atad5f644d3TokenStop, Whisper word timestampsverbose_json, c=1; 24 clips (544 words) plus a 90 s and a 605 s clip; 8 main boots, 7 PR boots, each with its own compile cacheTokenStopprefill_walks, Command A+ TP4 atad5f644d3(before the move ontoEND_PARTITION)MSTAR_TP_ASYNC_SCHED=1max_tokensbudget, with and withoutignore_eos, returns the expected count. Greedy 64-token replies byte-identical. 96 mixed requests at c=8: 0 failures, 0 wrong counts, KV pool drained to 0 pages.TokenStop, CPUtest_canary_model(batched agrees with per-request),test_whisper_modelandtest_qwen3_asr_model(batched path taken),test_whisper_decoder_node,test_glm52_model(a stop id or a one-token budget at prefill ends the partition, with and withoutignore_eos),test_glm52_engine_cycle, Command A+'stest_integration,test_qwen3_5_check_stop_batched(only the last prefill pass's token ends the partition),test_inline_tokens(a withheld step skips the fused route-and-send).ad5f644d3vs mainevent_sync0.12 of a 2.46 ms step at c=32, 0.19 of 3.26 ms at c=64), 9B TP2 GPU-bound (3.64 of 6.01 ms at c=16, 5.00 of 8.77 ms at c=64); step time equal across arms within 0.04 ms.088a61d37vs53a60d15c)ad5f644d3. CPU:test_accelerator_graph_backend(pins where the capability says, one pinned allocation, CPU device),test_h2d_to_device(an already-pinned tensor is copied from in place, contiguous or not).test_moe_router: matches the torch router, compiles, off by default;test_fused_moe_decode). The tiles aren't bit-exact (different K blocking); the Omni greedy divergence they cause is within batching noise.test_diffusion_numerics(one table across prompt lengths).torch.equalto the single-thread decode on two boxes; served V-JEPA2 outputs byte-identical. CPU:test_media_decode_limits.ruff; CPU Core vs main9f84f18cdruffclean. CPU Core against main9f84f18cd: 8751 passed, 1007 skipped, 2 failed, the same 2 as main (thetest_eval_compare_multigpudump_stacks pair); none fail only on this branch. GitHub CI green ata4b5fcacb(CPU Core, TP lockstep sim, rust-transport).Not yet run on GPU at the head: everything after
ad5f644d3(the move ofprefill_walksonto #794'sEND_PARTITION; main merges through165b403b7, among them #810's per-node dtype, #849, #850, #876, #794 and #950; the head-sharding change; skipping the fused route-and-send on a withheld step); GLM-5.2 (not enough hardware) and the merged H2D helper's DSA and GLM-5.2 callers; the loop-cap budget on Canary-Qwen on this PR's own head; LTX-2.5 SP2 and TP2 with main's per-node dtype merged in; and, since push-1, DP2 routing and multi-replica warmup, the sampler keep-mask H2D, raggedcompile_op(#860), the MoE router on Omni, and V-JEPA2's seeking loader's speed.🤖 Generated with Claude Code