Commit Graph
4117 Commits
Author SHA1 Message Date
8cc112d486 [DSA] Skip indexer KV cache for skip-topk layers (#30531)
Co-authored-by: Brayden Zhong <brayden@radixark.ai>
Co-authored-by: mmangkad <mohammad.angkad@radixark.ai>
2026-08-17 02:02:23 -07:00
YAMY 7c423cfd41 [PD] Avoid unused PREBUILT prompt tensor transfer (#35070) 2026-08-17 16:48:12 +08:00
Liangsheng Yin 711bdacb82 [Spec] Resolve shared-read ends from the backend declaration alone (#35059) 2026-08-17 01:35:29 -07:00
b83d507cd7 [NPU] Support DeepSeek-V4 DSpark and refactor DSV4 cache management (#33676)
Co-authored-by: JiaruiChang5268 <jc5268@columbia.edu>
Co-authored-by: Kelon <kelonlu@163.com>
Co-authored-by: unknown <z8ruev42yk@gmail.com>
Co-authored-by: Talantan1102 <545811257@qq.com>
Co-authored-by: Talantan1102 <44429302+Talantan1102@users.noreply.github.com>
2026-08-17 16:27:44 +08:00
Mohammad Miadh AngkadandMohammad Angkad 5769b6d637 [JIT Kernel] Migrate causal_conv1d_fwd and causal_conv1d_update from AOT to JIT (#35031)
Co-authored-by: Mohammad Angkad <mohammad.angkad@radixark.ai>
2026-08-17 15:22:37 +08:00
Mohammad Miadh Angkad 721e359ca7 Suppress expected FlashInfer TRT-LLM workspace warnings (#34921) 2026-08-17 15:16:16 +08:00
vikram singh shekhawatandClaude Sonnet 4.5 f7a404e9c3 Fix rope config compatibility and VL/transformers-fallback weight loading (#31575)
Co-authored-by: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
2026-08-17 14:53:59 +08:00
ziang663andZhangheng 43226af812 fix(hicache): limit load-back pending to write-back (#34519)
Co-authored-by: Zhangheng <hzh0425@apache.org>
2026-08-17 14:23:54 +08:00
Wang, FangYuan eb61cb2823 [AMD] Support prefill context parallel two batch overlap for DeepSeek V4 (#33480) 2026-08-16 22:40:29 -07:00
Kaixi f3225bceb3 [DSV4] Emit TMA-aligned UE8M0 scales for FP8 einsum (#34277) 2026-08-16 22:06:41 -07:00
Khoa PhamandClaude Opus 5 0fb040cbeb [DCP]Localize HiCache DCP indices once per transfer, not per layer (#34889)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 21:54:51 -07:00
Enrique Shockwave 92b1d382c7 [Fix] Correct dense FP8 Marlin bias ordering (#35020) 2026-08-17 03:43:36 +00:00
b6d7602914 [CPU] Add support for Gemma4 on Xeon (#22498)
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: jianan-gu <jianan.gu@intel.com>
Co-authored-by: Haotong Zou <haotong.zou@intel.com>
2026-08-17 10:52:26 +08:00
hanwlax 0e500feae6 fix tpot by adjusting the sliding max-prefill-size window size (#34856) 2026-08-17 09:50:48 +08:00
Mick 0e178c3d22 [diffusion] chore: reuse srt siglip vision model (#34988) 2026-08-17 09:16:17 +08:00
Xiaoyu Zhang 0aa09ab40d [diffusion] Reuse bit-exact modulation fast path for LTX-2.3 (#34930) 2026-08-17 09:04:10 +08:00
d91c3682b0 [AMD][CI] Add GPT-OSS perf benchmarks to the ROCm 7.2 nightly (#34645)
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Michael <michaelzhang-ai@users.noreply.github.com>
2026-08-16 16:35:09 -07:00
Yuwei AnandClaude Opus 5 a508d60295 [BCG][6/N] Allow prefill breakable CUDA graph for the Kimi archs (#34245)
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 15:57:14 -07:00
Lianmin Zheng f61f584347 Add explicit EPLB balancedness reporting modes (#34998) 2026-08-16 15:31:11 -07:00
Lianmin ZhengandJialin Ouyang c6ebcf39ee [VLM] Avoid synchronizing multimodal placeholder counts (#34995)
Co-authored-by: Jialin Ouyang <Jialin.Ouyang@gmail.com>
2026-08-16 15:15:49 -07:00
Lianmin ZhengandYe Qi 32e6fb4fdc [Frontend] Apply request header overrides to chat completions (#35001)
Co-authored-by: Ye (Charlotte) Qi <ye.charlotte.qi@gmail.com>
2026-08-16 15:08:47 -07:00
EthanandQAQEthan 5534380d46 [Spec] Support logprobs with DSpark speculative decoding (#34696)
Co-authored-by: QAQEthan <QAQEthan@users.noreply.github.com>
2026-08-16 15:05:13 -07:00
Lianmin Zheng 67e12131df Build Rust extensions on demand in source checkouts (#34994) 2026-08-16 14:58:06 -07:00
Liangsheng Yin bae353ba55 [misc] Rename shared-read boundary to shared-read ends and fix wrapper delegation (#34982) 2026-08-16 14:36:31 -07:00
Ke Bao ace7314173 Add bit-exact guard for extra_buffer_lazy (#35030) 2026-08-16 23:34:46 +08:00
Xiaoyu Zhang 41abbb0d32 [diffusion] Accelerate Cosmos3 T2I QKNorm+RoPE (#34932) 2026-08-16 20:15:32 +08:00
Xiaoyu Zhang 095ec6c997 [diffusion][kernel] Accelerate Sana BCG with bit-exact conv post-processing (#34928) 2026-08-16 20:05:57 +08:00
Mick 3d3194f6c3 vlm: cache kimi-k3 per-image processor artifacts (#34404) 2026-08-16 19:51:13 +08:00
Mick 968b355f12 vlm: streamline vision sdpa reshapes (#34991) 2026-08-16 19:05:53 +08:00
Mohammad Miadh AngkadandMohammad Angkad 6ab4b99bc2 [Quantization] Fix GPTQ scheme attachment broken by LinearBase.scheme default (#34962)
Co-authored-by: Mohammad Angkad <mohammad.angkad@radixark.ai>
2026-08-16 00:48:02 -07:00
cctry 8922bb98e2 refactor(hicache): flatten L2 transfer execution (#34793)
GB300 test fails unrelated
2026-08-16 00:33:34 -07:00
Chenzhou LiandXiaoyu Zhang 56a759cffc [JIT Kernel] Migrate moe_topk_softmax from AOT to JIT (#34509)
Co-authored-by: Xiaoyu Zhang <1182563586@qq.com>
2026-08-16 15:02:57 +08:00
wangwenmingaa 4654b927eb [HiCache] Optimize LogicalHostPool free-list release (#33998) 2026-08-16 12:17:33 +08:00
Ke Bao 0f706c33d2 Fix swa eviction frontier for bigram keys (#34870) 2026-08-16 11:52:14 +08:00
Spandan Tiwari f68517f644 [AMD][Quantization][Bugfix] Fix bug related to fp8 max on gfx95x for per-token-group quant (ROCm) (#30900) 2026-08-15 19:54:16 -07:00
gilfordtingandClaude Fable 5 4a6dc267e1 [Spec] Support mamba-radix-cache-strategy extra_buffer_lazy with DFLASH (#34763)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 16:36:59 -07:00
Mohammad Miadh AngkadandMohammad Angkad 3802a725ac [CI] Pin the allowed_media_domains supplied-instance reads in the step-12 ratchet (#34961)
Co-authored-by: Mohammad Angkad <mohammad.angkad@radixark.ai>
2026-08-15 16:35:19 -07:00
Liangsheng Yin 0f7aaceda5 [misc] Rename the WAR read-done fastpath to shared-read-done (#34916) 2026-08-15 15:02:02 -07:00
Ke Bao dd458f3212 Fix dsv4 kl test timeout (#34963) 2026-08-16 01:57:40 +08:00
cctryandYilong Zhao e5b3a48751 Add --http2-max-concurrent-streams server arg (#34796)
Co-authored-by: Yilong Zhao <74357408+happierpig@users.noreply.github.com>
2026-08-15 10:34:49 -07:00
Shenxiu Liu fb97be4359 Fix Whisper transcription for audio over 30 seconds (#33604) 2026-08-15 23:51:05 +08:00
Mick a64791b312 test: restore GLM-4.1V nightly latency threshold (#34811) 2026-08-15 18:13:19 +08:00
Mick 35cefd1c51 feat: add safeguards for remote media URLs (#34892) 2026-08-15 18:12:15 +08:00
Baizhou Zhang 7769f54feb [Kimi-K3] Use explicit SiTU activation for MegaMoE (#34883) 2026-08-15 01:41:59 -07:00
Cheng Wan c87a2ced12 test(step-12): state the bag contract as what resolution produced, and the skill rule that goes with it
`test_bag_values_match_server_args` asserted `bag == field`. That holds today
only because construction resolves in place; step 12 keeps the record raw, and
the plan doc calls this test out as one that becomes **false by design** for
every field resolution fills in.

Rewritten against the resolved projection, which is the half that survives: the
bag carries what resolution produced. The `bag == field` assertion stays as one
line at the end, labelled as the tripwire -- when it starts failing for a
resolution-written leaf, the flip has landed and the bag is the only place the
effective value lives.

The reference is an independent resolution of the same raw input (a fresh,
never-published record) rather than `resolved_server_args_dict()`, which reads
`vars(server_args)` back and therefore only restates the published instance.
And the record goes through the real pipeline on a real mini config, published
through `publish()`: the dummy-model path returns at the dummy boundary with
every sampled leaf still raw, so the old comparison was raw==raw and vacuous
(both Codex catches). Reproducibility (#34094) licenses the sibling as a
stand-in for the pipeline output.

The sample admits only leaves resolution writes on this input on both CI
device shapes (attention_backend, page_size, chunked_prefill_size,
mem_fraction_static), and the raw-differs guard asserts it per leaf -- a
default-count threshold let supplied inputs like `model_path` (no dataclass
default, so any path "differs") stand in for resolution work. Passthrough
leaves (host, hicache_ratio, moe_runner_backend, model_path) move to a
separate projection smoke that claims only what it checks: publish projected
an unchanged field into its namespace. Between the two resolutions the test
restores environ and the EnvField none-flags, so the sibling resolves the
same pristine input rather than the first resolution's leftovers.

And the class runs its body exactly once, like the other dual-resolve
harnesses: a CI retry re-enters after the first attempt leaked process
state, which is the hazard the pristine snapshot exists to rule out.


docs(skill): a supplied-instance read is not automatically safe

The whole-object rule said "keep the supplied-instance contract; don't rewrite
the parameter reads unless the field is runtime-mutated". That is the right rule
for the *object* and the wrong stopping point for the *field*: after step 12 the
record carries the user's raw input, so `server_args.page_size` inside a
runner-owned constructor reads the CLI default rather than the effective value.

The rule now names that second case as step-12 debt with a guard attached
(`test_supplied_instance_exposure_ratchet.py` fails on a new pair, so the
decision is made when the read is written), and names the two shapes that stay
parameter-form on purpose: a helper the resolution pipeline calls with a
`resolved_view`, and a factory whose contract is "build X from the record you
are handed".
2026-08-15 00:40:37 -07:00
Cheng Wan d804b6bd98 config: pin the step-12 debt on the supplied-instance surface
A callee that takes `server_args` keeps the supplied-instance contract, so no
ratchet counts its reads -- and that is right for the *object*. What it does not
cover is what the object will carry after step 12: the instance stays at the
user's raw input, so a callee reading a field resolution fills in starts seeing
the CLI default instead of the effective value.

Measured, not guessed: **297 distinct file/field pairs read one of the 125
fields resolution can write** -- what remains after the earlier members'
conversions (the census found 314 across the package; the page_size,
chunked_prefill_size and graph/limit families were converted by the members
below this one, so the list lands at the remaining debt with no churn). The
census counts three spellings of the read: `server_args.field` off the
parameter, `getattr(server_args, "field", default)` with a literal name, and
the *parked* form -- `self.x = server_args` in a method that takes the
parameter, read as `self.x.field` anywhere in the class. Parking under a
different object, a container, or a computed name stays invisible, like in
every census of this family.

The written-field set is derived in-test from resolved configs against the
dataclass defaults -- the same matrix the context repo's audit tool uses -- and
the union is only as complete as the matrix: fields a matrix entry passes in
are excluded, so each entry must let resolution make the decision the entry is
about. The DWDP shape is the loudest example: `_handle_dwdp` writes `dp_size`,
`enable_dp_attention`, `ep_size` and friends itself, and without a
`{tp_size: 2, dwdp_size: 2}` entry the whole DP/EP topology family (37 pairs
across the launcher, the controllers, the tokenizer and the spec workers)
never entered the written set at all. Multi-item scoring is the same shape in
miniature -- `_handle_multi_item_scoring` writes `disable_radix_cache` itself,
and the `{enable_mis, attention_backend=flashinfer}` entry (the backend is
passed because the handler asserts rather than switches) pins the radix-cache
builder and its friends.

The written set carries **may-write semantics**, statically collected from
every mechanism that can put a resolved value on the record, unioned with the
matrix (construct-and-diff still catches value-level writes statics cannot
name). The enumeration approach kept losing to review -- the DFLASH hook hole,
then the declarative registry (`MODEL_OVERRIDES` forces dtype for two arches
through a setattr applier no assignment scan sees), then the deprecated-alias
loop that writes through a name tuple -- because each round found one more
*mechanism*, not one more field. So all of them are collected now: hook
assignments under `arg_groups/`, the record's own method assignments (the
mooncake layout rewrite, the deepseek-EP mode defaults, the seed fill that
only fires when the caller did NOT supply one -- which construct-and-diff can
never see, since measuring requires supplying), the declarative override
registry, the alias-normalization tuple (drift-guarded), and the
late-resolution keywords. A statically-collected write site that can never
fire is a dead branch to delete upstream, not a reason to shrink the census
(maintainer's ruling). And the pin is split by host: `_EXPOSED` is asserted everywhere,
`_EXPOSED_CUDA_ONLY` (empty today) is where a capability-gated write's
readers go -- one shared exact list cannot hold such a pair at all, since
pinning it fails CPU as "gone" and omitting it fails CUDA as "new".

The resolve-once shape also undercounts on axes a construction call never
sees (each of these was a review catch, and each added pinned families):
resolution branches on the *environment*, so every matrix entry resolves under
the plain env and under the CI shape (`SGLANG_IS_IN_CI`), with the pristine
process state (environ plus the `EnvField` descriptor flags) restored between
entries -- that is where `soft_watchdog_timeout`'s four readers come from. Some
fields resolve *late*, at validation rather than construction
(`declare_late_resolution`): those writers are collected statically by keyword
-- `lora_paths`, `reasoning_parser`, `tool_call_parser` -- with the one dynamic
`**detected` site spelled out in a table guarded against drift. Fields holding
only a `default_factory` are materialized rather than skipped, and
`tokenizer_path` / `served_model_name` -- always filled from `model_path` --
leave the passed-inputs exemption and pin their twelve readers. A module the
census cannot parse fails the test instead of shrinking it, which immediately
caught a BOM-carrying file every previous census had silently skipped (the
scans read `utf-8-sig` now). And the test registers on the CUDA runner besides
the CPU suite, because capability-gated writes only open on real hardware;
AMD is intentionally not registered -- an exact pin cannot be verified from
any pinning host -- with the reasoning in the header.

**A second axis is already wrong today**, independent of step 12. Some config is
decided after publish and recorded with `get_context().override(...)` -- elastic
EP resizing `ep_size`, a weight update rewriting `model_path` / `load_format`,
HiCache attach naming a storage backend, adaptive speculative decoding moving
`speculative_num_steps`. That write reaches the bags and never the record, so a
supplied-instance read of one of those fields answers with the startup value from
the moment the override lands. **73 pairs over 13 fields** are in that position -- including the overrides
that arrive as `**kwargs`: the collector statically resolves dict-literal
expansions (the HiCache attach shape, whose write/read pairs on
`hicache_write_policy` / `hicache_storage_prefetch_policy` were invisible
before) and fails loudly on anything it cannot resolve, with
`update_server_args` exempted by name because its key set is the API's
caller's, not this file's.
Whether each is a defect depends on ordering -- a value copied at construction,
before any override, is fine -- so the axis is pinned as a measurement with the
same growth guard, not as a list of bugs.

One of them *was* a defect and is fixed at the base of this stack: the
linear-attn dispatch table rebuilt itself from the record after the SM100 GDN
prefill decision had been recorded in the bag, so a second runner's rebuild
dropped it. That choice is a per-runner stamp now and is not recorded
process-wide at all, which is why neither the read nor the field appears on this
axis.

The list is pinned both ways, on both axes. A new pair fails, because the moment
to decide where a resolved value comes from is when the read is written, not
during the flip; a disappeared pair fails too, naming the entry to delete, so the
registry stays a measurement rather than a memory of one. Both axes
reverse-verified: a new read of a written field is reported by file and field.

Per-field dispositions live in the plan doc; several of these are "should this
callee take a config at all?", which is a design call rather than a sweep.


test(step-12): tripwire on the EPD guard that a raw record would silence

`_reject_missing_dispatched_encoder_embedding` is one of the two reads the
step-12 audit calls a blocker: it keys on `encoder_transfer_backend`, a field
resolution fills in, off a handed record. Today that record carries the
resolved value; after the flip it stays at the argument default `"auto"`
(`ENCODER_TRANSFER_BACKEND_CHOICES[0]`) for every auto-resolved launch and the
503 stops firing -- a guard that goes quiet, which no existing case notices.

The tripwire resolves a real language-only Kimi-K3 TP2 launch (a mini config,
the shape whose auto pick is `"zmq_to_tokenizer"`) and asserts the guard
rejects with the record resolution produced. A fixed double cannot trip on the
flip -- it would keep handing the guard the resolved value by construction --
so the record has to come from resolution itself: when step 12 lands, this
same launch hands the guard `"auto"`, the rejection silently stops, and this
test fails, which is exactly the signal that this reader needs the resolved
value from somewhere else (the per-engine overlay or the bag).

The launch pins `mamba_radix_cache_strategy=no_buffer` (+ the overlap-off it
requires): resolution's hybrid state-cache sizing branches on the host device
and asserts a GPU stack for extra_buffer, which a CPU CI runner does not have,
while the guard under test reads a field independent of that branch. The case
restores env *and* the EnvField descriptor flags -- a real resolution leaves
state os.environ does not carry.
2026-08-15 00:40:07 -07:00
Cheng Wan 1ab713c334 config: the post-publish consumers of the supplied-instance surface read the bags
config: the speculative workers take page_size from the bags

Seven worker constructors stored `self.page_size = server_args.page_size` off
the handed record. They all run after publish and all keep a copy of a
process-level value, which is the first row of the plan doc's supplied-instance
disposition table -- so they read `get_schedule().page_size`, and a post-publish
override now reaches them like it reaches every other consumer.

The supplied-instance census named the seven pairs; the exposure ratchet in the
next member pins what remains after this batch of conversions.


config: the post-publish chunked_prefill_size consumers read the bags

Four of the ten supplied-instance `chunked_prefill_size` reads are plain
post-publish consumers -- the EPLB recorder's buffer sizing, the deep-gemm
compile warmup (five reads), the KV-cache builder's effective size, and the
ngram embedding manager's assert. All are reached from runner init, so they read
`get_schedule()`.

Two are deliberately left: `create_kt_config_from_server_args` builds a config
*from a supplied record* by name and contract, and `CanaryLaunchCapacities.from_args`
is the same shape. Converting those would change what the function is, not where
it reads -- the plan doc's disposition table says so per field.


config: the remaining post-publish graph/limit consumers read the bags

Three more of the census's supplied-instance debts are plain post-publish reads: the dspark worker's
cuda-graph decode sizes, the dspark planner's SPS table bound
(`max_running_requests`), and the LoRA manager's cuda-graph moe buffers. The
dspark worker is the clearest of them -- it already read
`get_exec().graph.cuda_graph_config.decode.bs` thirty lines below the instance
read, so the file disagreed with itself about where the same value comes from.

Left where the function's contract is "build a config from the record you are
handed" rather than "read this process's config":
`create_kt_config_from_server_args`, `DllmConfig.from_server_args`,
`CanaryLaunchCapacities.from_args`, `build_compilation_config`. Changing those
would change what the function is.


config: the runner, scheduler and offload manager take page_size from the bags

The same `self.page_size = server_args.page_size` shape as the speculative
workers, in the three remaining process-owned constructors: `ModelRunner`,
`Scheduler`, and the decode-side KV offload manager. The scheduler process
publishes before any of them run. The one path that did not is `ModelRunner`
constructed standalone -- `python -m sglang.benchmark.one_batch` and the manual
runner tests build it with no prior publish, and the constructor's own publish
sat below this read -- so that publish moves above the constructor's first bag
read instead of leaving a window where the runner half-exists unpublished.

Left where the read belongs to something else: `utils/common`'s predicates are
called only from the resolution pipeline with a `resolved_view`,
`allocation_sizing` takes the config its callers supply by contract, and
`CudaVmmFeatureTransport` is tokenizer-owned -- one per tokenizer worker, which
is the per-instance boundary.

The conversion left the offload manager parking a record it no longer
reads; the parked copy goes with the read (the constructor parameter stays
-- its hicache sizing still reads it directly).
2026-08-15 00:39:35 -07:00
Cheng Wan f2ab6e306b config: the alias form of the runner-side instance read
The previous batch counted `self.server_args.X` and called the runner surface
done. It was not: the same read spelled through a local alias --
`server_args = model_runner.server_args` (or `sa = kvc.server_args`, `args = ...`)
followed by `server_args.leaf` -- is the same process-global read wearing a
local name, and the AST census counts **57 of them** across eleven files that
the grep never saw. Census per function, following the alias.

52 were leaves and go to their bag (`spec` 11, `schedule` 9, `memory` 7,
`exec.graph` 5, `exec.moe` 5, `parallel` 4, `disagg` 4, `model` 3,
`exec.mamba` 2, `exec.overlap` 2). Five were not leaves:
three derived members on the eager runner --
`max_speculative_num_draft_tokens` and `enable_mamba_extra_buffer` already had
accessors, and `max_prefill_buffer_tokens` gets one (all its inputs are `schedule`
leaves plus the configured PP size, so it derives from the bags and follows a
post-publish override; `TestDerivedPredicatesAgreeAcrossTiers` pins it against
the member over a 48-case matrix) -- plus `get_attention_backends()`, which the
same commit routes through `attention_backends()`, and a dict that merely shares
the name (`server_args_dict.items`). That dict is the one read left behind.

`build_attention_backends` also stops resolving the pair from the record: it
runs after publish, so it asks `attention_backends()` like every other consumer.
The draft override on the runner still wins first.

`dispatch_event_loop`'s three PP checks read the *configured* PP size, not the
live topology: the MLX runner stub never initializes torch.distributed, so the
live property asserts before the MLX event loop can start (a Codex catch). The
configured leaf answers the same value wherever the live groups exist.

`flashinfer_gdn_prefill_default`'s guard is the one read here that asks what the
*operator* named rather than what the config resolved to, and the bag leaf now
answers exactly that: the per-runner auto-default is stamped on the runner and
deliberately never recorded process-wide, so nothing writes that leaf after
launch and reading it back cannot mistake another runner's default for a flag.

Three test doubles injected a `SimpleNamespace`/`MagicMock` record for exactly
these reads and now publish instead (pool configurator, cache registry, GDN
prefill policy) -- the fixture publishes what the case configures and hands the
published instance to the whole-object contracts that still take one.

The functions this sweep partially converted stop mixing sources (review
catches): the flash-attention constructor's remaining seed reads
(`speculative_eagle_topk`, `speculative_algorithm`, both deterministic gates)
read their bags next to the leaves already converted;
`_should_disable_scheduler_metadata_precompute` reads the parallel config
leaves itself instead of taking the record (its alias binding was the last
use); and the autotune gates (`disable_flashinfer_autotune`, deterministic,
`flashinfer_autotune_skip_ops`) join the moe leaves the same function already
reads from the bags. The pool-configurator fixture drops a parameter nothing
published or read.
2026-08-15 00:39:03 -07:00
Cheng Wan 61908870f6 config: spell out the one dynamic config read the census could not see
`_is_dsa_active` asked `getattr(server_args, "_is_dsa_model_arch", False)`, and
that name has never existed on `ServerArgs` -- it arrived as a placeholder with
the CP strategy abstractions (#27313), so the getattr default has always decided
the predicate. A dynamic read of a name nothing sets is the one shape the config
census cannot follow, and it looked like a live decision while being dead.

Spelled as the constant it evaluates to, with the placeholder written down: what
it should ask (whether this process runs a DSA model arch) is the CP path's
call, and its only consumer, `ContextParallelStrategy.per_layer_attn_cp_comm`,
has no readers yet.

That was the sole entry in the read ratchet's `_INERT_DYNAMIC_READS`, so the
exemption list is gone with it -- there is no way to exempt a read from the
baselines any more, which is the invariant worth having. The `counted()`
indirection it existed for goes too (verified the three shapes it guarded still
report: direct, `getattr`, and an attribute-parked alias).
2026-08-15 00:38:30 -07:00
Cheng Wan d13d5c03ab config: decisions keyed on the attention backend read the configured pair
`--attention-backend` is one field of three: a launch that sets only
`--prefill-attention-backend` or `--decode-attention-backend` leaves the base
field at `None`. Seven decisions read that base field alone and therefore
answered from a field the operator never set. `attention_backends()` is the
pair with the base-field fallback already applied, so each site now asks it for
the half it actually needs:

- `inkling_common/attn` assembles backend-specific kwargs (rel_bias / score
  mods) and gates its fused prologue; the backend those describe is the one
  `self.attn` dispatches to, so `serving_attention_backend()` selects the pair
  member by `forward_batch.forward_mode`, mirroring
  `HybridAttnBackend._select_backend` exactly -- draft-extend routes through
  the prefill branch like the dispatcher does -- and preferring the
  runner-stamped pair, so a draft runner answers with its own backend. That
  preference only works if every backend that can enter a ForwardContext
  carries the stamp, so `DraftBackendFactory._create_backend` now stamps its
  products with the backend it resolved (draft override first), and the
  draft-extend conv-sidecar wrapper copies the wrapped backend's stamp -- the
  replacement backends the spec workers install had no stamp at all and fell
  back to the target's configured pair.
- The chunked-prefix-cache gate is a *prefill* feature -> prefill half. Reading
  the base field switched the feature off for every prefill-only configuration.
- `init_deterministic_inference_config` maps *prefill* knobs
  (SPLIT_TILE / PREFILL_TRUNCATION_ALIGN) -> prefill half; the map missed and
  left truncation unset.
- `two_batch_overlap` computes extend positions -> prefill half.
- mrope's interleaved-rope kernel runs in both phases -> both halves must
  support triton. This one is not conservative when it misreads:
  `support_triton(None)` answers **True**, so a `--prefill-attention-backend
  torch_native` launch took the triton path.
- The req-to-token writer has one caller, `alloc_for_extend` -> prefill half;
  its fallback pays several `.item()` syncs per request, so gating it on the
  decode half too would send every extend of a mixed launch through the slow
  path. `get_last_loc` (the spec-decode allocator's helper) keeps the
  both-halves reading: verify tokens are served by either half depending on
  `speculative_attention_mode`.
- The flashinfer version floor is a guard; it never fired for a launch that
  pinned flashinfer through a split field.

One more site the census found is not converted here: `gpt_oss` derives its
`sinks` parameter dtype from the backend, and a single parameter dtype cannot
serve a split pair (FA4 asserts bfloat16, trtllm_mha consumes float32), so
that one is a behaviour question rather than a config-source one and is fixed
in its own PR.

`test_split_attention_backend_decisions.py` pins the callable decisions by
calling them under a split-only publish, and pins the remaining ones
statically -- the file/why map fails if any of them goes back to the base field
(reverse-verified). It also asserts the `support_triton(None) is True` trap the
sweep exists for.

The stamp comes from the constructor, not the request: every factory leaf
answers ("effective_name", backend), because several map entries do not build
what their key says -- cutedsl_mla draft-extend builds the trtllm-mla backend,
"nsa" is a deprecated alias building dsa, and the hybrid-linear entries pick
fa3/intel_amx/triton by host, which no static rename table can express (a
review catch: on Blackwell the alias stamp reached Inkling's per-forward
kwargs assembly, which asserts a concrete kernel name, and crashed the first
draft-extend forward). The stamping is pinned by unit tests, not only by a
spec e2e: removing the child-stamping loop, stamping an alias from a leaf, or
dropping the wrapper copy goes red (reverse-verified), and a static guard
walks the factory source asserting no leaf answers an alias name. The child loop states its contract explicitly --
`create_decode_backend` passes `stamps_children=True` because its products
are per-step containers by construction, so a container without
`attn_backends` raises instead of being silently skipped by a defensive
probe. The `_version` invalidation names its contract (autograd's in-place
counter: private, chosen because it is the only per-tensor signal that ticks
on copy_-style updates; removal fails loudly). The version-floor guard's file
joins the pair-reader ratchet, and the one runner-seed chain read sharing the
backend's __init__ (`speculative_eagle_topk`) reads the spec bag.
2026-08-15 00:38:00 -07:00