Editor/Inference/FlowNormalization.cs
// C# adaptation of FlowHMR's RMS normalization. See THIRD_PARTY_NOTICES.md.
using System;
namespace HumanoidMocapX.Inference;
internal static class FlowNormalization
{
internal static void Rms(Span<float> values, ReadOnlySpan<float> scale)
{
const float epsilon = 1e-6f;
if (scale.Length < 1 || values.Length % scale.Length != 0)
throw new ArgumentException("RMS normalization needs complete rows and one scale per channel.");
for (var offset = 0; offset < values.Length; offset += scale.Length)
{
var row = values.Slice(offset, scale.Length);
var squares = 0f;
foreach (var value in row) squares += value * value;
var inverse = 1f / MathF.Sqrt(squares / row.Length + epsilon);
for (var channel = 0; channel < row.Length; channel++) row[channel] *= inverse * scale[channel];
}
}
}
/// <summary>Non-affine normalization using Humanoid Mocap's existing layer norm.</summary>
internal sealed class FlowLayerNorm
{
readonly int width;
readonly float epsilon;
readonly Tensor scale, bias;
internal FlowLayerNorm(int width, float epsilon)
{
if (width < 1 || !float.IsFinite(epsilon) || epsilon <= 0) throw new ArgumentException("Invalid layer normalization dimensions or epsilon.");
this.width = width; this.epsilon = epsilon;
var ones = new float[width]; Array.Fill(ones, 1f);
scale = new("unit scale", ones, new[] { width }); bias = new("zero bias", new float[width], new[] { width });
}
internal void Run(ReadOnlySpan<float> input, Span<float> output)
{
if (input.Length % width != 0 || output.Length != input.Length) throw new ArgumentException("Layer normalization needs equal tensors with complete rows.");
input.CopyTo(output);
NeuralOps.LayerNorm(output, output.Length / width, width, scale, bias, epsilon);
}
}