Skip to content

[feat]: add MLX INT8 FastWan-QAD 1.3B inference on Apple Silicon - #12

Draft
aryan5v wants to merge 1 commit into
mainfrom
aryan/release/fastwan-qad-int8-1.3b-mlx
Draft

[feat]: add MLX INT8 FastWan-QAD 1.3B inference on Apple Silicon#12
aryan5v wants to merge 1 commit into
mainfrom
aryan/release/fastwan-qad-int8-1.3b-mlx

Conversation

@aryan5v

@aryan5v aryan5v commented Jul 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add an Apple Silicon MLX runtime for FastWan-QAD 1.3B text-to-video inference with INT8 quantization, pre-quantized checkpoint save/load, DMD sampling, memory controls, and mx.compile support.
  • Add MPS prompt encoding and checksum-verified TAEHV decoding, including the vendored MIT-licensed TAEHV source.
  • Add optional RIFE fast mode through --fast. It generates fewer diffusion frames and uses the Apple-Silicon-native rife-mlx backend to interpolate back to the requested target frame count.
  • Add MLX benchmarks, parity and unit tests, macOS Metal smoke CI, MLX CPU smoke CI, installation guidance, and support-matrix coverage.

Install

brew install ffmpeg
uv venv --python 3.12 --seed
source .venv/bin/activate
uv pip install -e '.[mlx]'

The mlx extra installs both the MLX runtime and rife-mlx required by --fast.

Generate with RIFE fast mode

python examples/inference/basic/mlx_wan_prompt_to_video.py \
  --model-root <FastWan-QAD-INT8-1.3B-model-root> \
  --mlx-checkpoint <model-root>/mlx_dit \
  --prompt 'A fox runs through a misty pine forest.' \
  --num-frames 81 \
  --fast \
  --fast-factor 2 \
  --output-path video_samples/fox_fast.mp4

--num-frames remains the final target length. With the default --fast-factor 2, the runtime generates approximately half the frames and RIFE interpolates the final video to the requested length. Fast mode also supports --fast-sharpen and composes with INT8 quantization, mx.compile, TAEHV, and full Wan VAE decoding.

Test

python -m pytest \
  fastvideo/tests/mlx \
  fastvideo/tests/platforms/test_mps_vsa_error.py \
  -q

python -m fastvideo.mlx_runtime.rife_interp --self-test

Documentation

  • docs/getting_started/installation/mps.md: Apple Silicon installation, model setup, generation, quality mode, and troubleshooting.
  • docs/design/apple_silicon_fast_mode.md: RIFE fast-mode behavior, installation, CLI flags, performance, and quality guidance.
  • docs/inference/support_matrix.md and README.md: runtime support and discoverability.

Validation

  • pre-commit run --from-ref origin/main --to-ref HEAD: passed.
  • python -m pytest fastvideo/tests/mlx fastvideo/tests/platforms/test_mps_vsa_error.py -q: 42 passed on Apple Silicon with MLX 0.31.2 and Torch 2.12.0.
  • git diff --check: passed.
  • Python syntax check across all 24 changed Python files: passed.
  • pyproject.toml parse check: passed.

@coderabbitai

coderabbitai Bot commented Jul 9, 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: 08aa7a26-0e2b-4124-ab69-b67bb8f48085

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/release/fastwan-qad-int8-1.3b-mlx

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.

