use crate::model::ParsingContext;
use crate::pb::NodeProto;
use tract_core::ops::cast::cast;
use tract_hir::internal::*;
use tract_hir::ops::logic::wire_with_rank_broadcast;
use tract_hir::ops::math::{add, mul, rsqrt, square, sub};
use tract_hir::ops::nn::{Reduce, Reducer};
pub fn group_normalization(
ctx: &ParsingContext,
node: &NodeProto,
) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {
let epsilon = node.get_attr_opt("epsilon")?.unwrap_or(1e-5);
let num_groups: usize = node.get_attr("num_groups")?;
let per_channel_affine = ctx.onnx_operator_set_version >= 21;
Ok((expand(GroupNorm { epsilon, num_groups, per_channel_affine }), vec![]))
}
#[derive(Debug, Clone, new)]
struct GroupNorm {
epsilon: f32,
num_groups: usize,
per_channel_affine: bool,
}
fn broadcast_to_channel_axis(
model: &mut TypedModel,
base: &str,
outlet: OutletId,
target_rank: usize,
) -> TractResult<OutletId> {
let mut wire = model.wire_node(format!("{base}.ax0"), AxisOp::Add(0), &[outlet])?;
for ax in 2..target_rank {
wire = model.wire_node(format!("{base}.ax{ax}"), AxisOp::Add(ax), &wire)?;
}
Ok(wire[0])
}
impl Expansion for GroupNorm {
fn name(&self) -> StaticName {
"GroupNorm".into()
}
fn rules<'r, 'p: 'r, 's: 'r>(
&'s self,
s: &mut Solver<'r>,
inputs: &'p [TensorProxy],
outputs: &'p [TensorProxy],
) -> InferenceResult {
check_input_arity(inputs, 3)?;
check_output_arity(outputs, 1)?;
s.equals(&inputs[0].datum_type, &inputs[1].datum_type)?;
s.equals(&inputs[0].datum_type, &inputs[2].datum_type)?;
s.equals(&inputs[0].datum_type, &outputs[0].datum_type)?;
s.equals(&inputs[0].shape, &outputs[0].shape)?;
Ok(())
}
fn wire(
&self,
prefix: &str,
model: &mut TypedModel,
inputs: &[OutletId],
) -> TractResult<TVec<OutletId>> {
let fact = model.outlet_fact(inputs[0])?.clone();
let rank = fact.rank();
let dt = fact.datum_type;
ensure!(rank >= 2, "GroupNormalization expects rank >= 2, got {rank}");
let c = fact.shape[1].clone();
let groups = self.num_groups.to_dim();
let channels_per_group = c.clone().div_ceil(self.num_groups as u64);
let stash = DatumType::F32;
let x = model.wire_node(format!("{prefix}.cast_in"), cast(stash), &inputs[0..1])?;
let grouped = model.wire_node(
format!("{prefix}.split"),
AxisOp::Reshape(1, tvec![c.clone()], tvec![groups.clone(), channels_per_group.clone()]),
&x,
)?;
let red_axes: Vec<i64> = (2..=rank as i64).collect();
let mean = Reduce::new(Some(red_axes.clone()), true, Reducer::Mean).wire(
&format!("{prefix}.mean"),
model,
&grouped,
)?;
let diff = wire_with_rank_broadcast(
format!("{prefix}.diff"),
model,
sub(),
&[grouped[0], mean[0]],
)?;
let sq = model.wire_node(format!("{prefix}.sq"), square(), &diff)?;
let var = Reduce::new(Some(red_axes), true, Reducer::Mean).wire(
&format!("{prefix}.var"),
model,
&sq,
)?;
let eps = model.add_const(
format!("{prefix}.eps"),
tensor0(self.epsilon).cast_to_dt(stash)?.into_owned(),
)?;
let var_eps =
wire_with_rank_broadcast(format!("{prefix}.var_eps"), model, add(), &[var[0], eps])?;
let inv = model.wire_node(format!("{prefix}.rsqrt"), rsqrt(), &var_eps)?;
let normed_f32 =
wire_with_rank_broadcast(format!("{prefix}.normed"), model, mul(), &[diff[0], inv[0]])?;
let normed = model.wire_node(format!("{prefix}.cast_out"), cast(dt), &normed_f32)?;
let merge = |model: &mut TypedModel, name: String, wire: &[OutletId]| {
model.wire_node(
name,
AxisOp::Reshape(
1,
tvec![groups.clone(), channels_per_group.clone()],
tvec![c.clone()],
),
wire,
)
};
if self.per_channel_affine {
let merged = merge(model, format!("{prefix}.merge"), &normed)?;
let scale =
broadcast_to_channel_axis(model, &format!("{prefix}.scale"), inputs[1], rank)?;
let scaled = wire_with_rank_broadcast(
format!("{prefix}.scaled"),
model,
mul(),
&[merged[0], scale],
)?;
let bias =
broadcast_to_channel_axis(model, &format!("{prefix}.bias"), inputs[2], rank)?;
wire_with_rank_broadcast(prefix, model, add(), &[scaled[0], bias])
} else {
let gr_rank = rank + 1;
let scale =
broadcast_to_channel_axis(model, &format!("{prefix}.scale"), inputs[1], gr_rank)?;
let scaled = wire_with_rank_broadcast(
format!("{prefix}.scaled"),
model,
mul(),
&[normed[0], scale],
)?;
let bias =
broadcast_to_channel_axis(model, &format!("{prefix}.bias"), inputs[2], gr_rank)?;
let biased = wire_with_rank_broadcast(
format!("{prefix}.biased"),
model,
add(),
&[scaled[0], bias],
)?;
merge(model, prefix.to_string(), &biased)
}
}
}