Skip to content

[bugfix] Fix MPS worker handoff and named Studio image downloads - #1847

Open
leo2ultimate wants to merge 11 commits into
hao-ai-lab:mainfrom
leo2ultimate:fix/mps-worker-result-serialization
Open

leo2ultimate wants to merge 11 commits into
hao-ai-lab:mainfrom
leo2ultimate:fix/mps-worker-result-serialization

Conversation

@leo2ultimate

@leo2ultimate leo2ultimate commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

Fixes #1846 and a separate named-image download bug found while verifying the fix in Studio.

1. MPS generation finishes, but the worker cannot return the result.

When Studio uses MultiprocExecutor on a Mac, the worker can finish FLUX generation successfully and then fail at pipe.send(response):

RuntimeError: _share_filename_: only available on CPU

The response can contain MPS tensors in both the generated output and nested metadata/logging. PyTorch 2.12.0 cannot serialize their MPS storage through this multiprocessing pipe. The parent therefore never receives a usable result, even though GPU computation finished. This can happen between two Python processes on the same Mac; it transfers the result, not the model.

The fix copies MPS tensor leaves to CPU immediately before sending the response, including nested tensors. CPU/CUDA responses keep their existing path.

2. A named image job says “completed,” but its download returns 404.

Studio previously appended .mp4 to every job name and passed that full path as output_path, including for image jobs. For an image workload, the generator treats that mismatched path as a directory and saves the PNG inside it. Studio then searches for *.mp4, mistakenly selects that directory, and records it as the generated file.

For a fresh image job named portrait with prompt a red dinosaur:

Step Before After
File actually saved outputs/<job>/portrait.mp4/a red dinosaur.png outputs/<job>/portrait.png
Path recorded by Studio outputs/<job>/portrait.mp4 — a directory The actual PNG file returned by the generator
Download Job is marked completed, but the media endpoint rejects the directory with HTTP 404 HTTP 200 with the correct file, filename and content type

This second bug is platform-independent. The fix passes the output directory and optional filename separately, records the generator's returned path, and requires it to be a real file. Supported image names such as portrait.jpg and portrait.webp keep their formats; serving, preview and download code now recognize them correctly.

PyTorch has different ways of doing this for different devices:

Where the tensor lives How the handoff works
CPU PyTorch can arrange shared CPU memory.
NVIDIA GPU PyTorch can use CUDA IPC to share GPU allocations between processes.
Apple GPU through MPS The PyTorch version tested here, 2.12.0, lacks an equivalent working handoff for MPS tensors.
FastVideo: pipe.send(result)
                  |
           PyTorch examines tensor
                  |
          +-------+--------+
          |                |
        CUDA              MPS
          |                |
   _share_cuda_()    No MPS-specific branch
          |                |
   CUDA sharing      General storage handler
                           |
                   _share_filename_cpu_()
                           |
                   "This isn't CPU storage!"

The relevant PyTorch code is the CUDA tensor branch, storage reducer, and CPU-storage check that raises the error.

Changes

  • Add an explicit MPS branch before sending the execute_forward response. Other platforms send it directly.
  • Convert each MPS tensor with tensor.detach().cpu(), including tensors nested in dictionaries, lists, tuples, named tuples, dataclasses, and stage logging.
  • Copy response containers without changing the worker's original tensors. CPU/CUDA tensor objects and the existing CUDA latent-output handoff retain their behavior.
  • Add real spawned-process and pipe-level tests for nested results, repeated requests, source preservation, and shutdown. The spawned case now covers CPU, MPS, and CUDA; CUDA results must remain on CUDA, and received tensor references are released before the producer exits.
  • Fix named Studio jobs: pass the name through output_video_name and record the generator's returned video_path instead of guessing from directory entries. Require the returned path to be an actual file.
  • Preserve generated PNG/JPEG/WebP formats. Python's standard plus common MIME mappings (strict=False) determine the content type from the saved path, including WebP without system MIME files; the gallery recognizes those image formats, and downloads retain the actual saved filename and suffix.
  • Add Studio output regressions that preserve .jpg, .jpeg, and .webp, verify decoded image formats and HTTP content types, and ignore stale files/directories. Include the backend regression file in the existing unit-test CI lane. Frontend tests cover image previews and download names, including a Windows server path.
  • Exercise the nested-container helper directly with CPU tensors, so Linux CI covers dictionaries, named tuples, frozen dataclasses, and logging objects even when MPS cases skip.
  • Test output media types against Python's built-in-only MIME database, rather than relying on the development host's /etc/mime.types.
  • Isolate fake worker shutdown from pytest's shared Inductor pools, use the platform's default tensor-sharing strategy in spawned transport tests, and shut down Studio fixture managers after each test.

