spark_model/layers/qwen3_ssm/
hc.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2
3//! mHC on the GDN layer.
4//!
5//! Qwen3.8-Flash-Next carries a `hc_mult`-wide residual highway on ALL 48
6//! layers, 36 of which are GDN. DeepSeek-V4 — the model Atlas built mHC for —
7//! is all-attention, so `Qwen3SsmLayer` never needed to know about it.
8//!
9//! The forward paths live in `trait_prefill_hc.rs` and `trait_decode_hc.rs`;
10//! `trait_layer.rs` routes to them when `hc` is present. What is left here is
11//! attachment and the one refusal that remains.
12
13use super::Qwen3SsmLayer;
14impl Qwen3SsmLayer {
15    /// Attach mHC weights. Both concrete layer types carry them on this
16    /// model: the 12 full-attention layers are `Qwen3AttentionLayer`, the 36
17    /// GDN layers are this one.
18    pub fn set_hc_weights(&mut self, hc: crate::layers::qwen3_attention::HcWeights) {
19        self.hc = Some(hc);
20    }
21
22    /// Attach the PLE n-gram injection to this layer. Exactly one model layer
23    /// carries it.
24    pub fn set_ple(&mut self, ple: crate::layers::ple::PleLayer) {
25        self.ple = Some(ple);
26    }
27
28    /// Refuse the batched and multi-sequence decode paths while the highway
29    /// is live.
30    ///
31    /// Those paths keep their own residual bookkeeping, which the highway
32    /// replaces — running them would add each block output to the residual a
33    /// second time. v1 is C=1 only on this model (Avarok #753), and refusing
34    /// is the point: a batched GDN step on an unmixed stream produces
35    /// plausible, wrong activations with nothing in the log.
36    ///
37    /// This is what is LEFT of the blanket `ensure_no_unwired_hc` guard —
38    /// prefill and single-token decode now run the highway rather than
39    /// refusing it.
40    pub(crate) fn refuse_batched_under_hc(&self, path: &str) -> anyhow::Result<()> {
41        anyhow::ensure!(
42            self.hc.is_none(),
43            "qwen3_ssm::{path}: the mHC highway has no batched GDN path yet. \
44             This model serves at concurrency 1; the batched paths maintain \
45             their own residual, which the highway replaces, so running them \
46             would count every block output twice. Avarok #753 item B."
47        );
48        Ok(())
49    }
50}