Skip to main content

katgpt_types/
config.rs

1//! Transformer Config + InferenceOverrides + kv_dim.
2
3use super::*;
4
5// ---------------------------------------------------------------------------
6// Config
7// ---------------------------------------------------------------------------
8
9/// Transformer model configuration — superset of both katgpt-rs and riir-engine.
10///
11/// Fields are ordered by descending alignment to minimize padding:
12/// usize/u64 → f64 → enums (usize-discriminant) → f32 → Vec → u16 → u8/bool.
13#[derive(Clone)]
14pub struct Config {
15    // --- usize / pointer-sized fields (8-byte aligned) ---
16    pub vocab_size: usize,
17    pub block_size: usize,
18    pub n_embd: usize,
19    pub n_head: usize,
20    pub head_dim: usize,
21    pub mlp_hidden: usize,
22    pub n_layer: usize,
23    pub n_kv_head: usize,
24    pub bos_token: usize,
25    pub draft_lookahead: usize,
26    pub tree_budget: usize,
27    pub parallel_threshold: usize,
28    pub lora_rank: usize,
29    pub early_exit_patience: usize,
30    pub mtp_activation_threshold: usize,
31    pub mtp_cluster_vocab_threshold: usize,
32    pub mtp_shared_kv_prompt_threshold: usize,
33    pub mtp_cluster_size: usize,
34    /// Minimum expected output tokens for MTP speculative decoding.
35    /// If remaining tokens < this threshold, MTP is skipped (single-token path).
36    /// Prevents MoE overhead on short texts (Plan 117 Phase 2).
37    pub mtp_min_output_tokens: usize,
38    /// Top-K cluster selection for clustered LM head (Plan 117 T20).
39    /// When K > 1, compute logits for tokens in top-K clusters instead of just top-1.
40    /// Default 1 = backward compatible (single cluster = current behavior).
41    pub mtp_cluster_topk: usize,
42    pub mask_token: usize,
43    pub sp_kv_window: usize,
44    pub sp_kv_predictor_hidden: usize,
45    pub width_rollouts: usize,
46    pub d2f_block_size: usize,
47    /// Number of last layers to sum before LM head. 0 = disabled (standard).
48    /// (Plan 104: Research 68)
49    pub mls_layers: usize,
50
51    // --- f64 (8-byte aligned) ---
52    pub rms_norm_eps: f64,
53
54    // --- f32 (4-byte aligned) ---
55    pub sp_kv_predictor_lr_mult: f32,
56    pub temperature: f32,
57    pub lora_alpha: f32,
58    pub lora_dropout: f32,
59    // Screening Pruner (Plan 021)
60    pub screening_threshold: f32,
61    // Sparse MLP (Plan 022)
62    pub sparse_threshold: f32,
63    // Early exit (Plan 026: AutoTTS)
64    pub early_exit_gap: f32,
65    pub hla_decay: f32,
66    pub rope_theta: f32,
67    pub attn_logit_softcapping: f32,
68    pub final_logit_softcapping: f32,
69    pub sp_kv_threshold: f32,
70    pub early_stop_threshold: f32,
71    // Parallax Attention (Plan 135: Parameterized Local Linear Attention)
72    /// Parallax covariance correction gate scale. 0.0 = disabled (pure softmax),
73    /// 1.0 = full correction. Only meaningful when `parallax_attn` feature is enabled
74    /// and R projection weights are loaded.
75    pub parallax_gate_scale: f32,
76    /// Desperation score threshold for emotion-aware session flagging (Plan 162 T12).
77    /// When the mean desperation projection exceeds this value, `is_desperate_session()` returns true.
78    /// Default: 0.5 (moderate desperation). Range: [0.0, 1.0].
79    pub emotion_desperation_threshold: f32,
80
81    // --- Vec (pointer-sized, 8-byte aligned) ---
82    pub lora_targets: Vec<String>,
83
84    // --- #[repr(u8)] enums (1-byte) + bool fields (1-byte), tail-packed ---
85    // HLA Attention (Plan 057: Higher-order Linear Attention)
86    pub hla_mode: HlaMode,
87    // Gemma 2 architecture fields (Plan 087)
88    pub model_arch: ModelArchitecture,
89    // D2F Discrete Diffusion Forcing (Plan 066)
90    pub attention_mode: AttentionMode,
91    // EqR Convergence Selection (Plan 119)
92    pub convergence_selector: ConvergenceSelector,
93    // LT2 Looped Inference Pipeline (Plan 108, Research 73)
94    pub loop_mode: LoopMode,
95    pub hybrid_pattern: HybridPattern,
96    // Any-Time LT2 Dispatch (Issue 035, Research 273 — ELT arXiv:2604.09168).
97    // `loop_min` = floor for elastic override (refuse exit below this).
98    // `loop_max` = trained max loop count; 0 = sentinel meaning "derive from
99    //   loop_mode" (i.e. use WeightShared.loop_count).
100    // Hard ceiling for elastic over-iteration is `2 × loop_max` (ELT §1.5:
101    // modest over-looping beyond L_max is regularized by training; cap at 2×
102    // to prevent runaway). Both default to 0 = "derive from loop_mode".
103    pub loop_min: usize,
104    pub loop_max: usize,
105    pub weight_dtype: WeightDtype,
106    pub hla_normalize: bool,
107    pub rms_norm_offset: bool,
108    pub tied_embeddings: bool,
109    pub use_rope: bool,
110    pub post_norm: bool,
111    pub gated_attn: bool,
112    /// Whether W_R starts zeroed (true = recover exact softmax at init).
113    pub parallax_zero_init: bool,
114
115    // --- Loop Stability Fix (Plan 428, Research 414) ---
116    /// Inter-loop stabilization mode for weight-shared looped inference.
117    /// `None` = byte-identical to pre-Plan-428 behavior (zero cost).
118    /// `InterLoopNorm` = normalize hidden state between loop iterations.
119    /// Only effective when `loop_mode` is `WeightShared` and `loop_count > 1`.
120    #[cfg(feature = "loop_stability_fix")]
121    pub loop_stability_mode: super::LoopStabilityMode,
122
123    // --- Hydra Adaptive Layer Budget (Research 148, Plan 165) ---
124    /// Per-layer Hydra importance profiles. Empty = disabled.
125    /// Populated from calibration data via `calibrate_profiles()`.
126    #[cfg(feature = "hydra_budget")]
127    pub hydra_profiles: Vec<super::HydraLayerProfile>,
128
129    // --- DeltaNet Inference (Plan 182: Luce Megakernel Distill) ---
130    /// Per-layer type map: DeltaNet vs standard Attention.
131    /// Length = n_layer. Empty = all layers are Attention (backward compatible).
132    /// Only used when model_arch = QwenDeltaNet.
133    #[cfg(feature = "deltanet_inference")]
134    pub layer_types: Vec<DeltaNetLayerType>,
135    /// Depthwise conv kernel size for DeltaNet layers (typically 4).
136    #[cfg(feature = "deltanet_inference")]
137    pub deltanet_conv_kernel_size: usize,
138    /// Recurrence state dimension per head (key_dim * value_dim, typically 128*128 = 16384).
139    #[cfg(feature = "deltanet_inference")]
140    pub deltanet_state_dim: usize,
141    /// Linear attention key/value head dimension (128 for Qwen 3.5).
142    /// Separate from `head_dim` which refers to full attention heads.
143    #[cfg(feature = "deltanet_inference")]
144    pub deltanet_linear_head_dim: usize,
145    /// Number of linear attention key heads (16 for Qwen 3.5).
146    /// Separate from `n_head` which refers to full attention heads.
147    #[cfg(feature = "deltanet_inference")]
148    pub deltanet_linear_n_heads: usize,
149    /// Number of linear attention value heads (16 for Qwen 3.5).
150    /// Usually equals `deltanet_linear_n_heads`.
151    #[cfg(feature = "deltanet_inference")]
152    pub deltanet_linear_n_value_heads: usize,
153
154    // --- RiM Reasoning Buffer Slots (Plan 172, Research 192) ---
155    /// Number of reasoning buffer blocks (K in RiM paper). 0 = disabled.
156    #[cfg(feature = "rim_slots")]
157    pub rim_block_count: usize,
158    /// Tokens per buffer block (M in RiM paper). Default 2.
159    ///
160    /// The M=2 default suits the RiM paper's pause-token use case. For reasoning
161    /// tasks (LOTUS-style latent CoT), LOTUS Table 7 proves an M≥5 floor: M=1→5
162    /// is a +17.8pp cliff on GSM8K and quality saturates at M≥25. Callers
163    /// enabling RiM for reasoning should set M≥5 (ideally ≥25); see
164    /// `.issues/156` T1 and `.research/442` §2.4.
165    #[cfg(feature = "rim_slots")]
166    pub rim_tokens_per_block: usize,
167    /// Token ID used for buffer positions (default: bos_token, reused as buffer).
168    #[cfg(feature = "rim_slots")]
169    pub rim_buffer_token: usize,
170
171    // --- Wall Attention (Plan 173) ---
172    /// Wall attention config. None = use RoPE/fallback.
173    #[cfg(feature = "wall_attention")]
174    pub wall_config: Option<WallConfig>,
175
176    // --- Collapse-Aware Adaptive Thinking (Plan 212) ---
177    /// Per-instance adaptive budget for collapse-aware thinking.
178    #[cfg(feature = "collapse_aware_thinking")]
179    pub collapse_budget: ThinkingBudget,
180
181    // --- NextLat Belief-State Speculative Drafter (Plan 217) ---
182    /// Path to `nextlat.bin` MLP weights. None = random init.
183    #[cfg(feature = "belief_drafter")]
184    pub belief_drafter_path: Option<String>,
185    /// Entropy threshold for belief drafter variable-length stopping.
186    /// Lower = more conservative drafts. Higher = more aggressive.
187    /// Default: 2.0. Only used when `belief_drafter` feature is enabled.
188    #[cfg(feature = "belief_drafter")]
189    pub belief_drafter_entropy_threshold: f32,
190}
191
192impl Config {
193    /// Compute the effective loop count for `forward_looped`, applying an
194    /// optional elastic override clamped to `[loop_min, 2×loop_max]`.
195    ///
196    /// (Issue 035, Research 273 — ELT arXiv:2604.09168 Any-Time inference.)
197    ///
198    /// - `elastic_override = None` → use `loop_mode`'s natural loop count
199    ///   (byte-identical to pre-Issue-035 behavior).
200    /// - `elastic_override = Some(L)` with `LoopMode::WeightShared` → clamp L
201    ///   to `[max(loop_min, 1), 2 × max(loop_max, base)]`.
202    ///   - Below `loop_min` (default 1): clamped up. ELT §1.4 establishes a
203    ///     minimum depth for representational capacity (`1N × 32L` collapsed
204    ///     to FID 10.30 vs 2.83 for `16N × 2L`).
205    ///   - Above `2 × loop_max`: clamped down. ELT §1.5 shows modest
206    ///     over-looping beyond L_max is regularized (UCF-101 peak FVD at L=6
207    ///     with L_max=4), but quality eventually deteriorates — 2× is the cap.
208    /// - `elastic_override = Some(_)` with `LoopMode::None` or `TrainingFree` →
209    ///   refused (returns base); there is no weight-shared loop to elastically
210    ///   exit from.
211    ///
212    /// `loop_max == 0` is a sentinel meaning "derive from `loop_mode`" (use
213    /// `WeightShared.loop_count`). This keeps the 12 existing Config
214    /// constructors unchanged in semantics — they default to `loop_max: 0`
215    /// which resolves to the natural loop count.
216    #[inline]
217    pub fn effective_loop_count(&self, elastic_override: Option<usize>) -> usize {
218        let base = match self.loop_mode {
219            LoopMode::WeightShared { loop_count } => loop_count,
220            LoopMode::None | LoopMode::TrainingFree => 1,
221        };
222        let requested = match elastic_override {
223            None => return base,
224            Some(o) => o,
225        };
226        // Refuse elastic override when there's no weight-shared loop to exit.
227        if !matches!(self.loop_mode, LoopMode::WeightShared { .. }) {
228            return base;
229        }
230        let lo = self.loop_min.max(1);
231        let max_base = if self.loop_max == 0 {
232            base
233        } else {
234            self.loop_max
235        };
236        let hi = max_base.max(base).max(lo);
237        let hard_cap = 2 * hi;
238        requested.clamp(lo, hard_cap)
239    }
240
241    /// Micro GPT config matching [talos-vs-macbook](https://github.com/AlexCheema/talos-vs-macbook) reference:
242    /// vocab=27, block=16, n_layer=1, n_head=4, n_embd=16, head_dim=4,
243    /// RMSNorm (no learnable gain), ReLU MLP (4x), no biases, untied lm_head.
244    pub fn micro() -> Self {
245        Self {
246            vocab_size: 27,
247            block_size: 16,
248            n_embd: 16,
249            n_head: 4,
250            head_dim: 4,
251            mlp_hidden: 64,
252            n_layer: 1,
253            n_kv_head: 4,
254            bos_token: 26,
255            temperature: 0.5,
256            draft_lookahead: 8,
257            tree_budget: 16,
258            parallel_threshold: 128,
259            lora_rank: 4,
260            lora_alpha: 8.0,
261            lora_dropout: 0.0,
262            lora_targets: Vec::new(),
263            screening_threshold: 0.0,
264            sparse_threshold: 0.8,
265            early_exit_patience: 0,
266            early_exit_gap: 0.0,
267            mtp_activation_threshold: usize::MAX,
268            mtp_cluster_vocab_threshold: usize::MAX,
269            mtp_shared_kv_prompt_threshold: usize::MAX,
270            mtp_cluster_size: 512,
271            mtp_min_output_tokens: usize::MAX,
272            mtp_cluster_topk: 1,
273            hla_mode: HlaMode::Standard,
274            hla_normalize: false,
275            hla_decay: 1.0,
276            model_arch: ModelArchitecture::Generic,
277            rms_norm_eps: 1e-5,
278            rms_norm_offset: false,
279            tied_embeddings: false,
280            use_rope: false,
281            rope_theta: 10000.0,
282            post_norm: false,
283            attn_logit_softcapping: 0.0,
284            final_logit_softcapping: 0.0,
285            weight_dtype: WeightDtype::F32,
286            mask_token: 0,
287            attention_mode: AttentionMode::Causal,
288            sp_kv_window: 128,
289            sp_kv_threshold: 0.5,
290            sp_kv_predictor_hidden: 0,
291            sp_kv_predictor_lr_mult: 5.0,
292            width_rollouts: 1,
293            early_stop_threshold: 0.0,
294            convergence_selector: ConvergenceSelector::default(),
295            d2f_block_size: 8,
296            mls_layers: 0,
297            loop_mode: LoopMode::None,
298            hybrid_pattern: HybridPattern::Uniform,
299            loop_min: 0,
300            loop_max: 0,
301            gated_attn: false,
302            parallax_gate_scale: 0.0,
303            emotion_desperation_threshold: 0.5,
304            parallax_zero_init: true,
305            #[cfg(feature = "loop_stability_fix")]
306            loop_stability_mode: super::LoopStabilityMode::None,
307            #[cfg(feature = "hydra_budget")]
308            hydra_profiles: Vec::new(),
309            #[cfg(feature = "deltanet_inference")]
310            layer_types: Vec::new(),
311            #[cfg(feature = "deltanet_inference")]
312            deltanet_conv_kernel_size: 0,
313            #[cfg(feature = "deltanet_inference")]
314            deltanet_state_dim: 0,
315            #[cfg(feature = "deltanet_inference")]
316            deltanet_linear_head_dim: 0,
317            #[cfg(feature = "deltanet_inference")]
318            deltanet_linear_n_heads: 0,
319            #[cfg(feature = "deltanet_inference")]
320            deltanet_linear_n_value_heads: 0,
321            #[cfg(feature = "rim_slots")]
322            rim_block_count: 0,
323            #[cfg(feature = "rim_slots")]
324            rim_tokens_per_block: 2,
325            #[cfg(feature = "rim_slots")]
326            rim_buffer_token: 0,
327            #[cfg(feature = "wall_attention")]
328            wall_config: None,
329            #[cfg(feature = "collapse_aware_thinking")]
330            collapse_budget: ThinkingBudget::default(),
331            #[cfg(feature = "belief_drafter")]
332            belief_drafter_path: None,
333            #[cfg(feature = "belief_drafter")]
334            belief_drafter_entropy_threshold: 2.0,
335        }
336    }
337
338    /// Micro config with LoRA defaults (Plan 008).
339    pub fn micro_lora() -> Self {
340        let mut c = Self::micro();
341        c.lora_rank = 4;
342        c.lora_alpha = 8.0;
343        c.lora_dropout = 0.0;
344        c.lora_targets = vec![
345            "q".into(),
346            "k".into(),
347            "v".into(),
348            "o".into(),
349            "mlp1".into(),
350            "mlp2".into(),
351        ];
352        c
353    }
354
355    /// Micro config for Discrete Diffusion Language Model training (Plan 068: D2F).
356    /// Bidirectional attention by default, mask_token = vocab_size - 1.
357    pub fn micro_dllm() -> Self {
358        Self {
359            attention_mode: AttentionMode::Bidirectional,
360            mask_token: 26,
361            d2f_block_size: 8,
362            ..Self::micro()
363        }
364    }
365
366    /// Game config for Bomberman LoRA training (Plan 041).
367    /// Tiny Transformer for board state → action prediction.
368    /// 10-token vocab: 4 board cells (0-3) + 6 actions (4-9).
369    /// 170-token sequences: 169 board cells + 1 action.
370    /// ~18K params total, ~1.5K LoRA params (rank=4).
371    pub fn game() -> Self {
372        Self {
373            vocab_size: 10,
374            block_size: 170,
375            n_embd: 32,
376            n_head: 4,
377            head_dim: 8,
378            mlp_hidden: 128,
379            n_layer: 1,
380            n_kv_head: 4,
381            bos_token: 0,
382            temperature: 1.0,
383            draft_lookahead: 0,
384            tree_budget: 0,
385            parallel_threshold: 128,
386            lora_rank: 4,
387            lora_alpha: 8.0,
388            lora_dropout: 0.0,
389            lora_targets: vec![
390                "q".into(),
391                "k".into(),
392                "v".into(),
393                "o".into(),
394                "mlp1".into(),
395                "mlp2".into(),
396            ],
397            screening_threshold: 0.0,
398            sparse_threshold: 0.8,
399            early_exit_patience: 0,
400            early_exit_gap: 0.0,
401            mtp_activation_threshold: usize::MAX,
402            mtp_cluster_vocab_threshold: usize::MAX,
403            mtp_shared_kv_prompt_threshold: usize::MAX,
404            mtp_cluster_size: 512,
405            mtp_min_output_tokens: usize::MAX,
406            mtp_cluster_topk: 1,
407            hla_mode: HlaMode::Standard,
408            hla_normalize: false,
409            hla_decay: 1.0,
410            model_arch: ModelArchitecture::Generic,
411            rms_norm_eps: 1e-5,
412            rms_norm_offset: false,
413            tied_embeddings: false,
414            use_rope: false,
415            rope_theta: 10000.0,
416            post_norm: false,
417            attn_logit_softcapping: 0.0,
418            final_logit_softcapping: 0.0,
419            weight_dtype: WeightDtype::F32,
420            mask_token: 0,
421            attention_mode: AttentionMode::Causal,
422            sp_kv_window: 128,
423            sp_kv_threshold: 0.5,
424            sp_kv_predictor_hidden: 0,
425            sp_kv_predictor_lr_mult: 5.0,
426            width_rollouts: 1,
427            early_stop_threshold: 0.0,
428            convergence_selector: ConvergenceSelector::default(),
429            d2f_block_size: 8,
430            mls_layers: 0,
431            loop_mode: LoopMode::None,
432            hybrid_pattern: HybridPattern::Uniform,
433            loop_min: 0,
434            loop_max: 0,
435            gated_attn: false,
436            parallax_gate_scale: 0.0,
437            emotion_desperation_threshold: 0.5,
438            parallax_zero_init: true,
439            #[cfg(feature = "loop_stability_fix")]
440            loop_stability_mode: super::LoopStabilityMode::None,
441            #[cfg(feature = "hydra_budget")]
442            hydra_profiles: Vec::new(),
443            #[cfg(feature = "deltanet_inference")]
444            layer_types: Vec::new(),
445            #[cfg(feature = "deltanet_inference")]
446            deltanet_conv_kernel_size: 0,
447            #[cfg(feature = "deltanet_inference")]
448            deltanet_state_dim: 0,
449            #[cfg(feature = "deltanet_inference")]
450            deltanet_linear_head_dim: 0,
451            #[cfg(feature = "deltanet_inference")]
452            deltanet_linear_n_heads: 0,
453            #[cfg(feature = "deltanet_inference")]
454            deltanet_linear_n_value_heads: 0,
455            #[cfg(feature = "rim_slots")]
456            rim_block_count: 0,
457            #[cfg(feature = "rim_slots")]
458            rim_tokens_per_block: 2,
459            #[cfg(feature = "rim_slots")]
460            rim_buffer_token: 0,
461            #[cfg(feature = "wall_attention")]
462            wall_config: None,
463            #[cfg(feature = "collapse_aware_thinking")]
464            collapse_budget: ThinkingBudget::default(),
465            #[cfg(feature = "belief_drafter")]
466            belief_drafter_path: None,
467            #[cfg(feature = "belief_drafter")]
468            belief_drafter_entropy_threshold: 2.0,
469        }
470    }
471
472    /// Game config for Go 9×9 LoRA training (Plan 078).
473    /// Tiny Transformer for board state → move prediction.
474    /// 85-token vocab: 3 board cells (Empty=0, Black=1, White=2) + 81 positions (3..83) + 1 pass (84).
475    /// 82-token sequences: 81 board cells + 1 action.
476    /// ~16K params total, ~1.3K LoRA params (rank=4).
477    pub fn game_go() -> Self {
478        Self {
479            vocab_size: 85,
480            block_size: 82,
481            n_embd: 32,
482            n_head: 4,
483            head_dim: 8,
484            mlp_hidden: 128,
485            n_layer: 1,
486            n_kv_head: 4,
487            bos_token: 0,
488            temperature: 1.0,
489            draft_lookahead: 0,
490            tree_budget: 0,
491            parallel_threshold: 128,
492            lora_rank: 4,
493            lora_alpha: 8.0,
494            lora_dropout: 0.0,
495            lora_targets: vec![
496                "q".into(),
497                "k".into(),
498                "v".into(),
499                "o".into(),
500                "mlp1".into(),
501                "mlp2".into(),
502            ],
503            screening_threshold: 0.0,
504            sparse_threshold: 0.8,
505            early_exit_patience: 0,
506            early_exit_gap: 0.0,
507            mtp_activation_threshold: usize::MAX,
508            mtp_cluster_vocab_threshold: usize::MAX,
509            mtp_shared_kv_prompt_threshold: usize::MAX,
510            mtp_cluster_size: 512,
511            mtp_min_output_tokens: usize::MAX,
512            mtp_cluster_topk: 1,
513            hla_mode: HlaMode::Standard,
514            hla_normalize: false,
515            hla_decay: 1.0,
516            model_arch: ModelArchitecture::Generic,
517            rms_norm_eps: 1e-5,
518            rms_norm_offset: false,
519            tied_embeddings: false,
520            use_rope: false,
521            rope_theta: 10000.0,
522            post_norm: false,
523            attn_logit_softcapping: 0.0,
524            final_logit_softcapping: 0.0,
525            weight_dtype: WeightDtype::F32,
526            mask_token: 0,
527            attention_mode: AttentionMode::Causal,
528            sp_kv_window: 128,
529            sp_kv_threshold: 0.5,
530            sp_kv_predictor_hidden: 0,
531            sp_kv_predictor_lr_mult: 5.0,
532            width_rollouts: 1,
533            early_stop_threshold: 0.0,
534            convergence_selector: ConvergenceSelector::default(),
535            d2f_block_size: 8,
536            mls_layers: 0,
537            loop_mode: LoopMode::None,
538            hybrid_pattern: HybridPattern::Uniform,
539            loop_min: 0,
540            loop_max: 0,
541            gated_attn: false,
542            parallax_gate_scale: 0.0,
543            emotion_desperation_threshold: 0.5,
544            parallax_zero_init: true,
545            #[cfg(feature = "loop_stability_fix")]
546            loop_stability_mode: super::LoopStabilityMode::None,
547            #[cfg(feature = "hydra_budget")]
548            hydra_profiles: Vec::new(),
549            #[cfg(feature = "deltanet_inference")]
550            layer_types: Vec::new(),
551            #[cfg(feature = "deltanet_inference")]
552            deltanet_conv_kernel_size: 0,
553            #[cfg(feature = "deltanet_inference")]
554            deltanet_state_dim: 0,
555            #[cfg(feature = "deltanet_inference")]
556            deltanet_linear_head_dim: 0,
557            #[cfg(feature = "deltanet_inference")]
558            deltanet_linear_n_heads: 0,
559            #[cfg(feature = "deltanet_inference")]
560            deltanet_linear_n_value_heads: 0,
561            #[cfg(feature = "rim_slots")]
562            rim_block_count: 0,
563            #[cfg(feature = "rim_slots")]
564            rim_tokens_per_block: 2,
565            #[cfg(feature = "rim_slots")]
566            rim_buffer_token: 0,
567            #[cfg(feature = "wall_attention")]
568            wall_config: None,
569            #[cfg(feature = "collapse_aware_thinking")]
570            collapse_budget: ThinkingBudget::default(),
571            #[cfg(feature = "belief_drafter")]
572            belief_drafter_path: None,
573            #[cfg(feature = "belief_drafter")]
574            belief_drafter_entropy_threshold: 2.0,
575        }
576    }
577
578    /// Game config for FFT Tactics Arena LoRA training (Plan 296 T7.3).
579    /// Tiny Transformer for battle state → action prediction.
580    ///
581    /// # Token Layout
582    ///
583    /// - State vocab (values 0..9): team(0-1), class(0-5), hp_bucket(0-7),
584    ///   mp_bucket(0-3), pos_x(0-7), pos_y(0-7), alive(0-1).
585    /// - Action tokens: 10..19 (9 FFT ActionTypes).
586    ///
587    /// Sequence (58 tokens):
588    ///   `[tick, u0_team, u0_class, u0_hp, u0_mp, u0_x, u0_y, u0_alive,
589    ///     u1_..., ..., u7_..., action_token]`
590    ///
591    /// Per-unit = 7 tokens × 8 units = 56, +1 tick +1 action = 58 tokens.
592    /// ~18K params total, ~1.5K LoRA params (rank=4). Comparable to Bomber/Go.
593    pub fn game_fft() -> Self {
594        Self {
595            vocab_size: 19,
596            block_size: 58,
597            n_embd: 32,
598            n_head: 4,
599            head_dim: 8,
600            mlp_hidden: 128,
601            n_layer: 1,
602            n_kv_head: 4,
603            bos_token: 0,
604            temperature: 1.0,
605            draft_lookahead: 0,
606            tree_budget: 0,
607            parallel_threshold: 128,
608            lora_rank: 4,
609            lora_alpha: 8.0,
610            lora_dropout: 0.0,
611            lora_targets: vec![
612                "q".into(),
613                "k".into(),
614                "v".into(),
615                "o".into(),
616                "mlp1".into(),
617                "mlp2".into(),
618            ],
619            screening_threshold: 0.0,
620            sparse_threshold: 0.8,
621            early_exit_patience: 0,
622            early_exit_gap: 0.0,
623            mtp_activation_threshold: usize::MAX,
624            mtp_cluster_vocab_threshold: usize::MAX,
625            mtp_shared_kv_prompt_threshold: usize::MAX,
626            mtp_cluster_size: 512,
627            mtp_min_output_tokens: usize::MAX,
628            mtp_cluster_topk: 1,
629            hla_mode: HlaMode::Standard,
630            hla_normalize: false,
631            hla_decay: 1.0,
632            model_arch: ModelArchitecture::Generic,
633            rms_norm_eps: 1e-5,
634            rms_norm_offset: false,
635            tied_embeddings: false,
636            use_rope: false,
637            rope_theta: 10000.0,
638            post_norm: false,
639            attn_logit_softcapping: 0.0,
640            final_logit_softcapping: 0.0,
641            weight_dtype: WeightDtype::F32,
642            mask_token: 0,
643            attention_mode: AttentionMode::Causal,
644            sp_kv_window: 128,
645            sp_kv_threshold: 0.5,
646            sp_kv_predictor_hidden: 0,
647            sp_kv_predictor_lr_mult: 5.0,
648            width_rollouts: 1,
649            early_stop_threshold: 0.0,
650            convergence_selector: ConvergenceSelector::default(),
651            d2f_block_size: 8,
652            mls_layers: 0,
653            loop_mode: LoopMode::None,
654            hybrid_pattern: HybridPattern::Uniform,
655            loop_min: 0,
656            loop_max: 0,
657            gated_attn: false,
658            parallax_gate_scale: 0.0,
659            emotion_desperation_threshold: 0.5,
660            parallax_zero_init: true,
661            #[cfg(feature = "loop_stability_fix")]
662            loop_stability_mode: super::LoopStabilityMode::None,
663            #[cfg(feature = "hydra_budget")]
664            hydra_profiles: Vec::new(),
665            #[cfg(feature = "deltanet_inference")]
666            layer_types: Vec::new(),
667            #[cfg(feature = "deltanet_inference")]
668            deltanet_conv_kernel_size: 0,
669            #[cfg(feature = "deltanet_inference")]
670            deltanet_state_dim: 0,
671            #[cfg(feature = "deltanet_inference")]
672            deltanet_linear_head_dim: 0,
673            #[cfg(feature = "deltanet_inference")]
674            deltanet_linear_n_heads: 0,
675            #[cfg(feature = "deltanet_inference")]
676            deltanet_linear_n_value_heads: 0,
677            #[cfg(feature = "rim_slots")]
678            rim_block_count: 0,
679            #[cfg(feature = "rim_slots")]
680            rim_tokens_per_block: 2,
681            #[cfg(feature = "rim_slots")]
682            rim_buffer_token: 0,
683            #[cfg(feature = "wall_attention")]
684            wall_config: None,
685            #[cfg(feature = "collapse_aware_thinking")]
686            collapse_budget: ThinkingBudget::default(),
687            #[cfg(feature = "belief_drafter")]
688            belief_drafter_path: None,
689            #[cfg(feature = "belief_drafter")]
690            belief_drafter_entropy_threshold: 2.0,
691        }
692    }
693
694    /// Lightweight draft model for speculative decoding (~4× smaller than target).
695    /// Same vocab/block to share embeddings, but embd=4, heads=2, mlp=16.
696    pub fn draft() -> Self {
697        Self {
698            vocab_size: 27,
699            block_size: 16,
700            n_embd: 4,
701            n_head: 2,
702            head_dim: 2,
703            mlp_hidden: 16,
704            n_layer: 1,
705            n_kv_head: 2,
706            bos_token: 26,
707            temperature: 0.5,
708            draft_lookahead: 8,
709            tree_budget: 16,
710            parallel_threshold: 128,
711            lora_rank: 4,
712            lora_alpha: 8.0,
713            lora_dropout: 0.0,
714            lora_targets: Vec::new(),
715            screening_threshold: 0.0,
716            sparse_threshold: 0.8,
717            early_exit_patience: 0,
718            early_exit_gap: 0.0,
719            mtp_activation_threshold: usize::MAX,
720            mtp_cluster_vocab_threshold: usize::MAX,
721            mtp_shared_kv_prompt_threshold: usize::MAX,
722            mtp_cluster_size: 512,
723            mtp_min_output_tokens: usize::MAX,
724            mtp_cluster_topk: 1,
725            hla_mode: HlaMode::Standard,
726            hla_normalize: false,
727            hla_decay: 1.0,
728            model_arch: ModelArchitecture::Generic,
729            rms_norm_eps: 1e-5,
730            rms_norm_offset: false,
731            tied_embeddings: false,
732            use_rope: false,
733            rope_theta: 10000.0,
734            post_norm: false,
735            attn_logit_softcapping: 0.0,
736            final_logit_softcapping: 0.0,
737            weight_dtype: WeightDtype::F32,
738            mask_token: 0,
739            attention_mode: AttentionMode::Causal,
740            sp_kv_window: 128,
741            sp_kv_threshold: 0.5,
742            sp_kv_predictor_hidden: 0,
743            sp_kv_predictor_lr_mult: 5.0,
744            width_rollouts: 1,
745            early_stop_threshold: 0.0,
746            convergence_selector: ConvergenceSelector::default(),
747            d2f_block_size: 8,
748            mls_layers: 0,
749            loop_mode: LoopMode::None,
750            hybrid_pattern: HybridPattern::Uniform,
751            loop_min: 0,
752            loop_max: 0,
753            gated_attn: false,
754            parallax_gate_scale: 0.0,
755            emotion_desperation_threshold: 0.5,
756            parallax_zero_init: true,
757            #[cfg(feature = "loop_stability_fix")]
758            loop_stability_mode: super::LoopStabilityMode::None,
759            #[cfg(feature = "hydra_budget")]
760            hydra_profiles: Vec::new(),
761            #[cfg(feature = "deltanet_inference")]
762            layer_types: Vec::new(),
763            #[cfg(feature = "deltanet_inference")]
764            deltanet_conv_kernel_size: 0,
765            #[cfg(feature = "deltanet_inference")]
766            deltanet_state_dim: 0,
767            #[cfg(feature = "deltanet_inference")]
768            deltanet_linear_head_dim: 0,
769            #[cfg(feature = "deltanet_inference")]
770            deltanet_linear_n_heads: 0,
771            #[cfg(feature = "deltanet_inference")]
772            deltanet_linear_n_value_heads: 0,
773            #[cfg(feature = "rim_slots")]
774            rim_block_count: 0,
775            #[cfg(feature = "rim_slots")]
776            rim_tokens_per_block: 2,
777            #[cfg(feature = "rim_slots")]
778            rim_buffer_token: 0,
779            #[cfg(feature = "wall_attention")]
780            wall_config: None,
781            #[cfg(feature = "collapse_aware_thinking")]
782            collapse_budget: ThinkingBudget::default(),
783            #[cfg(feature = "belief_drafter")]
784            belief_drafter_path: None,
785            #[cfg(feature = "belief_drafter")]
786            belief_drafter_entropy_threshold: 2.0,
787        }
788    }
789
790    /// Small target model for multi-layer testing.
791    /// vocab=4096, block=256, n_layer=4, n_head=4, n_embd=64, head_dim=16,
792    /// MLP hidden=256.
793    pub fn small_target() -> Self {
794        Self {
795            vocab_size: 4096,
796            block_size: 256,
797            n_embd: 64,
798            n_head: 4,
799            head_dim: 16,
800            mlp_hidden: 256,
801            n_layer: 4,
802            n_kv_head: 4,
803            bos_token: 0,
804            temperature: 0.8,
805            draft_lookahead: 5,
806            tree_budget: 32,
807            parallel_threshold: 128,
808            lora_rank: 4,
809            lora_alpha: 8.0,
810            lora_dropout: 0.0,
811            lora_targets: Vec::new(),
812            screening_threshold: 0.0,
813            sparse_threshold: 0.8,
814            early_exit_patience: 0,
815            early_exit_gap: 0.0,
816            mtp_activation_threshold: 64,
817            mtp_cluster_vocab_threshold: usize::MAX,
818            mtp_shared_kv_prompt_threshold: 128,
819            mtp_cluster_size: 512,
820            mtp_min_output_tokens: 16,
821            mtp_cluster_topk: 1,
822            hla_mode: HlaMode::Standard,
823            hla_normalize: false,
824            hla_decay: 1.0,
825            model_arch: ModelArchitecture::Generic,
826            rms_norm_eps: 1e-5,
827            rms_norm_offset: false,
828            tied_embeddings: false,
829            use_rope: false,
830            rope_theta: 10000.0,
831            post_norm: false,
832            attn_logit_softcapping: 0.0,
833            final_logit_softcapping: 0.0,
834            weight_dtype: WeightDtype::F32,
835            mask_token: 0,
836            attention_mode: AttentionMode::Causal,
837            sp_kv_window: 128,
838            sp_kv_threshold: 0.5,
839            sp_kv_predictor_hidden: 0,
840            sp_kv_predictor_lr_mult: 5.0,
841            width_rollouts: 1,
842            early_stop_threshold: 0.0,
843            convergence_selector: ConvergenceSelector::default(),
844            d2f_block_size: 16,
845            mls_layers: 0,
846            loop_mode: LoopMode::None,
847            hybrid_pattern: HybridPattern::Uniform,
848            loop_min: 0,
849            loop_max: 0,
850            gated_attn: false,
851            parallax_gate_scale: 0.0,
852            emotion_desperation_threshold: 0.5,
853            parallax_zero_init: true,
854            #[cfg(feature = "loop_stability_fix")]
855            loop_stability_mode: super::LoopStabilityMode::None,
856            #[cfg(feature = "hydra_budget")]
857            hydra_profiles: Vec::new(),
858            #[cfg(feature = "deltanet_inference")]
859            layer_types: Vec::new(),
860            #[cfg(feature = "deltanet_inference")]
861            deltanet_conv_kernel_size: 0,
862            #[cfg(feature = "deltanet_inference")]
863            deltanet_state_dim: 0,
864            #[cfg(feature = "deltanet_inference")]
865            deltanet_linear_head_dim: 0,
866            #[cfg(feature = "deltanet_inference")]
867            deltanet_linear_n_heads: 0,
868            #[cfg(feature = "deltanet_inference")]
869            deltanet_linear_n_value_heads: 0,
870            #[cfg(feature = "rim_slots")]
871            rim_block_count: 0,
872            #[cfg(feature = "rim_slots")]
873            rim_tokens_per_block: 2,
874            #[cfg(feature = "rim_slots")]
875            rim_buffer_token: 0,
876            #[cfg(feature = "wall_attention")]
877            wall_config: None,
878            #[cfg(feature = "collapse_aware_thinking")]
879            collapse_budget: ThinkingBudget::default(),
880            #[cfg(feature = "belief_drafter")]
881            belief_drafter_path: None,
882            #[cfg(feature = "belief_drafter")]
883            belief_drafter_entropy_threshold: 2.0,
884        }
885    }
886
887    /// GQA draft config: 8 Q heads, 2 KV heads (4:1 ratio, 4× KV cache reduction).
888    pub fn gqa_draft() -> Self {
889        Self {
890            vocab_size: 4096,
891            block_size: 256,
892            n_embd: 64,
893            n_head: 8,
894            head_dim: 8,
895            mlp_hidden: 256,
896            n_layer: 4,
897            n_kv_head: 2,
898            bos_token: 0,
899            temperature: 0.8,
900            draft_lookahead: 5,
901            tree_budget: 32,
902            parallel_threshold: 128,
903            lora_rank: 4,
904            lora_alpha: 8.0,
905            lora_dropout: 0.0,
906            lora_targets: Vec::new(),
907            screening_threshold: 0.0,
908            sparse_threshold: 0.8,
909            early_exit_patience: 0,
910            early_exit_gap: 0.0,
911            mtp_activation_threshold: 64,
912            mtp_cluster_vocab_threshold: usize::MAX,
913            mtp_shared_kv_prompt_threshold: 128,
914            mtp_cluster_size: 512,
915            mtp_min_output_tokens: 16,
916            mtp_cluster_topk: 1,
917            hla_mode: HlaMode::Standard,
918            hla_normalize: false,
919            hla_decay: 1.0,
920            model_arch: ModelArchitecture::Generic,
921            rms_norm_eps: 1e-5,
922            rms_norm_offset: false,
923            tied_embeddings: false,
924            use_rope: false,
925            rope_theta: 10000.0,
926            post_norm: false,
927            attn_logit_softcapping: 0.0,
928            final_logit_softcapping: 0.0,
929            weight_dtype: WeightDtype::F32,
930            mask_token: 0,
931            attention_mode: AttentionMode::Causal,
932            sp_kv_window: 128,
933            sp_kv_threshold: 0.5,
934            sp_kv_predictor_hidden: 0,
935            sp_kv_predictor_lr_mult: 5.0,
936            width_rollouts: 1,
937            early_stop_threshold: 0.0,
938            convergence_selector: ConvergenceSelector::default(),
939            d2f_block_size: 16,
940            mls_layers: 0,
941            loop_mode: LoopMode::None,
942            hybrid_pattern: HybridPattern::Uniform,
943            loop_min: 0,
944            loop_max: 0,
945            gated_attn: false,
946            parallax_gate_scale: 0.0,
947            emotion_desperation_threshold: 0.5,
948            parallax_zero_init: true,
949            #[cfg(feature = "loop_stability_fix")]
950            loop_stability_mode: super::LoopStabilityMode::None,
951            #[cfg(feature = "hydra_budget")]
952            hydra_profiles: Vec::new(),
953            #[cfg(feature = "deltanet_inference")]
954            layer_types: Vec::new(),
955            #[cfg(feature = "deltanet_inference")]
956            deltanet_conv_kernel_size: 0,
957            #[cfg(feature = "deltanet_inference")]
958            deltanet_state_dim: 0,
959            #[cfg(feature = "deltanet_inference")]
960            deltanet_linear_head_dim: 0,
961            #[cfg(feature = "deltanet_inference")]
962            deltanet_linear_n_heads: 0,
963            #[cfg(feature = "deltanet_inference")]
964            deltanet_linear_n_value_heads: 0,
965            #[cfg(feature = "rim_slots")]
966            rim_block_count: 0,
967            #[cfg(feature = "rim_slots")]
968            rim_tokens_per_block: 2,
969            #[cfg(feature = "rim_slots")]
970            rim_buffer_token: 0,
971            #[cfg(feature = "wall_attention")]
972            wall_config: None,
973            #[cfg(feature = "collapse_aware_thinking")]
974            collapse_budget: ThinkingBudget::default(),
975            #[cfg(feature = "belief_drafter")]
976            belief_drafter_path: None,
977            #[cfg(feature = "belief_drafter")]
978            belief_drafter_entropy_threshold: 2.0,
979        }
980    }
981
982    /// BPE tokenizer config for Rust source code.
983    /// vocab=4096, block=256, n_layer=1, n_head=4, n_embd=32, head_dim=8,
984    /// MLP hidden=128.
985    pub fn bpe() -> Self {
986        Self {
987            vocab_size: 4096,
988            block_size: 256,
989            n_embd: 32,
990            n_head: 4,
991            head_dim: 8,
992            mlp_hidden: 128,
993            n_layer: 1,
994            n_kv_head: 4,
995            bos_token: 1,
996            temperature: 0.8,
997            draft_lookahead: 8,
998            tree_budget: 32,
999            parallel_threshold: 128,
1000            lora_rank: 4,
1001            lora_alpha: 8.0,
1002            lora_dropout: 0.0,
1003            lora_targets: Vec::new(),
1004            screening_threshold: 0.0,
1005            sparse_threshold: 0.8,
1006            early_exit_patience: 0,
1007            early_exit_gap: 0.0,
1008            mtp_activation_threshold: 32,
1009            mtp_cluster_vocab_threshold: 4096,
1010            mtp_shared_kv_prompt_threshold: 64,
1011            mtp_cluster_size: 512,
1012            mtp_min_output_tokens: 16,
1013            mtp_cluster_topk: 8,
1014            hla_mode: HlaMode::Standard,
1015            hla_normalize: false,
1016            hla_decay: 1.0,
1017            model_arch: ModelArchitecture::Generic,
1018            rms_norm_eps: 1e-5,
1019            rms_norm_offset: false,
1020            tied_embeddings: false,
1021            use_rope: false,
1022            rope_theta: 10000.0,
1023            post_norm: false,
1024            attn_logit_softcapping: 0.0,
1025            final_logit_softcapping: 0.0,
1026            weight_dtype: WeightDtype::F32,
1027            mask_token: 0,
1028            attention_mode: AttentionMode::Causal,
1029            sp_kv_window: 128,
1030            sp_kv_threshold: 0.5,
1031            sp_kv_predictor_hidden: 0,
1032            sp_kv_predictor_lr_mult: 5.0,
1033            width_rollouts: 1,
1034            early_stop_threshold: 0.0,
1035            convergence_selector: ConvergenceSelector::default(),
1036            d2f_block_size: 16,
1037            mls_layers: 0,
1038            loop_mode: LoopMode::None,
1039            hybrid_pattern: HybridPattern::Uniform,
1040            loop_min: 0,
1041            loop_max: 0,
1042            gated_attn: false,
1043            parallax_gate_scale: 0.0,
1044            emotion_desperation_threshold: 0.5,
1045            parallax_zero_init: true,
1046            #[cfg(feature = "loop_stability_fix")]
1047            loop_stability_mode: super::LoopStabilityMode::None,
1048            #[cfg(feature = "hydra_budget")]
1049            hydra_profiles: Vec::new(),
1050            #[cfg(feature = "deltanet_inference")]
1051            layer_types: Vec::new(),
1052            #[cfg(feature = "deltanet_inference")]
1053            deltanet_conv_kernel_size: 0,
1054            #[cfg(feature = "deltanet_inference")]
1055            deltanet_state_dim: 0,
1056            #[cfg(feature = "deltanet_inference")]
1057            deltanet_linear_head_dim: 0,
1058            #[cfg(feature = "deltanet_inference")]
1059            deltanet_linear_n_heads: 0,
1060            #[cfg(feature = "deltanet_inference")]
1061            deltanet_linear_n_value_heads: 0,
1062            #[cfg(feature = "rim_slots")]
1063            rim_block_count: 0,
1064            #[cfg(feature = "rim_slots")]
1065            rim_tokens_per_block: 2,
1066            #[cfg(feature = "rim_slots")]
1067            rim_buffer_token: 0,
1068            #[cfg(feature = "wall_attention")]
1069            wall_config: None,
1070            #[cfg(feature = "collapse_aware_thinking")]
1071            collapse_budget: ThinkingBudget::default(),
1072            #[cfg(feature = "belief_drafter")]
1073            belief_drafter_path: None,
1074            #[cfg(feature = "belief_drafter")]
1075            belief_drafter_entropy_threshold: 2.0,
1076        }
1077    }
1078
1079    /// BPE draft model (smaller for speculative decoding).
1080    /// Same vocab/block as bpe(), but embd=16, heads=2, mlp=64.
1081    pub fn bpe_draft() -> Self {
1082        Self {
1083            vocab_size: 4096,
1084            block_size: 256,
1085            n_embd: 16,
1086            n_head: 2,
1087            head_dim: 8,
1088            mlp_hidden: 64,
1089            n_layer: 1,
1090            n_kv_head: 2,
1091            bos_token: 1,
1092            temperature: 0.8,
1093            draft_lookahead: 8,
1094            tree_budget: 32,
1095            parallel_threshold: 128,
1096            lora_rank: 4,
1097            lora_alpha: 8.0,
1098            lora_dropout: 0.0,
1099            lora_targets: Vec::new(),
1100            screening_threshold: 0.0,
1101            sparse_threshold: 0.8,
1102            early_exit_patience: 0,
1103            early_exit_gap: 0.0,
1104            mtp_activation_threshold: 16,
1105            mtp_cluster_vocab_threshold: 4096,
1106            mtp_shared_kv_prompt_threshold: 64,
1107            mtp_cluster_size: 512,
1108            mtp_min_output_tokens: usize::MAX,
1109            mtp_cluster_topk: 1,
1110            hla_mode: HlaMode::Standard,
1111            hla_normalize: false,
1112            hla_decay: 1.0,
1113            model_arch: ModelArchitecture::Generic,
1114            rms_norm_eps: 1e-5,
1115            rms_norm_offset: false,
1116            tied_embeddings: false,
1117            use_rope: false,
1118            rope_theta: 10000.0,
1119            post_norm: false,
1120            attn_logit_softcapping: 0.0,
1121            final_logit_softcapping: 0.0,
1122            weight_dtype: WeightDtype::F32,
1123            mask_token: 0,
1124            attention_mode: AttentionMode::Causal,
1125            sp_kv_window: 128,
1126            sp_kv_threshold: 0.5,
1127            sp_kv_predictor_hidden: 0,
1128            sp_kv_predictor_lr_mult: 5.0,
1129            width_rollouts: 1,
1130            early_stop_threshold: 0.0,
1131            convergence_selector: ConvergenceSelector::default(),
1132            d2f_block_size: 16,
1133            mls_layers: 0,
1134            loop_mode: LoopMode::None,
1135            hybrid_pattern: HybridPattern::Uniform,
1136            loop_min: 0,
1137            loop_max: 0,
1138            gated_attn: false,
1139            parallax_gate_scale: 0.0,
1140            emotion_desperation_threshold: 0.5,
1141            parallax_zero_init: true,
1142            #[cfg(feature = "loop_stability_fix")]
1143            loop_stability_mode: super::LoopStabilityMode::None,
1144            #[cfg(feature = "hydra_budget")]
1145            hydra_profiles: Vec::new(),
1146            #[cfg(feature = "deltanet_inference")]
1147            layer_types: Vec::new(),
1148            #[cfg(feature = "deltanet_inference")]
1149            deltanet_conv_kernel_size: 0,
1150            #[cfg(feature = "deltanet_inference")]
1151            deltanet_state_dim: 0,
1152            #[cfg(feature = "deltanet_inference")]
1153            deltanet_linear_head_dim: 0,
1154            #[cfg(feature = "deltanet_inference")]
1155            deltanet_linear_n_heads: 0,
1156            #[cfg(feature = "deltanet_inference")]
1157            deltanet_linear_n_value_heads: 0,
1158            #[cfg(feature = "rim_slots")]
1159            rim_block_count: 0,
1160            #[cfg(feature = "rim_slots")]
1161            rim_tokens_per_block: 2,
1162            #[cfg(feature = "rim_slots")]
1163            rim_buffer_token: 0,
1164            #[cfg(feature = "wall_attention")]
1165            wall_config: None,
1166            #[cfg(feature = "collapse_aware_thinking")]
1167            collapse_budget: ThinkingBudget::default(),
1168            #[cfg(feature = "belief_drafter")]
1169            belief_drafter_path: None,
1170            #[cfg(feature = "belief_drafter")]
1171            belief_drafter_entropy_threshold: 2.0,
1172        }
1173    }
1174
1175    /// Gemma 2 2B config for real model inference (Plan 087).
1176    /// hidden_size=2304, intermediate_size=9216, vocab=256000, layers=26,
1177    /// heads=8, kv_heads=4, head_dim=256, max_seq=8192.
1178    /// Uses GeGLU MLP, RoPE, RMSNorm offset, tied embeddings, post-norm.
1179    pub fn gemma2_2b() -> Self {
1180        Self {
1181            vocab_size: 256000,
1182            block_size: 8192,
1183            n_embd: 2304,
1184            n_head: 8,
1185            head_dim: 256,
1186            mlp_hidden: 9216,
1187            n_layer: 26,
1188            n_kv_head: 4,
1189            bos_token: 2, // Gemma 2 BOS token
1190            temperature: 0.8,
1191            draft_lookahead: 0,
1192            tree_budget: 0,
1193            parallel_threshold: 8192,
1194            lora_rank: 0,
1195            lora_alpha: 1.0,
1196            lora_dropout: 0.0,
1197            lora_targets: Vec::new(),
1198            screening_threshold: 0.0,
1199            sparse_threshold: 0.0,
1200            early_exit_patience: 0,
1201            early_exit_gap: 0.0,
1202            mtp_activation_threshold: 0,
1203            mtp_cluster_vocab_threshold: 256000,
1204            mtp_shared_kv_prompt_threshold: 8192,
1205            mtp_cluster_size: 1024,
1206            mtp_min_output_tokens: 16,
1207            mtp_cluster_topk: 1,
1208            hla_mode: HlaMode::Standard,
1209            hla_normalize: false,
1210            hla_decay: 1.0,
1211            model_arch: ModelArchitecture::Gemma2,
1212            rms_norm_eps: 1e-6,
1213            rms_norm_offset: true,
1214            tied_embeddings: true,
1215            use_rope: true,
1216            rope_theta: 10000.0,
1217            post_norm: true,
1218            attn_logit_softcapping: 50.0,
1219            final_logit_softcapping: 30.0,
1220            weight_dtype: WeightDtype::BF16,
1221            mask_token: 0,
1222            attention_mode: AttentionMode::Causal,
1223            sp_kv_window: 128,
1224            sp_kv_threshold: 0.5,
1225            sp_kv_predictor_hidden: 0,
1226            sp_kv_predictor_lr_mult: 5.0,
1227            width_rollouts: 1,
1228            early_stop_threshold: 0.0,
1229            convergence_selector: ConvergenceSelector::default(),
1230            d2f_block_size: 16,
1231            mls_layers: 0,
1232            loop_mode: LoopMode::None,
1233            hybrid_pattern: HybridPattern::Uniform,
1234            loop_min: 0,
1235            loop_max: 0,
1236            gated_attn: false,
1237            parallax_gate_scale: 0.0,
1238            emotion_desperation_threshold: 0.5,
1239            parallax_zero_init: true,
1240            #[cfg(feature = "loop_stability_fix")]
1241            loop_stability_mode: super::LoopStabilityMode::None,
1242            #[cfg(feature = "hydra_budget")]
1243            hydra_profiles: Vec::new(),
1244            #[cfg(feature = "deltanet_inference")]
1245            layer_types: Vec::new(),
1246            #[cfg(feature = "deltanet_inference")]
1247            deltanet_conv_kernel_size: 0,
1248            #[cfg(feature = "deltanet_inference")]
1249            deltanet_state_dim: 0,
1250            #[cfg(feature = "deltanet_inference")]
1251            deltanet_linear_head_dim: 0,
1252            #[cfg(feature = "deltanet_inference")]
1253            deltanet_linear_n_heads: 0,
1254            #[cfg(feature = "deltanet_inference")]
1255            deltanet_linear_n_value_heads: 0,
1256            #[cfg(feature = "rim_slots")]
1257            rim_block_count: 0,
1258            #[cfg(feature = "rim_slots")]
1259            rim_tokens_per_block: 2,
1260            #[cfg(feature = "rim_slots")]
1261            rim_buffer_token: 0,
1262            #[cfg(feature = "wall_attention")]
1263            wall_config: None,
1264            #[cfg(feature = "collapse_aware_thinking")]
1265            collapse_budget: ThinkingBudget::default(),
1266            #[cfg(feature = "belief_drafter")]
1267            belief_drafter_path: None,
1268            #[cfg(feature = "belief_drafter")]
1269            belief_drafter_entropy_threshold: 2.0,
1270        }
1271    }
1272
1273    /// Config for Qwen 3.5-0.8B hybrid DeltaNet/Attention model (Plan 182).
1274    ///
1275    /// Typical layout: early layers use DeltaNet (linear recurrence, no KV cache),
1276    /// later layers use standard attention. The `layer_types` vec specifies per-layer.
1277    /// If `layer_types` is empty, all layers default to Attention (backward compatible).
1278    #[cfg(feature = "deltanet_inference")]
1279    pub fn qwen_deltanet(n_layer: usize, layer_types: Vec<DeltaNetLayerType>) -> Self {
1280        let n_head = 16;
1281        let head_dim = 128;
1282        let n_embd = n_head * head_dim; // 2048
1283        let mlp_hidden = n_embd * 4; // 8192 (SwiGLU: gate+up = 2× mlp_hidden)
1284        let n_kv_head = n_head; // MHA (no GQA for 0.8B)
1285
1286        Self {
1287            vocab_size: 151936,
1288            block_size: 32768,
1289            n_embd,
1290            n_head,
1291            head_dim,
1292            mlp_hidden,
1293            n_layer,
1294            n_kv_head,
1295            bos_token: 151643, // Qwen BOS
1296            temperature: 0.8,
1297            draft_lookahead: 0,
1298            tree_budget: 0,
1299            parallel_threshold: 8192,
1300            lora_rank: 0,
1301            lora_alpha: 1.0,
1302            lora_dropout: 0.0,
1303            lora_targets: Vec::new(),
1304            screening_threshold: 0.0,
1305            sparse_threshold: 0.0,
1306            early_exit_patience: 0,
1307            early_exit_gap: 0.0,
1308            mtp_activation_threshold: 0,
1309            mtp_cluster_vocab_threshold: 151936,
1310            mtp_shared_kv_prompt_threshold: 32768,
1311            mtp_cluster_size: 1024,
1312            mtp_min_output_tokens: 16,
1313            mtp_cluster_topk: 1,
1314            hla_mode: HlaMode::Standard,
1315            hla_normalize: false,
1316            hla_decay: 1.0,
1317            model_arch: ModelArchitecture::QwenDeltaNet,
1318            rms_norm_eps: 1e-6,
1319            rms_norm_offset: false,
1320            tied_embeddings: false,
1321            use_rope: true,
1322            rope_theta: 10000.0,
1323            post_norm: false,
1324            attn_logit_softcapping: 0.0,
1325            final_logit_softcapping: 0.0,
1326            weight_dtype: WeightDtype::BF16,
1327            mask_token: 0,
1328            attention_mode: AttentionMode::Causal,
1329            sp_kv_window: 128,
1330            sp_kv_threshold: 0.5,
1331            sp_kv_predictor_hidden: 0,
1332            sp_kv_predictor_lr_mult: 5.0,
1333            width_rollouts: 1,
1334            early_stop_threshold: 0.0,
1335            convergence_selector: ConvergenceSelector::default(),
1336            d2f_block_size: 16,
1337            mls_layers: 0,
1338            loop_mode: LoopMode::None,
1339            hybrid_pattern: HybridPattern::Uniform,
1340            loop_min: 0,
1341            loop_max: 0,
1342            gated_attn: false,
1343            parallax_gate_scale: 0.0,
1344            emotion_desperation_threshold: 0.5,
1345            parallax_zero_init: true,
1346            #[cfg(feature = "loop_stability_fix")]
1347            loop_stability_mode: super::LoopStabilityMode::None,
1348            #[cfg(feature = "hydra_budget")]
1349            hydra_profiles: Vec::new(),
1350            layer_types,
1351            deltanet_conv_kernel_size: 4,
1352            deltanet_state_dim: head_dim * head_dim, // 128 × 128 = 16384 per head
1353            deltanet_linear_head_dim: head_dim,
1354            deltanet_linear_n_heads: n_head,
1355            deltanet_linear_n_value_heads: n_kv_head,
1356            #[cfg(feature = "rim_slots")]
1357            rim_block_count: 0,
1358            #[cfg(feature = "rim_slots")]
1359            rim_tokens_per_block: 2,
1360            #[cfg(feature = "rim_slots")]
1361            rim_buffer_token: 0,
1362            #[cfg(feature = "wall_attention")]
1363            wall_config: None,
1364            #[cfg(feature = "collapse_aware_thinking")]
1365            collapse_budget: ThinkingBudget::default(),
1366            #[cfg(feature = "belief_drafter")]
1367            belief_drafter_path: None,
1368            #[cfg(feature = "belief_drafter")]
1369            belief_drafter_entropy_threshold: 2.0,
1370        }
1371    }
1372
1373    /// Validate config consistency. Returns Err with message on invalid config.
1374    pub fn validate(&self) -> Result<(), String> {
1375        if !self.n_head.is_multiple_of(self.n_kv_head) {
1376            return Err(format!(
1377                "n_head ({}) must be divisible by n_kv_head ({})",
1378                self.n_head, self.n_kv_head
1379            ));
1380        }
1381        // Gemma 2 intentionally has q_dim != n_embd (e.g., 8*256=2048 != 2304)
1382        // LLaMA with GQA may also have q_dim != n_embd
1383        // QwenDeltaNet also has q_dim == n_embd but is excluded for forward compat
1384        let arch_exempt = match self.model_arch {
1385            ModelArchitecture::Gemma2 | ModelArchitecture::Llama => true,
1386            _ => {
1387                #[cfg(feature = "deltanet_inference")]
1388                if self.model_arch == ModelArchitecture::QwenDeltaNet {
1389                    // layer_types length must match n_layer when non-empty
1390                    if !self.layer_types.is_empty() && self.layer_types.len() != self.n_layer {
1391                        return Err(format!(
1392                            "layer_types length ({}) must match n_layer ({})",
1393                            self.layer_types.len(),
1394                            self.n_layer
1395                        ));
1396                    }
1397                    // deltanet_state_dim must be head_dim^2
1398                    let expected = self.head_dim * self.head_dim;
1399                    if self.deltanet_state_dim != expected {
1400                        return Err(format!(
1401                            "deltanet_state_dim ({}) must equal head_dim^2 ({})",
1402                            self.deltanet_state_dim, expected
1403                        ));
1404                    }
1405                    true
1406                } else {
1407                    false
1408                }
1409                #[cfg(not(feature = "deltanet_inference"))]
1410                false
1411            }
1412        };
1413        if !arch_exempt && self.n_head * self.head_dim != self.n_embd {
1414            return Err(format!(
1415                "n_head ({}) * head_dim ({}) must equal n_embd ({})",
1416                self.n_head, self.head_dim, self.n_embd
1417            ));
1418        }
1419        if self.n_kv_head * self.head_dim > self.n_embd {
1420            return Err(format!(
1421                "n_kv_head ({}) * head_dim ({}) must not exceed n_embd ({})",
1422                self.n_kv_head, self.head_dim, self.n_embd
1423            ));
1424        }
1425        // MTP thresholds must be consistent (only for Generic arch; Gemma 2 and Llama don't use MTP)
1426        if self.model_arch == ModelArchitecture::Generic && self.mtp_cluster_size == 0 {
1427            return Err("mtp_cluster_size must be > 0".into());
1428        }
1429        if self.mtp_cluster_topk == 0 {
1430            return Err("mtp_cluster_topk must be >= 1".into());
1431        }
1432        Ok(())
1433    }
1434
1435    /// Apply per-domain inference overrides, returning a new Config.
1436    ///
1437    /// Total number of buffer tokens when RiM slots are active: K × M.
1438    /// Returns 0 when disabled (rim_block_count == 0).
1439    #[cfg(feature = "rim_slots")]
1440    #[inline]
1441    pub fn rim_total_buffer_tokens(&self) -> usize {
1442        if self.rim_block_count == 0 {
1443            0
1444        } else {
1445            self.rim_block_count * self.rim_tokens_per_block
1446        }
1447    }
1448
1449    /// Whether RiM buffer slots are active.
1450    #[cfg(feature = "rim_slots")]
1451    #[inline]
1452    pub fn rim_enabled(&self) -> bool {
1453        self.rim_block_count > 0
1454    }
1455
1456    /// Whether Wall Attention is active (Plan 173).
1457    /// True when feature is enabled AND config has wall_config set.
1458    #[cfg(feature = "wall_attention")]
1459    pub fn wall_enabled(&self) -> bool {
1460        self.wall_config.is_some()
1461    }
1462
1463    /// `None` fields are left unchanged; `Some` fields replace the current value.
1464    /// Used by the router to inject domain-specific budgets from TOML config.
1465    pub fn with_overrides(mut self, overrides: &InferenceOverrides) -> Self {
1466        if let Some(v) = overrides.tree_budget {
1467            self.tree_budget = v;
1468        }
1469        if let Some(v) = overrides.draft_lookahead {
1470            self.draft_lookahead = v;
1471        }
1472        if let Some(v) = overrides.parallel_threshold {
1473            self.parallel_threshold = v;
1474        }
1475        if let Some(v) = overrides.screening_threshold {
1476            self.screening_threshold = v;
1477        }
1478        if let Some(v) = overrides.temperature {
1479            self.temperature = v;
1480        }
1481        if let Some(v) = overrides.sparse_threshold {
1482            self.sparse_threshold = v;
1483        }
1484        if let Some(v) = overrides.early_exit_patience {
1485            self.early_exit_patience = v;
1486        }
1487        if let Some(v) = overrides.early_exit_gap {
1488            self.early_exit_gap = v;
1489        }
1490        if let Some(v) = overrides.mtp_activation_threshold {
1491            self.mtp_activation_threshold = v;
1492        }
1493        if let Some(v) = overrides.mtp_cluster_vocab_threshold {
1494            self.mtp_cluster_vocab_threshold = v;
1495        }
1496        if let Some(v) = overrides.mtp_shared_kv_prompt_threshold {
1497            self.mtp_shared_kv_prompt_threshold = v;
1498        }
1499        if let Some(v) = overrides.mtp_cluster_size {
1500            self.mtp_cluster_size = v;
1501        }
1502        if let Some(v) = overrides.mtp_min_output_tokens {
1503            self.mtp_min_output_tokens = v;
1504        }
1505        if let Some(v) = overrides.mtp_cluster_topk {
1506            self.mtp_cluster_topk = v;
1507        }
1508        if let Some(v) = overrides.sp_kv_threshold {
1509            self.sp_kv_threshold = v;
1510        }
1511        if let Some(v) = overrides.width_rollouts {
1512            self.width_rollouts = v;
1513        }
1514        if let Some(v) = overrides.early_stop_threshold {
1515            self.early_stop_threshold = v;
1516        }
1517        if let Some(v) = overrides.convergence_selector {
1518            self.convergence_selector = v;
1519        }
1520        if let Some(v) = overrides.mls_layers {
1521            self.mls_layers = v;
1522        }
1523        // SR²AM horizon truncation override (Plan 112 T11)
1524        if let Some(v) = overrides.max_plan_horizon {
1525            self.draft_lookahead = self.draft_lookahead.min(v);
1526        }
1527        // Hydra Adaptive Layer Budget overrides (Research 148, Plan 165)
1528        // Applied via HydraBudgetConfig at call site, not directly on Config.
1529        // The overrides fields exist on InferenceOverrides for downstream consumption.
1530        self
1531    }
1532}
1533
1534// ---------------------------------------------------------------------------
1535// InferenceOverrides
1536// ---------------------------------------------------------------------------
1537
1538/// Override DTO for applying per-domain inference budget to a [`Config`].
1539///
1540/// All fields are `Option` — `None` means "keep Config's current value".
1541/// This is a plain struct (no serde) to keep `katgpt-core` dependency-free
1542/// from router/TOML types. Conversion from the router's `InferenceBudget`
1543/// happens at the router boundary.
1544///
1545/// Note: `decode_strategy` is NOT included here because it depends on
1546/// project-specific types. Each project handles it at the call site.
1547///
1548/// See Plan 026 (AutoTTS Dynamic Inference Budget).
1549#[derive(Debug, Clone, Default)]
1550// Fields ordered by descending alignment to minimize padding:
1551// Option<usize>/Option<PathBuf> (16/32 bytes) → Option<f32> (8 bytes) →
1552// Option<#[repr(u8)] enum> (2 bytes).
1553pub struct InferenceOverrides {
1554    // --- Option<usize> (16 bytes each, 8-byte aligned) ---
1555    pub tree_budget: Option<usize>,
1556    pub draft_lookahead: Option<usize>,
1557    pub parallel_threshold: Option<usize>,
1558    pub early_exit_patience: Option<usize>,
1559    // MTP Drafter overrides (Plan 055: Gemma 4 MTP)
1560    pub mtp_activation_threshold: Option<usize>,
1561    pub mtp_cluster_vocab_threshold: Option<usize>,
1562    pub mtp_shared_kv_prompt_threshold: Option<usize>,
1563    pub mtp_cluster_size: Option<usize>,
1564    /// Minimum expected output tokens for MTP (Plan 117 T15).
1565    /// When overridden, skips MTP when remaining tokens < threshold.
1566    pub mtp_min_output_tokens: Option<usize>,
1567    /// Top-K cluster selection for clustered LM head (Plan 117 T22).
1568    /// When K > 1, compute logits for tokens in top-K clusters instead of just top-1.
1569    pub mtp_cluster_topk: Option<usize>,
1570    // PTRM width scaling (Plan 083)
1571    pub width_rollouts: Option<usize>,
1572    // MLS Multi-Layer Sum override (Plan 104)
1573    pub mls_layers: Option<usize>,
1574    // SR²AM horizon truncation override (Plan 112 T11)
1575    pub max_plan_horizon: Option<usize>,
1576
1577    // --- Option<PathBuf> (32 bytes, 8-byte aligned) ---
1578    // Drafter LoRA path (Plan 117: MTP LoRA Drafter)
1579    pub drafter_lora_path: Option<std::path::PathBuf>,
1580
1581    // --- Option<f32> (8 bytes each, 4-byte aligned) ---
1582    pub screening_threshold: Option<f32>,
1583    pub temperature: Option<f32>,
1584    pub sparse_threshold: Option<f32>,
1585    pub early_exit_gap: Option<f32>,
1586    // SP-KV inference-time threshold knob (Plan 070)
1587    pub sp_kv_threshold: Option<f32>,
1588    pub early_stop_threshold: Option<f32>,
1589
1590    // --- Option<#[repr(u8) enum> (2 bytes each, 1-byte aligned) ---
1591    // EqR Convergence Selection (Plan 119)
1592    pub convergence_selector: Option<ConvergenceSelector>,
1593
1594    // --- Hydra Adaptive Layer Budget (Research 148, Plan 165) ---
1595    /// Override Hydra skip threshold.
1596    #[cfg(feature = "hydra_budget")]
1597    pub hydra_skip_threshold: Option<f32>,
1598    /// Override Hydra erasure-skip-draft flag.
1599    #[cfg(feature = "hydra_budget")]
1600    pub hydra_skip_erasure_draft: Option<bool>,
1601
1602    // --- Adaptive Depth Tier (Plan 284) ---
1603    /// Override depth tier for layer count capping at inference time.
1604    /// When set, caps the layer loop to tier.max_layers().
1605    /// None = use all layers (backward compatible).
1606    pub depth_tier: Option<DepthTier>,
1607}
1608
1609impl Default for Config {
1610    fn default() -> Self {
1611        Self::micro()
1612    }
1613}
1614
1615// ---------------------------------------------------------------------------
1616// KV dimension helper
1617// ---------------------------------------------------------------------------
1618
1619/// KV dimension: total float count per token in KV cache.
1620#[inline(always)]
1621pub fn kv_dim(config: &Config) -> usize {
1622    config.n_kv_head * config.head_dim
1623}