Editor/HumanoidMocap/Inference/LiteModel.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace HumanoidMocap.Inference;
/// <summary>Read-only subset of the documented TFLite flatbuffer format. No native runtime.</summary>
public sealed class LiteModel
{
public sealed record Tensor(string Name,int[] Shape,int Type,float[] Values,bool Constant);
public sealed record Operator(int Code,int[] Inputs,int[] Outputs,int Options);
public List<Tensor> Tensors { get; } = new();
public List<Operator> Operators { get; } = new();
public int[] Inputs { get; }
public int[] Outputs { get; }
readonly byte[] data;
int I(int p)=>BitConverter.ToInt32(data,p);
ushort U(int p)=>BitConverter.ToUInt16(data,p);
public int Field(int table,int field)
{
if(table==0)return 0;var vt=table-I(table);var index=4+field*2;
return index<U(vt) && U(vt+index)!=0?table+U(vt+index):0;
}
int Ref(int at)=>at==0?0:at+I(at);
public int Int(int table,int field,int fallback=0){var p=Field(table,field);return p==0?fallback:I(p);}
public int Byte(int table,int field,int fallback=0){var p=Field(table,field);return p==0?fallback:data[p];}
int[] Array(int table,int field,bool refs=false)
{
var p=Ref(Field(table,field));if(p==0)return System.Array.Empty<int>();
var n=I(p);if(n<0||n>1000000)throw new FormatException("Invalid model vector size.");
return Enumerable.Range(0,n).Select(i=>refs?Ref(p+4+i*4):I(p+4+i*4)).ToArray();
}
string String(int table,int field)
{
var p=Ref(Field(table,field));return p==0?"":Encoding.UTF8.GetString(data,p+4,I(p));
}
public LiteModel(byte[] bytes)
{
data=bytes;
if(bytes.Length<8||Encoding.ASCII.GetString(bytes,4,4)!="TFL3")throw new FormatException("Not a TFLite model.");
var root=I(0);var codes=Array(root,1,true);var subgraphs=Array(root,2,true);var buffers=Array(root,4,true);
if(subgraphs.Length!=1)throw new NotSupportedException("Only single-graph inference is supported.");
var graph=subgraphs[0];Inputs=Array(graph,1);Outputs=Array(graph,2);
long total=0;
foreach(var t in Array(graph,0,true))
{
var shape=Array(t,0);var count=shape.Aggregate(1,(a,b)=>checked(a*b));total+=count;
if(count<0||total>200000000)throw new FormatException("Model exceeds managed inference budget.");
var type=Byte(t,1);var buffer=Int(t,2);var offset=Ref(Field(buffers[buffer],0));var length=offset==0?0:I(offset);
var values=new float[count];
if(length>0)
{
var stride=type switch{0 or 2=>4,1=>2,_=>throw new NotSupportedException($"Tensor type {type} is not supported.")};
if(length!=count*stride)throw new FormatException("Tensor size mismatch.");
for(var i=0;i<count;i++)values[i]=type switch
{0=>BitConverter.ToSingle(data,offset+4+i*4),1=>HalfToFloat(U(offset+4+i*2)),2=>I(offset+4+i*4),_=>0};
}
Tensors.Add(new(String(t,3),shape,type,values,length>0));
}
foreach(var op in Array(graph,3,true))
{
var code=codes[Int(op,0)];var builtin=Int(code,3,Byte(code,0));
Operators.Add(new(builtin,Array(op,1),Array(op,2),Ref(Field(op,4))));
}
}
static float HalfToFloat(ushort bits)
{
var sign=(bits&0x8000)==0?1f:-1f;var exponent=(bits>>10)&31;var mantissa=bits&1023;
if(exponent==0)return sign*MathF.Pow(2,-14)*(mantissa/1024f);
if(exponent==31)return mantissa==0?sign*float.PositiveInfinity:float.NaN;
return sign*MathF.Pow(2,exponent-15)*(1+mantissa/1024f);
}
}