dev/yolo_gpu_checks.cs
using HumanoidMocapX.Inference;
using Sandbox;

namespace HumanoidMocapX.Checks;

/// <summary>Checks a complete YOLOX-L GPU pass using sparse synthetic filters and an
/// independent CPU evaluation. Does not validate published checkpoint accuracy.</summary>
public static class YoloGpuChecks
{
    /// <summary>Compares all 8400 prediction rows, tests input rejection and cancellation,
    /// and throws on disagreement. Run through trusted automation in a live editor.</summary>
    public static async Task<object> Run(CancellationToken cancellation)
    {
        var network = new YoloNetwork();
        var input = new float[640 * 640 * 3];
        for (var i = 0; i < input.Length; i++) input[i] = 128 + 100 * MathF.Sin(i * .013f);
        var filters = new YoloFilter[network.Nodes.Length];
        for (var i = 0; i < filters.Length; i++)
        {
            var node = network.Nodes[i];
            if (node.Operation != YoloOperation.Convolution) continue;
            var channels = network.Nodes[node.First].Channels;
            var weights = new float[node.Channels * channels * node.Kernel * node.Kernel];
            var bias = new float[node.Channels];
            for (var c = 0; c < node.Channels; c++)
            {
                weights[WeightIndex(node, channels, c)] = .2f + .01f * MathF.Sin(c);
                bias[c] = .01f * MathF.Cos(c + i);
            }
            filters[i] = new(weights, bias);
        }
        var expected = await Task.Run(() => Reference(network, filters, input, cancellation), cancellation);
        await GameTask.MainThread();
        var detector = new YoloGpuDetector(network, filters);
        try
        {
            var actual = new float[expected.Length];
            using var cancelled = new CancellationTokenSource(); cancelled.Cancel();
            var observed = false;
            try { await detector.PredictAsync(input, actual, cancelled.Token); }
            catch (OperationCanceledException) { observed = true; }
            if (!observed) throw new Exception("YOLOX ignored pre-cancellation.");
            try { await detector.PredictAsync(input, new float[1], cancellation); throw new Exception("YOLOX accepted a short output."); }
            catch (ArgumentException) { }
            await detector.PredictAsync(input, actual, cancellation);
            double largestError = 0;
            for (var i = 0; i < actual.Length; i++)
            {
                var error = Math.Abs((double)actual[i] - expected[i]); largestError = Math.Max(largestError, error);
                if (!float.IsFinite(actual[i]) || error > 1e-4 * Math.Max(1, Math.Abs(expected[i])))
                    throw new Exception($"YOLOX output {i}: expected {expected[i]:R}, got {actual[i]:R}.");
            }
            return new { Predictions = YoloNetwork.Predictions, LargestAbsoluteError = largestError };
        }
        finally { await GameTask.MainThread(); detector.Dispose(); }
    }

    static int WeightIndex(YoloNode node, int channels, int output)
        => ((output * channels + (output * 7 + 3) % channels) * node.Kernel + node.Kernel / 2) * node.Kernel + node.Kernel / 2;

    static float[] Reference(YoloNetwork network, YoloFilter[] filters, float[] input, CancellationToken cancellation)
    {
        var slots = network.Capacities.Select(count => new float[count]).ToArray();
        input.CopyTo(slots[network.Slots[0]], 0);
        for (var index = 1; index < network.Nodes.Length; index++)
        {
            cancellation.ThrowIfCancellationRequested();
            var node = network.Nodes[index]; var source = network.Nodes[node.First];
            var first = slots[network.Slots[node.First]]; var destination = slots[network.Slots[index]];
            var second = node.Second >= 0 ? slots[network.Slots[node.Second]] : null;
            for (var y = 0; y < node.Size; y++)
                for (var x = 0; x < node.Size; x++)
                    for (var channel = 0; channel < node.Channels; channel++)
                    {
                        var at = (y * node.Size + x) * node.Channels + channel;
                        switch (node.Operation)
                        {
                            case YoloOperation.Convolution:
                                var filter = filters[index];
                                var from = ((y * node.Stride) * source.Size + x * node.Stride) * source.Channels + (channel * 7 + 3) % source.Channels;
                                destination[at] = (float)((double)first[from] * filter.Weight[WeightIndex(node, source.Channels, channel)] + filter.Bias[channel]); break;
                            case YoloOperation.Silu: destination[at] = (float)(first[at] / (1 + Math.Exp(-first[at]))); break;
                            case YoloOperation.Add: destination[at] = first[at] + second[at]; break;
                            case YoloOperation.Concat:
                                var secondChannels = node.Channels - source.Channels;
                                destination[at] = channel < source.Channels ? first[(y * node.Size + x) * source.Channels + channel]
                                    : second[(y * node.Size + x) * secondChannels + channel - source.Channels]; break;
                            case YoloOperation.Focus:
                                var slice = channel / source.Channels;
                                destination[at] = first[((y * 2 + slice % 2) * source.Size + x * 2 + slice / 2) * source.Channels + channel % source.Channels]; break;
                            case YoloOperation.Upsample:
                                destination[at] = first[((y / 2) * source.Size + x / 2) * source.Channels + channel]; break;
                            case YoloOperation.MaxPool:
                                var maximum = float.NegativeInfinity;
                                for (var dy = -node.Kernel / 2; dy <= node.Kernel / 2; dy++)
                                    for (var dx = -node.Kernel / 2; dx <= node.Kernel / 2; dx++)
                                        if (x + dx >= 0 && x + dx < source.Size && y + dy >= 0 && y + dy < source.Size)
                                            maximum = Math.Max(maximum, first[((y + dy) * source.Size + x + dx) * source.Channels + channel]);
                                destination[at] = maximum; break;
                            default: throw new Exception("Unrecognized reference operation.");
                        }
                    }
        }
        var output = new float[YoloNetwork.Predictions * YoloNetwork.PredictionChannels]; var offset = 0;
        foreach (var head in network.Heads)
        {
            var length = network.Nodes[head].Elements;
            slots[network.Slots[head]].AsSpan(0, length).CopyTo(output.AsSpan(offset)); offset += length;
        }
        return output;
    }
}