operator: trellis2_mlx — the 72s/26GB MLX fork as a farm operator (vertex baker default; m1 routed via nodes.json)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
m3ultra 2026-07-20 17:04:20 +10:00
parent eb3862bb8a
commit de0b509dfa
3 changed files with 110 additions and 0 deletions

20
scripts/install_trellis2_mlx.sh Executable file
View File

@ -0,0 +1,20 @@
#!/bin/bash
# Vendor the MRP MLX fork of TRELLIS.2 (monster/trellis-2-mrp-mlx) for the
# trellis2_mlx operator. Idempotent. Existing per-box clones also work
# (run.py resolves m3 staging / m1 ~/trellis2-mlx automatically) — this
# script is for fresh boxes.
# Weights: TRELLIS.2-4B + gated facebook/dinov3 + briaai/RMBG-2.0 — rsync
# from m3ultra's HF cache (no HF login):
# for M in models--facebook--dinov3-vitl16-pretrain-lvd1689m \
# models--microsoft--TRELLIS.2-4B models--briaai--RMBG-2.0; do
# rsync -a m3ultra@100.89.131.57:.cache/huggingface/hub/$M ~/.cache/huggingface/hub/
# done
set -euo pipefail
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
DEST="$ROOT/vendor/trellis2-mlx"
REPO="ssh://git@100.71.119.27:222/monster/trellis-2-mrp-mlx.git"
[ -d "$DEST/.git" ] && echo "already cloned: $DEST" || git clone "$REPO" "$DEST"
cd "$DEST"
PB="${PYTHON_BIN:-$(/opt/homebrew/bin/uv python find 3.11)}"
PYTHON_BIN="$PB" bash scripts/setup_macos.sh
.venv/bin/python -c "import mlx_backend" && echo "trellis2-mlx OK: $DEST"

View File

@ -0,0 +1,20 @@
{
"id": "trellis2_mlx",
"name": "TRELLIS.2 MLX (local, fastest)",
"category": "mesh-gen",
"description": "Image → full-density vertex-baked GLB via the MRP MLX fork of TRELLIS.2 (monster/trellis-2-mrp-mlx): pure-MLX sampler + vertex baker + validated pipeline. 72s/gen @ 26.5GB on m3ultra, 182s @ 17.2GB on m1ultra — 3× faster and 3× lighter than the torch-MPS trellis_mac operator. Vertex-colored output (albedo; no MR maps) — pair with the MESHGOD finish farm for game budgets. Weights: TRELLIS.2-4B + gated DINOv3/RMBG (rsync from m3 HF cache to workers; no HF login needed).",
"accepts": ["image"],
"produces": ["model"],
"resources": "gpu",
"entry": "run.py",
"python": "vendor/trellis2-mlx/.venv/bin/python",
"params_schema": {
"type": "object",
"properties": {
"pipeline_type": {"type": "string", "enum": ["512", "1024", "1024_cascade"], "default": "1024_cascade", "description": "Resolution tier"},
"seed": {"type": "integer", "default": 0, "description": "Random seed"},
"steps": {"type": "integer", "default": 12, "description": "Sampler steps per stage (12 = upstream default)"},
"baker": {"type": "string", "enum": ["vertex", "metal", "kdtree"], "default": "vertex", "description": "vertex = 1s bake, clean colors (recommended); metal = UV textures but has the dark-patch sampling bug"}
}
}
}

View File

@ -0,0 +1,70 @@
import argparse
import json
import os
import subprocess
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[3]
# fork checkout resolution: vendored path first, then the per-box conventional
# clones (m3 staging, m1 home) so existing installs work without re-vendoring.
CANDIDATES = [
ROOT / "vendor" / "trellis2-mlx",
Path.home() / "Documents" / "trellis2-mlx-staging" / "trellis2-jourloy",
Path.home() / "trellis2-mlx",
]
FORK = next((c for c in CANDIDATES if (c / "scripts" / "generate_asset.py").exists()), None)
ap = argparse.ArgumentParser()
ap.add_argument("--input", action="append", default=[])
ap.add_argument("--outdir", required=True)
ap.add_argument("--params", default="{}")
a = ap.parse_args()
p = json.loads(a.params)
if not a.input:
print("ERROR: no input image")
sys.exit(1)
if FORK is None:
print("ERROR: trellis-2-mrp-mlx not found. Run scripts/install_trellis2_mlx.sh")
sys.exit(1)
py = FORK / ".venv" / "bin" / "python"
if not py.exists():
print(f"ERROR: no venv at {FORK}/.venv — run its scripts/setup_macos.sh")
sys.exit(1)
outdir = Path(a.outdir)
cmd = [
str(py), str(FORK / "scripts" / "generate_asset.py"),
str(Path(a.input[0]).resolve()),
"--output-dir", str(outdir.resolve()),
"--backend", "mlx-experimental",
"--baker", str(p.get("baker", "vertex")),
"--pipeline-type", str(p.get("pipeline_type", "1024_cascade")),
"--seed", str(p.get("seed", 0)),
"--texture-size", "2048",
"--force",
]
if p.get("steps"):
cmd += ["--steps", str(p["steps"])]
env = os.environ.copy()
env["HF_HUB_DISABLE_XET"] = "1"
print("+", " ".join(cmd), flush=True)
res = subprocess.run(cmd, cwd=str(FORK), stdout=sys.stdout,
stderr=subprocess.STDOUT, env=env)
if res.returncode != 0:
sys.exit(res.returncode)
glbs = sorted(outdir.rglob("candidate_pbr.glb")) or sorted(outdir.rglob("*.glb"))
if not glbs:
print("ERROR: no GLB produced")
sys.exit(1)
stem = Path(a.input[0]).stem
outputs = [{"path": str(glbs[0]), "name": f"trellis2mlx_{stem}.glb",
"meta": {"tool": "trellis2_mlx", "baker": p.get("baker", "vertex")}}]
(outdir / "result.json").write_text(json.dumps({"outputs": outputs}))
print("done:", glbs[0], flush=True)