Repository navigation
Conversation
a113cbf to
4d67f53
Compare
|
This pull request has merge conflicts with main. Please rebase it so it can be reviewed and merged. |
Detect DSpine bridge checkpoints from dflash_config.selected_prefix_code_bridge, validate the bridge and serial-refinement invariants, and reserve the bridge weights, TG/TSV tables, and lane scratch before KV pool sizing. Reject unsupported bridge configs loudly, including the flat schema, so a DSpine checkpoint cannot silently load as plain DFlash. Refs pegainfer-project#1169 Signed-off-by: bohaotu <bohaotu@pony.ai>
4d67f53 to
5237b4a
Compare
FeathBow
left a comment
There was a problem hiding this comment.
The bridge weights billed here match the release's safetensors header, and the config rules follow the Tencent reference. But nothing consumes the parsed config yet: a valid DSpine checkpoint still loads and drafts as plain DFlash, now with about 926 MiB less KV. Until the bridge forward lands, could the draft load refuse uses_dspine()?
| // per-step bias is the dominant term). Zero for plain DFlash drafters. | ||
| let markov = MarkovHead::reservation_bytes(config, max_decode_batch_size)?; | ||
| let selector = SelectorHead::reservation_bytes(config, max_decode_batch_size)?; | ||
| let dspine = dspine_bytes(config, max_decode_batch_size)?; |
There was a problem hiding this comment.
Could this move into the PR that allocates the bridge? The scratch layout is not written yet and the pinned number restates the formula. Next to the allocation it can be checked against measured memory.
| /// DSpine checkpoints nest this under `dflash_config`; accept the flat form too. | ||
| #[serde(default)] | ||
| draft_block_causal: Option<bool>, | ||
| /// Parsed only to reject the unsupported flat DSpine schema loudly. |
There was a problem hiding this comment.
The reference reads the bridge and draft_block_causal only from dflash_config and has no flat schema, so both top level fields can go.
| ); | ||
|
|
||
| ensure!( | ||
| bridge.code_size > 0 && bridge.code_size.is_multiple_of(128), |
There was a problem hiding this comment.
The reference has no code_size constraint. This can go, or land with the kernel that needs it.
|
|
||
| impl DSpineConfig { | ||
| /// `Ok(None)` for a plain DFlash/DSpark checkpoint; `Ok(Some(_))` when the | ||
| /// checkpoint carries a code bridge. Every DSpine-only field is validated |
There was a problem hiding this comment.
The reference also rejects selected_prefix_feature_reliability, selected_prefix_feature_next_token_rows, conv or selector fields next to a bridge, and a code_bridge_second_pass whose head_topk disagrees. The fields left unchecked (kappa, selected_prefix_decode_mode, the teacher fields, refine version and weight) are training only, so the doc comment should say that.
A parsed bridge is not consumed yet, so a DSpine checkpoint would still load as plain DFlash while its reservation shrank the KV pool. Refuse the load until the bridge forward lands, drop the flat-schema fields the reference never reads, keep only the kernel's rank/hidden constraints, and add the reference's reliability/next-token/conv/selector and second-pass agreement checks. The bridge scratch reservation moves to the allocation PR so it can be checked against measured memory; the checkpoint-sized weights stay. Signed-off-by: bohaotu <bohaotu@pony.ai>
FeathBow
left a comment
There was a problem hiding this comment.
The plain DFlash fallback is closed, but the refusal comes after the resource work. Startup loads the target, adds the DSpine reservation (about 805 MiB of weights and tables) to the KV margin, profiles and allocates the pool, and only then loads the drafter and refuses. On a card with little headroom the profile fails first with "memory profile leaves no room for KV cache", which hides the actual reason. Could the refusal move into the config check before the target loads, and the reservation land with the allocations it bills, where it can be compared with measured memory?
None of the new tests reach the refusal: with the ensure! in loading.rs removed, every path they cover stays the same. A test on the early refusal would pin it.
The reference also rejects an explicit is_causal = false, layer_types other than full attention and a sliding window; this parser does not read those yet. The description still describes the first commit: the scratch term and the 970,933,248 B total, the code_size rule, the flat bridge rejection, and no draft code path changes.
| pub(crate) rank: usize, | ||
| // Read by the bridge forward/refinement and the weight loader in the next slice. | ||
| #[allow(dead_code)] | ||
| pub(crate) eps: f32, |
There was a problem hiding this comment.
eps, feature_layers and head_topk have no reader in this PR. The refusal only needs bridge detection, so these can land with the loader and forward that read them.
| self.markov_rank > 0 | ||
| } | ||
|
|
||
| /// DSpine code bridge is active; the draft forward injects per-layer |
There was a problem hiding this comment.
This says the forward injects features, while every load refuses. The refusal message in loading.rs and the doc comment on from_inner also claim near zero acceptance without a measurement; saying the checkpoint is unsupported is enough.
| self.hidden_size | ||
| ); | ||
| anyhow::ensure!( | ||
| !self.anchor_first(), |
There was a problem hiding this comment.
from_json already rejects num_anchors and a nonzero markov_rank, the only inputs that set anchor_first, so this check and the test that sets the field by hand can go.
| /// Top-level refinement declaration some DSpine checkpoints carry instead of | ||
| /// (or alongside) `dflash_config.selected_prefix_last_write_refine`. | ||
| #[derive(Clone, Debug, Deserialize)] | ||
| struct RawDSpineSecondPass { |
There was a problem hiding this comment.
The reference raises on unknown code_bridge_second_pass keys. #[serde(deny_unknown_fields)] here would match it, so a key added later that changes the pass gets refused at load.
…rly refusal Move the memory reservation to the PR that allocates the bridge, so the resolved bridge dimensions leave DSpineConfig and a marker remains. Run the refusal before the target loads, where a test can pin it, and reject explicitly non-causal configs, non-full layer types and sliding windows as the reference does. Deny unknown second-pass keys. Signed-off-by: bohaotu <bohaotu@pony.ai>
Summary
First slice of #1169: parse and validate a DSpine drafter's config, and refuse
to serve it until the bridge forward lands.
dflash_config.selected_prefix_code_bridge.reference (
tencent/Qwen3-8B-DSpine-b16).serving them as plain DFlash.
from_runtime_with_decode_environmentbefore thetarget loads, so a DSpine path never profiles or allocates KV and never
reaches the drafter loader.
No DSpine weights are loaded, no memory is billed, and no draft computation
path changes in this PR. The bridge reservation lands with the allocation that
defines its layout, where it can be checked against measured memory.
What changed
pegainfer-qwen3/src/config.rsDFlashConfig.dspine: Option<DSpineConfig>(marker) anduses_dspine()DFlashConfig::from_json(testable entry point;from_filedelegates)DFlashConfig::ensure_servable()refuses DSpine checkpointsdraft_block_causal = true,is_causal != false)num_anchors, no markov head)selected_prefix_last_write_refineortop-level
code_bridge_second_pass; both must agree when present, withmode = serial_table,first_exact = L-1, power-of-twohead_topkin [2, 64]ranka non-zero multiple of 128; positivecode_sizeandepshead_dim == 128,hidden_size % 512 == 0DSpineConfigdeliberately carries no dimensions yet; they land with theloader and forward that read them
pegainfer-qwen3/src/executor.rsensure_servable()before the target modelloads and before KV profiling
Tests
Rust formatting, CPU Clippy, CPU unit tests, Simulated frontend E2E, and every
model-line CUDA Clippy (sm_80) pass
config::tests:tencent/Qwen3-8B-DSpine-b16config parses and validates against Qwen3-8Bensure_servableerrors, plain DFlash does not)head_dim/hidden_sizetarget constraints are rejectedNotes
The next PR lands the kernel/FFI skeleton, flat-2D 3D loader, loading, forward,
and bridge reservation as one slice. The probabilistic sampled chain remains a
separate follow-up as tracked in #1169.