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
48pub struct Qwen3Encoder {
49 embed: QTensor,
50 layers: Vec<Layer>,
51 final_norm: Option<Vec<f32>>,
52 proj: Option<ClipProj>,
53 proj_vis: Option<ClipProj>,
59 pool: Option<Arc<Pool>>,
60 pub hidden: usize,
61 nh: usize,
62 nkv: usize,
63 hd: usize,
64 theta: f32,
65 eps: f64,
66}
67
68fn rms_norm_into(x: &[f32], w: &[f32], eps: f64, dst: &mut [f32]) {
69 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
70 let inv = 1.0 / (ss + eps).sqrt();
71 for ((d, &v), &g) in dst.iter_mut().zip(x).zip(w) {
72 *d = (v as f64 * inv) as f32 * g;
73 }
74}
75
76fn silu(v: f32) -> f32 {
77 v / (1.0 + (-v).exp())
78}
79
80struct SendPtr(*mut f32);
81unsafe impl Send for SendPtr {}
82unsafe impl Sync for SendPtr {}
83impl SendPtr {
84 #[allow(clippy::mut_from_ref)]
86 unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
87 unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
88 }
89}
90
91fn erf(x: f64) -> f64 {
95 let s = if x < 0.0 { -1.0 } else { 1.0 };
96 let x = x.abs();
97 let t = 1.0 / (1.0 + 0.327_591_1 * x);
98 let y = 1.0
99 - (((((1.061_405_429 * t - 1.453_152_027) * t) + 1.421_413_741) * t - 0.284_496_736) * t
100 + 0.254_829_592)
101 * t
102 * (-x * x).exp();
103 s * y
104}
105
106fn gelu(v: f32) -> f32 {
107 (0.5 * v as f64 * (1.0 + erf(v as f64 / std::f64::consts::SQRT_2))) as f32
108}
109
110struct ProjMlp {
113 w0: Vec<f32>, b0: Vec<f32>,
115 w2: Vec<f32>, b2: Vec<f32>,
117 hidden: usize,
118}
119
120struct ClipProj {
131 w: Vec<f32>, mean_in: Vec<f32>,
133 std_in: Vec<f32>,
134 mean_out: Vec<f32>,
135 std_out: Vec<f32>,
136 sink_out: Vec<f32>,
137 mlp: Option<ProjMlp>,
138 d_in: usize,
139 d_out: usize,
140}
141
142impl ClipProj {
143 fn apply(&self, h: &[f32], n: usize, pool: Option<&Pool>) -> Vec<f32> {
145 self.apply_inner(h, n, pool, true)
146 }
147
148 fn apply_inner(&self, h: &[f32], n: usize, pool: Option<&Pool>, sink: bool) -> Vec<f32> {
151 let (di, dout) = (self.d_in, self.d_out);
152 let mut out = vec![0f32; n * dout];
153 let ptr = SendPtr(out.as_mut_ptr());
154 let work = |lo: usize, hi: usize| {
155 let mut xn = vec![0f32; di];
156 let mut g = vec![0f32; self.mlp.as_ref().map_or(0, |m| m.hidden)];
157 for p in lo..hi {
158 let hp = &h[p * di..(p + 1) * di];
159 for i in 0..di {
160 xn[i] = (hp[i] - self.mean_in[i]) / self.std_in[i];
161 }
162 let o = unsafe { ptr.row(p * dout, dout) };
164 o.fill(0.0);
165 for i in 0..di {
166 let x = xn[i];
167 let row = &self.w[i * dout..(i + 1) * dout];
168 for (d, &wv) in o.iter_mut().zip(row) {
169 *d += x * wv;
170 }
171 }
172 if let Some(m) = &self.mlp {
173 for (oi, gv) in g.iter_mut().enumerate() {
174 let row = &m.w0[oi * di..(oi + 1) * di];
175 let mut s = m.b0[oi];
176 for (&r, &x) in row.iter().zip(&xn) {
177 s += r * x;
178 }
179 *gv = gelu(s);
180 }
181 for (j, d) in o.iter_mut().enumerate() {
182 let row = &m.w2[j * m.hidden..(j + 1) * m.hidden];
183 let mut s = m.b2[j];
184 for (&r, &gv) in row.iter().zip(&g) {
185 s += r * gv;
186 }
187 *d += s;
188 }
189 }
190 for (j, d) in o.iter_mut().enumerate() {
191 *d = *d * self.std_out[j] + self.mean_out[j];
192 }
193 }
194 };
195 match pool {
196 Some(pl) => pl.run_rows(n, &work),
197 None => work(0, n),
198 }
199 if sink && n > 0 {
200 out[..dout].copy_from_slice(&self.sink_out);
201 }
202 out
203 }
204}
205
206pub struct ImageSpan {
209 pub start: usize,
210 pub len: usize,
211 pub merged_h: usize,
212 pub merged_w: usize,
213}
214
215impl Qwen3Encoder {
216 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
217 let cfg: serde_json::Value = serde_json::from_slice(
218 model
219 .tensor_bytes("te.config_json")
220 .map_err(|e| e.to_string())?,
221 )
222 .map_err(|e| format!("te.config_json: {e}"))?;
223 let u = |k: &str, d: usize| cfg[k].as_u64().map(|v| v as usize).unwrap_or(d);
224 let nl = match std::env::var("CMF_TE_TAP")
229 .ok()
230 .and_then(|v| v.parse().ok())
231 {
232 Some(t) if t > 0 && t <= u("num_hidden_layers", 0) => t,
233 _ => u("num_hidden_layers", 0),
234 };
235 let f32v = |n: &str| crate::dit::cmf_f32(model, n);
236 let mut layers = Vec::with_capacity(nl);
237 for l in 0..nl {
238 let p = format!("te.layers.{l}");
239 layers.push(Layer {
240 input_norm: f32v(&format!("{p}.input_layernorm.weight"))?,
241 q: Proj::from_model(model, &format!("{p}.self_attn.q_proj.weight"))?,
242 k: Proj::from_model(model, &format!("{p}.self_attn.k_proj.weight"))?,
243 v: Proj::from_model(model, &format!("{p}.self_attn.v_proj.weight"))?,
244 o: Proj::from_model(model, &format!("{p}.self_attn.o_proj.weight"))?,
245 q_norm: f32v(&format!("{p}.self_attn.q_norm.weight"))?,
246 k_norm: f32v(&format!("{p}.self_attn.k_norm.weight"))?,
247 post_attn_norm: f32v(&format!("{p}.post_attention_layernorm.weight"))?,
248 gate: Proj::from_model(model, &format!("{p}.mlp.gate_proj.weight"))?,
249 up: Proj::from_model(model, &format!("{p}.mlp.up_proj.weight"))?,
250 down: Proj::from_model(model, &format!("{p}.mlp.down_proj.weight"))?,
251 });
252 }
253 let hidden = u("hidden_size", 0);
254 let proj = match model.tensor_bytes("te.proj.config_json") {
255 Ok(b) => {
256 let pc: serde_json::Value =
257 serde_json::from_slice(b).map_err(|e| format!("te.proj.config_json: {e}"))?;
258 let pu = |k: &str| pc[k].as_u64().unwrap_or(0) as usize;
259 let (d_in, d_out) = (pu("d_in"), pu("d_out"));
260 if d_in != hidden {
261 return Err(format!(
262 "te.proj expects a {d_in}-wide encoder, the packed one is {hidden}: \
263 the projection and the encoder come from different models"
264 ));
265 }
266 let mlp = match pc["mlp"].as_bool() {
267 Some(true) => Some(ProjMlp {
268 w0: f32v("te.proj.mlp.0.weight")?,
269 b0: f32v("te.proj.mlp.0.bias")?,
270 w2: f32v("te.proj.mlp.2.weight")?,
271 b2: f32v("te.proj.mlp.2.bias")?,
272 hidden: pu("mlp_hidden"),
273 }),
274 _ => None,
275 };
276 Some(ClipProj {
277 w: f32v("te.proj.W")?,
278 mean_in: f32v("te.proj.mean_in")?,
279 std_in: f32v("te.proj.std_in")?,
280 mean_out: f32v("te.proj.mean_out")?,
281 std_out: f32v("te.proj.std_out")?,
282 sink_out: f32v("te.proj.sink_out")?,
283 mlp,
284 d_in,
285 d_out,
286 })
287 }
288 Err(_) => None,
289 };
290 let proj_vis = match (&proj, model.tensor_bytes("te.proj.vis.W").is_ok()) {
291 (Some(base), true) => {
292 let g = |n: &str| crate::dit::cmf_f32(model, n);
293 Some(ClipProj {
294 w: g("te.proj.vis.W")?,
295 mean_in: g("te.proj.vis.mean_in")?,
296 std_in: g("te.proj.vis.std_in")?,
297 mean_out: g("te.proj.vis.mean_out")?,
298 std_out: g("te.proj.vis.std_out")?,
299 sink_out: base.sink_out.clone(),
302 mlp: None,
303 d_in: base.d_in,
304 d_out: base.d_out,
305 })
306 }
307 _ => None,
308 };
309 Ok(Self {
310 embed: QTensor::from_model(model, "te.embed_tokens.weight")?,
311 layers,
312 final_norm: match cfg["final_norm"].as_bool() {
313 Some(false) | None => None,
314 Some(true) => Some(f32v("te.norm.weight")?),
315 },
316 proj,
317 proj_vis,
318 pool: Pool::from_env(),
319 hidden,
320 nh: u("num_attention_heads", 0),
321 nkv: u("num_key_value_heads", 0),
322 hd: u("head_dim", 128),
323 theta: cfg["rope_theta"].as_f64().unwrap_or(5e6) as f32,
324 eps: cfg["rms_norm_eps"].as_f64().unwrap_or(1e-6),
325 })
326 }
327
328 fn norm_rope(&self, all: &mut [f32], n: usize, heads: usize, w: &[f32], pos: &[[f32; 3]]) {
331 let hd = self.hd;
332 let half = hd / 2;
333 let pool = self.pool.as_deref();
334 let ptr = SendPtr(all.as_mut_ptr());
335 let work = |lo: usize, hi: usize| {
336 for p in lo..hi {
337 for h in 0..heads {
338 let x = unsafe { ptr.row((p * heads + h) * hd, hd) };
340 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / hd as f64;
341 let inv = 1.0 / (ss + self.eps).sqrt();
342 for (d, &g) in x.iter_mut().zip(w) {
343 *d = (*d as f64 * inv) as f32 * g;
344 }
345 for i in 0..half {
346 let freq = 1.0 / self.theta.powf(2.0 * i as f32 / hd as f32);
347 let axis = if i < 60 && i % 3 == 1 {
353 1
354 } else if i < 60 && i % 3 == 2 {
355 2
356 } else {
357 0
358 };
359 let (s, c) = (pos[p][axis] * freq).sin_cos();
360 let (a, b) = (x[i], x[i + half]);
361 x[i] = a * c - b * s;
362 x[i + half] = a * s + b * c;
363 }
364 }
365 }
366 };
367 match pool {
368 Some(pl) => pl.run_rows(n, &work),
369 None => work(0, n),
370 }
371 }
372
373 pub fn encode(&self, ids: &[u32]) -> Vec<f32> {
377 self.encode_with_images(ids, &[], &[], &[])
378 }
379
380 fn mrope_positions(&self, n: usize, spans: &[ImageSpan]) -> Vec<[f32; 3]> {
389 let mut pos: Vec<[f32; 3]> = (0..n).map(|i| [i as f32; 3]).collect();
390 let mut offset = 0i64;
391 for sp in spans {
392 let (start, end) = (sp.start, sp.start + sp.len);
393 let len_max = sp.merged_h.max(sp.merged_w) as i64;
394 let base = start as i64 + offset;
395 for i in start..end {
396 let k = i - start;
397 pos[i] = [
398 base as f32,
399 (base + (k / sp.merged_w) as i64) as f32,
400 (base + (k % sp.merged_w) as i64) as f32,
401 ];
402 }
403 let next = len_max + start as i64;
404 for (j, p) in pos.iter_mut().enumerate().skip(end) {
405 let v = (next + offset + (j - end) as i64) as f32;
406 *p = [v; 3];
407 }
408 offset += len_max - sp.len as i64;
409 }
410 pos
411 }
412
413 pub fn encode_with_images(
419 &self,
420 ids: &[u32],
421 spans: &[ImageSpan],
422 embeds: &[Vec<f32>],
423 deepstack: &[Vec<f32>],
424 ) -> Vec<f32> {
425 let n = ids.len();
426 let hs = self.hidden;
427 let (nh, nkv, hd) = (self.nh, self.nkv, self.hd);
428 let hpk = nh / nkv;
429 let pool = self.pool.as_deref();
430 let scale = 1.0 / (hd as f32).sqrt();
431
432 let mut h = vec![0f32; n * hs];
433 for (i, &id) in ids.iter().enumerate() {
434 self.embed
435 .row_f32(id as usize, &mut h[i * hs..(i + 1) * hs]);
436 }
437 for (sp, e) in spans.iter().zip(embeds) {
438 h[sp.start * hs..(sp.start + sp.len) * hs].copy_from_slice(&e[..sp.len * hs]);
439 }
440 let pos = self.mrope_positions(n, spans);
441 let visual: Vec<usize> = spans
443 .iter()
444 .flat_map(|s| s.start..s.start + s.len)
445 .collect();
446 let mut xn = vec![0f32; n * hs];
447 let mut q_all = vec![0f32; n * nh * hd];
448 let mut k_all = vec![0f32; n * nkv * hd];
449 let mut v_all = vec![0f32; n * nkv * hd];
450 let mut attn = vec![0f32; n * nh * hd];
451 let mut proj = vec![0f32; n * hs];
452
453 for (li, layer) in self.layers.iter().enumerate() {
454 for (o, src) in xn.chunks_exact_mut(hs).zip(h.chunks_exact(hs)) {
455 rms_norm_into(src, &layer.input_norm, self.eps, o);
456 }
457 layer.q.matmat(&xn, n, &mut q_all, pool);
458 layer.k.matmat(&xn, n, &mut k_all, pool);
459 layer.v.matmat(&xn, n, &mut v_all, pool);
460 self.norm_rope(&mut q_all, n, nh, &layer.q_norm, &pos);
461 self.norm_rope(&mut k_all, n, nkv, &layer.k_norm, &pos);
462
463 attn.fill(0.0);
464 let ap = SendPtr(attn.as_mut_ptr());
465 let heads = |lo: usize, hi: usize| {
466 let mut row = vec![0f32; n];
467 for hh in lo..hi {
468 let kv = hh / hpk;
469 for p in 0..n {
470 let qv = &q_all[(p * nh + hh) * hd..(p * nh + hh + 1) * hd];
471 for (j, r) in row[..=p].iter_mut().enumerate() {
472 let kvv = &k_all[(j * nkv + kv) * hd..(j * nkv + kv + 1) * hd];
473 *r = qv.iter().zip(kvv).map(|(&a, &b)| a * b).sum::<f32>() * scale;
474 }
475 let mx = row[..=p].iter().cloned().fold(f32::MIN, f32::max);
476 let mut den = 0f32;
477 for r in row[..=p].iter_mut() {
478 *r = (*r - mx).exp();
479 den += *r;
480 }
481 let inv = 1.0 / den;
482 let out = unsafe { ap.row((p * nh + hh) * hd, hd) };
485 for (j, &rw) in row[..=p].iter().enumerate() {
486 let vv = &v_all[(j * nkv + kv) * hd..(j * nkv + kv + 1) * hd];
487 let wgt = rw * inv;
488 for (o, &s) in out.iter_mut().zip(vv) {
489 *o += wgt * s;
490 }
491 }
492 }
493 }
494 };
495 match pool {
496 Some(pl) => pl.run_rows(nh, &heads),
497 None => heads(0, nh),
498 }
499
500 layer.o.matmat(&attn, n, &mut proj, pool);
501 for (d, &v) in h.iter_mut().zip(&proj) {
502 *d += v;
503 }
504
505 for (o, src) in xn.chunks_exact_mut(hs).zip(h.chunks_exact(hs)) {
506 rms_norm_into(src, &layer.post_attn_norm, self.eps, o);
507 }
508 let inter = layer.gate.rows();
509 let mut g = vec![0f32; n * inter];
510 let mut u = vec![0f32; n * inter];
511 layer.gate.matmat(&xn, n, &mut g, pool);
512 layer.up.matmat(&xn, n, &mut u, pool);
513 for (a, &b) in g.iter_mut().zip(&u) {
514 *a = silu(*a) * b;
515 }
516 layer.down.matmat(&g, n, &mut proj, pool);
517 for (d, &v) in h.iter_mut().zip(&proj) {
518 *d += v;
519 }
520 if let Some(f) = deepstack.get(li) {
521 for (k, &row) in visual.iter().enumerate() {
522 for c in 0..hs {
523 h[row * hs + c] += f[k * hs + c];
524 }
525 }
526 }
527 }
528 if let Some(w) = &self.final_norm {
529 let mut out = vec![0f32; n * hs];
530 for (o, src) in out.chunks_exact_mut(hs).zip(h.chunks_exact(hs)) {
531 rms_norm_into(src, w, self.eps, o);
532 }
533 return out;
534 }
535 if let (Ok(path), Some(_)) = (std::env::var("CMF_CP_DUMP"), &self.proj) {
543 let mut b = Vec::with_capacity(16 + h.len() * 4);
544 b.extend_from_slice(&(n as u64).to_le_bytes());
545 b.extend_from_slice(&(self.hidden as u64).to_le_bytes());
546 for v in &h {
547 b.extend_from_slice(&v.to_le_bytes());
548 }
549 if let Err(e) = std::fs::write(&path, &b) {
550 tracing::warn!("CMF_CP_DUMP {path}: {e}");
551 } else {
552 eprintln!("cp dump: {n} tokens x {} -> {path}", self.hidden);
553 }
554 }
555 match &self.proj {
556 Some(p) => {
557 let mut out = p.apply(&h, n, self.pool.as_deref());
558 if let Some(pv) = &self.proj_vis {
561 if !visual.is_empty() {
562 let (din, dout) = (pv.d_in, pv.d_out);
563 let mut hv = Vec::with_capacity(visual.len() * din);
564 for &r in &visual {
565 hv.extend_from_slice(&h[r * din..(r + 1) * din]);
566 }
567 let ov = pv.apply_inner(&hv, visual.len(), self.pool.as_deref(), false);
568 for (i, &r) in visual.iter().enumerate() {
569 out[r * dout..(r + 1) * dout]
570 .copy_from_slice(&ov[i * dout..(i + 1) * dout]);
571 }
572 }
573 }
574 out
575 }
576 None => h,
577 }
578 }
579
580 pub fn out_hidden(&self) -> usize {
584 self.proj.as_ref().map_or(self.hidden, |p| p.d_out)
585 }
586}