tract-onnx 0.23.0-dev.3

Tiny, no-nonsense, self contained, TensorFlow and ONNX inference
Documentation
use crate::model::{OnnxOpRegister, ParsingContext};
use crate::pb::NodeProto;
use tract_core::ops::math::{abs, div, max, mul, rsqrt, square};
use tract_core::ops::nn::{Reduce, Reducer};
use tract_hir::internal::*;
use tract_hir::ops::logic::wire_with_rank_broadcast;

pub fn register_all_ops(reg: &mut OnnxOpRegister) {
    reg.insert("Normalizer", normalizer);
}

#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
enum NormKind {
    Max,
    L1,
    L2,
}

fn parse_norm_kind(s: &str) -> TractResult<NormKind> {
    match s.to_ascii_uppercase().as_str() {
        "MAX" => Ok(NormKind::Max),
        "L1" => Ok(NormKind::L1),
        "L2" => Ok(NormKind::L2),
        other => bail!("Invalid norm kind: {}", other),
    }
}

#[derive(Debug, Clone, Hash)]
struct Normalizer {
    kind: NormKind,
}

fn normalizer(
    _ctx: &ParsingContext,
    node: &NodeProto,
) -> TractResult<(Box<dyn InferenceOp>, Vec<String>)> {
    let norm: String = node.get_attr_opt("norm")?.unwrap_or_else(|| "MAX".to_string());
    let kind = parse_norm_kind(&norm)?;
    Ok((expand(Normalizer { kind }), vec![]))
}

impl Expansion for Normalizer {
    fn name(&self) -> StaticName {
        "Normalizer".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, 1)?;
        check_output_arity(outputs, 1)?;
        s.equals(&outputs[0].datum_type, DatumType::F32)?;
        s.equals(&inputs[0].shape, &outputs[0].shape)?;
        Ok(())
    }

    fn wire(
        &self,
        prefix: &str,
        model: &mut TypedModel,
        inputs: &[OutletId],
    ) -> TractResult<TVec<OutletId>> {
        let input_fact = model.outlet_fact(inputs[0])?.clone();
        let rank = input_fact.rank();
        ensure!(rank >= 1, "Normalizer expects rank 1 or 2 inputs");
        let axis = rank - 1;

        let mut x = inputs[0];
        let x_fact = model.outlet_fact(x)?.clone();

        if x_fact.datum_type != f32::datum_type() {
            x = model.wire_node(
                format!("{prefix}.to_f32"),
                tract_core::ops::cast::cast(f32::datum_type()),
                &[x],
            )?[0];
        }

        let eps = model.add_const(format!("{prefix}.eps"), rctensor0(1e-12f32))?;

        let y = match self.kind {
            NormKind::Max => {
                let ax = model.wire_node(format!("{prefix}.abs"), abs(), &[x])?;
                let d0 = model.wire_node(
                    format!("{prefix}.max"),
                    Reduce { axes: tvec![axis], reducer: Reducer::Max },
                    &ax,
                )?[0];
                let d = wire_with_rank_broadcast(
                    format!("{prefix}.clamp_max"),
                    model,
                    max(),
                    &[d0, eps],
                )?[0];
                wire_with_rank_broadcast(format!("{prefix}.div_max"), model, div(), &[x, d])?[0]
            }
            NormKind::L1 => {
                let ax = model.wire_node(format!("{prefix}.abs"), abs(), &[x])?;
                let d0 = model.wire_node(
                    format!("{prefix}.sum_abs"),
                    Reduce { axes: tvec![axis], reducer: Reducer::Sum },
                    &ax,
                )?[0];
                let d = wire_with_rank_broadcast(
                    format!("{prefix}.clamp_l1"),
                    model,
                    max(),
                    &[d0, eps],
                )?[0];
                wire_with_rank_broadcast(format!("{prefix}.div_sum"), model, div(), &[x, d])?[0]
            }
            NormKind::L2 => {
                let x2 = model.wire_node(format!("{prefix}.square"), square(), &[x])?;
                let ss0 = model.wire_node(
                    format!("{prefix}.sum_sq"),
                    Reduce { axes: tvec![axis], reducer: Reducer::Sum },
                    &x2,
                )?[0];
                let ss = wire_with_rank_broadcast(
                    format!("{prefix}.clamp_l2"),
                    model,
                    max(),
                    &[ss0, eps],
                )?[0];
                let inv = model.wire_node(format!("{prefix}.rsqrt"), rsqrt(), &[ss])?[0];
                wire_with_rank_broadcast(format!("{prefix}.mul_invnorm"), model, mul(), &[x, inv])?
                    [0]
            }
        };

        Ok(tvec!(y))
    }

    fn nboutputs(&self) -> TractResult<usize> {
        Ok(1)
    }
}