1use crate::dit::Proj;
29use crate::pool::Pool;
30use crate::qtensor::QTensor;
31use cortiq_core::CmfModel;
32use std::sync::Arc;
33
34struct Layer {
35 input_norm: Vec<f32>,
36 q: Proj,
37 k: Proj,
38 v: Proj,
39 o: Proj,
40 q_norm: Vec<f32>, k_norm: Vec<f32>,
42 post_attn_norm: Vec<f32>,
43 gate: Proj,
44 up: Proj,
45 down: Proj,
46}
47
48const GEMM_ATTN_MIN: usize = 256;
53
54pub struct Qwen3Encoder {
55 embed: QTensor,
56 layers: Vec<Layer>,
57 final_norm: Option<Vec<f32>>,
58 proj: Option<ClipProj>,
59 proj_vis: Option<ClipProj>,
65 pool: Option<Arc<Pool>>,
66 pub hidden: usize,
67 nh: usize,
68 nkv: usize,
69 hd: usize,
70 theta: f32,
71 eps: f64,
72 exact_q8: bool,
75 dev_ops: u8,
79}
80
81pub const PROJ_NAMES: [&str; 7] = ["q", "k", "v", "o", "gate", "up", "down"];
83
84fn rms_norm_into(x: &[f32], w: &[f32], eps: f64, dst: &mut [f32]) {
85 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
86 let inv = 1.0 / (ss + eps).sqrt();
87 for ((d, &v), &g) in dst.iter_mut().zip(x).zip(w) {
88 *d = (v as f64 * inv) as f32 * g;
89 }
90}
91
92fn silu(v: f32) -> f32 {
93 v / (1.0 + (-v).exp())
94}
95
96struct SendPtr(*mut f32);
97unsafe impl Send for SendPtr {}
98unsafe impl Sync for SendPtr {}
99impl SendPtr {
100 #[allow(clippy::mut_from_ref)]
102 unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
103 unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
104 }
105}
106
107fn erf(x: f64) -> f64 {
111 let s = if x < 0.0 { -1.0 } else { 1.0 };
112 let x = x.abs();
113 let t = 1.0 / (1.0 + 0.327_591_1 * x);
114 let y = 1.0
115 - (((((1.061_405_429 * t - 1.453_152_027) * t) + 1.421_413_741) * t - 0.284_496_736) * t
116 + 0.254_829_592)
117 * t
118 * (-x * x).exp();
119 s * y
120}
121
122fn gelu(v: f32) -> f32 {
123 (0.5 * v as f64 * (1.0 + erf(v as f64 / std::f64::consts::SQRT_2))) as f32
124}
125
126struct ProjMlp {
129 w0: Vec<f32>, b0: Vec<f32>,
131 w2: Vec<f32>, b2: Vec<f32>,
133 hidden: usize,
134}
135
136struct ClipProj {
147 w: Vec<f32>, mean_in: Vec<f32>,
149 std_in: Vec<f32>,
150 mean_out: Vec<f32>,
151 std_out: Vec<f32>,
152 sink_out: Vec<f32>,
153 mlp: Option<ProjMlp>,
154 d_in: usize,
155 d_out: usize,
156}
157
158impl ClipProj {
159 fn apply(&self, h: &[f32], n: usize, pool: Option<&Pool>) -> Vec<f32> {
161 self.apply_inner(h, n, pool, true)
162 }
163
164 fn apply_inner(&self, h: &[f32], n: usize, pool: Option<&Pool>, sink: bool) -> Vec<f32> {
167 let (di, dout) = (self.d_in, self.d_out);
168 let mut out = vec![0f32; n * dout];
169 let ptr = SendPtr(out.as_mut_ptr());
170 let work = |lo: usize, hi: usize| {
171 let mut xn = vec![0f32; di];
172 let mut g = vec![0f32; self.mlp.as_ref().map_or(0, |m| m.hidden)];
173 for p in lo..hi {
174 let hp = &h[p * di..(p + 1) * di];
175 for i in 0..di {
176 xn[i] = (hp[i] - self.mean_in[i]) / self.std_in[i];
177 }
178 let o = unsafe { ptr.row(p * dout, dout) };
180 o.fill(0.0);
181 for i in 0..di {
182 let x = xn[i];
183 let row = &self.w[i * dout..(i + 1) * dout];
184 for (d, &wv) in o.iter_mut().zip(row) {
185 *d += x * wv;
186 }
187 }
188 if let Some(m) = &self.mlp {
189 for (oi, gv) in g.iter_mut().enumerate() {
190 let row = &m.w0[oi * di..(oi + 1) * di];
191 let mut s = m.b0[oi];
192 for (&r, &x) in row.iter().zip(&xn) {
193 s += r * x;
194 }
195 *gv = gelu(s);
196 }
197 for (j, d) in o.iter_mut().enumerate() {
198 let row = &m.w2[j * m.hidden..(j + 1) * m.hidden];
199 let mut s = m.b2[j];
200 for (&r, &gv) in row.iter().zip(&g) {
201 s += r * gv;
202 }
203 *d += s;
204 }
205 }
206 for (j, d) in o.iter_mut().enumerate() {
207 *d = *d * self.std_out[j] + self.mean_out[j];
208 }
209 }
210 };
211 match pool {
212 Some(pl) => pl.run_rows(n, &work),
213 None => work(0, n),
214 }
215 if sink && n > 0 {
216 out[..dout].copy_from_slice(&self.sink_out);
217 }
218 out
219 }
220}
221
222pub struct ImageSpan {
225 pub start: usize,
226 pub len: usize,
227 pub merged_h: usize,
228 pub merged_w: usize,
229}
230
231impl Qwen3Encoder {
232 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
233 let cfg: serde_json::Value = serde_json::from_slice(
234 model
235 .tensor_bytes("te.config_json")
236 .map_err(|e| e.to_string())?,
237 )
238 .map_err(|e| format!("te.config_json: {e}"))?;
239 let u = |k: &str, d: usize| cfg[k].as_u64().map(|v| v as usize).unwrap_or(d);
240 let nl = match std::env::var("CMF_TE_TAP")
245 .ok()
246 .and_then(|v| v.parse().ok())
247 {
248 Some(t) if t > 0 && t <= u("num_hidden_layers", 0) => t,
249 _ => u("num_hidden_layers", 0),
250 };
251 let f32v = |n: &str| crate::dit::cmf_f32(model, n);
252 let mut layers = Vec::with_capacity(nl);
253 for l in 0..nl {
254 let p = format!("te.layers.{l}");
255 layers.push(Layer {
256 input_norm: f32v(&format!("{p}.input_layernorm.weight"))?,
257 q: Proj::from_model(model, &format!("{p}.self_attn.q_proj.weight"))?,
258 k: Proj::from_model(model, &format!("{p}.self_attn.k_proj.weight"))?,
259 v: Proj::from_model(model, &format!("{p}.self_attn.v_proj.weight"))?,
260 o: Proj::from_model(model, &format!("{p}.self_attn.o_proj.weight"))?,
261 q_norm: f32v(&format!("{p}.self_attn.q_norm.weight"))?,
262 k_norm: f32v(&format!("{p}.self_attn.k_norm.weight"))?,
263 post_attn_norm: f32v(&format!("{p}.post_attention_layernorm.weight"))?,
264 gate: Proj::from_model(model, &format!("{p}.mlp.gate_proj.weight"))?,
265 up: Proj::from_model(model, &format!("{p}.mlp.up_proj.weight"))?,
266 down: Proj::from_model(model, &format!("{p}.mlp.down_proj.weight"))?,
267 });
268 }
269 let hidden = u("hidden_size", 0);
270 let proj = match model.tensor_bytes("te.proj.config_json") {
271 Ok(b) => {
272 let pc: serde_json::Value =
273 serde_json::from_slice(b).map_err(|e| format!("te.proj.config_json: {e}"))?;
274 let pu = |k: &str| pc[k].as_u64().unwrap_or(0) as usize;
275 let (d_in, d_out) = (pu("d_in"), pu("d_out"));
276 if d_in != hidden {
277 return Err(format!(
278 "te.proj expects a {d_in}-wide encoder, the packed one is {hidden}: \
279 the projection and the encoder come from different models"
280 ));
281 }
282 let mlp = match pc["mlp"].as_bool() {
283 Some(true) => Some(ProjMlp {
284 w0: f32v("te.proj.mlp.0.weight")?,
285 b0: f32v("te.proj.mlp.0.bias")?,
286 w2: f32v("te.proj.mlp.2.weight")?,
287 b2: f32v("te.proj.mlp.2.bias")?,
288 hidden: pu("mlp_hidden"),
289 }),
290 _ => None,
291 };
292 Some(ClipProj {
293 w: f32v("te.proj.W")?,
294 mean_in: f32v("te.proj.mean_in")?,
295 std_in: f32v("te.proj.std_in")?,
296 mean_out: f32v("te.proj.mean_out")?,
297 std_out: f32v("te.proj.std_out")?,
298 sink_out: f32v("te.proj.sink_out")?,
299 mlp,
300 d_in,
301 d_out,
302 })
303 }
304 Err(_) => None,
305 };
306 let proj_vis = match (&proj, model.tensor_bytes("te.proj.vis.W").is_ok()) {
307 (Some(base), true) => {
308 let g = |n: &str| crate::dit::cmf_f32(model, n);
309 Some(ClipProj {
310 w: g("te.proj.vis.W")?,
311 mean_in: g("te.proj.vis.mean_in")?,
312 std_in: g("te.proj.vis.std_in")?,
313 mean_out: g("te.proj.vis.mean_out")?,
314 std_out: g("te.proj.vis.std_out")?,
315 sink_out: base.sink_out.clone(),
318 mlp: None,
319 d_in: base.d_in,
320 d_out: base.d_out,
321 })
322 }
323 _ => None,
324 };
325 Ok(Self {
326 embed: QTensor::from_model(model, "te.embed_tokens.weight")?,
327 layers,
328 final_norm: match cfg["final_norm"].as_bool() {
329 Some(false) | None => None,
330 Some(true) => Some(f32v("te.norm.weight")?),
331 },
332 proj,
333 proj_vis,
334 pool: Pool::from_env(),
335 hidden,
336 nh: u("num_attention_heads", 0),
337 nkv: u("num_key_value_heads", 0),
338 hd: u("head_dim", 128),
339 theta: cfg["rope_theta"].as_f64().unwrap_or(5e6) as f32,
340 eps: cfg["rms_norm_eps"].as_f64().unwrap_or(1e-6),
341 exact_q8: false,
342 dev_ops: 0,
343 })
344 }
345
346 pub fn set_exact_q8(&mut self, on: bool) {
350 self.exact_q8 = on;
351 }
352
353 pub fn set_device_ops(&mut self, spec: &str) {
357 self.dev_ops = 0;
358 for t in spec.split(',').map(str::trim) {
359 if t == "all" {
360 self.dev_ops = 0x7f;
361 } else if let Some(i) = PROJ_NAMES.iter().position(|n| *n == t) {
362 self.dev_ops |= 1 << i;
363 }
364 }
365 }
366
367 fn mm(&self, p: &Proj, which: usize, xs: &[f32], n: usize, out: &mut [f32], pool: Option<&Pool>) {
368 if self.dev_ops & (1 << which) != 0 {
369 if let Proj::Q(q) = p {
370 if q.device_matmat(&xs[..n * q.cols()], n, &mut out[..n * q.rows()]) {
371 return;
372 }
373 }
374 }
375 if self.exact_q8 && q8_exact_matmat(p, xs, n, out, pool).is_some() {
376 return;
377 }
378 if self.exact_q8 {
385 if let Proj::F32 { w, rows, cols } = p {
386 crate::fcd_ops::gemm_nt_host(xs, w, out, n, *cols, *rows, pool);
387 return;
388 }
389 }
390 p.matmat(xs, n, out, pool);
391 }
392
393 fn norm_rope(&self, all: &mut [f32], n: usize, heads: usize, w: &[f32], pos: &[[f32; 3]]) {
396 let hd = self.hd;
397 let half = hd / 2;
398 let pool = self.pool.as_deref();
399 let ptr = SendPtr(all.as_mut_ptr());
400 let work = |lo: usize, hi: usize| {
401 for p in lo..hi {
402 for h in 0..heads {
403 let x = unsafe { ptr.row((p * heads + h) * hd, hd) };
405 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / hd as f64;
406 let inv = 1.0 / (ss + self.eps).sqrt();
407 for (d, &g) in x.iter_mut().zip(w) {
408 *d = (*d as f64 * inv) as f32 * g;
409 }
410 for i in 0..half {
411 let freq = 1.0 / self.theta.powf(2.0 * i as f32 / hd as f32);
412 let axis = if i < 60 && i % 3 == 1 {
418 1
419 } else if i < 60 && i % 3 == 2 {
420 2
421 } else {
422 0
423 };
424 let (s, c) = (pos[p][axis] * freq).sin_cos();
425 let (a, b) = (x[i], x[i + half]);
426 x[i] = a * c - b * s;
427 x[i + half] = a * s + b * c;
428 }
429 }
430 }
431 };
432 match pool {
433 Some(pl) => pl.run_rows(n, &work),
434 None => work(0, n),
435 }
436 }
437
438 fn attention_gemm(&self, q_all: &[f32], k_all: &[f32], v_all: &[f32], n: usize, attn: &mut [f32]) {
444 use crate::zimage::host_gemm;
445 let (nh, nkv, hd) = (self.nh, self.nkv, self.hd);
446 let hpk = nh / nkv;
447 let pool = self.pool.as_deref();
448 let scale = 1.0 / (hd as f32).sqrt();
449 let mut qh = vec![0f32; n * hd];
450 let mut kh = vec![0f32; n * hd];
451 let mut vt = vec![0f32; hd * n];
452 let mut scores = vec![0f32; n * n];
453 let mut oh = vec![0f32; n * hd];
454 let mut kv_cur = usize::MAX;
455 for hh in 0..nh {
456 let kv = hh / hpk;
457 for p in 0..n {
458 for d in 0..hd {
459 qh[p * hd + d] = q_all[(p * nh + hh) * hd + d] * scale;
460 }
461 }
462 if kv != kv_cur {
463 kv_cur = kv;
464 for p in 0..n {
465 kh[p * hd..(p + 1) * hd].copy_from_slice(&k_all[(p * nkv + kv) * hd..(p * nkv + kv + 1) * hd]);
466 for d in 0..hd {
467 vt[d * n + p] = v_all[(p * nkv + kv) * hd + d];
468 }
469 }
470 }
471 host_gemm::gemm_nt(&qh, &kh, &mut scores, n, hd, n, pool);
472 {
473 let sp = SendPtr(scores.as_mut_ptr());
474 let soft = |lo: usize, hi: usize| {
475 for r in lo..hi {
476 let row = unsafe { sp.row(r * n, n) };
478 let (live, dead) = row.split_at_mut(r + 1);
479 let mx = live.iter().cloned().fold(f32::MIN, f32::max);
480 let mut den = 0f32;
481 for x in live.iter_mut() {
482 *x = (*x - mx).exp();
483 den += *x;
484 }
485 let inv = 1.0 / den;
486 for x in live.iter_mut() {
487 *x *= inv;
488 }
489 dead.fill(0.0);
490 }
491 };
492 match pool {
493 Some(pl) => pl.run_rows(n, &soft),
494 None => soft(0, n),
495 }
496 }
497 host_gemm::gemm_nt(&scores, &vt, &mut oh, n, n, hd, pool);
498 for p in 0..n {
499 attn[(p * nh + hh) * hd..(p * nh + hh + 1) * hd].copy_from_slice(&oh[p * hd..(p + 1) * hd]);
500 }
501 }
502 }
503
504 pub fn encode(&self, ids: &[u32]) -> Vec<f32> {
508 self.encode_with_images(ids, &[], &[], &[])
509 }
510
511 fn mrope_positions(&self, n: usize, spans: &[ImageSpan]) -> Vec<[f32; 3]> {
520 let mut pos: Vec<[f32; 3]> = (0..n).map(|i| [i as f32; 3]).collect();
521 let mut offset = 0i64;
522 for sp in spans {
523 let (start, end) = (sp.start, sp.start + sp.len);
524 let len_max = sp.merged_h.max(sp.merged_w) as i64;
525 let base = start as i64 + offset;
526 for i in start..end {
527 let k = i - start;
528 pos[i] = [
529 base as f32,
530 (base + (k / sp.merged_w) as i64) as f32,
531 (base + (k % sp.merged_w) as i64) as f32,
532 ];
533 }
534 let next = len_max + start as i64;
535 for (j, p) in pos.iter_mut().enumerate().skip(end) {
536 let v = (next + offset + (j - end) as i64) as f32;
537 *p = [v; 3];
538 }
539 offset += len_max - sp.len as i64;
540 }
541 pos
542 }
543
544 pub fn encode_with_images(
550 &self,
551 ids: &[u32],
552 spans: &[ImageSpan],
553 embeds: &[Vec<f32>],
554 deepstack: &[Vec<f32>],
555 ) -> Vec<f32> {
556 let n = ids.len();
557 let hs = self.hidden;
558 let (nh, nkv, hd) = (self.nh, self.nkv, self.hd);
559 let hpk = nh / nkv;
560 let pool = self.pool.as_deref();
561 let scale = 1.0 / (hd as f32).sqrt();
562
563 let mut h = vec![0f32; n * hs];
564 for (i, &id) in ids.iter().enumerate() {
565 self.embed
566 .row_f32(id as usize, &mut h[i * hs..(i + 1) * hs]);
567 }
568 for (sp, e) in spans.iter().zip(embeds) {
569 h[sp.start * hs..(sp.start + sp.len) * hs].copy_from_slice(&e[..sp.len * hs]);
570 }
571 let pos = self.mrope_positions(n, spans);
572 let visual: Vec<usize> = spans
574 .iter()
575 .flat_map(|s| s.start..s.start + s.len)
576 .collect();
577 let mut xn = vec![0f32; n * hs];
578 let mut q_all = vec![0f32; n * nh * hd];
579 let mut k_all = vec![0f32; n * nkv * hd];
580 let mut v_all = vec![0f32; n * nkv * hd];
581 let mut attn = vec![0f32; n * nh * hd];
582 let mut proj = vec![0f32; n * hs];
583
584 let taps = std::env::var("CMF_TE_TAPS").ok();
587 let tap = |name: String, v: &[f32]| {
588 if let Some(dir) = &taps {
589 let _ = std::fs::create_dir_all(dir);
590 let _ = std::fs::write(
591 std::path::Path::new(dir).join(format!("{name}.f32")),
592 v.iter().flat_map(|x| x.to_le_bytes()).collect::<Vec<u8>>(),
593 );
594 }
595 };
596 for (li, layer) in self.layers.iter().enumerate() {
597 for (o, src) in xn.chunks_exact_mut(hs).zip(h.chunks_exact(hs)) {
598 rms_norm_into(src, &layer.input_norm, self.eps, o);
599 }
600 self.mm(&layer.q, 0, &xn, n, &mut q_all, pool);
601 self.mm(&layer.k, 1, &xn, n, &mut k_all, pool);
602 self.mm(&layer.v, 2, &xn, n, &mut v_all, pool);
603 self.norm_rope(&mut q_all, n, nh, &layer.q_norm, &pos);
604 self.norm_rope(&mut k_all, n, nkv, &layer.k_norm, &pos);
605
606 attn.fill(0.0);
607 let ap = SendPtr(attn.as_mut_ptr());
608 let heads = |lo: usize, hi: usize| {
609 let mut row = vec![0f32; n];
610 for hh in lo..hi {
611 let kv = hh / hpk;
612 for p in 0..n {
613 let qv = &q_all[(p * nh + hh) * hd..(p * nh + hh + 1) * hd];
614 for (j, r) in row[..=p].iter_mut().enumerate() {
615 let kvv = &k_all[(j * nkv + kv) * hd..(j * nkv + kv + 1) * hd];
616 *r = qv.iter().zip(kvv).map(|(&a, &b)| a * b).sum::<f32>() * scale;
617 }
618 let mx = row[..=p].iter().cloned().fold(f32::MIN, f32::max);
619 let mut den = 0f32;
620 for r in row[..=p].iter_mut() {
621 *r = (*r - mx).exp();
622 den += *r;
623 }
624 let inv = 1.0 / den;
625 let out = unsafe { ap.row((p * nh + hh) * hd, hd) };
628 for (j, &rw) in row[..=p].iter().enumerate() {
629 let vv = &v_all[(j * nkv + kv) * hd..(j * nkv + kv + 1) * hd];
630 let wgt = rw * inv;
631 for (o, &s) in out.iter_mut().zip(vv) {
632 *o += wgt * s;
633 }
634 }
635 }
636 }
637 };
638 if n >= GEMM_ATTN_MIN {
639 self.attention_gemm(&q_all, &k_all, &v_all, n, &mut attn);
640 } else {
641 match pool {
642 Some(pl) => pl.run_rows(nh, &heads),
643 None => heads(0, nh),
644 }
645 }
646
647 tap(format!("layer{li}_q"), &q_all);
648 tap(format!("layer{li}_attn"), &attn);
649 self.mm(&layer.o, 3, &attn, n, &mut proj, pool);
650 for (d, &v) in h.iter_mut().zip(&proj) {
651 *d += v;
652 }
653 tap(format!("layer{li}_h_mid"), &h);
654
655 for (o, src) in xn.chunks_exact_mut(hs).zip(h.chunks_exact(hs)) {
656 rms_norm_into(src, &layer.post_attn_norm, self.eps, o);
657 }
658 let inter = layer.gate.rows();
659 let mut g = vec![0f32; n * inter];
660 let mut u = vec![0f32; n * inter];
661 self.mm(&layer.gate, 4, &xn, n, &mut g, pool);
662 self.mm(&layer.up, 5, &xn, n, &mut u, pool);
663 tap(format!("layer{li}_gate"), &g);
664 for (a, &b) in g.iter_mut().zip(&u) {
665 *a = silu(*a) * b;
666 }
667 tap(format!("layer{li}_act"), &g);
668 self.mm(&layer.down, 6, &g, n, &mut proj, pool);
669 tap(format!("layer{li}_down"), &proj);
670 for (d, &v) in h.iter_mut().zip(&proj) {
671 *d += v;
672 }
673 tap(format!("layer{li}"), &h);
674 if let Some(f) = deepstack.get(li) {
675 for (k, &row) in visual.iter().enumerate() {
676 for c in 0..hs {
677 h[row * hs + c] += f[k * hs + c];
678 }
679 }
680 }
681 }
682 if let Some(w) = &self.final_norm {
683 let mut out = vec![0f32; n * hs];
684 for (o, src) in out.chunks_exact_mut(hs).zip(h.chunks_exact(hs)) {
685 rms_norm_into(src, w, self.eps, o);
686 }
687 return out;
688 }
689 if let (Ok(path), Some(_)) = (std::env::var("CMF_CP_DUMP"), &self.proj) {
697 let mut b = Vec::with_capacity(16 + h.len() * 4);
698 b.extend_from_slice(&(n as u64).to_le_bytes());
699 b.extend_from_slice(&(self.hidden as u64).to_le_bytes());
700 for v in &h {
701 b.extend_from_slice(&v.to_le_bytes());
702 }
703 if let Err(e) = std::fs::write(&path, &b) {
704 tracing::warn!("CMF_CP_DUMP {path}: {e}");
705 } else {
706 eprintln!("cp dump: {n} tokens x {} -> {path}", self.hidden);
707 }
708 }
709 match &self.proj {
710 Some(p) => {
711 let mut out = p.apply(&h, n, self.pool.as_deref());
712 if let Some(pv) = &self.proj_vis {
715 if !visual.is_empty() {
716 let (din, dout) = (pv.d_in, pv.d_out);
717 let mut hv = Vec::with_capacity(visual.len() * din);
718 for &r in &visual {
719 hv.extend_from_slice(&h[r * din..(r + 1) * din]);
720 }
721 let ov = pv.apply_inner(&hv, visual.len(), self.pool.as_deref(), false);
722 for (i, &r) in visual.iter().enumerate() {
723 out[r * dout..(r + 1) * dout]
724 .copy_from_slice(&ov[i * dout..(i + 1) * dout]);
725 }
726 }
727 }
728 out
729 }
730 None => h,
731 }
732 }
733
734 pub fn out_hidden(&self) -> usize {
738 self.proj.as_ref().map_or(self.hidden, |p| p.d_out)
739 }
740}
741
742fn q8_exact_matmat(p: &Proj, xs: &[f32], n: usize, out: &mut [f32], pool: Option<&Pool>) -> Option<()> {
757 #[cfg(target_arch = "x86_64")]
758 {
759 use crate::qtensor::QTensor;
760 use cortiq_core::TensorDtype as T;
761 if !(std::arch::is_x86_feature_detected!("avx2") && std::arch::is_x86_feature_detected!("fma")) {
762 return None;
763 }
764 let Proj::Q(QTensor::Mapped { model, idx, dtype, rows, cols, row_scale, col_field, .. }) = p else {
765 return None;
766 };
767 if !matches!(dtype, T::Q8_2f | T::Q8Row) || row_scale.len() != *rows {
768 return None;
769 }
770 let (rows, cols) = (*rows, *cols);
771 let bytes = model.entry_bytes(&model.tensors[*idx]);
772 if bytes.len() < rows * cols || xs.len() < n * cols || out.len() < n * rows {
773 return None;
774 }
775 let xs2: Vec<f32> = if *dtype == T::Q8_2f && col_field.len() == cols {
777 xs[..n * cols].chunks_exact(cols).flat_map(|r| r.iter().zip(col_field).map(|(a, c)| a * c)).collect()
778 } else {
779 xs[..n * cols].to_vec()
780 };
781 let q = &bytes[..rows * cols];
782 let op = SendPtr(out.as_mut_ptr());
783 let work = |lo: usize, hi: usize| {
784 for o in lo..hi {
785 let wrow = &q[o * cols..(o + 1) * cols];
786 let mut b0 = 0;
787 while b0 < n {
788 let nb = (n - b0).min(4);
789 let mut acc = [0f32; 4];
790 unsafe { q8_dot4(wrow, &xs2[b0 * cols..], cols, nb, &mut acc) };
792 for (k, a) in acc.iter().take(nb).enumerate() {
793 unsafe { op.row((b0 + k) * rows + o, 1)[0] = a * row_scale[o] };
795 }
796 b0 += nb;
797 }
798 }
799 };
800 match pool {
801 Some(pl) => pl.run_rows(rows, &work),
802 None => work(0, rows),
803 }
804 Some(())
805 }
806 #[cfg(not(target_arch = "x86_64"))]
807 {
808 let _ = (p, xs, n, out, pool);
809 None
810 }
811}
812
813#[cfg(target_arch = "x86_64")]
815#[target_feature(enable = "avx2,fma")]
816unsafe fn q8_dot4(w: &[u8], xs: &[f32], cols: usize, nb: usize, acc: &mut [f32; 4]) {
817 use std::arch::x86_64::*;
818 unsafe {
819 let mut a = [_mm256_setzero_ps(); 4];
820 let wp = w.as_ptr() as *const i8;
821 let xp = xs.as_ptr();
822 let mut j = 0;
823 while j + 8 <= cols {
824 let wi = _mm_loadl_epi64(wp.add(j) as *const __m128i);
825 let wf = _mm256_cvtepi32_ps(_mm256_cvtepi8_epi32(wi));
826 for k in 0..4 {
827 if k < nb {
828 a[k] = _mm256_fmadd_ps(wf, _mm256_loadu_ps(xp.add(k * cols + j)), a[k]);
829 }
830 }
831 j += 8;
832 }
833 for k in 0..nb {
834 let v = a[k];
835 let hi = _mm256_extractf128_ps(v, 1);
836 let lo = _mm256_castps256_ps128(v);
837 let s = _mm_add_ps(lo, hi);
838 let s = _mm_hadd_ps(s, s);
839 let s = _mm_hadd_ps(s, s);
840 let mut t = _mm_cvtss_f32(s);
841 for jj in j..cols {
842 t += (w[jj] as i8) as f32 * *xp.add(k * cols + jj);
843 }
844 acc[k] = t;
845 }
846 }
847}