1use crate::model::author::{self, Authored, RowState, TpAuthored};
14
15#[derive(Debug, Clone)]
18pub struct Qwen38Cfg {
19 pub d: usize, pub layers: usize, pub full_attn_every: usize, pub dff: usize, pub vocab: usize, pub h: usize, pub kv: usize, pub hd: usize, pub rope_dim: usize, pub rope_theta: f64, pub rms_eps: f64, pub gdn_k_heads: usize, pub gdn_v_heads: usize, pub gdn_head_dim: usize, pub conv_taps: usize, pub ctx_len: usize, }
38
39impl Qwen38Cfg {
40 pub fn real() -> Self {
42 Qwen38Cfg {
43 d: 5120,
44 layers: 64,
45 full_attn_every: 4,
46 dff: 17408,
47 vocab: 248320,
48 h: 24,
49 kv: 4,
50 hd: 256,
51 rope_dim: 64,
52 rope_theta: 1.0e7,
53 rms_eps: 1e-6,
54 gdn_k_heads: 16,
55 gdn_v_heads: 48,
56 gdn_head_dim: 128,
57 conv_taps: 4,
58 ctx_len: 262144,
59 }
60 }
61 pub fn tiny() -> Self {
64 Qwen38Cfg {
65 d: 8,
66 layers: 4,
67 full_attn_every: 4,
68 dff: 16,
69 vocab: 32,
70 h: 2,
71 kv: 1,
72 hd: 4,
73 rope_dim: 2,
74 rope_theta: 1.0e7,
75 rms_eps: 1e-6,
76 gdn_k_heads: 1,
77 gdn_v_heads: 2,
78 gdn_head_dim: 4,
79 conv_taps: 4,
80 ctx_len: 64,
81 }
82 }
83 pub fn mini() -> Self {
87 Qwen38Cfg {
88 h: 8,
89 kv: 2,
90 gdn_k_heads: 4,
91 gdn_v_heads: 8,
92 ..Qwen38Cfg::tiny()
93 }
94 }
95 pub fn gdn_qk(&self) -> usize {
97 self.gdn_k_heads * self.gdn_head_dim
98 }
99 pub fn gdn_v(&self) -> usize {
101 self.gdn_v_heads * self.gdn_head_dim
102 }
103 pub fn aq(&self) -> usize {
105 self.h * self.hd
106 }
107 pub fn akv(&self) -> usize {
109 self.kv * self.hd
110 }
111 pub fn from_spec(spec: &crate::model::spec::ModelSpec) -> Result<Qwen38Cfg, author::NotYet> {
117 use crate::model::spec::{Act, Ffn, Mixer, RopeScaling};
118 use author::NotYet;
119 if spec.arch != "qwen35" {
120 return Err(NotYet("a model other than a Qwen3.5/3.8 hybrid, which the spatial lowering is written for"));
121 }
122 let every = spec
123 .layers
124 .iter()
125 .position(|l| matches!(l.mixer, Mixer::Attention(_)))
126 .map(|l| l + 1)
127 .ok_or(NotYet("a hybrid with no attention layer"))?;
128 let attention = spec.layers.iter().find_map(|l| match &l.mixer {
129 Mixer::Attention(a) => Some(a.clone()),
130 _ => None,
131 });
132 let delta = spec.layers.iter().find_map(|l| match &l.mixer {
133 Mixer::GatedDelta(g) => Some(g.clone()),
134 _ => None,
135 });
136 let (Some(a), Some(g)) = (attention, delta) else {
137 return Err(NotYet("a hybrid without both attention and Gated-DeltaNet layers"));
138 };
139 let rope = a.rope.as_ref().ok_or(NotYet("attention without a rotation"))?;
140 if !rope.neox || rope.scaling != RopeScaling::None {
141 return Err(NotYet("a rotation other than an unscaled NEOX one"));
142 }
143 let dff = match &spec.layers[0].ffn {
144 Ffn::Dense { dff, act: Act::Silu } => *dff,
145 _ => return Err(NotYet("a feed-forward block other than a dense SwiGLU")),
146 };
147 for (l, layer) in spec.layers.iter().enumerate() {
148 let alike = match &layer.mixer {
149 Mixer::Attention(x) => (l + 1) % every == 0 && *x == a,
150 Mixer::GatedDelta(x) => (l + 1) % every != 0 && *x == g,
151 Mixer::Mla(_) => false,
152 };
153 if !alike || layer.ffn != (Ffn::Dense { dff, act: Act::Silu }) || layer.post_mixer_norm || layer.post_ffn_norm {
154 return Err(NotYet("a layer unlike the hybrid's pattern of alike attention and Gated-DeltaNet layers"));
155 }
156 }
157 let cfg = Qwen38Cfg {
158 d: spec.d,
159 layers: spec.layers.len(),
160 full_attn_every: every,
161 dff,
162 vocab: spec.vocab,
163 h: a.heads,
164 kv: a.kv_heads,
165 hd: a.head_dim,
166 rope_dim: rope.dims,
167 rope_theta: rope.theta,
168 rms_eps: spec.rms_eps,
169 gdn_k_heads: g.k_heads,
170 gdn_v_heads: g.v_heads,
171 gdn_head_dim: g.head_dim,
172 conv_taps: g.conv_taps,
173 ctx_len: spec.ctx_len,
174 };
175 let step = crate::model::program::Cut::Decode { ctx: 64 };
176 if crate::model::program::written(&cfg.spec(), step).1 != crate::model::program::written(spec, step).1 {
177 return Err(NotYet("a hybrid whose configuration would write a program other than the checkpoint's"));
178 }
179 Ok(cfg)
180 }
181
182 pub fn is_attn(&self, l: usize) -> bool {
183 (l + 1) % self.full_attn_every == 0
184 }
185 pub fn attn_layers(&self) -> usize {
187 (0..self.layers).filter(|&l| self.is_attn(l)).count()
188 }
189
190 pub fn spec(&self) -> crate::model::spec::ModelSpec {
193 self.spec_of(self.layers)
194 }
195
196 pub fn layer_spec(&self, l: usize) -> crate::model::spec::ModelSpec {
199 self.spec_of(self.layers.max(l + 1))
200 }
201
202 fn spec_of(&self, layers: usize) -> crate::model::spec::ModelSpec {
204 use crate::model::spec::{
205 Act, Attention, Ffn, GateActivation, GatedDelta, Layer, Mixer, ModelSpec, Rope, RopeScaling,
206 };
207 let attention = Attention {
208 heads: self.h,
209 kv_heads: self.kv,
210 head_dim: self.hd,
211 value_dim: self.hd,
212 rope: Some(Rope {
213 dims: self.rope_dim,
214 theta: self.rope_theta,
215 neox: true,
216 scaling: RopeScaling::None,
217 sections: None,
218 }),
219 qk_norm: true,
220 output_gate: true,
221 fused_qkv: false,
222 bias: false,
223 output_bias: false,
224 window: None,
225 sinks: false,
226 softcap: None,
227 scale: 1.0 / (self.hd as f64).sqrt(),
228 temperature: None,
229 qk_unit_norm: false,
230 qk_norm_whole: false,
231 };
232 let gdn = GatedDelta {
233 k_heads: self.gdn_k_heads,
234 v_heads: self.gdn_v_heads,
235 head_dim: self.gdn_head_dim,
236 conv_taps: self.conv_taps,
237 gate: GateActivation::Silu,
238 };
239 ModelSpec {
240 arch: "qwen35".to_string(),
241 d: self.d,
242 vocab: self.vocab,
243 ctx_len: self.ctx_len,
244 rms_eps: self.rms_eps,
245 embedding_scale: None,
246 tied_output: false,
247 final_logit_softcap: None,
248 layers: (0..layers)
249 .map(|l| Layer {
250 mixer: if self.is_attn(l) {
251 Mixer::Attention(attention.clone())
252 } else {
253 Mixer::GatedDelta(gdn.clone())
254 },
255 ffn: Ffn::Dense {
256 dff: self.dff,
257 act: Act::Silu,
258 },
259 post_mixer_norm: false,
260 post_ffn_norm: false,
261 })
262 .collect(),
263 mtp_blocks: 0,
264 ffn_norm: "post_attention_norm",
265 hyper: None,
266 ple: None,
267 indexer: None,
268 }
269 }
270}
271
272pub fn qwen38_rope_cache(cfg: &Qwen38Cfg, rows: usize) -> (Vec<f32>, Vec<f32>) {
276 let half = cfg.rope_dim / 2;
277 let angle =
278 |p: usize, i: usize| p as f64 * cfg.rope_theta.powf(-2.0 * i as f64 / cfg.rope_dim as f64);
279 let (mut cos, mut sin) = (
280 Vec::with_capacity(rows * half),
281 Vec::with_capacity(rows * half),
282 );
283 for p in 0..rows {
284 for i in 0..half {
285 let a = angle(p, i);
286 cos.push(a.cos() as f32);
287 sin.push(a.sin() as f32);
288 }
289 }
290 (cos, sin)
291}
292
293pub fn gen_qwen38_filled_lg(cfg: &Qwen38Cfg, dir: &str) -> String {
299 let (authored, _f32w, _q8w) = gen_qwen38_lg(cfg, dir);
300 let mut src = String::new();
301 for line in authored.lines() {
302 src.push_str(line);
303 src.push('\n');
304 let Some(rest) = line.strip_prefix("Let ") else {
305 continue;
306 };
307 let Some((var, tail)) = rest.split_once(" be a ") else {
308 continue;
309 };
310 let Some(dims) = tail.strip_suffix(" tensor of Real.") else {
311 continue;
312 };
313 let (r, c) = dims.split_once(" by ").expect("R by C");
314 let n = r.parse::<usize>().expect("rows") * c.parse::<usize>().expect("cols");
315 let salt = var.bytes().map(|b| b as u64).sum::<u64>();
316 for i in 0..n {
317 let h = (i as u64)
318 .wrapping_mul(2654435761)
319 .wrapping_add(salt.wrapping_mul(40503));
320 let v = ((h % 97) as f64 / 97.0 - 0.5) * 0.4;
321 src.push_str(&format!("Set item {} of {var} to {v:?}.\n", i + 1));
322 }
323 }
324 src.push_str("Show round(item 1 of logits * 1000000).\n");
325 src.push_str(&format!(
326 "Show round(item {} of logits * 1000000).\n",
327 cfg.vocab
328 ));
329 src
330}
331
332pub fn gen_qwen38_lg(cfg: &Qwen38Cfg, dir: &str) -> Authored {
340 author::forward(&cfg.spec(), dir)
341}
342
343pub fn gen_qwen38_decode_step_lg(cfg: &Qwen38Cfg, dir: &str, ctx: usize, tok: usize, pos: usize) -> Authored {
346 author::decode_step(&cfg.spec(), dir, ctx, tok, pos)
347}
348
349pub fn gen_qwen38_prefill_lg(cfg: &Qwen38Cfg, dir: &str, ctx: usize, toks: &[usize], pos0: usize) -> Authored {
351 author::prefill(&cfg.spec(), dir, ctx, toks, pos0)
352}
353
354pub fn gen_qwen38_decode_batch_lg(cfg: &Qwen38Cfg, dir: &str, ctx: usize, seqs: &[(usize, usize)]) -> Authored {
356 author::decode_batch(&cfg.spec(), dir, ctx, seqs)
357}
358
359pub fn gen_qwen38_decode_batch_embed_lg(cfg: &Qwen38Cfg, dir: &str, toks: &[usize]) -> Authored {
361 author::decode_batch_embed(&cfg.spec(), dir, toks)
362}
363
364pub fn gen_qwen38_decode_batch_layer_lg(cfg: &Qwen38Cfg, l: usize, dir: &str, ctx: usize, poss: &[usize]) -> Authored {
366 author::stacked_layer(&cfg.layer_spec(l), l, dir, ctx, poss, RowState::PerSequence)
367}
368
369pub fn gen_qwen38_prefill_layer_lg(cfg: &Qwen38Cfg, l: usize, dir: &str, ctx: usize, pos0: usize, chunk: usize) -> Authored {
371 let poss: Vec<usize> = (pos0..pos0 + chunk).collect();
372 author::stacked_layer(&cfg.layer_spec(l), l, dir, ctx, &poss, RowState::Shared)
373}
374
375pub fn gen_qwen38_prefill_head_lg(cfg: &Qwen38Cfg, dir: &str, chunk: usize) -> Authored {
377 author::prefill_head(&cfg.spec(), dir, chunk)
378}
379
380pub fn gen_qwen38_decode_batch_head_lg(cfg: &Qwen38Cfg, dir: &str, bsz: usize) -> Authored {
382 author::decode_batch_head(&cfg.spec(), dir, bsz)
383}
384
385pub fn gen_qwen38_decode_embed_lg(cfg: &Qwen38Cfg, dir: &str, tok: usize) -> Authored {
387 author::decode_embed(&cfg.spec(), dir, tok)
388}
389
390pub fn gen_qwen38_decode_head_lg(cfg: &Qwen38Cfg, dir: &str) -> Authored {
392 author::decode_head(&cfg.spec(), dir)
393}
394
395pub fn gen_qwen38_layer_lg(cfg: &Qwen38Cfg, l: usize, dir: &str) -> Authored {
397 author::layer(&cfg.layer_spec(l), l, dir)
398}
399
400pub fn gen_qwen38_decode_layer_lg(cfg: &Qwen38Cfg, l: usize, dir: &str, ctx: usize, pos: usize) -> Authored {
402 author::decode_layer(&cfg.layer_spec(l), l, dir, ctx, pos)
403}
404
405pub fn gen_qwen38_head_lg(cfg: &Qwen38Cfg, dir: &str) -> Authored {
407 author::head(&cfg.spec(), dir)
408}
409
410fn split(cfg: &Qwen38Cfg, tp: usize, form: Result<TpAuthored, author::NotYet>) -> TpAuthored {
412 form.unwrap_or_else(|e| panic!("Qwen3.8 does not split over {tp} chips ({cfg:?}): {e}"))
413}
414
415pub fn gen_qwen38_tp_decode_step_lg(cfg: &Qwen38Cfg, dir: &str, ctx: usize, tok: usize, pos: usize, tp: usize) -> TpAuthored {
417 split(cfg, tp, author::tp_decode_step(&cfg.spec(), dir, ctx, tok, pos, tp))
418}
419
420pub fn gen_qwen38_decode_batch_tp_lg(cfg: &Qwen38Cfg, dir: &str, ctx: usize, seqs: &[(usize, usize)], tp: usize) -> TpAuthored {
422 split(cfg, tp, author::tp_decode_batch(&cfg.spec(), dir, ctx, seqs, tp))
423}
424
425pub fn gen_qwen38_decode_batch_tp_layer_lg(cfg: &Qwen38Cfg, l: usize, dir: &str, ctx: usize, poss: &[usize], tp: usize) -> TpAuthored {
427 split(cfg, tp, author::tp_stacked_layer(&cfg.layer_spec(l), l, dir, ctx, poss, tp))
428}
429
430pub fn gen_qwen38_tp_decode_layer_lg(cfg: &Qwen38Cfg, l: usize, dir: &str, ctx: usize, pos: usize, tp: usize) -> TpAuthored {
432 split(cfg, tp, author::tp_decode_layer(&cfg.layer_spec(l), l, dir, ctx, pos, tp))
433}
434
435pub fn emit_qwen38_generate(
463 cfg: &Qwen38Cfg,
464 wdir: &str,
465 prompt: &[usize],
466 n_gen: usize,
467 precise: bool,
468 deq: u8,
469) -> String {
470 let debug = std::env::var("LOGOS_Q38_DEBUG").is_ok();
471 let (d, dff, taps) = (cfg.d, cfg.dff, cfg.conv_taps);
472 let (qk, vw) = (cfg.gdn_qk(), cfg.gdn_v());
473 let (aq, akv) = (cfg.aq(), cfg.akv());
474 let (nk, nv, ghd) = (cfg.gdn_k_heads, cfg.gdn_v_heads, cfg.gdn_head_dim);
475 let convc = 2 * qk + vw; let projw = 2 * qk + 2 * vw; let attnw = 2 * aq + 2 * akv; let maxseq = prompt.len() + n_gen + 128;
479 let scale = 1.0f64 / (cfg.hd as f64).sqrt();
480 let prompt_c = prompt
481 .iter()
482 .map(|t| t.to_string())
483 .collect::<Vec<_>>()
484 .join(",");
485 let attn_split = 4usize;
486
487 let mut decls = String::new();
489 let mut loads = String::new();
490 let q8 = |decls: &mut String, loads: &mut String, var: &str, out: usize, inn: usize| {
491 let (qn, sn) = (out * inn, out * inn / 32);
492 decls.push_str(&format!(
493 "static signed char* {var}_q; static __half* {var}_s;\n"
494 ));
495 loads.push_str(&format!(
496 " LDQ({var}, {qn}, {sn}, \"{wdir}/{var}.q\", \"{wdir}/{var}.s\");\n"
497 ));
498 };
499 let f32t = |decls: &mut String, loads: &mut String, var: &str, n: usize| {
500 decls.push_str(&format!("static float* {var}_f;\n"));
501 loads.push_str(&format!(" LDF({var}, {n}, \"{wdir}/{var}.f\");\n"));
502 };
503 q8(&mut decls, &mut loads, "emb", cfg.vocab, d);
504 q8(&mut decls, &mut loads, "Wout", cfg.vocab, d);
505 f32t(&mut decls, &mut loads, "on", d);
506 for l in 0..cfg.layers {
507 f32t(&mut decls, &mut loads, &format!("an{l}"), d);
508 f32t(&mut decls, &mut loads, &format!("fn{l}"), d);
509 if cfg.is_attn(l) {
510 q8(&mut decls, &mut loads, &format!("qgkv{l}"), attnw, d);
511 f32t(&mut decls, &mut loads, &format!("Qn{l}"), cfg.hd);
512 f32t(&mut decls, &mut loads, &format!("Kn{l}"), cfg.hd);
513 q8(&mut decls, &mut loads, &format!("Wo{l}"), d, aq);
514 } else {
515 q8(&mut decls, &mut loads, &format!("qkvz{l}"), projw, d);
516 f32t(&mut decls, &mut loads, &format!("ba{l}"), 2 * nv * d);
517 f32t(&mut decls, &mut loads, &format!("Wc{l}"), taps * convc);
518 f32t(&mut decls, &mut loads, &format!("Al{l}"), nv);
519 f32t(&mut decls, &mut loads, &format!("Dt{l}"), nv);
520 f32t(&mut decls, &mut loads, &format!("Gn{l}"), ghd);
521 q8(&mut decls, &mut loads, &format!("Wo{l}"), d, vw);
522 }
523 q8(&mut decls, &mut loads, &format!("gu{l}"), 2 * dff, d);
524 q8(&mut decls, &mut loads, &format!("Wd{l}"), d, dff);
525 }
526
527 let mut state_decl = String::new();
529 let mut state_alloc = String::new();
530 for l in 0..cfg.layers {
531 if cfg.is_attn(l) {
532 state_decl.push_str(&format!("static float *kc{l}, *vc{l};\n"));
533 state_alloc.push_str(&format!(
534 " cudaMalloc(&kc{l},(size_t){maxseq}*{akv}*4); cudaMalloc(&vc{l},(size_t){maxseq}*{akv}*4);\n"
535 ));
536 } else {
537 state_decl.push_str(&format!("static float *st{l}, *cs{l};\n"));
538 state_alloc.push_str(&format!(
539 " cudaMalloc(&st{l},(size_t){vw}*{ghd}*4); cudaMemset(st{l},0,(size_t){vw}*{ghd}*4);\n\
540 \x20 cudaMalloc(&cs{l},(size_t){taps}*{convc}*4); cudaMemset(cs{l},0,(size_t){taps}*{convc}*4);\n"
541 ));
542 }
543 }
544
545 let fast_attn = if precise {
547 format!("gk_attn<<<{h},256,(size_t){maxseq}*4,s>>>(attnb,pbuf,kc{{L}},vc{{L}},d_pos,{h},{kv},{hd},SCALEV);", h = cfg.h, kv = cfg.kv, hd = cfg.hd)
548 } else {
549 format!("gk_attn_part<<<{h}*{p},256,0,s>>>(pbuf,kc{{L}},vc{{L}},d_pos,{h},{kv},{hd},SCALEV,{p},acc_part,m_part,d_part); \
550 gk_attn_comb<<<{h},128,0,s>>>(attnb,{h},{hd},{p},acc_part,m_part,d_part);", h = cfg.h, kv = cfg.kv, hd = cfg.hd, p = attn_split)
551 };
552 let all_deq = deq == 2;
557 let hl = deq == 3;
560 let wide = std::env::var("LOGOS_Q38_WIDE").is_ok();
562 let unroll = std::env::var("LOGOS_Q38_U2").is_ok();
564 let unroll4 = std::env::var("LOGOS_Q38_U4").is_ok();
565 let splitk: usize = std::env::var("LOGOS_Q38_SPLITK")
568 .ok()
569 .and_then(|s| s.parse().ok())
570 .unwrap_or(0);
571 let nval = move |n: &str| -> usize {
572 if n == "E" {
573 d
574 } else {
575 n.parse().unwrap_or(usize::MAX)
576 }
577 };
578 let narrow = move |n: &str| splitk > 1 && nval(n) <= 8192;
579 let gemv = |y: &str, w: &str, aq: &str, f32src: &str, k: &str, n: &str| -> String {
580 if all_deq {
581 format!("gk_gemv_deq_h<<<Gw({n}),256,0,s>>>({y},{w}_q,{w}_s,{f32src},{k},{n});")
582 } else if hl {
583 let (h, sc) = aq.split_once(',').expect("activation pair");
585 if narrow(n) {
586 format!("gk_gemv_hl_sk<<<dim3(Gw({n}),{splitk}),256,0,s>>>(skbuf,{w}_q,{w}_s,{h},{h}l,{sc},{k},{n},{splitk});\n\
587 \x20 gk_sk_combine<<<Gs({n}),256,0,s>>>({y},skbuf,{n},{splitk});")
588 } else if wide {
589 format!("gk_gemv_hl2<<<Gw2({n}),256,0,s>>>({y},{w}_q,{w}_s,{h},{h}l,{sc},{k},{n});")
590 } else if unroll4 {
591 format!("gk_gemv_hlu4<<<Gw({n}),256,0,s>>>({y},{w}_q,{w}_s,{h},{h}l,{sc},{k},{n});")
592 } else if unroll {
593 format!("gk_gemv_hlu<<<Gw({n}),256,0,s>>>({y},{w}_q,{w}_s,{h},{h}l,{sc},{k},{n});")
594 } else {
595 format!("gk_gemv_hl<<<Gw({n}),256,0,s>>>({y},{w}_q,{w}_s,{h},{h}l,{sc},{k},{n});")
596 }
597 } else if wide {
598 format!("gk_gemv_h2<<<Gw2({n}),256,0,s>>>({y},{w}_q,{w}_s,{aq},{k},{n});")
599 } else if unroll {
600 format!("gk_gemv_hu<<<Gw({n}),256,0,s>>>({y},{w}_q,{w}_s,{aq},{k},{n});")
601 } else {
602 format!("gk_gemv_h<<<Gw({n}),256,0,s>>>({y},{w}_q,{w}_s,{aq},{k},{n});")
603 }
604 };
605 let mut step = String::new();
606 for l in 0..cfg.layers {
607 let entry = if hl {
610 if l == 0 {
613 format!("gk_rmsnorm_q8f_hl<<<1,256,0,s>>>(x,aqE,aqEl,asE,x_f32,an{l}_f,E,EPSV);")
614 } else {
615 format!("gk_add_rmsnorm_q8f_hl<<<1,256,0,s>>>(x,tmp,aqE,aqEl,asE,x_f32,an{l}_f,E,EPSV);")
616 }
617 } else if cfg.is_attn(l) && all_deq {
618 if l == 0 {
619 format!("gk_rmsnorm_q8f<<<1,256,0,s>>>(x,aqE,asE,x_f32,an{l}_f,E,EPSV);")
620 } else {
621 format!("gk_add_rmsnorm_q8f<<<1,256,0,s>>>(x,tmp,aqE,asE,x_f32,an{l}_f,E,EPSV);")
622 }
623 } else if cfg.is_attn(l) {
624 if l == 0 {
625 format!("gk_rmsnorm_q8<<<1,256,0,s>>>(x,aqE,asE,an{l}_f,E,EPSV);")
626 } else {
627 format!("gk_add_rmsnorm_q8a<<<1,256,0,s>>>(x,tmp,aqE,asE,an{l}_f,E,EPSV);")
628 }
629 } else if l == 0 {
630 format!("gk_rmsnorm_q8f<<<1,256,0,s>>>(x,aqE,asE,x_f32,an{l}_f,E,EPSV);")
631 } else {
632 format!("gk_add_rmsnorm_q8f<<<1,256,0,s>>>(x,tmp,aqE,asE,x_f32,an{l}_f,E,EPSV);")
633 };
634 if cfg.is_attn(l) {
635 step.push_str(&format!(
636 " {entry}\n\
637 \x20 {g_qgkv}\n\
638 \x20 gk_kv_store<<<Gs({akv}*2),256,0,s>>>(kc{l},vc{l},d_pos,{akv},pbuf+{two_aq});\n\
639 \x20 gk_qk_norm_rope_part<<<{h},128,0,s>>>(pbuf,d_zero,0,Qn{l}_f,{h},{hd},{rot},ROPEV,EPSV);\n\
640 \x20 gk_qk_norm_rope_part<<<{kv},128,0,s>>>(kc{l},d_pos,{akv},Kn{l}_f,{kv},{hd},{rot},ROPEV,EPSV);\n\
641 \x20 {attn}\n\
642 \x20 gk_gate_mul<<<Gs({aq}),256,0,s>>>(attnb,pbuf+{aq},{aq});\n\
643 \x20 {q_attn}{g_wo_attn}\n",
644 attn = fast_attn.replace("{L}", &l.to_string()),
645 two_aq = 2 * aq, h = cfg.h, kv = cfg.kv, hd = cfg.hd, rot = cfg.rope_dim, entry = entry,
646 g_qgkv = gemv("pbuf", &format!("qgkv{l}"), "aqE,asE", "x_f32", "E", &attnw.to_string()),
647 q_attn = if all_deq { String::new() }
648 else if hl { format!("gk_quant_act_hl<<<Gs({aq}),256,0,s>>>(attnb,aqQ,aqQl,asQ,{aq});\n ") }
649 else { format!("gk_quant_act<<<Gs({aq}),256,0,s>>>(attnb,aqQ,asQ,{aq});\n ") },
650 g_wo_attn = gemv("tmp", &format!("Wo{l}"), "aqQ,asQ", "attnb", &aq.to_string(), "E"),
651 ));
652 } else {
653 step.push_str(&format!(
654 " {entry}\n\
655 \x20 {p0}\
656 \x20 {g_qkvz}\n\
657 \x20 gk_gemv_f32w<<<Gw({two_nv}),256,0,s>>>(gbuf,ba{l}_f,x_f32,E,{two_nv});\n\
658 \x20 {p1}gk_conv_silu_l2<<<{convc}/{ghd},{ghd},0,s>>>(pbuf,Wc{l}_f,cs{l},cbuf,{convc},{taps},{two_nk},EPSV);\n\
659 \x20 {p2}\
660 \x20 gk_gdn_gates<<<Gs({nv}),256,0,s>>>(gbuf,gbuf+{nv},Dt{l}_f,Al{l}_f,betab,decayb,{nv});\n\
661 \x20 {p3}\
662 \x20 gdn_step<<<dim3({nv},({ghd}+3)/4),128,0,s>>>(cbuf,cbuf+{qk},cbuf+{two_qk},betab,decayb,st{l},obuf,{ghd},{nk});\n\
663 \x20 {gate_norm}\n\
664 \x20 {p4}\
665 \x20 {g_wo_gdn}\n\
666 \x20 {p5}\n",
667 p1 = if debug && l == 0 { "gk_dbg3<<<1,32,0,s>>>(pbuf,dbg+3*72); " } else { "" },
668 p3 = if debug && l == 0 { "gk_dbg3<<<1,32,0,s>>>(betab,dbg+3*74); gk_dbg3<<<1,32,0,s>>>(decayb,dbg+3*75);" } else { "" },
669 p4 = if debug && l == 0 { "gk_dbg3<<<1,32,0,s>>>(obuf,dbg+3*76); gk_dbg3<<<1,32,0,s>>>(gnbuf,dbg+3*77);" } else { "" },
670 p5 = if debug && l == 0 { "gk_dbg3<<<1,32,0,s>>>(tmp,dbg+3*78);" } else { "" },
671 p2 = if debug && l == 0 { "gk_dbg3<<<1,32,0,s>>>(cbuf,dbg+3*73);" } else { "" },
672 p0 = if debug && l == 0 {
673 "gk_dbg3<<<1,32,0,s>>>(x,dbg+3*70); gk_dbg3<<<1,32,0,s>>>(x_f32,dbg+3*71);"
674 } else { "" },
675 two_nv = 2 * nv, two_nk = 2 * nk, two_qk = 2 * qk, entry = entry,
676 g_qkvz = gemv("pbuf", &format!("qkvz{l}"), "aqE,asE", "x_f32", "E", &projw.to_string()),
677 g_wo_gdn = gemv("tmp", &format!("Wo{l}"), "aqQ,asQ", "gnbuf", &vw.to_string(), "E"),
678 gate_norm = if hl {
679 format!("gdn_gate_norm_hl<<<{nv},{ghd},0,s>>>(pbuf+{off},Gn{l}_f,obuf,gnbuf,aqQ,aqQl,asQ,{ghd},EPSV);", off = 2 * qk + vw)
680 } else if all_deq {
681 format!("gdn_gate_norm<<<{nv},{ghd},0,s>>>(pbuf+{off},Gn{l}_f,obuf,gnbuf,{ghd},EPSV);", off = 2 * qk + vw)
682 } else {
683 format!("gdn_gate_norm_q<<<{nv},{ghd},0,s>>>(pbuf+{off},Gn{l}_f,obuf,gnbuf,aqQ,asQ,{ghd},EPSV);", off = 2 * qk + vw)
684 },
685 ));
686 }
687 step.push_str(&format!(
689 " {tail_norm}\n\
690 \x20 {g_gu}\n\
691 \x20 {had}\n\
692 \x20 {g_wd}\n\
693 \x20 {resid}",
694 tail_norm = if hl { format!("gk_add_rmsnorm_q8_hl<<<1,256,0,s>>>(x,tmp,x2n,fn{l}_f,ffQ,ffQl,ffS,E,EPSV);") }
695 else { format!("gk_add_rmsnorm_q8<<<1,256,0,s>>>(x,tmp,x2n,fn{l}_f,ffQ,ffS,E,EPSV);") },
696 g_gu = gemv("ffbuf", &format!("gu{l}"), "ffQ,ffS", "x2n", "E", &(2 * dff).to_string()),
697 g_wd = gemv("tmp", &format!("Wd{l}"), "hqQ,hqS", "hfbuf", &dff.to_string(), "E"),
698 had = if all_deq { format!("gk_silu_had_f<<<Gs({dff}),256,0,s>>>(ffbuf,ffbuf+{dff},hfbuf,{dff});") }
699 else if hl { format!("gk_silu_had_q8_hl<<<Gs({dff}),256,0,s>>>(ffbuf,ffbuf+{dff},hqQ,hqQl,hqS,{dff});") }
700 else { format!("gk_silu_had_q8<<<Gs({dff}),256,0,s>>>(ffbuf,ffbuf+{dff},hqQ,hqS,{dff});") },
701 resid = if l + 1 == cfg.layers { "gk_add<<<Gs(E),256,0,s>>>(x,tmp,E);\n" } else { "\n" },
703 ));
704 if debug {
705 step.push_str(&format!(" gk_dbg3<<<1,32,0,s>>>(x,dbg+3*{l});\n"));
706 }
707 }
708
709 let prov = super::provenance::header(super::provenance::Provenance::HandWritten {
710 model: "Qwen3.8-27B",
711 lane: "generation",
712 });
713 format!(
714 "{prov}// Qwen3.8-27B generation.\n\
715 #include <cstdio>\n#include <cstdlib>\n#include <cstring>\n#include <cmath>\n#include <cuda_runtime.h>\n#include <cuda_fp16.h>\n\
716 {gdn_k}{q38_k}{gen_k}{extra_k}\n\
717 // The dimension macros come AFTER the kernels on purpose: the shared kernel set takes `int E` as a\n\
718 // parameter name, and a `#define E 5120` above it would rewrite that into `int 5120`.\n\
719 #define E {d}\n#define VOCAB {vocab}\n#define LAYERS {layers}\n#define MAXSEQ {maxseq}\n\
720 #define EPSV {eps:e}f\n#define ROPEV {rope:e}f\n\
721 static const float SCALEV = {scale}f;\n\
722 {decls}\n{state}\n\
723 static float* x; static float* x_f32; static float* x2n; static float* tmp;\n\
724 static float *pbuf,*cbuf,*obuf,*gnbuf,*gbuf,*betab,*decayb,*qbuf,*attnb,*ffbuf,*hfbuf,*skbuf,*logits;\n\
725 static float *acc_part,*m_part,*d_part;\n\
726 static signed char *aqE,*aqQ,*ffQ,*hqQ,*aqEl,*aqQl,*ffQl,*hqQl; static float *asE,*asQ,*ffS,*hqS;\n\
727 static int *d_pos,*d_tok,*d_zero; static float* dbg;\n\
728 static inline int Gw(int n){{ return (n*32+255)/256; }}\n\
729 static inline int Gw2(int n){{ return (((n+1)/2)*32+255)/256; }}\n\
730 static inline int Gs(int n){{ return (n+255)/256; }}\n\
731 #define LDQ(v,QN,SN,QP,SP) do{{ \\\n\
732 \x20 signed char* hq=(signed char*)malloc((size_t)QN); float* hs=(float*)malloc((size_t)SN*4); \\\n\
733 \x20 FILE* f=fopen(QP,\"rb\"); if(!f||fread(hq,1,(size_t)QN,f)!=(size_t)QN){{fprintf(stderr,\"load %s\\n\",QP);return 1;}} fclose(f); \\\n\
734 \x20 f=fopen(SP,\"rb\"); if(!f||fread(hs,4,(size_t)SN,f)!=(size_t)SN){{fprintf(stderr,\"load %s\\n\",SP);return 1;}} fclose(f); \\\n\
735 \x20 cudaMalloc(&v##_q,(size_t)QN); cudaMemcpy(v##_q,hq,(size_t)QN,cudaMemcpyHostToDevice); \\\n\
736 \x20 __half* hh=(__half*)malloc((size_t)(SN)*2); for(long i=0;i<(long)(SN);++i) hh[i]=__float2half(hs[i]); \\\n\
737 \x20 cudaMalloc(&v##_s,(size_t)(SN)*2); cudaMemcpy(v##_s,hh,(size_t)(SN)*2,cudaMemcpyHostToDevice); \\\n\
738 \x20 free(hq); free(hs); free(hh); }}while(0)\n\
739 #define LDF(v,N,P) do{{ \\\n\
740 \x20 float* h=(float*)malloc((size_t)N*4); FILE* f=fopen(P,\"rb\"); \\\n\
741 \x20 if(!f||fread(h,4,(size_t)N,f)!=(size_t)N){{fprintf(stderr,\"load %s\\n\",P);return 1;}} fclose(f); \\\n\
742 \x20 cudaMalloc(&v##_f,(size_t)N*4); cudaMemcpy(v##_f,h,(size_t)N*4,cudaMemcpyHostToDevice); free(h); }}while(0)\n\
743 static void decode_step(cudaStream_t s){{\n\
744 \x20 gk_embed_h<<<Gs(E),256,0,s>>>(x,emb_q,emb_s,d_tok,E);\n\
745 {step}\
746 \x20 {final_norm}\n\
747 \x20 {head}\n\
748 \x20 gk_argmax_adv<<<1,256,0,s>>>(logits,VOCAB,d_tok,d_pos);\n}}\n\
749 int main(){{\n{loads}\n{alloc}\n\
750 \x20 cudaMalloc(&x,E*4); cudaMalloc(&x_f32,E*4); cudaMalloc(&x2n,E*4); cudaMalloc(&tmp,E*4);\n\
751 \x20 cudaMalloc(&pbuf,{pmax}*4); cudaMalloc(&cbuf,{convc}*4); cudaMalloc(&obuf,{vw}*4); cudaMalloc(&gnbuf,{vw}*4);\n\
752 \x20 cudaMalloc(&gbuf,{two_nv}*4); cudaMalloc(&betab,{nv}*4); cudaMalloc(&decayb,{nv}*4);\n\
753 \x20 cudaMalloc(&qbuf,{aq}*4); cudaMalloc(&attnb,{aq}*4); cudaMalloc(&ffbuf,{two_dff}*4);\n\
754 \x20 cudaMalloc(&logits,(size_t)VOCAB*4); cudaMalloc(&hfbuf,(size_t){dff}*4);\n\
755 \x20 cudaMalloc(&skbuf,(size_t)8*8192*4);\n\
756 \x20 cudaMalloc(&acc_part,(size_t){h}*{p}*{hd}*4); cudaMalloc(&m_part,(size_t){h}*{p}*4); cudaMalloc(&d_part,(size_t){h}*{p}*4);\n\
757 \x20 cudaMalloc(&aqE,E); cudaMalloc(&asE,(size_t)(E/32)*4); cudaMalloc(&aqEl,E);\n\
758 \x20 cudaMalloc(&aqQ,{amax}); cudaMalloc(&asQ,(size_t)({amax}/32)*4); cudaMalloc(&aqQl,{amax});\n\
759 \x20 cudaMalloc(&ffQ,E); cudaMalloc(&ffS,(size_t)(E/32)*4); cudaMalloc(&ffQl,E);\n\
760 \x20 cudaMalloc(&hqQ,{dff}); cudaMalloc(&hqS,(size_t)({dff}/32)*4); cudaMalloc(&hqQl,{dff});\n\
761 \x20 cudaMalloc(&d_pos,4); cudaMalloc(&d_tok,4); cudaMalloc(&d_zero,4); cudaMalloc(&dbg,(size_t)(LAYERS+16)*12);\n\
762 \x20 {{ int z=0; cudaMemcpy(d_zero,&z,4,cudaMemcpyHostToDevice); }}\n\
763 \x20 cudaStream_t s; cudaStreamCreate(&s);\n\
764 \x20 static const int PROMPT[] = {{{prompt_c}}}; const int NP = sizeof(PROMPT)/sizeof(int);\n\
765 \x20 // ── prefill: the same step, one token at a time, growing the caches and the memories ──\n\
766 \x20 for(int i=0;i<NP;++i){{ int p=i; cudaMemcpy(d_pos,&p,4,cudaMemcpyHostToDevice);\n\
767 \x20 cudaMemcpy(d_tok,&PROMPT[i],4,cudaMemcpyHostToDevice); decode_step(s); cudaStreamSynchronize(s); {dbgrep} }}\n\
768 // The logit vector at the LAST prompt position -- the one that chooses the first generated
769 // token, and the same vector llama.cpp returns from `llama_get_logits_ith(n_prompt-1)`. Dumped
770 // raw so accuracy is settled against an f64 oracle rather than by whether two argmaxes agree.
771 \x20 if(const char* lp = getenv(\"LOGOS_Q38_LOGITS\")){{ static float hl[VOCAB];
772 \x20 cudaMemcpy(hl,logits,(size_t)VOCAB*4,cudaMemcpyDeviceToHost);
773 \x20 FILE* lf=fopen(lp,\"wb\"); if(lf){{ fwrite(hl,4,VOCAB,lf); fclose(lf);
774 \x20 fprintf(stderr,\"WROTE_LOGITS %s (%d)\\n\", lp, VOCAB); }} }}\n\
775 \x20 // ── capture ONE decode step and replay it: ~800 launches per token become one graph launch ──\n\
776 \x20 cudaGraph_t g; cudaGraphExec_t ge;\n\
777 \x20 if(cudaStreamBeginCapture(s,cudaStreamCaptureModeGlobal)!=cudaSuccess){{fprintf(stderr,\"capture\\n\");return 1;}}\n\
778 \x20 decode_step(s);\n\
779 \x20 if(cudaStreamEndCapture(s,&g)!=cudaSuccess){{fprintf(stderr,\"endcapture\\n\");return 1;}}\n\
780 \x20 if(cudaGraphInstantiate(&ge,g,0)!=cudaSuccess){{fprintf(stderr,\"inst\\n\");return 1;}}\n\
781 \x20 cudaEvent_t t0,t1; cudaEventCreate(&t0); cudaEventCreate(&t1);\n\
782 \x20 int tok=0; cudaMemcpy(&tok,d_tok,4,cudaMemcpyDeviceToHost);\n\
783 \x20 printf(\"GENERATED_TOKENS: %d\", tok);\n\
784 \x20 float genms=0.f; int steps=0;\n\
785 \x20 for(int i=1;i<{n_gen};++i){{ cudaEventRecord(t0,s); cudaGraphLaunch(ge,s); cudaEventRecord(t1,s);\n\
786 \x20 cudaStreamSynchronize(s); float ms; cudaEventElapsedTime(&ms,t0,t1); genms+=ms; steps++;\n\
787 \x20 cudaMemcpy(&tok,d_tok,4,cudaMemcpyDeviceToHost); printf(\" %d\", tok); }}\n\
788 \x20 printf(\"\\n\");\n\
789 \x20 if(steps) fprintf(stderr,\"GEN_TOK_S %.2f (%.3f ms/token, %d steps)\\n\", 1000.0*steps/genms, genms/steps, steps);\n\
790 \x20 {{ int z=0; cudaMemcpy(d_pos,&z,4,cudaMemcpyHostToDevice); cudaStreamSynchronize(s);\n\
791 \x20 cudaEventRecord(t0,s); for(int i=0;i<128;++i) cudaGraphLaunch(ge,s); cudaEventRecord(t1,s);\n\
792 \x20 cudaStreamSynchronize(s); float ms; cudaEventElapsedTime(&ms,t0,t1);\n\
793 \x20 fprintf(stderr,\"GEN_TOK_S_TG128 %.2f (128 replays from an empty cache = llama-bench tg128)\\n\", 128000.0/ms); }}\n\
794 \x20 return 0;\n}}\n",
795 vocab = cfg.vocab, layers = cfg.layers, eps = cfg.rms_eps, rope = cfg.rope_theta,
796 gdn_k = crate::codegen_cuda::gdn::GDN_KERNELS,
797 q38_k = crate::codegen_cuda::gdn::QWEN38_KERNELS,
798 final_norm = if deq == 3 {
799 "gk_rmsnorm_q8f_hl<<<1,256,0,s>>>(x,aqE,aqEl,asE,x_f32,on_f,E,EPSV);"
800 } else {
801 "gk_rmsnorm_q8f<<<1,256,0,s>>>(x,aqE,asE,x_f32,on_f,E,EPSV);"
802 },
803 head = if hl && unroll4 {
804 "gk_gemv_hlu4<<<Gw(VOCAB),256,0,s>>>(logits,Wout_q,Wout_s,aqE,aqEl,asE,E,VOCAB);"
805 } else if hl && unroll {
806 "gk_gemv_hlu<<<Gw(VOCAB),256,0,s>>>(logits,Wout_q,Wout_s,aqE,aqEl,asE,E,VOCAB);"
807 } else if hl && wide {
808 "gk_gemv_hl2<<<Gw2(VOCAB),256,0,s>>>(logits,Wout_q,Wout_s,aqE,aqEl,asE,E,VOCAB);"
809 } else if hl {
810 "gk_gemv_hl<<<Gw(VOCAB),256,0,s>>>(logits,Wout_q,Wout_s,aqE,aqEl,asE,E,VOCAB);"
811 } else if deq >= 1 {
812 "gk_gemv_deq_h<<<Gw(VOCAB),256,0,s>>>(logits,Wout_q,Wout_s,x_f32,E,VOCAB);"
813 } else {
814 "gk_gemv_h<<<Gw(VOCAB),256,0,s>>>(logits,Wout_q,Wout_s,aqE,asE,E,VOCAB);"
815 },
816 gen_k = crate::codegen_cuda::qwen_emit::GEN_DEVICE_KERNELS,
817 extra_k = QWEN38_EXTRA_KERNELS,
818 state = state_decl, alloc = state_alloc,
819 pmax = projw.max(attnw), two_nv = 2 * nv, two_dff = 2 * dff,
820 amax = vw.max(aq), h = cfg.h, hd = cfg.hd, p = attn_split,
821 dbgrep = if debug {
822 "if(i==0){ float hb[(LAYERS+16)*3]; cudaMemcpy(hb,dbg,(size_t)(LAYERS+16)*12,cudaMemcpyDeviceToHost); for(int L=0;L<4;++L) fprintf(stderr,\"DBG l_out-%d=[%.4f,%.4f,%.4f]\\n\",L,hb[3*L],hb[3*L+1],hb[3*L+2]); const char* nm[9]={\"embed\",\"xn0\",\"qkv0\",\"conv0\",\"beta\",\"decay\",\"gdnout\",\"gatenorm\",\"oproj\"}; for(int L=0;L<9;++L) fprintf(stderr,\"DBG %s=[%.6f,%.6f,%.6f]\\n\",nm[L],hb[3*(70+L)],hb[3*(70+L)+1],hb[3*(70+L)+2]); } if(i==NP-1){ float* al=(float*)malloc((size_t)VOCAB*4); cudaMemcpy(al,logits,(size_t)VOCAB*4,cudaMemcpyDeviceToHost); int bi=0; for(int L=1;L<VOCAB;++L) if(al[L]>al[bi]) bi=L; int rank=0; for(int L=0;L<VOCAB;++L) if(al[L]>al[11751]) rank++; fprintf(stderr,\"DBG argmax=%d(%.4g) golden11751=%.4g rank=%d\\n\",bi,al[bi],al[11751],rank); free(al); }"
823 } else { "" },
824 )
825}
826
827pub const QWEN38_EXTRA_KERNELS: &str = r#"
832
833// A plain argmax that does NOT advance the position — the MTP draft is a guess about the future, not a step
834// through it, so it must not touch the decode state it is speculating on.
835__global__ void gk_argmax_only(const float* logits, int vocab, int* out){
836 __shared__ float bv[256]; __shared__ int bi[256]; float bestv=-1e30f; int besti=0;
837 for(int i=threadIdx.x;i<vocab;i+=blockDim.x){ float v=logits[i]; if(v>bestv){bestv=v;besti=i;} }
838 bv[threadIdx.x]=bestv; bi[threadIdx.x]=besti; __syncthreads();
839 for(int k=blockDim.x/2;k>0;k>>=1){ if(threadIdx.x<k && bv[threadIdx.x+k]>bv[threadIdx.x]){ bv[threadIdx.x]=bv[threadIdx.x+k]; bi[threadIdx.x]=bi[threadIdx.x+k]; } __syncthreads(); }
840 if(threadIdx.x==0) *out=bi[0];
841}
842// Concatenate the two normed halves the nextn adapter consumes: [ rmsnorm(embed(t),enorm) | rmsnorm(h,hnorm) ].
843__global__ void gk_mtp_cat(float* __restrict__ out, const float* __restrict__ e, const float* __restrict__ h,
844 const float* __restrict__ we, const float* __restrict__ wh, int n, float eps){
845 __shared__ float sh[256];
846 const float* src = blockIdx.x == 0 ? e : h;
847 const float* w = blockIdx.x == 0 ? we : wh;
848 float* dst = out + (long)blockIdx.x * n;
849 float s=0.f; for(int i=threadIdx.x;i<n;i+=blockDim.x) s+=src[i]*src[i];
850 sh[threadIdx.x]=s; __syncthreads();
851 for(int k=blockDim.x/2;k>0;k>>=1){ if(threadIdx.x<k) sh[threadIdx.x]+=sh[threadIdx.x+k]; __syncthreads(); }
852 float sc=rsqrtf(sh[0]/n+eps);
853 for(int i=threadIdx.x;i<n;i+=blockDim.x) dst[i]=src[i]*sc*w[i];
854}
855
856
857// ══ SPLIT-K FOR THE NARROW PROJECTIONS ══════════════════════════════════════════════════════════
858// One warp per output row means the o-projection and the down-projection (both N=5120) launch only 640
859// blocks — 38.8 of an H100's 64 warps per SM, against 100% for the wide matrices. Those two are 31.7% of the
860// model's weights, so a third of every token runs at 61% occupancy. Splitting K into S chunks multiplies the
861// block count by S and fills the machine.
862//
863// The combine is a SEPARATE kernel summing the partials in FIXED ascending order, not an atomicAdd: float
864// addition is not associative, so an atomic would make the result depend on scheduler order and the same
865// prompt could produce different tokens on different runs. Determinism is not negotiable here.
866//
867// This is NOT bit-identical to the rolled form — chunking K re-associates the sum — so it is gated on the
868// golden token stream and on distance from the f64 oracle, never adopted on throughput alone.
869__global__ void gk_gemv_hl_sk(float* __restrict__ part, const signed char* __restrict__ wq,
870 const __half* __restrict__ ws, const signed char* __restrict__ xh,
871 const signed char* __restrict__ xl, const float* __restrict__ xs,
872 int K, int N, int S){
873 int o=(blockIdx.x*blockDim.x+threadIdx.x)>>5; if(o>=N) return;
874 int sl=blockIdx.y, lane=threadIdx.x&31;
875 const int4* w16=(const int4*)(wq+(long)o*K);
876 const int4* h16=(const int4*)xh; const int4* l16=(const int4*)xl;
877 const __half* wsr=ws+(long)o*(K/32);
878 int nq=K/512, per=(nq+S-1)/S, b0=sl*per, be=b0+per<nq?b0+per:nq;
879 float acc=0.f;
880 for(int b=b0;b<be;b++){
881 int4 w=w16[b*32+lane], hh=h16[b*32+lane], ll=l16[b*32+lane];
882 int blk=b*16+lane/2;
883 int dh=__dp4a(w.x,hh.x,0)+__dp4a(w.y,hh.y,0)+__dp4a(w.z,hh.z,0)+__dp4a(w.w,hh.w,0);
884 int dl=__dp4a(w.x,ll.x,0)+__dp4a(w.y,ll.y,0)+__dp4a(w.z,ll.z,0)+__dp4a(w.w,ll.w,0);
885 acc += ((float)dh + (float)dl*(1.0f/127.0f)) * (__half2float(wsr[blk])*xs[blk]);
886 }
887 if(sl==S-1){ int done=nq*512;
888 for(int j=done+lane;j<K;j+=32){ int blk=j/32;
889 float a=(float)xh[j] + (float)xl[j]*(1.0f/127.0f);
890 acc += a*(float)wq[(long)o*K+j]*(__half2float(wsr[blk])*xs[blk]); } }
891 for(int t=16;t>0;t>>=1) acc+=__shfl_down_sync(0xffffffffu,acc,t);
892 if(lane==0) part[(long)sl*N+o]=acc;
893}
894// Fixed ascending order over the K-chunks: same inputs always give the same bits.
895__global__ void gk_sk_combine(float* __restrict__ y, const float* __restrict__ part, int N, int S){
896 int o=blockIdx.x*blockDim.x+threadIdx.x; if(o>=N) return;
897 float a=0.f;
898 for(int s=0;s<S;++s) a+=part[(long)s*N+o];
899 y[o]=a;
900}
901
902
903// Four K-blocks in flight. Two measured +5.2% on the two-pass lane (38.30 -> 40.29); this is the same idea
904// pushed one step further, and stops there because the tail handling and register pressure both start to
905// cost more than the extra loads return. Accumulation order is unchanged, so it is still bit-identical.
906__global__ void gk_gemv_hlu4(float* y, const signed char* __restrict__ wq, const __half* __restrict__ ws,
907 const signed char* __restrict__ xh, const signed char* __restrict__ xl,
908 const float* __restrict__ xs, int K, int N){
909 int o=(blockIdx.x*blockDim.x+threadIdx.x)>>5; if(o>=N) return; int lane=threadIdx.x&31;
910 const int4* w16=(const int4*)(wq+(long)o*K);
911 const int4* h16=(const int4*)xh; const int4* l16=(const int4*)xl;
912 const __half* wsr=ws+(long)o*(K/32);
913 float acc=0.f; int nq=K/512; int b=0;
914 for(; b+3<nq; b+=4){
915 int4 w0=w16[b*32+lane], h0=h16[b*32+lane], l0=l16[b*32+lane];
916 int4 w1=w16[(b+1)*32+lane], h1=h16[(b+1)*32+lane], l1=l16[(b+1)*32+lane];
917 int4 w2=w16[(b+2)*32+lane], h2=h16[(b+2)*32+lane], l2=l16[(b+2)*32+lane];
918 int4 w3=w16[(b+3)*32+lane], h3=h16[(b+3)*32+lane], l3=l16[(b+3)*32+lane];
919 int k0=b*16+lane/2, k1=(b+1)*16+lane/2, k2=(b+2)*16+lane/2, k3=(b+3)*16+lane/2;
920 int a0=__dp4a(w0.x,h0.x,0)+__dp4a(w0.y,h0.y,0)+__dp4a(w0.z,h0.z,0)+__dp4a(w0.w,h0.w,0);
921 int c0=__dp4a(w0.x,l0.x,0)+__dp4a(w0.y,l0.y,0)+__dp4a(w0.z,l0.z,0)+__dp4a(w0.w,l0.w,0);
922 int a1=__dp4a(w1.x,h1.x,0)+__dp4a(w1.y,h1.y,0)+__dp4a(w1.z,h1.z,0)+__dp4a(w1.w,h1.w,0);
923 int c1=__dp4a(w1.x,l1.x,0)+__dp4a(w1.y,l1.y,0)+__dp4a(w1.z,l1.z,0)+__dp4a(w1.w,l1.w,0);
924 int a2=__dp4a(w2.x,h2.x,0)+__dp4a(w2.y,h2.y,0)+__dp4a(w2.z,h2.z,0)+__dp4a(w2.w,h2.w,0);
925 int c2=__dp4a(w2.x,l2.x,0)+__dp4a(w2.y,l2.y,0)+__dp4a(w2.z,l2.z,0)+__dp4a(w2.w,l2.w,0);
926 int a3=__dp4a(w3.x,h3.x,0)+__dp4a(w3.y,h3.y,0)+__dp4a(w3.z,h3.z,0)+__dp4a(w3.w,h3.w,0);
927 int c3=__dp4a(w3.x,l3.x,0)+__dp4a(w3.y,l3.y,0)+__dp4a(w3.z,l3.z,0)+__dp4a(w3.w,l3.w,0);
928 acc += ((float)a0 + (float)c0*(1.0f/127.0f)) * (__half2float(wsr[k0])*xs[k0]);
929 acc += ((float)a1 + (float)c1*(1.0f/127.0f)) * (__half2float(wsr[k1])*xs[k1]);
930 acc += ((float)a2 + (float)c2*(1.0f/127.0f)) * (__half2float(wsr[k2])*xs[k2]);
931 acc += ((float)a3 + (float)c3*(1.0f/127.0f)) * (__half2float(wsr[k3])*xs[k3]);
932 }
933 for(; b<nq; b++){
934 int4 w=w16[b*32+lane], hh=h16[b*32+lane], ll=l16[b*32+lane]; int blk=b*16+lane/2;
935 int dh=__dp4a(w.x,hh.x,0)+__dp4a(w.y,hh.y,0)+__dp4a(w.z,hh.z,0)+__dp4a(w.w,hh.w,0);
936 int dl=__dp4a(w.x,ll.x,0)+__dp4a(w.y,ll.y,0)+__dp4a(w.z,ll.z,0)+__dp4a(w.w,ll.w,0);
937 acc += ((float)dh + (float)dl*(1.0f/127.0f)) * (__half2float(wsr[blk])*xs[blk]);
938 }
939 int done=nq*512;
940 for(int j=done+lane;j<K;j+=32){ int blk=j/32;
941 float a=(float)xh[j] + (float)xl[j]*(1.0f/127.0f);
942 acc += a*(float)wq[(long)o*K+j]*(__half2float(wsr[blk])*xs[blk]); }
943 for(int s=16;s>0;s>>=1) acc+=__shfl_down_sync(0xffffffffu,acc,s); if(lane==0) y[o]=acc;
944}
945
946
947// ══ TWO K-BLOCKS IN FLIGHT ══════════════════════════════════════════════════════════════════════
948// Widening in N (two output rows per warp) was MEASURED slower on A100 -- 41.10 against 42.28 -- because it
949// halves the warp count, and this GEMV needs the parallelism more than it needs the reuse. Unrolling in K
950// costs no warps: the same warp issues the loads for two consecutive 512-weight blocks before consuming
951// either, so there are twice as many loads in flight per warp with the same occupancy.
952//
953// The accumulation ORDER is deliberately unchanged -- one accumulator, blocks folded in ascending order --
954// so this is bit-identical to the rolled form and cannot move a token.
955__global__ void gk_gemv_hu(float* y, const signed char* __restrict__ wq, const __half* __restrict__ ws,
956 const signed char* __restrict__ xq, const float* __restrict__ xs, int K, int N){
957 int o=(blockIdx.x*blockDim.x+threadIdx.x)>>5; if(o>=N) return; int lane=threadIdx.x&31;
958 const int4* w16=(const int4*)(wq+(long)o*K); const int4* x16=(const int4*)xq;
959 const __half* wsr=ws+(long)o*(K/32);
960 float acc=0.f; int nq=K/512; int b=0;
961 for(; b+1<nq; b+=2){
962 int4 wa=w16[b*32+lane], xa=x16[b*32+lane];
963 int4 wb=w16[(b+1)*32+lane], xb=x16[(b+1)*32+lane];
964 int ba=b*16+lane/2, bb=(b+1)*16+lane/2;
965 int da=__dp4a(wa.x,xa.x,0)+__dp4a(wa.y,xa.y,0)+__dp4a(wa.z,xa.z,0)+__dp4a(wa.w,xa.w,0);
966 int db=__dp4a(wb.x,xb.x,0)+__dp4a(wb.y,xb.y,0)+__dp4a(wb.z,xb.z,0)+__dp4a(wb.w,xb.w,0);
967 acc += (float)da * (__half2float(wsr[ba])*xs[ba]);
968 acc += (float)db * (__half2float(wsr[bb])*xs[bb]);
969 }
970 for(; b<nq; b++){
971 int4 w=w16[b*32+lane], xx=x16[b*32+lane]; int blk=b*16+lane/2;
972 int d=__dp4a(w.x,xx.x,0)+__dp4a(w.y,xx.y,0)+__dp4a(w.z,xx.z,0)+__dp4a(w.w,xx.w,0);
973 acc += (float)d * (__half2float(wsr[blk])*xs[blk]);
974 }
975 int done=nq*512; for(int j=done+lane;j<K;j+=32){ int blk=j/32;
976 acc += (float)((int)xq[j]*(int)wq[(long)o*K+j])*(__half2float(wsr[blk])*xs[blk]); }
977 for(int s=16;s>0;s>>=1) acc+=__shfl_down_sync(0xffffffffu,acc,s); if(lane==0) y[o]=acc;
978}
979
980// The same unrolling for the two-pass lane, where each iteration already has three streams to keep in flight.
981__global__ void gk_gemv_hlu(float* y, const signed char* __restrict__ wq, const __half* __restrict__ ws,
982 const signed char* __restrict__ xh, const signed char* __restrict__ xl,
983 const float* __restrict__ xs, int K, int N){
984 int o=(blockIdx.x*blockDim.x+threadIdx.x)>>5; if(o>=N) return; int lane=threadIdx.x&31;
985 const int4* w16=(const int4*)(wq+(long)o*K);
986 const int4* h16=(const int4*)xh; const int4* l16=(const int4*)xl;
987 const __half* wsr=ws+(long)o*(K/32);
988 float acc=0.f; int nq=K/512; int b=0;
989 for(; b+1<nq; b+=2){
990 int4 wa=w16[b*32+lane], ha=h16[b*32+lane], la=l16[b*32+lane];
991 int4 wb=w16[(b+1)*32+lane], hb=h16[(b+1)*32+lane], lb=l16[(b+1)*32+lane];
992 int ba=b*16+lane/2, bb=(b+1)*16+lane/2;
993 int dha=__dp4a(wa.x,ha.x,0)+__dp4a(wa.y,ha.y,0)+__dp4a(wa.z,ha.z,0)+__dp4a(wa.w,ha.w,0);
994 int dla=__dp4a(wa.x,la.x,0)+__dp4a(wa.y,la.y,0)+__dp4a(wa.z,la.z,0)+__dp4a(wa.w,la.w,0);
995 int dhb=__dp4a(wb.x,hb.x,0)+__dp4a(wb.y,hb.y,0)+__dp4a(wb.z,hb.z,0)+__dp4a(wb.w,hb.w,0);
996 int dlb=__dp4a(wb.x,lb.x,0)+__dp4a(wb.y,lb.y,0)+__dp4a(wb.z,lb.z,0)+__dp4a(wb.w,lb.w,0);
997 acc += ((float)dha + (float)dla*(1.0f/127.0f)) * (__half2float(wsr[ba])*xs[ba]);
998 acc += ((float)dhb + (float)dlb*(1.0f/127.0f)) * (__half2float(wsr[bb])*xs[bb]);
999 }
1000 for(; b<nq; b++){
1001 int4 w=w16[b*32+lane], hh=h16[b*32+lane], ll=l16[b*32+lane]; int blk=b*16+lane/2;
1002 int dh=__dp4a(w.x,hh.x,0)+__dp4a(w.y,hh.y,0)+__dp4a(w.z,hh.z,0)+__dp4a(w.w,hh.w,0);
1003 int dl=__dp4a(w.x,ll.x,0)+__dp4a(w.y,ll.y,0)+__dp4a(w.z,ll.z,0)+__dp4a(w.w,ll.w,0);
1004 acc += ((float)dh + (float)dl*(1.0f/127.0f)) * (__half2float(wsr[blk])*xs[blk]);
1005 }
1006 int done=nq*512;
1007 for(int j=done+lane;j<K;j+=32){ int blk=j/32;
1008 float a=(float)xh[j] + (float)xl[j]*(1.0f/127.0f);
1009 acc += a*(float)wq[(long)o*K+j]*(__half2float(wsr[blk])*xs[blk]); }
1010 for(int s=16;s>0;s>>=1) acc+=__shfl_down_sync(0xffffffffu,acc,s); if(lane==0) y[o]=acc;
1011}
1012
1013
1014// ══ TWO OUTPUT ROWS PER WARP ════════════════════════════════════════════════════════════════════
1015// A batch-1 GEMV at these shapes is not bandwidth-saturated -- it is LATENCY bound: one row per warp gives
1016// each warp a single outstanding weight load per iteration, and the measured 1050 GB/s against an A100 that
1017// delivers ~1500 is the gap that leaves. Giving a warp two adjacent rows doubles the loads in flight, and
1018// the activation word (and its scale) is fetched once and used twice, which halves the L2 traffic the
1019// activation generates across the 7760 warps that read it.
1020__global__ void gk_gemv_h2(float* y, const signed char* __restrict__ wq, const __half* __restrict__ ws,
1021 const signed char* __restrict__ xq, const float* __restrict__ xs, int K, int N){
1022 int r=((blockIdx.x*blockDim.x+threadIdx.x)>>5)*2; if(r>=N) return; int lane=threadIdx.x&31;
1023 bool r1 = (r+1)<N;
1024 const int4* x16=(const int4*)xq;
1025 const int4* wa=(const int4*)(wq+(long)r*K);
1026 const int4* wb=(const int4*)(wq+(long)(r+1)*K);
1027 const __half* sa=ws+(long)r*(K/32); const __half* sb=ws+(long)(r+1)*(K/32);
1028 float acc0=0.f, acc1=0.f; int nq=K/512;
1029 for(int b=0;b<nq;b++){
1030 int4 xx=x16[b*32+lane]; int blk=b*16+lane/2; float xsv=xs[blk];
1031 int4 w0=wa[b*32+lane];
1032 int d0=__dp4a(w0.x,xx.x,0)+__dp4a(w0.y,xx.y,0)+__dp4a(w0.z,xx.z,0)+__dp4a(w0.w,xx.w,0);
1033 acc0 += (float)d0 * (__half2float(sa[blk])*xsv);
1034 if(r1){ int4 w1=wb[b*32+lane];
1035 int d1=__dp4a(w1.x,xx.x,0)+__dp4a(w1.y,xx.y,0)+__dp4a(w1.z,xx.z,0)+__dp4a(w1.w,xx.w,0);
1036 acc1 += (float)d1 * (__half2float(sb[blk])*xsv); }
1037 }
1038 int done=nq*512;
1039 for(int j=done+lane;j<K;j+=32){ int blk=j/32; float xv=(float)xq[j]*xs[blk];
1040 acc0 += xv*(float)wq[(long)r*K+j]*__half2float(sa[blk]);
1041 if(r1) acc1 += xv*(float)wq[(long)(r+1)*K+j]*__half2float(sb[blk]); }
1042 for(int s=16;s>0;s>>=1){ acc0+=__shfl_down_sync(0xffffffffu,acc0,s); acc1+=__shfl_down_sync(0xffffffffu,acc1,s); }
1043 if(lane==0){ y[r]=acc0; if(r1) y[r+1]=acc1; }
1044}
1045
1046// The same widening for the two-pass lane, where it pays double: the activation's HIGH and LOW words are both
1047// fetched once per iteration and used for both rows.
1048__global__ void gk_gemv_hl2(float* y, const signed char* __restrict__ wq, const __half* __restrict__ ws,
1049 const signed char* __restrict__ xh, const signed char* __restrict__ xl,
1050 const float* __restrict__ xs, int K, int N){
1051 int r=((blockIdx.x*blockDim.x+threadIdx.x)>>5)*2; if(r>=N) return; int lane=threadIdx.x&31;
1052 bool r1 = (r+1)<N;
1053 const int4* h16=(const int4*)xh; const int4* l16=(const int4*)xl;
1054 const int4* wa=(const int4*)(wq+(long)r*K);
1055 const int4* wb=(const int4*)(wq+(long)(r+1)*K);
1056 const __half* sa=ws+(long)r*(K/32); const __half* sb=ws+(long)(r+1)*(K/32);
1057 float acc0=0.f, acc1=0.f; int nq=K/512;
1058 for(int b=0;b<nq;b++){
1059 int4 hh=h16[b*32+lane], ll=l16[b*32+lane]; int blk=b*16+lane/2; float xsv=xs[blk];
1060 int4 w0=wa[b*32+lane];
1061 int dh0=__dp4a(w0.x,hh.x,0)+__dp4a(w0.y,hh.y,0)+__dp4a(w0.z,hh.z,0)+__dp4a(w0.w,hh.w,0);
1062 int dl0=__dp4a(w0.x,ll.x,0)+__dp4a(w0.y,ll.y,0)+__dp4a(w0.z,ll.z,0)+__dp4a(w0.w,ll.w,0);
1063 acc0 += ((float)dh0 + (float)dl0*(1.0f/127.0f)) * (__half2float(sa[blk])*xsv);
1064 if(r1){ int4 w1=wb[b*32+lane];
1065 int dh1=__dp4a(w1.x,hh.x,0)+__dp4a(w1.y,hh.y,0)+__dp4a(w1.z,hh.z,0)+__dp4a(w1.w,hh.w,0);
1066 int dl1=__dp4a(w1.x,ll.x,0)+__dp4a(w1.y,ll.y,0)+__dp4a(w1.z,ll.z,0)+__dp4a(w1.w,ll.w,0);
1067 acc1 += ((float)dh1 + (float)dl1*(1.0f/127.0f)) * (__half2float(sb[blk])*xsv); }
1068 }
1069 int done=nq*512;
1070 for(int j=done+lane;j<K;j+=32){ int blk=j/32;
1071 float a=((float)xh[j] + (float)xl[j]*(1.0f/127.0f))*xs[blk];
1072 acc0 += a*(float)wq[(long)r*K+j]*__half2float(sa[blk]);
1073 if(r1) acc1 += a*(float)wq[(long)(r+1)*K+j]*__half2float(sb[blk]); }
1074 for(int s=16;s>0;s>>=1){ acc0+=__shfl_down_sync(0xffffffffu,acc0,s); acc1+=__shfl_down_sync(0xffffffffu,acc1,s); }
1075 if(lane==0){ y[r]=acc0; if(r1) y[r+1]=acc1; }
1076}
1077
1078
1079// ══ TWO-PASS INT8 ACTIVATIONS ═══════════════════════════════════════════════════════════════════
1080// A batch-1 GEMV reads a whole weight matrix and ONE activation vector, so the activation costs no
1081// measurable bandwidth -- but rounding it onto a single int8 grid costs accuracy at every one of the 300+
1082// projections in a token. Splitting it into a high and a low int8 word represents it as
1083// a ~= s * (qh + ql/127), | a/s - (qh + ql/127) | <= 0.5/127
1084// which is 254x tighter, and keeps `dp4a`. That last part is the point: the obvious alternative -- feeding
1085// the GEMV f32 activations -- was MEASURED at 32.27 tok/s against 42.28, because one int-to-float convert
1086// per weight is quarter rate on this chip. Two words of int8 double the dp4a count instead, on a kernel
1087// that is memory bound, and the weight matrix is still read exactly once.
1088
1089__global__ void gk_gemv_hl(float* y, const signed char* __restrict__ wq, const __half* __restrict__ ws,
1090 const signed char* __restrict__ xh, const signed char* __restrict__ xl,
1091 const float* __restrict__ xs, int K, int N){
1092 int o=(blockIdx.x*blockDim.x+threadIdx.x)>>5; if(o>=N) return; int lane=threadIdx.x&31;
1093 const int4* w16=(const int4*)(wq+(long)o*K);
1094 const int4* h16=(const int4*)xh; const int4* l16=(const int4*)xl;
1095 const __half* wsr=ws+(long)o*(K/32);
1096 float acc=0.f; int nq=K/512;
1097 for(int b=0;b<nq;b++){
1098 int4 w=w16[b*32+lane], hh=h16[b*32+lane], ll=l16[b*32+lane];
1099 int blk=b*16+lane/2;
1100 int dh=__dp4a(w.x,hh.x,0)+__dp4a(w.y,hh.y,0)+__dp4a(w.z,hh.z,0)+__dp4a(w.w,hh.w,0);
1101 int dl=__dp4a(w.x,ll.x,0)+__dp4a(w.y,ll.y,0)+__dp4a(w.z,ll.z,0)+__dp4a(w.w,ll.w,0);
1102 acc += ((float)dh + (float)dl*(1.0f/127.0f)) * (__half2float(wsr[blk])*xs[blk]);
1103 }
1104 int done=nq*512;
1105 for(int j=done+lane;j<K;j+=32){ int blk=j/32;
1106 float a=(float)xh[j] + (float)xl[j]*(1.0f/127.0f);
1107 acc += a*(float)wq[(long)o*K+j]*(__half2float(wsr[blk])*xs[blk]); }
1108 for(int s=16;s>0;s>>=1) acc+=__shfl_down_sync(0xffffffffu,acc,s); if(lane==0) y[o]=acc;
1109}
1110
1111// The producers, each the single-word kernel with its final rounding replaced by the two-word split. The
1112// scale and every float value are computed exactly as before, so the high word is bit-identical to what the
1113// single-word lane produced and the low word is pure addition.
1114__global__ void gk_quant_act_hl(const float* x, signed char* xq, signed char* xl, float* xs, int K){
1115 int blk=blockIdx.x*(blockDim.x/32)+threadIdx.x/32; if((long)blk*32>=K) return; int lane=threadIdx.x&31;
1116 float v=x[blk*32+lane], a=fabsf(v);
1117 for(int s=16;s>0;s>>=1) a=fmaxf(a,__shfl_down_sync(0xffffffffu,a,s)); a=__shfl_sync(0xffffffffu,a,0);
1118 float sc=a>0?a/127.0f:1.0f; if(lane==0) xs[blk]=sc;
1119 lgx_q8hl(v/sc, xq, xl, (long)blk*32+lane);
1120}
1121__global__ void gk_rmsnorm_q8f_hl(const float* __restrict__ x, signed char* __restrict__ xq,
1122 signed char* __restrict__ xl, float* __restrict__ xs,
1123 float* __restrict__ xf, const float* __restrict__ w, int n, float eps){
1124 __shared__ float sh[256];
1125 float s=0.f; for(int i=threadIdx.x;i<n;i+=blockDim.x) s+=x[i]*x[i];
1126 sh[threadIdx.x]=s; __syncthreads();
1127 for(int k=blockDim.x/2;k>0;k>>=1){ if(threadIdx.x<k) sh[threadIdx.x]+=sh[threadIdx.x+k]; __syncthreads(); }
1128 float sc=rsqrtf(sh[0]/n+eps); __syncthreads();
1129 for(int i=threadIdx.x;i<n;i+=blockDim.x) xf[i]=x[i]*sc*w[i];
1130 __syncthreads();
1131 int nb=n/32;
1132 for(int b=threadIdx.x;b<nb;b+=blockDim.x){
1133 float amax=1e-30f;
1134 for(int j=0;j<32;++j){ float a=fabsf(xf[b*32+j]); if(a>amax) amax=a; }
1135 float q=amax/127.0f; xs[b]=q;
1136 for(int j=0;j<32;++j) lgx_q8hl(xf[b*32+j]/q, xq, xl, (long)b*32+j);
1137 }
1138}
1139__global__ void gk_add_rmsnorm_q8f_hl(float* __restrict__ x, const float* __restrict__ y,
1140 signed char* __restrict__ xq, signed char* __restrict__ xl,
1141 float* __restrict__ xs, float* __restrict__ xf,
1142 const float* __restrict__ w, int n, float eps){
1143 __shared__ float sh[256];
1144 float s=0.f;
1145 for(int i=threadIdx.x;i<n;i+=blockDim.x){ float v=x[i]+y[i]; x[i]=v; s+=v*v; }
1146 sh[threadIdx.x]=s; __syncthreads();
1147 for(int k=blockDim.x/2;k>0;k>>=1){ if(threadIdx.x<k) sh[threadIdx.x]+=sh[threadIdx.x+k]; __syncthreads(); }
1148 float sc=rsqrtf(sh[0]/n+eps); __syncthreads();
1149 for(int i=threadIdx.x;i<n;i+=blockDim.x) xf[i]=x[i]*sc*w[i];
1150 __syncthreads();
1151 int nb=n/32;
1152 for(int b=threadIdx.x;b<nb;b+=blockDim.x){
1153 float amax=1e-30f;
1154 for(int j=0;j<32;++j){ float a=fabsf(xf[b*32+j]); if(a>amax) amax=a; }
1155 float q=amax/127.0f; xs[b]=q;
1156 for(int j=0;j<32;++j) lgx_q8hl(xf[b*32+j]/q, xq, xl, (long)b*32+j);
1157 }
1158}
1159__global__ void gk_add_rmsnorm_q8_hl(float* x, const float* y, float* out, const float* w,
1160 signed char* oq, signed char* ol, float* os_, int n, float eps){
1161 __shared__ float sh[256]; float s=0; for(int i=threadIdx.x;i<n;i+=blockDim.x){ float v=x[i]+y[i]; x[i]=v; s+=v*v; }
1162 sh[threadIdx.x]=s; __syncthreads();
1163 for(int k=blockDim.x/2;k>0;k>>=1){ if(threadIdx.x<k) sh[threadIdx.x]+=sh[threadIdx.x+k]; __syncthreads(); }
1164 float scale=rsqrtf(sh[0]/n+eps); for(int i=threadIdx.x;i<n;i+=blockDim.x) out[i]=x[i]*scale*w[i];
1165 __syncthreads();
1166 int warp=threadIdx.x>>5, lane=threadIdx.x&31, nb=n/32;
1167 for(int b=warp;b<nb;b+=(blockDim.x>>5)){ float v=out[b*32+lane], av=fabsf(v);
1168 for(int t=16;t>0;t>>=1) av=fmaxf(av,__shfl_down_sync(0xffffffffu,av,t));
1169 av=__shfl_sync(0xffffffffu,av,0); float amax=av>1e-30f?av:1e-30f; float qs=amax/127.0f;
1170 if(lane==0) os_[b]=qs; lgx_q8hl(v/qs, oq, ol, (long)b*32+lane); }
1171}
1172__global__ void gk_silu_had_q8_hl(const float* __restrict__ g, const float* __restrict__ u,
1173 signed char* __restrict__ hq, signed char* __restrict__ hl_,
1174 float* __restrict__ hs, int n){
1175 int blk = blockIdx.x * blockDim.x + threadIdx.x; int nb = n / 32; if(blk >= nb) return;
1176 float amax = 1e-30f; float t[32];
1177 for(int j = 0; j < 32; ++j){ float gv = g[blk*32+j]; float h = (gv / (1.0f + __expf(-gv))) * u[blk*32+j];
1178 t[j] = h; float a = fabsf(h); if(a > amax) amax = a; }
1179 float sc = amax / 127.0f; hs[blk] = sc;
1180 for(int j = 0; j < 32; ++j) lgx_q8hl(t[j]/sc, hq, hl_, (long)blk*32+j);
1181}
1182
1183
1184// The Q8_0 GEMV that keeps its ACTIVATION in f32: the weights are still int8 with a __half block scale, so
1185// the memory traffic — the thing that decides batch-1 decode time — is byte for byte the same as the dp4a
1186// form. Only the arithmetic changes, from a quarter of a dp4a per weight to about three float ops, which
1187// leaves the kernel comfortably memory bound. What it removes is the activation quantization error, which at
1188// batch 1 was never buying anything: one activation vector against a whole weight matrix is not where the
1189// bandwidth goes.
1190__global__ void gk_gemv_deq_h(float* y, const signed char* __restrict__ wq, const __half* __restrict__ ws,
1191 const float* __restrict__ x, int K, int N){
1192 int o=(blockIdx.x*blockDim.x+threadIdx.x)>>5; if(o>=N) return; int lane=threadIdx.x&31;
1193 const int4* w16=(const int4*)(wq+(long)o*K); const __half* wsr=ws+(long)o*(K/32);
1194 const float4* x4=(const float4*)x;
1195 float acc=0.f; int nq=K/512;
1196 // The four weight words stay in REGISTERS and are unpacked by shifting. Reading them back through a
1197 // `(signed char*)&wv` alias instead puts the int4 in local memory, and the resulting spill cost 12% of
1198 // throughput -- on a kernel whose whole premise was that it would be free.
1199#define LGX_W(W,S) ((float)(int)(signed char)(((W)>>(S))&0xff))
1200 for(int b=0;b<nq;b++){
1201 int4 wv=w16[b*32+lane];
1202 int base4=b*128+lane*4;
1203 float4 a0=x4[base4], a1=x4[base4+1], a2=x4[base4+2], a3=x4[base4+3];
1204 float p = LGX_W(wv.x,0)*a0.x + LGX_W(wv.x,8)*a0.y + LGX_W(wv.x,16)*a0.z + LGX_W(wv.x,24)*a0.w
1205 + LGX_W(wv.y,0)*a1.x + LGX_W(wv.y,8)*a1.y + LGX_W(wv.y,16)*a1.z + LGX_W(wv.y,24)*a1.w
1206 + LGX_W(wv.z,0)*a2.x + LGX_W(wv.z,8)*a2.y + LGX_W(wv.z,16)*a2.z + LGX_W(wv.z,24)*a2.w
1207 + LGX_W(wv.w,0)*a3.x + LGX_W(wv.w,8)*a3.y + LGX_W(wv.w,16)*a3.z + LGX_W(wv.w,24)*a3.w;
1208 acc += p*__half2float(wsr[b*16+lane/2]);
1209 }
1210#undef LGX_W
1211 int done=nq*512;
1212 for(int j=done+lane;j<K;j+=32){ int blk=j/32; acc += (float)wq[(long)o*K+j]*__half2float(wsr[blk])*x[j]; }
1213 for(int s=16;s>0;s>>=1) acc+=__shfl_down_sync(0xffffffffu,acc,s); if(lane==0) y[o]=acc;
1214}
1215
1216
1217// The Q8_0 GEMV with the block scale read as __half — identical arithmetic, 2 bytes per 32 weights instead
1218// of 4. At batch 1 the whole decode is a stream of these, so the 5.6% of weight traffic this returns is
1219// 5.6% off the token time. The scale was an f16 in the checkpoint; widening it on the way to the GPU only
1220// ever cost bandwidth.
1221__global__ void gk_gemv_h(float* y, const signed char* __restrict__ wq, const __half* __restrict__ ws,
1222 const signed char* __restrict__ xq, const float* __restrict__ xs, int K, int N){
1223 int o=(blockIdx.x*blockDim.x+threadIdx.x)>>5; if(o>=N) return; int lane=threadIdx.x&31;
1224 const int4* w16=(const int4*)(wq+(long)o*K); const int4* x16=(const int4*)xq; const __half* wsr=ws+(long)o*(K/32);
1225 float acc=0.f; int nq=K/512;
1226 for(int b=0;b<nq;b++){ int4 w=w16[b*32+lane], xx=x16[b*32+lane]; int blk=b*16+lane/2;
1227 int d=__dp4a(w.x,xx.x,0)+__dp4a(w.y,xx.y,0)+__dp4a(w.z,xx.z,0)+__dp4a(w.w,xx.w,0);
1228 acc += (float)d * (__half2float(wsr[blk])*xs[blk]); }
1229 int done=nq*512; for(int j=done+lane;j<K;j+=32){ int blk=j/32;
1230 acc += (float)((int)xq[j]*(int)wq[(long)o*K+j])*(__half2float(wsr[blk])*xs[blk]); }
1231 for(int s=16;s>0;s>>=1) acc+=__shfl_down_sync(0xffffffffu,acc,s); if(lane==0) y[o]=acc;
1232}
1233__global__ void gk_embed_h(float* x, const signed char* wq, const __half* ws, const int* d_tok, int E){
1234 int t=blockIdx.x*blockDim.x+threadIdx.x; if(t>=E) return;
1235 long tok=*d_tok; x[t]=(float)wq[tok*E+t]*__half2float(ws[tok*(E/32)+t/32]);
1236}
1237
1238// RMSNorm that emits BOTH the int8-quantized activation (for the big Q8_0 GEMVs) and the f32 normed vector
1239// (for GDN's two small f32-weight gate projections). Emitting both from one pass costs a 20 KB store and
1240// saves re-reading and re-normalizing the hidden state.
1241// The same, with the RESIDUAL ADD fused in. Every layer but the first is entered from a residual add, and
1242// the add is a full pass over the hidden state that the norm is about to read again — so the layer entry
1243// folds `x += y` into the reduction it already does. Removes one launch and one round-trip per layer.
1244__global__ void gk_add_rmsnorm_q8f(float* __restrict__ x, const float* __restrict__ y,
1245 signed char* __restrict__ xq, float* __restrict__ xs,
1246 float* __restrict__ xf, const float* __restrict__ w, int n, float eps){
1247 __shared__ float sh[256];
1248 float s = 0.f;
1249 for(int i = threadIdx.x; i < n; i += blockDim.x){ float v = x[i] + y[i]; x[i] = v; s += v * v; }
1250 sh[threadIdx.x] = s; __syncthreads();
1251 for(int k = blockDim.x / 2; k > 0; k >>= 1){ if(threadIdx.x < k) sh[threadIdx.x] += sh[threadIdx.x + k]; __syncthreads(); }
1252 float sc = rsqrtf(sh[0] / n + eps); __syncthreads();
1253 for(int i = threadIdx.x; i < n; i += blockDim.x) xf[i] = x[i] * sc * w[i];
1254 __syncthreads();
1255 int nb = n / 32;
1256 for(int b = threadIdx.x; b < nb; b += blockDim.x){
1257 float amax = 1e-30f;
1258 for(int j = 0; j < 32; ++j){ float a = fabsf(xf[b*32+j]); if(a > amax) amax = a; }
1259 float q = amax / 127.0f; xs[b] = q;
1260 for(int j = 0; j < 32; ++j) xq[b*32+j] = (signed char)__float2int_rn(xf[b*32+j] / q);
1261 }
1262}
1263__global__ void gk_rmsnorm_q8f(const float* __restrict__ x, signed char* __restrict__ xq, float* __restrict__ xs,
1264 float* __restrict__ xf, const float* __restrict__ w, int n, float eps){
1265 __shared__ float sh[256];
1266 float s = 0.f; for(int i = threadIdx.x; i < n; i += blockDim.x) s += x[i] * x[i];
1267 sh[threadIdx.x] = s; __syncthreads();
1268 for(int k = blockDim.x / 2; k > 0; k >>= 1){ if(threadIdx.x < k) sh[threadIdx.x] += sh[threadIdx.x + k]; __syncthreads(); }
1269 float sc = rsqrtf(sh[0] / n + eps); __syncthreads();
1270 for(int i = threadIdx.x; i < n; i += blockDim.x) xf[i] = x[i] * sc * w[i];
1271 __syncthreads();
1272 int nb = n / 32;
1273 for(int b = threadIdx.x; b < nb; b += blockDim.x){
1274 float amax = 1e-30f;
1275 for(int j = 0; j < 32; ++j){ float a = fabsf(xf[b*32+j]); if(a > amax) amax = a; }
1276 float q = amax / 127.0f; xs[b] = q;
1277 for(int j = 0; j < 32; ++j) xq[b*32+j] = (signed char)__float2int_rn(xf[b*32+j] / q);
1278 }
1279}
1280// Diagnostic: L2 norm of a vector, so a debug build can report where the hidden state collapses or explodes.
1281__global__ void gk_dbg3(const float* __restrict__ v, float* __restrict__ out){
1282 if(threadIdx.x < 3) out[threadIdx.x] = v[threadIdx.x];
1283}
1284__global__ void gk_l2probe(const float* __restrict__ v, int n, float* __restrict__ out){
1285 __shared__ float sh[256]; float s = 0.f;
1286 for(int i = threadIdx.x; i < n; i += blockDim.x) s += v[i] * v[i];
1287 sh[threadIdx.x] = s; __syncthreads();
1288 for(int k = blockDim.x / 2; k > 0; k >>= 1){ if(threadIdx.x < k) sh[threadIdx.x] += sh[threadIdx.x + k]; __syncthreads(); }
1289 if(threadIdx.x == 0) *out = sqrtf(sh[0]);
1290}
1291__global__ void gk_gemv_f32w(float* __restrict__ y, const float* __restrict__ w,
1292 const float* __restrict__ x, int K, int N){
1293 int o = (blockIdx.x * blockDim.x + threadIdx.x) >> 5; if(o >= N) return;
1294 int lane = threadIdx.x & 31;
1295 const float* wr = w + (long)o * K;
1296 float acc = 0.f;
1297 for(int j = lane; j < K; j += 32) acc += wr[j] * x[j];
1298 for(int s = 16; s > 0; s >>= 1) acc += __shfl_down_sync(0xffffffffu, acc, s);
1299 if(lane == 0) y[o] = acc;
1300}
1301// Split the fused projection's k|v halves into their caches at the current position.
1302__global__ void gk_kv_store(float* __restrict__ kc, float* __restrict__ vc, const int* __restrict__ d_pos,
1303 int KD, const float* __restrict__ kv){
1304 int i = blockIdx.x * blockDim.x + threadIdx.x; if(i >= 2 * KD) return;
1305 long base = (long)(*d_pos) * KD;
1306 if(i < KD) kc[base + i] = kv[i]; else vc[base + (i - KD)] = kv[i];
1307}
1308// silu(gate) * up, with the int8 re-quantization fused into the epilogue.
1309// The same SwiGLU epilogue, but leaving the result in f32 instead of an int8 grid. Used by the deq lane,
1310// where the projection that consumes it reads f32 activations.
1311__global__ void gk_silu_had_f(const float* __restrict__ g, const float* __restrict__ u,
1312 float* __restrict__ out, int n){
1313 int i = blockIdx.x * blockDim.x + threadIdx.x; if(i >= n) return;
1314 float gv = g[i];
1315 out[i] = (gv / (1.0f + __expf(-gv))) * u[i];
1316}
1317__global__ void gk_silu_had_q8(const float* __restrict__ g, const float* __restrict__ u,
1318 signed char* __restrict__ hq, float* __restrict__ hs, int n){
1319 int blk = blockIdx.x * blockDim.x + threadIdx.x; int nb = n / 32; if(blk >= nb) return;
1320 float amax = 1e-30f; float t[32];
1321 for(int j = 0; j < 32; ++j){ float gv = g[blk*32+j]; float h = (gv / (1.0f + __expf(-gv))) * u[blk*32+j];
1322 t[j] = h; float a = fabsf(h); if(a > amax) amax = a; }
1323 float sc = amax / 127.0f; hs[blk] = sc;
1324 for(int j = 0; j < 32; ++j) hq[blk*32+j] = (signed char)__float2int_rn(t[j] / sc);
1325}
1326"#;
1327
1328pub fn emit_qwen38_mtp_probe(
1344 cfg: &Qwen38Cfg,
1345 wdir: &str,
1346 prompt: &[usize],
1347 golden: &[usize],
1348) -> String {
1349 let base = emit_qwen38_generate(cfg, wdir, prompt, golden.len(), true, 3);
1350 let (d, dff) = (cfg.d, cfg.dff);
1351 let (aq, akv) = (cfg.aq(), cfg.akv());
1352 let attnw = 2 * aq + 2 * akv;
1353 let l = cfg.layers; let two_d = 2 * d;
1355
1356 let mut decls = String::new();
1358 let mut loads = String::new();
1359 for (v, rows, inn, f) in [
1360 (format!("qgkv{l}"), attnw, d, format!("qgkv{l}")),
1361 (format!("Wo{l}"), d, aq, format!("Wo{l}")),
1362 (format!("gu{l}"), 2 * dff, d, format!("gu{l}")),
1363 (format!("Wd{l}"), d, dff, format!("Wd{l}")),
1364 ("ehproj".into(), d, two_d, "ehproj".into()),
1365 ] {
1366 decls.push_str(&format!(
1367 "static signed char* {v}_q; static __half* {v}_s;\n"
1368 ));
1369 loads.push_str(&format!(
1370 " LDQ({v},(size_t){rows}*{inn},(size_t){rows}*({inn}/32),\"{wdir}/{f}.q\",\"{wdir}/{f}.s\");\n"));
1371 }
1372 for (v, n, f) in [
1373 (format!("an{l}"), d, format!("an{l}")),
1374 (format!("fn{l}"), d, format!("fn{l}")),
1375 (format!("Qn{l}"), cfg.hd, format!("Qn{l}")),
1376 (format!("Kn{l}"), cfg.hd, format!("Kn{l}")),
1377 ("enorm".into(), d, "enorm".into()),
1378 ("hnorm".into(), d, "hnorm".into()),
1379 ("shnorm".into(), d, "shnorm".into()),
1380 ] {
1381 decls.push_str(&format!("static float* {v}_f;\n"));
1382 loads.push_str(&format!(" LDF({v},(size_t){n},\"{wdir}/{f}.f\");\n"));
1383 }
1384
1385 let draft = format!(
1386"static float *mx,*mtmp,*m2n,*mcat,*membed,*mf32,*dlogits,*mkc,*mvc;
1387static signed char *maq,*maql; static float *mas;
1388static int *d_draft;
1389{decls}
1390// One draft step: the nextn adapter, then blk.{l} as an ordinary gated-attention layer + FFN, then the
1391// model's own output head. `x` is the main model's residual stream — the hidden state MTP conditions on.
1392static void mtp_draft(cudaStream_t s){{
1393 gk_embed_h<<<Gs(E),256,0,s>>>(membed,emb_q,emb_s,d_tok,E);
1394 gk_mtp_cat<<<2,256,0,s>>>(mcat,membed,x,enorm_f,hnorm_f,E,EPSV);
1395 gk_quant_act_hl<<<Gs({two_d}),256,0,s>>>(mcat,maq,maql,mas,{two_d});
1396 gk_gemv_hl<<<Gw(E),256,0,s>>>(mx,ehproj_q,ehproj_s,maq,maql,mas,{two_d},E);
1397 gk_rmsnorm_q8f_hl<<<1,256,0,s>>>(mx,maq,maql,mas,mf32,an{l}_f,E,EPSV);
1398 gk_gemv_hl<<<Gw({attnw}),256,0,s>>>(pbuf,qgkv{l}_q,qgkv{l}_s,maq,maql,mas,E,{attnw});
1399 gk_kv_store<<<Gs({akv}*2),256,0,s>>>(mkc,mvc,d_pos,{akv},pbuf+{two_aq});
1400 gk_qk_norm_rope_part<<<{h},128,0,s>>>(pbuf,d_zero,0,Qn{l}_f,{h},{hd},{rot},ROPEV,EPSV);
1401 gk_qk_norm_rope_part<<<{kv},128,0,s>>>(mkc,d_pos,{akv},Kn{l}_f,{kv},{hd},{rot},ROPEV,EPSV);
1402 gk_attn<<<{h},256,(size_t)MAXSEQ*4,s>>>(attnb,pbuf,mkc,mvc,d_pos,{h},{kv},{hd},SCALEV);
1403 gk_gate_mul<<<Gs({aq}),256,0,s>>>(attnb,pbuf+{aq},{aq});
1404 gk_quant_act_hl<<<Gs({aq}),256,0,s>>>(attnb,maq,maql,mas,{aq});
1405 gk_gemv_hl<<<Gw(E),256,0,s>>>(mtmp,Wo{l}_q,Wo{l}_s,maq,maql,mas,{aq},E);
1406 gk_add_rmsnorm_q8_hl<<<1,256,0,s>>>(mx,mtmp,m2n,fn{l}_f,maq,maql,mas,E,EPSV);
1407 gk_gemv_hl<<<Gw({two_dff}),256,0,s>>>(ffbuf,gu{l}_q,gu{l}_s,maq,maql,mas,E,{two_dff});
1408 gk_silu_had_q8_hl<<<Gs({dff}),256,0,s>>>(ffbuf,ffbuf+{dff},maq,maql,mas,{dff});
1409 gk_gemv_hl<<<Gw(E),256,0,s>>>(mtmp,Wd{l}_q,Wd{l}_s,maq,maql,mas,{dff},E);
1410 gk_add<<<Gs(E),256,0,s>>>(mx,mtmp,E);
1411 gk_rmsnorm_q8f_hl<<<1,256,0,s>>>(mx,maq,maql,mas,mf32,shnorm_f,E,EPSV);
1412 gk_gemv_hl<<<Gw(VOCAB),256,0,s>>>(dlogits,Wout_q,Wout_s,maq,maql,mas,E,VOCAB);
1413 gk_argmax_only<<<1,256,0,s>>>(dlogits,VOCAB,d_draft);
1414}}
1415",
1416 two_aq = 2 * aq, two_dff = 2 * dff, h = cfg.h, kv = cfg.kv, hd = cfg.hd, rot = cfg.rope_dim);
1417
1418 let mscratch = two_d.max(dff).max(aq);
1419 let alloc = format!(
1420" cudaMalloc(&mx,(size_t)E*4); cudaMalloc(&mtmp,(size_t)E*4); cudaMalloc(&m2n,(size_t)E*4);
1421 cudaMalloc(&mcat,(size_t){two_d}*4); cudaMalloc(&membed,(size_t)E*4); cudaMalloc(&mf32,(size_t)E*4);
1422 cudaMalloc(&dlogits,(size_t)VOCAB*4); cudaMalloc(&d_draft,4);
1423 cudaMalloc(&mkc,(size_t)MAXSEQ*{akv}*4); cudaMalloc(&mvc,(size_t)MAXSEQ*{akv}*4);
1424 cudaMalloc(&maq,(size_t){mscratch}); cudaMalloc(&maql,(size_t){mscratch}); cudaMalloc(&mas,(size_t)({mscratch}/32)*4);
1425{loads}");
1426
1427 let mut out = base.replace(
1429 "static void decode_step(cudaStream_t s){",
1430 &format!("{draft}\nstatic void decode_step(cudaStream_t s){{"),
1431 );
1432 out = out.replace(
1433 " cudaStream_t s; cudaStreamCreate(&s);",
1434 &format!("{alloc}\n cudaStream_t s; cudaStreamCreate(&s);"),
1435 );
1436 let gl: Vec<String> = golden.iter().map(|t| t.to_string()).collect();
1437 let probe = format!(
1438" static const int GOLD[] = {{{}}}; const int NG = sizeof(GOLD)/sizeof(int);
1439 int accepted = 0, checked = 0;
1440 for(int i=0;i<NG;++i){{
1441 // the draft head speculates on the token the main model just produced
1442 mtp_draft(s);
1443 if(cudaStreamSynchronize(s)!=cudaSuccess){{ fprintf(stderr,\"MTP DRAFT FAILED: %s\\n\",cudaGetErrorString(cudaGetLastError())); return 1; }}
1444 int dt=0, mt=0; cudaMemcpy(&dt,d_draft,4,cudaMemcpyDeviceToHost);
1445 cudaMemcpy(&mt,d_tok,4,cudaMemcpyDeviceToHost);
1446 if(mt!=GOLD[i]){{ fprintf(stderr,\"MAIN MODEL DIVERGED at %d: got %d want %d — accept rate would be meaningless\\n\",i,mt,GOLD[i]); return 1; }}
1447 if(i+1<NG){{ ++checked; if(dt==GOLD[i+1]) ++accepted;
1448 printf(\"step %2d: main=%d draft=%d next_golden=%d %s\\n\", i, mt, dt, GOLD[i+1],
1449 dt==GOLD[i+1] ? \"ACCEPT\" : \"reject\"); }}
1450 if(i+1<NG){{ cudaGraphLaunch(ge,s); cudaStreamSynchronize(s); }}
1451 }}
1452 printf(\"MTP_ACCEPT_RATE %d/%d = %.3f\\n\", accepted, checked, checked? (double)accepted/checked : 0.0);
1453 {{ double f=25.85, dr=1.80, a = checked? (double)accepted/checked : 0.0;
1454 printf(\"MTP_PROJECTED_SPEEDUP %.3fx (draft is %.1f%% of a forward)\\n\", (1+a)*f/(f+dr), 100*dr/f); }}
1455", gl.join(","));
1456 if let Some(cut) = out.find(" printf(\"GENERATED_TOKENS:") {
1458 let end = out[cut..]
1459 .find("\n {")
1460 .map(|e| cut + e)
1461 .unwrap_or(out.len());
1462 out.replace_range(cut..end, &probe);
1463 }
1464 out
1465}