Editor/Gpu/SamGpuDecoderBlock.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 Sandbox;
using HumanoidMocapX.TensorInference;

namespace HumanoidMocapX.Inference;

internal sealed class SamGpuDecoderBlock
{
    readonly SamDecoderShape shape;
    readonly SamGpuNorm tokenPositionNorm, imagePositionNorm, selfNorm, crossTokenNorm, crossImageNorm, hiddenNorm;
    readonly SamGpuAttention self, cross;
    readonly FlowGpuLinear first, second;

    internal SamGpuDecoderBlock(FlowGpuStorage storage, FlowWeights weights, string name, SamDecoderShape shape)
    {
        if (shape.Tokens == 0 || shape.HeadWidth is not (64 or 128)) throw new ArgumentException("GPU SAM needs 64 or 128 channels per head.");
        this.shape = shape;
        tokenPositionNorm = new(storage, weights, name + ".ln_pe_1", shape.Width); imagePositionNorm = new(storage, weights, name + ".ln_pe_2", shape.ContextWidth);
        selfNorm = new(storage, weights, name + ".ln1", shape.Width); crossTokenNorm = new(storage, weights, name + ".ln2_1", shape.Width);
        crossImageNorm = new(storage, weights, name + ".ln2_2", shape.ContextWidth); hiddenNorm = new(storage, weights, name + ".ln3", shape.Width);
        self = new(storage, weights, name + ".self_attn", shape, false); cross = new(storage, weights, name + ".cross_attn", shape, true);
        first = new(storage, weights, name + ".ffn.layers.0.0", shape.Width, shape.HiddenWidth);
        second = new(storage, weights, name + ".ffn.layers.1", shape.HiddenWidth, shape.Width);
    }

    internal void Run(SamGpuDecoderWorkspace w, GpuBuffer<float> tokens, GpuBuffer<float> image,
        GpuBuffer<float> tokenPositions, GpuBuffer<float> imagePositions, bool firstLayer)
    {
        if (w.Shape != shape) throw new ArgumentException("SAM GPU decoder workspace has different dimensions.");
        var k = w.Kernels;
        tokenPositionNorm.Run(k, tokenPositions, w.TokenPositions, shape.Tokens); imagePositionNorm.Run(k, imagePositions, w.ImagePositions, shape.ImageTokens);
        selfNorm.Run(k, tokens, w.NormalizedTokens, shape.Tokens);
        if (firstLayer) w.NormalizedTokens.CopyTo(w.PositionedTokens, shape.TokenElements);
        else k.Add(w.NormalizedTokens, w.TokenPositions, w.PositionedTokens, shape.TokenElements);
        self.Run(w, w.PositionedTokens, w.PositionedTokens, w.NormalizedTokens, w.Projected);
        k.Add(tokens, w.Projected, w.Residual, shape.TokenElements);
        crossTokenNorm.Run(k, w.Residual, w.NormalizedTokens, shape.Tokens); crossImageNorm.Run(k, image, w.NormalizedImage, shape.ImageTokens);
        k.Add(w.NormalizedTokens, w.TokenPositions, w.PositionedTokens, shape.TokenElements);
        k.Add(w.NormalizedImage, w.ImagePositions, w.PositionedImage, shape.ImageElements);
        cross.Run(w, w.PositionedTokens, w.PositionedImage, w.NormalizedImage, w.Projected);
        k.Add(w.Residual, w.Projected, tokens, shape.TokenElements);
        hiddenNorm.Run(k, tokens, w.NormalizedTokens, shape.Tokens);
        first.Run(k, w.NormalizedTokens, w.Hidden, shape.Tokens);
        k.Activate(w.Hidden, w.Activated, shape.Tokens * shape.HiddenWidth, TensorActivation.GeluErf);
        second.Run(k, w.Activated, w.Projected, shape.Tokens);
        k.Add(tokens, w.Projected, w.Residual, shape.TokenElements); w.Residual.CopyTo(tokens, shape.TokenElements);
    }
}

