Editor/Inference/SamDecoderBlock.cs
// C# adaptation of SAM 3D Body's transformer decoder. See LICENSE-SAM and THIRD_PARTY_NOTICES.md.
// Copyright (c) Meta Platforms, Inc. and affiliates.
using System;
using System.Threading;
namespace HumanoidMocapX.Inference;
internal sealed class SamDecoderBlock
{
readonly SamDecoderShape shape;
readonly SamLayerNorm tokenPositionNorm, imagePositionNorm, selfNorm, crossTokenNorm, crossImageNorm, hiddenNorm;
readonly SamAttention self, cross;
readonly FlowLinear first, second;
internal SamDecoderBlock(FlowWeights weights, string name, SamDecoderShape shape)
{
if (shape.Tokens == 0) throw new ArgumentException("Construct a valid SAM decoder shape.", nameof(shape));
this.shape = shape;
tokenPositionNorm = new(weights, name + ".ln_pe_1", shape.Width); imagePositionNorm = new(weights, name + ".ln_pe_2", shape.ContextWidth);
selfNorm = new(weights, name + ".ln1", shape.Width); crossTokenNorm = new(weights, name + ".ln2_1", shape.Width);
crossImageNorm = new(weights, name + ".ln2_2", shape.ContextWidth); hiddenNorm = new(weights, name + ".ln3", shape.Width);
self = new(weights, name + ".self_attn", shape, false); cross = new(weights, name + ".cross_attn", shape, true);
first = new(weights, name + ".ffn.layers.0.0", shape.Width, shape.HiddenWidth);
second = new(weights, name + ".ffn.layers.1", shape.HiddenWidth, shape.Width);
}
internal void Run(Span<float> tokens, ReadOnlySpan<float> image, ReadOnlySpan<float> tokenPositions,
ReadOnlySpan<float> imagePositions, bool firstLayer, SamDecoderWorkspace workspace, CancellationToken cancellation)
{
if (workspace.Shape != shape || tokens.Length != shape.TokenElements || tokenPositions.Length != tokens.Length
|| image.Length != shape.ImageElements || imagePositions.Length != image.Length)
throw new ArgumentException("SAM decoder tensors do not match their workspace.");
cancellation.ThrowIfCancellationRequested(); var w = workspace;
tokenPositionNorm.Run(tokenPositions, w.TokenPositions); imagePositionNorm.Run(imagePositions, w.ImagePositions);
selfNorm.Run(tokens, w.NormalizedTokens);
if (firstLayer) w.NormalizedTokens.AsSpan().CopyTo(w.PositionedTokens);
else Add(w.NormalizedTokens, w.TokenPositions, w.PositionedTokens);
self.Run(w, w.PositionedTokens, w.PositionedTokens, w.NormalizedTokens, w.Projected, cancellation);
Add(tokens, w.Projected, tokens);
crossTokenNorm.Run(tokens, w.NormalizedTokens); crossImageNorm.Run(image, w.NormalizedImage);
Add(w.NormalizedTokens, w.TokenPositions, w.PositionedTokens); Add(w.NormalizedImage, w.ImagePositions, w.PositionedImage);
cross.Run(w, w.PositionedTokens, w.PositionedImage, w.NormalizedImage, w.Projected, cancellation);
Add(tokens, w.Projected, tokens);
hiddenNorm.Run(tokens, w.NormalizedTokens);
first.Run(w.NormalizedTokens, shape.Tokens, w.Hidden, cancellation); OmegaActivation.Gelu(w.Hidden);
second.Run(w.Hidden, shape.Tokens, w.Projected, cancellation); Add(tokens, w.Projected, tokens);
}
static void Add(ReadOnlySpan<float> first, ReadOnlySpan<float> second, Span<float> output)
{ for (var i = 0; i < output.Length; i++) output[i] = first[i] + second[i]; }
}
internal readonly struct SamLayerNorm
{
readonly Tensor weight, bias;
internal const float Epsilon = 1e-6f;
internal SamLayerNorm(FlowWeights weights, string name, int width)
{ weight = weights.Get(name + ".weight", width); bias = weights.Get(name + ".bias", width); }
internal void Run(ReadOnlySpan<float> values, Span<float> output)
{
if (output.Length != values.Length || output.Length % weight.Data.Length != 0)
throw new ArgumentException("SAM normalization needs equally sized complete rows.");
values.CopyTo(output); NeuralOps.LayerNorm(output, output.Length / weight.Data.Length, weight.Data.Length, weight, bias, Epsilon);
}
}
internal sealed class SamAttention
{
readonly SamDecoderShape shape;
readonly bool isCross;
readonly FlowLinear query, key, value, projection;
internal SamAttention(FlowWeights weights, string name, SamDecoderShape shape, bool isCross)
{
this.shape = shape; this.isCross = isCross;
var context = isCross ? shape.ContextWidth : shape.Width;
query = new(weights, name + ".q_proj", shape.Width, shape.AttentionWidth);
key = new(weights, name + ".k_proj", context, shape.AttentionWidth); value = new(weights, name + ".v_proj", context, shape.AttentionWidth);
projection = new(weights, name + ".proj", shape.AttentionWidth, shape.Width);
}
internal void Run(SamDecoderWorkspace workspace, ReadOnlySpan<float> queries, ReadOnlySpan<float> keys,
ReadOnlySpan<float> values, Span<float> output, CancellationToken cancellation)
{
var count = isCross ? shape.ImageTokens : shape.Tokens;
var k = workspace.Key.AsSpan(0, count * shape.AttentionWidth); var v = workspace.Value.AsSpan(0, k.Length);
query.Run(queries, shape.Tokens, workspace.Query, cancellation); key.Run(keys, count, k, cancellation); value.Run(values, count, v, cancellation);
(isCross ? workspace.Cross : workspace.Self).Run(workspace.Query, k, v, workspace.Attended, cancellation);
projection.Run(workspace.Attended, shape.Tokens, output, cancellation);
}
}
internal sealed class SamDecoderWorkspace
{
internal readonly SamDecoderShape Shape;
internal readonly FlowAttention Self, Cross;
internal readonly float[] NormalizedTokens, PositionedTokens, TokenPositions, NormalizedImage, PositionedImage, ImagePositions;
internal readonly float[] Projected, Hidden, Query, Key, Value, Attended;
internal SamDecoderWorkspace(SamDecoderShape shape)
{
if (shape.Tokens == 0) throw new ArgumentException("Construct a valid SAM decoder shape.", nameof(shape));
Shape = shape;
NormalizedTokens = new float[shape.TokenElements]; PositionedTokens = new float[shape.TokenElements]; TokenPositions = new float[shape.TokenElements];
NormalizedImage = new float[shape.ImageElements]; PositionedImage = new float[shape.ImageElements]; ImagePositions = new float[shape.ImageElements];
Projected = new float[shape.TokenElements]; Hidden = new float[shape.Tokens * shape.HiddenWidth];
Query = new float[shape.Tokens * shape.AttentionWidth]; Attended = new float[Query.Length];
Key = new float[Math.Max(shape.Tokens, shape.ImageTokens) * shape.AttentionWidth]; Value = new float[Key.Length];
Self = new(shape.Tokens, shape.AttentionWidth, shape.Heads, false);
Cross = new(shape.Tokens, shape.ImageTokens, shape.AttentionWidth, shape.Heads);
}
}