Editor/Inference/YoloWeights.cs
// C# adaptation of YOLOX's inference-time batch normalization. See THIRD_PARTY_NOTICES.md.
using System;
using System.IO;
using System.Threading;

namespace HumanoidMocapX.Inference;

internal sealed record YoloFilter(float[] Weight, float[] Bias);

internal static class YoloWeights
{
    internal const float Epsilon = 1e-3f;

    /// <summary>Folds evaluation batch normalization into OIHW convolution weights in
    /// place and returns its bias. Rejects invalid statistics; discard weights on failure.</summary>
    internal static float[] Fold(float[] weights, ReadOnlySpan<float> scale, ReadOnlySpan<float> bias,
        ReadOnlySpan<float> mean, ReadOnlySpan<float> variance, CancellationToken cancellation = default)
    {
        if (scale.Length == 0 || weights.Length % scale.Length != 0 || weights.Length == 0
            || bias.Length != scale.Length || mean.Length != scale.Length || variance.Length != scale.Length)
            throw new InvalidDataException("YOLOX batch normalization dimensions disagree.");
        var outputBias = new float[scale.Length]; var width = weights.Length / scale.Length;
        for (var channel = 0; channel < scale.Length; channel++)
        {
            cancellation.ThrowIfCancellationRequested();
            if (!float.IsFinite(scale[channel]) || !float.IsFinite(bias[channel]) || !float.IsFinite(mean[channel])
                || !float.IsFinite(variance[channel]) || variance[channel] < 0)
                throw new InvalidDataException("YOLOX batch normalization contains invalid statistics.");
            var multiplier = scale[channel] / MathF.Sqrt(variance[channel] + Epsilon);
            outputBias[channel] = bias[channel] - mean[channel] * multiplier;
            if (!float.IsFinite(outputBias[channel])) throw new InvalidDataException("YOLOX folded bias overflowed.");
            for (var index = channel * width; index < (channel + 1) * width; index++)
            {
                weights[index] *= multiplier;
                if (!float.IsFinite(weights[index])) throw new InvalidDataException("YOLOX folded weight is not finite.");
            }
        }
        return outputBias;
    }
}