Skip to main content

qualia_core_db/inference/lab/
ablate.rs

1//! Ablation matrix: toggle axes × decode-proxy tok/s → experiment CSV.
2
3use std::path::Path;
4
5use crate::hardware_passport::measure_decode_proxy_tok_s;
6use crate::inference_modes::{set_inference_mode, InferenceMode};
7use crate::lab::experiment_log::{append_run_csv, ExperimentRun};
8use crate::llm_bench::{set_coop_gemv, set_ffn_fusion, set_kv_int8, set_resident_decode};
9
10#[derive(Debug, Clone)]
11pub struct AblationRow {
12    pub label: String,
13    pub tok_s: Option<f64>,
14    pub toggles: String,
15}
16
17/// Default small ablation matrix (fast enough for lab sessions).
18pub fn run_ablation_matrix(model: &Path, tokens: u32, csv_out: Option<&Path>) -> Vec<AblationRow> {
19    let tokens = tokens.max(4).min(32);
20    let model_id = model
21        .file_name()
22        .and_then(|s| s.to_str())
23        .unwrap_or("model")
24        .to_string();
25
26    // (label, resident, coop, ffn_flag, kv_int8, mode)
27    let cases: [(&str, bool, bool, bool, bool, InferenceMode); 6] = [
28        (
29            "baseline_resident",
30            true,
31            true,
32            true,
33            true,
34            InferenceMode::Portable,
35        ),
36        ("no_coop", true, false, true, true, InferenceMode::Portable),
37        (
38            "no_resident",
39            false,
40            true,
41            true,
42            true,
43            InferenceMode::Portable,
44        ),
45        ("kv_f32", true, true, true, false, InferenceMode::Portable),
46        (
47            "fast_verify",
48            true,
49            true,
50            true,
51            true,
52            InferenceMode::FastVerify,
53        ),
54        ("cuda_mode", true, true, true, true, InferenceMode::CudaTc),
55    ];
56
57    let mut rows = Vec::new();
58    let passport_key =
59        crate::hardware_passport::read_passport(&crate::hardware_passport::default_cache_path())
60            .map(|p| p.key)
61            .unwrap_or_default();
62
63    for (label, res, coop, ffn, kv, mode) in cases {
64        set_resident_decode(res);
65        set_coop_gemv(coop);
66        set_ffn_fusion(ffn);
67        set_kv_int8(kv);
68        set_inference_mode(mode);
69        std::env::set_var("QUALIA_INFERENCE_MODE", mode.as_str());
70
71        let toggles = format!(
72            "{{\"resident\":{res},\"coop\":{coop},\"ffn_fusion\":{ffn},\"kv_int8\":{kv},\"mode\":\"{}\"}}",
73            mode.as_str()
74        );
75        let tok_s = measure_decode_proxy_tok_s(model, tokens);
76        rows.push(AblationRow {
77            label: label.into(),
78            tok_s,
79            toggles: toggles.clone(),
80        });
81
82        if let Some(path) = csv_out {
83            let mut run = ExperimentRun {
84                run_id: ExperimentRun::new_id(),
85                utc: ExperimentRun::utc_now(),
86                git_sha: String::new(),
87                host_passport_key: passport_key.clone(),
88                adapter: String::new(),
89                backend: std::env::var("QUALIA_WGPU_BACKEND").unwrap_or_else(|_| "auto".into()),
90                model_id: model_id.clone(),
91                model_hash: String::new(),
92                layout: guess_layout(&model_id),
93                mode: mode.as_str().into(),
94                profile: "lab-ablate".into(),
95                toggles_json: toggles,
96                qualia_decode_tok_s: tok_s,
97                ollama_decode_tok_s: None,
98                a_gap: None,
99                prefill_tok_s: None,
100                phase_ns_json: "{}".into(),
101                n_ulp_max: None,
102                c_score: None,
103                notes: label.into(),
104            };
105            run.compute_a_gap();
106            let _ = append_run_csv(path, &run);
107        }
108    }
109
110    // Restore sane defaults
111    set_resident_decode(true);
112    set_coop_gemv(true);
113    set_ffn_fusion(true);
114    set_kv_int8(true);
115    set_inference_mode(InferenceMode::Portable);
116    std::env::remove_var("QUALIA_INFERENCE_MODE");
117
118    // ── Forge backend profile rows ──────────────────────────────────────────
119    // Test each forge shader backend with default toggles (resident+coop+ffn+kv_int8).
120    // These run in-process like the toggle rows above; the forge backend is
121    // selected via QUALIA_FORGE_BACKEND env var which is read at pipeline
122    // compile time.
123    let forge_backends: &[(&str, &str)] = &[
124        ("forge_wgsl", "wgsl"),
125        ("forge_hlsl", "hlsl"),
126        ("forge_spirv", "spirv"),
127        ("forge_ptx", "ptx"),
128    ];
129
130    for &(label, backend) in forge_backends {
131        set_resident_decode(true);
132        set_coop_gemv(true);
133        set_ffn_fusion(true);
134        set_kv_int8(true);
135        set_inference_mode(InferenceMode::Portable);
136        std::env::set_var("QUALIA_INFERENCE_MODE", "portable");
137        std::env::set_var("QUALIA_FORGE_BACKEND", backend);
138
139        let toggles = format!(
140            "{{\"forge_backend\":\"{backend}\",\"resident\":true,\"coop\":true,\"ffn_fusion\":true,\"kv_int8\":true,\"mode\":\"portable\"}}"
141        );
142        let tok_s = measure_decode_proxy_tok_s(model, tokens);
143        rows.push(AblationRow {
144            label: label.into(),
145            tok_s,
146            toggles: toggles.clone(),
147        });
148
149        if let Some(path) = csv_out {
150            let mut run = ExperimentRun {
151                run_id: ExperimentRun::new_id(),
152                utc: ExperimentRun::utc_now(),
153                git_sha: String::new(),
154                host_passport_key: passport_key.clone(),
155                adapter: String::new(),
156                backend: format!("forge-{backend}"),
157                model_id: model_id.clone(),
158                model_hash: String::new(),
159                layout: guess_layout(&model_id),
160                mode: "portable".into(),
161                profile: "lab-ablate".into(),
162                toggles_json: toggles,
163                qualia_decode_tok_s: tok_s,
164                ollama_decode_tok_s: None,
165                a_gap: None,
166                prefill_tok_s: None,
167                phase_ns_json: "{}".into(),
168                n_ulp_max: None,
169                c_score: None,
170                notes: label.into(),
171            };
172            run.compute_a_gap();
173            let _ = append_run_csv(path, &run);
174        }
175    }
176
177    // Clean up forge backend env
178    std::env::remove_var("QUALIA_FORGE_BACKEND");
179
180    rows
181}
182
183fn guess_layout(name: &str) -> String {
184    let n = name.to_ascii_lowercase();
185    if n.contains(".soa.") || n.contains("soa") {
186        "soa".into()
187    } else if n.contains("f16") {
188        "f16".into()
189    } else {
190        "verbatim".into()
191    }
192}
193
194pub fn format_ablation_report(rows: &[AblationRow]) -> String {
195    let mut s = String::from("Ablation matrix (decode-proxy tok/s)\n");
196    for r in rows {
197        s.push_str(&format!(
198            "  {:20}  {:>8}\n",
199            r.label,
200            r.tok_s
201                .map(|t| format!("{t:.3}"))
202                .unwrap_or_else(|| "fail".into())
203        ));
204    }
205    s
206}