@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 extensive agent infrastructure, including onboarding guides, contracts, and reusable skills under .agents/, alongside the integration of the Dreamverse real-time video generation and editing platform under apps/dreamverse/. It also updates Buildkite pipelines to support new test suites and cleans up repository configuration files. The code review feedback identifies several critical issues in the Dreamverse prompt enhancer module: first, the initialization of provider runtimes incorrectly requires all API keys to be present, breaking single-provider setups; second, saving prompt configurations erroneously overwrites default git-tracked files instead of utilizing local overrides; and finally, the requested JSON response format is not forwarded to the Cerebras and OpenAI-compatible clients, which can cause parsing failures. All of these comments are highly actionable and should be addressed.

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 +420 to +442
for provider_name in PROMPT_PROVIDER_PRIORITY:
api_key = PROMPT_API_KEYS[provider_name]
api_base_url = PROMPT_API_BASE_URLS[provider_name]
if not isinstance(api_key, str) or not api_key.strip():
env_names = ", ".join(PROMPT_PROVIDER_API_KEY_NAMES[provider_name])
raise RuntimeError("Missing required environment variable: one of "
f"{env_names}")
runtimes.append(
ProviderRuntime(
name=provider_name,
api_key=api_key,
api_base_url=api_base_url,
request_model=self.provider_request_models.get(
provider_name,
self.model,
),
client=self._build_client(
provider=provider_name,
api_key=api_key,
api_base_url=api_base_url,
),
))
return runtimes

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

The current implementation of _build_provider_runtimes iterates over all providers in PROMPT_PROVIDER_PRIORITY (which includes both "cerebras" and "groq") and raises a RuntimeError if any of their API keys are missing. This makes both API keys mandatory at startup, completely breaking single-provider setups and forcing users to supply credentials for providers they do not intend to use.

Instead, the loop should skip any provider whose API key is not set, and only raise an error if no valid providers are configured at all.

        for provider_name in PROMPT_PROVIDER_PRIORITY:
            api_key = PROMPT_API_KEYS[provider_name]
            api_base_url = PROMPT_API_BASE_URLS[provider_name]
            if not isinstance(api_key, str) or not api_key.strip():
                continue
            runtimes.append(
                ProviderRuntime(
                    name=provider_name,
                    api_key=api_key,
                    api_base_url=api_base_url,
                    request_model=self.provider_request_models.get(
                        provider_name,
                        self.model,
                    ),
                    client=self._build_client(
                        provider=provider_name,
                        api_key=api_key,
                        api_base_url=api_base_url,
                    ),
                ))
        if not runtimes:
            raise RuntimeError("No prompt providers configured. Please set at least one API key (e.g., CEREBRAS_API_KEY or GROQ_API_KEY).")
        return runtimes

Comment on lines +548 to +594
_save_prompt(
_resolve_prompt_save_path(
self.enhance_system_prompt_path,
self.enhance_system_prompt_fallback_path,
),
normalized,
"next-segment",
)

if auto_extension_system_prompt is not None:
normalized = auto_extension_system_prompt.strip()
if not normalized:
raise ValueError("auto_extension_system_prompt cannot be empty.")
_save_prompt(
_resolve_prompt_save_path(
self.auto_system_prompt_path,
self.auto_system_prompt_fallback_path,
),
normalized,
"auto-extension",
)

if rewrite_window_system_prompt is not None:
normalized = rewrite_window_system_prompt.strip()
if not normalized:
raise ValueError("rewrite_window_system_prompt cannot be empty.")
_save_prompt(
_resolve_prompt_save_path(
self.rewrite_all_system_prompt_path,
self.rewrite_all_system_prompt_fallback_path,
),
normalized,
"rewrite-window",
)

if rewrite_user_system_prompt is not None:
normalized = rewrite_user_system_prompt.strip()
if not normalized:
raise ValueError("rewrite_user_system_prompt cannot be empty.")
_save_prompt(
_resolve_prompt_save_path(
self.rewrite_user_system_prompt_path,
self.rewrite_user_system_prompt_fallback_path,
),
normalized,
"rewrite-user",
)

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

In save_prompt_config, the code calls _resolve_prompt_save_path to determine where to save the edited system prompts. However, _resolve_prompt_save_path is designed to fall back to the default tracked path (e.g., prompts/next_segment_system_prompt.md) if the local override file in prompts.local/ does not exist yet.

As a result, when a user edits a prompt in the UI for the first time, the changes will overwrite the default git-tracked files in prompts/ instead of creating the local override files in prompts.local/. This defeats the purpose of having a local overlay directory and pollutes the git repository.

