Editor/HumanoidMocap/Inference/TorchCheckpoint.cs
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;

namespace HumanoidMocap.Inference;

/// <summary>Reads tensor data from the pinned PyTorch ZIP checkpoint format without Python,
/// importing modules, invoking pickle callables, or deserializing executable objects.</summary>
public sealed partial class TorchCheckpoint : IDisposable
{
    public sealed record TensorInfo(string Name,string Storage,string Dtype,long StorageLength,long Offset,int[] Shape,long[] Stride);
    sealed record Symbol(string Module,string Name);
    sealed record StorageRef(string Key,string Dtype,long Count);
    sealed record TensorRef(StorageRef Storage,long Offset,int[] Shape,long[] Stride);
    readonly ZipArchive archive;
    readonly string prefix;
    readonly Dictionary<string,TensorInfo> tensors=new(StringComparer.Ordinal);
    public IReadOnlyDictionary<string,TensorInfo> Tensors=>tensors;
    const int MaximumMetadataBytes=32*1024*1024;
    const long MaximumTensorElements=1_000_000_000;

    public TorchCheckpoint(string path,string? stateKey=null)
    {
        archive=ZipFile.OpenRead(path);
        try
        {
            var entries=archive.Entries.Where(e=>e.FullName.EndsWith("/data.pkl",StringComparison.Ordinal)).ToArray();
            if(entries.Length!=1)throw new InvalidDataException("Expected exactly one PyTorch data.pkl entry.");
            var entry=entries[0];prefix=entry.FullName.Substring(0,entry.FullName.Length-8);
            if(entry.Length>MaximumMetadataBytes)throw new InvalidDataException("Checkpoint metadata exceeds the 32 MiB limit.");
            if(archive.GetEntry(prefix+"byteorder") is { } byteorder)
            {using var r=new StreamReader(byteorder.Open());if(r.ReadToEnd().Trim()!="little")throw new NotSupportedException("Only little-endian checkpoints are supported.");}
            using var source=entry.Open();using var memory=new MemoryStream();source.CopyTo(memory);
            var root=new DataReader(memory.ToArray()).Read() as Dictionary<object,object?> ?? throw new InvalidDataException("Checkpoint root must be a dictionary.");
            // Some inference checkpoints (including ACE) keep separately named
            // component dictionaries. Select one explicitly; never traverse optimizer state.
            var key=stateKey??(root.ContainsKey("state_dict")?"state_dict":null);
            var state=key is null?root:root.TryGetValue(key,out var value)?value as Dictionary<object,object?>:null;
            if(state is null)throw new InvalidDataException($"Checkpoint component '{key}' is missing or is not a dictionary.");
            foreach(var pair in state)
            {
                if(pair.Value is not TensorRef t)continue;
                if(pair.Key is not string name)throw new InvalidDataException("Tensor names must be strings.");
                var info=new TensorInfo(name,t.Storage.Key,t.Storage.Dtype,t.Storage.Count,t.Offset,t.Shape,t.Stride);
                Validate(info);tensors.Add(name,info);
            }
            if(tensors.Count==0)throw new InvalidDataException("No tensor weights in checkpoint.");
        }
        catch{archive.Dispose();throw;}
    }
    static long Count(TensorInfo tensor)
    {long count=1;foreach(var d in tensor.Shape){if(d<0)throw new InvalidDataException("Negative tensor dimension.");count=checked(count*d);if(count>MaximumTensorElements)throw new InvalidDataException("Tensor exceeds element limit.");}return count;}
    void Validate(TensorInfo tensor)
    {
        if(tensor.Shape.Length>8||tensor.Shape.Length!=tensor.Stride.Length||tensor.Offset<0||tensor.StorageLength<0||tensor.StorageLength>MaximumTensorElements)
            throw new InvalidDataException("Invalid tensor dimensions/storage.");
        if(tensor.Storage.Length==0||tensor.Storage.Any(c=>!char.IsAsciiDigit(c)))throw new InvalidDataException("Unexpected tensor storage key.");
        var count=Count(tensor);long last=tensor.Offset;
        for(var i=0;i<tensor.Shape.Length;i++)
        {if(tensor.Stride[i]<0)throw new InvalidDataException("Negative tensor stride.");last=checked(last+Math.Max(0,tensor.Shape[i]-1)*tensor.Stride[i]);}
        if(count>0&&last>=tensor.StorageLength)throw new InvalidDataException("Tensor view exceeds its storage.");
        var storage=archive.GetEntry(prefix+"data/"+tensor.Storage)??throw new InvalidDataException("Missing tensor storage.");
        if(storage.Length!=checked(tensor.StorageLength*ElementSize(tensor.Dtype)))throw new InvalidDataException("Tensor storage byte length mismatch.");
    }
    static int ElementSize(string dtype)=>dtype switch
    {"FloatStorage" or "IntStorage"=>4,"DoubleStorage" or "LongStorage"=>8,"HalfStorage" or "BFloat16Storage"=>2,"ByteStorage" or "BoolStorage"=>1,_=>throw new NotSupportedException("Unsupported checkpoint dtype: "+dtype)};

