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 pool: Option<Arc<Pool>>,
54 pub hidden: usize,
55 nh: usize,
56 nkv: usize,
57 hd: usize,
58 theta: f32,
59 eps: f64,
60}
61
62fn rms_norm_into(x: &[f32], w: &[f32], eps: f64, dst: &mut [f32]) {
63 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / x.len() as f64;
64 let inv = 1.0 / (ss + eps).sqrt();
65 for ((d, &v), &g) in dst.iter_mut().zip(x).zip(w) {
66 *d = (v as f64 * inv) as f32 * g;
67 }
68}
69
70fn silu(v: f32) -> f32 {
71 v / (1.0 + (-v).exp())
72}
73
74struct SendPtr(*mut f32);
75unsafe impl Send for SendPtr {}
76unsafe impl Sync for SendPtr {}
77impl SendPtr {
78 #[allow(clippy::mut_from_ref)]
80 unsafe fn row(&self, off: usize, len: usize) -> &mut [f32] {
81 unsafe { std::slice::from_raw_parts_mut(self.0.add(off), len) }
82 }
83}
84
85fn erf(x: f64) -> f64 {
89 let s = if x < 0.0 { -1.0 } else { 1.0 };
90 let x = x.abs();
91 let t = 1.0 / (1.0 + 0.327_591_1 * x);
92 let y = 1.0
93 - (((((1.061_405_429 * t - 1.453_152_027) * t) + 1.421_413_741) * t - 0.284_496_736) * t
94 + 0.254_829_592)
95 * t
96 * (-x * x).exp();
97 s * y
98}
99
100fn gelu(v: f32) -> f32 {
101 (0.5 * v as f64 * (1.0 + erf(v as f64 / std::f64::consts::SQRT_2))) as f32
102}
103
104struct ProjMlp {
107 w0: Vec<f32>, b0: Vec<f32>,
109 w2: Vec<f32>, b2: Vec<f32>,
111 hidden: usize,
112}
113
114struct ClipProj {
125 w: Vec<f32>, mean_in: Vec<f32>,
127 std_in: Vec<f32>,
128 mean_out: Vec<f32>,
129 std_out: Vec<f32>,
130 sink_out: Vec<f32>,
131 mlp: Option<ProjMlp>,
132 d_in: usize,
133 d_out: usize,
134}
135
136impl ClipProj {
137 fn apply(&self, h: &[f32], n: usize, pool: Option<&Pool>) -> Vec<f32> {
139 let (di, dout) = (self.d_in, self.d_out);
140 let mut out = vec![0f32; n * dout];
141 let ptr = SendPtr(out.as_mut_ptr());
142 let work = |lo: usize, hi: usize| {
143 let mut xn = vec![0f32; di];
144 let mut g = vec![0f32; self.mlp.as_ref().map_or(0, |m| m.hidden)];
145 for p in lo..hi {
146 let hp = &h[p * di..(p + 1) * di];
147 for i in 0..di {
148 xn[i] = (hp[i] - self.mean_in[i]) / self.std_in[i];
149 }
150 let o = unsafe { ptr.row(p * dout, dout) };
152 o.fill(0.0);
153 for i in 0..di {
154 let x = xn[i];
155 let row = &self.w[i * dout..(i + 1) * dout];
156 for (d, &wv) in o.iter_mut().zip(row) {
157 *d += x * wv;
158 }
159 }
160 if let Some(m) = &self.mlp {
161 for (oi, gv) in g.iter_mut().enumerate() {
162 let row = &m.w0[oi * di..(oi + 1) * di];
163 let mut s = m.b0[oi];
164 for (&r, &x) in row.iter().zip(&xn) {
165 s += r * x;
166 }
167 *gv = gelu(s);
168 }
169 for (j, d) in o.iter_mut().enumerate() {
170 let row = &m.w2[j * m.hidden..(j + 1) * m.hidden];
171 let mut s = m.b2[j];
172 for (&r, &gv) in row.iter().zip(&g) {
173 s += r * gv;
174 }
175 *d += s;
176 }
177 }
178 for (j, d) in o.iter_mut().enumerate() {
179 *d = *d * self.std_out[j] + self.mean_out[j];
180 }
181 }
182 };
183 match pool {
184 Some(pl) => pl.run_rows(n, &work),
185 None => work(0, n),
186 }
187 if n > 0 {
188 out[..dout].copy_from_slice(&self.sink_out);
189 }
190 out
191 }
192}
193
194pub struct ImageSpan {
197 pub start: usize,
198 pub len: usize,
199 pub merged_h: usize,
200 pub merged_w: usize,
201}
202
203impl Qwen3Encoder {
204 pub fn from_cmf(model: &Arc<CmfModel>) -> Result<Self, String> {
205 let cfg: serde_json::Value = serde_json::from_slice(
206 model.tensor_bytes("te.config_json").map_err(|e| e.to_string())?,
207 )
208 .map_err(|e| format!("te.config_json: {e}"))?;
209 let u = |k: &str, d: usize| cfg[k].as_u64().map(|v| v as usize).unwrap_or(d);
210 let nl = match std::env::var("CMF_TE_TAP").ok().and_then(|v| v.parse().ok()) {
215 Some(t) if t > 0 && t <= u("num_hidden_layers", 0) => t,
216 _ => u("num_hidden_layers", 0),
217 };
218 let f32v = |n: &str| crate::dit::cmf_f32(model, n);
219 let mut layers = Vec::with_capacity(nl);
220 for l in 0..nl {
221 let p = format!("te.layers.{l}");
222 layers.push(Layer {
223 input_norm: f32v(&format!("{p}.input_layernorm.weight"))?,
224 q: Proj::from_model(model, &format!("{p}.self_attn.q_proj.weight"))?,
225 k: Proj::from_model(model, &format!("{p}.self_attn.k_proj.weight"))?,
226 v: Proj::from_model(model, &format!("{p}.self_attn.v_proj.weight"))?,
227 o: Proj::from_model(model, &format!("{p}.self_attn.o_proj.weight"))?,
228 q_norm: f32v(&format!("{p}.self_attn.q_norm.weight"))?,
229 k_norm: f32v(&format!("{p}.self_attn.k_norm.weight"))?,
230 post_attn_norm: f32v(&format!("{p}.post_attention_layernorm.weight"))?,
231 gate: Proj::from_model(model, &format!("{p}.mlp.gate_proj.weight"))?,
232 up: Proj::from_model(model, &format!("{p}.mlp.up_proj.weight"))?,
233 down: Proj::from_model(model, &format!("{p}.mlp.down_proj.weight"))?,
234 });
235 }
236 let hidden = u("hidden_size", 0);
237 let proj = match model.tensor_bytes("te.proj.config_json") {
238 Ok(b) => {
239 let pc: serde_json::Value =
240 serde_json::from_slice(b).map_err(|e| format!("te.proj.config_json: {e}"))?;
241 let pu = |k: &str| pc[k].as_u64().unwrap_or(0) as usize;
242 let (d_in, d_out) = (pu("d_in"), pu("d_out"));
243 if d_in != hidden {
244 return Err(format!(
245 "te.proj expects a {d_in}-wide encoder, the packed one is {hidden}: \
246 the projection and the encoder come from different models"
247 ));
248 }
249 let mlp = match pc["mlp"].as_bool() {
250 Some(true) => Some(ProjMlp {
251 w0: f32v("te.proj.mlp.0.weight")?,
252 b0: f32v("te.proj.mlp.0.bias")?,
253 w2: f32v("te.proj.mlp.2.weight")?,
254 b2: f32v("te.proj.mlp.2.bias")?,
255 hidden: pu("mlp_hidden"),
256 }),
257 _ => None,
258 };
259 Some(ClipProj {
260 w: f32v("te.proj.W")?,
261 mean_in: f32v("te.proj.mean_in")?,
262 std_in: f32v("te.proj.std_in")?,
263 mean_out: f32v("te.proj.mean_out")?,
264 std_out: f32v("te.proj.std_out")?,
265 sink_out: f32v("te.proj.sink_out")?,
266 mlp,
267 d_in,
268 d_out,
269 })
270 }
271 Err(_) => None,
272 };
273 Ok(Self {
274 embed: QTensor::from_model(model, "te.embed_tokens.weight")?,
275 layers,
276 final_norm: match cfg["final_norm"].as_bool() {
277 Some(false) | None => None,
278 Some(true) => Some(f32v("te.norm.weight")?),
279 },
280 proj,
281 pool: Pool::from_env(),
282 hidden,
283 nh: u("num_attention_heads", 0),
284 nkv: u("num_key_value_heads", 0),
285 hd: u("head_dim", 128),
286 theta: cfg["rope_theta"].as_f64().unwrap_or(5e6) as f32,
287 eps: cfg["rms_norm_eps"].as_f64().unwrap_or(1e-6),
288 })
289 }
290
291 fn norm_rope(&self, all: &mut [f32], n: usize, heads: usize, w: &[f32], pos: &[[f32; 3]]) {
294 let hd = self.hd;
295 let half = hd / 2;
296 let pool = self.pool.as_deref();
297 let ptr = SendPtr(all.as_mut_ptr());
298 let work = |lo: usize, hi: usize| {
299 for p in lo..hi {
300 for h in 0..heads {
301 let x = unsafe { ptr.row((p * heads + h) * hd, hd) };
303 let ss = x.iter().map(|&v| (v as f64) * (v as f64)).sum::<f64>() / hd as f64;
304 let inv = 1.0 / (ss + self.eps).sqrt();
305 for (d, &g) in x.iter_mut().zip(w) {
306 *d = (*d as f64 * inv) as f32 * g;
307 }
308 for i in 0..half {
309 let freq = 1.0 / self.theta.powf(2.0 * i as f32 / hd as f32);
310 let axis = if i < 60 && i % 3 == 1 {
316 1
317 } else if i < 60 && i % 3 == 2 {
318 2
319 } else {
320 0
321 };
322 let (s, c) = (pos[p][axis] * freq).sin_cos();
323 let (a, b) = (x[i], x[i + half]);
324 x[i] = a * c - b * s;
325 x[i + half] = a * s + b * c;
326 }
327 }
328 }
329 };
330 match pool {
331 Some(pl) => pl.run_rows(n, &work),
332 None => work(0, n),
333 }
334 }
335
336 pub fn encode(&self, ids: &[u32]) -> Vec<f32> {
340 self.encode_with_images(ids, &[], &[], &[])
341 }
342
343 fn mrope_positions(&self, n: usize, spans: &[ImageSpan]) -> Vec<[f32; 3]> {
352 let mut pos: Vec<[f32; 3]> = (0..n).map(|i| [i as f32; 3]).collect();
353 let mut offset = 0i64;
354 for sp in spans {
355 let (start, end) = (sp.start, sp.start + sp.len);
356 let len_max = sp.merged_h.max(sp.merged_w) as i64;
357 let base = start as i64 + offset;
358 for i in start..end {
359 let k = i - start;
360 pos[i] = [
361 base as f32,
362 (base + (k / sp.merged_w) as i64) as f32,
363 (base + (k % sp.merged_w) as i64) as f32,
364 ];
365 }
366 let next = len_max + start as i64;
367 for (j, p) in pos.iter_mut().enumerate().skip(end) {
368 let v = (next + offset + (j - end) as i64) as f32;
369 *p = [v; 3];
370 }
371 offset += len_max - sp.len as i64;
372 }
373 pos
374 }
375
376 pub fn encode_with_images(
382 &self,
383 ids: &[u32],
384 spans: &[ImageSpan],
385 embeds: &[Vec<f32>],
386 deepstack: &[Vec<f32>],
387 ) -> Vec<f32> {
388 let n = ids.len();
389 let hs = self.hidden;
390 let (nh, nkv, hd) = (self.nh, self.nkv, self.hd);
391 let hpk = nh / nkv;
392 let pool = self.pool.as_deref();
393 let scale = 1.0 / (hd as f32).sqrt();
394
395 let mut h = vec![0f32; n * hs];
396 for (i, &id) in ids.iter().enumerate() {
397 self.embed.row_f32(id as usize, &mut h[i * hs..(i + 1) * hs]);
398 }
399 for (sp, e) in spans.iter().zip(embeds) {
400 h[sp.start * hs..(sp.start + sp.len) * hs].copy_from_slice(&e[..sp.len * hs]);
401 }
402 let pos = self.mrope_positions(n, spans);
403 let visual: Vec<usize> = spans
405 .iter()
406 .flat_map(|s| s.start..s.start + s.len)
407 .collect();
408 let mut xn = vec![0f32; n * hs];
409 let mut q_all = vec![0f32; n * nh * hd];
410 let mut k_all = vec![0f32; n * nkv * hd];
411 let mut v_all = vec![0f32; n * nkv * hd];
412 let mut attn = vec![0f32; n * nh * hd];
413 let mut proj = vec![0f32; n * hs];
414
415 for (li, layer) in self.layers.iter().enumerate() {
416 for (o, src) in xn.chunks_exact_mut(hs).zip(h.chunks_exact(hs)) {
417 rms_norm_into(src, &layer.input_norm, self.eps, o);
418 }
419 layer.q.matmat(&xn, n, &mut q_all, pool);
420 layer.k.matmat(&xn, n, &mut k_all, pool);
421 layer.v.matmat(&xn, n, &mut v_all, pool);
422 self.norm_rope(&mut q_all, n, nh, &layer.q_norm, &pos);
423 self.norm_rope(&mut k_all, n, nkv, &layer.k_norm, &pos);
424
425 attn.fill(0.0);
426 let ap = SendPtr(attn.as_mut_ptr());
427 let heads = |lo: usize, hi: usize| {
428 let mut row = vec![0f32; n];
429 for hh in lo..hi {
430 let kv = hh / hpk;
431 for p in 0..n {
432 let qv = &q_all[(p * nh + hh) * hd..(p * nh + hh + 1) * hd];
433 for (j, r) in row[..=p].iter_mut().enumerate() {
434 let kvv = &k_all[(j * nkv + kv) * hd..(j * nkv + kv + 1) * hd];
435 *r = qv.iter().zip(kvv).map(|(&a, &b)| a * b).sum::<f32>() * scale;
436 }
437 let mx = row[..=p].iter().cloned().fold(f32::MIN, f32::max);
438 let mut den = 0f32;
439 for r in row[..=p].iter_mut() {
440 *r = (*r - mx).exp();
441 den += *r;
442 }
443 let inv = 1.0 / den;
444 let out = unsafe { ap.row((p * nh + hh) * hd, hd) };
447 for (j, &rw) in row[..=p].iter().enumerate() {
448 let vv = &v_all[(j * nkv + kv) * hd..(j * nkv + kv + 1) * hd];
449 let wgt = rw * inv;
450 for (o, &s) in out.iter_mut().zip(vv) {
451 *o += wgt * s;
452 }
453 }
454 }
455 }
456 };
457 match pool {
458 Some(pl) => pl.run_rows(nh, &heads),
459 None => heads(0, nh),
460 }
461
462 layer.o.matmat(&attn, n, &mut proj, pool);
463 for (d, &v) in h.iter_mut().zip(&proj) {
464 *d += v;
465 }
466
467 for (o, src) in xn.chunks_exact_mut(hs).zip(h.chunks_exact(hs)) {
468 rms_norm_into(src, &layer.post_attn_norm, self.eps, o);
469 }
470 let inter = layer.gate.rows();
471 let mut g = vec![0f32; n * inter];
472 let mut u = vec![0f32; n * inter];
473 layer.gate.matmat(&xn, n, &mut g, pool);
474 layer.up.matmat(&xn, n, &mut u, pool);
475 for (a, &b) in g.iter_mut().zip(&u) {
476 *a = silu(*a) * b;
477 }
478 layer.down.matmat(&g, n, &mut proj, pool);
479 for (d, &v) in h.iter_mut().zip(&proj) {
480 *d += v;
481 }
482 if let Some(f) = deepstack.get(li) {
483 for (k, &row) in visual.iter().enumerate() {
484 for c in 0..hs {
485 h[row * hs + c] += f[k * hs + c];
486 }
487 }
488 }
489 }
490 if let Some(w) = &self.final_norm {
491 let mut out = vec![0f32; n * hs];
492 for (o, src) in out.chunks_exact_mut(hs).zip(h.chunks_exact(hs)) {
493 rms_norm_into(src, w, self.eps, o);
494 }
495 return out;
496 }
497 match &self.proj {
501 Some(p) => p.apply(&h, n, self.pool.as_deref()),
502 None => h,
503 }
504 }
505
506 pub fn out_hidden(&self) -> usize {
510 self.proj.as_ref().map_or(self.hidden, |p| p.d_out)
511 }
512}