Editor/Inference/FlowRefiner.cs
// C# adaptation of FlowHMR's SingleTokenRefiner. See THIRD_PARTY_NOTICES.md.
using System;
using System.Threading;
namespace HumanoidMocapX.Inference;
/// <summary>Refines unpadded, batch-one video features. Uses two full-attention blocks,
/// including the refiner's unscaled timestep embedding. Instances are not thread safe.</summary>
internal sealed class FlowRefiner
{
readonly int frames, width;
readonly FlowLinear embed;
readonly FlowConditionMlp contextMlp, timeMlp;
readonly RefinerBlock[] blocks;
readonly float[] mean, time, context, condition;
internal FlowRefiner(FlowWeights weights, int frames, int width = 1024, int heads = 16)
{
if (frames < 1 || frames > FlowMotion.MaximumFrames || width < 2 || heads < 1 || width % heads != 0)
throw new ArgumentException("Invalid FlowHMR refiner dimensions.");
this.frames = frames; this.width = width;
const string prefix = "text_refiner.";
embed = new(weights, prefix + "input_embedder", width, width);
contextMlp = new(weights, prefix + "context_encoder.linears", width, width);
timeMlp = new(weights, prefix + "timestep_encoder.blocks", width, width);
mean = new float[width]; time = new float[width]; context = new float[width]; condition = new float[width];
blocks = new RefinerBlock[2];
for (var i = 0; i < blocks.Length; i++)
blocks[i] = new(weights, prefix + "individual_token_refiner.blocks." + i, frames, width, heads);
}
internal void Run(ReadOnlySpan<float> input, float timestep, Span<float> output, CancellationToken cancellation = default)
{
cancellation.ThrowIfCancellationRequested();
if (input.Length != (long)frames * width || output.Length != input.Length || input.Overlaps(output))
throw new ArgumentException("Refiner needs separate [frames,width] input and output tensors.");
if (!float.IsFinite(timestep) || timestep < 0 || timestep > 1)
throw new ArgumentOutOfRangeException(nameof(timestep));
foreach (var value in input)
if (!float.IsFinite(value)) throw new ArgumentException("Refiner input contains non-finite video features.", nameof(input));
mean.AsSpan().Clear();
for (var frame = 0; frame < frames; frame++)
for (var channel = 0; channel < width; channel++) mean[channel] += input[frame * width + channel];
for (var channel = 0; channel < width; channel++) mean[channel] /= frames;
contextMlp.Run(mean, context, cancellation);
FlowConditioning.Time(timestep, 1, time);
timeMlp.Run(time, condition, cancellation);
NeuralOps.AddInPlace(condition, context);
embed.Run(input, frames, output, cancellation);
foreach (var block in blocks) block.Run(output, condition, cancellation);
}
sealed class RefinerBlock
{
readonly int frames, width, heads, headWidth;
readonly FlowLinear qkvProjection, projection, mlpFirst, mlpSecond, modulation;
readonly Tensor norm1Weight, norm1Bias, norm2Weight, norm2Bias, queryWeight, queryBias, keyWeight, keyBias;
readonly FlowAttention attention;
readonly float[] norm, qkv, query, key, value, attended, projected, hidden, activatedCondition, gates;
internal RefinerBlock(FlowWeights weights, string name, int frames, int width, int heads)
{
this.frames = frames; this.width = width; this.heads = heads; headWidth = width / heads;
qkvProjection = new(weights, name + ".self_attn_qkv", width, 3 * width);
projection = new(weights, name + ".self_attn_proj", width, width);
mlpFirst = new(weights, name + ".mlp.fc1", width, 4 * width);
mlpSecond = new(weights, name + ".mlp.fc2", 4 * width, width);
modulation = new(weights, name + ".adaLN_modulation.linear", width, 2 * width);
norm1Weight = weights.Get(name + ".norm1.weight", width); norm1Bias = weights.Get(name + ".norm1.bias", width);
norm2Weight = weights.Get(name + ".norm2.weight", width); norm2Bias = weights.Get(name + ".norm2.bias", width);
queryWeight = weights.Get(name + ".self_attn_q_norm.weight", headWidth); queryBias = weights.Get(name + ".self_attn_q_norm.bias", headWidth);
keyWeight = weights.Get(name + ".self_attn_k_norm.weight", headWidth); keyBias = weights.Get(name + ".self_attn_k_norm.bias", headWidth);
var size = checked(frames * width);
norm = new float[size]; qkv = new float[checked(size * 3)];
query = new float[size]; key = new float[size]; value = new float[size];
attended = new float[size]; projected = new float[size]; hidden = new float[checked(size * 4)];
activatedCondition = new float[width]; gates = new float[width * 2];
attention = new(frames, width, heads, false);
}
internal void Run(Span<float> features, ReadOnlySpan<float> condition, CancellationToken cancellation)
{
condition.CopyTo(activatedCondition); FlowConditioning.Silu(activatedCondition);
modulation.Run(activatedCondition, 1, gates, cancellation);
features.CopyTo(norm); NeuralOps.LayerNorm(norm, frames, width, norm1Weight, norm1Bias);
qkvProjection.Run(norm, frames, qkv, cancellation);
for (var frame = 0; frame < frames; frame++)
{
var source = qkv.AsSpan(frame * 3 * width, 3 * width);
source[..width].CopyTo(query.AsSpan(frame * width, width));
source.Slice(width, width).CopyTo(key.AsSpan(frame * width, width));
source.Slice(2 * width, width).CopyTo(value.AsSpan(frame * width, width));
}
NeuralOps.LayerNorm(query, frames * heads, headWidth, queryWeight, queryBias);
NeuralOps.LayerNorm(key, frames * heads, headWidth, keyWeight, keyBias);
attention.Run(query, key, value, attended, cancellation);
projection.Run(attended, frames, projected, cancellation);
FlowConditioning.AddGated(features, projected, gates.AsSpan(0, width));
features.CopyTo(norm); NeuralOps.LayerNorm(norm, frames, width, norm2Weight, norm2Bias);
mlpFirst.Run(norm, frames, hidden, cancellation); FlowConditioning.Silu(hidden);
mlpSecond.Run(hidden, frames, projected, cancellation);
FlowConditioning.AddGated(features, projected, gates.AsSpan(width, width));
}
}
}