The MPS conversion applies only to the execute_forward response in worker_busy_loop. Generic execute_method responses, streaming reset/step calls, and streaming queues are unchanged; MPS streaming support is outside this PR. Executor selection is unchanged. MPS copies preserve values, shape, and dtype; they do not carry autograd history or preserve shared storage between distinct views.

Current single-device behavior: #1879 already selects UniprocExecutor for the default num_gpus=1 configuration. The full-model diagnostic explicitly selected the existing MultiprocExecutor to exercise the failing handoff.

Test Plan

Rebased onto main 866a3cdb91a85aef8561847365fc8564add9e4c8; current head is 8737f1b640db7181c551654d377c817fe83f4a33. The rebase preserved the production diff and existing test coverage. One additional fixture cleanup closes Studio test managers after use.

Local hardware/software: Apple M5 Pro, 48 GB unified memory, macOS 26.6.2, Python 3.12.13, PyTorch 2.12.0.

PYTHONPATH=. PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python -m pytest \
  apps/fastvideo_studio/tests \
  fastvideo/tests/worker/test_multiproc_executor.py \
  fastvideo/tests/worker/test_uniproc_executor.py -q -p no:cacheprovider
python -m pytest apps/fastvideo_studio/tests/test_inference_output.py \
  fastvideo/tests/worker/test_multiproc_executor.py -q -p no:cacheprovider
# Verify interpreter exit as well as the pytest summary.
npm test --prefix apps/fastvideo_studio
npm run typecheck --prefix apps/fastvideo_studio
python -m pre_commit run --all-files

Full-model diagnostics use FastAPI TestClient with the real Studio handlers, database, JobRunner, model pipeline, saved file, and media response. They explicitly select the existing MultiprocExecutor to exercise this PR's handoff. These are API/backend checks, not browser automation.

Mac case Model and configuration
Image black-forest-labs/FLUX.2-klein-4B, revision e7b7dc27f91deacad38e78976d1f2b499d76a294; prompt a red dinosaur; 832 x 480, 1 frame, 4 steps, guidance 1, seed 1024; name Rebased Mac image.webp.
Native video Wan-AI/Wan2.1-T2V-1.3B-Diffusers, revision 0fad780a534b6463e45facd96134c9f345acfa5b; orange-cat walking prompt; 50 steps, guidance 5, seed 1024, 16 fps; dimensions/frame count below.

The native Wan diagnostic sets the existing public configuration pipeline_config.text_encoder_precisions = ("bf16",) to match its BF16 transformer on MPS. Stock FP32 text conditioning aborts in a Metal dtype mismatch before the result handoff. Studio's form does not expose this override: this validates a configured native MPS backend path, not out-of-box Wan support through the form. No Wan model, precision-default, or executor-selection changes are included in this PR.

Test Results

Check Result
Unchanged upstream, explicit multiprocessing FLUX generation finishes; MPS handoff fails with _share_filename_: only available on CPU.
Earlier fixed/default comparison The fixed separate-worker PNG matches the default single-process PNG byte-for-byte for seed 1024; two consecutive jobs also pass on one cached generator.
Rebased Studio + worker + uniproc tests, 8737f1b64 93 passed, 4 skipped, process exit 0. CUDA is unavailable on this Mac.
Mac image, 8737f1b64 FLUX through native PyTorch/MPS and Studio: valid 832 x 480 RGB WebP; HTTP 200, image/webp; visual check and clean exit pass.
Mac native video, 8737f1b64 Wan 1.3B through native PyTorch/MPS and Studio: job completed in about 105 s, HTTP 200 with video/mp4, valid H.264 at 256 x 256, 25 frames, 16 fps (1.5625 s); full-file decode and clean process exit passed.
CUDA runner, rebased 8737f1b64 All three CUDA cases ran and passed, confirmed in build #2289 / runner report. NVIDIA GB10, Python 3.12.15, PyTorch 2.12.0+cu130, CUDA 13.0; 3920 passed, 75 skipped, 5 deselected, exit 0, 448.22 s.
CI after rebase, 8737f1b64 Build #2289: All six Fastcheck lanes passed, with aggregate fastcheck-passed=success; pre-commit also passed.
Built-in-only MIME regression Before: 1 failed, 5 passed, reproducing WebP served as text/plain. After: 6 passed with PNG/JPEG/WebP/MP4 content types.
Studio output regression Original three cases fail on unchanged upstream and pass with this fix. Current tests use real naming, files and HTTP responses with mocked generation; Pillow checks actual image encodings.
Studio frontend 110 passed across 19 files; TypeScript typecheck passed. Source unchanged by the rebase.
Full pre-commit, 8737f1b64 All hooks passed in a clean temporary checkout.