    /// <summary>Materializes one tensor, respecting noncontiguous views. Never loads the whole
    /// checkpoint into memory. Callers choose which model's tensors to retain.</summary>
    public float[] ReadFloat(string name,CancellationToken cancellation=default)
    {
        var tensor=tensors.TryGetValue(name,out var found)?found:throw new KeyNotFoundException(name);
        var count=checked((int)Count(tensor));var bytes=checked((int)(tensor.StorageLength*ElementSize(tensor.Dtype)));
        cancellation.ThrowIfCancellationRequested();var storage=new byte[bytes];
        using(var input=archive.GetEntry(prefix+"data/"+tensor.Storage)!.Open())
        {
            var offset=0;while(offset<storage.Length){cancellation.ThrowIfCancellationRequested();var n=input.Read(storage,offset,Math.Min(65536,storage.Length-offset));if(n==0)throw new EndOfStreamException();offset+=n;}
        }
        var output=new float[count];var elementSize=ElementSize(tensor.Dtype);
        long contiguousStride=1;var contiguous=true;
        for(var dim=tensor.Shape.Length-1;dim>=0;dim--)
        {if(tensor.Shape[dim]>1&&tensor.Stride[dim]!=contiguousStride)contiguous=false;contiguousStride*=tensor.Shape[dim];}
        if(contiguous&&tensor.Dtype=="FloatStorage"&&BitConverter.IsLittleEndian)
        {
            cancellation.ThrowIfCancellationRequested();
            Buffer.BlockCopy(storage,checked((int)(tensor.Offset*4)),output,0,checked(count*4));
            cancellation.ThrowIfCancellationRequested();return output;
        }
        for(var i=0;i<count;i++)
        {
            if((i&16383)==0)cancellation.ThrowIfCancellationRequested();
            long index=tensor.Offset;var remainder=i;
            for(var dim=tensor.Shape.Length-1;dim>=0;dim--){index+=(remainder%tensor.Shape[dim])*tensor.Stride[dim];remainder/=tensor.Shape[dim];}
            var at=checked((int)(index*elementSize));
            output[i]=tensor.Dtype switch
            {
                "FloatStorage"=>BitConverter.ToSingle(storage,at),"DoubleStorage"=>(float)BitConverter.ToDouble(storage,at),
                "HalfStorage"=>(float)BitConverter.UInt16BitsToHalf(BitConverter.ToUInt16(storage,at)),
                "BFloat16Storage"=>BitConverter.Int32BitsToSingle(BitConverter.ToUInt16(storage,at)<<16),
                "IntStorage"=>BitConverter.ToInt32(storage,at),"LongStorage"=>BitConverter.ToInt64(storage,at),
                "ByteStorage" or "BoolStorage"=>storage[at],_=>throw new NotSupportedException(tensor.Dtype)
            };
        }
        return output;
    }
    public void Dispose()=>archive.Dispose();

