1use std::io::{BufReader, Read, Seek};
15use std::path::Path;
16
17use anyhow::{Context, Result};
18use candle_core::quantized::gguf_file;
19use candle_core::{DType, Device, Tensor, D};
20
21use crate::config::TabDptConfig;
22
23struct BlockW {
28 attn_norm_w: Tensor,
29 attn_norm_b: Tensor,
30 ff_norm_w: Tensor,
31 ff_norm_b: Tensor,
32 q_proj_w: Tensor,
33 k_proj_w: Tensor,
34 v_proj_w: Tensor,
35 out_proj_w: Tensor,
36 q_gate_w: Tensor,
37 q_norm_w: Tensor,
38 k_norm_w: Tensor,
39 ff_up_w: Tensor,
40 ff_down_w: Tensor,
41}
42
43struct YEncW {
44 fc1_w: Tensor,
45 fc1_b: Tensor,
46 fc2_w: Tensor,
47 fc2_b: Tensor,
48}
49
50pub struct TabDptModel {
51 device: Device,
52 config: TabDptConfig,
53 encoder_w: Tensor,
54 encoder_b: Tensor,
55 thinking_embed: Tensor,
56 blocks: Vec<BlockW>,
57 y_encoders: Vec<YEncW>,
58 head_fc1_w: Tensor,
59 head_fc1_b: Tensor,
60 head_fc2_w: Tensor,
61 head_fc2_b: Tensor,
62}
63
64const LN_EPS: f64 = 1e-5;
65const RMS_EPS: f64 = 1e-8;
66const CLIP_N_SIGMA: f32 = 8.0;
67
68fn load_t(content: &gguf_file::Content, reader: &mut (impl Read + Seek), name: &str, device: &Device) -> Result<Tensor> {
73 zsfm_nn::load_tensor(content, reader, name, device, DType::F32)
74}
75
76impl TabDptModel {
77 pub fn load(gguf_path: &Path, config: TabDptConfig) -> Result<Self> {
78 let device = Device::Cpu;
79 let file = std::fs::File::open(gguf_path).with_context(|| format!("open {}", gguf_path.display()))?;
80 let mut reader = BufReader::with_capacity(zsfm_gguf::READ_BUF_CAPACITY, file);
81 let content = gguf_file::Content::read(&mut reader).context("parse GGUF header")?;
82
83 let encoder_w = load_t(&content, &mut reader, "encoder.weight", &device)?;
84 let encoder_b = load_t(&content, &mut reader, "encoder.bias", &device)?;
85 let thinking_embed = load_t(&content, &mut reader, "thinking_embed", &device)?;
86
87 let mut blocks = Vec::with_capacity(config.n_layers);
88 let mut y_encoders = Vec::with_capacity(config.n_layers);
89 for n in 0..config.n_layers {
90 let p = |s: &str| format!("blk.{n}.{s}");
91 blocks.push(BlockW {
92 attn_norm_w: load_t(&content, &mut reader, &p("attn_norm.weight"), &device)?,
93 attn_norm_b: load_t(&content, &mut reader, &p("attn_norm.bias"), &device)?,
94 ff_norm_w: load_t(&content, &mut reader, &p("ff_norm.weight"), &device)?,
95 ff_norm_b: load_t(&content, &mut reader, &p("ff_norm.bias"), &device)?,
96 q_proj_w: load_t(&content, &mut reader, &p("q_proj.weight"), &device)?,
97 k_proj_w: load_t(&content, &mut reader, &p("k_proj.weight"), &device)?,
98 v_proj_w: load_t(&content, &mut reader, &p("v_proj.weight"), &device)?,
99 out_proj_w: load_t(&content, &mut reader, &p("out_proj.weight"), &device)?,
100 q_gate_w: load_t(&content, &mut reader, &p("q_gate.weight"), &device)?,
101 q_norm_w: load_t(&content, &mut reader, &p("q_norm.weight"), &device)?,
102 k_norm_w: load_t(&content, &mut reader, &p("k_norm.weight"), &device)?,
103 ff_up_w: load_t(&content, &mut reader, &p("ff_up.weight"), &device)?,
104 ff_down_w: load_t(&content, &mut reader, &p("ff_down.weight"), &device)?,
105 });
106
107 let yp = |s: &str| format!("y_enc.{n}.{s}");
108 y_encoders.push(YEncW {
109 fc1_w: load_t(&content, &mut reader, &yp("fc1.weight"), &device)?,
110 fc1_b: load_t(&content, &mut reader, &yp("fc1.bias"), &device)?,
111 fc2_w: load_t(&content, &mut reader, &yp("fc2.weight"), &device)?,
112 fc2_b: load_t(&content, &mut reader, &yp("fc2.bias"), &device)?,
113 });
114 }
115
116 let head_fc1_w = load_t(&content, &mut reader, "head_fc1.weight", &device)?;
117 let head_fc1_b = load_t(&content, &mut reader, "head_fc1.bias", &device)?;
118 let head_fc2_w = load_t(&content, &mut reader, "head_fc2.weight", &device)?;
119 let head_fc2_b = load_t(&content, &mut reader, "head_fc2.bias", &device)?;
120
121 Ok(Self {
122 device,
123 config,
124 encoder_w,
125 encoder_b,
126 thinking_embed,
127 blocks,
128 y_encoders,
129 head_fc1_w,
130 head_fc1_b,
131 head_fc2_w,
132 head_fc2_b,
133 })
134 }
135
136 pub fn predict_classification(
144 &self,
145 x_support: &[Vec<f32>],
146 y_support: &[usize],
147 x_query: &[Vec<f32>],
148 n_classes: usize,
149 ) -> Result<Vec<Vec<f32>>> {
150 let (x_s, x_q) = preprocess_x_outer(x_support, x_query, self.config.max_num_features);
151 let y_s: Vec<f32> = y_support.iter().map(|&c| c as f32).collect();
152
153 let out = self.forward(&x_s, &y_s, &x_q)?; let n_q = x_query.len();
155 let width = self.config.max_num_classes + self.config.regression_bin_count;
156 let flat: Vec<f32> = out.flatten_all()?.to_vec1()?;
157
158 let mut result = Vec::with_capacity(n_q);
159 for i in 0..n_q {
160 let row = &flat[i * width..i * width + n_classes];
161 let log_probs = log_softmax(row);
162 result.push(softmax(&log_probs));
163 }
164 Ok(result)
165 }
166
167 pub fn predict_regression(&self, x_support: &[Vec<f32>], y_support: &[f32], x_query: &[Vec<f32>]) -> Result<Vec<f32>> {
169 let (x_s, x_q) = preprocess_x_outer(x_support, x_query, self.config.max_num_features);
170
171 let mean_y: f64 = y_support.iter().map(|&v| v as f64).sum::<f64>() / y_support.len() as f64;
172 let var_y: f64 = y_support.iter().map(|&v| (v as f64 - mean_y).powi(2)).sum::<f64>() / (y_support.len() - 1) as f64;
173 let std_y = var_y.sqrt() + 1e-6;
174 let y_s: Vec<f32> = y_support.iter().map(|&v| ((v as f64 - mean_y) / std_y) as f32).collect();
175
176 let out = self.forward(&x_s, &y_s, &x_q)?;
177 let n_q = x_query.len();
178 let width = self.config.max_num_classes + self.config.regression_bin_count;
179 let flat: Vec<f32> = out.flatten_all()?.to_vec1()?;
180
181 let n_bins = self.config.regression_bin_count;
182 let bin_lo = self.config.regression_bin_min;
183 let bin_hi = self.config.regression_bin_max;
184 let bin_width = (bin_hi - bin_lo) / n_bins as f32;
185 let bin_centers: Vec<f32> = (0..n_bins).map(|i| bin_lo + (i as f32 + 0.5) * bin_width).collect();
186
187 let mut result = Vec::with_capacity(n_q);
188 for i in 0..n_q {
189 let row = &flat[i * width + self.config.max_num_classes..i * width + width];
190 let probs = softmax(row);
191 let expectation: f32 = probs.iter().zip(&bin_centers).map(|(p, c)| p * c).sum();
192 result.push((expectation as f64 * std_y + mean_y) as f32);
193 }
194 Ok(result)
195 }
196
197 fn forward(&self, x_support: &[Vec<f32>], y_support: &[f32], x_query: &[Vec<f32>]) -> Result<Tensor> {
202 let n_s = x_support.len();
203 let n_q = x_query.len();
204 let n_feat = self.config.max_num_features;
205 let n_think = self.config.n_thinking_rows;
206 let ctx_len = n_think + n_s;
207
208 let mut all_rows: Vec<Vec<f32>> = Vec::with_capacity(n_s + n_q);
210 all_rows.extend_from_slice(x_support);
211 all_rows.extend_from_slice(x_query);
212 let all_rows = clip_outliers(all_rows, n_s, n_feat, CLIP_N_SIGMA);
213 let (all_rows, _mean, _std) = normalize_data(all_rows, n_s, n_feat);
214 let mut all_rows = clip_outliers(all_rows, n_s, n_feat, CLIP_N_SIGMA);
215 for row in &mut all_rows {
216 for v in row.iter_mut() {
217 if v.is_nan() || v.is_infinite() {
218 *v = 0.0;
219 }
220 }
221 }
222
223 let flat: Vec<f32> = all_rows.into_iter().flatten().collect();
224 let x_t = Tensor::from_vec(flat, (n_s + n_q, n_feat), &self.device)?;
225 let x_enc = zsfm_nn::linear_bias(&x_t, &self.encoder_w, &self.encoder_b)?; let x_enc = layer_norm_no_affine(&x_enc, LN_EPS)?;
227
228 let mut src = Tensor::cat(&[&self.thinking_embed, &x_enc], 0)?; let y_support_t = Tensor::from_vec(y_support.to_vec(), (n_s, 1), &self.device)?;
231 let zeros_think_emb = Tensor::zeros((n_think, self.config.y_encoder_dim), DType::F32, &self.device)?;
232
233 let kappa = self.config.kappa();
234 let beta = attention_beta(kappa, ctx_len, self.config.base_len, self.config.max_len);
235
236 for (blk, yenc) in self.blocks.iter().zip(self.y_encoders.iter()) {
237 let y_h = zsfm_nn::linear_bias(&y_support_t, ¥c.fc1_w, ¥c.fc1_b)?.gelu_erf()?;
241 let y_h = zsfm_nn::linear_bias(&y_h, ¥c.fc2_w, ¥c.fc2_b)?;
242 let y_support_emb = layer_norm_no_affine(&y_h, LN_EPS)?; let y_emb = Tensor::cat(&[&zeros_think_emb, &y_support_emb], 0)?; src = layer_forward(&src, &y_emb, ctx_len, blk, self.config.n_heads, beta)?;
246 }
247
248 let query_out = src.narrow(0, ctx_len, n_q)?; let h = zsfm_nn::linear_bias(&query_out, &self.head_fc1_w, &self.head_fc1_b)?.gelu_erf()?;
250 zsfm_nn::linear_bias(&h, &self.head_fc2_w, &self.head_fc2_b).map_err(anyhow::Error::from)
251 }
252}
253
254fn layer_forward(src: &Tensor, y_emb: &Tensor, ctx_len: usize, blk: &BlockW, n_heads: usize, beta: f64) -> Result<Tensor> {
257 let dim = src.dim(1)?;
258 let head_dim = dim / n_heads;
259 let l = src.dim(0)?;
260
261 let h = zsfm_nn::layer_norm(src, &blk.attn_norm_w, &blk.attn_norm_b, LN_EPS)?;
262 let q = zsfm_nn::linear_nobias(&h, &blk.q_proj_w)?; let gate = candle_nn::ops::sigmoid(&zsfm_nn::linear_nobias(&q, &blk.q_gate_w)?)?; let h_ctx = h.narrow(0, 0, ctx_len)?;
266 let k = zsfm_nn::linear_nobias(&h_ctx, &blk.k_proj_w)?; let v_in = Tensor::cat(&[&h_ctx, y_emb], 1)?; let v = zsfm_nn::linear_nobias(&v_in, &blk.v_proj_w)?; let q = q.reshape((l, n_heads, head_dim))?.permute((1, 0, 2))?.contiguous()?; let k = k.reshape((ctx_len, n_heads, head_dim))?.permute((1, 0, 2))?.contiguous()?;
272 let v = v.reshape((ctx_len, n_heads, head_dim))?.permute((1, 0, 2))?.contiguous()?;
273
274 let q = zsfm_nn::rms_norm(&q, Some(&blk.q_norm_w), RMS_EPS)?;
275 let k = zsfm_nn::rms_norm(&k, Some(&blk.k_norm_w), RMS_EPS)?;
276
277 let scale = beta / (head_dim as f64).sqrt();
278 let scores = (q.matmul(&k.transpose(1, 2)?)? * scale)?; let attn = candle_nn::ops::softmax_last_dim(&scores)?;
280 let out = attn.matmul(&v)?; let out = out.permute((1, 0, 2))?.contiguous()?; let gate = gate.reshape((l, n_heads, 1))?;
284 let out = out.broadcast_mul(&gate)?.reshape((l, dim))?;
285 let attn_out = zsfm_nn::linear_nobias(&out, &blk.out_proj_w)?;
286
287 let x1 = (src + attn_out)?;
288 let ff_in = zsfm_nn::layer_norm(&x1, &blk.ff_norm_w, &blk.ff_norm_b, LN_EPS)?;
289 let up = zsfm_nn::linear_nobias(&ff_in, &blk.ff_up_w)?;
290 let ff_dim2 = up.dim(1)? / 2;
291 let u = up.narrow(1, 0, ff_dim2)?;
292 let v_ff = up.narrow(1, ff_dim2, ff_dim2)?;
293 let ff_out = zsfm_nn::linear_nobias(&(u.silu()? * v_ff)?, &blk.ff_down_w)?;
294
295 Ok((x1 + ff_out)?)
296}
297
298fn attention_beta(kappa: Option<f64>, ctx_len: usize, base_len: usize, max_len: usize) -> f64 {
301 match kappa {
302 None => 1.0,
303 Some(kappa) => {
304 let n = (ctx_len as f64).min(max_len as f64);
305 let log_term = (n / base_len as f64).ln().max(0.0);
306 1.0 + kappa * log_term
307 }
308 }
309}
310
311fn layer_norm_no_affine(x: &Tensor, eps: f64) -> Result<Tensor> {
312 let mean = x.mean_keepdim(D::Minus1)?;
313 let centered = x.broadcast_sub(&mean)?;
314 let var = centered.sqr()?.mean_keepdim(D::Minus1)?;
315 let std = (var + eps)?.sqrt()?;
316 Ok(centered.broadcast_div(&std)?)
317}
318
319fn log_softmax(logits: &[f32]) -> Vec<f32> {
320 let max = logits.iter().copied().fold(f32::NEG_INFINITY, f32::max);
321 let log_sum_exp = logits.iter().map(|&v| (v - max).exp()).sum::<f32>().ln();
322 logits.iter().map(|&v| v - max - log_sum_exp).collect()
323}
324
325fn softmax(logits: &[f32]) -> Vec<f32> {
326 let max = logits.iter().copied().fold(f32::NEG_INFINITY, f32::max);
327 let exps: Vec<f32> = logits.iter().map(|&v| (v - max).exp()).collect();
328 let sum: f32 = exps.iter().sum();
329 exps.into_iter().map(|v| v / sum).collect()
330}
331
332fn preprocess_x_outer(x_support: &[Vec<f32>], x_query: &[Vec<f32>], max_features: usize) -> (Vec<Vec<f32>>, Vec<Vec<f32>>) {
338 let n_feat = x_support[0].len();
339
340 let mut impute_mean = vec![0f32; n_feat];
341 for f in 0..n_feat {
342 let mut sum = 0f64;
343 let mut count = 0usize;
344 for row in x_support {
345 if !row[f].is_nan() {
346 sum += row[f] as f64;
347 count += 1;
348 }
349 }
350 impute_mean[f] = if count > 0 { (sum / count as f64) as f32 } else { 0.0 };
351 }
352 let impute = |row: &[f32]| -> Vec<f32> {
353 row.iter().enumerate().map(|(f, &v)| if v.is_nan() { impute_mean[f] } else { v }).collect()
354 };
355 let support_imputed: Vec<Vec<f32>> = x_support.iter().map(|r| impute(r)).collect();
356 let query_imputed: Vec<Vec<f32>> = x_query.iter().map(|r| impute(r)).collect();
357
358 let n_s = support_imputed.len();
360 let mut scaler_mean = vec![0f32; n_feat];
361 let mut scaler_std = vec![0f32; n_feat];
362 for f in 0..n_feat {
363 let mean: f64 = support_imputed.iter().map(|r| r[f] as f64).sum::<f64>() / n_s as f64;
364 let var: f64 = support_imputed.iter().map(|r| (r[f] as f64 - mean).powi(2)).sum::<f64>() / n_s as f64;
365 scaler_mean[f] = mean as f32;
366 scaler_std[f] = if var == 0.0 { 1.0 } else { var.sqrt() as f32 };
368 }
369
370 let scale_and_pad = |rows: &[Vec<f32>]| -> Vec<Vec<f32>> {
371 rows.iter()
372 .map(|row| {
373 let mut out = vec![0f32; max_features];
374 for f in 0..n_feat.min(max_features) {
375 out[f] = (row[f] - scaler_mean[f]) / scaler_std[f];
376 }
377 out
378 })
379 .collect()
380 };
381
382 (scale_and_pad(&support_imputed), scale_and_pad(&query_imputed))
383}
384
385fn clip_outliers(mut rows: Vec<Vec<f32>>, n_ctx: usize, n_feat: usize, n_sigma: f32) -> Vec<Vec<f32>> {
391 for f in 0..n_feat {
392 let ctx_vals: Vec<f32> = rows[..n_ctx].iter().map(|r| r[f]).filter(|v| !v.is_nan()).collect();
393 if ctx_vals.is_empty() {
394 continue;
395 }
396 let mean = ctx_vals.iter().sum::<f32>() / ctx_vals.len() as f32;
401 let std1 = population_std(&ctx_vals, mean);
402 let cutoff1 = n_sigma * std1;
403 let refined: Vec<f32> = ctx_vals.iter().copied().filter(|&v| (v - mean).abs() <= cutoff1).collect();
404 let std2 = if refined.len() < 2 {
405 std1
406 } else {
407 let refined_mean = refined.iter().sum::<f32>() / refined.len() as f32;
408 population_std(&refined, refined_mean)
409 };
410 let cutoff2 = n_sigma * std2;
411 let (lo, hi) = (mean - cutoff2, mean + cutoff2);
412 for row in rows.iter_mut() {
413 row[f] = row[f].clamp(lo, hi);
414 }
415 }
416 rows
417}
418
419fn normalize_data(rows: Vec<Vec<f32>>, n_ctx: usize, n_feat: usize) -> (Vec<Vec<f32>>, Vec<f32>, Vec<f32>) {
420 let mut mean = vec![0f32; n_feat];
421 let mut std = vec![0f32; n_feat];
422 for f in 0..n_feat {
423 let ctx_vals: Vec<f32> = rows[..n_ctx].iter().map(|r| r[f]).filter(|v| !v.is_nan()).collect();
424 if ctx_vals.is_empty() {
425 mean[f] = 0.0;
426 std[f] = 1e-6;
427 continue;
428 }
429 let m = ctx_vals.iter().sum::<f32>() / ctx_vals.len() as f32;
430 mean[f] = m;
431 std[f] = population_std(&ctx_vals, m) + 1e-6;
432 }
433 let out: Vec<Vec<f32>> =
434 rows.into_iter().map(|row| row.iter().enumerate().map(|(f, &v)| (v - mean[f]) / std[f]).collect()).collect();
435 (out, mean, std)
436}
437
438fn population_std(vals: &[f32], mean: f32) -> f32 {
439 if vals.len() < 2 {
440 return 0.0;
441 }
442 let var = vals.iter().map(|&v| (v - mean).powi(2)).sum::<f32>() / (vals.len() - 1) as f32;
443 var.sqrt()
444}