Skip to main content

steeldb/
needle_model.rs

1//! Pure-Rust (candle) port of the Cactus **needle-1** Simple Attention Network — the encoder-decoder we
2//! CAN faithfully reimplement (its architecture is documented; needle-2's novel MHC/engram/MTP stack is
3//! not). This is the model the finetuning harness trains: load the published bf16 safetensors, run
4//! teacher-forced, and serve fully in-Rust (no cactus C++ wheel, no Python).
5//!
6//! Architecture (config.json + `neural/needle_torch.py` reference): d_model 512, 8 heads / 4 KV (GQA),
7//! head_dim 64, vocab 8192, RoPE θ=1e4, ZCRMSNorm `(1+scale)·x/rms(x)`. Encoder ×12: pre-norm →
8//! GQA self-attn (per-head q/k ZCRMSNorm + RoPE) → sigmoid-gated residual (NO FFN). Decoder ×8: gated
9//! self-attn + gated cross-attn (NO FFN). Tied-ish `lm_head` (shipped separately).
10
11use candle_core::{DType, Device, IndexOp, Module, Result, Tensor, D};
12use candle_nn::{embedding, ops::softmax, Embedding, Linear, VarBuilder};
13
14pub const D_MODEL: usize = 512;
15pub const N_HEADS: usize = 8;
16pub const N_KV: usize = 4;
17pub const HEAD_DIM: usize = 64;
18pub const VOCAB: usize = 8192;
19pub const N_ENC: usize = 12;
20pub const N_DEC: usize = 8;
21const ROPE_THETA: f64 = 10000.0;
22const EPS: f64 = 1e-6;
23
24/// Zero-centred RMSNorm: `(1 + scale) · x / rms(x)`, computed in f32.
25struct ZCRMSNorm {
26    scale: Tensor, // [dim]
27}
28impl ZCRMSNorm {
29    fn load(dim: usize, vb: VarBuilder) -> Result<Self> {
30        Ok(Self { scale: vb.get(dim, "weight")? })
31    }
32    fn forward(&self, x: &Tensor) -> Result<Tensor> {
33        let dt = x.dtype();
34        let x = x.to_dtype(DType::F32)?;
35        let rms = (x.sqr()?.mean_keepdim(D::Minus1)? + EPS)?.sqrt()?;
36        let scale = (self.scale.to_dtype(DType::F32)? + 1.0)?;
37        x.broadcast_div(&rms)?.broadcast_mul(&scale)?.to_dtype(dt)
38    }
39}
40
41fn no_bias_linear(inp: usize, out: usize, vb: VarBuilder) -> Result<Linear> {
42    Ok(Linear::new(vb.get((out, inp), "weight")?, None))
43}
44
45/// Precompute RoPE cos/sin: `[T, head_dim/2]`.
46fn rope_tables(seq_len: usize, dev: &Device) -> Result<(Tensor, Tensor)> {
47    let half = HEAD_DIM / 2;
48    let inv: Vec<f32> = (0..half).map(|i| 1f32 / (ROPE_THETA as f32).powf((2 * i) as f32 / HEAD_DIM as f32)).collect();
49    let inv = Tensor::from_vec(inv, (1, half), dev)?;
50    let t: Vec<f32> = (0..seq_len).map(|i| i as f32).collect();
51    let t = Tensor::from_vec(t, (seq_len, 1), dev)?;
52    let ang = t.broadcast_mul(&inv)?; // [T, half]
53    Ok((ang.cos()?, ang.sin()?))
54}
55
56/// Apply split-half RoPE to `x: [B,H,T,head_dim]` with cos/sin `[T, head_dim/2]`.
57fn apply_rope(x: &Tensor, cos: &Tensor, sin: &Tensor) -> Result<Tensor> {
58    let (_b, _h, t, d) = x.dims4()?;
59    let half = d / 2;
60    let cos = cos.i((..t, ..))?.reshape((1, 1, t, half))?;
61    let sin = sin.i((..t, ..))?.reshape((1, 1, t, half))?;
62    let x1 = x.narrow(D::Minus1, 0, half)?;
63    let x2 = x.narrow(D::Minus1, half, half)?;
64    let r1 = (x1.broadcast_mul(&cos)? - x2.broadcast_mul(&sin)?)?;
65    let r2 = (x2.broadcast_mul(&cos)? + x1.broadcast_mul(&sin)?)?;
66    Tensor::cat(&[r1, r2], D::Minus1)
67}
68
69struct Attention {
70    q_proj: Linear,
71    k_proj: Linear,
72    v_proj: Linear,
73    out_proj: Linear,
74    q_norm: ZCRMSNorm,
75    k_norm: ZCRMSNorm,
76}
77impl Attention {
78    fn load(vb: VarBuilder) -> Result<Self> {
79        Ok(Self {
80            q_proj: no_bias_linear(D_MODEL, N_HEADS * HEAD_DIM, vb.pp("q_proj"))?,
81            k_proj: no_bias_linear(D_MODEL, N_KV * HEAD_DIM, vb.pp("k_proj"))?,
82            v_proj: no_bias_linear(D_MODEL, N_KV * HEAD_DIM, vb.pp("v_proj"))?,
83            out_proj: no_bias_linear(D_MODEL, D_MODEL, vb.pp("out_proj"))?,
84            q_norm: ZCRMSNorm::load(HEAD_DIM, vb.pp("q_norm"))?,
85            k_norm: ZCRMSNorm::load(HEAD_DIM, vb.pp("k_norm"))?,
86        })
87    }
88
89    /// `causal` = apply a causal mask over the (square) self-attention; `rope` = rotate q,k (self-attn).
90    fn forward(&self, q_in: &Tensor, kv_in: &Tensor, causal: bool, rope: Option<&(Tensor, Tensor)>) -> Result<Tensor> {
91        let (b, tq, _) = q_in.dims3()?;
92        let tk = kv_in.dim(1)?;
93        let q = self.q_proj.forward(q_in)?.reshape((b, tq, N_HEADS, HEAD_DIM))?.transpose(1, 2)?;
94        let k = self.k_proj.forward(kv_in)?.reshape((b, tk, N_KV, HEAD_DIM))?.transpose(1, 2)?;
95        let v = self.v_proj.forward(kv_in)?.reshape((b, tk, N_KV, HEAD_DIM))?.transpose(1, 2)?;
96        let q = self.q_norm.forward(&q)?;
97        let k = self.k_norm.forward(&k)?;
98        let (q, k) = match rope {
99            Some((cos, sin)) => (apply_rope(&q, cos, sin)?, apply_rope(&k, cos, sin)?),
100            None => (q, k),
101        };
102        // GQA: repeat KV heads to match query heads.
103        let rep = N_HEADS / N_KV;
104        let k = repeat_kv(&k, rep)?;
105        let v = repeat_kv(&v, rep)?;
106        let scale = 1.0 / (HEAD_DIM as f64).sqrt();
107        let mut att = (q.contiguous()?.matmul(&k.transpose(2, 3)?.contiguous()?)? * scale)?; // [b,h,tq,tk]
108        if causal {
109            att = att.broadcast_add(&causal_mask(tq, tk, q_in.device())?)?;
110        }
111        let att = softmax(&att, D::Minus1)?;
112        let out = att.matmul(&v.contiguous()?)?; // [b,h,tq,hd]
113        let out = out.transpose(1, 2)?.reshape((b, tq, N_HEADS * HEAD_DIM))?;
114        self.out_proj.forward(&out)
115    }
116}
117
118fn repeat_kv(x: &Tensor, rep: usize) -> Result<Tensor> {
119    if rep == 1 {
120        return Ok(x.clone());
121    }
122    let (b, kv, t, d) = x.dims4()?;
123    x.unsqueeze(2)?.expand((b, kv, rep, t, d))?.reshape((b, kv * rep, t, d))
124}
125
126/// Additive causal mask `[1,1,tq,tk]`: 0 on/below the diagonal, -inf above.
127fn causal_mask(tq: usize, tk: usize, dev: &Device) -> Result<Tensor> {
128    let mut v = vec![0f32; tq * tk];
129    for i in 0..tq {
130        for j in 0..tk {
131            if j > i {
132                v[i * tk + j] = f32::NEG_INFINITY;
133            }
134        }
135    }
136    Tensor::from_vec(v, (1, 1, tq, tk), dev)
137}
138
139fn gate(vb: &VarBuilder, name: &str) -> Result<Tensor> {
140    // sigmoid of a learned scalar (shape [1]); returned as a scalar tensor for broadcasting.
141    let g = vb.get(1, name)?;
142    candle_nn::ops::sigmoid(&g.to_dtype(DType::F32)?)
143}
144
145struct EncoderLayer {
146    ln: ZCRMSNorm,
147    attn: Attention,
148    gate: Tensor,
149}
150impl EncoderLayer {
151    fn load(vb: VarBuilder) -> Result<Self> {
152        Ok(Self {
153            ln: ZCRMSNorm::load(D_MODEL, vb.pp("input_layernorm"))?,
154            attn: Attention::load(vb.pp("self_attn"))?,
155            gate: gate(&vb, "attn_gate")?,
156        })
157    }
158    fn forward(&self, x: &Tensor, rope: &(Tensor, Tensor)) -> Result<Tensor> {
159        let h = self.ln.forward(x)?;
160        let a = self.attn.forward(&h, &h, false, Some(rope))?;
161        x + a.broadcast_mul(&self.gate)?
162    }
163}
164
165struct DecoderLayer {
166    ln: ZCRMSNorm,
167    self_attn: Attention,
168    self_gate: Tensor,
169    cross_ln: ZCRMSNorm,
170    cross_attn: Attention,
171    cross_gate: Tensor,
172}
173impl DecoderLayer {
174    fn load(vb: VarBuilder) -> Result<Self> {
175        Ok(Self {
176            ln: ZCRMSNorm::load(D_MODEL, vb.pp("input_layernorm"))?,
177            self_attn: Attention::load(vb.pp("self_attn"))?,
178            self_gate: gate(&vb, "self_attn_gate")?,
179            cross_ln: ZCRMSNorm::load(D_MODEL, vb.pp("encoder_attn_layer_norm"))?,
180            cross_attn: Attention::load(vb.pp("encoder_attn"))?,
181            cross_gate: gate(&vb, "cross_attn_gate")?,
182        })
183    }
184    fn forward(&self, x: &Tensor, enc: &Tensor, rope: &(Tensor, Tensor)) -> Result<Tensor> {
185        let h = self.ln.forward(x)?;
186        let sa = self.self_attn.forward(&h, &h, true, Some(rope))?;
187        let x = (x + sa.broadcast_mul(&self.self_gate)?)?;
188        let hd = self.cross_ln.forward(&x)?;
189        let ca = self.cross_attn.forward(&hd, enc, false, None)?;
190        x + ca.broadcast_mul(&self.cross_gate)?
191    }
192}
193
194pub struct NeedleModel {
195    embed: Embedding,
196    enc: Vec<EncoderLayer>,
197    enc_final: ZCRMSNorm,
198    dec: Vec<DecoderLayer>,
199    dec_norm: ZCRMSNorm,
200    lm_head: Linear,
201    device: Device,
202}
203
204impl NeedleModel {
205    /// Load from the needle-1 bf16 safetensors (HF naming). Runs in f32 on CPU.
206    pub fn load(safetensors: &std::path::Path, device: &Device) -> Result<Self> {
207        let vb = unsafe { VarBuilder::from_mmaped_safetensors(&[safetensors], DType::F32, device)? };
208        let m = vb.pp("model");
209        let embed = embedding(VOCAB, D_MODEL, m.pp("embed_tokens"))?;
210        let enc = (0..N_ENC).map(|i| EncoderLayer::load(m.pp("encoder").pp("layers").pp(i))).collect::<Result<_>>()?;
211        let enc_final = ZCRMSNorm::load(D_MODEL, m.pp("encoder").pp("final_norm"))?;
212        let dec = (0..N_DEC).map(|i| DecoderLayer::load(m.pp("decoder").pp("layers").pp(i))).collect::<Result<_>>()?;
213        let dec_norm = ZCRMSNorm::load(D_MODEL, m.pp("decoder").pp("norm"))?;
214        let lm_head = no_bias_linear(D_MODEL, VOCAB, vb.pp("lm_head"))?;
215        Ok(Self { embed, enc, enc_final, dec, dec_norm, lm_head, device: device.clone() })
216    }
217
218    /// Encode `enc_ids: [B,Te]` → encoder states `[B,Te,D]` (no padding mask; assumes packed sequences).
219    pub fn encode(&self, enc_ids: &Tensor) -> Result<Tensor> {
220        let t = enc_ids.dim(1)?;
221        let rope = rope_tables(t, &self.device)?;
222        let mut x = self.embed.forward(enc_ids)?;
223        for l in &self.enc {
224            x = l.forward(&x, &rope)?;
225        }
226        self.enc_final.forward(&x)
227    }
228
229    /// Decode `dec_ids: [B,Td]` against `enc_out` → logits `[B,Td,VOCAB]` (causal self-attn).
230    pub fn decode(&self, dec_ids: &Tensor, enc_out: &Tensor) -> Result<Tensor> {
231        let t = dec_ids.dim(1)?;
232        let rope = rope_tables(t, &self.device)?;
233        let mut x = self.embed.forward(dec_ids)?;
234        for l in &self.dec {
235            x = l.forward(&x, enc_out, &rope)?;
236        }
237        let x = self.dec_norm.forward(&x)?;
238        self.lm_head.forward(&x)
239    }
240
241    /// Full teacher-forced forward: `(enc_ids, dec_ids) → logits [B,Td,VOCAB]`.
242    pub fn forward(&self, enc_ids: &Tensor, dec_ids: &Tensor) -> Result<Tensor> {
243        let enc = self.encode(enc_ids)?;
244        self.decode(dec_ids, &enc)
245    }
246
247    pub fn device(&self) -> &Device {
248        &self.device
249    }
250}