The native video is a deliberately small functional smoke: its output is blurry, and image quality is not an acceptance criterion for this result-transfer/download fix. The generated MP4 has all requested frames and decodes completely. The BF16 text-encoder configuration required for this Mac run is disclosed above. A separate optional 480p quality check was stopped when we agreed to keep validation focused on the interface; it is not counted as a passing test.

Test output and validation boundaries
Rebased local Studio + worker + uniproc suite:
93 passed, 4 skipped, 15 warnings in 30.77s
process exit code: 0

Built-in-only MIME database regression:
before: 1 failed, 5 passed
after: 6 passed

Linux NVIDIA runner on rebased 8737f1b64:
test_execute_forward_response_survives_spawn_and_repeated_requests[cuda] PASSED
test_response_preparation_preserves_non_mps_tensor_identity[cuda] PASSED
test_prepare_worker_output_moves_latent_cuda_tensor_to_cpu PASSED
3920 passed, 75 skipped, 5 deselected in 448.22s
process exit code: 0

The CUDA round trip uses real CUDA tensors and a spawned pipe, with a fake inference worker; it is not full-model CUDA generation. The linked runner report confirms all three CUDA cases executed on the current rebased SHA. The production diff and test content were preserved by the rebase, as independently confirmed in the follow-up review.

Build #2279 had a WebP assertion failure and a separate post-summary timeout. Later builds #2282 and #2288 exited normally; the rebased Unit lane is also green. The test cleanup fixes reproduced local process leaks, but it does not establish the root cause of the earlier Slurm timeout.

Earlier broader worker tests had four distributed-rendezvous failures with hostname-resolution errors on this Mac; the same four reproduce on unchanged upstream. CUDA runtime evidence comes from the NVIDIA runner, not this Mac. No performance improvement or cross-device output-quality parity is claimed.

Frontend tests used a temporary checkout without spaces because the existing Vitest alias configuration URL-encodes the workspace path. The all-files hooks used the docs lockfile dependencies.

Checklist

  • I ran pre-commit run --all-files and fixed all issues
  • I added or updated tests for my changes
  • I updated documentation if needed — no public API changes; the transport behavior is explained above.
  • I considered GPU memory impact of my changes — MPS results temporarily need CPU copies while the worker retains the original tensors; CPU/CUDA tensor handling is preserved.

Model/pipeline and new-model/serving-runtime checks: Not applicable. This PR changes result transport, adds no model/runtime, and does not change model calculations, serving YAML, or the support matrix.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Welcome to FastVideo! Thanks for your first pull request.

How our CI works:

PRs run a three-tier CI system:

  1. Pre-commit — formatting (yapf), linting (ruff), type checking (mypy). Runs immediately on every PR.
  2. Fastcheck — six core GPU lanes run automatically via Buildkite (~10-15 min).
  3. Merge gate — a reviewer adds ready; changed paths select only the relevant integration, training, golden, or SSIM coverage.

Before your PR is reviewed:

  • pre-commit run --all-files passes locally
  • You've added or updated tests for your changes
  • The PR description explains what and why

If pre-commit fails, a bot comment will explain how to fix it. Fastcheck and merge-gate results appear in the Checks section below.

Useful links:

@mergify mergify Bot added type: bugfix Bug fix scope: inference Inference pipeline, serving, CLI scope: infra CI, tests, Docker, build labels Sep 14, 2026
@mergify

mergify Bot commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

Merge Protections

🔴 1 of 1 protections blocking · waiting on 👀 reviews and 🤖 CI

Protection Waiting on
🔴 PR merge requirements 👀 reviews and 🤖 CI

🔴 PR merge requirements

Waiting for

  • #approved-reviews-by>=1
  • check-success=full-suite-passed
This rule is failing.
  • #approved-reviews-by>=1
  • check-success=full-suite-passed
  • check-success=fastcheck-passed
  • check-success~=pre-commit
  • title~=(?i)^\[(feat|feature|bugfix|fix|refactor|perf|ci|doc|docs|misc|chore|kernel|new.?model|skill|skills|infra)\]

@aryan5v

aryan5v commented Nov 6, 2026

Copy link
Copy Markdown
Collaborator

@fv-superagent review

@greptile-apps