internal readonly struct SamGpuNorm
{
    readonly GpuBuffer<float> weight, bias;
    readonly int width;

    internal SamGpuNorm(FlowGpuStorage storage, FlowWeights weights, string name, int width)
    { this.width = width; weight = storage.Weight(weights, name + ".weight", width); bias = storage.Weight(weights, name + ".bias", width); }

    internal void Run(TensorKernels kernels, GpuBuffer<float> input, GpuBuffer<float> output, int rows)
        => kernels.Normalize(input, weight, bias, output, rows, width, SamLayerNorm.Epsilon);
}

internal sealed class SamGpuAttention
{
    readonly SamDecoderShape shape;
    readonly bool isCross;
    readonly FlowGpuLinear query, key, value, projection;

    internal SamGpuAttention(FlowGpuStorage storage, FlowWeights weights, string name, SamDecoderShape shape, bool isCross)
    {
        this.shape = shape; this.isCross = isCross; var context = isCross ? shape.ContextWidth : shape.Width;
        query = new(storage, weights, name + ".q_proj", shape.Width, shape.AttentionWidth);
        key = new(storage, weights, name + ".k_proj", context, shape.AttentionWidth); value = new(storage, weights, name + ".v_proj", context, shape.AttentionWidth);
        projection = new(storage, weights, name + ".proj", shape.AttentionWidth, shape.Width);
    }

    internal void Run(SamGpuDecoderWorkspace w, GpuBuffer<float> queries, GpuBuffer<float> keys, GpuBuffer<float> values, GpuBuffer<float> output)
    {
        var count = isCross ? shape.ImageTokens : shape.Tokens;
        query.Run(w.Kernels, queries, w.Query, shape.Tokens); key.Run(w.Kernels, keys, w.Key, count); value.Run(w.Kernels, values, w.Value, count);
        w.Kernels.Attention(w.Query, w.Key, w.Value, isCross ? w.CrossRanges : w.SelfRanges, w.Attended,
            shape.Tokens, shape.Heads, shape.HeadWidth, count);
        projection.Run(w.Kernels, w.Attended, output, shape.Tokens);
    }
}

internal sealed class SamGpuDecoderWorkspace
{
    internal readonly SamDecoderShape Shape;
    internal readonly TensorKernels Kernels = new();
    internal readonly GpuBuffer<float> NormalizedTokens, PositionedTokens, TokenPositions, NormalizedImage, PositionedImage, ImagePositions;
    internal readonly GpuBuffer<float> Projected, Residual, Hidden, Activated, Query, Key, Value, Attended;
    internal readonly GpuBuffer<uint> SelfRanges, CrossRanges;

    internal SamGpuDecoderWorkspace(FlowGpuStorage storage, SamDecoderShape shape)
    {
        if (shape.Tokens == 0) throw new ArgumentException("Construct a valid SAM decoder shape.", nameof(shape));
        Shape = shape;
        NormalizedTokens = storage.Create<float>(shape.TokenElements); PositionedTokens = storage.Create<float>(shape.TokenElements); TokenPositions = storage.Create<float>(shape.TokenElements);
        NormalizedImage = storage.Create<float>(shape.ImageElements); PositionedImage = storage.Create<float>(shape.ImageElements); ImagePositions = storage.Create<float>(shape.ImageElements);
        Projected = storage.Create<float>(shape.TokenElements); Residual = storage.Create<float>(shape.TokenElements);
        Hidden = storage.Create<float>(shape.Tokens * shape.HiddenWidth); Activated = storage.Create<float>(Hidden.ElementCount);
        Query = storage.Create<float>(shape.Tokens * shape.AttentionWidth); Attended = storage.Create<float>(Query.ElementCount);
        Key = storage.Create<float>(Math.Max(shape.Tokens, shape.ImageTokens) * shape.AttentionWidth); Value = storage.Create<float>(Key.ElementCount);
        var ranges = new uint[shape.Tokens * 4];
        for (var row = 0; row < shape.Tokens; row++) ranges[row * 4 + 1] = (uint)shape.Tokens;
        SelfRanges = storage.Upload<uint>(ranges);
        for (var row = 0; row < shape.Tokens; row++) ranges[row * 4 + 1] = (uint)shape.ImageTokens;
        CrossRanges = storage.Upload<uint>(ranges);
    }
}