To fix this, save_prompt_config should write directly to the primary paths (self.enhance_system_prompt_path, self.auto_system_prompt_path, etc.) without calling _resolve_prompt_save_path.

            _save_prompt(
                self.enhance_system_prompt_path,
                normalized,
                "next-segment",
            )

        if auto_extension_system_prompt is not None:
            normalized = auto_extension_system_prompt.strip()
            if not normalized:
                raise ValueError("auto_extension_system_prompt cannot be empty.")
            _save_prompt(
                self.auto_system_prompt_path,
                normalized,
                "auto-extension",
            )

        if rewrite_window_system_prompt is not None:
            normalized = rewrite_window_system_prompt.strip()
            if not normalized:
                raise ValueError("rewrite_window_system_prompt cannot be empty.")
            _save_prompt(
                self.rewrite_all_system_prompt_path,
                normalized,
                "rewrite-window",
            )

        if rewrite_user_system_prompt is not None:
            normalized = rewrite_user_system_prompt.strip()
            if not normalized:
                raise ValueError("rewrite_user_system_prompt cannot be empty.")
            _save_prompt(
                self.rewrite_user_system_prompt_path,
                normalized,
                "rewrite-user",
            )

Comment on lines +953 to +958
cerebras_body = {
"model": body.get("model"),
"messages": body.get("messages"),
"temperature": body.get("temperature"),
}
cerebras_body = {key: value for key, value in cerebras_body.items() if value is not None}

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

The _build_body method sets "response_format": {"type": "json_object"} to request JSON output from the LLM. However, _request_content_with_body_cerebras does not pass response_format to the Cerebras client's chat.completions.create call.

Without response_format being forwarded to the API, the model is not guaranteed to return JSON, which can lead to parsing failures in _parse_json_response. We should pass response_format to the client if it is present in the body.

        cerebras_body = {
            "model": body.get("model"),
            "messages": body.get("messages"),
            "temperature": body.get("temperature"),
            "response_format": body.get("response_format"),
        }
        cerebras_body = {key: value for key, value in cerebras_body.items() if value is not None}

Comment on lines +974 to +981
openai_body = {
"model": body.get("model"),
"messages": body.get("messages"),
"temperature": body.get("temperature"),
}
max_completion_tokens = body.get("max_completion_tokens")
if max_completion_tokens is not None:
openai_body["max_completion_tokens"] = max_completion_tokens

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

The _build_body method sets "response_format": {"type": "json_object"} to request JSON output from the LLM. However, _request_content_with_body_openai_compatible does not pass response_format to the OpenAI-compatible client's chat.completions.create call.

Without response_format being forwarded to the API, the model is not guaranteed to return JSON, which can lead to parsing failures in _parse_json_response. We should pass response_format to the client if it is present in the body.

Suggested change
openai_body = {
"model": body.get("model"),
"messages": body.get("messages"),
"temperature": body.get("temperature"),
}
max_completion_tokens = body.get("max_completion_tokens")
if max_completion_tokens is not None:
openai_body["max_completion_tokens"] = max_completion_tokens
openai_body = {
"model": body.get("model"),
"messages": body.get("messages"),
"temperature": body.get("temperature"),
"response_format": body.get("response_format"),
}
max_completion_tokens = body.get("max_completion_tokens")
if max_completion_tokens is not None:
openai_body["max_completion_tokens"] = max_completion_tokens

@aryan5v aryan5v self-assigned this Jul 21, 2026
@aryan5v
aryan5v force-pushed the aryan/release/fastwan-qad-int8-1.3b-mlx branch from d60d8e1 to 7ed0356 Compare July 23, 2026 22:35
@aryan5v aryan5v changed the title [feat]: Apple Silicon FastWan QAD 1.3B launch lane [feat]: add MLX INT8 FastWan-QAD 1.3B inference on Apple Silicon Jul 23, 2026
@aryan5v
aryan5v force-pushed the aryan/release/fastwan-qad-int8-1.3b-mlx branch 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.

1 participant