Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,23 @@ user-requested inference results, not test or audit scratch data. Preserve the
temporary root only when its artifacts are needed for diagnosis; otherwise
remove it after recording the relevant result.

## TRELLIS.2 Miniature Golden Fixture

The TRELLIS.2 golden test does not read `weights/` or download model assets. It
generates a miniature source checkpoint, applies the production selective INT8
quantizer, and runs image conditioning, sparse sampling, shape and texture SLat
sampling, both decoders, artifact serialization, and GLB export without stage
mocking.

```bash
uv run pytest -m heavy tests/test_trellis2_golden_fixture.py -q
```

Reviewed tensor and GLB expectations live in
`tests/data/trellis2_miniature_golden.json`. Do not regenerate that manifest
automatically during tests. Rebaseline it only after reviewing an intentional
inference-contract change.

## Editing Constraints

- Prefer existing module boundaries over new abstractions.
Expand Down
20 changes: 13 additions & 7 deletions model-cards/trellis2-mlx-8bit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,13 @@ mlx-spatial-trellis2 generate-textured \
--dino-root weights/trellis2-mlx-8bit/dinov3 \
--rmbg-root weights/trellis2-mlx-8bit/rmbg \
--output outputs/trellis2/object-8bit/model.glb \
--pipeline-type 512 \
--pipeline-type 1024_cascade \
--seed 42
```

`1024_cascade` is the recommended quality tier; use `512` when lower memory
use or faster iteration matters more.

Do not pass `--slat-steps` for a quality run; the model configuration uses 12
steps. `--slat-steps 1` is intended only for a quick runtime smoke test.

Expand Down Expand Up @@ -205,19 +208,22 @@ of the quantized checkpoints to the 8-bit repository.
recognizable appearance.
- The same run completed in 152.23 seconds, observed 3.516 GB peak MLX
allocator use, and recorded zero swap growth.
- A separate `1024_cascade`, 12-step run completed the same end-to-end path
and produced a Blender-readable 12,670,448-byte GLB with 199,884 faces and
embedded 1024 x 1024 PBR textures.

The runtime and memory figures are one local Apple Silicon observation, not a
general benchmark. The run establishes executable compatibility and artifact
health; it is not a formal claim of visual equivalence to the source weights.
The 512 runtime and memory figures are one local Apple Silicon observation,
not a general benchmark. The `1024_cascade` run overlapped another MLX workload,
so it establishes compatibility and artifact health rather than performance.
Neither run is a formal claim of visual equivalence to the source weights.

## Limitations

- Quantization changes the sampling trajectory. Geometry, pose, topology,
texture placement, material values, and unseen surfaces can differ from the
source-precision model even with the same seed.
- The verified end-to-end run used the 512 pipeline. The 1024 and cascade
checkpoints passed inventory and runtime tests but have not received an
equivalent end-to-end quality evaluation here.
- End-to-end validation covers `512` and `1024_cascade`. The standalone `1024`
and `1536_cascade` routes have not received an equivalent quality evaluation.
- Single-view reconstruction cannot determine unseen geometry with certainty.
- Fine detail depends on foreground extraction, cropping, occlusion,
reflections, transparency, and thin structures.
Expand Down
107 changes: 107 additions & 0 deletions tests/data/trellis2_miniature_golden.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
{
"fixture": {
"checkpoint_source": "generated synthetic tensors",
"glb_target_faces": 256,
"pipeline_type": "512",
"quantization_bits": 8,
"quantization_group_size": 64,
"sampler_steps": 1,
"seed": 7,
"texture_size": 32
},
"glb": {
"images": 2,
"materials": 1,
"meshes": 1,
"primitives": [
{
"has_normal": true,
"has_texcoord_0": true,
"material": 0,
"positions": 4565,
"triangles": 3912
}
],
"sha256": "44b55e7184c56449d87a9bed214a7ab24460f9ad3bd159e92867cec67efa9470",
"textures": 2
},
"schema_version": 1,
"trace": {
"completed_stages": [
"asset-config-validation",
"checkpoint-probe-readiness",
"input-image",
"image-preprocessing-background",
"image-conditioning",
"sparse-structure-sampling",
"shape-slat-sampling",
"texture-slat-sampling",
"shape-decoder",
"texture-decoder",
"decoded-artifact-write",
"mesh-export"
],
"tensor_outputs": {
"cond_512": {
"dtype": "float32",
"sha256": "aad5ce8209c55c84c2e64b92b8162e7e49ba89af0f19270a3762df796b90a5e5",
"shape": [
1,
65,
64
]
},
"shape_flexidualgrid_fields": {
"dtype": "float32",
"sha256": "d13460feb6ebeeb09b7fb153ee3c5c607bf1ec2205471396b5a8c5908e5f1f62",
"shape": [
512,
7
]
},
"shape_slat": {
"dtype": "float32",
"sha256": "69670f899f1c5f681fb9b3fd71a229e3f4e22271ed6ee470f36af14f32cc31bd",
"shape": [
64,
32
]
},
"sparse_latent": {
"dtype": "float32",
"sha256": "2fa4d566772d77461fd6630c042e69b0002adf31ee4b724b6557c8a45666ef51",
"shape": [
1,
2,
2,
2,
2
]
},
"texture_slat": {
"dtype": "float32",
"sha256": "99d23e5097506efb79c83865dc63c505ad4fad0db6c8d468d22845baa66b2141",
"shape": [
64,
32
]
},
"texture_voxel_attrs": {
"dtype": "float32",
"sha256": "49b45f028a546f707bcdf37677c63eb279187de4561bbe06f1a91720320396b4",
"shape": [
512,
6
]
},
"texture_voxel_coordinates": {
"dtype": "int32",
"sha256": "d572dc92213b089492d529519ba3356793c57f19955851405b63c16bc944b436",
"shape": [
512,
4
]
}
}
}
}
70 changes: 70 additions & 0 deletions tests/test_trellis2_golden_fixture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
"""End-to-end regression coverage for the miniature TRELLIS.2 golden fixture."""

from __future__ import annotations

import json
from pathlib import Path

import pytest

from mlx_spatial.trellis2_inference import Trellis2InferencePipeline
from mlx_spatial.trellis2_quantization import read_trellis2_quantization_spec
from tests.trellis2_golden_fixture import (
build_trellis2_miniature_golden_fixture,
summarize_glb,
summarize_trellis2_golden_trace,
)

GOLDEN_MANIFEST = Path(__file__).parent / "data/trellis2_miniature_golden.json"


@pytest.mark.heavy
def test_miniature_int8_pipeline_emits_golden_trace_and_glb(tmp_path):
fixture = build_trellis2_miniature_golden_fixture(tmp_path)
quantization = read_trellis2_quantization_spec(
fixture.quantized_root / "ckpts/ss_flow_img_dit_1_3B_64_bf16.safetensors"
)
assert quantization is not None
assert quantization.bits == 8
assert quantization.group_size == 64
assert quantization.tensors

dino_quantization = read_trellis2_quantization_spec(
fixture.dino_root / "model.safetensors"
)
assert dino_quantization is not None
assert dino_quantization.bits == 8
assert dino_quantization.group_size == 64
assert dino_quantization.tensors

result = Trellis2InferencePipeline(fixture.quantized_root).generate_textured_glb(
fixture.image_path,
output_path=fixture.output_path,
dino_root=fixture.dino_root,
pipeline_type="512",
seed=7,
max_num_tokens=4_096,
decoder_token_limit=10_000,
texture_size=32,
glb_target_faces=256,
retain_trace_payloads=True,
)

assert result.ready, result.trace.blocker
summary = {
"schema_version": 1,
"fixture": {
"checkpoint_source": "generated synthetic tensors",
"pipeline_type": "512",
"seed": 7,
"sampler_steps": 1,
"quantization_bits": 8,
"quantization_group_size": 64,
"texture_size": 32,
"glb_target_faces": 256,
},
"trace": summarize_trellis2_golden_trace(result.trace),
"glb": summarize_glb(fixture.output_path),
}
expected = json.loads(GOLDEN_MANIFEST.read_text(encoding="utf-8"))
assert summary == expected
Loading