runner: gpu AND cpu are now a node pool. Per-node cpu_slots (primary 3, helpers 2, nodes.json-overridable); net stays primary-only. Python-less ops (ffmpeg/ffprobe) now run remotely on the node's system python3. Verified: 9 concurrent ffmpeg_frames distributed 4 local / 2 m1 / 2 m4. hunyuan3d_mlx: default to Studio-quality (octree 384, texture 4096, remesh 120k) — 4096 bake verified watchdog-free on M3 Ultra; big quality gain (defined face, 120k faces). remesh_faces now a param. All param-overridable.
73 lines
2.5 KiB
Python
73 lines
2.5 KiB
Python
import argparse
|
|
import json
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[3]
|
|
VENDOR = ROOT / "vendor" / "hunyuan3d-mlx"
|
|
|
|
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 not (VENDOR / "generate_e2e.py").exists():
|
|
print(f"ERROR: hunyuan3d-mlx not installed at {VENDOR}. Run scripts/install_hunyuan3d_mlx.sh")
|
|
sys.exit(1)
|
|
|
|
outdir = Path(a.outdir)
|
|
out_stem = outdir / f"hunyuan3d_{Path(a.input[0]).stem}"
|
|
|
|
cmd = [
|
|
sys.executable, "generate_e2e.py", str(Path(a.input[0]).resolve()),
|
|
"--output", str(out_stem.resolve()),
|
|
"--steps", str(p.get("steps", 50)),
|
|
"--guidance", str(p.get("guidance_scale", 7.5)),
|
|
"--octree-resolution", str(p.get("octree_resolution", 384)),
|
|
"--texture-size", str(p.get("texture_size", 4096)),
|
|
"--remesh-faces", str(p.get("remesh_faces", 120000)),
|
|
"--max-num-view", str(p.get("max_num_view", 6)),
|
|
"--seed", str(p.get("seed", 42)),
|
|
]
|
|
if p.get("no_texture"):
|
|
cmd.append("--no-texture")
|
|
|
|
env = os.environ.copy()
|
|
# xet chunked downloader fails on some HF repos — force the reliable HTTP path
|
|
# (same fix as trellis_mac / flux_local).
|
|
env["HF_HUB_DISABLE_XET"] = "1"
|
|
# guard the torch + compiled-kernel OpenMP collision like sf3d / trellis_mac.
|
|
env["KMP_DUPLICATE_LIB_OK"] = "TRUE"
|
|
env.setdefault("OMP_NUM_THREADS", "1")
|
|
env.setdefault("MKL_NUM_THREADS", "1")
|
|
|
|
print("+", " ".join(cmd), flush=True)
|
|
print("(first run downloads ~15GB MLX weights: dgrauet/hunyuan3d-2.1-mlx)", flush=True)
|
|
res = subprocess.run(cmd, cwd=str(VENDOR), stdout=sys.stdout, stderr=subprocess.STDOUT, env=env)
|
|
if res.returncode != 0:
|
|
sys.exit(res.returncode)
|
|
|
|
# generate_e2e.py writes <stem>.glb (textured, or geometry if --no-texture) and
|
|
# optionally <stem>_shape.glb. Prefer the primary <stem>.glb.
|
|
final = out_stem.with_suffix(".glb")
|
|
if final.exists():
|
|
chosen = final
|
|
else:
|
|
glbs = sorted(outdir.rglob("*.glb"))
|
|
if not glbs:
|
|
print("ERROR: hunyuan3d_mlx produced no .glb")
|
|
sys.exit(1)
|
|
chosen = glbs[0]
|
|
|
|
outputs = [{"path": str(chosen), "name": f"hunyuan3d_{Path(a.input[0]).stem}.glb",
|
|
"meta": {"tool": "hunyuan3d_mlx"}}]
|
|
(outdir / "result.json").write_text(json.dumps({"outputs": outputs}))
|
|
print("done:", chosen, flush=True)
|