Add TencentARC/Pixal3D (SIGGRAPH 2026, MIT) as a selectable modeling stage backend. Pixal3D back-projects pixel features into 3D for direct pixel-to-3D correspondence, giving near-reconstruction fidelity with PBR textures — a clear step up from TripoSG / TripoSR / InstantMesh.
Target shape: a deployed service that consumes a RabbitMQ message with W3C trace context, runs weights baked into the container, uploads the GLB to MinIO, and drives the UI over SignalR via the API.
Key constraints (established during planning)
CUDA/Linux only. The TRELLIS.2 base env, natten and flex_gemm are CUDA-compiled and the pipeline hardcodes .cuda(). Runs on the 4090 node (artpipe-gpu lane) only — not SERVER's 3070 Ti (8 GB), not Apple Silicon. No AppleSilicon method variant.
VRAM: 18,000 MB at the default 1536 resolution (~10-12 GB with low_vram). Lane arithmetic: 24,576 MB total, ~24,064 usable, minus SafetyCheck 1,024 + Blender 2,048 = 20,992 free. 18,000 is grantable. Note spikersoft-image-description holds 20,000 on the same lane, so the two serialize.
Image size ~35-45 GB — weights alone are ~27 GB (TencentARC/Pixal3D is 24.04 GB across three 1.3B bf16 cascade checkpoints; plus MoGe-2 1.31 GB, DINOv3 1.21 GB, BiRefNet 0.44 GB).
No new .NET worker project.SpikerSoft.EventHandlers.ArtPipeProcessor already does the whole loop (consume → trace context → subprocess → NSFW gate → MinIO → stage events → SignalR relay). A bespoke project would fork the safety gate, artifact rollback, failover chains, DLQ retry and telemetry.
WRAP strategy, mirroring trellis_mac (#358): inference.py sets os.environ before importing torch, exposes no progress callback, and hardcodes .cuda() — importing it in-process fights all three.
Notable finding: upstream's export is lossy
inference.py ends with glb.export(path, extension_webp=True). trimesh's exporter does img.save(f, format="WEBP") with no lossless=True and no quality=, so Pillow's default applies — lossy, quality 80 — silently re-compressing every 4096px PBR map. It also marks EXT_texture_webp as required, so readers lacking the extension must refuse the file.
Converting to PNG afterwards would preserve already-damaged pixels, so we intervene before the damage: our runner neutralises the flag at the trimesh boundary and exports lossless PNG by default. texture_format=webp reproduces upstream for A/B comparison.
Pixal3D code is MIT; MoGe-2 and BiRefNet are MIT. Caveat: upstream pins DINOv3 to the third-party re-upload camenduru/dinov3-vitl16-pretrain-lvd1689m rather than Meta's gated facebook/ repo. Meta's DINOv3 licence still governs those weights — decision taken to keep upstream's mirror as-shipped for now; revisit before commercial distribution.
## Goal
Add [TencentARC/Pixal3D](https://github.com/TencentARC/Pixal3D) (SIGGRAPH 2026, MIT) as a selectable **modeling** stage backend. Pixal3D back-projects pixel features into 3D for direct pixel-to-3D correspondence, giving near-reconstruction fidelity with PBR textures — a clear step up from TripoSG / TripoSR / InstantMesh.
Target shape: a deployed service that consumes a RabbitMQ message with W3C trace context, runs weights baked into the container, uploads the GLB to MinIO, and drives the UI over SignalR via the API.
## Key constraints (established during planning)
- **CUDA/Linux only.** The TRELLIS.2 base env, `natten` and `flex_gemm` are CUDA-compiled and the pipeline hardcodes `.cuda()`. Runs on the `4090` node (`artpipe-gpu` lane) only — not SERVER's 3070 Ti (8 GB), not Apple Silicon. No `AppleSilicon` method variant.
- **VRAM: 18,000 MB** at the default 1536 resolution (~10-12 GB with `low_vram`). Lane arithmetic: 24,576 MB total, ~24,064 usable, minus SafetyCheck 1,024 + Blender 2,048 = **20,992 free**. 18,000 is grantable. Note `spikersoft-image-description` holds 20,000 on the same lane, so the two serialize.
- **Image size ~35-45 GB** — weights alone are ~27 GB (`TencentARC/Pixal3D` is 24.04 GB across three 1.3B bf16 cascade checkpoints; plus MoGe-2 1.31 GB, DINOv3 1.21 GB, BiRefNet 0.44 GB).
- **No new .NET worker project.** `SpikerSoft.EventHandlers.ArtPipeProcessor` already does the whole loop (consume → trace context → subprocess → NSFW gate → MinIO → stage events → SignalR relay). A bespoke project would fork the safety gate, artifact rollback, failover chains, DLQ retry and telemetry.
- **WRAP strategy**, mirroring `trellis_mac` (#358): `inference.py` sets `os.environ` before importing torch, exposes no progress callback, and hardcodes `.cuda()` — importing it in-process fights all three.
## Notable finding: upstream's export is lossy
`inference.py` ends with `glb.export(path, extension_webp=True)`. trimesh's exporter does `img.save(f, format="WEBP")` with **no `lossless=True` and no `quality=`**, so Pillow's default applies — **lossy, quality 80** — silently re-compressing every 4096px PBR map. It also marks `EXT_texture_webp` as *required*, so readers lacking the extension must refuse the file.
Converting to PNG afterwards would preserve already-damaged pixels, so we intervene before the damage: our runner neutralises the flag at the trimesh boundary and exports lossless PNG by default. `texture_format=webp` reproduces upstream for A/B comparison.
## Slices
- [ ] PR-1 — artpipe: manifest + WRAP backend + runner + tests
- [ ] PR-2 — artpipe: make the VRAM admission gate tunable (18000 is unadmittable at today's constants)
- [ ] PR-3 — backend: decouple model-queue binding from resident mode + per-model routing allow-list
- [ ] PR-4 — CI: bake the `pixal3d` env image
- [ ] PR-5 — deploy stack + method option + routing cutover
## Licensing
Pixal3D code is MIT; MoGe-2 and BiRefNet are MIT. **Caveat:** upstream pins DINOv3 to the third-party re-upload `camenduru/dinov3-vitl16-pretrain-lvd1689m` rather than Meta's gated `facebook/` repo. Meta's DINOv3 licence still governs those weights — decision taken to keep upstream's mirror as-shipped for now; revisit before commercial distribution.
#834 / #835 are genuinely done in code.models/Pixal3D/artpipe.json (image_to_3d, vram_mb: 18000, py3.10/cu128, ATTN_BACKEND=sdpa, verify_imports for every compiled extension); run_pixal3d.py:64force_png_textures(); src/artpipe/models/pixal3d.pyPixal3DBackend; config.py:130TIMEOUT_PIXAL3D = 2700 and :404 pixal3d in the CUDA allowed_models["modeling"] — with :391 confirming the stated negative constraint holds (stage_defaults["modeling"] = "hunyuan3d", so Pixal3D is not silently the default). Both review fixes landed via PR #35: TRELLIS.2 setup.sh without --basic, and the WebP guarantee promoted to an artifact-level postcondition (pixal3d.py:85_WEBP_EXTENSION, checked :248-262, covered by tests/test_pixal3d_backend.py:392-474). #835's tunables are at config.py:112/:117 with the deployed-env-var-name test at tests/test_config.py:447-468 — which closes the Jaeger:EndPoint-class typo gap rather than just asserting the constants.
#838 is why this epic can't close. The infra stack merged (spikersoft-artpipe-model-pixal3d/docker-stack-gpu.yml, infra PR #149) and the tier-2 build case exists (model-env-images.yml:269), but git grep -in "pixal" origin/master returns zero matches in spikersoft-backend — so:
No pixal3d) case in .gitea/workflows/spikersoft-artpipe-processor.yml; it can't be built even on manual dispatch.
spikersoft-artpipe-model-pixal3d is absent from the deploy loop at :548-553 — the merged stack file is inert.
No Key = "pixal3d" in ArtStudioStageMethodOptions.CreateDefault(), so it can't be selected.
Net: the backend half of the cutover was never written, which is also why every verification checkbox across all three children is unrun — there is currently no path from a user request to the Pixal3D queue.
Remaining, in order: the three backend items on #838 (small, mechanical), then the 4090 runs that #834/#835 are waiting on — artpipe setup --model Pixal3D, a real POST /jobs timing/VRAM run with the overrides exported, and the Blender 5.1 GLB PBR round-trip.
One caveat worth carrying: #615 means an infra deploy can silently ship a stale stack file, so once #838's deploy-loop entry lands, confirm the running service directly rather than trusting a green deploy.
**Epic status roll-up** — audited all three children against `origin/master` / `origin/main`. Staying open: the code is done, but nothing can route to it yet.
| Child | State |
|---|---|
| #834 PR-1: artpipe manifest + WRAP backend + runner | ✅ code complete; 4090 hardware runs unrun |
| #835 PR-2: tunable VRAM admission gate | ✅ code complete; 4090 confirmation unrun |
| #838 PR-5: deploy stack, method option, routing cutover | ⚠️ **PARTIAL — the stack is inert** |
**#834 / #835 are genuinely done in code.** `models/Pixal3D/artpipe.json` (image_to_3d, `vram_mb: 18000`, py3.10/cu128, `ATTN_BACKEND=sdpa`, `verify_imports` for every compiled extension); `run_pixal3d.py:64` `force_png_textures()`; `src/artpipe/models/pixal3d.py` `Pixal3DBackend`; `config.py:130` `TIMEOUT_PIXAL3D = 2700` and `:404` pixal3d in the CUDA `allowed_models["modeling"]` — with `:391` confirming the stated negative constraint holds (`stage_defaults["modeling"] = "hunyuan3d"`, so Pixal3D is not silently the default). Both review fixes landed via PR #35: TRELLIS.2 `setup.sh` without `--basic`, and the WebP guarantee promoted to an artifact-level postcondition (`pixal3d.py:85` `_WEBP_EXTENSION`, checked `:248-262`, covered by `tests/test_pixal3d_backend.py:392-474`). #835's tunables are at `config.py:112`/`:117` with the deployed-env-var-name test at `tests/test_config.py:447-468` — which closes the `Jaeger:EndPoint`-class typo gap rather than just asserting the constants.
**#838 is why this epic can't close.** The infra stack merged (`spikersoft-artpipe-model-pixal3d/docker-stack-gpu.yml`, infra PR #149) and the tier-2 build case exists (`model-env-images.yml:269`), but `git grep -in "pixal" origin/master` returns **zero matches in spikersoft-backend** — so:
1. No `pixal3d)` case in `.gitea/workflows/spikersoft-artpipe-processor.yml`; it can't be built even on manual dispatch.
2. `spikersoft-artpipe-model-pixal3d` is absent from the deploy loop at `:548-553` — **the merged stack file is inert**.
3. No `Key = "pixal3d"` in `ArtStudioStageMethodOptions.CreateDefault()`, so it can't be selected.
Net: the backend half of the cutover was never written, which is also why every verification checkbox across all three children is unrun — there is currently no path from a user request to the Pixal3D queue.
**Remaining, in order:** the three backend items on #838 (small, mechanical), then the 4090 runs that #834/#835 are waiting on — `artpipe setup --model Pixal3D`, a real `POST /jobs` timing/VRAM run with the overrides exported, and the Blender 5.1 GLB PBR round-trip.
One caveat worth carrying: **#615** means an infra deploy can silently ship a stale stack file, so once #838's deploy-loop entry lands, confirm the running service directly rather than trusting a green deploy.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Goal
Add TencentARC/Pixal3D (SIGGRAPH 2026, MIT) as a selectable modeling stage backend. Pixal3D back-projects pixel features into 3D for direct pixel-to-3D correspondence, giving near-reconstruction fidelity with PBR textures — a clear step up from TripoSG / TripoSR / InstantMesh.
Target shape: a deployed service that consumes a RabbitMQ message with W3C trace context, runs weights baked into the container, uploads the GLB to MinIO, and drives the UI over SignalR via the API.
Key constraints (established during planning)
nattenandflex_gemmare CUDA-compiled and the pipeline hardcodes.cuda(). Runs on the4090node (artpipe-gpulane) only — not SERVER's 3070 Ti (8 GB), not Apple Silicon. NoAppleSiliconmethod variant.low_vram). Lane arithmetic: 24,576 MB total, ~24,064 usable, minus SafetyCheck 1,024 + Blender 2,048 = 20,992 free. 18,000 is grantable. Notespikersoft-image-descriptionholds 20,000 on the same lane, so the two serialize.TencentARC/Pixal3Dis 24.04 GB across three 1.3B bf16 cascade checkpoints; plus MoGe-2 1.31 GB, DINOv3 1.21 GB, BiRefNet 0.44 GB).SpikerSoft.EventHandlers.ArtPipeProcessoralready does the whole loop (consume → trace context → subprocess → NSFW gate → MinIO → stage events → SignalR relay). A bespoke project would fork the safety gate, artifact rollback, failover chains, DLQ retry and telemetry.trellis_mac(#358):inference.pysetsos.environbefore importing torch, exposes no progress callback, and hardcodes.cuda()— importing it in-process fights all three.Notable finding: upstream's export is lossy
inference.pyends withglb.export(path, extension_webp=True). trimesh's exporter doesimg.save(f, format="WEBP")with nolossless=Trueand noquality=, so Pillow's default applies — lossy, quality 80 — silently re-compressing every 4096px PBR map. It also marksEXT_texture_webpas required, so readers lacking the extension must refuse the file.Converting to PNG afterwards would preserve already-damaged pixels, so we intervene before the damage: our runner neutralises the flag at the trimesh boundary and exports lossless PNG by default.
texture_format=webpreproduces upstream for A/B comparison.Slices
pixal3denv imageLicensing
Pixal3D code is MIT; MoGe-2 and BiRefNet are MIT. Caveat: upstream pins DINOv3 to the third-party re-upload
camenduru/dinov3-vitl16-pretrain-lvd1689mrather than Meta's gatedfacebook/repo. Meta's DINOv3 licence still governs those weights — decision taken to keep upstream's mirror as-shipped for now; revisit before commercial distribution.Epic status roll-up — audited all three children against
origin/master/origin/main. Staying open: the code is done, but nothing can route to it yet.#834 / #835 are genuinely done in code.
models/Pixal3D/artpipe.json(image_to_3d,vram_mb: 18000, py3.10/cu128,ATTN_BACKEND=sdpa,verify_importsfor every compiled extension);run_pixal3d.py:64force_png_textures();src/artpipe/models/pixal3d.pyPixal3DBackend;config.py:130TIMEOUT_PIXAL3D = 2700and:404pixal3d in the CUDAallowed_models["modeling"]— with:391confirming the stated negative constraint holds (stage_defaults["modeling"] = "hunyuan3d", so Pixal3D is not silently the default). Both review fixes landed via PR #35: TRELLIS.2setup.shwithout--basic, and the WebP guarantee promoted to an artifact-level postcondition (pixal3d.py:85_WEBP_EXTENSION, checked:248-262, covered bytests/test_pixal3d_backend.py:392-474). #835's tunables are atconfig.py:112/:117with the deployed-env-var-name test attests/test_config.py:447-468— which closes theJaeger:EndPoint-class typo gap rather than just asserting the constants.#838 is why this epic can't close. The infra stack merged (
spikersoft-artpipe-model-pixal3d/docker-stack-gpu.yml, infra PR #149) and the tier-2 build case exists (model-env-images.yml:269), butgit grep -in "pixal" origin/masterreturns zero matches in spikersoft-backend — so:pixal3d)case in.gitea/workflows/spikersoft-artpipe-processor.yml; it can't be built even on manual dispatch.spikersoft-artpipe-model-pixal3dis absent from the deploy loop at:548-553— the merged stack file is inert.Key = "pixal3d"inArtStudioStageMethodOptions.CreateDefault(), so it can't be selected.Net: the backend half of the cutover was never written, which is also why every verification checkbox across all three children is unrun — there is currently no path from a user request to the Pixal3D queue.
Remaining, in order: the three backend items on #838 (small, mechanical), then the 4090 runs that #834/#835 are waiting on —
artpipe setup --model Pixal3D, a realPOST /jobstiming/VRAM run with the overrides exported, and the Blender 5.1 GLB PBR round-trip.One caveat worth carrying: #615 means an infra deploy can silently ship a stale stack file, so once #838's deploy-loop entry lands, confirm the running service directly rather than trusting a green deploy.