1use std::io::{BufReader, Read, Seek};
2use std::path::Path;
3
4use anyhow::{Context, Result};
5use candle_core::quantized::gguf_file;
6use candle_core::{DType, Device, Tensor};
7
8use simdeez::prelude::*;
9use simdeez::math::{SimdMathF32Core, SimdMathF32Hyperbolic};
10
11use crate::config::TiRexConfig;
12
13struct Block {
18 norm_slstm: Vec<f32>, fizo_w: Vec<f32>, slstm_kernel_t: Vec<f32>, slstm_bias: Vec<f32>, group_norm_w: Vec<f32>, norm_ffn: Vec<f32>, ffn_gate_w: Tensor, ffn_up_w: Tensor, ffn_down_w: Tensor, }
28
29struct EmbedBlock {
30 hidden_w: Tensor, hidden_b: Vec<f32>, output_w: Tensor, output_b: Vec<f32>, residual_w: Tensor, residual_b: Vec<f32>, }
37
38pub struct TiRexModel {
39 device: Device,
40 config: TiRexConfig,
41 in_emb: EmbedBlock,
42 blocks: Vec<Block>,
43 out_norm: Vec<f32>, out_emb: EmbedBlock,
45}
46
47fn load_t(
52 content: &gguf_file::Content,
53 reader: &mut (impl Read + Seek),
54 name: &str,
55 device: &Device,
56) -> Result<Tensor> {
57 zsfm_nn::load_tensor(content, reader, name, device, DType::F32)
58}
59
60fn load_vec(
61 content: &gguf_file::Content,
62 reader: &mut (impl Read + Seek),
63 name: &str,
64 device: &Device,
65) -> Result<Vec<f32>> {
66 Ok(load_t(content, reader, name, device)?.flatten_all()?.to_vec1()?)
67}
68
69fn load_embed_block(
70 content: &gguf_file::Content,
71 reader: &mut (impl Read + Seek),
72 prefix: &str,
73 device: &Device,
74) -> Result<EmbedBlock> {
75 let p = |s: &str| format!("{prefix}.{s}");
76 Ok(EmbedBlock {
77 hidden_w: load_t(&content, reader, &p("hidden.weight"), device)?,
78 hidden_b: load_vec(&content, reader, &p("hidden.bias"), device)?,
79 output_w: load_t(&content, reader, &p("output.weight"), device)?,
80 output_b: load_vec(&content, reader, &p("output.bias"), device)?,
81 residual_w: load_t(&content, reader, &p("residual.weight"), device)?,
82 residual_b: load_vec(&content, reader, &p("residual.bias"), device)?,
83 })
84}
85
86impl TiRexModel {
87 pub fn load(gguf_path: &Path, config: TiRexConfig) -> Result<Self> {
88 let device = Device::Cpu;
89 let file = std::fs::File::open(gguf_path)
90 .with_context(|| format!("open {}", gguf_path.display()))?;
91 let mut reader = BufReader::with_capacity(zsfm_gguf::READ_BUF_CAPACITY, file);
92 let content = gguf_file::Content::read(&mut reader).context("parse GGUF header")?;
93
94 let in_emb = load_embed_block(&content, &mut reader, "in_emb", &device)?;
95
96 let nh = config.num_heads;
97 let dh = config.head_dim();
98 let wpp = dh * dh; let mut blocks = Vec::with_capacity(config.num_blocks);
101 for n in 0..config.num_blocks {
102 let p = |s: &str| format!("blk.{n}.{s}");
103
104 let fgate_w = load_vec(&content, &mut reader, &p("fgate.weight"), &device)?;
106 let igate_w = load_vec(&content, &mut reader, &p("igate.weight"), &device)?;
107 let zgate_w = load_vec(&content, &mut reader, &p("zgate.weight"), &device)?;
108 let ogate_w = load_vec(&content, &mut reader, &p("ogate.weight"), &device)?;
109 let mut fizo_w = vec![0.0f32; nh * 4 * wpp];
110 for h in 0..nh {
111 fizo_w[h*4*wpp.. h*4*wpp+wpp ].copy_from_slice(&fgate_w[h*wpp..(h+1)*wpp]);
112 fizo_w[h*4*wpp+wpp.. h*4*wpp+2*wpp].copy_from_slice(&igate_w[h*wpp..(h+1)*wpp]);
113 fizo_w[h*4*wpp+2*wpp.. h*4*wpp+3*wpp].copy_from_slice(&zgate_w[h*wpp..(h+1)*wpp]);
114 fizo_w[h*4*wpp+3*wpp.. h*4*wpp+4*wpp].copy_from_slice(&ogate_w[h*wpp..(h+1)*wpp]);
115 }
116
117 let raw_kernel = load_vec(&content, &mut reader, &p("slstm_kernel"), &device)?;
118 let ng = 4usize;
121 let mut slstm_kernel_t = vec![0.0f32; nh * ng * dh * dh];
122 for head in 0..nh {
123 for gate_d in 0..(ng * dh) {
124 for di in 0..dh {
125 slstm_kernel_t[head * ng * dh * dh + gate_d * dh + di] =
126 raw_kernel[head * dh * ng * dh + di * ng * dh + gate_d];
127 }
128 }
129 }
130
131 blocks.push(Block {
132 norm_slstm: load_vec(&content, &mut reader, &p("norm_slstm"), &device)?,
133 fizo_w,
134 slstm_kernel_t,
135 slstm_bias: load_vec(&content, &mut reader, &p("slstm_bias"), &device)?,
136 group_norm_w: load_vec(&content, &mut reader, &p("group_norm"), &device)?,
137 norm_ffn: load_vec(&content, &mut reader, &p("norm_ffn"), &device)?,
138 ffn_gate_w: load_t(&content, &mut reader, &p("ffn_gate.weight"), &device)?,
139 ffn_up_w: load_t(&content, &mut reader, &p("ffn_up.weight"), &device)?,
140 ffn_down_w: load_t(&content, &mut reader, &p("ffn_down.weight"), &device)?,
141 });
142 }
143
144 let out_norm = load_vec(&content, &mut reader, "out_norm", &device)?;
145 let out_emb = load_embed_block(&content, &mut reader, "out_emb", &device)?;
146
147 Ok(TiRexModel { device, config, in_emb, blocks, out_norm, out_emb })
148 }
149
150 pub fn forecast(&self, context: &[f32], prediction_length: usize) -> Result<(Vec<Vec<f32>>, Vec<f32>)> {
158 let cfg = &self.config;
159 let patch_size = cfg.patch_size;
160 let n_quantiles = cfg.num_quantiles;
161 let median_idx = cfg.quantiles.iter().position(|&q| (q - 0.5).abs() < 1e-6).unwrap_or(4);
162
163 let n_steps = prediction_length.div_ceil(patch_size);
165
166 let mut all_q: Vec<f32> = Vec::with_capacity(n_steps * patch_size * n_quantiles);
168
169 let mut ctx: Vec<f32> = context.to_vec();
171
172 for _ in 0..n_steps {
173 let full_ctx = adjust_context(&ctx, cfg.train_ctx_len);
175
176 let (loc, scale) = standard_scaler(&full_ctx);
178
179 let s_vals: Vec<f32> = full_ctx.iter().map(|&x| {
181 if x.is_nan() { 0.0f32 } else { (x - loc) / scale }
182 }).collect();
183 let s_mask: Vec<f32> = full_ctx.iter().map(|&x| {
184 if x.is_nan() { 0.0f32 } else { 1.0f32 }
185 }).collect();
186
187 let num_patches = cfg.train_ctx_len / patch_size;
189 let mut patched_vals = vec![0.0f32; num_patches * patch_size];
190 let mut patched_mask = vec![0.0f32; num_patches * patch_size];
191 for p in 0..num_patches {
192 let start = p * patch_size;
193 patched_vals[p * patch_size..(p + 1) * patch_size]
194 .copy_from_slice(&s_vals[start..start + patch_size]);
195 patched_mask[p * patch_size..(p + 1) * patch_size]
196 .copy_from_slice(&s_mask[start..start + patch_size]);
197 }
198
199 let in_dim = patch_size * 2;
201 let mut x_in = vec![0.0f32; num_patches * in_dim];
202 for p in 0..num_patches {
203 x_in[p * in_dim..p * in_dim + patch_size]
204 .copy_from_slice(&patched_vals[p * patch_size..(p + 1) * patch_size]);
205 x_in[p * in_dim + patch_size..p * in_dim + in_dim]
206 .copy_from_slice(&patched_mask[p * patch_size..(p + 1) * patch_size]);
207 }
208
209 let mut hidden = residual_block_forward(
211 &x_in, num_patches, in_dim, cfg.input_ff_dim, cfg.embedding_dim,
212 &self.in_emb, &self.device,
213 )?;
214
215 let ng = 4usize;
219 let d = cfg.embedding_dim;
220 let mut sc_xg = vec![0.0f32; num_patches * ng * d]; let mut sc_hout = vec![0.0f32; num_patches * d]; let mut sc_y = vec![0.0f32; num_patches * d]; let mut sc_xn = vec![0.0f32; num_patches * d]; let mut sc_raw = vec![0.0f32; ng * d]; let mut sc_ry_raw = vec![0.0f32; ng * d]; let mut sc_ry_out = vec![0.0f32; ng * d]; let mut sc_hnew = vec![0.0f32; d]; let mut sc_cnew = vec![0.0f32; d];
229 let mut sc_nnew = vec![0.0f32; d];
230 let mut sc_mnew = vec![0.0f32; d];
231
232 for block in &self.blocks {
234 hidden = self.forward_slstm_block(
235 &hidden, num_patches, block,
236 &mut sc_xg, &mut sc_hout, &mut sc_y, &mut sc_xn,
237 &mut sc_raw, &mut sc_ry_raw, &mut sc_ry_out,
238 &mut sc_hnew, &mut sc_cnew, &mut sc_nnew, &mut sc_mnew,
239 )?;
240 }
241
242 rms_norm_inplace(&mut hidden, &self.out_norm, cfg.embedding_dim, 1e-6);
244
245 let out_dim = cfg.output_dim();
247 let preds = residual_block_forward(
248 &hidden, num_patches, cfg.embedding_dim, cfg.input_ff_dim, out_dim,
249 &self.out_emb, &self.device,
250 )?;
251 let last = &preds[(num_patches - 1) * out_dim..num_patches * out_dim];
254
255 for q in 0..n_quantiles {
257 for d in 0..patch_size {
258 let v = last[q * patch_size + d] * scale + loc;
263 all_q.push(v);
264 }
265 }
266
267 ctx.extend(std::iter::repeat(f32::NAN).take(patch_size));
269 }
270
271 let total = n_steps * patch_size;
275 let mut quantiles: Vec<Vec<f32>> = vec![Vec::with_capacity(prediction_length); n_quantiles];
276 for t in 0..prediction_length.min(total) {
277 let step = t / patch_size;
278 let d = t % patch_size;
279 for q in 0..n_quantiles {
280 let v = all_q[step * n_quantiles * patch_size + q * patch_size + d];
281 quantiles[q].push(v);
282 }
283 }
284
285 let median: Vec<f32> = (0..prediction_length).map(|t| quantiles[median_idx][t]).collect();
286
287 Ok((quantiles, median))
288 }
289
290 #[allow(clippy::too_many_arguments)]
291 fn forward_slstm_block(
292 &self, x: &[f32], s: usize, blk: &Block,
293 sc_xg: &mut [f32], sc_hout: &mut [f32], sc_y: &mut [f32], sc_xn: &mut [f32], sc_raw: &mut [f32], sc_ry_raw: &mut [f32], sc_ry_out: &mut [f32], sc_hnew: &mut Vec<f32>, sc_cnew: &mut Vec<f32>,
301 sc_nnew: &mut Vec<f32>, sc_mnew: &mut Vec<f32>,
302 ) -> Result<Vec<f32>> {
303 let cfg = &self.config;
304 let d = cfg.embedding_dim;
305 let nh = cfg.num_heads;
306 let dh = cfg.head_dim();
307 let ng = 4usize;
308
309 sc_xn[..s * d].copy_from_slice(&x[..s * d]);
311 rms_norm_inplace(&mut sc_xn[..s * d], &blk.norm_slstm, d, 1e-6);
312
313 headwise_linear_batch_ng(&sc_xn[..s * d], &blk.fizo_w, s, nh, dh, ng, sc_xg);
316
317 let mut h = vec![0.0f32; d];
319 let mut c = vec![0.0f32; d];
320 let mut n = vec![0.0f32; d];
321 let mut m = vec![f32::NEG_INFINITY; d];
322
323 for t in 0..s {
324 let wx = &sc_xg[t * ng * d..(t + 1) * ng * d];
325 compute_ry(&h, &blk.slstm_kernel_t, nh, dh, ng, sc_ry_raw, sc_ry_out);
326
327 for i in 0..ng * d {
329 sc_raw[i] = wx[i] + sc_ry_out[i] + blk.slstm_bias[i];
330 }
331
332 let is_first = t == 0;
334
335 simd_gate_update(
341 &sc_raw[..d], &sc_raw[d..2*d], &sc_raw[2*d..3*d], &sc_raw[3*d..],
342 &c, &n, &m,
343 sc_cnew, sc_nnew, sc_hnew, sc_mnew,
344 is_first,
345 );
346
347 std::mem::swap(&mut h, sc_hnew);
349 std::mem::swap(&mut c, sc_cnew);
350 std::mem::swap(&mut n, sc_nnew);
351 std::mem::swap(&mut m, sc_mnew);
352 sc_hout[t * d..(t + 1) * d].copy_from_slice(&h);
353 }
354
355 for t in 0..s {
357 let h_t = &sc_hout[t * d..(t + 1) * d];
358 for head in 0..nh {
359 let start = head * dh;
360 let h_slice = &h_t[start..start + dh];
361 let mean = h_slice.iter().sum::<f32>() / dh as f32;
362 let var = h_slice.iter().map(|&v| (v - mean) * (v - mean)).sum::<f32>() / dh as f32;
363 let inv_std = 1.0 / (var + 1e-5f32).sqrt();
364 let w_slice = &blk.group_norm_w[start..start + dh];
365 for d_i in 0..dh {
366 sc_y[t * d + start + d_i] =
367 (h_t[start + d_i] - mean) * inv_std * (1.0 + w_slice[d_i]);
368 }
369 }
370 }
371
372 let mut x_out = x.to_vec();
374 for i in 0..s * d {
375 x_out[i] += sc_y[i];
376 }
377
378 sc_xn[..s * d].copy_from_slice(&x_out[..s * d]);
380 rms_norm_inplace(&mut sc_xn[..s * d], &blk.norm_ffn, d, 1e-6);
381
382 let ffn_out = ffn_forward(&sc_xn[..s * d], s, d, cfg.ffn_up_dim, blk, &self.device)?;
384
385 for i in 0..s * d {
387 x_out[i] += ffn_out[i];
388 }
389
390 Ok(x_out)
391 }
392}
393
394#[inline]
399fn sigmoid(x: f32) -> f32 {
400 1.0 / (1.0 + (-x).exp())
401}
402
403#[inline]
404fn log_sigmoid(x: f32) -> f32 {
405 if x >= 0.0 {
407 -(1.0 + (-x).exp()).ln()
408 } else {
409 x - (1.0 + x.exp()).ln()
410 }
411}
412
413simd_runtime_generate!(
418 fn simd_sq_sum(row: &[f32]) -> f32 {
419 let mut r = &row[..];
420 let mut acc = S::Vf32::zeroes();
421 while r.len() >= S::Vf32::WIDTH {
422 let v = S::Vf32::load_from_slice(r);
423 acc = v.mul_add(v, acc);
424 r = &r[S::Vf32::WIDTH..];
425 }
426 let mut sum = acc.horizontal_add();
427 for &x in r { sum += x * x; }
428 sum
429 }
430);
431
432simd_runtime_generate!(
433 fn simd_scale_weight(row: &mut [f32], w: &[f32], scale: f32) {
434 let sc = S::Vf32::set1(scale);
435 let mut r = &mut row[..];
436 let mut wv = &w[..];
437 while r.len() >= S::Vf32::WIDTH {
438 let v = S::Vf32::load_from_slice(r);
439 let wi = S::Vf32::load_from_slice(wv);
440 (v * sc * wi).copy_to_slice(r);
441 r = &mut r[S::Vf32::WIDTH..];
442 wv = &wv[S::Vf32::WIDTH..];
443 }
444 for i in 0..r.len() { r[i] *= scale * wv[i]; }
445 }
446);
447
448simd_runtime_generate!(
449 fn simd_dot(a: &[f32], b: &[f32]) -> f32 {
450 let mut aa = &a[..];
451 let mut bb = &b[..];
452 let mut acc = S::Vf32::zeroes();
453 while aa.len() >= S::Vf32::WIDTH {
454 let va = S::Vf32::load_from_slice(aa);
455 let vb = S::Vf32::load_from_slice(bb);
456 acc = va.mul_add(vb, acc);
457 aa = &aa[S::Vf32::WIDTH..];
458 bb = &bb[S::Vf32::WIDTH..];
459 }
460 let mut sum = acc.horizontal_add();
461 for (&x, &y) in aa.iter().zip(bb.iter()) { sum += x * y; }
462 sum
463 }
464);
465
466simd_runtime_generate!(
467 fn simd_gate_update(
468 iraw: &[f32], fraw: &[f32], zraw: &[f32], oraw: &[f32],
469 c: &[f32], n: &[f32], m: &[f32],
470 cnew: &mut [f32], nnew: &mut [f32], hnew: &mut [f32], mnew: &mut [f32],
471 is_first: bool,
472 ) {
473 let clamp = S::Vf32::set1(15.0f32);
474 let one = S::Vf32::set1(1.0f32);
475 let eps = S::Vf32::set1(1e-8f32);
476 let zero = S::Vf32::zeroes();
477
478 let mut ir = &iraw[..]; let mut fr = &fraw[..];
479 let mut zr = &zraw[..]; let mut or_ = &oraw[..];
480 let mut cv = &c[..]; let mut nv = &n[..]; let mut mv = &m[..];
481 let mut cnw = &mut cnew[..]; let mut nnw = &mut nnew[..];
482 let mut hnw = &mut hnew[..]; let mut mnw = &mut mnew[..];
483
484 while ir.len() >= S::Vf32::WIDTH {
485 let iv = S::Vf32::load_from_slice(ir);
486 let fv = S::Vf32::load_from_slice(fr).min(clamp);
487 let zv = S::Vf32::load_from_slice(zr);
488 let ov = S::Vf32::load_from_slice(or_);
489 let cv_ = S::Vf32::load_from_slice(cv);
490 let nv_ = S::Vf32::load_from_slice(nv);
491 let mv_ = S::Vf32::load_from_slice(mv);
492
493 let ls_pos = -(one + (-fv).exp_u35()).ln_u35(); let ls_neg = fv - (one + fv.exp_u35()).ln_u35(); let log_sig_f = fv.cmp_lt(zero).blendv(ls_pos, ls_neg);
497
498 let logfplusm = mv_ + log_sig_f;
499 let mnew_v = if is_first { iv } else { iv.max(logfplusm) };
500
501 let ogate = one / (one + (-ov).exp_u35()); let igate = (iv - mnew_v).exp_u35().min(one);
503 let fgate = (logfplusm - mnew_v).exp_u35().min(one);
504 let zgate = zv.tanh_u35();
505
506 let cnew_v = fgate.mul_add(cv_, igate * zgate);
507 let nnew_v = fgate.mul_add(nv_, igate);
508
509 let mask = nnew_v.abs().cmp_gt(eps);
510 let hnew_v = mask.blendv(zero, ogate * cnew_v / nnew_v);
511
512 cnew_v.copy_to_slice(cnw); nnew_v.copy_to_slice(nnw);
513 hnew_v.copy_to_slice(hnw); mnew_v.copy_to_slice(mnw);
514
515 ir = &ir[S::Vf32::WIDTH..]; fr = &fr[S::Vf32::WIDTH..];
516 zr = &zr[S::Vf32::WIDTH..]; or_ = &or_[S::Vf32::WIDTH..];
517 cv = &cv[S::Vf32::WIDTH..]; nv = &nv[S::Vf32::WIDTH..];
518 mv = &mv[S::Vf32::WIDTH..];
519 cnw = &mut cnw[S::Vf32::WIDTH..]; nnw = &mut nnw[S::Vf32::WIDTH..];
520 hnw = &mut hnw[S::Vf32::WIDTH..]; mnw = &mut mnw[S::Vf32::WIDTH..];
521 }
522
523 for i in 0..ir.len() {
524 let iv_s = ir[i]; let fv_s = fr[i].min(15.0); let zv_s = zr[i]; let ov_s = or_[i];
525 let c_s = cv[i]; let n_s = nv[i]; let m_s = mv[i];
526 let ls = if fv_s >= 0.0 { -(1.0 + (-fv_s).exp()).ln() } else { fv_s - (1.0 + fv_s.exp()).ln() };
527 let lfpm = m_s + ls;
528 let mn = if is_first { iv_s } else { iv_s.max(lfpm) };
529 let og = 1.0 / (1.0 + (-ov_s).exp());
530 let ig = (iv_s - mn).exp().min(1.0);
531 let fg = (lfpm - mn).exp().min(1.0);
532 let zg = zv_s.tanh();
533 cnw[i] = fg * c_s + ig * zg;
534 nnw[i] = fg * n_s + ig;
535 hnw[i] = if nnw[i].abs() > 1e-8 { og * cnw[i] / nnw[i] } else { 0.0 };
536 mnw[i] = mn;
537 }
538 }
539);
540
541fn standard_scaler(x: &[f32]) -> (f32, f32) {
543 let eps = 1e-5f32;
544 let valid: Vec<f32> = x.iter().copied().filter(|v| !v.is_nan()).collect();
545 if valid.is_empty() {
546 return (0.0, 1.0);
547 }
548 let loc = valid.iter().sum::<f32>() / valid.len() as f32;
549 let variance = valid.iter().map(|&v| (v - loc) * (v - loc)).sum::<f32>() / valid.len() as f32;
550 let scale = variance.sqrt();
551 let scale = if scale == 0.0 { loc.abs() + eps } else { scale };
552 (loc, scale)
553}
554
555fn adjust_context(x: &[f32], target_len: usize) -> Vec<f32> {
557 if x.len() >= target_len {
558 x[x.len() - target_len..].to_vec()
559 } else {
560 let pad = target_len - x.len();
561 let mut out = vec![f32::NAN; target_len];
562 out[pad..].copy_from_slice(x);
563 out
564 }
565}
566
567fn rms_norm_inplace(x: &mut [f32], w: &[f32], d: usize, eps: f32) {
569 let s = x.len() / d;
570 for t in 0..s {
571 let row = &mut x[t * d..(t + 1) * d];
572 let ss = simd_sq_sum(row);
573 let scale = 1.0 / (ss / d as f32 + eps).sqrt();
574 simd_scale_weight(row, w, scale);
575 }
576}
577
578fn headwise_linear_batch_ng(x: &[f32], w: &[f32], s: usize, nh: usize, dh: usize, ng: usize, out: &mut [f32]) {
585 let d = nh * dh;
586 let ngdh = ng * dh;
587 for t in 0..s {
588 for h in 0..nh {
589 let x_h = &x[t * d + h * dh..t * d + (h + 1) * dh];
590 let w_h_off = h * ngdh * dh;
591 for g in 0..ng {
592 let out_base = t * ng * d + g * d + h * dh;
593 let w_gate_off = w_h_off + g * dh * dh;
594 for o in 0..dh {
595 let w_row = &w[w_gate_off + o * dh..w_gate_off + (o + 1) * dh];
596 out[out_base + o] = simd_dot(x_h, w_row);
597 }
598 }
599 }
600 }
601}
602
603fn compute_ry(
609 h: &[f32], kernel_t: &[f32],
610 nh: usize, dh: usize, ng: usize,
611 ry_raw: &mut [f32],
612 out: &mut [f32],
613) {
614 for head in 0..nh {
616 let h_h = &h[head * dh..(head + 1) * dh];
617 let k_head = &kernel_t[head * ng * dh * dh..];
618 for gate_d in 0..ng * dh {
619 let k_row = &k_head[gate_d * dh..(gate_d + 1) * dh]; ry_raw[head * ng * dh + gate_d] = simd_dot(h_h, k_row);
621 }
622 }
623 for head in 0..nh {
625 for g in 0..ng {
626 for d in 0..dh {
627 out[g * nh * dh + head * dh + d] = ry_raw[head * ng * dh + g * dh + d];
628 }
629 }
630 }
631}
632
633fn residual_block_forward(
635 x: &[f32],
636 s: usize,
637 in_dim: usize,
638 h_dim: usize,
639 out_dim: usize,
640 emb: &EmbedBlock,
641 device: &Device,
642) -> Result<Vec<f32>> {
643 let xt = Tensor::from_slice(x, (s, in_dim), device)?;
645
646 let h = xt.matmul(&emb.hidden_w.t()?)
648 .with_context(|| "in_emb hidden matmul")?;
649 let bh = Tensor::from_slice(&emb.hidden_b, (1, h_dim), device)?;
651 let h = h.broadcast_add(&bh)?;
652 let h = h.relu()?;
653
654 let out = h.matmul(&emb.output_w.t()?)
656 .with_context(|| "in_emb output matmul")?;
657 let bo = Tensor::from_slice(&emb.output_b, (1, out_dim), device)?;
658 let out = out.broadcast_add(&bo)?;
659
660 let res = xt.matmul(&emb.residual_w.t()?)
662 .with_context(|| "in_emb residual matmul")?;
663 let br = Tensor::from_slice(&emb.residual_b, (1, out_dim), device)?;
664 let res = res.broadcast_add(&br)?;
665
666 Ok((out + res)?.flatten_all()?.to_vec1()?)
667}
668
669fn ffn_forward(x: &[f32], s: usize, in_dim: usize, up_dim: usize, blk: &Block, device: &Device) -> Result<Vec<f32>> {
671 let xt = Tensor::from_slice(x, (s, in_dim), device)?;
672
673 let gate = xt.matmul(&blk.ffn_gate_w.t()?)?;
675 let up = xt.matmul(&blk.ffn_up_w.t()?)?;
677 let h = gate.silu()?.mul(&up)?;
679 let out = h.matmul(&blk.ffn_down_w.t()?)?;
681
682 Ok(out.flatten_all()?.to_vec1()?)
683}
684
685impl zsfm_core::Forecaster for TiRexModel {
690 type Config = TiRexConfig;
691
692 fn load(gguf_path: &Path, config: TiRexConfig) -> Result<Self> {
693 TiRexModel::load(gguf_path, config)
694 }
695
696 fn forecast(
699 &self,
700 context: &[Vec<f32>],
701 _mask: &[Vec<bool>],
702 horizon: usize,
703 ) -> Result<zsfm_core::QuantileMatrix> {
704 anyhow::ensure!(context.len() == 1, "TiRexModel only supports univariate forecasting (1 variate)");
705 let (quantiles, _median) = TiRexModel::forecast(self, &context[0], horizon)?; Ok(quantiles.into_iter().map(|row| vec![row]).collect())
707 }
708}