1use std::collections::HashMap;
7use std::path::Path;
8
9use candle_core::{DType, Device, IndexOp, Tensor, D};
10use candle_nn::{LayerNorm, Linear, Module, VarBuilder};
11use candle_transformers::models::modernbert::{Config as MbConfig, ModernBert};
12
13use crate::common::CollatedBatch;
14use crate::error::{LayaError, Result};
15
16const LN_EPS: f64 = 1e-5;
18const MASK_FILL: f64 = -1e4;
20
21pub type ForwardOutput = (Vec<Vec<f32>>, Vec<Vec<f32>>);
23
24fn e(err: candle_core::Error) -> LayaError {
25 LayaError::Model(err.to_string())
26}
27
28struct HeadLayer {
31 in_proj: Linear, out_proj: Linear,
33 linear1: Linear, linear2: Linear, norm1: LayerNorm,
36 norm2: LayerNorm,
37 n_heads: usize,
38 head_dim: usize,
39}
40
41impl HeadLayer {
42 fn self_attention(&self, x: &Tensor, add_mask: &Tensor) -> Result<Tensor> {
43 let (b, s, d) = x.dims3().map_err(e)?;
44 let qkv = self.in_proj.forward(x).map_err(e)?; let q = qkv.narrow(2, 0, d).map_err(e)?;
46 let k = qkv.narrow(2, d, d).map_err(e)?;
47 let v = qkv.narrow(2, 2 * d, d).map_err(e)?;
48 let split = |t: &Tensor| -> Result<Tensor> {
49 t.reshape((b, s, self.n_heads, self.head_dim))
50 .map_err(e)?
51 .transpose(1, 2)
52 .map_err(e)?
53 .contiguous()
54 .map_err(e) };
56 let q = split(&q)?;
57 let k = split(&k)?;
58 let v = split(&v)?;
59 let scale = (self.head_dim as f64).powf(-0.5);
60 let q = (q * scale).map_err(e)?;
61 let att = q
62 .matmul(&k.transpose(2, 3).map_err(e)?.contiguous().map_err(e)?)
63 .map_err(e)?; let att = att.broadcast_add(add_mask).map_err(e)?; let att = candle_nn::ops::softmax(&att, D::Minus1).map_err(e)?;
66 let ctx = att.matmul(&v).map_err(e)?; let ctx = ctx
68 .transpose(1, 2)
69 .map_err(e)?
70 .contiguous()
71 .map_err(e)?
72 .reshape((b, s, d))
73 .map_err(e)?;
74 self.out_proj.forward(&ctx).map_err(e)
75 }
76
77 fn forward(&self, x: &Tensor, add_mask: &Tensor) -> Result<Tensor> {
78 let normed = self.norm1.forward(x).map_err(e)?;
79 let attn = self.self_attention(&normed, add_mask)?;
80 let x = (x + attn).map_err(e)?;
81 let normed2 = self.norm2.forward(&x).map_err(e)?;
82 let ff = self
83 .linear2
84 .forward(
85 &self
86 .linear1
87 .forward(&normed2)
88 .map_err(e)?
89 .relu()
90 .map_err(e)?,
91 )
92 .map_err(e)?;
93 (x + ff).map_err(e)
94 }
95}
96
97pub struct DecisionModel {
99 encoder: ModernBert,
100 type_emb: Tensor, head_layers: Vec<HeadLayer>,
102 scorer_norm: LayerNorm,
103 scorer_lin1: Linear,
104 scorer_lin2: Linear, act_lin1: Linear, act_lin2: Linear, device: Device,
108 hidden: usize,
109}
110
111impl DecisionModel {
112 pub fn load(
119 weights_path: &Path,
120 encoder_config: &serde_json::Value,
121 head_layers: usize,
122 n_act: usize,
123 device: Device,
124 ) -> Result<Self> {
125 let mbcfg = build_mb_config(encoder_config);
126 let hidden = mbcfg.hidden_size;
127
128 let raw = candle_core::safetensors::load(weights_path, &device).map_err(e)?;
131 let mut map: HashMap<String, Tensor> = HashMap::with_capacity(raw.len());
132 for (k, t) in raw {
133 let t = t.to_dtype(DType::F32).map_err(e)?;
134 let nk = match k.strip_prefix("encoder.") {
135 Some(rest) => format!("model.{}", rest),
136 None => k,
137 };
138 map.insert(nk, t);
139 }
140 let vb = VarBuilder::from_tensors(map, DType::F32, &device);
141
142 let encoder = ModernBert::load(vb.clone(), &mbcfg).map_err(e)?;
143
144 let get = |name: &str| -> Result<Tensor> { vb.get_unchecked(name).map_err(e) };
145 let linear =
146 |w: &str, b: &str| -> Result<Linear> { Ok(Linear::new(get(w)?, Some(get(b)?))) };
147 let layernorm = |w: &str, b: &str| -> Result<LayerNorm> {
148 Ok(LayerNorm::new(get(w)?, get(b)?, LN_EPS))
149 };
150
151 let type_emb = get("type_emb.weight")?; let n_heads = std::cmp::max(1, hidden / 64);
154 let head_dim = hidden / n_heads;
155 let mut layers = Vec::with_capacity(head_layers);
156 for i in 0..head_layers {
157 let p = format!("head.layers.{i}");
158 layers.push(HeadLayer {
159 in_proj: Linear::new(
160 get(&format!("{p}.self_attn.in_proj_weight"))?,
161 Some(get(&format!("{p}.self_attn.in_proj_bias"))?),
162 ),
163 out_proj: linear(
164 &format!("{p}.self_attn.out_proj.weight"),
165 &format!("{p}.self_attn.out_proj.bias"),
166 )?,
167 linear1: linear(&format!("{p}.linear1.weight"), &format!("{p}.linear1.bias"))?,
168 linear2: linear(&format!("{p}.linear2.weight"), &format!("{p}.linear2.bias"))?,
169 norm1: layernorm(&format!("{p}.norm1.weight"), &format!("{p}.norm1.bias"))?,
170 norm2: layernorm(&format!("{p}.norm2.weight"), &format!("{p}.norm2.bias"))?,
171 n_heads,
172 head_dim,
173 });
174 }
175
176 let scorer_norm = layernorm("scorer.0.weight", "scorer.0.bias")?;
177 let scorer_lin1 = linear("scorer.1.weight", "scorer.1.bias")?;
178 let scorer_lin2 = linear("scorer.3.weight", "scorer.3.bias")?;
179 let act_lin1 = linear("act_head.0.weight", "act_head.0.bias")?;
180 let act_lin2 = linear("act_head.2.weight", "act_head.2.bias")?;
181
182 let _ = n_act;
184
185 Ok(DecisionModel {
186 encoder,
187 type_emb,
188 head_layers: layers,
189 scorer_norm,
190 scorer_lin1,
191 scorer_lin2,
192 act_lin1,
193 act_lin2,
194 device,
195 hidden,
196 })
197 }
198
199 pub fn hidden_size(&self) -> usize {
201 self.hidden
202 }
203
204 pub fn embed(&self, input_ids: &[Vec<u32>], attention: &[Vec<u32>]) -> Result<Vec<Vec<f32>>> {
208 let dev = &self.device;
209 let n = input_ids.len();
210 let l = input_ids.first().map(|r| r.len()).unwrap_or(0);
211 if n == 0 || l == 0 {
212 return Ok(Vec::new());
213 }
214 let ids = Tensor::from_vec(
215 input_ids.iter().flatten().copied().collect::<Vec<u32>>(),
216 (n, l),
217 dev,
218 )
219 .map_err(e)?;
220 let attn = Tensor::from_vec(
221 attention
222 .iter()
223 .flatten()
224 .map(|&x| x as f32)
225 .collect::<Vec<f32>>(),
226 (n, l),
227 dev,
228 )
229 .map_err(e)?;
230 let hidden = self.encoder.forward(&ids, &attn).map_err(e)?; let mask = attn.unsqueeze(2).map_err(e)?; let summed = hidden.broadcast_mul(&mask).map_err(e)?.sum(1).map_err(e)?; let counts = mask
234 .sum(1)
235 .map_err(e)?
236 .clamp(1.0f32, f32::INFINITY)
237 .map_err(e)?; let pooled = summed.broadcast_div(&counts).map_err(e)?; pooled.to_vec2().map_err(e)
240 }
241
242 pub fn forward(&self, b: &CollatedBatch) -> Result<ForwardOutput> {
245 let dev = &self.device;
246 let n = b.input_ids.len();
247 let l = b.input_ids.first().map(|r| r.len()).unwrap_or(0);
248 let kmax = b.marker_pos.first().map(|r| r.len()).unwrap_or(0);
249 let d = self.hidden;
250
251 let flat_ids: Vec<u32> = b.input_ids.iter().flatten().copied().collect();
252 let input_ids = Tensor::from_vec(flat_ids, (n, l), dev).map_err(e)?;
253 let flat_att: Vec<f32> = b
254 .attention_mask
255 .iter()
256 .flatten()
257 .map(|&x| x as f32)
258 .collect();
259 let attn = Tensor::from_vec(flat_att, (n, l), dev).map_err(e)?;
260
261 let hidden = self.encoder.forward(&input_ids, &attn).map_err(e)?;
263
264 let qtype: Vec<u32> = b.qtype.iter().map(|&x| x as u32).collect();
266 let qtype = Tensor::from_vec(qtype, (n,), dev).map_err(e)?;
267 let type_vec = self.type_emb.index_select(&qtype, 0).map_err(e)?; let mut h = hidden
269 .broadcast_add(&type_vec.unsqueeze(1).map_err(e)?)
270 .map_err(e)?;
271
272 let add_mask = attn.affine(-1.0, 1.0).map_err(e)?; let add_mask = (add_mask * f32::MIN as f64)
275 .map_err(e)?
276 .reshape((n, 1, 1, l))
277 .map_err(e)?;
278
279 for layer in &self.head_layers {
280 h = layer.forward(&h, &add_mask)?;
281 }
282
283 let flat_pos: Vec<u32> = b.marker_pos.iter().flatten().map(|&x| x as u32).collect();
285 let idx = Tensor::from_vec(flat_pos, (n, kmax), dev).map_err(e)?;
286 let idx3 = idx
287 .unsqueeze(2)
288 .map_err(e)?
289 .broadcast_as((n, kmax, d))
290 .map_err(e)?
291 .contiguous()
292 .map_err(e)?;
293 let m = h.gather(&idx3, 1).map_err(e)?; let s = self.scorer_norm.forward(&m).map_err(e)?;
297 let s = self.scorer_lin1.forward(&s).map_err(e)?;
298 let s = s.gelu_erf().map_err(e)?;
299 let s = self.scorer_lin2.forward(&s).map_err(e)?; let logits = s.squeeze(2).map_err(e)?; let flat_maskf: Vec<f32> = b
304 .marker_mask
305 .iter()
306 .flatten()
307 .map(|&x| if x { 1.0 } else { 0.0 })
308 .collect();
309 let maskf = Tensor::from_vec(flat_maskf, (n, kmax), dev).map_err(e)?;
310 let logits = (logits.mul(&maskf).map_err(e)?
311 + maskf.affine(-MASK_FILL, MASK_FILL).map_err(e)?)
312 .map_err(e)?;
313 let logits_vec: Vec<Vec<f32>> = logits.to_vec2().map_err(e)?;
314
315 let pooled = h.i((.., 0, ..)).map_err(e)?.contiguous().map_err(e)?; let feats = self.action_features(&logits_vec, &b.marker_mask);
318 let feats_t = Tensor::from_vec(
319 feats.iter().flatten().copied().collect::<Vec<f32>>(),
320 (n, 4),
321 dev,
322 )
323 .map_err(e)?;
324 let cat = Tensor::cat(&[pooled, feats_t], D::Minus1).map_err(e)?; let a = self.act_lin1.forward(&cat).map_err(e)?;
326 let a = a.gelu_erf().map_err(e)?;
327 let act_logits = self.act_lin2.forward(&a).map_err(e)?; let act_vec: Vec<Vec<f32>> = act_logits.to_vec2().map_err(e)?;
329
330 Ok((logits_vec, act_vec))
331 }
332
333 fn action_features(&self, logits: &[Vec<f32>], marker_mask: &[Vec<bool>]) -> Vec<[f32; 4]> {
336 logits
337 .iter()
338 .zip(marker_mask.iter())
339 .map(|(row, mask)| {
340 let p = crate::common::softmax(&row.iter().map(|&v| v as f64).collect::<Vec<_>>());
342 let k = (mask.iter().filter(|&&m| m).count() as f64).max(2.0);
343 let ent: f64 = -p
344 .iter()
345 .map(|&v| v * v.clamp(1e-9, f64::INFINITY).ln())
346 .sum::<f64>()
347 / k.ln();
348 let mut sorted = p.clone();
349 sorted.sort_by(|a, b| b.partial_cmp(a).unwrap_or(std::cmp::Ordering::Equal));
350 let top1 = sorted.first().copied().unwrap_or(0.0);
351 let top2 = sorted.get(1).copied().unwrap_or(0.0);
352 [
353 top1 as f32,
354 (top1 - top2) as f32,
355 ent as f32,
356 (k / 255.0) as f32,
357 ]
358 })
359 .collect()
360 }
361}
362
363fn build_mb_config(cfg: &serde_json::Value) -> MbConfig {
366 let u = |key: &str, default: usize| -> usize {
367 cfg.get(key)
368 .and_then(|v| v.as_u64())
369 .map(|v| v as usize)
370 .unwrap_or(default)
371 };
372 let f = |key: &str, default: f64| -> f64 {
373 cfg.get(key).and_then(|v| v.as_f64()).unwrap_or(default)
374 };
375 let rope_theta = |section: &str, flat_key: &str, default: f64| -> f64 {
379 cfg.get("rope_parameters")
380 .and_then(|rp| rp.get(section))
381 .and_then(|s| s.get("rope_theta"))
382 .and_then(|v| v.as_f64())
383 .or_else(|| cfg.get(flat_key).and_then(|v| v.as_f64()))
384 .unwrap_or(default)
385 };
386 MbConfig {
387 vocab_size: u("vocab_size", 50368),
388 hidden_size: u("hidden_size", 768),
389 num_hidden_layers: u("num_hidden_layers", 22),
390 num_attention_heads: u("num_attention_heads", 12),
391 intermediate_size: u("intermediate_size", 1152),
392 max_position_embeddings: u("max_position_embeddings", 8192),
393 layer_norm_eps: f("norm_eps", 1e-5),
394 pad_token_id: u("pad_token_id", 50283) as u32,
395 global_attn_every_n_layers: u("global_attn_every_n_layers", 3),
396 global_rope_theta: rope_theta("full_attention", "global_rope_theta", 160000.0),
397 local_attention: u("local_attention", 128),
398 local_rope_theta: rope_theta("sliding_attention", "local_rope_theta", 10000.0),
399 classifier_config: None,
400 }
401}