Skip to content

studio: Wasserman Slate AI Studio, an AI film studio on TensorFold (Mac + DGX Spark) - #941

Open
drowzeys wants to merge 2 commits into
ashhart:mainfrom
drowzeys:upstream/slate-studio
Open

drowzeys wants to merge 2 commits into
ashhart:mainfrom
drowzeys:upstream/slate-studio

Conversation

@drowzeys

Copy link
Copy Markdown
Contributor

Thank you, Ash, for the invitation to send the Studio for review, and for the runtime it is built on.

This is a complete AI film studio running on TensorFold, on both of its platforms. The same web app plans a film, makes its pictures and renders its clips with sound on an Apple-silicon Mac (TensorFold's Zig + Metal runtime) and on an NVIDIA DGX Spark (TensorFold's CUDA engine), and spreads a film's clips over several Sparks. Nothing like it has been built on TensorFold before: it is TensorFold carrying video, sound and pictures, not only text, through one queue a person can use.

Measured on a Mac Studio M5 Ultra (256 GB) and on DGX Spark (GB10, 128 GB), models loaded, one run each, receipts from studio/README.md:

5 s clip, 864x480, FastH3 8 passes First version (before TensorFold's engines) Now, on TensorFold
Mac, text to video not offered 66 s (Zig + Metal engine, 5.33 s a pass)
Mac, image then video 74 s (MiniMax H3 Turbo, 3 passes) 79 s (image 6 s, video 73 s)
DGX Spark, text to video 118 to 121 s (ComfyUI's bf16 blocks) 73 s (TensorFold CUDA engine, int8)
DGX Spark, one denoising pass 10.5 s 7.1 s
DGX Spark, video decode 26 s 14 s (8-bit decoder)
One 1344x768 picture, Qwen-Image-2.1-Turbo 9.2 s (Mac), 18 to 21 s (Spark) 8.9 s (Mac, peak 15 GiB against 28), 9.5 s (Spark)
  • Parity. On each machine the same seed gives a bit-identical clip on the 1.0.4-based engine as on the 1.0.2-based one, at the same speed. The Qwen-Image port against mflux bfloat16 (the reference): TensorFold bfloat16 32.7 dB, int8 30.4 dB, the same picture with different fine detail.
  • Memory. The DGX Spark memory guard (spark/memory_guard.py) refuses a clip before it is queued and again before anything loads, from measured peaks (1376x768, 124 frames on the int8 engine: 62.2 GB at its lowest point on a 124.5 GB Spark), and watches the server twenty times a second against a floor of a tenth of the machine. With it, renders that used to push the GPU driver into NV_ERR_NO_MEMORY kept 8 GB or more free in the same time (146 s against 145 s).
  • Several Sparks. Eight 56-frame 864x480 clips through the app's own API: 263 s on one Spark, 130 s on two (2.03x), 98 s on three (2.70x), 69 s on four (3.81x), 65 s on five (4.05x).
  • Tests. 103 pass on a DGX Spark and 96 on the Mac; every failure is the same environment one as on the Studio's own 2.8.0 tree (below).
  • Lean. studio/ follows the lean rules: no source file over 600 lines, single-line comments and docstrings of at most 120 columns (details below).

Why it matters for TensorFold: the Studio is the first end-user application in which TensorFold runs video with sound (MiniMax H3 / FastH3), pictures (Qwen-Image-2.1) and, in the stacked follow-ups, speech (Fish Audio Drama 3), on the same runtime as its language models. It exercises the Zig + Metal runtime and the CUDA engine on workloads of 15K to 75K rows a pass, and it gives TensorFold's desktop app a creative workspace beside Code and Chat.

This is the first of a stacked series; the next three add one feature each:

  1. this PR: the Studio (upstream/slate-studio);
  2. Motion Previs tab (upstream/studio-previs), stacked on this one;
  3. FLUX3-K picture models (upstream/studio-flux3k), stacked on 2;
  4. Voice tab, Fish Audio Drama 3 (upstream/studio-voice), stacked on 3.

Commits of this PR only: main...drowzeys:TensorFold:upstream/slate-studio (two commits on main at 46a08c18).

What it adds

Everything is under a new top-level folder, studio/; nothing outside it changes.

  • studio/app/, the web app (FastAPI, plain JS pages, bash scripts/app.sh, http://127.0.0.1:7870). The old 1,897-line server.py is now modules with one job each:
    • server.py (wiring: the app, its routers, the hub), settings.py (folders, presets, qualities, measured times), prompt_text.py (MiniMax H3's prompt forms), engines.py (what this machine can render and how long it takes), picture_models.py (the picture-model registry: Qwen 2.1 Turbo), job_build.py (a job's command and environment, checked before it is queued), job_run.py (the queue's worker), job_state.py, api_jobs.py, api_files.py.
    • Create wizard, Production board, Editor, Library, Tutorial; the Wasserman Slate AI Studio workflow (slate.py, slate_brain.py, static/slate.js: a port of Sam Wasserman's Slate, with its data files unchanged under static/slate/); the Keys and Wasserman LoRA Hub (loras.py, lora_*.py); Library actions (actions.py); picture edits (picture_edit.py); the QA gate (qa.py, Sam Wasserman's Sharp Spec) and shot check (shot.py); what a prompt forbids by name (negation.py); clip lengths (lengths.py); clearing and the trash (clear.py); another app's clips (remote.py).
    • Multi-Spark (sparks.py with sparks_wire.py, sparks_rules.py, sparks_workers.py, sparks_queue.py, sparks_clip.py): one clip per Spark over ssh tunnels the app opens itself; a worker listens only on its own loopback, runs this Spark's code (compared by hash) and its own memory guard.
    • The page: static/index.html (styles) and page.js, hub.js, create.js, board.js, tutorial.js, queue.js beside the tab files; the old 1,226-line inline script was split in its load order, with no load-time call into a later file (checked with acorn).
  • studio/scripts/: the render scripts (image.sh, edit.sh, video.sh, studio.sh, scout.sh, fast.sh, turnaround.sh), the native engine build, the one-click Mac app, LoRA tools.
  • studio/spark/: the DGX Spark side. Every render goes through the ComfyUI server of comfy.sh with the memory guard beside it; FastH3 runs on TensorFold's CUDA H3 engine (tensorfold_h3, TensorFoldH3Blocks), Qwen-Image-2.1 through ComfyUI. spark_generate.py, spark_edit.py and spark_workflows.py were split into spark_comfy.py, spark_media.py, spark_finish.py, workflow_comfy.py and workflow_frames.py.
  • studio/*.py: the Mac renderers on TensorFold's families: h3_generate.py (with h3_text.py, h3_media.py, h3_condition.py, h3_decode.py), zig_fasth3.py (drives the Zig + Metal FastH3 runtime), qwen_image_generate.py, qwen_image_edit.py, edit_plan.py, fasth3_q8.py.
  • Install: oneshot-setup.sh (Mac, hands over to oneshot-setup-spark.sh on Linux), oneshot-essential-bundle.sh (the LoRA bundle).
  • studio/tests/: 18 test files, each runnable alone (python3 -m pytest tests/test_x.py); no GPU, no weights, no network.
  • Docs: README.md (receipts), TUTORIAL.md, AGENTS.md, docs/multi-spark.md, CREDITS.md, NOTICE, THIRD_PARTY_NOTICES.md (the Library actions' node packs).

Qwen-Image-2.1-Turbo is the default and, in this PR, the only picture model: through ComfyUI on a Spark and the existing Mac path (qwen_image_generate.py on TensorFold's Qwen-Image family). There is no further Studio-side Qwen work after this PR; the engine family is a separate contribution.

How it relates to the Zig engine and to app/

The engines. The Studio does not add an engine. It calls TensorFold through three doors, all pinned in the install scripts:

  • Mac, FastH3: zig_fasth3.py runs tf-h3-dit, an H3 family on TensorFold 1.0's Zig + Metal runtime (fork branch h3-1.0.4), for every transformer pass; prompt encoding, the first frame and the decoders are TensorFold's Python H3 family on MLX.
  • Mac, pictures and MiniMax H3: TensorFold 0.6.5's Python H3 and Qwen-Image families (fork branch studio, a2068c03).
  • DGX Spark: TensorFold's CUDA H3 engine as libtf_h3.so (branch h3-cuda-lora), loaded by a ComfyUI node; the LoRA is merged into the int8 engine with tf_h3_set_weight.

As the H3 and Qwen-Image families land on main (separate pull requests), the install scripts switch their pins from the fork to main's releases; the app does not change.

The desktop app. app/ is the Electron + React coding app with a local engine, a loopback API and a headless orchestrator. The Studio is a separate Python web app today, and there are three steps to bring them together, smallest first:

  1. Launch and embed. A Studio entry in the app's Models or Workers area starts studio/scripts/app.sh on loopback (the Studio already binds 127.0.0.1 by default and opens nothing else) and shows it in a webview, the way the app shows its local API. The Studio's own state (/api/state) says what is installed and what is missing, so the app can offer the install (oneshot-setup.sh) with its sizes and licences first.
  2. Shared authentication and lifecycle. Put the Studio behind the same loopback token as the app's API and the headless orchestrator, and let the app stop it, as it does its engine (the Studio has no login of its own; it is for one person's machine).
  3. Native pages. Port the tabs to React one at a time against the Studio's JSON API (/api/jobs, /api/library, /api/slate/*), keeping the Python queue as the render back end until the renders run through native engine commands.

I am happy to do step 1 as a follow-up once you say which of these you want.

Receipts (from studio/README.md)

  • Mac Studio M5 Ultra, 256 GB, native engine, FastH3: 864x480, 5 s: 43 s at 4 passes, 66 s at 8; the MLX engine 51 s and 78 s. A pass 5.33 s native against 7.12 s on MLX.
  • DGX Spark (GB10, 128 GB), TensorFold CUDA engine, int8, 5 s text to video: 864x480 73 s, 1280x720 183 s, 1344x768 about 195 s. 10 s at 1280x720 (243 frames) 444 s, a pass 43.7 s; a 30 s chained 864x480 clip 611 s.
  • Qwen-Image-2.1, 1344x768, Mac Studio M5 Ultra: mflux bf16 40 steps 0.73 s a step, 40.7 s; TensorFold bf16 0.78 s, 34.3 s; TensorFold int8 0.48 s, 24.3 s; Qwen-Image-2.1-Turbo, int8, 8 steps 3.9 s denoise, 8.9 s whole run.
  • Memory guard, DGX Spark, 124.5 GB: lowest point of memory taken, int8 engine: 57.6 GB at 864x480 x 124 frames; 62.2 GB at 1376x768 x 124; 67.1 GB at 192 frames and 69.0 to 70.9 GB at 243. With a LoRA merged into the int8 engine within 0.6 GB of those at every size.
  • Multi-Spark: the table above (1 to 5 Sparks, 263 s to 65 s for eight clips). One clip split across Sparks is measured outside the app (1.40x, 1.73x, 1.77x on two to four) and is not built in.

Tests

Each test file runs in its own process (as the Studio's tests are written), with a fresh STUDIO_HOME, on a DGX Spark worker (Linux, no ffmpeg or node on that test host) and on a Mac Studio M5 Ultra (ffmpeg and node from Homebrew):

Passed Failed Skipped
DGX Spark (GB10, 128 GB) 103 3 0
Mac Studio M5 Ultra, 256 GB 96 10 0

The failures are the same, test for test, as on the Studio's own 2.8.0 tree before the split (Spark 125 / 3, Mac 113 / 15 there, with the features of the later PRs in):

  • both machines: test_picture_edit::test_further_pictures_and_the_size_of_the_result needs Qwen-Image-2.1's ComfyUI files installed on the test host;
  • DGX Spark: test_actions::test_mend_paints_over_dull_green_residue and test_sparks::test_a_clip_that_opens_on_another_waits_for_it need ffmpeg, which that test host lacks (both pass where ffmpeg is installed);
  • Mac: seven test_sparks tests and test_qa_remote::test_the_head_runs_the_gate_on_a_workers_clip exercise the Linux-only multi-Spark path; test_lastframe::test_build_carries_the_last_frame compares a macOS /private/var temp path with its /var link.
  • test_slate_page runs the page code in node and returns early where node is missing, so on the Spark host its 4 passes are vacuous; on the Mac they ran.

The split itself is covered: every re-commented Python file was checked by AST against the original (docstrings aside), and code moved between modules is unchanged apart from imports, calls that now name their module (engines.media_tools, picture_models.image_model_env) and the queue thread starting in server.py. Tests that replaced a function of server now replace it in the module it moved to.

Lean check

  • python3 tools/zig/lean_check.py scans zig/ and tools/zig/ only: 1941 files, 517 problems, before and after (the baseline at 46a08c18; this PR adds nothing there).
  • The same rules run over studio/ (a wrapper that points the checker's problems() at the folder): from 846 findings in the 2.8.0 tree to 11 here, all 11 the checker reading a /* glob in shell code as a block comment (oneshot-setup.sh 8, scripts/video.sh 2, spark/build-engine.sh 1). The page files, under the same rules with // comments: 15 findings, all CSS block comments in index.html (CSS has no other kind) and accept: "image/*" strings.

Smoke run of this tree (the full stacked series), DGX Spark

The branch's studio/ run as a second app beside an installed Studio, real weights, one job at a time:

  • FLUX3-K Fast picture from a prompt: done in 36 s.
  • Text to picture to a 5 s video with sound (FLUX3-K Fast, FastH3 8 passes, 864x480): done in 102 s, the same as the 2.8.0 code (96 to 102 s); the app's shot check passed (first frame held, likeness 0.998, no cut).
  • The page in headless Chromium after the script split: every tab and the queue render, no console errors.

Not tested

  • A fresh install with downloads from nothing (the models were already on the test machines), and the install scripts after their comments were condensed (they were checked with bash -n and their code lines are unchanged).
  • A full render through the split app on a Mac (the DGX Spark smoke run below covers the Spark).
  • Any GPU other than the GB10; Macs other than the M5 Ultra (64 to 128 GB Macs and M1 to M4 are untested); six to eight Sparks; clips longer than 15 s in one piece and chains longer than 30 s.

What comes next

  • Switch the engine pins from the fork to main as the H3 and Qwen-Image families land.
  • The app/ integration above, starting with launch and embed.
  • Two environment-bound tests (test_picture_edit, the multi-Spark wait) can stand in for their model files and ffmpeg, so they pass on any host.

Licences

  • The Studio's code: Apache-2.0 (studio/LICENSE), the same as TensorFold.
  • No model weights ship with the Studio. Each model is downloaded by the install scripts under its own licence: Qwen-Image-2.1 under the Qwen Research License (non-commercial); MiniMax H3, FastH3 and the 2x decoder under the MiniMax H3 Community License (territory limits). NOTICE says so for each.
  • Sam Wasserman's Slate (Apache-2.0): its data files are kept unchanged with its NOTICE at studio/app/static/slate/NOTICE. The Library actions' ComfyUI node packs are installed from their authors at pinned commits, never copied (THIRD_PARTY_NOTICES.md). Every author is credited in CREDITS.md.
  • The Studio keeps its name, Wasserman Slate AI Studio, powered by TensorFold.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PfjmufXm4L7qsVNFyRw9ZK

@drowzeys

Copy link
Copy Markdown
Contributor Author

@ashhart Ha, guilty. We kept going because nobody had shown video, speech or pictures on TensorFold yet, and we wanted them working before we said anything. Everything is here now as pull requests, each listing what was tested and what wasn't.

Demo: the Studio on one Mac (M5 Ultra, 256 GB)

▶ Demo video, 60 s. It was recorded live today: the Studio app at work, then two clips with sound, then the times and memory measured during those renders.

Clip Picture Video (FastH3, 8 passes) Total Peak memory
864x480, 5 s, spoken line FLUX3-K Fast, 11 s 70 s (5.93 s a pass) 81 s 29.0 GB
1344x768, 5 s, music Qwen-Image 2.1 Turbo, 11 s 201 s (20.1 s a pass) 212 s 33.0 GB
  • Memory is the render process's macOS footprint, GPU allocations included, sampled every second.
  • The peaks are brief, at the handover from the picture model. FastH3 itself holds at 23 GB at 864x480 and 27 GB at 1344x768.
  • Engine: these renders use our int8 Zig engine.

864x480 clip
1344x768 clip
Memory during both renders

H3 on the Mac in bf16 (what we're proposing upstream)

On DGX Spark

Drama 3

#939 (CUDA) and #956 (Zig + Metal):

Frames/s First audio Memory
CUDA, GB10 34 0.24 s 37 GiB
Metal, M5 Ultra 84 0.15 s 9.5 GB, experts mapped from disk (MLX port: 68 GB)
  • Accuracy: teacher-forced against the PyTorch reference: 98.5–98.8 % on the clone prompt, every differing pick listed with its margin.
  • Codec: 113–115 dB SNR.

Very happy to compare notes with netrunner before we duplicate anything. Our open gaps are:

  • running Drama 3 on the lane core (a frame is 11 columns);
  • chunk-invariant prompts;
  • gates 1–4 for the remaining kernels.

Point us at their branch.

Also

Every branch passes CI on our fork, and the Drama 3 PRs have the review fixes from @cwschroeder. Runs here are waiting on your Actions approval. Happy to do a live session whenever suits.

🤖 Generated with Claude Code

@ashhart

ashhart commented Oct 16, 2026

Copy link
Copy Markdown
Owner

@drowzeys thank you, this is a huge piece of work: a film studio that plans a film, makes its pictures, renders clips with sound and casts voices, on Macs and Sparks, all on TensorFold.

Here's what we're going to do with it. Rather than add a second app under studio/, we'll lift its pieces into the TensorFold macOS app, so everybody gets the same app. The engine side comes first, as native families (MiniMax H3, Qwen-Image and Drama 3, from your other PRs), and the app features follow on top of them. Your commits keep your name where they carry over, and anything we rewrite carries a Co-authored-by line for you. We'll link each piece here as it lands, and keep #941 to #944 open until then.

Posted by Ash's AI Assistant Synthena

@drowzeys

Copy link
Copy Markdown
Contributor Author

Thank you, Ash. We'd be honored for the Studio to become part of the TensorFold macOS app, and lifting it in piece by piece, engines first, is the right order.

A few things that may help along the way:

Thank you again for the invitation, and for TensorFold itself.

@ashhart ashhart mentioned this pull request Oct 17, 2026
A web app (FastAPI and plain JS pages) that queues picture and video jobs on a Mac (Metal) or a DGX Spark (CUDA):
Qwen 2.1 Turbo pictures and edits, MiniMax H3 and FastH3 clips with sound, reference pictures, videos and audio,
long chains, the Production board and Editor, the Wasserman Slate workflow (a port of Sam Wasserman's Slate), the
LoRA hub, Library actions, the QA gate and shot check, the memory guard and multi-Spark rendering. The render
scripts under scripts/ and spark/ drive TensorFold's H3 and Qwen-Image families; tests run without a GPU.
Install and use, measured receipts, the agent rules, the credits of every author the Studio builds on, and the
licence notices; no model weights ship with the Studio.
@drowzeys
drowzeys force-pushed the upstream/slate-studio branch from f7ea15a to b195525 Compare October 17, 2026 01:30
@drowzeys

Copy link
Copy Markdown
Contributor Author

Rebased on main 6ee828c2; head is now b1955250. The pull request's diff against main is patch-identical to before (main's new commits only touch docs).

@drowzeys

Copy link
Copy Markdown
Contributor Author

Music 3 is up as #982, as offered above: the native CUDA engine on the Zig runtime. The MLX engine stays on our fork (branch music3-mlx) as a reference, since the Python engine is frozen.

@cwschroeder

Copy link
Copy Markdown
Contributor

@drowzeys, netrunner is @plotarmordev. The branches to compare are #920 (feat/drama3-cuda), #921 (feat/drama3-streaming) and #927 (perf/drama3-pdl), all in plotarmordev/TensorFold. Their current #920 and #927 heads are 65cd2568 and d9869145. This should give you the concrete branches for the common frame scheduler and native speech adapter discussion Ash outlined on #956.

@plotarmordev

Copy link
Copy Markdown
Contributor

@drowzeys, thanks for asking to compare first, and @cwschroeder, thanks for the pointer. Here's where our side stands, so the combined family Ash described can take the better half of each.

Happy to run any of your gates on GB10, or ours on your branches.

@ashhart

ashhart commented Oct 17, 2026

Copy link
Copy Markdown
Owner

Thanks @plotarmordev for the side-by-side, and @drowzeys for asking to compare first. We're adopting the consent rule: in the merged Drama family and the app's voice workspace, a voice file must declare its source (kind plus a note), and the server refuses one that doesn't. The rest stands as planned: one Drama family taking the better half of each, @drowzeys' FP32 codec (113–115 dB against the reference), and #923–#926 re-proved on the merged baseline. The app's media workspace is being built in the app itself, and #941–#944 close as its stages land, with credit for the ideas.

Posted by Ash's AI Assistant Synthena

@ashhart

ashhart commented Oct 21, 2026

Copy link
Copy Markdown
Owner

Thank you for Studio. We want the TensorFold app itself to do what Studio does, starting with video generation, so rather than land studio/ as an app of its own we're folding its pipelines into the TensorFold app under the app's design. Your code and commits carry over with your authorship, and we'll keep Sam Wasserman's NOTICE files and credits as the Motion Previs and Slate licence asks. We're mapping the Python pieces against the app and the engine first (MiniMax H3 from #958 included) and will link each piece here as it lands; we may ask you about parts of it along the way. We'll keep #941 to #944 open until then.

- Synthema, Ash's AI Assistant.

@ashhart

ashhart commented Oct 21, 2026 •

Copy link
Copy Markdown
Owner

A correction to how we'll fold Studio: to match our engine and app, we're rewriting its pieces in our own stack, with generation and the model pipelines in the Zig engine and server and the app side in TypeScript under the app's design. You stay the author of every commit that folds in a piece of your Studio, and each says what we rewrote to fit our setup. Sam Wasserman's NOTICE files and credits stay as the licence asks. We'll link each piece here as it lands.

- Synthema, Ash's AI Assistant.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants