1use crate::qwen4_exp;
4use anyhow::Result;
5
6fn argmax(logits: &[f32]) -> u32 {
7 logits
8 .iter()
9 .enumerate()
10 .max_by(|a, b| a.1.total_cmp(b.1))
11 .map(|(i, _)| i as u32)
12 .unwrap()
13}
14
15fn logits_diff(a: &[f32], b: &[f32]) -> (f32, f32) {
16 let max_abs = a
17 .iter()
18 .zip(b)
19 .map(|(x, y)| (x - y).abs())
20 .fold(0.0f32, f32::max);
21 let scale = b.iter().map(|v| v.abs()).fold(0.0f32, f32::max).max(1e-6);
22
23 (max_abs, max_abs / scale)
24}
25
26pub(super) fn dump_run(gpu: &qwen4_exp::gpu::Gpu<'_>) -> Result<()> {
27 if let Ok(path) = std::env::var("CHERENKOV_DUMP_EXPERTS") {
28 std::fs::write(&path, serde_json::to_vec(&gpu.expert_history)?)?;
29 eprintln!(
30 "expert history ({} steps) written to {path}",
31 gpu.expert_history.len()
32 );
33 }
34
35 if let Ok(path) = std::env::var("CHERENKOV_DUMP_STATES") {
36 use std::io::Write as _;
39
40 let mut f = std::io::BufWriter::new(std::fs::File::create(&path)?);
41 let steps = gpu.state_history.len();
42 let blocks = gpu.state_history.first().map_or(0, |s| s.len());
43 let hidden = gpu
44 .state_history
45 .first()
46 .and_then(|s| s.first())
47 .map_or(0, |b| b.0.len());
48 let k = gpu
49 .state_history
50 .first()
51 .and_then(|s| s.first())
52 .map_or(0, |b| b.1.len());
53
54 writeln!(
57 f,
58 "{{\"format\":2,\"steps\":{steps},\"blocks\":{blocks},\"hidden\":{hidden},\"k\":{k}}}"
59 )?;
60
61 for step in &gpu.state_history {
62 f.write_all(&(step.len() as u32).to_le_bytes())?;
63
64 for (x, ids) in step {
65 for v in x {
66 f.write_all(&v.to_le_bytes())?;
67 }
68
69 for id in ids {
70 f.write_all(&id.to_le_bytes())?;
71 }
72 }
73 }
74
75 eprintln!("router states ({steps} steps x {blocks} blocks x {hidden}) written to {path}");
76 }
77
78 if let Ok(path) = std::env::var("CHERENKOV_DUMP_ROUTES") {
79 std::fs::write(&path, serde_json::to_vec(&gpu.route_history)?)?;
80 eprintln!(
81 "route history ({} steps) written to {path}",
82 gpu.route_history.len()
83 );
84 }
85
86 if let Ok(path) = std::env::var("CHERENKOV_DUMP_LA") {
87 std::fs::write(&path, serde_json::to_vec(&gpu.la_log)?)?;
88 eprintln!(
89 "lookahead log ({} predictions) written to {path}",
90 gpu.la_log.len()
91 );
92 }
93
94 Ok(())
95}
96
97#[allow(clippy::too_many_arguments)]
102pub(super) fn qwen4_exp_check_rows(
103 m: &qwen4_exp::cpu::CpuModel<'_>,
104 st: &mut qwen4_exp::cpu::State,
105 gpu: &qwen4_exp::gpu::Gpu<'_>,
106 rows: &[u32],
107 next: Option<&[u32]>,
108 pos0: usize,
109 label: &str,
110 prefill: bool,
111) -> Result<()> {
112 for (r, &t) in rows.iter().enumerate() {
113 let ref_logits = m.forward_token(t, st)?;
114 let g = if prefill {
115 gpu.pf_logits_row(r)
116 } else {
117 gpu.logits_row(r)
118 };
119 let (abs, rel) = logits_diff(g, &ref_logits);
120 let (ga, ra) = (argmax(g), argmax(&ref_logits));
121
122 eprintln!(
123 " {label} {}: gpu argmax {ga} cpu argmax {ra} | max abs diff {abs:.4} (rel {rel:.2e}){}",
124 pos0 + r,
125 if ga == ra { "" } else { " <-- MISMATCH" }
126 );
127
128 if let Some(next) = next {
129 let hyper = st.last_hyper.clone();
130 let (ml, _) = m.mtp_forward(next[r], &hyper, pos0 + r, st)?;
131
132 if r + 1 == rows.len() {
133 let g = gpu.mtp_logits_row(if prefill { 0 } else { r });
134 let (abs, rel) = logits_diff(g, &ml);
135 let (ga, ra) = (argmax(g), argmax(&ml));
136
137 eprintln!(
138 " {label} {} mtp: gpu draft {ga} cpu draft {ra} | max abs diff {abs:.4} (rel {rel:.2e}){}",
139 pos0 + r,
140 if ga == ra { "" } else { " <-- MISMATCH" }
141 );
142 }
143 }
144 }
145
146 Ok(())
147}
148
149pub(super) fn dump_prefill_chunk(
151 gpu: &qwen4_exp::gpu::Gpu<'_>,
152 engine: bool,
153 pos: usize,
154 rows: usize,
155 prompt_tokens: usize,
156 lines: &mut Vec<String>,
157) {
158 for row in 0..rows {
159 let logits = if engine {
160 gpu.pf_logits_row(row)
161 } else {
162 gpu.logits_row(row)
163 };
164 let token = argmax(logits);
165
166 lines.push(format!(
167 "{} {token} {:.4}",
168 pos + row,
169 logits[token as usize]
170 ));
171 }
172
173 let cfg = &gpu.p.cfg;
174
175 if pos + rows != prompt_tokens
176 || prompt_tokens <= cfg.indexer_budget + cfg.indexer_compress_ratio
177 {
178 return;
179 }
180
181 if engine {
182 let blocks = gpu.debug_engine_blocks(rows - 1);
183
184 eprintln!(
185 "engine last row: {} blocks selected, last 8 {:?}",
186 blocks.len(),
187 &blocks[blocks.len().saturating_sub(8)..]
188 );
189
190 return;
191 }
192
193 let vis = gpu.debug_row_vis(rows - 1);
194 let blocks: Vec<u32> = vis
195 .iter()
196 .filter(|&&t| t % 4 == 0)
197 .map(|&t| t / 4)
198 .collect();
199
200 eprintln!(
201 "row path last row: {} visible tokens, last 8 {:?}; {} block starts, last 8 {:?}",
202 vis.len(),
203 &vis[vis.len().saturating_sub(8)..],
204 blocks.len(),
205 &blocks[blocks.len().saturating_sub(8)..]
206 );
207}
208
209pub(super) fn dump_decode(
210 gpu: &qwen4_exp::gpu::Gpu<'_>,
211 ids: &[u32],
212 out: &[u32],
213 prefill_steps: usize,
214) -> Result<()> {
215 if let Ok(path) = std::env::var("CHERENKOV_DUMP_TOKENS") {
216 std::fs::write(
217 &path,
218 serde_json::to_vec(&serde_json::json!({ "prompt": ids, "out": out }))?,
219 )?;
220 eprintln!("tokens written to {path}");
221 }
222
223 if std::env::var_os("CHERENKOV_TRACE").is_some() {
224 for i in prefill_steps..gpu.step_ms.len() {
225 eprintln!(
226 " step {i}: {} rows, {:.1} ms wall, {:.1} ms gpu, {:.1} ms io wait, {} sync fetches ({:.0} MB), lookahead hit {:.2} fetched {}",
227 gpu.rows[i],
228 gpu.step_ms[i],
229 gpu.gpu_ms[i],
230 gpu.io_ms[i],
231 gpu.misses[i],
232 gpu.miss_bytes[i] as f64 / 1e6,
233 gpu.lookahead_hit[i],
234 gpu.lookahead_issued[i]
235 );
236 }
237 }
238
239 Ok(())
240}