From de0b509dfa2ba126fd00fbfe1b422d326fe0f21c Mon Sep 17 00:00:00 2001 From: m3ultra Date: Mon, 20 Jul 2026 17:04:20 +1000 Subject: [PATCH] =?UTF-8?q?operator:=20trellis2=5Fmlx=20=E2=80=94=20the=20?= =?UTF-8?q?72s/26GB=20MLX=20fork=20as=20a=20farm=20operator=20(vertex=20ba?= =?UTF-8?q?ker=20default;=20m1=20routed=20via=20nodes.json)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- scripts/install_trellis2_mlx.sh | 20 ++++++ server/operators/trellis2_mlx/manifest.json | 20 ++++++ server/operators/trellis2_mlx/run.py | 70 +++++++++++++++++++++ 3 files changed, 110 insertions(+) create mode 100755 scripts/install_trellis2_mlx.sh create mode 100644 server/operators/trellis2_mlx/manifest.json create mode 100644 server/operators/trellis2_mlx/run.py diff --git a/scripts/install_trellis2_mlx.sh b/scripts/install_trellis2_mlx.sh new file mode 100755 index 0000000..b177520 --- /dev/null +++ b/scripts/install_trellis2_mlx.sh @@ -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" diff --git a/server/operators/trellis2_mlx/manifest.json b/server/operators/trellis2_mlx/manifest.json new file mode 100644 index 0000000..210cf83 --- /dev/null +++ b/server/operators/trellis2_mlx/manifest.json @@ -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"} + } + } +} diff --git a/server/operators/trellis2_mlx/run.py b/server/operators/trellis2_mlx/run.py new file mode 100644 index 0000000..6d5cb21 --- /dev/null +++ b/server/operators/trellis2_mlx/run.py @@ -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)