greptile-apps Bot commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5 Tier: plus

[Medium impact] The changes reach multiprocessing MPS inference and Studio’s output workflow.

No behavioral blocker was found, but the comment-rule violation must be fixed before merging.

Findings

  1. P2 Comments repeat the code ▶
Fix with agent prompt
### Issue 1
apps/fastvideo_studio/tests/test_inference_output.py:66
This comment repeats the `_prepare_output_path` call below it. The repository requires removing comments outside its keep-list, which allows license headers, public API docs, required dependency or protocol explanations, formatting directives, and issue links-not narration.

The test module’s opening docstring and `_copy_mps_tensors_to_cpu`’s docstring have the same problem, with similar narration elsewhere in the added tests. Remove these comments before merging to satisfy the repository requirement.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Copies nested MPS results to CPU before the worker sends them to the parent, while leaving the CUDA response path unchanged.

  • Multiprocessing MPS results reach the parent as CPU tensors.
  • Studio keeps each generated file’s real name and format.

Both supplied earlier threads were rechecked. The redundant-comment thread records its fix; the image-format issue is addressed in the current code. Neither thread has a supplied comment number.

Review checks were static. No tests were run. Browser capture was blocked by missing frontend dependencies.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Worker[Worker result] --> Device{MPS?}
  Device -->|Yes| Copy[Copy nested MPS tensors to CPU]
  Device -->|No| Direct[Keep existing response]
  Copy --> Pipe[Send through pipe]
  Direct --> Pipe
  Pipe --> Generator[Generator saves media]
  Generator --> Runner[Studio records returned file path]
  Runner --> Browser[Preview or download saved file]
Loading

Reviews (5) · Last reviewed commit: "[test] Close Studio fixture managers aft..." · Reviewed by Greptile

@fv-superagent

Copy link
Copy Markdown
Collaborator

fv-superagent score: 4/5

The approach is right: PyTorch has no MPS storage-sharing path over the multiprocessing pipe (_share_filename_cpu_() is the CPU fallback that raises), so materialising CPU storage before pipe.send is the correct boundary, not the executor. Gating on current_platform.is_mps() leaves CPU/CUDA behaviour untouched, and the recursion preserves container types — I traced dict/OrderedDict/list/tuple/namedtuple/frozen dataclass/PipelineLoggingInfo and the worker's originals stay on device in each case. The tests assert value, dtype, container type and source preservation, plus a real spawn round-trip. Focused, well-tested diff.

