Skip to content

[feat]: native Apple MLX studio with Liquid Glass onboarding - #14

Draft
aryan5v wants to merge 15 commits into
aryan/release/fastwan-qad-int8-1.3b-mlxfrom
aryan/fastvideo-mac-studio
Draft

[feat]: native Apple MLX studio with Liquid Glass onboarding#14
aryan5v wants to merge 15 commits into
aryan/release/fastwan-qad-int8-1.3b-mlxfrom
aryan/fastvideo-mac-studio

Conversation

@aryan5v

@aryan5v aryan5v commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add a native SwiftUI FastVideo app for Apple Silicon with a landing page, Liquid Glass onboarding on macOS 26+, prompt composition, EMA/RAW selection, generation controls, AVKit playback, export/share actions, notifications, sleep prevention, and a persistent local generation library.
  • Add a narrow JSON-lines bridge for MLX runtime diagnosis, local artifact discovery, optional managed downloads after publication, progress, cancellation, metrics, and final output handling.
  • Add opt-in per-step x0 previews to the existing MLX example. Each non-final DMD prediction is decoded with TAEHV behind a temporary filename and atomically published while denoising continues.
  • Add a local app-bundle packaging flow that embeds the required FastVideo source while keeping the managed Python environment and model weights outside the signed app.

Why native

The release runtime is MLX and Metal native. A native app keeps all prompts and media local while supporting direct Metal execution, native files, Share Sheet, completion notifications, sleep prevention, and durable local history. A browser-only version would require a separate WebGPU model port.

Live preview behavior

DMD already produces a full x0 prediction at every step. With --preview-dir, the MLX example decodes that prediction through TAEHV after each non-final step. The bridge emits a typed preview event only after an atomic rename, so the app never opens a partially written MP4. The final export replaces the preview automatically.

The preview path is opt-in. Existing CLI users pay no additional graph or decode cost.

Local artifact mapping

No Hugging Face download is required for development testing. The app detects and migrates to the validated run-2 artifacts already installed on this Mac:

  • shared model: ~/models/qad_int8_v2_ema
  • EMA MLX INT8: ~/mlx-ckpt-cache-qad-v2-ema/int8
  • RAW MLX INT8: ~/mlx-ckpt-cache-qad-v2/int8

The earlier v1 EMA export is intentionally skipped because visual review proved that it collapses to noise. Checkpoint validation matches the native MLX format, mlx_dit.json plus mlx_dit.safetensors. ffmpeg is discovered through either PATH or the managed runtimes imageio binary.

End-to-end validation

Packaged app, macOS 27, Apple M4 Max:

  • EMA v2, INT8 MLX DiT, compiled graph, bf16 prompt encode, TAEHV decode
  • 832x480, 81 frames, 16 fps, seed 1024
  • prompt: A fox runs through a misty pine forest, leaves kicking up behind it.
  • step-1 x0 appeared in the native player at 37 percent progress and was visually coherent
  • final MP4 completed in 125.98 seconds
  • MLX denoise: 99.51 seconds
  • final TAEHV decode/export: 1.66 seconds
  • MLX denoise peak: 4,159,476,954 bytes
  • final file: 813,705 bytes
  • final video was decoded again with ffmpeg without errors
  • the completed record exposes Reveal in Finder, Export, and Share actions and remains in Library

The first live-preview insertion exposed a macOS 27 beta crash in SwiftUI VideoPlayer inside _AVKit_SwiftUI. The app now uses AppKits AVPlayerView through NSViewRepresentable; the same preview-to-final reproduction completes without crashing.

Automated validation

  • apps/fastvideo_mac/scripts/test.sh
    • Swift debug build passed
    • Foundation history/process/preview self-test passed
    • 6 Python bridge and source-contract tests passed
  • apps/fastvideo_mac/scripts/package_app.sh
    • Swift release build passed
    • FastVideo.app assembled and ad-hoc codesigned
  • uvx pre-commit run --files <changed files> passed yapf, ruff, codespell, PyMarkdown, mypy, and repository checks

Stack and remaining release gates

