[ArtStudio M4] Entire stack on the MacBook — native ArtPipeProcessor (no GPU in Docker on macOS), compose.mac, dev-mode GpuCoordinator #359

Closed
opened 2026-07-04 05:28:28 +00:00 by spikerj · 6 comments
Owner

Sub-ticket of epic #346, Mac workstream. Depends on #347/#348 (worker exists) + #357 (Mac profile). Goal: the full SpikerSoft stack — infra, backend, GPU pipeline, Angular — running on one M5 Pro Max (128 GB).

What's already solved: spikersoft-backend/docker-compose.mac.yml runs the platform on Apple Silicon (full sharded Mongo, RabbitMQ, Seq, Jaeger, single-node Redis Stack; ClamAV intentionally skipped). .NET 10 and Angular/Nx run natively on arm64. The gap is the GPU worker.

The architectural constraint: Docker on macOS has no GPU/MPS passthrough — the Linux deployment runs ArtPipeProcessor in a CUDA container (#348), but on a Mac it must run as a native host process talking to the containerized RabbitMQ/Mongo. #348's worker must therefore keep model-dispatch config-driven and host-agnostic (no container-path assumptions).

Scope:

  • ArtPipeProcessor native-run story on macOS: dotnet run (arm64) + art_pipe venvs on the host, config profile pointing at compose.mac endpoints (localhost RabbitMQ/Mongo/Seq/Jaeger); document in the worker README; note in #348 that paths/venv locations must be configurable, not baked into the Dockerfile
  • GpuCoordinator dev mode: either bypass leases on macOS (config flag; single-user machine) or run the coordinator natively with TotalGpuVramMB set from unified memory — pick one, document it (bypass recommended: the lease broker exists to arbitrate a shared card, which doesn't apply to a dev laptop)
  • Keycloak locally: add to the mac compose story (quay.io Keycloak images are multi-arch) or document pointing at ids.spikersoft.com with the existing spikersoft realm — decide + document (e2e realm import script if local)
  • One-command bring-up: script/Make target that starts compose.mac services, the API, the worker, and pnpm start for Angular; smoke-check endpoints (/healthz, hub negotiate)
  • End-to-end proof: submit from the local Angular Art Studio (#351) → native worker runs the macos-mps profile (#357) → GLB lands in local GridFS → SignalR progress renders — the entire student flow with zero non-Mac dependencies
  • Fold the instructions into the repo docs/skills so both Cursor and Claude pick them up (mirror convention)

Note: this also becomes the demo rig — the whole platform plus 3D generation on one laptop is exactly the story to show schools (no NVIDIA server required for pilots; a Mac Studio could even be a school's on-prem option later).

Acceptance: on a clean-ish M5 Mac: one documented command sequence brings up the full stack; a prompt submitted in the local browser produces a rigged animated GLB end-to-end with no connection to SERVER; teardown is clean.

Sub-ticket of epic #346, Mac workstream. Depends on #347/#348 (worker exists) + #357 (Mac profile). Goal: the full SpikerSoft stack — infra, backend, GPU pipeline, Angular — running on one M5 Pro Max (128 GB). **What's already solved:** `spikersoft-backend/docker-compose.mac.yml` runs the platform on Apple Silicon (full sharded Mongo, RabbitMQ, Seq, Jaeger, single-node Redis Stack; ClamAV intentionally skipped). .NET 10 and Angular/Nx run natively on arm64. The gap is the GPU worker. **The architectural constraint:** Docker on macOS has **no GPU/MPS passthrough** — the Linux deployment runs `ArtPipeProcessor` in a CUDA container (#348), but on a Mac it must run as a **native host process** talking to the containerized RabbitMQ/Mongo. #348's worker must therefore keep model-dispatch config-driven and host-agnostic (no container-path assumptions). **Scope:** - [ ] `ArtPipeProcessor` native-run story on macOS: `dotnet run` (arm64) + art_pipe venvs on the host, config profile pointing at compose.mac endpoints (localhost RabbitMQ/Mongo/Seq/Jaeger); document in the worker README; note in #348 that paths/venv locations must be configurable, not baked into the Dockerfile - [ ] GpuCoordinator dev mode: either bypass leases on macOS (config flag; single-user machine) or run the coordinator natively with `TotalGpuVramMB` set from unified memory — pick one, document it (bypass recommended: the lease broker exists to arbitrate a shared card, which doesn't apply to a dev laptop) - [ ] Keycloak locally: add to the mac compose story (quay.io Keycloak images are multi-arch) or document pointing at ids.spikersoft.com with the existing spikersoft realm — decide + document (e2e realm import script if local) - [ ] One-command bring-up: script/Make target that starts compose.mac services, the API, the worker, and `pnpm start` for Angular; smoke-check endpoints (`/healthz`, hub negotiate) - [ ] End-to-end proof: submit from the local Angular Art Studio (#351) → native worker runs the macos-mps profile (#357) → GLB lands in local GridFS → SignalR progress renders — the entire student flow with zero non-Mac dependencies - [ ] Fold the instructions into the repo docs/skills so both Cursor and Claude pick them up (mirror convention) **Note:** this also becomes the demo rig — the whole platform plus 3D generation on one laptop is exactly the story to show schools (no NVIDIA server required for pilots; a Mac Studio could even be a school's on-prem option later). **Acceptance:** on a clean-ish M5 Mac: one documented command sequence brings up the full stack; a prompt submitted in the local browser produces a rigged animated GLB end-to-end with no connection to SERVER; teardown is clean.
spikerj added the enhancement label 2026-07-04 05:28:28 +00:00
Author
Owner

Scope clarification per epic topology decision: the Mac is a development/demo environment, not a deployment target. Production is CUDA Swarm services (#348 + stage services). This ticket's deliverable is the dev loop: native ArtPipeProcessor (all stage queues in one process, ArtPipe:Stages=all, GpuCoordinator bypass), compose.mac platform services, one-command bring-up, end-to-end student flow locally. Parity requirement: the native process and the swarm services run the same binary and config schema — only profile (macos-mps vs linux-cuda) and stage-set differ, so behavior verified on the Mac transfers to prod.

Scope clarification per epic topology decision: the Mac is a **development/demo environment, not a deployment target**. Production is CUDA Swarm services (#348 + stage services). This ticket's deliverable is the dev loop: native `ArtPipeProcessor` (all stage queues in one process, `ArtPipe:Stages=all`, GpuCoordinator bypass), compose.mac platform services, one-command bring-up, end-to-end student flow locally. Parity requirement: the native process and the swarm services run the *same binary and config schema* — only profile (macos-mps vs linux-cuda) and stage-set differ, so behavior verified on the Mac transfers to prod.
Author
Owner

Slice 1 acceptance evidence: the platform chain ran end-to-end on the M5. Submit (POST api/artstudio/submit, test auth) → RabbitMQ → native ArtPipeProcessor (BypassGpuLease) → real MPS generations via art_pipe worker protocol → GridFS → download endpoint verified byte-for-byte:

Stage Model Result Time Artifact
concept sdxl_lightning 10.4 s 1024² PNG (visually verified red dragon figurine)
modeling triposr 9.1 s glTF v2 GLB, 41,420 verts (trimesh-verified)
texturing sd_turbo_tex 46.0 s (incl. first-use weight download) texture PNG
export designed clean failure (No StageSettings for 'export'), zero partial artifacts, failure events published

Runbook committed: spikersoft-backend/docs/mac-local-stack.md (PR #64) — every command + config override, including the traps (base RabbitMQ: outranks Development Rabbit:; redis port remap; worker's hardcoded :8080).

Production bug found and fixed (PR #64): NotificationsHub only joined user-{preferred_username} while the #349 relay targets user-{sub} — no client would ever have received lifecycle events anywhere. Fix verified live with a connected SignalR client.

Remaining for this ticket: one-command bring-up script, Keycloak-proper local auth (this run used TestAuthentication), export/enrichment stages (Blender — P3/#349 scope), Angular-in-the-loop run. art_pipe side gained a TripoSR output_format=glb param (commit f6b4387 on the device branch).

**Slice 1 acceptance evidence: the platform chain ran end-to-end on the M5.** Submit (`POST api/artstudio/submit`, test auth) → RabbitMQ → native ArtPipeProcessor (`BypassGpuLease`) → real MPS generations via art_pipe worker protocol → GridFS → download endpoint verified byte-for-byte: | Stage | Model | Result | Time | Artifact | |---|---|---|---|---| | concept | sdxl_lightning | ✅ | 10.4 s | 1024² PNG (visually verified red dragon figurine) | | modeling | triposr | ✅ | 9.1 s | glTF v2 GLB, 41,420 verts (trimesh-verified) | | texturing | sd_turbo_tex | ✅ | 46.0 s (incl. first-use weight download) | texture PNG | | export | — | designed clean failure (`No StageSettings for 'export'`), zero partial artifacts, failure events published | — | — | Runbook committed: `spikersoft-backend/docs/mac-local-stack.md` (PR #64) — every command + config override, including the traps (base `RabbitMQ:` outranks Development `Rabbit:`; redis port remap; worker's hardcoded :8080). **Production bug found and fixed** (PR #64): `NotificationsHub` only joined `user-{preferred_username}` while the #349 relay targets `user-{sub}` — no client would ever have received lifecycle events anywhere. Fix verified live with a connected SignalR client. Remaining for this ticket: one-command bring-up script, Keycloak-proper local auth (this run used TestAuthentication), export/enrichment stages (Blender — P3/#349 scope), Angular-in-the-loop run. art_pipe side gained a TripoSR `output_format=glb` param (commit `f6b4387` on the device branch).
Author
Owner

The full Prop plan now completes end-to-end on the M5 in ~40 s — concept 13.2 s → modeling 9.5 s → texturing 5.3 s → export 2.7 s (Blender 5.1.2 via brew, Cycles confirmed on METAL — the #356 fix working) → enrichment 8.7 s. Final artifacts in GridFS: 16.8 MB game-ready GLB with texture applied, real rendered thumbnail, CV metrics report.

How: art_pipe gained a blender pseudo-backend (models/Blender/artpipe.json + models/blender_stage.py, commit e9f116d on the device branch) that exposes the existing headless export/enrichment machinery through the standard worker protocol — nothing reimplemented. Backend side is PR #66 (StageSettings for both stages, PBR filename param mapping, mesh walk-back when the previous stage emitted only images).

Remaining on this ticket: one-command bring-up script, local Keycloak (runs still use TestAuthentication), Angular-in-the-loop run. Deploy side: the artpipe-modeling swarm deploy failure is fixed in spikersoft-infrastructure PR #6 (SERVER's stack deploy rejects reservations.devices; env-based GPU access per the swarm's working pattern).

**The full Prop plan now completes end-to-end on the M5 in ~40 s** — concept 13.2 s → modeling 9.5 s → texturing 5.3 s → export 2.7 s (Blender 5.1.2 via brew, Cycles confirmed on METAL — the #356 fix working) → enrichment 8.7 s. Final artifacts in GridFS: 16.8 MB game-ready GLB with texture applied, real rendered thumbnail, CV metrics report. How: art_pipe gained a `blender` pseudo-backend (`models/Blender/artpipe.json` + `models/blender_stage.py`, commit `e9f116d` on the device branch) that exposes the existing headless export/enrichment machinery through the standard worker protocol — nothing reimplemented. Backend side is PR [#66](https://git.spikersoft.com/spikerj/spikersoft-backend/pulls/66) (StageSettings for both stages, PBR filename param mapping, mesh walk-back when the previous stage emitted only images). Remaining on this ticket: one-command bring-up script, local Keycloak (runs still use TestAuthentication), Angular-in-the-loop run. Deploy side: the `artpipe-modeling` swarm deploy failure is fixed in spikersoft-infrastructure PR #6 (SERVER's stack deploy rejects `reservations.devices`; env-based GPU access per the swarm's working pattern).
Author
Owner

One-command bring-up merged (backend PR #72): scripts/mac-dev-up.sh / mac-dev-down.sh / mac-dev-smoke.sh, triple-validated live (smoke 72 s cold / 51 s warm / 45 s independent re-run; idempotent re-up 4–8 s). The Mac dev/demo rig is now one command. Remaining on this ticket: local Keycloak (test auth stands in today), Angular-in-the-loop run, SignalR client verification — none block the weekend push.

One-command bring-up merged (backend PR #72): `scripts/mac-dev-up.sh` / `mac-dev-down.sh` / `mac-dev-smoke.sh`, triple-validated live (smoke 72 s cold / 51 s warm / 45 s independent re-run; idempotent re-up 4–8 s). The Mac dev/demo rig is now one command. Remaining on this ticket: local Keycloak (test auth stands in today), Angular-in-the-loop run, SignalR client verification — none block the weekend push.
Author
Owner

Angular-in-the-loop: done — green end-to-end browser run in 51.7 s. Real Keycloak login (e2e.student) → Art Studio → "a tiny wooden rowboat, game prop" (Prop) → stage timeline advanced live over SignalR (wss://localhost:5291/hubs/notifications) through concept → modeling → texturing → export → enrichment (~43 s pipeline) → exported GLB rendered in the three.js viewer → My Assets card showed the real enrichment thumbnail. Zero unexpected console errors.

Wiring: TestAuthenticationHandler ignores the Authorization header entirely, so the SPA keeps live ids.spikersoft.com auth while the API runs test-auth — real login + test API coexist with no Keycloak-local work. Angular side on branch feature/mac-browser-validation (commit 6b3d947): environment.development.tshttps://localhost:5291, dev-config build served over https://localhost:4200 (checked-in cert; origin already in CORS + Keycloak allowlists). Repeatable spec: e2e/playwright/authenticated-artstudio-live.spec.ts; screenshots committed under e2e/test-results/artstudio-live/. Runbook: backend docs/mac-local-stack.md §6 (branch feature/mac-browser-validation, commit 7557dae).

Bugs flushed out (all pre-existing, none Mac-specific): #369 (legacy art-asset docs 500 the whole My Assets list — EF required-property gap), #370 (viewer never rendered anywhere — static ViewChild inside *transloco; fixed on the branch — plus no student nav entry to Art Studio), and new evidence on #345 (NG0200 cycle pinned + fix in hand, but session-restore boot still blanks — demo rule: log in fresh, don't hard-refresh).

Remaining on this ticket: local Keycloak story (test auth stands in today).

**Angular-in-the-loop: done — green end-to-end browser run in 51.7 s.** Real Keycloak login (e2e.student) → Art Studio → "a tiny wooden rowboat, game prop" (Prop) → stage timeline advanced **live over SignalR** (`wss://localhost:5291/hubs/notifications`) through concept → modeling → texturing → export → enrichment (~43 s pipeline) → exported GLB rendered in the three.js viewer → My Assets card showed the real enrichment thumbnail. Zero unexpected console errors. **Wiring:** `TestAuthenticationHandler` ignores the Authorization header entirely, so the SPA keeps live `ids.spikersoft.com` auth while the API runs test-auth — real login + test API coexist with no Keycloak-local work. Angular side on branch `feature/mac-browser-validation` (commit `6b3d947`): `environment.development.ts` → `https://localhost:5291`, dev-config build served over `https://localhost:4200` (checked-in cert; origin already in CORS + Keycloak allowlists). Repeatable spec: `e2e/playwright/authenticated-artstudio-live.spec.ts`; screenshots committed under `e2e/test-results/artstudio-live/`. Runbook: backend `docs/mac-local-stack.md` §6 (branch `feature/mac-browser-validation`, commit `7557dae`). **Bugs flushed out (all pre-existing, none Mac-specific):** #369 (legacy art-asset docs 500 the whole My Assets list — EF required-property gap), #370 (viewer never rendered anywhere — static ViewChild inside `*transloco`; fixed on the branch — plus no student nav entry to Art Studio), and new evidence on #345 (NG0200 cycle pinned + fix in hand, but session-restore boot still blanks — demo rule: log in fresh, don't hard-refresh). Remaining on this ticket: local Keycloak story (test auth stands in today).
Author
Owner

All deliverables merged: platform-on-Mac via compose.mac (pre-existing), native ArtPipeProcessor with host-agnostic config (#348/#62), one-command mac-dev-up/down/smoke (#72), and the browser-in-the-loop validation (docs §6, PR #75-era branches all merged) — full click-through in 51.7 s with live SignalR verified in-page. Acceptance met with one documented deviation: identity uses live ids.spikersoft.com + API test-auth coexistence rather than a local Keycloak (validated as the working dev wiring; a local-Keycloak variant remains a nicety, noted in the runbook, not scoped). Known constraint carried in the runbook: fresh login only — hard-refresh-while-logged-in is #345, tracked separately. Closing — the M-workstream (M1–M4) is now fully closed.

All deliverables merged: platform-on-Mac via compose.mac (pre-existing), native ArtPipeProcessor with host-agnostic config (#348/#62), one-command `mac-dev-up/down/smoke` (#72), and the browser-in-the-loop validation (docs §6, PR #75-era branches all merged) — full click-through in 51.7 s with live SignalR verified in-page. Acceptance met with one documented deviation: identity uses live ids.spikersoft.com + API test-auth coexistence rather than a local Keycloak (validated as the working dev wiring; a local-Keycloak variant remains a nicety, noted in the runbook, not scoped). Known constraint carried in the runbook: fresh login only — hard-refresh-while-logged-in is #345, tracked separately. Closing — the M-workstream (M1–M4) is now fully closed.
Sign in to join this conversation.