Repository navigation
feat(qwen4): share the Qwen3.5 GDN decode wrappers and prove their reuse - #1159
Conversation
The Qwen3.8-Flash-Next line's 36 Gated DeltaNet layers have Qwen3.5's decode geometry (16 key / 48 value heads at head dim 128, 4-tap conv), so they must drive Qwen3.5's GDN and conv1d kernels rather than a second implementation. The raw FFI was already shared in pegainfer-kernels, but the host-side wrapper — marshalling, shape assertions and state-pointer handling — lived only inside the fully feature-gated pegainfer-qwen35, which the new line cannot depend on. Move that wrapper (and the three kernel limits it asserts) into pegainfer-core::ops::gdn, behind a new qwen35 feature that forwards to the kernels feature the shipped line already enabled, so the kernel build is unchanged; Qwen3.5 consumes the promoted functions through its ops facade with its call sites untouched. Prove the reuse on layer 0 of the real checkpoint: a test behind the crate's qwen4 feature loads the nine GDN tensors from the real shards, holds their headers against the frozen contract, derives the geometry the shapes imply, and drives both kernels on a GPU — the conv against the reference formula and an exact state handoff, the GDN step against a host transcription of the documented recurrence, each request's own state slot, and the real A_log/dt_bias decaying a state by exactly the host factor. No reference oracle for this checkpoint exists locally, so the test states plainly what it is: a real-weight invariant check, not a parity check. Signed-off-by: bohaotu <bohaotu@pony.ai>
Code review审计对象:本 PR 改动集(
覆盖清单(14/14 已认领): 三条数据流逐条模拟(全部 ✓ 符合预期):
改动五问:①层——本体落在 FFI 与同一 feature 门所在的层, 修改措施
|
Review receipt (independent audit + reproduction)Audited the full diff ( No changes required. Reproduced on this box (RTX 5060, sm_120, shared):
The documented non-goals (no HF oracle, synthetic activations, block composition and prefill path open, sm_89 row untested) are correctly scoped in the PR and the bring-up doc. |
FeathBow
left a comment
There was a problem hiding this comment.
Thanks for this. The move itself is clean. The two bodies are byte for byte the code that left recurrent.rs, and the tensor names do match Qwen3.5's. On one sm_89 GPU, fmt and the Qwen3.5 clippy pass, pegainfer-qwen4 passes its 48 default tests, and the CPU half of the reuse test passes. Requesting changes on where the wrappers land and on the new test, which the description presents as a proof of reuse on the real weights but which mostly re-proves what the tree already pins.
The Qwen3.5 wrappers that need the kernels' qwen35 feature already live in pegainfer-kernels/src/ops/qwen35.rs, behind that same feature and over the same csrc/qwen35 sources, and pegainfer-qwen35 imports them from pegainfer_kernels::ops directly. pegainfer-core has no model feature today and re-exports kernels items one by one, so this adds a second owner, which the self review's R5 already notes. Moving the two functions into the kernels module needs no change to the kernels Cargo.toml or build.rs, so the reason given for deferring the kernels side does not apply.
Check in gdn_decode_reuse.rs |
Status |
|---|---|
| GDN tensor shapes and dtypes match the frozen contract | already held by the_contract_is_the_frozen_checkpoint |
| Frozen config passes the geometry rules | already held by the validator's head dim and conv checks |
Gate in (0, 1) on the real A_log and dt_bias |
an identity, computed on the host |
| conv1d and GDN against a host transcription | copies the kernel and starts from a zero state (inline) |
| Per request state slots | already held by gdr_decode_batch_matches_single_slot_reference, which runs this exact 16/48/128 geometry at batch 3 |
What is new is the check of the mirror against the shared constants, and the file only builds with the qwen4 feature, so nothing runs it unless someone builds that feature by hand. Under that feature cargo clippy -p pegainfer-qwen4 --features qwen4 --all-targets -- -D warnings fails with three errors in the test (chunks_exact_to_as_chunks, needless_range_loop, float_cmp), while the description reports clippy for pegainfer-qwen35 only.
Before merge we would ask for three things.
- Move the two wrappers and the three constants into
pegainfer-kernels/src/ops/qwen35.rsand drop the newpegainfer-corefeature (inline). - Drop
gdn_decode_reuse.rs, and with it the newqwen4feature, which otherwise pulls the whole Qwen3.5 kernel set (HD256 attention and the Triton AOT prefill included) into this crate for one test. The roadmap in #1103 covers the reused GDN through the model graph fixtures and makes the first correct prefill and decode its M0 exit, so that is where the reuse gets proven. If a component check should land now, run several decode steps from a live state against the Transformers recurrence (inline). - Correct the description and the bring-up note where they call this a proof of reuse on the real weights (inline).
Smaller points.
- The two wrappers go from crate private to public here, so the qkv width and the
dt_biasanda_loglengths are worth checking against the head counts like the other dimensions already are. - If the test stays, an explicitly set but unusable
PEGAINFER_TEST_MODEL_PATHshould fail instead of skipping, andin_proj_z,norm.weightandout_proj(about 60 MiB) are loaded but never used. - The two audit comments on this thread come from the author's account, so we read their conclusions as claims and checked them here.
| # Gated DeltaNet decode ops. Their kernels are Qwen3.5's (`csrc/qwen35/…`), which | ||
| # both the Qwen3.5 and the Qwen3.8-Flash-Next lines build; the flag names the | ||
| # kernel set, not the model line. | ||
| qwen35 = ["pegainfer-kernels/qwen35"] |
There was a problem hiding this comment.
This would be the first model feature on pegainfer-core. The qwen35 gated wrappers otherwise sit in pegainfer-kernels/src/ops/qwen35.rs behind this same kernels feature, so the two functions can go there with no edit to the kernels Cargo.toml or build.rs, and this feature and its forwarding in pegainfer-qwen35/Cargo.toml go away.
| for row in 0..batch { | ||
| let mut reference_state = vec![0.0f32; head_state_len]; | ||
| let expected = reference_gdn_step( | ||
| &gdn_input[row * qkv_dim..(row + 1) * qkv_dim], | ||
| &b_rows[row], | ||
| &a_rows[row], | ||
| &dt_bias, | ||
| &a_log, | ||
| &mut reference_state, | ||
| key_heads, | ||
| value_heads, | ||
| key_dim, | ||
| val_dim, | ||
| ); | ||
| let got = &out_host[row * out_dim..(row + 1) * out_dim]; | ||
| let diff = max_rel_diff( | ||
| &got.iter().map(|v| v.to_f32()).collect::<Vec<_>>(), | ||
| &expected.iter().map(|v| v.to_f32()).collect::<Vec<_>>(), | ||
| ); | ||
| ensure!( | ||
| diff <= 8e-3, | ||
| "gdn decode row {row}: the reused kernel differs from the reference recurrence by a \ | ||
| relative {diff}" | ||
| ); | ||
| let device_state: Vec<f32> = ctx.stream.clone_dtoh(&gdn_slots[row])?; | ||
| ctx.sync()?; | ||
| let state_diff = max_rel_diff(&device_state, &reference_state); | ||
| ensure!( | ||
| state_diff <= 1e-4, | ||
| "gdn decode row {row}: the updated state differs from the reference by a relative \ | ||
| {state_diff}" | ||
| ); |
There was a problem hiding this comment.
Both the device state and reference_state start at zero here, so k · S is zero and a kernel that dropped the feedback subtraction would still match. The state comparison also has no finite check, and fold(0.0, f32::max) skips NaN, so an all NaN state reports zero error. A component check would need several steps from a nonzero state, with both arrays checked finite before the reduction.
| fn reference_gdn_step( | ||
| qkv: &[bf16], | ||
| b_proj: &[bf16], | ||
| a_proj: &[bf16], | ||
| dt_bias: &[f32], | ||
| a_log: &[f32], | ||
| state: &mut [f32], | ||
| num_key_heads: usize, | ||
| num_value_heads: usize, | ||
| key_dim: usize, | ||
| val_dim: usize, | ||
| ) -> Vec<bf16> { | ||
| let q_dim = key_dim * num_key_heads; | ||
| let out_dim = val_dim * num_value_heads; | ||
| let mut out = vec![bf16::ZERO; out_dim]; | ||
| let head_stride = key_dim * val_dim; | ||
| for v_head in 0..num_value_heads { | ||
| let k_head = v_head * num_key_heads / num_value_heads; | ||
| let q: Vec<f32> = (0..key_dim) | ||
| .map(|i| qkv[k_head * key_dim + i].to_f32()) | ||
| .collect(); | ||
| let k: Vec<f32> = (0..key_dim) | ||
| .map(|i| qkv[q_dim + k_head * key_dim + i].to_f32()) | ||
| .collect(); | ||
| let v: Vec<f32> = (0..val_dim) | ||
| .map(|i| qkv[2 * q_dim + v_head * val_dim + i].to_f32()) | ||
| .collect(); | ||
|
|
||
| let q_sq: f32 = q.iter().map(|value| value * value).sum(); | ||
| let k_sq: f32 = k.iter().map(|value| value * value).sum(); | ||
| let q_scale = 1.0 / (q_sq + 1e-12).sqrt() / (key_dim as f32).sqrt(); | ||
| let k_scale = 1.0 / (k_sq + 1e-12).sqrt(); | ||
| let q: Vec<f32> = q.iter().map(|value| value * q_scale).collect(); | ||
| let k: Vec<f32> = k.iter().map(|value| value * k_scale).collect(); | ||
|
|
||
| let x = a_proj[v_head].to_f32() + dt_bias[v_head]; | ||
| let softplus = if x > 20.0 { x } else { (1.0 + x.exp()).ln() }; | ||
| let decay = (-a_log[v_head].exp() * softplus).exp(); | ||
| let beta = 1.0 / (1.0 + (-b_proj[v_head].to_f32()).exp()); |
There was a problem hiding this comment.
This transcribes the kernel, including its 1e-12 L2 epsilon, f32 normalization and f32 sigmoid, while the Transformers 5.16.1 reference uses l2norm(..., eps=1e-6) on the bf16 q and k before promoting them and takes b.sigmoid() in bf16. Agreement with it shows the kernel matches itself, which the recurrent.rs tests already cover at this geometry.
| // 3. The gate path on the real scalars: the reused kernel computes | ||
| // `-exp(A_log) * softplus(a + dt_bias)`; on this checkpoint that must be a | ||
| // decay in (0, 1) for every value head, or the state would grow. | ||
| let mut decays = Vec::with_capacity(value_heads); | ||
| for head in 0..value_heads { | ||
| for row in 0..batch { | ||
| let x = a_rows[row][head].to_f32() + dt_bias[head]; | ||
| let softplus = if x > 20.0 { x } else { (1.0 + x.exp()).ln() }; | ||
| decays.push((-a_log[head].exp() * softplus).exp()); | ||
| } | ||
| } | ||
| ensure!( | ||
| decays.iter().all(|d| d.is_finite() && *d > 0.0 && *d < 1.0), | ||
| "the real A_log/dt_bias must put every gate in (0, 1), got {:?}", | ||
| decays.iter().fold(f32::INFINITY, |a, b| a.min(*b)) | ||
| ); | ||
| println!( | ||
| "{TEST}: real gates decay in ({:.3e}, {:.6})", | ||
| decays.iter().fold(f32::INFINITY, |a, b| a.min(*b)), | ||
| decays.iter().fold(0.0f32, |a, b| a.max(*b)) | ||
| ); |
There was a problem hiding this comment.
This runs on the host, and exp(-exp(A_log) * softplus(x)) lies in (0, 1] for any finite input because both factors in the exponent are positive. It can only fail at the ends through underflow or rounding, so it says nothing about the kernel or about the regime the description mentions.
| /// The largest deviation relative to the reference's own magnitude, so a check is | ||
| /// as tight at 1e-3 as at 10. One bf16 rounding is 2^-9 relative; the reference and | ||
| /// the kernel differ only by summation order and by `exp`/`ln` implementations. | ||
| fn max_rel_diff(a: &[f32], b: &[f32]) -> f32 { | ||
| assert_eq!(a.len(), b.len()); | ||
| a.iter() | ||
| .zip(b) | ||
| .map(|(x, y)| (x - y).abs() / y.abs().max(1.0)) | ||
| .fold(0.0f32, f32::max) | ||
| } |
There was a problem hiding this comment.
.max(1.0) makes this an absolute bound for anything below magnitude 1, so it is not as tight at 1e-3 as at 10. An expected 0.001 against an output of 0 reports 0.001 and passes both the conv 5e-3 and the GDN 8e-3 bounds.
| /// The GDN and full-attention kernel limits: the first three are owned by the | ||
| /// shared Gated DeltaNet decode wrappers in `pegainfer_core::ops::gdn` (which this | ||
| /// line drives with the same numbers as Qwen3.5), the last by the HD256 | ||
| /// full-attention kernels. This crate's config layer runs with no device — a probe | ||
| /// must not need CUDA — so the values are mirrored here rather than imported. | ||
| /// `tests/gdn_decode_reuse.rs` binds the frozen checkpoint's geometry to the | ||
| /// shared constants, so a drift between this mirror and the kernels is a test | ||
| /// failure rather than a silent mis-read. |
There was a problem hiding this comment.
The test that binds this mirror only builds with the qwen4 feature, so a drift stays silent until someone builds that feature by hand, which is weaker than the test failure this comment promises.
| The reuse is proven **on the real weights**. `pegainfer-qwen4/tests/gdn_decode_reuse.rs` | ||
| (behind this crate's `qwen4` feature, pointed at a checkpoint with | ||
| `PEGAINFER_TEST_MODEL_PATH`) loads layer 0's nine GDN tensors from the real shards, holds | ||
| their headers against `tests/frozen_tensors.json`, and drives both kernels on a GPU: the | ||
| conv against the reference formula plus an exact state handoff, the GDN step against a | ||
| host transcription of the same recurrence, each request's own state slot, and the real | ||
| `A_log`/`dt_bias` decaying a state by exactly the host-computed factor. |
There was a problem hiding this comment.
The kernels already run at 16 key and 48 value heads in gdr_decode_batch_matches_single_slot_reference, and the checks here compare them with a transcription of themselves, so this is not a proof of reuse on the real weights. Please describe it as what it is, and leave the exit criteria row open until slice B's GDN layer has a reference to compare against.
The two decode wrappers and the three kernel limits belong beside the other Qwen3.5 wrappers in pegainfer-kernels/src/ops/qwen35.rs, behind the kernels' qwen35 feature. That removes the first model feature from pegainfer-core and keeps Qwen3.5 on the owner it already imports for HD256; qwen4 will import the same owner when slice B wires its executor. Drop the real-weight reuse test and the qwen4 device feature with it. The tensor contract and config already pin the geometry, names, dtypes and 1-D lengths, while the component test compared the kernels with a transcription of themselves from a zero state. The reuse gets a reference check in slice B's model-graph fixture, which is pegainfer-project#1103's M0 exit. Also check the now-public wrapper's qkv width and dt_bias/A_log lengths against the head counts. Refs pegainfer-project#1103 Signed-off-by: bohaotu <bohaotu@pony.ai>
|
改动内容:
验证结果:
|
Add a GPU unit test for `conv1d_decode_batch_into` at the production channel width (10240), four decode steps, and three slots with distinct initial states. It compares the output with the kernel's documented recurrence within one bf16 ulp and the state hand-off bit for bit, so the shared wrapper keeps a direct guard after the qwen4 component test was removed. Also assert the GDN wrapper's head-group invariant (positive key heads, value heads a whole multiple of them) before the kernel's integer head mapping runs, and correct the qwen4 notes: the wrappers will run once slice B wires the text graph, and this line's config pins CPU-only mirrors rather than sharing the kernels' definition. Refs pegainfer-project#1103 Signed-off-by: bohaotu <bohaotu@pony.ai>
|
@codex review pelase. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 47cd85144c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| pub(crate) use pegainfer_kernels::ops::conv1d_decode_batch_into; | ||
| pub(crate) use pegainfer_kernels::ops::gated_delta_rule_decode_batch_into; |
There was a problem hiding this comment.
Run the production Qwen3.5 decode gates
Before redirecting the shipped Qwen3.5 decode path through these relocated wrappers, run the production hf_golden_gate/e2e_scheduler: the supplied verification explicitly leaves both unrun, while the added GPU unit tests exercise isolated conv/GDR calls rather than real weights, scheduler integration, or captured decode. The repository requires production E2E evidence for feature and fix changes, so this relocation is not yet verified to preserve serving behavior.
AGENTS.md reference: AGENTS.md:L126-L126
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Thanks — Codex is right that the relocation had not been exercised on a real checkpoint. I ran both production gates on the L20 (sm_89) at head 47cd8514, with Qwen3.5-0.8B :
Environment: L20 46 GB, CUDA 12.9.86, Triton 3.7.1, FlashInfer 4d75a33f / CCCL 16bd510c, PEGAINFER_CUDA_SM=89, CUDA_VISIBLE_DEVICES=0.
The relocated bodies are unchanged apart from the new shape and head-group asserts, so this is the production regression loop on top of the byte-level diff. The 48-value-head geometry remains covered by gdr_decode_batch_matches_single_slot_reference (16K/48V@128, batch 3); the 0.8B E2E exercises the same wrapper, scheduler and graph path at 16 value heads.
Commands (run inside the L20 pod):
export PEGAINFER_TEST_MODEL_PATH=/home/pony-trainer/models/Qwen3.5-0.8B
export PEGAINFER_TEST_MODEL_REVISION=2fc06364715b967f1860aea9cf38778875588b17
export PEGAINFER_CUDA_SM=89 PEGAINFER_TRITON_PYTHON=/usr/bin/python3.10
export CUDA_VISIBLE_DEVICES=0
cargo test --release --locked -p pegainfer-qwen35 --features qwen35 \
--test hf_golden_gate -- --exact --nocapture --test-threads 1 \
pega_logprobs_match_hf_golden_within_qwen35_tolerance
cargo test --release --locked -p pegainfer-qwen35 --features qwen35 \
--test hf_golden_gate -- --exact --nocapture --test-threads 1 \
pega_logprobs_match_hf_long_golden_within_qwen35_tolerance
cargo test --release --locked -p pegainfer-qwen35 --features qwen35 \
--test e2e_scheduler -- --exact --nocapture --test-threads 1 \
test_e2e_qwen35_scheduler|
This pull request has merge conflicts with main. Please rebase it so it can be reviewed and merged. |
Signed-off-by: Feathbow <feathbow@gmail.com>
… it to one bf16 ulp Signed-off-by: Feathbow <feathbow@gmail.com>
…s as future work Signed-off-by: Feathbow <feathbow@gmail.com>
Commit attribution noticeThis pull request contains commits attributed to an account other than the PR author @sysuTobo without a verified signature from that account: If you are mentioned above and did not take part in this pull request, please say so here. Attributing a commit to someone else normally requires that person to be involved; maintainers should treat unacknowledged attribution as a review blocker. This check never fails the build. |
|
We pushed three commits to this branch to close the review points, a merge of main, a test fix and a docs fix.
We checked the test by breaking the conv decode kernel on purpose.
Verification at the new head on two sm_90 GPUs with CUDA 13.1.
|
Summary
The Qwen3.8-Flash-Next (
qwen4_exp) line has 36 Gated DeltaNet layers whose geometry(16 key / 48 value heads at head dim 128, 4-tap depthwise conv) is identical to Qwen3.5's,
so its decode path reuses Qwen3.5's GDN and conv1d kernels instead of growing a second
implementation.
The FFI was already shared; the host-side wrapper — argument marshalling, shape
assertions, state-pointer handling — lived only inside
pegainfer-qwen35, which isentirely feature-gated. This PR moves that wrapper and the three kernel limits it asserts
into the kernels crate's existing Qwen3.5 ops module, beside the HD256 wrappers that
already live there, so both lines reach one owner through
pegainfer_kernels::ops. It addsno feature to
pegainfer-core, no new crate and no second copy, and leaves the shippedline's call sites and compiled kernel set unchanged.
Commits
cea0b149— promote the wrappers, repoint Qwen3.5 at them, and add a real-weightcomponent test behind a qwen4 device feature.
1397a754— review response: move the wrappers intopegainfer-kernels/src/ops/qwen35.rs(under the existing kernels
qwen35feature), drop the component test and the qwen4device feature with it, correct the docs, and check the now-public wrapper's qkv width
and
dt_bias/A_loglengths against the head counts.47cd8514— add a GPU unit test forconv1d_decode_batch_intoat the production channelwidth, assert the GDN wrapper's head-group invariant, and correct the qwen4 wording about
the CPU-only constant mirror.
The final diff is 9 files, +339/−112.
pegainfer-kernels/src/ops/qwen35.rsdt_bias/A_loglengths, and the key/value head-group invariantpegainfer-kernels/src/ops.rsqwen35featurepegainfer-qwen35/src/recurrent.rspegainfer-qwen35/src/ops.rspegainfer_kernels::opspegainfer-qwen35/src/config/{mod,model}.rspegainfer-qwen4/src/{lib,config}.rsdocs/models/qwen4/bring-up.mdWhy the reuse is sound
[num_value_heads, key_dim, val_dim]f32 with the value dim contiguous(FLA
[H, K, V]), addressed through a device array of per-request pointers; both linesallocate f32 state (this line's config asserts
mamba_ssm_dtype: float32).geometry and the kernels' baked-in limits (
GDR_KEY_DIM/GDR_VAL_DIM,LINEAR_CONV_MAX_KERNEL_DIM). Head counts are runtime arguments, so 48 value heads needsno rebuild.
model.language_model.layers.N.prefix.Why this owner
csrc/qwen35/{gated_delta_rule,conv1d}.cubehind the sameqwen35feature aspegainfer-kernels/src/ops/qwen35.rs's HD256 wrappers.pegainfer-qwen35already imports that module directly; every kernel-owning model cratedoes the same (gemma4, dsv2-lite, glm52, k3, kimi-k2, qwen3), and feat(qwen4): score QSA token-indexer blocks in native CUDA #1160 puts qwen4's
device ops in
pegainfer-kernels/src/ops/qwen4.rsfor the same reason.pegainfer-corehas no model feature today; promoting the wrapper there would have addedthe first one and a second owner. Moving into the existing module needs no change to
pegainfer-kernels/{Cargo.toml,build.rs}: the feature exists and thecsrc/qwen35pathfilter already covers the sources.
What is proven, and what is not
Pinned without a GPU:
1658 headers (
the_contract_is_the_frozen_checkpoint);constants;
The mirror is the one deliberate exception to the single-owner rule: qwen4's config must
run with no device, so it cannot import the kernels' constants. Nothing binds the mirror to
the kernels until slice B wires the device path, and this line does not serve until then.
Not proven here: the reuse on this line's activations. The component test from
cea0b149loaded layer 0's real tensors and drove the kernels, but it compared them with a
transcription of the same kernels from a zero state (a dropped feedback term would still
pass), and its relative-error reduction degraded to an absolute bound below 1 and skipped
NaN. It was removed rather than presented as a reuse proof. The first correct prefill and
decode through this line's own graph is #1103's M0 exit (slice B, #1127), where the GDN
block gets a reference to compare against (
transformers' recurrence, not a kerneltranscription).
Verification
All commands run from the PR worktree; the CUDA ones inside
docker/dev.sh(
pegainfer-dev:cu132, CUDA 12.9.1, Triton 3.7.1). GPU is a shared RTX 5060 Laptop(sm_120, 8 GB) — neither support-matrix row.
cargo fmt --all -- --check— clean.cargo test --release --locked -p pegainfer-qwen4— 48 passed (default, CUDA-free).cargo clippy --locked -p pegainfer-qwen35 -p pegainfer-kernels --features qwen35 --all-targets -- -D warnings— clean.cargo test --release --locked -p pegainfer-qwen35 --features qwen35 --lib recurrent::tests::conv1d_decode_batch_matches_reference_steps— ok (10240 channels, 4 steps, 3 slots with distinct states; output within one bf16 ulp, state bit-exact).cargo test --release --locked -p pegainfer-qwen35 --features qwen35 --lib recurrent::tests::gdr_decode_batch_matches_single_slot_reference— ok (16K/48V@128, batch 3).47cd8514: all jobs pass, including Qwen3.5 CUDA Clippy (sm_80), CPU Clippy, CPU unit tests, DCO and formatting.Not runnable here: qwen35's
hf_golden_gate/e2e_schedulerneed a Qwen3.5 checkpoint;this host has the Qwen3.8-Flash-Next partial shards and no Qwen3.5 weights. The moved
paths are covered by the two unit tests above.
Refs #1103