Two non-blocking notes:

  1. fastvideo/worker/multiproc_executor.py:882-887 — only the execute_forward arm converts. execute_streaming_reset / execute_streaming_step (lines 245-267) also cross this pipe through execute_method, and GPUWorker.execute_streaming_step returns a ForwardBatch (fastvideo/worker/gpu_worker.py:235-236), so an MPS tensor there re-raises the same _share_filename_ error; streaming_queue_loop (lines 929-936) pickles into the streaming Queue for the same reason. Scoping streaming out is fine for [Bug] MPS generation fails when multiprocessing worker returns output tensors #1846, but "limited to inference responses" understates it — this is a response on the same pipe. Worth a follow-up if MPS + StreamingVideoGenerator is a configuration you want to support.

  2. The MPS path itself is never exercised in CI. Every ("mps", …) case skips without Apple hardware, the Linux ("cpu", "all") case bypasses the helper entirely, and the macOS lane neither triggers on fastvideo/worker/** nor runs this file (and it installs a CPU-only torch). So the container reconstruction in _copy_mps_tensors_to_cpu is covered only by the M5 Pro run. A device-agnostic case that calls the helper directly with a namedtuple / dataclass / PipelineLoggingInfo fixture would exercise that walk on Linux without needing MPS.

Greptile's single P2 (a redundant test comment) is fixed on the branch in f179370.

— fv-superagent · run by @aryan5v

@mergify mergify Bot added the scope: studio label Nov 6, 2026
@leo2ultimate leo2ultimate changed the title [bugfix] Serialize MPS worker results through CPU storage [bugfix] Fix MPS worker handoff and named Studio image downloads Nov 6, 2026
Comment thread apps/fastvideo_studio/job_runner.py Resolved

@fv-superagent fv-superagent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fv-superagent score: 3/5 (was 4/5)

The MPS half is unchanged and still looks right. Reviewing the new Studio commit 9b7c2b92f turned up one must-fix (the same issue Greptile flagged).

Must fix:

  1. Image jobs named with an image extension now produce a .jpg/.jpeg/.webp file that Studio can't serve, preview, or download correctly.
    apps/fastvideo_studio/job_runner.py:1108 passes the raw safe_name into output_video_name, and for an image workload _prepare_output_path honours an image extension in that name (fastvideo/entrypoints/video_generator.py:764-770), so a job named portrait.jpg writes portrait.jpg. But apps/fastvideo_studio/server.py:664 labels every non-.mp4 output image/png, apps/fastvideo_studio/src/app/gallery/page.tsx:12 treats only .png as an image, and apps/fastvideo_studio/src/components/jobs/JobCard.tsx:152 downloads everything that is not .png as .mp4. Job names are unconstrained (CreateJobRequest.name), so this is reachable from the UI and API.
    Either keep Studio image outputs as PNG (don't forward an image extension through output_video_name), or extend the media type, gallery preview, and download-extension logic to cover .jpg/.jpeg/.webp. A regression that names a t2i job portrait.jpg would pin whichever you pick.

Non-blocking, carried over from the earlier review:

  1. execute_method responses — execute_streaming_reset / execute_streaming_step (fastvideo/worker/multiproc_executor.py:245-267, dispatched through the else arm at :882-887) and streaming_queue_loop (:929-936) still send device tensors raw, so MPS streaming would hit _share_filename_ too. Out of scope for #1846.

  2. The MPS conversion path has no CI coverage: every ("mps", …) case skips without Apple hardware, and the ("cpu", "all") case bypasses the helper.

— fv-superagent, automated maintainer run by @aryan5v

@fv-superagent fv-superagent left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fv-superagent score: 4/5 (was 3/5)

The P1 is fixed and now covered: apps/fastvideo_studio/job_runner.py:1104-1106 forces .png for 2i workloads before handing the name to the generator, so portrait.jpg / .jpeg / .webp become portrait.png, and the test asserts both the saved artifact and the served bytes are PNG (image.format == "PNG"). Job.workload_type is a str, so the endswith("2i") gate is safe there.

Thanks also for test_response_preparation_rebuilds_nested_containers_on_cpu — that is the Linux-runnable coverage for the dataclass / namedtuple / OrderedDict / PipelineLoggingInfo walk, and it pins type reconstruction and source immutability, which is what the MPS-only cases could not do in CI.

The only remaining item is the earlier non-blocking scope note: execute_method responses — execute_streaming_reset / execute_streaming_step (fastvideo/worker/multiproc_executor.py:245-267, via the else arm at :882-887) and streaming_queue_loop (:929-936) — still send device tensors raw, so MPS streaming would hit _share_filename_. Fine as a follow-up for #1846.

CI update — the Unit lane hangs after the tests finish, so fastcheck cannot go green on this head. buildkite/pr-fastcheck/microscope-unit-tests completes the whole session (3891 passed, 75 skipped, 5 deselected in 454s) and then the step never exits: it sat until the 90-minute step timeout on build #2252 (exit 124), and the same happened on 9b7c2b92f (build #2251: pytest finished in 450s, step still running 10+ min later when the next push cancelled it). Normal Unit runs finish in ~8 min (#2232–#2234), so this is a post-run teardown hang, not a failing assertion. pre-commit and build are green.

Can you reproduce and bisect locally? The lane's explicit list gains apps/fastvideo_studio/tests/test_inference_output.py plus the new worker tests:

pytest apps/fastvideo_studio/tests/test_inference_output.py fastvideo/tests/worker/test_multiproc_executor.py -q -p no:cacheprovider
echo $?   # does the shell return after the summary, or hang?

If it hangs on exit, something live is not being reaped — the Studio tests bring up JobRunner (spawn mp.Manager in job_runner.py:347, QueueListener) through a TestClient, and the worker tests spawn MultiprocExecutor children. py-spy dump or faulthandler.dump_traceback_later on the stuck process will name it. If it does not reproduce locally, say so and we will treat it as a Slurm step-teardown issue and re-run the lane.

— fv-superagent · run by @aryan5v

@fv-superagent fv-superagent left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fv-superagent score: 3/5 (was 4/5)

Correction after the Unit lane finished: the media-type inference does not hold for .webp here. apps/fastvideo_studio/tests/test_inference_output.py::test_named_job_serves_generated_artifact[t2i-portrait.webp-portrait.webp-image/webp] fails with assert 'text/plain; charset=utf-8' == 'image/webp' — FileResponse falls back to mimetypes.guess_type, and CPython's built-in map has no .webp entry (it only resolves via the host's /etc/mime.types, which this CI image lacks). Fix: mimetypes.add_type("image/webp", ".webp") at import, or an explicit media-type map in get_video. Details in my comment below.

The rest of the rework is the right direction: portrait.jpg/.jpeg keep their name, imageio.imwrite writes the encoding matching the extension, isImage covers all four image extensions, and the download keeps the real basename. The png/jpg/jpeg/mp4 cases pass; only .webp fails.

Also still open: the Unit lane's post-run teardown hang — pytest finished (1 failed, 3890 passed in 455s) and the step then sat until the 90-minute timeout (exit 124, build #2279), same as builds #2252/#2251. Repro command below.

pytest apps/fastvideo_studio/tests/test_inference_output.py fastvideo/tests/worker/test_multiproc_executor.py -q -p no:cacheprovider
echo $?

Carried-forward scope note, no action needed here: execute_method responses (execute_streaming_reset/step) and streaming_queue_loop still send device tensors raw, so MPS streaming would hit _share_filename_ — a follow-up for #1846.

— fv-superagent · run by @aryan5v

@fv-superagent

Copy link
Copy Markdown
Collaborator

Unit lane on this head has a real failure, not just the timeout.

apps/fastvideo_studio/tests/test_inference_output.py::test_named_job_serves_generated_artifact[t2i-portrait.webp-portrait.webp-image/webp] fails with AssertionError: assert 'text/plain; charset=utf-8' == 'image/webp' (build #2279, 1 failed, 3890 passed).

Cause: FileResponse with no explicit media type falls back to mimetypes.guess_type, and CPython's built-in mimetypes map has no .webp entry — it only resolves when the host has an /etc/mime.types that lists it, which this CI image does not. So a WebP output is served as text/plain. .png/.jpg/.jpeg/.mp4 are unaffected. One line fixes it, e.g. at module import in server.py:

mimetypes.add_type("image/webp", ".webp")

or pass an explicit media_type for the image/video extensions in get_video.

Separately, the Unit step still hangs after pytest finishes (the run above took 455s, then exit 124 ~82 min later at the 90-minute timeout) — same post-run teardown hang as the previous two heads, so that still needs the local repro from my earlier note.

— fv-superagent · run by @aryan5v

@fv-superagent fv-superagent left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fv-superagent score: 5/5 (was 4/5)

Confirmed on CI: all six fastcheck lanes, fastcheck-passed, pre-commit and build are green on 14773cb, so the post-run teardown hang is gone. Both fixes hold in the run — .webp serves image/webp, and the in-process transport test no longer shuts down the shared Inductor pool.

For the record, the PR: MPS worker responses are copied to CPU before pipe.send; Studio artifact names keep their real format end to end (png/jpg/jpeg/webp), with serve/download and transport isolation covered by tests. The only carried-forward item is the out-of-scope execute_method/streaming MPS transport, a follow-up for #1846.

Nothing blocks. Greptile's review predates the last two heads; its P1 (image names break downloads) is fixed and covered by the new tests.

— fv-superagent · the compiler pool lives to fight another test · run by aryan5v

@fv-superagent fv-superagent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fv-superagent score: 5/5 (was 5/5)

Test-only delta, and a good one: the spawn round-trip test now covers cpu, mps and cuda. The expectations are right — _run_output_worker stubs current_platform.is_mps per case, so CPU and CUDA tensors cross the pipe as-is (the CUDA case asserts cuda) while the MPS case still returns on CPU through _copy_mps_tensors_to_cpu. The assert_tensor helper plus the explicit response = output = metadata = metric = None releases keep imported CUDA/CPU storage from outliving the producer process, which is the correct discipline for IPC-backed tensors. No gpu marker, so the CUDA case runs in the fastcheck Unit lane (which has CUDA) and skips where it does not.

This sits on top of the 5/5 on the previous head, where all six fastcheck lanes, fastcheck-passed, build and pre-commit were green. CI is re-running on this head; nothing blocks.

— fv-superagent · three devices, one pipe, no leaks · run by aryan5v

leo2ultimate commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor Author

Update: the native PyTorch/MPS video check through Studio is now complete, together with the image check, on rebased head 8737f1b64. Both passed result transfer, saving, media download and file decoding. The required Wan BF16 text-encoder configuration and final-review request are documented in the latest results. The older MLX run below remains only supplementary evidence.

@fv-superagent review

Could you review head 661285034e3366499d9ce30169b16f648dd6e399 and verify the CUDA tests on an existing Linux NVIDIA runner? We have no local CUDA GPU. Please reply here with the GPU, PyTorch/CUDA versions, exact tested SHA, per-test results/log links, and confirmation that pytest exits normally. The CUDA round-trip must report passed, not skipped. If current CI already has those results, a link to the exact test output is sufficient.

python -m pytest -vv -rs \
  'fastvideo/tests/worker/test_multiproc_executor.py::test_execute_forward_response_survives_spawn_and_repeated_requests[cuda]' \
  'fastvideo/tests/worker/test_multiproc_executor.py::test_response_preparation_preserves_non_mps_tensor_identity[cuda]' \
  fastvideo/tests/worker/test_multiproc_executor.py::test_prepare_worker_output_moves_latent_cuda_tensor_to_cpu
echo "pytest_exit=$?"

This needs one GPU and tiny tensors, with no model download. The new case checks real CUDA IPC through the spawned worker/pipe, CUDA device retention, exact nested values, two requests, and release of received storage before producer shutdown.

Local verification on the same SHA is complete:

  • Worker + uniproc tests: 43 passed, 4 skipped on the M5 Pro; CUDA cases skip locally.
  • Image: real FLUX.2-klein-4B Studio API job through MultiprocExecutor on PyTorch MPS, 4 steps, 832 x 480, seed 1024, named Mac image.webp. Job completed; media endpoint returned HTTP 200 and image/webp; decoded RGB WebP and clean process exit verified.
  • Video: cached FastMetal-1.3B-QAD through FastVideo's recommended MLX example, 3 steps, 832 x 480, 81 frames at 16 fps, seed 1024. Process exited 0; MP4 decoded successfully and ffprobe confirmed H.264 / 81 frames / 5.0625 seconds. Beginning/middle/end frames were visually checked. This is a separate Mac MLX smoke, not validation of the MPS multiprocessing path.

After the requested five-minute wait, I found no unresolved review threads; your latest review was positive and the new CI run was still pending. Please send the CUDA execution result back here so we can distinguish source review from GPU runtime validation.

@fv-superagent

Copy link
Copy Markdown
Collaborator

Verified on the Linux NVIDIA runner — all three CUDA cases ran and passed; nothing was skipped.

  • Build/job: buildkite/pr-fastcheck/microscope-unit-tests, build #2288, unit job log
  • Tested SHA: 661285034e3366499d9ce30169b16f648dd6e399
  • Runner: NVIDIA GB10 (Spark 4, srun --gres=gpu:1), image fastvideo-ci-gb10:20261024-fa2; Python 3.12.15, PyTorch 2.12.0+cu130, CUDA 13.0
test result on the GPU runner
test_execute_forward_response_survives_spawn_and_repeated_requests[cuda] passed (logged CUDA is available; [cpu] passed, [mps] skipped on Linux)
test_response_preparation_preserves_non_mps_tensor_identity[cuda] passed
test_prepare_worker_output_moves_latent_cuda_tensor_to_cpu passed

Pytest exited normally: job state passed, exit 0, 3893 passed, 75 skipped, 5 deselected in 447.36s. No test failure and no teardown timeout — the lane finished in ~7.5 minutes, well inside the 90-minute limit.

For completeness, the existing CUDA cases also passed on the previous head's run (build #2282, 14773cb), which is where that lane's environment line (torch 2.12.0+cu130, cuda 13.0, gpus 1) is printed.

Review of 6612850 is at 5/5 (review 5835845893), and Greptile's re-run on this head is clean.

— fv-superagent · CUDA passed, MPS stayed home · run by aryan5v

@leo2ultimate
leo2ultimate force-pushed the fix/mps-worker-result-serialization branch from 6612850 to 8737f1b Compare November 10, 2026 04:03

@fv-superagent fv-superagent left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fv-superagent score: 5/5 (was 5/5)

The branch was rewritten since my last review (head 6612850 → 8737f1b): the MPS change is now four commits and the tree is rebased. I re-diffed the production files against main and the net change is identical to what I reviewed — fastvideo/worker/multiproc_executor.py still adds _copy_mps_tensors_to_cpu plus the current_platform.is_mps() branch in worker_busy_loop, and the Studio changes (job_runner output name/returned path, server mimetypes.guess_type(..., strict=False), the gallery/JobCard extension handling) are unchanged. No test coverage was dropped: test_execute_forward_response_survives_spawn_and_repeated_requests is still parametrized over cpu/mps/cuda.

The new commit is a good one: apps/fastvideo_studio/tests/test_duplicate_and_edit.py now yields the JobRunner and calls _shutdown() in finally, so each test releases its mp.Manager instead of leaving a live child for interpreter exit — the same hygiene as test_inference_output.py.

My CUDA verification still holds: the CUDA case content is unchanged (it is now cfc07408b; test_execute_forward_response_survives_spawn_and_repeated_requests[cuda], test_response_preparation_preserves_non_mps_tensor_identity[cuda] and test_prepare_worker_output_moves_latent_cuda_tensor_to_cpu all passed on the GB10 runner). CI is re-running on this head.

— fv-superagent · the history got a rewrite, the diff didn't · run by aryan5v

Copy link
Copy Markdown
Contributor Author

@fv-superagent review

Thanks for confirming the CUDA passes and reviewing the rebase. The local functional checks are now complete, and this is ready for final review at 8737f1b640db7181c551654d377c817fe83f4a33, rebased onto main 866a3cdb9.

On the M5 Pro, both examples passed through the real Studio API, JobRunner and explicit native PyTorch/MPS MultiprocExecutor:

  • Image: FLUX.2-klein-4B, 832 x 480 WebP; generation completed, HTTP 200 with image/webp, decoded image verified, process exited cleanly.
  • Video: Wan2.1-T2V-1.3B-Diffusers, 50 steps, 256 x 256, 25 frames at 16 fps; generation completed in about 105 seconds, HTTP 200 with video/mp4, all H.264 frames decoded, process exited cleanly. This is native MPS, not the earlier MLX example.

The Wan diagnostic needed the existing text_encoder_precisions=("bf16",) option to avoid an unrelated MPS dtype mismatch. That configuration is documented in the PR; it is not a new model/default change. These are backend/API checks, not browser automation. The small clip is blurry, but our acceptance criterion here is correct result transfer, saving and download, not model quality.

After the rebase: 93 passed, 4 skipped locally, exit 0; all pre-commit hooks passed. The only extra commit closes Studio test fixture managers. The production diff and CUDA test content are unchanged.

Build #2289 now has green Unit, Kernel and Dreamverse app checks; the remaining Fastcheck work is still pending. If the Unit log already records the three CUDA cases on this new SHA, please link that confirmation—no duplicate run is needed.

Could you give the final review and flag any remaining maintainer approval or merge-gate requirements? The PR description now follows the template and contains the updated Mac/CUDA evidence.

@fv-superagent

Copy link
Copy Markdown
Collaborator

Final review is in, and the CUDA cases are confirmed on this SHA.

  • Review: 5/5, review 5836608682 on 8737f1b. The net diff against main is unchanged from the pre-rebase head and no test coverage was dropped.
  • CUDA on 8737f1b: build #2289, unit job log:
    • test_execute_forward_response_survives_spawn_and_repeated_requests[cuda] — passed (ran, logged CUDA is available; [mps] skipped on Linux)
    • test_response_preparation_preserves_non_mps_tensor_identity[cuda] — passed
    • test_prepare_worker_output_moves_latent_cuda_tensor_to_cpu — passed
    • Job passed, exit 0, 3920 passed, 75 skipped, 5 deselected in 448.22s; NVIDIA GB10, image fastvideo-ci-gb10:20261024-fa2, Python 3.12.15, PyTorch 2.12.0+cu130, CUDA 13.0.

Remaining merge-gate requirements (Mergify):

gate state
pre-commit green
fastcheck-passed pending — Unit, Kernel and DreamVerse app passed; Encoder running, VAE and Transformer scheduled
#approved-reviews-by>=1 a maintainer approval is still needed
full-suite-passed required by the gate; runs after approval / in the merge queue

So nothing further is needed from my side; the approval and merge are the maintainers' call. Your M5 Studio/MPS image+video report is noted, and using the existing documented text_encoder_precisions=("bf16",) option instead of changing a default is the right call.

— fv-superagent · the rebase didn't shake CUDA loose · run by aryan5v

Copy link
Copy Markdown
Contributor Author

@fv-superagent review

Automatic CI is now green on 8737f1b640db7181c551654d377c817fe83f4a33: pre-commit and all six Fastcheck lanes passed in build #2289.

The local native MPS image and video functional checks also passed on this SHA, as documented above. The PR's Purpose section now explains both concrete failures and includes the named-image path/download before-and-after example.

Could you give the final review and confirm the remaining maintainer approval/merge-gate steps? A new Greptile comment about redundant comments/docstrings arrived as CI finished; please include that in the final review.

This branch has not been deployed

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

Labels

scope: inference Inference pipeline, serving, CLI scope: infra CI, tests, Docker, build scope: studio type: bugfix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] MPS generation fails when multiprocessing worker returns output tensors

3 participants