1use crate::internal::*;
2use crate::ops::binary::{BinMiniOp, TypedBinOp};
3use crate::ops::element_wise::ElementWiseOp;
4use crate::ops::math::{Add, Mul, Rsqrt};
5use crate::ops::nn::{Reduce, Reducer};
6use tract_itertools::Itertools;
7
8#[derive(Clone, Debug, Hash, PartialEq, Eq)]
9pub struct RmsNorm {
10 pub axis: usize,
11 pub eps: Arc<Tensor>,
12}
13
14impl Op for RmsNorm {
15 fn name(&self) -> StaticName {
16 "RmsNorm".to_string().into()
17 }
18 fn info(&self) -> TractResult<Vec<String>> {
19 Ok(vec![format!("axis: {:?}, eps: {:?}", self.axis, self.eps)])
20 }
21 op_as_typed_op!();
22}
23
24impl EvalOp for RmsNorm {
25 op_out_of_plan!();
26
27 fn eval(&self, _ctx: &EvalContext, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
28 let input = args_1!(inputs);
29 let in_dt = input.datum_type();
30
31 if matches!(in_dt, DatumType::F32 | DatumType::F16)
38 && input.rank() > 0
39 && self.axis == input.rank() - 1
40 {
41 let eps_f32: f32 = self.eps.cast_to_scalar::<f32>()?;
42 let already_f32 = in_dt == DatumType::F32;
43 let mut buf = if already_f32 {
44 input.into_tensor()
45 } else {
46 input.cast_to::<f32>()?.into_owned()
47 };
48 let row_len = buf.shape()[self.axis];
49 if row_len > 0 {
50 let data = unsafe { buf.as_slice_mut_unchecked::<f32>() };
51 let rms_norm = tract_linalg::routines::rms_norm_f32()?;
52 let total = data.len();
53 tract_linalg::multithread::par_chunks_mut(data, row_len, total, |_, chunk| {
54 for row in chunk.chunks_mut(row_len) {
55 rms_norm(row, eps_f32);
56 }
57 Ok(())
58 })?;
59 }
60 if already_f32 {
61 return Ok(tvec![buf.into_tvalue()]);
62 }
63 return Ok(tvec![buf.cast_to_dt(in_dt)?.into_owned().into()]);
64 }
65
66 let already_f32 = in_dt == DatumType::F32;
68 let input_f32 =
69 if already_f32 { input.into_tensor() } else { input.cast_to::<f32>()?.into_owned() };
70 let eps = self.eps.cast_to::<f32>()?.into_owned();
77 let a1 = Reducer::MeanOfSquares.reduce(&[self.axis], &input_f32)?;
78 let mut a2 = Add.eval(a1.into_tvalue(), eps.into_tvalue(), DatumType::F32)?;
79 Rsqrt {}.eval_in_place(&mut a2, None)?;
80 let a3 = Mul.eval(a2.into_tvalue(), input_f32.into_tvalue(), DatumType::F32)?;
81 if already_f32 {
82 return Ok(tvec![a3.into_tvalue()]);
83 }
84 Ok(tvec![a3.cast_to_dt(in_dt)?.into_owned().into()])
85 }
86}
87
88impl TypedOp for RmsNorm {
89 fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
90 ensure!(self.eps.rank() == 0, "RmsNorm: eps must be a rank-0 tensor");
91 ensure!(
92 self.axis < inputs[0].rank(),
93 "RmsNorm: axis {} is out of bounds for input rank {}",
94 self.axis,
95 inputs[0].rank()
96 );
97 let dt = inputs[0].datum_type;
98 let fact = dt.fact(inputs[0].shape.clone());
99 Ok(tvec!(fact))
100 }
101
102 fn input_roi(
103 &self,
104 model: &TypedModel,
105 node: &TypedNode,
106 ) -> TractResult<Option<TVec<Option<TDim>>>> {
107 crate::optim::propagate_roi::bubble_roi(model, node)
108 }
109
110 fn axes_mapping(
111 &self,
112 inputs: &[&TypedFact],
113 _outputs: &[&TypedFact],
114 ) -> TractResult<AxesMapping> {
115 let rank = inputs[0].rank();
116 let mut letters = 'a'..;
117 let axes = (0..rank)
118 .map(|ix| {
119 Axis::new(letters.next().unwrap(), inputs.len(), 1).input(0, ix).output(0, ix)
120 })
121 .collect_vec();
122 AxesMapping::new(1, 1, axes)
123 }
124
125 fn change_axes(
126 &self,
127 model: &TypedModel,
128 node: &TypedNode,
129 _io: InOut,
130 change: &AxisOp,
131 ) -> TractResult<Option<AxisChangeConsequence>> {
132 if let Some(axis) = change.transform_axis(self.axis) {
133 let op = Some(Box::new(RmsNorm { axis, eps: self.eps.clone() }) as _);
134 Ok(Some(AxisChangeConsequence::new(model, node, op, change)))
135 } else {
136 Ok(None)
137 }
138 }
139
140 fn slice(
141 &self,
142 patch: &mut TypedModelPatch,
143 _model: &TypedModel,
144 node: &TypedNode,
145 _prefix: &str,
146 inputs: &[OutletId],
147 output_axis: usize,
148 _start: &TDim,
149 _end: &TDim,
150 ) -> TractResult<Option<TVec<OutletId>>> {
151 rule_if!(output_axis != self.axis);
152 patch.wire_node(&node.name, self.clone(), inputs).map(Some)
153 }
154
155 fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {
156 let dt = inputs[0].datum_type;
157 let count: TDim = inputs[0].shape.iter().product();
158 let groups: TDim = inputs[0]
161 .shape
162 .iter()
163 .enumerate()
164 .filter(|(i, _)| *i != self.axis)
165 .map(|(_, d)| d)
166 .product();
167 Ok(tvec!((Cost::FMA(dt), count * 3), (Cost::Div(dt), groups)))
168 }
169
170 as_op!();
171}
172
173#[derive(Clone, Debug, Hash, PartialEq, Eq)]
183pub struct ScaledRmsNorm {
184 pub axis: usize,
185 pub eps: Arc<Tensor>,
186 pub out_dt: Option<DatumType>,
189 pub scale_dt: Option<DatumType>,
198}
199
200impl Op for ScaledRmsNorm {
201 fn name(&self) -> StaticName {
202 "ScaledRmsNorm".to_string().into()
203 }
204 fn info(&self) -> TractResult<Vec<String>> {
205 Ok(vec![format!(
206 "axis: {:?}, eps: {:?}, out_dt: {:?}, scale_dt: {:?}",
207 self.axis, self.eps, self.out_dt, self.scale_dt
208 )])
209 }
210 op_as_typed_op!();
211}
212
213impl EvalOp for ScaledRmsNorm {
214 op_out_of_plan!();
215
216 fn eval(&self, ctx: &EvalContext, inputs: TVec<TValue>) -> TractResult<TVec<TValue>> {
217 let (input, scale) = args_2!(inputs);
218 let in_dt = input.datum_type();
219 let input = input.cast_to::<f32>()?.into_owned().into_tvalue();
220 let normed =
221 RmsNorm { axis: self.axis, eps: self.eps.clone() }.eval(ctx, tvec!(input))?.remove(0);
222 let normed = match self.scale_dt {
223 Some(dt) if dt != DatumType::F32 => {
224 normed.cast_to_dt(dt)?.into_owned().cast_to::<f32>()?.into_owned().into_tvalue()
225 }
226 _ => normed,
227 };
228 let mut buf = normed.into_tensor();
229 let scale = scale.cast_to::<f32>()?.into_owned();
230 let scale = unsafe { scale.as_slice_unchecked::<f32>() };
231 let shape = buf.shape().to_vec();
232 let dim = shape[self.axis];
233 ensure!(scale.len() == dim, "ScaledRmsNorm: scale len {} != axis dim {}", scale.len(), dim);
234 let inner: usize = shape[self.axis + 1..].iter().product();
235 let data = unsafe { buf.as_slice_mut_unchecked::<f32>() };
236 for chunk in data.chunks_mut(dim * inner) {
237 for (d, s) in scale.iter().enumerate() {
238 for x in &mut chunk[d * inner..(d + 1) * inner] {
239 *x *= s;
240 }
241 }
242 }
243 let out_dt = self.out_dt.unwrap_or(in_dt);
244 if out_dt == DatumType::F32 {
245 return Ok(tvec![buf.into_tvalue()]);
246 }
247 Ok(tvec![buf.cast_to_dt(out_dt)?.into_owned().into()])
248 }
249}
250
251impl TypedOp for ScaledRmsNorm {
252 fn output_facts(&self, inputs: &[&TypedFact]) -> TractResult<TVec<TypedFact>> {
253 ensure!(self.eps.rank() == 0, "ScaledRmsNorm: eps must be a rank-0 tensor");
254 ensure!(inputs.len() == 2, "ScaledRmsNorm expects 2 inputs (input, scale)");
255 ensure!(
256 self.axis < inputs[0].rank(),
257 "ScaledRmsNorm: axis {} is out of bounds for input rank {}",
258 self.axis,
259 inputs[0].rank()
260 );
261 ensure!(inputs[1].rank() == 1, "ScaledRmsNorm: scale must be rank 1");
262 if let (Ok(axis_dim), Ok(scale_dim)) =
263 (inputs[0].shape[self.axis].to_usize(), inputs[1].shape[0].to_usize())
264 {
265 ensure!(
266 axis_dim == scale_dim,
267 "ScaledRmsNorm: scale len {} != axis dim {}",
268 scale_dim,
269 axis_dim
270 );
271 }
272 if let Some(out_dt) = self.out_dt {
273 ensure!(out_dt.is_float(), "ScaledRmsNorm: out_dt must be a float type");
274 }
275 if let Some(scale_dt) = self.scale_dt {
276 ensure!(scale_dt.is_float(), "ScaledRmsNorm: scale_dt must be a float type");
277 }
278 let dt = self.out_dt.unwrap_or(inputs[0].datum_type);
279 let fact = dt.fact(inputs[0].shape.clone());
280 Ok(tvec!(fact))
281 }
282
283 fn cost(&self, inputs: &[&TypedFact]) -> TractResult<TVec<(Cost, TDim)>> {
284 let dt = inputs[0].datum_type;
285 let count: TDim = inputs[0].shape.iter().product();
286 Ok(tvec!((Cost::FMA(dt), count * 4)))
287 }
288
289 as_op!();
290}
291
292pub fn detect_rms_norm(
294 op: &Reduce,
295 model: &TypedModel,
296 node: &TypedNode,
297) -> TractResult<Option<TypedModelPatch>> {
298 rule_if!(op.reducer == Reducer::MeanOfSquares);
299 rule_if!(op.axes.len() == 1);
300 let axis = op.axes[0];
301
302 let in_fact = model.node_input_facts(node.id)?[0];
303 let dt = in_fact.datum_type;
304
305 rule_if!(matches!(dt, DatumType::F32 | DatumType::F16));
307
308 rule_if_some!(add_succ = model.single_succ(node.id)?);
310 rule_if_some!(add_succ_op = add_succ.op_as::<TypedBinOp>());
311 rule_if!(add_succ_op.0.is::<Add>());
312
313 let add_consts = model.collect_const_inputs(add_succ);
315 rule_if!(add_consts.len() == 1);
316 let eps = add_consts[0].val().clone();
317 rule_if!(eps.len() == 1);
318 rule_if!(eps.datum_type() == dt);
319 let eps = eps.into_tensor().into_shape(&[])?.into_arc_tensor();
320
321 rule_if_some!(rsqrt_succ = model.single_succ(add_succ.id)?);
323 rule_if_some!(rsqrt_succ_op = rsqrt_succ.op_as::<ElementWiseOp>());
324 rule_if!(rsqrt_succ_op.0.is::<Rsqrt>());
325
326 rule_if_some!(mul_succ = model.find_succ_bin_with_outlet::<Mul>(rsqrt_succ, &node.inputs[0]));
328
329 let mut patch = TypedModelPatch::default();
330 let rsm_input = patch.taps(model, &node.inputs)?;
331 let out =
332 patch.wire_node(format!("{}.rms_norm", node.name), RmsNorm { axis, eps }, &rsm_input)?;
333
334 patch.shunt_outside(model, mul_succ.id.into(), out[0])?;
335 Ok(Some(patch))
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341 use crate::ops::nn::RmsNorm;
342
343 #[test]
349 fn eval_with_f16_eps_and_f16_input() {
350 let to_h = |x: f32| f16::from_f32(x);
351 let input = tensor1(&[to_h(1.0), to_h(2.0), to_h(3.0), to_h(4.0)]);
352 let eps = tensor0(to_h(1e-5)).into_arc_tensor();
353 let op = RmsNorm { axis: 0, eps };
354 let out = op
355 .eval(&EvalContext::out_of_plan(), tvec!(input.clone().into()))
356 .expect("eval should not panic");
357 let out = out.into_iter().next().unwrap().into_tensor();
358 assert_eq!(out.datum_type(), DatumType::F16);
359 assert_eq!(out.shape(), &[4]);
360 let got = unsafe { out.as_slice_unchecked::<f16>() };
363 let expected = [0.365_f32, 0.730, 1.095, 1.461];
364 for (i, (g, e)) in got.iter().zip(expected.iter()).enumerate() {
365 let diff = (g.to_f32() - e).abs();
366 assert!(diff < 0.01, "lane {i}: got {} expected {}", g.to_f32(), e);
367 }
368 }
369
370 #[test]
377 fn eval_with_non_trailing_axis_f32() {
378 let input = tensor2(&[[1.0_f32, 2.0, 3.0], [4.0, 5.0, 6.0]]);
384 let eps = tensor0(0.0_f32).into_arc_tensor();
385 let op = RmsNorm { axis: 0, eps };
386 let out = op
387 .eval(&EvalContext::out_of_plan(), tvec!(input.into()))
388 .expect("eval should not panic");
389 let out = out.into_iter().next().unwrap().into_tensor();
390 assert_eq!(out.datum_type(), DatumType::F32);
391 assert_eq!(out.shape(), &[2, 3]);
392 let got = unsafe { out.as_slice_unchecked::<f32>() };
393 let inv = |ms: f32| ms.sqrt().recip();
394 let expected: [f32; 6] = [
395 1.0 * inv(8.5),
396 2.0 * inv(14.5),
397 3.0 * inv(22.5),
398 4.0 * inv(8.5),
399 5.0 * inv(14.5),
400 6.0 * inv(22.5),
401 ];
402 for (i, (g, e)) in got.iter().zip(expected.iter()).enumerate() {
403 let diff = (g - e).abs();
404 assert!(diff < 1e-5, "lane {i}: got {g}, want {e}, diff {diff}");
405 }
406 }
407}