1use candle_core::{DType, Device, Module, Result, Tensor, D};
25use candle_nn::{ops::softmax, Linear, VarBuilder};
26
27pub struct RmsNorm {
29 w: Tensor,
30 eps: f64,
31}
32
33impl RmsNorm {
34 pub fn new(h: usize, vb: VarBuilder) -> Result<Self> {
35 Ok(RmsNorm { w: vb.get(h, "w")?, eps: 1e-6 })
36 }
37 pub fn forward(&self, x: &Tensor) -> Result<Tensor> {
38 let ms = x.sqr()?.mean_keepdim(D::Minus1)?;
39 let scaled = x.broadcast_div(&(ms + self.eps)?.sqrt()?)?;
40 scaled.broadcast_mul(&self.w)
41 }
42}
43
44struct SelfAttention {
46 q: Linear,
47 k: Linear,
48 v: Linear,
49 o: Linear,
50 heads: usize,
51 head_dim: usize,
52}
53
54impl SelfAttention {
55 fn new(h: usize, heads: usize, vb: VarBuilder) -> Result<Self> {
56 Ok(SelfAttention {
57 q: candle_nn::linear(h, h, vb.pp("q"))?,
58 k: candle_nn::linear(h, h, vb.pp("k"))?,
59 v: candle_nn::linear(h, h, vb.pp("v"))?,
60 o: candle_nn::linear(h, h, vb.pp("o"))?,
61 heads,
62 head_dim: h / heads,
63 })
64 }
65
66 fn forward(&self, x: &Tensor, mask: Option<&Tensor>) -> Result<Tensor> {
68 let (b, t, h) = x.dims3()?;
69 let split = |p: &Linear, x: &Tensor| -> Result<Tensor> {
70 p.forward(x)?.reshape((b, t, self.heads, self.head_dim))?.transpose(1, 2)?.contiguous()
71 };
72 let q = split(&self.q, x)?;
73 let k = split(&self.k, x)?;
74 let v = split(&self.v, x)?;
75 let scale = 1.0 / (self.head_dim as f64).sqrt();
76 let mut att = (q.matmul(&k.transpose(2, 3)?.contiguous()?)? * scale)?; if let Some(m) = mask {
78 const MASK_PENALTY: f64 = -1e9;
82 let neg = m
83 .to_dtype(DType::F32)?
84 .affine(-1.0, 1.0)? .affine(MASK_PENALTY, 0.0)? .reshape((b, 1, 1, t))?;
87 att = att.broadcast_add(&neg)?;
88 }
89 let att = softmax(&att, D::Minus1)?;
90 let out = att.matmul(&v)?.transpose(1, 2)?.reshape((b, t, h))?;
91 self.o.forward(&out)
92 }
93}
94
95struct Block {
97 n1: RmsNorm,
98 attn: SelfAttention,
99 n2: RmsNorm,
100 w_in: Linear,
101 w_out: Linear,
102}
103
104impl Block {
105 fn new(h: usize, heads: usize, mult: usize, vb: VarBuilder) -> Result<Self> {
106 Ok(Block {
107 n1: RmsNorm::new(h, vb.pp("n1"))?,
108 attn: SelfAttention::new(h, heads, vb.pp("attn"))?,
109 n2: RmsNorm::new(h, vb.pp("n2"))?,
110 w_in: candle_nn::linear(h, 2 * mult * h, vb.pp("w_in"))?,
112 w_out: candle_nn::linear(mult * h, h, vb.pp("w_out"))?,
113 })
114 }
115
116 fn forward(&self, x: &Tensor, mask: Option<&Tensor>) -> Result<Tensor> {
117 let a = self.attn.forward(&self.n1.forward(x)?, mask)?;
118 let x = (x + a)?;
119 let g = self.n2.forward(&x)?;
120 let uv = self.w_in.forward(&g)?;
121 let half = uv.dim(D::Minus1)? / 2;
122 let u = uv.narrow(D::Minus1, 0, half)?;
123 let v = uv.narrow(D::Minus1, half, half)?;
124 let gated = (u.silu()? * v)?;
125 x + self.w_out.forward(&gated)?
126 }
127}
128
129pub struct HrmCore {
132 inj: Linear,
133 l_blocks: Vec<Block>,
134 h_blocks: Vec<Block>,
135 z_l0: Tensor,
136 z_h0: Tensor,
137 pub t_inner: usize,
138 pub n_cycles: usize,
139}
140
141impl HrmCore {
142 pub fn new(h: usize, layers_l: usize, layers_h: usize, t_inner: usize, n_cycles: usize, heads: usize, vb: VarBuilder) -> Result<Self> {
143 let l_blocks = (0..layers_l).map(|i| Block::new(h, heads, 2, vb.pp(format!("L{i}")))).collect::<Result<Vec<_>>>()?;
144 let h_blocks = (0..layers_h).map(|i| Block::new(h, heads, 2, vb.pp(format!("H{i}")))).collect::<Result<Vec<_>>>()?;
145 Ok(HrmCore {
146 inj: candle_nn::linear(h, h, vb.pp("inj"))?,
147 l_blocks,
148 h_blocks,
149 z_l0: vb.get(h, "z_l0")?,
150 z_h0: vb.get(h, "z_h0")?,
151 t_inner,
152 n_cycles,
153 })
154 }
155
156 fn l_step(&self, z_l: &Tensor, z_h: &Tensor, x: &Tensor, mask: Option<&Tensor>) -> Result<Tensor> {
157 let mut y = ((z_l + z_h)? + x)?;
158 for b in &self.l_blocks {
159 y = b.forward(&y, mask)?;
160 }
161 Ok(y)
162 }
163
164 fn h_step(&self, z_h: &Tensor, z_l: &Tensor, mask: Option<&Tensor>) -> Result<Tensor> {
165 let mut y = (z_h + z_l)?;
166 for b in &self.h_blocks {
167 y = b.forward(&y, mask)?;
168 }
169 Ok(y)
170 }
171
172 pub fn forward(&self, reps: &Tensor, mask: Option<&Tensor>, grad_last_step: bool) -> Result<Tensor> {
176 let (b, t, _h) = reps.dims3()?;
177 let x = self.inj.forward(reps)?;
178 let mut z_l = self.z_l0.reshape((1, 1, ()))?.broadcast_as((b, t, self.z_l0.dim(0)?))?.contiguous()?;
179 let mut z_h = self.z_h0.reshape((1, 1, ()))?.broadcast_as((b, t, self.z_h0.dim(0)?))?.contiguous()?;
180
181 let cycles = if grad_last_step { self.n_cycles } else { self.n_cycles };
182 for _ in 0..cycles {
183 for _ in 0..self.t_inner {
184 z_l = self.l_step(&z_l, &z_h, &x, mask)?;
185 if grad_last_step {
186 z_l = z_l.detach();
187 }
188 }
189 z_h = self.h_step(&z_h, &z_l, mask)?;
190 if grad_last_step {
191 z_h = z_h.detach();
192 }
193 }
194 let z_l = self.l_step(&z_l, &z_h, &x, mask)?;
196 let z_h = self.h_step(&z_h, &z_l, mask)?;
197 z_h + reps
198 }
199}
200
201
202pub struct BertEmbeddingsOnly {
209 word: candle_nn::Embedding,
210 position: candle_nn::Embedding,
211 token_type: candle_nn::Embedding,
212 norm: candle_nn::LayerNorm,
213 pub hidden: usize,
214}
215
216impl BertEmbeddingsOnly {
217 pub fn load(vb: VarBuilder, vocab: usize, max_pos: usize, type_vocab: usize, hidden: usize) -> Result<Self> {
218 Ok(BertEmbeddingsOnly {
219 word: candle_nn::embedding(vocab, hidden, vb.pp("word_embeddings"))?,
220 position: candle_nn::embedding(max_pos, hidden, vb.pp("position_embeddings"))?,
221 token_type: candle_nn::embedding(type_vocab, hidden, vb.pp("token_type_embeddings"))?,
222 norm: candle_nn::layer_norm(hidden, 1e-12, vb.pp("LayerNorm"))?,
223 hidden,
224 })
225 }
226
227 pub fn forward(&self, ids: &Tensor) -> Result<Tensor> {
229 let (b, t) = ids.dims2()?;
230 let w = self.word.forward(ids)?;
231 let pos_ids = Tensor::arange(0u32, t as u32, ids.device())?.reshape((1, t))?.broadcast_as((b, t))?.contiguous()?;
232 let p = self.position.forward(&pos_ids)?;
233 let tt = self.token_type.forward(&ids.zeros_like()?)?;
234 self.norm.forward(&((w + p)? + tt)?)
235 }
236}
237
238pub struct HrmTagger {
242 emb: BertEmbeddingsOnly,
243 core: HrmCore,
244 head_a: Linear,
245 head_b: Linear,
246 hidden: usize,
247}
248
249#[derive(Debug, Clone, Copy)]
251pub struct HrmConfig {
252 pub vocab: usize,
253 pub max_pos: usize,
254 pub type_vocab: usize,
255 pub hidden: usize,
256 pub layers: usize,
257 pub heads: usize,
258 pub t_inner: usize,
259 pub n_cycles: usize,
260}
261
262impl HrmConfig {
263 pub fn bert_tiny(vocab: usize, hidden: usize, max_pos: usize, type_vocab: usize) -> Self {
265 HrmConfig { vocab, max_pos, type_vocab, hidden, layers: 2, heads: 4, t_inner: 2, n_cycles: 2 }
266 }
267}
268
269impl HrmTagger {
270 pub fn new(vb: VarBuilder, cfg: &HrmConfig, n_a: usize, n_b: usize) -> Result<Self> {
271 let emb = BertEmbeddingsOnly::load(vb.pp("bert").pp("embeddings"), cfg.vocab, cfg.max_pos, cfg.type_vocab, cfg.hidden)?;
272 let core = HrmCore::new(cfg.hidden, cfg.layers, cfg.layers, cfg.t_inner, cfg.n_cycles, cfg.heads, vb.pp("core"))?;
273 Ok(HrmTagger {
274 emb,
275 core,
276 head_a: candle_nn::linear(cfg.hidden, n_a, vb.pp("head_a"))?,
277 head_b: candle_nn::linear(cfg.hidden, n_b, vb.pp("head_b"))?,
278 hidden: cfg.hidden,
279 })
280 }
281
282 pub fn hidden_size(&self) -> usize {
283 self.hidden
284 }
285
286 pub fn hidden(&self, ids: &Tensor, attn: &Tensor, grad_last_step: bool) -> Result<Tensor> {
288 let reps = self.emb.forward(ids)?;
289 self.core.forward(&reps, Some(attn), grad_last_step)
290 }
291
292 pub fn forward(&self, ids: &Tensor, attn: &Tensor, grad_last_step: bool) -> Result<(Tensor, Tensor)> {
294 let y = self.hidden(ids, attn, grad_last_step)?;
295 Ok((self.head_a.forward(&y)?, self.head_b.forward(&y)?))
296 }
297}
298
299#[cfg(test)]
300mod tests {
301 use super::*;
302 use candle_nn::VarMap;
303
304 fn core(h: usize, cycles: usize, t_inner: usize) -> (HrmCore, Device) {
305 let device = Device::Cpu;
306 let varmap = VarMap::new();
307 let vb = VarBuilder::from_varmap(&varmap, DType::F32, &device);
308 let c = HrmCore::new(h, 1, 1, t_inner, cycles, 4, vb).unwrap();
309 (c, device)
310 }
311
312 #[test]
314 fn independent_heads_and_a_small_core() {
315 let device = Device::Cpu;
316 let varmap = VarMap::new();
317 let vb = VarBuilder::from_varmap(&varmap, DType::F32, &device);
318 let cfg = HrmConfig::bert_tiny(30522, 128, 512, 2);
319 let t = HrmTagger::new(vb, &cfg, 19, 4).unwrap();
320 let total: usize = varmap.all_vars().iter().map(|v| v.as_tensor().elem_count()).sum();
321 let emb_params = 30522 * 128 + 512 * 128 + 2 * 128 + 2 * 128;
322 eprintln!("total params {total} (embeddings {emb_params}, core+heads {})", total - emb_params);
323 assert!(total < 5_000_000, "must stay a ~4M model, got {total}");
324 assert!(emb_params * 10 / 8 > total - emb_params, "embeddings should dominate the budget");
325
326 let ids = Tensor::from_vec(vec![101u32, 2054, 2003, 102], (1, 4), &device).unwrap();
327 let attn = Tensor::from_vec(vec![1u32, 1, 1, 1], (1, 4), &device).unwrap();
328 let (a, b) = t.forward(&ids, &attn, false).unwrap();
329 assert_eq!(a.dims(), &[1, 4, 19]);
330 assert_eq!(b.dims(), &[1, 4, 4]);
331 let hidden = t.hidden(&ids, &attn, false).unwrap();
333 assert_eq!(hidden.dims(), &[1, 4, 128]);
334 let av: Vec<f32> = a.flatten_all().unwrap().to_vec1().unwrap();
335 assert!(av.iter().all(|v| v.is_finite()));
336 }
337
338 #[test]
339 fn rmsnorm_matches_the_reference_formula() {
340 let device = Device::Cpu;
341 let varmap = VarMap::new();
342 let vb = VarBuilder::from_varmap(&varmap, DType::F32, &device);
343 let n = RmsNorm::new(4, vb.pp("n")).unwrap();
344 let x = Tensor::from_vec(vec![1f32, 2., 3., 4.], (1, 1, 4), &device).unwrap();
347 let y1 = n.forward(&x).unwrap().flatten_all().unwrap().to_vec1::<f32>().unwrap();
348 let y2 = n.forward(&(x.affine(10.0, 0.0).unwrap())).unwrap().flatten_all().unwrap().to_vec1::<f32>().unwrap();
349 for (a, b) in y1.iter().zip(y2.iter()) {
350 assert!((a - b).abs() < 1e-4, "RMSNorm must be scale-invariant: {y1:?} vs {y2:?}");
351 }
352 }
353
354 #[test]
355 fn core_preserves_shape_and_is_residual() {
356 let (c, device) = core(8, 2, 2);
357 let reps = Tensor::rand(0f32, 1f32, (2, 5, 8), &device).unwrap();
358 let mask = Tensor::from_vec(vec![1u32, 1, 1, 0, 0, 1, 1, 1, 1, 0], (2, 5), &device).unwrap();
359 let out = c.forward(&reps, Some(&mask), false).unwrap();
360 assert_eq!(out.dims(), &[2, 5, 8]);
361 let d: Vec<f32> = (out - &reps).unwrap().flatten_all().unwrap().to_vec1().unwrap();
363 assert!(d.iter().all(|v| v.is_finite()), "refinement must be finite (padding mask must not produce NaN)");
364 assert!(d.iter().any(|v| v.abs() > 1e-6), "core must actually change the representation");
365 }
366
367 #[test]
368 fn more_cycles_change_the_refinement() {
369 let device = Device::Cpu;
371 let varmap = VarMap::new();
372 let vb = VarBuilder::from_varmap(&varmap, DType::F32, &device);
373 let shallow = HrmCore::new(8, 1, 1, 1, 1, 4, vb.pp("c")).unwrap();
374 let deep = HrmCore::new(8, 1, 1, 2, 3, 4, vb.pp("c")).unwrap(); let reps = Tensor::rand(0f32, 1f32, (1, 4, 8), &device).unwrap();
376 let a: Vec<f32> = shallow.forward(&reps, None, false).unwrap().flatten_all().unwrap().to_vec1().unwrap();
377 let b: Vec<f32> = deep.forward(&reps, None, false).unwrap().flatten_all().unwrap().to_vec1().unwrap();
378 assert!(a.iter().zip(b.iter()).any(|(x, y)| (x - y).abs() > 1e-5), "cycle count must affect the output");
379 }
380
381 #[test]
382 fn padding_mask_blocks_attention_to_pad_positions() {
383 let (c, device) = core(8, 1, 1);
384 let mut a = vec![0f32; 3 * 8];
386 for (i, v) in a.iter_mut().enumerate() {
387 *v = (i % 7) as f32 * 0.1;
388 }
389 let reps_a = Tensor::from_vec(a.clone(), (1, 3, 8), &device).unwrap();
390 let mut b = a.clone();
392 for v in b[16..24].iter_mut() {
393 *v = 99.0;
394 }
395 let reps_b = Tensor::from_vec(b, (1, 3, 8), &device).unwrap();
396 let mask = Tensor::from_vec(vec![1u32, 1, 0], (1, 3), &device).unwrap();
397 let oa = c.forward(&reps_a, Some(&mask), false).unwrap().narrow(1, 0, 2).unwrap();
398 let ob = c.forward(&reps_b, Some(&mask), false).unwrap().narrow(1, 0, 2).unwrap();
399 let va: Vec<f32> = oa.flatten_all().unwrap().to_vec1().unwrap();
400 let vb2: Vec<f32> = ob.flatten_all().unwrap().to_vec1().unwrap();
401 for (x, y) in va.iter().zip(vb2.iter()) {
402 assert!((x - y).abs() < 1e-3, "masked positions must not influence real ones: {va:?} vs {vb2:?}");
403 }
404 }
405}