spark_model/
lib.rs

1// SPDX-License-Identifier: AGPL-3.0-only
2
3#![deny(warnings)]
4#![deny(clippy::all)]
5// Kernel-launch helpers and trait-impl wide signatures legitimately exceed
6// clippy's 7-argument default. The same goes for the indexing-loop patterns
7// that mirror the kernel grids we dispatch.
8#![allow(clippy::too_many_arguments)]
9#![allow(clippy::needless_range_loop)]
10// Some FP/integer special-case branches return the same value but have
11// distinct semantic meanings (NaN vs zero, etc.). Audit shows these are
12// intentional.
13#![allow(clippy::if_same_then_else)]
14// The HSS / disk-spill plumbing threads `Vec<u32>` through trait methods so
15// callers can grow them in place; converting to slices breaks the contract.
16#![allow(clippy::ptr_arg)]
17// HF safetensors index tuples are wide on purpose.
18#![allow(clippy::type_complexity)]
19
20pub mod engine;
21pub mod factory;
22pub mod forward;
23pub mod kimi_k3;
24pub mod layer;
25pub mod layers;
26pub mod lora;
27pub mod mistral_loader;
28pub mod model;
29pub mod mtp_layout;
30pub mod precision_schedule;
31pub mod preflight;
32pub mod quant_format;
33mod rank_agree;
34pub mod seq_state_reserve;
35pub mod speculative;
36pub mod ssm_reserve;
37pub mod tp_shard;
38pub mod traits;
39pub mod video_decode_ffmpeg;
40pub mod video_preprocess;
41pub mod vision_item;
42pub mod vision_preprocess;
43/// Marconi snapshot-restore threshold: the shipped default, and the setter
44/// `spark serve` uses to pin it from `--marconi-min-tokens` before anything
45/// reads it. Re-exported rather than making `model::mtp_carry` public — the
46/// rest of that module is internal.
47pub use model::mtp_carry::{DEFAULT_MARCONI_MIN_TOKENS, set_marconi_min_tokens};
48pub use vision_item::VisionItem;
49
50pub mod weight_loader;
51pub mod weight_map;
52
53/// True when the checkpoint ships **HF-vanilla** RMSNorm weights — i.e. the norm
54/// weight is used as `out = x * w / rms`, not Qwen3-Next's offset-from-1
55/// `out = x * (1 + w) / rms`.
56///
57/// Such a model must load its norm weights **exactly** and dispatch
58/// `rms_norm_vanilla`. The alternative — pre-subtracting 1.0 and storing
59/// `bf16(w - 1)` for the offset kernel — is only lossless when `w ≈ 1`.
60/// DeepSeek-V4's norm weights are ≈ 0.03, so `w - 1 ≈ -0.97`, and BF16's
61/// rounding error there (~1.9e-3 absolute) becomes a **1.8-3.4 % relative error
62/// on the weight itself** once 1 is added back — catastrophic cancellation.
63/// Measured over all 249 V4 norm tensors: up to 19 % on `q_norm`, and 100 %
64/// with sign flips on the compressor norms.
65///
66/// This is an explicit model dispatch, NOT an inference from weight statistics.
67pub fn ships_vanilla_norm_weights(config: &atlas_core::config::ModelConfig) -> bool {
68    model_type_ships_vanilla_norm_weights(&config.model_type)
69}
70
71/// The dispatch predicate itself, on the bare `model_type`, so it is unit-testable
72/// without constructing a full `ModelConfig`.
73pub fn model_type_ships_vanilla_norm_weights(model_type: &str) -> bool {
74    // 🪤 `glm5_next` added 2026-08-27. GLM-5.3's norms are PLAIN `x * rms * w` — the same
75    // trap `glm5next_layer` documents for its per-layer norms. This predicate additionally
76    // picks the kernel for the MODEL-LEVEL final norm (`model/impl_a1.rs`), which is applied
77    // outside any layer, so omitting GLM here silently normalises the final hidden state with
78    // the `(1 + w)` offset and corrupts every token's logits. Nothing about the shapes says so.
79    matches!(
80        model_type,
81        "deepseek_v4" | "laguna" | "glm5_next" | "kimi_k3"
82    )
83}
84
85/// Must chunked prefill run as a SINGLE chunk for this model?
86///
87/// True only for models that reach the chunk-LOCAL MLA prefill in
88/// `qwen3_attention/prefill.rs`, which attends over the current chunk's K/V alone —
89/// multi-chunk there silently corrupts attention output (Mistral-Small-4, 2026-05-01: 8 K
90/// collapses to "The\nThe…").
91///
92/// 🔴 `kv_lora_rank > 0` is a PROXY for that kernel and `glm5_next` breaks it: GLM-5.3 is
93/// MLA (rank 512) but prefills through `Glm5NextLayer::prefill`, a per-token walk that
94/// attends the whole paged prefix at each absolute position — chunk boundaries are
95/// invisible to it. Answering true capped every GLM prompt at `2 × --max-prefill-tokens`,
96/// because `prefill_a_step` splits the FIRST chunk at the cap regardless and this gate then
97/// made the remainder one unsplit chunk the buffer arena refused. ANOMALIES A61.
98pub fn requires_single_chunk_prefill(model_type: &str, kv_lora_rank: usize) -> bool {
99    kv_lora_rank > 0 && model_type != "glm5_next"
100}
101
102#[cfg(test)]
103mod single_chunk_prefill_tests {
104    use super::requires_single_chunk_prefill as single;
105
106    /// GLM-5.3 is MLA and must still be chunked — that is the whole of A61.
107    #[test]
108    fn glm5_next_is_mla_but_chunks_fine() {
109        assert!(!single("glm5_next", 512));
110        // Every other MLA family keeps the single-chunk guard.
111        assert!(single("deepseek_v4", 512));
112        assert!(single("mistral", 512));
113        // Non-MLA models were never gated.
114        assert!(!single("qwen3_5_moe", 0));
115    }
116}
117
118#[cfg(test)]
119mod norm_convention_tests {
120    use super::model_type_ships_vanilla_norm_weights as vanilla;
121
122    /// Only explicitly listed model families take the vanilla path. Every
123    /// other family keeps the offset-from-1 convention it was validated under.
124    #[test]
125    fn vanilla_norm_models_are_explicit() {
126        assert!(vanilla("deepseek_v4"));
127        assert!(vanilla("laguna"));
128        // GLM-5.3's norms are plain; the final norm is applied outside any layer.
129        assert!(vanilla("glm5_next"));
130        assert!(vanilla("kimi_k3"));
131        for other in [
132            "qwen3_next",
133            "qwen3_5_moe",
134            "qwen3_moe",
135            "deepseek_v3",
136            "llama",
137            "mistral",
138            "nemotron",
139            "",
140        ] {
141            assert!(!vanilla(other), "{other} must keep offset-from-1 semantics");
142        }
143    }
144}