This remains a separate stacked PR targeting aryan/release/fastwan-qad-int8-1.3b-mlx at bec73975. It does not modify the launch branch directly.

Remaining public-distribution gates are the published Hugging Face model URL and checksums, Developer ID signing, Hardened Runtime review, and notarization.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a82a005-f872-4079-af5f-796e19785d33

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch aryan/fastvideo-mac-studio

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

claude added 2 commits July 10, 2026 03:14
…decode

- mx.compile the DiT forward by default (--no-mlx-compile to opt out);
  bit-identical to eager with an eager fallback, ~1.4x faster denoise.
  mlx_dit_from_diffusers_safetensors gains a compile= passthrough.
- Encode prompts with UMT5 in bf16 by default (--text-encoder-dtype):
  fp32 exponent range removes the T5-family fp16 overflow risk at fp16
  memory cost; bf16 embeds are cast to fp32 for the numpy transport so
  fp16/fp32 callers (the benchmark) are byte-identical.
- Decode wan-vae in bf16 by default (--vae-decode-dtype), matching the
  reference pipeline's effectively-lossless decode default; TAEHV keeps
  its validated fp16 path and becomes the default backend.
- Defaults are now the validated release shape (480x832x81, INT8) and
  --model-root resolves through the HF cache instead of a pinned
  snapshot hash (DEFAULT_MODEL_ROOT kept for the bench import).
- CI: run test_mlx_compile_parity.py in both MLX smoke jobs so the
  default path is gated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BZCNbygNyuXZba1nQNZnFa
…ac callout

- Blog draft: restore the launch story (why Mac, stack, QAD retarget,
  results, roadmap) using only run-2 numbers and the release-record
  constraints; EMA named as the released checkpoint pending final
  visual-review confirmation; remaining TODOs are HF links, refreshed
  timings, review grid, and acknowledgements.
- Release record: log the owner's EMA selection (visual review still
  gates publication), require stock FP16/PTQ reference columns in the
  review, and flag that recorded timings predate the new defaults.
- README: Apple Silicon quickstart callout next to the install section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BZCNbygNyuXZba1nQNZnFa

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a native macOS SwiftUI application for the FastWan-QAD 1.3B MLX runtime, featuring local video generation, progressive TAEHV previews, and a Python-to-Swift bridge. The feedback highlights critical improvements to concurrency and resource management: resolving a potential out-of-order execution issue in AppModel by replacing unstructured tasks with sequential dispatch, fixing a retain cycle in ProcessDriver's termination handler, avoiding side-effects in SwiftUI by lazy-initializing AVPlayer on view appearance, and ensuring temporary preview files are cleaned up in mlx_wan_prompt_to_video.py using a try...finally block.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +275 to +276
onLine: { [weak self] line in Task { @MainActor in self?.handleGenerationLine(line, id: id) } },
onTermination: { [weak self] status in Task { @MainActor in self?.finishGenerationProcess(id: id, status: status) } }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Using Task { @MainActor in ... } inside the onLine callback does not guarantee FIFO (First-In-First-Out) execution order on the MainActor. Since onLine is called sequentially from a background thread, spawning unstructured tasks can cause progress updates or the final completion event to be processed out of order, leading to UI glitches or incorrect state transitions. Use DispatchQueue.main.async instead to guarantee that the events are processed in the exact order they are received.

Suggested change
onLine: { [weak self] line in Task { @MainActor in self?.handleGenerationLine(line, id: id) } },
onTermination: { [weak self] status in Task { @MainActor in self?.finishGenerationProcess(id: id, status: status) } }
onLine: { [weak self] line in DispatchQueue.main.async { self?.handleGenerationLine(line, id: id) } },
onTermination: { [weak self] status in DispatchQueue.main.async { self?.finishGenerationProcess(id: id, status: status) } }

