--- license: mit base_model: VAST-AI/SkinTokens tags: - rigging - skinning - skeleton - autoregressive - fsq - safetensors - bf16 --- # SkinTokens (TokenRig), bf16 safetensors for SpawnDev.ILGPU.ML A format conversion of **VAST-AI/SkinTokens** (`experiments/articulation_xl_quantization_256_token_4/grpo_1400.ckpt`), the GRPO-refined TokenRig checkpoint, for the C# / ILGPU re-implementation in [SpawnDev.ILGPU.ML](https://github.com/LostBeard/SpawnDev.ILGPU.ML) (`SpawnDev.ILGPU.ML.SkinTokens`), which rigs a character mesh (skeleton and learned per-vertex skin weights) on the GPU, desktop or browser (WebGPU). All credit for the model goes to the authors of SkinTokens / UniRig and VAST AI Research; the original work is MIT licensed and so is this conversion. - Paper: SkinTokens: A Learned Compact Representation for Unified Autoregressive Rigging (arXiv:2602.04805) - Original weights and code: https://huggingface.co/VAST-AI/SkinTokens , https://github.com/VAST-AI-Research/SkinTokens ## What was changed `skintokens.safetensors` (one file, 1.15 GB, 512 tensors, bf16) holds the inference tensors of the original checkpoint with these mechanical changes, nothing retrained: - the checkpoint's bf16 tensors are stored unchanged; training-only tensors (the VAE's encoder and quantiser input side) are dropped; - Qwen3 layers: `q_proj`, `k_proj`, `v_proj` fused into `self_attn.qkv_proj` (rows `[q | k | v]`), `gate_proj` and `up_proj` fused into `mlp.gate_up_proj`; - the Michelangelo encoder's fused attention projections regrouped from `[head][part][dim]` rows to `[part][head][dim]`; - the VAE's attention (Tripo2 blocks interleave q, k, v per head): `attn1.qkv.weight` and `attn2.kv.weight` regrouped to `[part][head][dim]`. The conversion script is `tools/unirig-harness/reference/export_weights.py skintokens` in the SpawnDev.ILGPU.ML repository. The C# port is tested against the original PyTorch classes (same token sequence, skin weights to 5.7e-5 relative RMS with exact kernels). Note for anyone loading this elsewhere: the original model keeps some non-persistent buffers (the VAE's frequency / phase tables and Qwen3's `inv_freq`) rounded to bf16 by `.to(bfloat16)`; those are not stored here, they are recomputed (and must be rounded to bf16 to match the original).