Verification

July 14, 2026 · View on GitHub

Every ported stage is validated numerically against the PyTorch reference, per component, not just end-to-end — the depth-anything.cpp approach. Reference activations are dumped from microsoft/TRELLIS.2 (via scripts/refgen.sh inside the CUDA container) into GGUF files under dumps/, and the C++ side is compared tap-by-tap with tests/parity.hpp (gate: |got-ref| <= atol + rtol*|ref|, reported as max-abs / rel-L2 / per-row).

One-time setup

docker build -f docker/Dockerfile.ref -t trellis2-ref docker   # PyTorch reference env
scripts/download_models.sh                                     # HF checkpoints -> models/
docker run --rm -v "$PWD":/work -w /work trellis2-ref bash -c '
  python convert_dino_to_gguf.py        --output ggufs/dino_f32.gguf       --ftype 0
  python convert_ss_flow_to_gguf.py     --model models/TRELLIS.2-4B/ckpts/ss_flow_img_dit_1_3B_64_bf16.safetensors --output ggufs/ss_flow_f32.gguf --ftype 0
  python convert_ss_dec_to_gguf.py      --model models/TRELLIS-image-large/ckpts/ss_dec_conv3d_16l8_fp16.safetensors --output ggufs/ss_dec_f32.gguf --ftype 0
  python convert_slat_flow_to_gguf.py   --model models/TRELLIS.2-4B/ckpts/slat_flow_img2shape_dit_1_3B_512_bf16.safetensors --pipeline-json models/TRELLIS.2-4B/pipeline.json --output ggufs/slat_flow_f32.gguf --ftype 0
  python convert_shape_dec_to_gguf.py   --output ggufs/shape_dec_f32.gguf  --ftype 0'
scripts/refgen.sh                                              # dump reference activations

Run

cmake -B build -DTRELLIS2_BUILD_TESTS=ON && cmake --build build -j
ctest --test-dir build -LE model        # fast, no assets (marching cubes, preprocess)
ctest --test-dir build                  # full parity (needs ggufs/ + dumps/)

Parity table (f32 GGUF vs true-fp32 reference)

stagetesttap coverageresult
image preprocess (alpha crop, premultiply, Lanczos-512)`test_preprocess$\text{full} 512 \times 512 \text{RGB}\text{byte}-\text{exact} (0/786432 \text{differ})
\text{DINOv3} \text{ViT}-\text{L}/16 \text{encoder}$test_dino`40 taps: embeddings, RoPE, per-layer output + first/last-layer detail (norm/attn/layerscale/mlp), final affine-free LNPASS, rel-L2 ≤ 7e-7 all taps
SS-flow DiT forwardtest_ss_flow_forwardfull outputPASS, rel-L2 2.4e-4
SS-flow Euler sampler (12-step CFG)test_ss_samplez_s latentPASS, rel-L2 5.7e-3, sign 99.85% (CPU)
SS decoder (dense 3D-conv → 64³ occupancy)test_ss_decoccupancy logitsPASS, rel-L2 2e-5
shape-SLAT flow forwardtest_slatfull outputPASS, rel-L2 2.9e-4 (CPU) / 8e-4 (GPU)
shape-SLAT VAE decoder (sparse ConvNeXt U-Net, 4 levels, 16× up)test_slatper-level features + subdivision logits + final 7-ch output, all 5 levelsPASS, rel-L2 ≤ 6e-7 (levels 0–3 exact; final set within 0.0001%)
integrated subdivision guidetest_slatall decoder levels; final guide coordinates equal decoded shape coordinatesPASS
standalone shape encoder → texture flow → texture decodertest_textureshape latent, flow forward/sampler, guided 6-channel PBR decodeparity-gated; sampler backend drift uses the documented loose gate
sparse PBR surface samplingtest_pbr_samplingdense trilinear interpolation + sparse-boundary normalizationPASS
GLB PBR/alpha exporttest_mesh_exportdirect vertex RGBA, retained metallic/roughness, glTF alpha modePASS
dual-grid mesh extraction`test_marching_cubes$ (\text{invariants}) + \text{visual}\text{watertight}-\text{manifold}, \text{Euler} \text{characteristic}, \text{winding}\text{PASS}
1024 \text{cascade} — \text{decoder} \text{upsample}( \times 4) → 512³ \text{coords}$test_cascade`full coord set + quantized 64³ HR scaffoldPASS, set match to 0.0001% (1 voxel of 995k)
1024 cascade — HR (1024-model) flow forwardtest_cascadefull outputPASS, rel-L2 ~3e-4 (CPU); ~1e-2 on GPU flash
1024 cascade — final 1024³ decode (3.97M voxels)test_cascadeper-level features + subdivision + 7-ch outputPASS, rel-L2 ≤ 2e-2, set within 0.0001%

Notes:

  • Flash attention (default for every flow forward). sdpa_auto() uses ggml_flash_attn_ext for both flow DiTs at all token counts; TRELLIS2_SDPA_EXACT restores the old materialized [L_k, L_q, heads] softmax. Flash is bit-faithful to full softmax on CPU (SS-flow 2.4e-4, SLAT 2.9e-4 — identical to exact, so the tap parity above is unaffected) but incurs ~3e-3 rel-L2 on the CUDA F16-MMA kernel. It was already required for the HR cascade (49,152-token attention fits on 16 GB vs a 108 GiB exact matrix); it is now the default because on the GPU it is also ~30 % faster per forward and O(L) memory — the exact path's 805 MB SS-flow score matrix alloc_graph-fails when the resident pipeline leaves little free VRAM. Was previously gated to score matrices >1 GiB (i.e. only the HR stage). See docs/PLAN.md for the per-stage runtime profile.
  • TF32 matters. PyTorch's default CUDA matmul/attention uses TF32 (≈10-bit mantissa) and reduced-precision flash SDPA, which shows up as ~1e-3 relative error versus true fp32. scripts/ref_common.py disables it so the golden dumps are real fp32; otherwise a correct port looks like it has a 0.08-rel-L2 bug (this exact trap cost a debugging session — see the flow-forward gate).
  • Sampler drift. The 12-step Euler + CFG-rescale loop chaotically amplifies per-step fp differences between backends; it validates tightly on CPU and drifts to ~0.1 rel-L2 on GPU. The decoder gate therefore decodes the reference SLAT so decoder parity is independent of sampler trajectory.
  • Subdivision boundary. A handful of level-3 subdivision logits sit within fp-noise of zero; the >0 threshold can flip them, so the final active-voxel set differs by ~4 voxels out of ~4 million (0.0001%) run-to-run and hardware-to-hardware. This is inherent to a hard threshold, not a port bug.

GPU

-DGGML_CUDA=ON -DCMAKE_CUDA_ARCHITECTURES=120 (Blackwell / RTX 50-series). The flow DiTs and DINOv3 run on CUDA; the 3D-conv decoders (SS decoder CONV_3D and the sparse-conv gather-GEMM) run on CPU because the bundled ggml has no CUDA CONV_3D kernel — they are a small fraction of total inference time. GPU f32 matmul is fp16-class, so tap parity on CUDA is ~1e-3 (deterministic, not device noise); CPU is the tight-tolerance reference backend.