Comment on lines +97 to +124
try await withCheckedThrowingContinuation { continuation in
let driver = ProcessDriver()
let outputLock = NSLock()
var collected: [String] = []
do {
try driver.start(
executable: executable,
arguments: arguments,
currentDirectory: currentDirectory,
onLine: { line in
outputLock.withLock { collected.append(line) }
onLine(line)
},
onTermination: { status in
// Keep the driver alive until its fast child exits.
// Without this capture, a one-line command can finish
// after the local driver has deallocated and its pipe
// buffer is lost before the continuation resumes.
_ = driver.isRunning
let text = outputLock.withLock { collected.joined(separator: "\n") }
continuation.resume(returning: (status, text))
}
)
} catch {
continuation.resume(throwing: error)
}
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Capturing driver strongly inside the onTermination closure creates a retain cycle (driver -> process -> terminationHandler -> onTermination -> driver), causing a memory leak. To prevent this, capture driver weakly in the closure, and use withExtendedLifetime(driver) after the await call in runAndCollect to guarantee the driver remains allocated until the process finishes.

        let driver = ProcessDriver()
        let result = try await withCheckedThrowingContinuation { continuation in
            let outputLock = NSLock()
            var collected: [String] = []
            do {
                try driver.start(
                    executable: executable,
                    arguments: arguments,
                    currentDirectory: currentDirectory,
                    onLine: { line in
                        outputLock.withLock { collected.append(line) }
                        onLine(line)
                    },
                    onTermination: { [weak driver] status in
                        guard let driver else {
                            continuation.resume(returning: (status, ""))
                            return
                        }
                        _ = driver.isRunning
                        let text = outputLock.withLock { collected.joined(separator: "\n") }
                        continuation.resume(returning: (status, text))
                    }
                )
            } catch {
                continuation.resume(throwing: error)
            }
        }
        withExtendedLifetime(driver) {}
        return result

Comment on lines +615 to +629
private struct VideoSurface: View {
@State private var player: AVPlayer

init(url: URL) {
_player = State(initialValue: AVPlayer(url: url))
}

var body: some View {
VideoPlayer(player: player)
.onAppear {
player.isMuted = true
player.play()
}
.onDisappear { player.pause() }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Initializing AVPlayer directly inside the init of VideoSurface is a side-effect that runs every time the view is initialized, even if SwiftUI decides to discard the view or re-evaluate it without changing its identity. To make the view side-effect free and prevent unnecessary resource allocation, initialize the player as an optional @State and instantiate it inside .onAppear.

private struct VideoSurface: View {
    let url: URL
    @State private var player: AVPlayer?

    var body: some View {
        Group {
            if let player {
                VideoPlayer(player: player)
            } else {
                Color.clear
            }
        }
        .onAppear {
            let p = AVPlayer(url: url)
            p.isMuted = true
            p.play()
            player = p
        }
        .onDisappear {
            player?.pause()
            player = nil
        }
    }
}

Comment on lines +485 to +498
decode_latents_to_video(
model_root=args.model_root,
latents_np=preview_latents,
output_path=preview_tmp_path,
fps=args.fps,
device_arg=args.torch_device,
dtype_arg=args.torch_dtype,
backend="taehv",
taehv_source_path=args.taehv_source_path,
taehv_checkpoint_path=args.taehv_checkpoint_path,
taehv_parallel=args.taehv_parallel,
)
preview_tmp_path.replace(preview_path)
del preview_latents, pred_video_f32

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If decode_latents_to_video raises an exception, the temporary file .preview-step-*.tmp.mp4 will be left behind in the preview directory. Wrap the decoding and replacement in a try...finally block to ensure that any temporary files are cleaned up if an error occurs.

Suggested change
decode_latents_to_video(
model_root=args.model_root,
latents_np=preview_latents,
output_path=preview_tmp_path,
fps=args.fps,
device_arg=args.torch_device,
dtype_arg=args.torch_dtype,
backend="taehv",
taehv_source_path=args.taehv_source_path,
taehv_checkpoint_path=args.taehv_checkpoint_path,
taehv_parallel=args.taehv_parallel,
)
preview_tmp_path.replace(preview_path)
del preview_latents, pred_video_f32
try:
decode_latents_to_video(
model_root=args.model_root,
latents_np=preview_latents,
output_path=preview_tmp_path,
fps=args.fps,
device_arg=args.torch_device,
dtype_arg=args.torch_dtype,
backend="taehv",
taehv_source_path=args.taehv_source_path,
taehv_checkpoint_path=args.taehv_checkpoint_path,
taehv_parallel=args.taehv_parallel,
)
preview_tmp_path.replace(preview_path)
finally:
if preview_tmp_path.exists():
preview_tmp_path.unlink()
del preview_latents, pred_video_f32

@aryan5v aryan5v changed the title [feat]: native Apple MLX video studio with live previews [feat]: native Apple MLX studio with Liquid Glass onboarding Jul 10, 2026
@aryan5v

aryan5v commented Jul 10, 2026

Copy link
Copy Markdown
Owner Author

Follow-up 18353035 adds the macOS 26+ product pass requested after the initial PR: native SwiftUI Liquid Glass on navigation and primary controls with older-system material fallbacks, plus a four-step interactive/skippable onboarding flow covering starter prompts, live x0 preview, local privacy/history, real device checks, and Setup/Create handoff. The packaged app was rebuilt and the complete onboarding was exercised through macOS Computer Use. End-to-end generation remains explicitly gated: the configured release repository currently returns RepositoryNotFoundError (404) for the authenticated local Hugging Face client, and no RAW/EMA MLX checkpoint exists on local or mounted volumes.

@aryan5v
aryan5v force-pushed the aryan/fastvideo-mac-studio branch from 1835303 to eea968f Compare July 10, 2026 03:46
@aryan5v

aryan5v commented Jul 11, 2026

Copy link
Copy Markdown
Owner Author

Final native-app polish is now pushed in a0014028 and e39f98c9.

  • Rebuilt Create as a prompt-first FastWan QAD home; generation UI appears only after submission.
  • Refined the generation workspace, visual Library, sidebar, Models & Runtime, and first-run onboarding.
  • Added first-party one-click model installation with EMA recommended by default and RAW optional.
  • Bundled the release catalog and uv runtime support; normal users no longer see Hugging Face or manual repository paths.
  • Added safe archive extraction, streaming download progress, optional SHA-256 verification, and a release-build guard requiring final checksums.
  • Verified Swift build/core self-test, 7 bridge tests, all applicable pre-commit checks, production packaging, strict code-signature validation, and the native UI through Computer Use.

Release gate: before public distribution, publish the shared/EMA/RAW archives at the catalog URLs and replace the blank SHA-256 fields. Local developer builds remain usable for current on-device testing; release builds intentionally fail packaging until the assets are immutable.

Aryan Kumar and others added 3 commits July 21, 2026 09:17
Generate 1/factor of the frames and RIFE-interpolate up to --num-frames on
Apple Silicon (rife-mlx), with a light unsharp to counter softening. ~2.7x
faster denoise at reconstruction MS-SSIM ~0.97. Adds the rife-mlx optional dep,
the fast-mode eval, and docs. Validated end-to-end on the 1.3B INT8 QAD model.

aryan5v commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

Fast generation from draft PR #12 is now integrated into the native app in fc861b77 (stack refreshed to 588aaafd).

  • Fast is the recommended mode for newly prepared runtimes; Full native-frame generation remains selectable.
  • The Swift request persists the mode per generation and the JSONL bridge forwards --fast to the 1.3B MLX entrypoint.
  • The app exposes RIFE progress as “fewer source frames,” “interpolating motion,” and “finishing motion.”
  • Existing history without a mode decodes as Full, so older renders are never mislabeled.
  • Existing runtimes without RIFE automatically fall back to Full and show a dedicated one-click Fast generation installer.
  • RIFE 4.25 is now a first-party catalog asset and the entrypoint accepts a local weights directory, avoiding a surprise Hugging Face fetch in the shipped app.
  • Library cards and details record whether a render used Fast/RIFE or Full.

Validation:

  • apps/fastvideo_mac/scripts/test.sh: Swift build and core self-test passed; 8 bridge tests passed.
  • uvx pre-commit run --files …: all applicable hooks passed.
  • Production app bundle built with the matching Xcode toolchain and passed strict codesign verification.
  • Computer Use verified the prompt menu and Models & Runtime Fast-generation states in the packaged app.

Release asset gate: publish fastwan-qad-rife-4.25.tar.gz at the catalog URL and add its SHA-256 alongside the shared/EMA/RAW checksums before FASTWAN_RELEASE_BUILD=1 packaging.

@aryan5v

aryan5v commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

Local setup repair validation (macOS 27)

Commit dc2f2fbb fixes a setup false-negative where the app required PyTorch MPS even though MLX/Metal owns DiT denoising and the CLI supports CPU fallback for auxiliary stages.

Local artifacts restored and mapped without Hugging Face downloads:

  • EMA MLX INT8: ~/mlx-ckpt-cache-qad-v2-ema/int8
  • RAW MLX INT8: ~/mlx-ckpt-cache-qad-v2/int8
  • RIFE 4.25: mapped from the existing local cache into the V2 EMA model root

Fresh app diagnosis reports Apple Silicon, MLX, Torch, ffmpeg, EMA, RAW, and RIFE ready. The setup and onboarding UI now describe MPS as an optional auxiliary accelerator rather than a runtime requirement.

End-to-end packaged-app results:

  • Fast EMA: 448x256, 33 frames, 16 fps, 37.83 s total, 3.65 s MLX denoise, valid 2.06 s H.264 output
  • Full EMA: 832x480, 81 frames, 16 fps, 127.73 s total, 105.55 s MLX denoise, valid 5.06 s H.264 output
  • The Fast run displayed a playable live x0 preview during denoising
  • Contact-sheet inspection across both outputs confirmed coherent fox/forest motion rather than noise

Validation:

  • apps/fastvideo_mac/scripts/test.sh: Swift build + core self-test + 9 bridge tests passed
  • repository pre-commit hooks passed for all changed FastWan app files
  • packaged app passed codesign --verify --deep --strict

@aryan5v

aryan5v commented Jul 21, 2026

Copy link
Copy Markdown
Owner Author

Public download preparation

Commit 4a0bcb76 adds the Railway-ready landing/download broker and finishes the native app's reset/uninstall experience.

Included

  • Minimal FastWan QAD landing page with fail-closed release state
  • Private Railway Bucket download broker using 15-minute presigned S3 redirects
  • Stable /download/<artifact> routes and generated /catalog.json
  • Multipart artifact uploader that records exact byte sizes and SHA-256 values
  • Self-contained release archive packager that follows local cache symlinks
  • Reset models, runtime, library, settings, or everything with destructive confirmation
  • Real temporary-directory deletion tests that prove unrelated neighboring files survive

Local release artifacts

  • Shared/core: 21,580,051,059 bytes, SHA-256 b26ebdb77b79734f20d8c9788742c758c25c4a49d411b315efee10049974586e
  • EMA: 1,448,469,079 bytes, SHA-256 3a67bc451d08905578afcf4e8e87762e88f2e267193aabac71e44596a8d8ffda
  • RAW: 1,448,469,243 bytes, SHA-256 60b1d83dd85d7817da64ff96a0b783c429714179ab2982e54f92ee075f39b774
  • RIFE 4.25: 21,103,111 bytes, SHA-256 1539fbbc38f82a2debadb616589f9f9aaf9ff403b1e4be66b1911372cc093101

Validation

  • Swift production build and strict ad-hoc signature verification passed
  • Core self-test and 9 bridge tests passed
  • Landing service: 3 tests passed
  • Repository pre-commit hooks passed across all 22 changed files
  • Browser accessibility and viewport inspection passed

External release gates

  • Railway authentication succeeds, but the personal workspace reports that its trial expired and requires plan activation before project/bucket creation.
  • This Mac has no Developer ID signing identity or notarytool profile. The development app works locally, but public distribution still requires Developer ID signing and Apple notarization.

@aryan5v
aryan5v force-pushed the aryan/release/fastwan-qad-int8-1.3b-mlx branch 2 times, most recently from 7ed0356 to 7822aed Compare July 23, 2026 22:59
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.

2 participants