    sealed class DataReader
    {
        readonly BinaryReader reader;
        readonly List<object?> stack=new();
        readonly Dictionary<int,object?> memo=new();
        static readonly object Mark=new();
        int operations;
        public int Position=>checked((int)reader.BaseStream.Position);
        public DataReader(byte[] bytes,int offset=0){reader=new BinaryReader(new MemoryStream(bytes),Encoding.UTF8);reader.BaseStream.Position=offset;}
        object? Pop(){if(stack.Count==0)throw new InvalidDataException("Empty pickle stack.");var value=stack[^1];stack.RemoveAt(stack.Count-1);return value;}
        object? Peek()=>stack.Count==0?throw new InvalidDataException("Empty pickle stack."):stack[^1];
        object?[] MarkItems()
        {
            var index=stack.LastIndexOf(Mark);if(index<0)throw new InvalidDataException("Missing pickle mark.");
            var values=stack.Skip(index+1).ToArray();stack.RemoveRange(index,stack.Count-index);return values;
        }
        string Line()
        {var bytes=new List<byte>();byte b;while((b=reader.ReadByte())!=10){if(bytes.Count>=4096)throw new InvalidDataException("Pickle identifier too long.");bytes.Add(b);}return Encoding.UTF8.GetString(bytes.ToArray());}
        string Text(int length)
        {if(length<0||length>MaximumMetadataBytes||length>reader.BaseStream.Length-reader.BaseStream.Position)throw new InvalidDataException("Invalid pickle string length.");return Encoding.UTF8.GetString(reader.ReadBytes(length));}
        static long Integer(object? value)=>value switch{int i=>i,long l=>l,_=>throw new InvalidDataException("Expected integer.")};
        static object?[] Tuple(object? value)=>value as object?[]??throw new InvalidDataException("Expected tuple.");
        static Dictionary<object,object?> Dict(object? value)=>value as Dictionary<object,object?>??throw new InvalidDataException("Expected dictionary.");
        static void Set(Dictionary<object,object?> dictionary,object? key,object? value)
        {
            // Training checkpoints contain integer-keyed optimizer states. Keep those
            // as inert data, with numeric keys distinct from strings such as "0".
            var normalized=key switch{string s=>(object)s,int i=>(long)i,long l=>l,_=>throw new InvalidDataException("Only string and integer dictionary keys are supported.")};
            dictionary[normalized]=value;
        }
        public object? Read(bool requireEnd=true)
        {
            while(reader.BaseStream.Position<reader.BaseStream.Length)
            {
                if(++operations>2_000_000||stack.Count>100_000||memo.Count>100_000)throw new InvalidDataException("Checkpoint metadata complexity limit.");
                var op=reader.ReadByte();
                switch(op)
                {
                    case 0x80:var protocol=reader.ReadByte();if(protocol>5)throw new NotSupportedException("Pickle protocol "+protocol);break;
                    case (byte)'.':if(stack.Count!=1||(requireEnd&&reader.BaseStream.Position!=reader.BaseStream.Length))throw new InvalidDataException("Invalid pickle termination.");return Pop();
                    case (byte)'(':stack.Add(Mark);break;
                    case (byte)')':stack.Add(System.Array.Empty<object?>());break;
                    case (byte)'}':stack.Add(new Dictionary<object,object?>());break;
                    case (byte)']':stack.Add(new List<object?>());break;
                    case (byte)'N':stack.Add(null);break;
                    case 0x88:stack.Add(true);break;
                    case 0x89:stack.Add(false);break;
                    case (byte)'K':stack.Add((int)reader.ReadByte());break;
                    case (byte)'M':stack.Add((int)reader.ReadUInt16());break;
                    case (byte)'J':stack.Add(reader.ReadInt32());break;
                    case (byte)'G':var f=reader.ReadBytes(8);if(f.Length!=8)throw new EndOfStreamException();System.Array.Reverse(f);stack.Add(BitConverter.ToDouble(f));break;
                    case (byte)'X':stack.Add(Text(reader.ReadInt32()));break;
                    case 0x8c:stack.Add(Text(reader.ReadByte()));break;
                    case 0x95:var frame=reader.ReadUInt64();if(frame>(ulong)(reader.BaseStream.Length-reader.BaseStream.Position))throw new InvalidDataException("Truncated pickle frame.");break;
                    case (byte)'q':memo[reader.ReadByte()]=Peek();break;
                    case (byte)'r':memo[reader.ReadInt32()]=Peek();break;
                    case 0x94:memo[memo.Count]=Peek();break;
                    case (byte)'h':stack.Add(memo[reader.ReadByte()]);break;
                    case (byte)'j':stack.Add(memo[reader.ReadInt32()]);break;
                    case (byte)'t':stack.Add(MarkItems());break;
                    case 0x85:stack.Add(new[]{Pop()});break;
                    case 0x86:var second=Pop();stack.Add(new[]{Pop(),second});break;
                    case 0x87:var third=Pop();second=Pop();stack.Add(new[]{Pop(),second,third});break;
                    case (byte)'s':var value=Pop();var key=Pop();Set(Dict(Peek()),key,value);break;
                    case (byte)'u':var pairs=MarkItems();if(pairs.Length%2!=0)throw new InvalidDataException("Odd dictionary entries.");var d=Dict(Peek());for(var i=0;i<pairs.Length;i+=2)Set(d,pairs[i],pairs[i+1]);break;
                    case (byte)'a':value=Pop();((List<object?>)Peek()!).Add(value);break;
                    case (byte)'e':var items=MarkItems();((List<object?>)Peek()!).AddRange(items);break;
                    case (byte)'c':stack.Add(AllowedSymbol(Line(),Line()));break;
                    case 0x93:var name=Pop() as string??throw new InvalidDataException();var module=Pop() as string??throw new InvalidDataException();stack.Add(AllowedSymbol(module,name));break;
                    case (byte)'Q':
                        var storage=Tuple(Pop());
                        if(storage.Length<5||storage[0] as string!="storage"||storage[1] is not Symbol type||type.Module!="torch"||storage[2] is not string storageKey)
                            throw new InvalidDataException("Unsupported persistent pickle reference.");
                        if(storage.Length>5&&storage[5] is not null)throw new NotSupportedException("Legacy storage aliases are not supported.");
                        stack.Add(new StorageRef(storageKey,type.Name,Integer(storage[4])));break;
                    case (byte)'R':
                        var arguments=Tuple(Pop());var symbol=Pop() as Symbol??throw new InvalidDataException("Unsupported pickle callable.");
                        if(symbol==new Symbol("collections","OrderedDict")&&arguments.Length==0)stack.Add(new Dictionary<object,object?>());
                        else if(symbol==new Symbol("collections","Counter")&&arguments.Length<=1)
                            stack.Add(arguments.Length==0?new Dictionary<object,object?>():new Dictionary<object,object?>(Dict(arguments[0])));
                        else if(symbol.Module=="torch._utils"&&(symbol.Name=="_rebuild_tensor_v2"||symbol.Name=="_rebuild_tensor")&&arguments.Length>=4&&arguments[0] is StorageRef sr)
                            stack.Add(new TensorRef(sr,Integer(arguments[1]),Tuple(arguments[2]).Select(x=>checked((int)Integer(x))).ToArray(),Tuple(arguments[3]).Select(Integer).ToArray()));
                        else throw new InvalidDataException("Unsupported checkpoint construction: "+symbol);
                        break;
                    case (byte)'b':
                        var metadata=Dict(Pop());_ = Dict(Peek());
                        if(metadata.Keys.Any(k=>!Equals(k,"_metadata")))throw new InvalidDataException("Unsupported checkpoint object state.");
                        break;
                    default:throw new NotSupportedException($"Checkpoint pickle opcode 0x{op:X2} at {reader.BaseStream.Position-1} is unsupported.");
                }
            }
            throw new EndOfStreamException("Missing pickle STOP.");
        }
        static Symbol AllowedSymbol(string module,string name)
        {
            if(module=="collections"&&(name=="OrderedDict"||name=="Counter")||module=="torch._utils"&&(name=="_rebuild_tensor_v2"||name=="_rebuild_tensor"))return new(module,name);
            if(module=="torch"){_ = ElementSize(name);return new(module,name);}
            throw new InvalidDataException("Executable/unsupported checkpoint symbol rejected: "+module+"."+name);
        }
    }
}