1use std::io::{BufReader, Read, Seek};
37use std::path::Path;
38
39use anyhow::{Context, Result};
40use candle_core::quantized::gguf_file;
41use candle_core::{DType, Device, Tensor};
42
43use crate::config::TabPfnConfig;
44
45const RMS_EPS: f64 = 1.192_092_9e-7;
48
49struct SsmaxW {
54 base_fc1_w: Tensor,
55 base_fc1_b: Tensor,
56 base_fc2_w: Tensor,
57 base_fc2_b: Tensor,
58 query_fc1_w: Tensor,
59 query_fc1_b: Tensor,
60 query_fc2_w: Tensor,
61 query_fc2_b: Tensor,
62}
63
64struct QkvW {
67 q_w: Tensor,
68 k_w: Tensor,
69 v_w: Tensor,
70 out_w: Tensor,
71 ssmax: Option<SsmaxW>,
72}
73
74struct MlpW {
76 fc1_w: Tensor,
77 fc2_w: Tensor,
78}
79
80struct CrossAttnBlockW {
83 attn: QkvW,
84 mlp: MlpW,
85 ln_q_w: Tensor,
86 ln_kv_w: Tensor,
87 ln2_w: Tensor,
88}
89
90struct IsabW {
91 ind_vectors: Tensor,
92 block1: CrossAttnBlockW,
93 block2: CrossAttnBlockW,
94}
95
96struct TransformerBlockW {
100 attn: QkvW,
101 mlp: MlpW,
102 ln_w: Tensor,
103 ln_mlp_w: Tensor,
104}
105
106struct IclBlockW {
108 attn: QkvW,
109 mlp: MlpW,
110 ln_w: Tensor,
111 ln_mlp_w: Tensor,
112}
113
114struct ManyClassDecoderW {
115 q_w: Tensor,
116 q_b: Tensor,
117 k_w: Tensor,
118 k_b: Tensor,
119 ssmax: Option<SsmaxW>,
120}
121
122pub struct TabPfnModel {
123 device: Device,
124 config: TabPfnConfig,
125 x_embed_w: Tensor,
126 x_embed_b: Tensor,
127 col_y_encoder_w: Tensor,
128 icl_y_encoder_w: Tensor,
129 dist_embed: Vec<IsabW>,
130 col_agg_blocks: Vec<TransformerBlockW>,
131 col_agg_cls_tokens: Tensor,
132 col_agg_rope_freqs: Vec<f32>,
133 col_agg_out_ln_w: Tensor,
134 icl_blocks: Vec<IclBlockW>,
135 output_norm_w: Tensor,
136 many_class_decoder: ManyClassDecoderW,
137}
138
139fn load_t(content: &gguf_file::Content, reader: &mut (impl Read + Seek), name: &str, device: &Device) -> Result<Tensor> {
145 zsfm_nn::load_tensor(content, reader, name, device, DType::F32)
146}
147
148fn load_ssmax(content: &gguf_file::Content, reader: &mut (impl Read + Seek), prefix: &str, device: &Device) -> Result<SsmaxW> {
149 let mut t = |s: &str| load_t(content, reader, &format!("{prefix}.{s}"), device);
150 Ok(SsmaxW {
151 base_fc1_w: t("base_mlp.0.weight")?,
152 base_fc1_b: t("base_mlp.0.bias")?,
153 base_fc2_w: t("base_mlp.2.weight")?,
154 base_fc2_b: t("base_mlp.2.bias")?,
155 query_fc1_w: t("query_mlp.0.weight")?,
156 query_fc1_b: t("query_mlp.0.bias")?,
157 query_fc2_w: t("query_mlp.2.weight")?,
158 query_fc2_b: t("query_mlp.2.bias")?,
159 })
160}
161
162fn load_qkv(
163 content: &gguf_file::Content,
164 reader: &mut (impl Read + Seek),
165 prefix: &str,
166 device: &Device,
167 ssmax_prefix: Option<&str>,
168) -> Result<QkvW> {
169 let mut t = |s: &str| load_t(content, reader, &format!("{prefix}.{s}"), device);
170 let q_w = t("q_projection.weight")?;
171 let k_w = t("k_projection.weight")?;
172 let v_w = t("v_projection.weight")?;
173 let out_w = t("out_projection.weight")?;
174 let ssmax = match ssmax_prefix {
175 Some(p) => Some(load_ssmax(content, reader, p, device)?),
176 None => None,
177 };
178 Ok(QkvW { q_w, k_w, v_w, out_w, ssmax })
179}
180
181fn load_mlp(content: &gguf_file::Content, reader: &mut (impl Read + Seek), prefix: &str, device: &Device) -> Result<MlpW> {
182 Ok(MlpW {
183 fc1_w: load_t(content, reader, &format!("{prefix}.0.weight"), device)?,
184 fc2_w: load_t(content, reader, &format!("{prefix}.2.weight"), device)?,
185 })
186}
187
188fn load_cross_attn_block(
189 content: &gguf_file::Content,
190 reader: &mut (impl Read + Seek),
191 prefix: &str,
192 device: &Device,
193 has_ssmax: bool,
194) -> Result<CrossAttnBlockW> {
195 let ssmax_prefix = format!("{prefix}.attn.softmax_scaling_layer");
196 let attn = load_qkv(content, reader, &format!("{prefix}.attn"), device, has_ssmax.then_some(ssmax_prefix.as_str()))?;
197 let mlp = load_mlp(content, reader, &format!("{prefix}.mlp"), device)?;
198 let ln_q_w = load_t(content, reader, &format!("{prefix}.layernorm_q.weight"), device)?;
199 let ln_kv_w = load_t(content, reader, &format!("{prefix}.layernorm_kv.weight"), device)?;
200 let ln2_w = load_t(content, reader, &format!("{prefix}.layernorm2.weight"), device)?;
201 Ok(CrossAttnBlockW { attn, mlp, ln_q_w, ln_kv_w, ln2_w })
202}
203
204fn load_transformer_block(
205 content: &gguf_file::Content,
206 reader: &mut (impl Read + Seek),
207 prefix: &str,
208 device: &Device,
209) -> Result<TransformerBlockW> {
210 let attn = load_qkv(content, reader, &format!("{prefix}.attention"), device, None)?;
211 let mlp = load_mlp(content, reader, &format!("{prefix}.mlp"), device)?;
212 let ln_w = load_t(content, reader, &format!("{prefix}.layernorm.weight"), device)?;
213 let ln_mlp_w = load_t(content, reader, &format!("{prefix}.layernorm_mlp.weight"), device)?;
214 Ok(TransformerBlockW { attn, mlp, ln_w, ln_mlp_w })
215}
216
217fn load_icl_block(
218 content: &gguf_file::Content,
219 reader: &mut (impl Read + Seek),
220 prefix: &str,
221 device: &Device,
222) -> Result<IclBlockW> {
223 let ssmax_prefix = format!("{prefix}.icl_attention.softmax_scaling_layer");
224 let attn = load_qkv(content, reader, &format!("{prefix}.icl_attention"), device, Some(&ssmax_prefix))?;
225 let mlp = load_mlp(content, reader, &format!("{prefix}.mlp"), device)?;
226 let ln_w = load_t(content, reader, &format!("{prefix}.layernorm.weight"), device)?;
227 let ln_mlp_w = load_t(content, reader, &format!("{prefix}.layernorm_mlp.weight"), device)?;
228 Ok(IclBlockW { attn, mlp, ln_w, ln_mlp_w })
229}
230
231impl TabPfnModel {
232 pub fn load(gguf_path: &Path, config: TabPfnConfig) -> Result<Self> {
233 let device = Device::Cpu;
234 let file = std::fs::File::open(gguf_path).with_context(|| format!("open {}", gguf_path.display()))?;
235 let mut reader = BufReader::with_capacity(zsfm_gguf::READ_BUF_CAPACITY, file);
236 let content = gguf_file::Content::read(&mut reader).context("parse GGUF header")?;
237
238 let x_embed_w = load_t(&content, &mut reader, "x_embed.weight", &device)?;
239 let x_embed_b = load_t(&content, &mut reader, "x_embed.bias", &device)?;
240 let col_y_encoder_w = load_t(&content, &mut reader, "col_y_encoder.embedding.weight", &device)?;
241 let icl_y_encoder_w = load_t(&content, &mut reader, "icl_y_encoder.embedding.weight", &device)?;
242
243 let mut dist_embed = Vec::with_capacity(config.dist_embed_num_blocks);
244 for n in 0..config.dist_embed_num_blocks {
245 let p = format!("feature_distribution_embedder.layers.{n}");
246 let ind_vectors = load_t(&content, &mut reader, &format!("{p}.inducing_vectors"), &device)?;
247 let block1 = load_cross_attn_block(&content, &mut reader, &format!("{p}.cross_attn_block1"), &device, true)?;
248 let block2 = load_cross_attn_block(&content, &mut reader, &format!("{p}.cross_attn_block2"), &device, false)?;
249 dist_embed.push(IsabW { ind_vectors, block1, block2 });
250 }
251
252 let mut col_agg_blocks = Vec::with_capacity(config.feat_agg_num_blocks);
253 for n in 0..config.feat_agg_num_blocks {
254 let p = format!("column_aggregator.blocks.{n}");
255 col_agg_blocks.push(load_transformer_block(&content, &mut reader, &p, &device)?);
256 }
257 let col_agg_cls_tokens = load_t(&content, &mut reader, "column_aggregator.cls_tokens", &device)?;
258 let col_agg_out_ln_w = load_t(&content, &mut reader, "column_aggregator.out_ln.weight", &device)?;
259 let rope_freqs_t = load_t(&content, &mut reader, "column_aggregator.rope.freqs", &device)?;
260 let col_agg_rope_freqs: Vec<f32> = rope_freqs_t.flatten_all()?.to_vec1()?;
261
262 let mut icl_blocks = Vec::with_capacity(config.nlayers);
263 for n in 0..config.nlayers {
264 let p = format!("icl_blocks.{n}");
265 icl_blocks.push(load_icl_block(&content, &mut reader, &p, &device)?);
266 }
267
268 let output_norm_w = load_t(&content, &mut reader, "output_norm.weight", &device)?;
269
270 let decoder_ssmax_prefix = "many_class_decoder.softmax_scaling_layer";
271 let many_class_decoder = ManyClassDecoderW {
272 q_w: load_t(&content, &mut reader, "many_class_decoder.q_projection.weight", &device)?,
273 q_b: load_t(&content, &mut reader, "many_class_decoder.q_projection.bias", &device)?,
274 k_w: load_t(&content, &mut reader, "many_class_decoder.k_projection.weight", &device)?,
275 k_b: load_t(&content, &mut reader, "many_class_decoder.k_projection.bias", &device)?,
276 ssmax: if config.decoder_use_softmax_scaling {
277 Some(load_ssmax(&content, &mut reader, decoder_ssmax_prefix, &device)?)
278 } else {
279 None
280 },
281 };
282
283 Ok(Self {
284 device,
285 config,
286 x_embed_w,
287 x_embed_b,
288 col_y_encoder_w,
289 icl_y_encoder_w,
290 dist_embed,
291 col_agg_blocks,
292 col_agg_cls_tokens,
293 col_agg_rope_freqs,
294 col_agg_out_ln_w,
295 icl_blocks,
296 output_norm_w,
297 many_class_decoder,
298 })
299 }
300
301 pub fn predict_classification(
308 &self,
309 x_support: &[Vec<f32>],
310 y_support: &[usize],
311 x_query: &[Vec<f32>],
312 n_classes: usize,
313 ) -> Result<Vec<Vec<f32>>> {
314 let train_size = x_support.len();
315 let mut all_rows = x_support.to_vec();
316 all_rows.extend_from_slice(x_query);
317 let t = all_rows.len();
318 let h = all_rows[0].len();
319
320 let processed = preprocess_x(&all_rows, train_size);
321 let group_size = self.config.feature_group_size;
322 let grouped = feature_group_with_nan_indicators(&processed, h, group_size, self.config.use_nan_indicators);
323 let cell_dim = grouped[0][0].len();
324
325 let mut flat = vec![0f32; h * t * cell_dim];
326 for (row_idx, row) in grouped.iter().enumerate() {
327 for (col_idx, cell) in row.iter().enumerate() {
328 let base = col_idx * t * cell_dim + row_idx * cell_dim;
329 flat[base..base + cell_dim].copy_from_slice(cell);
330 }
331 }
332 let x_grouped = Tensor::from_vec(flat, (h, t, cell_dim), &self.device)?;
333
334 let y_col_emb = self.embedding_lookup(y_support, &self.col_y_encoder_w)?; let col_out = self.dist_embed_forward(&x_grouped, &y_col_emb, train_size)?; let row_out = self.col_agg_forward(&col_out, train_size)?; let y_icl_emb = self.embedding_lookup(y_support, &self.icl_y_encoder_w)?; let train_part = row_out.narrow(0, 0, train_size)?.broadcast_add(&y_icl_emb)?;
340 let mut r = if train_size < t {
341 Tensor::cat(&[&train_part, &row_out.narrow(0, train_size, t - train_size)?], 0)?
342 } else {
343 train_part
344 };
345 for blk in &self.icl_blocks {
346 r = self.icl_block_forward(&r, blk, train_size)?;
347 }
348 let r = zsfm_nn::rms_norm(&r, Some(&self.output_norm_w), RMS_EPS)?;
349
350 let train_emb = r.narrow(0, 0, train_size)?;
351 let test_emb = r.narrow(0, train_size, t - train_size)?;
352
353 let highest_target = *y_support.iter().max().context("y_support must be non-empty")?;
354 let logits = self.many_class_decoder_forward(&train_emb, &test_emb, y_support, highest_target, n_classes)?;
355
356 let flat_logits: Vec<f32> = logits.flatten_all()?.to_vec1()?;
357 let n_query = t - train_size;
358 const TEMPERATURE: f32 = 0.9;
359 let mut result = Vec::with_capacity(n_query);
360 for i in 0..n_query {
361 let row = &flat_logits[i * n_classes..(i + 1) * n_classes];
362 let scaled: Vec<f32> = row.iter().map(|&v| v / TEMPERATURE).collect();
363 result.push(softmax(&scaled));
364 }
365 Ok(result)
366 }
367
368 fn embedding_lookup(&self, y: &[usize], table: &Tensor) -> Result<Tensor> {
369 let dim = table.dim(1)?;
370 let idx = Tensor::from_vec(y.iter().map(|&c| c as u32).collect::<Vec<_>>(), y.len(), &self.device)?;
371 table.index_select(&idx, 0)?.reshape((y.len(), dim)).map_err(anyhow::Error::from)
372 }
373
374 fn dist_embed_forward(&self, x_grouped: &Tensor, y_col_emb: &Tensor, train_size: usize) -> Result<Tensor> {
377 let cell_emb = zsfm_nn::linear_bias(x_grouped, &self.x_embed_w, &self.x_embed_b)?;
378 let t = cell_emb.dim(1)?;
379 let train_part = cell_emb.narrow(1, 0, train_size)?.broadcast_add(&y_col_emb.unsqueeze(0)?)?;
380 let mut src = if train_size < t {
381 Tensor::cat(&[&train_part, &cell_emb.narrow(1, train_size, t - train_size)?], 1)?
382 } else {
383 train_part
384 };
385
386 let n_heads = self.config.dist_embed_num_heads;
387 let head_dim = self.config.embed_dim / n_heads;
388 for isab in &self.dist_embed {
389 let h = src.dim(0)?;
390 let num_inds = isab.ind_vectors.dim(0)?;
391 let dim = isab.ind_vectors.dim(1)?;
392 let ind = isab.ind_vectors.unsqueeze(0)?.expand((h, num_inds, dim))?.contiguous()?;
393 let kv_train = src.narrow(1, 0, train_size)?;
394 let hidden = cross_attn_block_forward(&ind, &kv_train, &isab.block1, n_heads, head_dim, train_size)?;
395 src = cross_attn_block_forward(&src, &hidden, &isab.block2, n_heads, head_dim, num_inds)?;
396 }
397 Ok(src)
398 }
399
400 fn col_agg_forward(&self, col_embeddings: &Tensor, _train_size: usize) -> Result<Tensor> {
402 let h = col_embeddings.dim(0)?;
403 let t = col_embeddings.dim(1)?;
404 let dim = self.config.embed_dim;
405 let n_cls = self.config.feat_agg_num_cls_tokens;
406 let n_heads = self.config.feat_agg_num_heads;
407 let head_dim = dim / n_heads;
408
409 let feat = col_embeddings.permute((1, 0, 2))?.contiguous()?; let cls = self.col_agg_cls_tokens.unsqueeze(0)?.expand((t, n_cls, dim))?.contiguous()?;
411 let mut seq = Tensor::cat(&[&cls, &feat], 1)?; let max_len = h + n_cls;
414 let (cos, sin) = rope_table(&self.col_agg_rope_freqs, max_len, &self.device)?;
415
416 let n_blocks = self.col_agg_blocks.len();
417 for (i, blk) in self.col_agg_blocks.iter().enumerate() {
418 if i + 1 == n_blocks {
419 let cls_q = seq.narrow(1, 0, n_cls)?;
420 seq = transformer_block_forward_cross(&cls_q, &seq, blk, n_heads, head_dim, Some((&cos, &sin)))?;
421 } else {
422 seq = transformer_block_forward(&seq, blk, n_heads, head_dim, Some((&cos, &sin)))?;
423 }
424 }
425
426 let out = zsfm_nn::rms_norm(&seq, Some(&self.col_agg_out_ln_w), RMS_EPS)?; out.reshape((t, n_cls * dim)).map_err(anyhow::Error::from)
428 }
429
430 fn icl_block_forward(&self, x: &Tensor, blk: &IclBlockW, train_size: usize) -> Result<Tensor> {
434 let n_heads = self.config.icl_num_heads;
435 let icl_dim = self.config.icl_dim();
436 let head_dim = icl_dim / n_heads;
437 let t = x.dim(0)?;
438
439 let normed = zsfm_nn::rms_norm(x, Some(&blk.ln_w), RMS_EPS)?;
440 let q = zsfm_nn::linear_nobias(&normed, &blk.attn.q_w)?.reshape((t, n_heads, head_dim))?;
441 let x_train = normed.narrow(0, 0, train_size)?;
442 let k = zsfm_nn::linear_nobias(&x_train, &blk.attn.k_w)?.reshape((train_size, n_heads, head_dim))?;
443 let v = zsfm_nn::linear_nobias(&x_train, &blk.attn.v_w)?.reshape((train_size, n_heads, head_dim))?;
444
445 let q = q.permute((1, 0, 2))?.contiguous()?;
447 let k = k.permute((1, 0, 2))?.contiguous()?;
448 let v = v.permute((1, 0, 2))?.contiguous()?;
449
450 let attn_out = match self.config.icl_num_kv_heads_test {
451 Some(kv_heads_test) if train_size < t => {
452 let q_train = q.narrow(1, 0, train_size)?;
453 let q_test = q.narrow(1, train_size, t - train_size)?;
454 let out_train = sdpa_with_ssmax(&q_train, &k, &v, blk.attn.ssmax.as_ref(), train_size, n_heads, head_dim)?;
455
456 let k_test = k.narrow(0, 0, kv_heads_test)?;
457 let v_test = v.narrow(0, 0, kv_heads_test)?;
458 let k_test = repeat_heads(&k_test, n_heads / kv_heads_test)?;
459 let v_test = repeat_heads(&v_test, n_heads / kv_heads_test)?;
460 let out_test =
461 sdpa_with_ssmax(&q_test, &k_test, &v_test, blk.attn.ssmax.as_ref(), train_size, n_heads, head_dim)?;
462 Tensor::cat(&[&out_train, &out_test], 1)? }
464 _ => sdpa_with_ssmax(&q, &k, &v, blk.attn.ssmax.as_ref(), train_size, n_heads, head_dim)?,
465 };
466
467 let attn_out = attn_out.permute((1, 0, 2))?.contiguous()?.reshape((t, icl_dim))?;
468 let attn_out = zsfm_nn::linear_nobias(&attn_out, &blk.attn.out_w)?;
469 let x = (x + attn_out)?;
470
471 let ff_in = zsfm_nn::rms_norm(&x, Some(&blk.ln_mlp_w), RMS_EPS)?;
472 let ff = mlp_forward(&ff_in, &blk.mlp)?;
473 Ok((x + ff)?)
474 }
475
476 fn many_class_decoder_forward(
478 &self,
479 train_emb: &Tensor,
480 test_emb: &Tensor,
481 y_support: &[usize],
482 highest_target: usize,
483 n_classes: usize,
484 ) -> Result<Tensor> {
485 let dec = &self.many_class_decoder;
486 let n_heads = self.config.decoder_num_heads;
487 let head_dim = self.config.decoder_head_dim;
488 let n = train_emb.dim(0)?;
489 let m = test_emb.dim(0)?;
490
491 let q = zsfm_nn::linear_bias(test_emb, &dec.q_w, &dec.q_b)?.reshape((m, n_heads, head_dim))?;
492 let k = zsfm_nn::linear_bias(train_emb, &dec.k_w, &dec.k_b)?.reshape((n, n_heads, head_dim))?;
493 let q = q.permute((1, 0, 2))?.contiguous()?; let k = k.permute((1, 0, 2))?.contiguous()?; let one_hot_width = highest_target + 1;
497 let mut one_hot = vec![0f32; n * one_hot_width];
498 for (i, &c) in y_support.iter().enumerate() {
499 one_hot[i * one_hot_width + c] = 1.0;
500 }
501 let v_row = Tensor::from_vec(one_hot, (n, one_hot_width), &self.device)?;
503 let v = v_row.unsqueeze(0)?.expand((n_heads, n, one_hot_width))?.contiguous()?;
504
505 let num_chunks = one_hot_width.div_ceil(head_dim);
506 let padded_width = num_chunks * head_dim;
507 let v = if padded_width > one_hot_width {
508 v.pad_with_zeros(2, 0, padded_width - one_hot_width)?
509 } else {
510 v
511 };
512
513 let mut chunk_outs = Vec::with_capacity(num_chunks);
514 for c in 0..num_chunks {
515 let v_chunk = v.narrow(2, c * head_dim, head_dim)?;
516 let out = sdpa_with_ssmax(&q, &k, &v_chunk, dec.ssmax.as_ref(), n, n_heads, head_dim)?; chunk_outs.push(out);
518 }
519 let out = if chunk_outs.len() == 1 {
520 chunk_outs.into_iter().next().unwrap()
521 } else {
522 let refs: Vec<&Tensor> = chunk_outs.iter().collect();
523 Tensor::cat(&refs, 2)? };
525 let out = out.narrow(2, 0, one_hot_width)?; let out = out.mean(0)?; let out = if n_classes > one_hot_width {
529 out.pad_with_zeros(1, 0, n_classes - one_hot_width)?
530 } else {
531 out.narrow(1, 0, n_classes)?
532 };
533
534 let clamped = out.clamp(1e-5f32, f32::INFINITY)?;
535 ((clamped + 3e-5)?).log().map_err(anyhow::Error::from)
536 }
537}
538
539fn sdpa_with_ssmax(
542 q: &Tensor,
543 k: &Tensor,
544 v: &Tensor,
545 ssmax: Option<&SsmaxW>,
546 ssmax_n: usize,
547 n_heads: usize,
548 head_dim: usize,
549) -> Result<Tensor> {
550 let q = match ssmax {
551 Some(s) => apply_ssmax(q, s, ssmax_n, n_heads, head_dim)?,
552 None => q.clone(),
553 };
554 let scale = 1.0 / (head_dim as f64).sqrt();
555 let scores = (q.matmul(&k.transpose(1, 2)?)? * scale)?;
556 let probs = candle_nn::ops::softmax_last_dim(&scores)?;
557 probs.matmul(v).map_err(anyhow::Error::from)
558}
559
560fn repeat_heads(x: &Tensor, repeat: usize) -> Result<Tensor> {
563 if repeat == 1 {
564 return Ok(x.clone());
565 }
566 let (h, s, d) = x.dims3()?;
567 x.unsqueeze(1)?.expand((h, repeat, s, d))?.reshape((h * repeat, s, d)).map_err(anyhow::Error::from)
568}
569
570fn apply_ssmax(q: &Tensor, ssmax: &SsmaxW, n: usize, n_heads: usize, head_dim: usize) -> Result<Tensor> {
573 let device = q.device();
574 let logn = (n.max(1) as f32).ln();
575 let logn_t = Tensor::from_vec(vec![logn], (1, 1), device)?;
576 let base = zsfm_nn::linear_bias(&logn_t, &ssmax.base_fc1_w, &ssmax.base_fc1_b)?.gelu_erf()?;
577 let base = zsfm_nn::linear_bias(&base, &ssmax.base_fc2_w, &ssmax.base_fc2_b)?; let base = base.reshape((n_heads, 1, head_dim))?;
579
580 let batch_heads = q.dim(0)?;
584 let seq = q.dim(1)?;
585 let batch = batch_heads / n_heads;
586 let base = if batch > 1 {
587 base.unsqueeze(0)?.expand((batch, n_heads, 1, head_dim))?.reshape((batch_heads, 1, head_dim))?
588 } else {
589 base
590 };
591
592 let q_flat = q.reshape((batch_heads * seq, head_dim))?;
593 let qm = zsfm_nn::linear_bias(&q_flat, &ssmax.query_fc1_w, &ssmax.query_fc1_b)?.gelu_erf()?;
594 let qm = zsfm_nn::linear_bias(&qm, &ssmax.query_fc2_w, &ssmax.query_fc2_b)?;
595 let modulation = (qm.tanh()? + 1.0)?.reshape((batch_heads, seq, head_dim))?;
596
597 let scales = base.broadcast_mul(&modulation)?;
598 Ok(q.broadcast_mul(&scales)?)
599}
600
601fn mlp_forward(x: &Tensor, mlp: &MlpW) -> Result<Tensor> {
602 let h = zsfm_nn::linear_nobias(x, &mlp.fc1_w)?.gelu_erf()?;
603 zsfm_nn::linear_nobias(&h, &mlp.fc2_w).map_err(anyhow::Error::from)
604}
605
606fn cross_attn_block_forward(
610 query: &Tensor,
611 context: &Tensor,
612 blk: &CrossAttnBlockW,
613 n_heads: usize,
614 head_dim: usize,
615 ssmax_n: usize,
616) -> Result<Tensor> {
617 let q_normed = zsfm_nn::rms_norm(query, Some(&blk.ln_q_w), RMS_EPS)?;
618 let kv_normed = zsfm_nn::rms_norm(context, Some(&blk.ln_kv_w), RMS_EPS)?;
619 let attn_out = batched_qkv_attention(&q_normed, &kv_normed, &blk.attn, n_heads, head_dim, ssmax_n, None)?;
620 let x = (query + attn_out)?;
621 let ff_in = zsfm_nn::rms_norm(&x, Some(&blk.ln2_w), RMS_EPS)?;
622 let ff = mlp_forward(&ff_in, &blk.mlp)?;
623 Ok((x + ff)?)
624}
625
626fn transformer_block_forward(
629 x: &Tensor,
630 blk: &TransformerBlockW,
631 n_heads: usize,
632 head_dim: usize,
633 rope_cos_sin: Option<(&Tensor, &Tensor)>,
634) -> Result<Tensor> {
635 let normed = zsfm_nn::rms_norm(x, Some(&blk.ln_w), RMS_EPS)?;
636 let attn_out = batched_qkv_self_attention(&normed, &blk.attn, n_heads, head_dim, rope_cos_sin)?;
637 let x = (x + attn_out)?;
638 let ff_in = zsfm_nn::rms_norm(&x, Some(&blk.ln_mlp_w), RMS_EPS)?;
639 let ff = mlp_forward(&ff_in, &blk.mlp)?;
640 Ok((x + ff)?)
641}
642
643fn transformer_block_forward_cross(
647 query: &Tensor,
648 context: &Tensor,
649 blk: &TransformerBlockW,
650 n_heads: usize,
651 head_dim: usize,
652 rope_cos_sin: Option<(&Tensor, &Tensor)>,
653) -> Result<Tensor> {
654 let q_normed = zsfm_nn::rms_norm(query, Some(&blk.ln_w), RMS_EPS)?;
655 let kv_normed = zsfm_nn::rms_norm(context, Some(&blk.ln_w), RMS_EPS)?;
656 let attn_out = batched_qkv_attention(&q_normed, &kv_normed, &blk.attn, n_heads, head_dim, 0, rope_cos_sin)?;
657 let x = (query + attn_out)?;
658 let ff_in = zsfm_nn::rms_norm(&x, Some(&blk.ln_mlp_w), RMS_EPS)?;
659 let ff = mlp_forward(&ff_in, &blk.mlp)?;
660 Ok((x + ff)?)
661}
662
663fn batched_qkv_attention(
669 q_in: &Tensor,
670 kv_in: &Tensor,
671 attn: &QkvW,
672 n_heads: usize,
673 head_dim: usize,
674 ssmax_n: usize,
675 rope_cos_sin: Option<(&Tensor, &Tensor)>,
676) -> Result<Tensor> {
677 let dim = n_heads * head_dim;
678 let batch = q_in.dim(0)?;
679 let q_len = q_in.dim(1)?;
680 let kv_len = kv_in.dim(1)?;
681
682 let q = zsfm_nn::linear_nobias(q_in, &attn.q_w)?.reshape((batch, q_len, n_heads, head_dim))?;
683 let k = zsfm_nn::linear_nobias(kv_in, &attn.k_w)?.reshape((batch, kv_len, n_heads, head_dim))?;
684 let v = zsfm_nn::linear_nobias(kv_in, &attn.v_w)?.reshape((batch, kv_len, n_heads, head_dim))?;
685
686 let q = q.permute((0, 2, 1, 3))?.contiguous()?.reshape((batch * n_heads, q_len, head_dim))?;
687 let k = k.permute((0, 2, 1, 3))?.contiguous()?.reshape((batch * n_heads, kv_len, head_dim))?;
688 let v = v.permute((0, 2, 1, 3))?.contiguous()?.reshape((batch * n_heads, kv_len, head_dim))?;
689
690 let (q, k) = if let Some((cos, sin)) = rope_cos_sin {
691 (apply_rope(&q, cos, sin, batch, n_heads)?, apply_rope(&k, cos, sin, batch, n_heads)?)
692 } else {
693 (q, k)
694 };
695
696 let n = if ssmax_n > 0 { ssmax_n } else { kv_len };
697 let out = sdpa_with_ssmax(&q, &k, &v, attn.ssmax.as_ref(), n, n_heads, head_dim)?;
698 let out = out.reshape((batch, n_heads, q_len, head_dim))?.permute((0, 2, 1, 3))?.contiguous()?.reshape((
699 batch,
700 q_len,
701 dim,
702 ))?;
703 zsfm_nn::linear_nobias(&out, &attn.out_w).map_err(anyhow::Error::from)
704}
705
706fn batched_qkv_self_attention(
708 x: &Tensor,
709 attn: &QkvW,
710 n_heads: usize,
711 head_dim: usize,
712 rope_cos_sin: Option<(&Tensor, &Tensor)>,
713) -> Result<Tensor> {
714 let dim = n_heads * head_dim;
715 let batch = x.dim(0)?;
716 let seq = x.dim(1)?;
717
718 let q = zsfm_nn::linear_nobias(x, &attn.q_w)?.reshape((batch, seq, n_heads, head_dim))?;
719 let k = zsfm_nn::linear_nobias(x, &attn.k_w)?.reshape((batch, seq, n_heads, head_dim))?;
720 let v = zsfm_nn::linear_nobias(x, &attn.v_w)?.reshape((batch, seq, n_heads, head_dim))?;
721
722 let q = q.permute((0, 2, 1, 3))?.contiguous()?.reshape((batch * n_heads, seq, head_dim))?;
723 let k = k.permute((0, 2, 1, 3))?.contiguous()?.reshape((batch * n_heads, seq, head_dim))?;
724 let v = v.permute((0, 2, 1, 3))?.contiguous()?.reshape((batch * n_heads, seq, head_dim))?;
725
726 let (q, k) = if let Some((cos, sin)) = rope_cos_sin {
727 (apply_rope(&q, cos, sin, batch, n_heads)?, apply_rope(&k, cos, sin, batch, n_heads)?)
728 } else {
729 (q, k)
730 };
731
732 let out = sdpa_with_ssmax(&q, &k, &v, attn.ssmax.as_ref(), seq, n_heads, head_dim)?;
733 let out =
734 out.reshape((batch, n_heads, seq, head_dim))?.permute((0, 2, 1, 3))?.contiguous()?.reshape((batch, seq, dim))?;
735 zsfm_nn::linear_nobias(&out, &attn.out_w).map_err(anyhow::Error::from)
736}
737
738fn apply_rope(x: &Tensor, cos: &Tensor, sin: &Tensor, _batch: usize, _n_heads: usize) -> Result<Tensor> {
740 let seq = x.dim(1)?;
741 let head_dim = x.dim(2)?;
742 let half = head_dim / 2;
743 let cos = cos.narrow(0, 0, seq)?.unsqueeze(0)?; let sin = sin.narrow(0, 0, seq)?.unsqueeze(0)?;
745 let x1 = x.narrow(2, 0, half)?;
746 let x2 = x.narrow(2, half, half)?;
747 let rotated = Tensor::cat(&[&x2.neg()?, &x1], 2)?;
748 Ok((x.broadcast_mul(&cos)? + rotated.broadcast_mul(&sin)?)?)
749}
750
751fn rope_table(freqs: &[f32], max_len: usize, device: &Device) -> Result<(Tensor, Tensor)> {
752 let half = freqs.len();
753 let mut cos = vec![0f32; max_len * half * 2];
754 let mut sin = vec![0f32; max_len * half * 2];
755 for p in 0..max_len {
756 for i in 0..half {
757 let angle = p as f32 * freqs[i];
758 let (s, c) = angle.sin_cos();
759 cos[p * 2 * half + i] = c;
760 cos[p * 2 * half + half + i] = c;
761 sin[p * 2 * half + i] = s;
762 sin[p * 2 * half + half + i] = s;
763 }
764 }
765 let cos_t = Tensor::from_vec(cos, (max_len, 2 * half), device)?;
766 let sin_t = Tensor::from_vec(sin, (max_len, 2 * half), device)?;
767 Ok((cos_t, sin_t))
768}
769
770fn softmax(logits: &[f32]) -> Vec<f32> {
771 let max = logits.iter().copied().fold(f32::NEG_INFINITY, f32::max);
772 let exps: Vec<f32> = logits.iter().map(|&v| (v - max).exp()).collect();
773 let sum: f32 = exps.iter().sum();
774 exps.into_iter().map(|v| v / sum).collect()
775}
776
777fn preprocess_x(rows: &[Vec<f32>], train_size: usize) -> Vec<Vec<f32>> {
786 let n_feat = rows[0].len();
787 let mut mean = vec![0f32; n_feat];
788 let mut std = vec![0f32; n_feat];
789 for f in 0..n_feat {
790 let m: f32 = rows[..train_size].iter().map(|r| r[f]).sum::<f32>() / train_size as f32;
791 let denom = if train_size > 1 { (train_size - 1) as f32 } else { 1.0 };
792 let var: f32 = rows[..train_size].iter().map(|r| (r[f] - m).powi(2)).sum::<f32>() / denom;
793 mean[f] = m;
794 std[f] = if var == 0.0 || train_size <= 1 { 1.0 } else { var.sqrt() };
795 }
796 let eps = f32::EPSILON;
797 rows.iter()
798 .map(|row| {
799 row.iter().enumerate().map(|(f, &v)| ((v - mean[f]) / (std[f] + eps)).clamp(-100.0, 100.0)).collect()
800 })
801 .collect()
802}
803
804fn feature_group_with_nan_indicators(rows: &[Vec<f32>], h: usize, size: usize, use_nan_indicators: bool) -> Vec<Vec<Vec<f32>>> {
809 rows.iter()
810 .map(|row| {
811 (0..h)
812 .map(|g| {
813 let mut cell: Vec<f32> = (0..size).map(|k| row[(g + (1usize << k)) % h]).collect();
814 if use_nan_indicators {
815 cell.extend(std::iter::repeat_n(0.0f32, size));
816 }
817 cell
818 })
819 .collect()
820 })
821 .collect()
822}