InferenceWorker/HandModelWeights.cs
using System.Security.Cryptography;
using HumanoidMocap.Inference;
using TorchSharp;
using static TorchSharp.torch;
using F=TorchSharp.torch.nn.functional;
namespace HumanoidMocap.Worker;
/// <summary>Data-only checkpoint loading for the pinned native C# hand models.</summary>
internal sealed class HandModelWeights : IDisposable
{
readonly Dictionary<string,Tensor> values=new(StringComparer.Ordinal);
/// <param name="reduce">Tensors to keep only in a reduced type. They are converted as they
/// are read, so a second full-precision copy never exists.</param>
public HandModelWeights(string path,string sha256,Func<string,bool> include,CancellationToken cancellation,Func<string,ScalarType?>? reduce=null)
{
if(!FileChecksum.Matches(path,sha256))
throw new InvalidDataException("Hand model checkpoint checksum mismatch.");
using var checkpoint=new TorchCheckpoint(path);var loaded=0;
try
{
foreach(var (name,info) in checkpoint.Tensors.Where(p=>include(p.Key)))
{
cancellation.ThrowIfCancellationRequested();
var data=checkpoint.ReadFloat(name,cancellation);
if(data.Any(v=>!float.IsFinite(v)))throw new InvalidDataException("Non-finite hand model weights: "+name);
using var flat=tensor(data);using var shaped=flat.reshape(info.Shape.Select(x=>(long)x).ToArray());
values.Add(name,(reduce?.Invoke(name) is { } type?shaped.to(type):shaped.clone()).DetachFromDisposeScope());
// Checkpoint arrays are large-object garbage; collect before the next ones pile up.
if(++loaded%48==0)GC.Collect();
}
}
catch{Dispose();throw;}
}
public Tensor this[string name]=>values.TryGetValue(name,out var value)?value:throw new InvalidDataException("Missing hand model tensor: "+name);
public bool Contains(string name)=>values.ContainsKey(name);
public Tensor Linear(Tensor input,string name)=>F.linear(input,this[name+".weight"],values.GetValueOrDefault(name+".bias"));
public Tensor Norm(Tensor input,string name,int width,double epsilon=1e-6)=>F.layer_norm(input,new long[]{width},this[name+".weight"],this[name+".bias"],epsilon);
public Tensor Conv(Tensor input,string name,int stride=1,int padding=0)=>F.conv2d(input,this[name+".weight"],values.GetValueOrDefault(name+".bias"),strides:new long[]{stride,stride},padding:new long[]{padding,padding});
public Tensor BatchNorm(Tensor input,string name)=>F.batch_norm(input,this[name+".running_mean"],this[name+".running_var"],this[name+".weight"],this[name+".bias"],training:false,eps:1e-5);
public static float[] Array(Tensor value)=>value.contiguous().data<float>().ToArray();
public void Dispose(){foreach(var value in values.Values)value.Dispose();values.Clear();}
}