Compare commits
45
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ef47caf86 | ||
|
|
e71d01648c | ||
|
|
e1f3904799 | ||
|
|
02f1ce11ae | ||
|
|
7f03e03dc6 | ||
|
|
e3b88bb12a | ||
|
|
cb66acd400 | ||
|
|
442e2d2e18 | ||
|
|
dd35763ad6 | ||
|
|
e90be598e5 | ||
|
|
ba5e81083c | ||
|
|
76ce9c7fd6 | ||
|
|
08d99c089e | ||
|
|
20751a21aa | ||
|
|
9dd2a837f4 | ||
|
|
93aab45ac2 | ||
|
|
017ce6602d | ||
|
|
a575055eec | ||
|
|
81f3fec7fd | ||
|
|
d265a454bf | ||
|
|
c100c66578 | ||
|
|
8760eb7a06 | ||
|
|
361f919c88 | ||
|
|
8b5377aab2 | ||
|
|
d995516da0 | ||
|
|
f47ad3f5b7 | ||
|
|
10bdf5e076 | ||
|
|
3e26db40b0 | ||
|
|
c73dd0ab55 | ||
|
|
430e52154e | ||
|
|
384eee8aef | ||
|
|
c4824c7764 | ||
|
|
9b0e57fe4b | ||
|
|
0100218594 | ||
|
|
39718cd54d | ||
|
|
37d06a832f | ||
|
|
8839ba8d4d | ||
|
|
61b91220c0 | ||
|
|
316f3876c2 | ||
|
|
614b59543c | ||
|
|
1c14afd559 | ||
|
|
9a3c45779c | ||
|
|
bfc9c01797 | ||
|
|
3a3ad3d209 | ||
|
|
aef4e9b3b1 |
@@ -0,0 +1,76 @@
|
||||
---
|
||||
name: env-var-conventions
|
||||
description: Add, read, rename, or remove an environment variable in FastVideo, or change the environment-variable policy. Use before touching fastvideo/envs.py, os.environ, os.getenv, or monkeypatch.setenv in fastvideo/, and when fastvideo/tests/contract/test_env_policy.py fails.
|
||||
---
|
||||
|
||||
# Environment Variable Conventions
|
||||
|
||||
## Purpose
|
||||
|
||||
FastVideo registers its environment variables as typed fields in
|
||||
`fastvideo/envs.py`. The policy that governs them is
|
||||
`docs/contributing/env_vars.md`, and the contract test
|
||||
`fastvideo/tests/contract/test_env_policy.py` enforces the policy in the unit
|
||||
CI lane. This skill routes an environment-variable change through that policy.
|
||||
The policy doc is the single source of the rules; read it instead of relying
|
||||
on a summary here.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Read `docs/contributing/env_vars.md` in full.
|
||||
- Decide whether the setting belongs in an environment variable or an argument
|
||||
(rule 5 in the policy doc). Settings that users change per deployment are
|
||||
arguments; add them through `fastvideo/fastvideo_args.py` instead.
|
||||
|
||||
## Inputs
|
||||
|
||||
| Parameter | Required | Description |
|
||||
| ---------- | -------- | -------------------------------------------------------------- |
|
||||
| `change` | Yes | Add, read, rename, or remove a variable, or change the policy. |
|
||||
| `variable` | Yes | The variable name, with the `FASTVIDEO_` prefix. |
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Declare or edit the variable in `fastvideo/envs.py`.**
|
||||
- Pick the field type and category that the policy doc lists.
|
||||
- Write a description that states what the variable does and its units.
|
||||
- To rename, keep the old name in `deprecated_names`. To remove, add the
|
||||
name to `DEPRECATED_VARIABLES`. Update the uses in `examples/`,
|
||||
`scripts/`, `docs/`, `apps/`, and the tests.
|
||||
2. **Read the variable with `envs.NAME.get()` inside a function.**
|
||||
- In tests, change the value with `envs.NAME.override(value)`.
|
||||
- Do not call `os.environ`, `os.getenv`, or `monkeypatch.setenv` for a
|
||||
FastVideo variable.
|
||||
- To set a variable that another tool reads, call `envs.set_external`,
|
||||
`envs.setdefault_external`, or `envs.unset_external`.
|
||||
3. **Regenerate the table in the policy doc.**
|
||||
- Run `python fastvideo/tests/contract/test_env_policy.py`.
|
||||
4. **Run the contract test.**
|
||||
- Run `pytest fastvideo/tests/contract/test_env_policy.py`.
|
||||
- When the test reports a fixed known violation, delete or lower its entry
|
||||
in `KNOWN_VIOLATIONS`. Never add an entry to `KNOWN_VIOLATIONS`.
|
||||
5. **When the policy itself changes, update the policy doc and the contract
|
||||
test in the same pull request.**
|
||||
- The rules in `docs/contributing/env_vars.md`, the checks and allowlist in
|
||||
`fastvideo/tests/contract/test_env_policy.py`, and this skill must agree.
|
||||
|
||||
## Outputs
|
||||
|
||||
- A registry entry in `fastvideo/envs.py` and call sites that use
|
||||
`envs.NAME.get()`.
|
||||
- A regenerated table in `docs/contributing/env_vars.md`.
|
||||
- A passing `fastvideo/tests/contract/test_env_policy.py`.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```
|
||||
Add a FASTVIDEO_DEBUG_MY_STAGE switch that logs MyStage inputs.
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- `docs/contributing/env_vars.md`: the policy, the field types, and the
|
||||
violation kinds that the contract test reports.
|
||||
- `fastvideo/envs.py`: the registry.
|
||||
- `fastvideo/tests/contract/test_env_policy.py`: the contract test,
|
||||
`EXTERNAL_ALLOWLIST`, and `KNOWN_VIOLATIONS`.
|
||||
@@ -22,6 +22,12 @@ else
|
||||
export PERF_UPLOAD_POLICY=never
|
||||
fi
|
||||
|
||||
# Alternate GPU backends compare against references without publishing records.
|
||||
# Their worker has read-only Hub credentials; publication is an operator task.
|
||||
if [ "${FASTVIDEO_CI_LOCAL_ONLY:-0}" = 1 ]; then
|
||||
export PERF_UPLOAD_POLICY=never
|
||||
fi
|
||||
|
||||
nvidia-smi \
|
||||
--query-gpu=index,timestamp,clocks.sm,clocks.max.sm,power.draw,power.limit,temperature.gpu \
|
||||
--format=csv -l 10 > "$PERF_REPORTS_DIR/gpu_telemetry.csv" 2>/dev/null &
|
||||
|
||||
@@ -16,6 +16,7 @@ exec pytest \
|
||||
./fastvideo/tests/worker/ \
|
||||
./fastvideo/tests/training/test_trackers.py \
|
||||
./fastvideo/tests/attention/test_sdpa_metadata_mask_contract.py \
|
||||
./fastvideo/tests/attention/test_vsa_h3_tile_grad_safety.py \
|
||||
./fastvideo/tests/modal/test_kernel_build_cache.py \
|
||||
./fastvideo/tests/modal/test_pr_test.py \
|
||||
./fastvideo/tests/modal/test_ssim_test.py \
|
||||
|
||||
@@ -11,6 +11,7 @@ jobs:
|
||||
if: >-
|
||||
github.event.context == 'direct-test-completed'
|
||||
&& github.event.state == 'success'
|
||||
&& (vars.CI_GPU_BACKEND == '' || vars.CI_GPU_BACKEND == 'slurm')
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check and update aggregate status
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
name: Promote Selected GPU Backend Status
|
||||
|
||||
on:
|
||||
status:
|
||||
|
||||
permissions:
|
||||
statuses: write
|
||||
|
||||
concurrency:
|
||||
group: gpu-ci-status-${{ github.event.sha }}-${{ vars.CI_GPU_BACKEND }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
promote:
|
||||
if: >-
|
||||
(vars.CI_GPU_BACKEND == 'modal' || vars.CI_GPU_BACKEND == 'vllm')
|
||||
&& (github.event.context == format('gpu-ci/{0}/fastcheck-passed', vars.CI_GPU_BACKEND)
|
||||
|| github.event.context == format('gpu-ci/{0}/full-suite-passed', vars.CI_GPU_BACKEND))
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
SELECTED_BACKEND: ${{ vars.CI_GPU_BACKEND }}
|
||||
steps:
|
||||
- name: Mirror the selected backend's latest suite results
|
||||
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
script: |
|
||||
const backend = process.env.SELECTED_BACKEND;
|
||||
if (!['modal', 'vllm'].includes(backend)) {
|
||||
throw new Error('Unsupported selected GPU backend');
|
||||
}
|
||||
const sha = context.payload.sha;
|
||||
// Read current state after entering the serialized workflow. A
|
||||
// delayed event must not overwrite a newer failure with success.
|
||||
const statuses = await github.paginate(github.rest.repos.listCommitStatusesForRef, {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: sha,
|
||||
per_page: 100,
|
||||
});
|
||||
for (const suffix of ['fastcheck-passed', 'full-suite-passed']) {
|
||||
const sourceContext = `gpu-ci/${backend}/${suffix}`;
|
||||
const matches = statuses.filter(status => status.context === sourceContext);
|
||||
matches.sort((a, b) =>
|
||||
Date.parse(b.updated_at) - Date.parse(a.updated_at) || b.id - a.id
|
||||
);
|
||||
const latest = matches[0];
|
||||
const state = latest ? latest.state : 'pending';
|
||||
if (!['pending', 'success', 'failure', 'error'].includes(state)) {
|
||||
throw new Error(`Unsupported status state for ${sourceContext}`);
|
||||
}
|
||||
await github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha,
|
||||
context: suffix,
|
||||
state,
|
||||
description: latest
|
||||
? `${backend} ${suffix}: ${state}`
|
||||
: `Waiting for ${backend} ${suffix}`,
|
||||
...(latest && latest.target_url ? {target_url: latest.target_url} : {}),
|
||||
});
|
||||
}
|
||||
@@ -92,6 +92,7 @@ jobs:
|
||||
fastvideo/tests/mlx/test_frame_upsample.py \
|
||||
fastvideo/tests/mlx/test_mlx_fast_spatial.py \
|
||||
fastvideo/tests/mlx/test_mlx_refine.py \
|
||||
fastvideo/tests/mlx/test_mlx_prompt_enhance.py \
|
||||
fastvideo/tests/mlx/test_mlx_prompt_to_video_decode.py \
|
||||
fastvideo/tests/mlx/test_mlx_wan22_prompt_cache_fingerprint.py \
|
||||
fastvideo/tests/mlx/test_wan22_sample.py \
|
||||
@@ -156,6 +157,7 @@ jobs:
|
||||
fastvideo/tests/mlx/test_frame_upsample.py \
|
||||
fastvideo/tests/mlx/test_mlx_fast_spatial.py \
|
||||
fastvideo/tests/mlx/test_mlx_refine.py \
|
||||
fastvideo/tests/mlx/test_mlx_prompt_enhance.py \
|
||||
fastvideo/tests/mlx/test_mlx_prompt_to_video_decode.py \
|
||||
fastvideo/tests/mlx/test_mlx_wan22_prompt_cache_fingerprint.py \
|
||||
fastvideo/tests/mlx/test_wan22_sample.py \
|
||||
|
||||
@@ -203,7 +203,8 @@ jobs:
|
||||
docker buildx imagetools create "${TAG_ARGS[@]}" "${IMAGE_REFS[@]}"
|
||||
docker buildx imagetools inspect "${TAGS[0]}"
|
||||
|
||||
# The CI runner is ARM64 like DGX Spark, but targets sm_100 rather than sm_121.
|
||||
# The CI runner is ARM64 like DGX Spark, but targets sm_100a rather than sm_121.
|
||||
# The architecture-specific target includes the GB200 VSA CUDA extensions.
|
||||
# Publish a single-architecture variant so the self-hosted CI runner can reuse
|
||||
# the exact prebuilt kernel instead of compiling it in every job.
|
||||
build-ci-runner-image:
|
||||
@@ -219,7 +220,7 @@ jobs:
|
||||
PYTHON_VERSION=3.12
|
||||
CUDA_VERSION=13.0.0
|
||||
UV_TORCH_BACKEND=cu130
|
||||
TORCH_CUDA_ARCH_LIST=10.0
|
||||
TORCH_CUDA_ARCH_LIST=10.0a
|
||||
CMAKE_BUILD_PARALLEL_LEVEL=1
|
||||
FLASH_ATTN_WHEEL_TAG=cu130torch2.12
|
||||
FLASH_ATTN_WHEEL_RELEASE_ARM64=https://github.com/mjun0812/flash-attention-prebuild-wheels/releases/download/v0.9.22
|
||||
|
||||
@@ -176,11 +176,11 @@ jobs:
|
||||
# the main extension for the full arch list. CMAKE_BUILD_PARALLEL_LEVEL caps
|
||||
# Ninja so heavy CUTLASS/TK template TUs don't OOM the 16 GB runner (exit 143).
|
||||
if [ "${{ matrix.platform.arch }}" = "aarch64" ]; then
|
||||
export TORCH_CUDA_ARCH_LIST="10.0a;12.0a"
|
||||
export TORCH_CUDA_ARCH_LIST="10.0a;10.3a;12.0a"
|
||||
export CMAKE_ARGS="${CMAKE_ARGS:-} -DFASTVIDEO_KERNEL_BUILD_TK=OFF -DFASTVIDEO_KERNEL_BUILD_ATTN_QAT_INFER=ON"
|
||||
export CMAKE_BUILD_PARALLEL_LEVEL=1
|
||||
elif [ "${{ matrix.torch-cuda.torch-cuda-short }}" = "cu130" ]; then
|
||||
export TORCH_CUDA_ARCH_LIST="9.0a;10.0a;12.0a"
|
||||
export TORCH_CUDA_ARCH_LIST="9.0a;10.0a;10.3a;12.0a"
|
||||
export CMAKE_ARGS="${CMAKE_ARGS:-} -DFASTVIDEO_KERNEL_BUILD_TK=ON -DFASTVIDEO_KERNEL_BUILD_ATTN_QAT_INFER=ON -DCMAKE_CUDA_ARCHITECTURES=90a"
|
||||
# A single FP4 TU (attn_qat_infer) can use ~8-12 GB on its own, so serialize.
|
||||
export CMAKE_BUILD_PARALLEL_LEVEL=1
|
||||
|
||||
@@ -66,14 +66,18 @@ Local guidance lives next to the code. Read the in-scope file before editing:
|
||||
| `fastvideo/AGENTS.md` | Core package map, public API, registry-driven model dispatch |
|
||||
| `fastvideo/configs/AGENTS.md` | Arch + pipeline config dataclasses, `param_names_mapping` |
|
||||
| `fastvideo/models/AGENTS.md` | DiT / VAE / encoder / scheduler / loader layout (pre-commit excluded) |
|
||||
| `fastvideo/models/wan/AGENTS.md` | Wan family-local transformers, VAE, configs, and the SP sharding invariant |
|
||||
| `fastvideo/layers/AGENTS.md` | Tensor-parallel linear/attention layer rules for ports |
|
||||
| `fastvideo/attention/AGENTS.md` | Backend registry + env-var override |
|
||||
| `fastvideo/pipelines/AGENTS.md` | Stage ABC, `basic/<model>/`, `preprocess/`, presets |
|
||||
| `fastvideo/pipelines/basic/wan/AGENTS.md` | Wan sampling stages, first-frame conditioning, DMD/causal boundaries |
|
||||
| `fastvideo/pipelines/basic/magi_human/AGENTS.md` | MagiHuman umbrella repo, lazy-loaded components, packing invariants |
|
||||
| `fastvideo/training/AGENTS.md` | Legacy monolithic pipelines (frozen for existing models) |
|
||||
| `fastvideo/train/AGENTS.md` | New modular trainer (methods × models × callbacks, YAML) |
|
||||
| `fastvideo/tests/AGENTS.md` | Test taxonomy, conftest, pre-commit-excluded path |
|
||||
| `fastvideo/tests/ssim/AGENTS.md` | GPU SSIM regression authoring + reference video sync |
|
||||
| `scripts/checkpoint_conversion/AGENTS.md` | Adding a converter for a new HF/official checkpoint |
|
||||
| `apps/dreamverse/AGENTS.md` | DreamVerse app structure and conventions |
|
||||
|
||||
## Critical: Two Training Stacks Coexist
|
||||
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
**FastVideo is a unified post-training and real-time inference framework for accelerated video generation.**
|
||||
|
||||
## NEWS
|
||||
- `2026/09/15`: Release [FastH3 8-Step V2](https://huggingface.co/FastVideo/FastVideo-FastH3-8-Step-V2), an eight-forward data-free DMD2 checkpoint distilled from MiniMax-H3 with 80% Video Sparse Attention. Run it with `examples/inference/basic/basic_fasth3_8step.py` or the [FastH3 8-Step V2 recipe](https://haoailab.com/FastVideo/cookbook/minimax-h3/).
|
||||
- `2026/09/01`: FastH3 now runs locally on Apple Silicon through MLX and on NVIDIA DGX Spark through CUDA 13, including two-Spark inference. Follow the [FastH3 recipes](https://haoailab.com/FastVideo/cookbook/minimax-h3/) and read the [Blog](https://haoailab.com/blogs/fasth3-local/).
|
||||
- `2026/08/27`: [FastH3 Preview v1](https://haoailab.com/blogs/fasth3-preview/) is an open-weight 4-step sparse-distilled MiniMax-H3 model for synchronized video-and-audio generation, developed in collaboration with [Nuva Lab](https://nuvalab.ai/) and the [NVIDIA FastGen team](https://github.com/NVlabs/FastGen). Download the recommended [VSA / Data-Free weights](https://huggingface.co/FastVideo/FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree), or see the [full FastH3 collection](https://huggingface.co/collections/FastVideo/fastvideo-fasth3).
|
||||
- `2026/08/19`: FastVideo now supports MLX on Apple Silicon with [FastMetal-QAD](https://huggingface.co/collections/FastVideo/fastmetal), a family of 1.3B, 5B, and 14B models optimized for Mac—follow the [Apple Silicon guide](https://hao-ai-lab.github.io/FastVideo/getting_started/installation/mps/) and read the [Blog](https://haoailab.com/blogs/fastmetal/).
|
||||
- `2026/08/19`: FastVideo now supports MLX on Apple Silicon with [FastMetal-QAD](https://huggingface.co/collections/FastVideo/fastmetal), a family of 1.3B, 5B, and 14B models optimized for Mac. Follow the [MLX install guide](https://hao-ai-lab.github.io/FastVideo/getting_started/installation/mlx/) and read the [Blog](https://haoailab.com/blogs/fastmetal/).
|
||||
- `2026/06/23`: Release FastWan-QAD: 5s of Video generated in 1.8s E2E. See the [FastWan-QAD models](https://huggingface.co/FastVideo/FastWan-QAD-FP8-1.3B), [Attn-QAT training guide](https://haoailab.com/FastVideo/training/attn_qat/), and [blog](https://haoailab.com/blogs/fastwan-qad/).
|
||||
- `2026/03/17`: Release demo: Into the Dreamverse: Vibe Directing in FastVideo, check out the [Blog](https://haoailab.com/blogs/dreamverse/).
|
||||
- `2026/03/13`: Release demo: Create a 5s 1080p Video in 4.5s with FastVideo on a Single GPU, check out the [Blog](https://haoailab.com/blogs/fastvideo_realtime_1080p/).
|
||||
@@ -63,13 +64,12 @@ UV_TORCH_BACKEND=cu126 uv pip install fastvideo
|
||||
```
|
||||
|
||||
Use `UV_TORCH_BACKEND=cu130` on CUDA 13. Apple silicon users should follow the
|
||||
[MPS installation guide](https://hao-ai-lab.github.io/FastVideo/getting_started/installation/mps/).
|
||||
[MLX install guide](https://hao-ai-lab.github.io/FastVideo/getting_started/installation/mlx/).
|
||||
|
||||
> **On an Apple Silicon Mac?** FastVideo runs FastMetal-QAD through an MLX
|
||||
> runtime. Install with `uv pip install -e '.[mlx]'`, download
|
||||
> [`FastVideo/FastMetal-1.3B-QAD`](https://huggingface.co/FastVideo/FastMetal-1.3B-QAD),
|
||||
> and follow the
|
||||
> [Apple Silicon guide](https://hao-ai-lab.github.io/FastVideo/getting_started/installation/mps/).
|
||||
> **On an Apple Silicon Mac?** Install with `uv pip install -e '.[mlx]'` from
|
||||
> a clone, then pick a recipe in the
|
||||
> [cookbook](https://haoailab.com/FastVideo/cookbook/). See the
|
||||
> [MLX install guide](https://hao-ai-lab.github.io/FastVideo/getting_started/installation/mlx/).
|
||||
|
||||
Please see our [docs](https://hao-ai-lab.github.io/FastVideo/getting_started/installation/) for more detailed installation instructions.
|
||||
|
||||
@@ -87,7 +87,7 @@ Install FastVideo (https://github.com/hao-ai-lab/FastVideo) into a fresh uv virt
|
||||
https://hao-ai-lab.github.io/FastVideo/getting_started/installation/):
|
||||
- NVIDIA GPU, x86_64 -> docs/getting_started/installation/gpu.md
|
||||
- NVIDIA DGX Spark / GB10, aarch64, CUDA 13 -> docs/getting_started/installation/spark.md
|
||||
- Apple Silicon, macOS -> docs/getting_started/installation/mps.md
|
||||
- Apple Silicon, macOS -> docs/getting_started/installation/mlx.md
|
||||
3. Use uv for every step. If a command fails, debug it and tell me what you changed.
|
||||
4. Verify the result:
|
||||
python -c "import fastvideo, torch; print('cuda', torch.cuda.is_available())"
|
||||
|
||||
@@ -331,11 +331,11 @@ def test_rewrite_prompt_sequence_accepts_numbered_prose_output():
|
||||
]
|
||||
|
||||
|
||||
def test_enhance_prompt_prefers_cerebras_before_groq_fallback():
|
||||
def test_enhance_prompt_uses_groq_when_it_returns_first():
|
||||
enhancer = _build_staged_enhancer(
|
||||
cerebras_payload=_chat_payload_with_content('{"prompt":"Cerebras prompt"}'),
|
||||
groq_payload=_chat_payload_with_content('{"prompt":"Groq prompt"}'),
|
||||
cerebras_delay_s=0.01,
|
||||
cerebras_delay_s=0.08,
|
||||
groq_delay_s=0.01,
|
||||
)
|
||||
|
||||
@@ -346,12 +346,12 @@ def test_enhance_prompt_prefers_cerebras_before_groq_fallback():
|
||||
|
||||
assert result.fallback_used is False
|
||||
assert result.error is None
|
||||
assert result.provider == "cerebras"
|
||||
assert result.provider == "groq"
|
||||
assert result.model == "gpt-test"
|
||||
assert result.prompt == "Cerebras prompt"
|
||||
assert result.prompt == "Groq prompt"
|
||||
assert enhancer.get_provider_success_counts() == {
|
||||
"cerebras": 1,
|
||||
"groq": 0,
|
||||
"cerebras": 0,
|
||||
"groq": 1,
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { skipWithoutMock } from './helpers';
|
||||
|
||||
test.describe('create job interactions', () => {
|
||||
skipWithoutMock();
|
||||
|
||||
for (const jobType of ['inference', 'finetuning', 'distillation']) {
|
||||
test(`${jobType} remains interactive after repeated dialog dismissals`, async ({ page }) => {
|
||||
await page.goto(`/${jobType}`);
|
||||
const trigger = page.getByRole('button', { name: 'Create Job', exact: true });
|
||||
const dialog = page.getByRole('dialog');
|
||||
|
||||
// Exercise both dismissal paths and reopen without reloading the page.
|
||||
for (const closeWithEscape of [false, true]) {
|
||||
await trigger.click();
|
||||
await page.getByRole('menuitem').first().click();
|
||||
await expect(dialog).toBeVisible();
|
||||
if (closeWithEscape) {
|
||||
await page.keyboard.press('Escape');
|
||||
} else {
|
||||
await dialog.getByRole('button', { name: 'Close', exact: true }).click();
|
||||
}
|
||||
await expect(dialog).toBeHidden();
|
||||
await expect(page.locator('body')).toHaveCSS('pointer-events', 'auto');
|
||||
await expect(trigger).toBeFocused();
|
||||
}
|
||||
|
||||
await page.getByRole('link', { name: 'Datasets', exact: true }).click();
|
||||
await expect(page).toHaveURL(/\/datasets$/);
|
||||
});
|
||||
}
|
||||
|
||||
test('preserves keyboard menu dismissal and dialog focus trapping', async ({ page }) => {
|
||||
await page.goto('/inference');
|
||||
const trigger = page.getByRole('button', { name: 'Create Job', exact: true });
|
||||
await trigger.focus();
|
||||
await page.keyboard.press('Enter');
|
||||
const firstItem = page.getByRole('menuitem').first();
|
||||
await expect(firstItem).toBeFocused();
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(page.getByRole('menu')).toBeHidden();
|
||||
await expect(trigger).toBeFocused();
|
||||
await expect(page.locator('body')).toHaveCSS('pointer-events', 'auto');
|
||||
|
||||
await page.keyboard.press('Enter');
|
||||
await expect(firstItem).toBeFocused();
|
||||
await page.keyboard.press('Enter');
|
||||
const dialog = page.getByRole('dialog');
|
||||
await expect(dialog).toBeVisible();
|
||||
await expect(dialog.getByLabel('Name (optional)')).toBeFocused();
|
||||
|
||||
// Shift+Tab from the first field wraps to Close, then Tab wraps back.
|
||||
await page.keyboard.press('Shift+Tab');
|
||||
await expect(dialog.getByRole('button', { name: 'Close', exact: true })).toBeFocused();
|
||||
await page.keyboard.press('Tab');
|
||||
await expect(dialog.getByLabel('Name (optional)')).toBeFocused();
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(dialog).toBeHidden();
|
||||
await expect(trigger).toBeFocused();
|
||||
await expect(page.locator('body')).toHaveCSS('pointer-events', 'auto');
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,6 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { skipWithoutMock } from './helpers';
|
||||
import { API_BASE, skipWithoutMock } from './helpers';
|
||||
|
||||
/**
|
||||
* Create-job flow: open the Create Job modal on /inference, fill the prompt
|
||||
@@ -10,7 +10,8 @@ import { skipWithoutMock } from './helpers';
|
||||
test.describe('create inference job', () => {
|
||||
skipWithoutMock();
|
||||
|
||||
test('creates a T2V job and shows it in the queue', async ({ page }) => {
|
||||
test('creates a T2V job and starts it without refreshing', async ({ page, request }) => {
|
||||
await request.put(`${API_BASE}/settings`, { data: { autoStartJob: false } });
|
||||
await page.goto('/inference');
|
||||
|
||||
// The trigger opens a real menu on click, so this path works for touch,
|
||||
@@ -38,5 +39,20 @@ test.describe('create inference job', () => {
|
||||
// Modal closes and the queue refreshes with the newly created job.
|
||||
await expect(dialog).toBeHidden();
|
||||
await expect(page.getByText(prompt)).toBeVisible();
|
||||
await expect(page.locator('body')).toHaveCSS('pointer-events', 'auto');
|
||||
|
||||
const card = page.getByRole('article').filter({ hasText: prompt });
|
||||
await expect(card.getByText('pending', { exact: true })).toBeVisible();
|
||||
const started = page.waitForResponse((response) =>
|
||||
response.url().startsWith(`${API_BASE}/jobs/`) &&
|
||||
response.url().endsWith('/start') &&
|
||||
response.request().method() === 'POST',
|
||||
);
|
||||
await card.getByRole('button', { name: 'Start', exact: true }).click();
|
||||
expect((await started).ok()).toBe(true);
|
||||
await expect(card.getByText('running', { exact: true })).toBeVisible();
|
||||
|
||||
await page.getByRole('link', { name: 'Datasets', exact: true }).click();
|
||||
await expect(page).toHaveURL(/\/datasets$/);
|
||||
});
|
||||
});
|
||||
|
||||
Generated
+868
-722
File diff suppressed because it is too large
Load Diff
@@ -17,20 +17,11 @@
|
||||
"start:all": "concurrently --kill-others-on-fail \"npm:start:api\" \"npm:start:web\""
|
||||
},
|
||||
"dependencies": {
|
||||
"@radix-ui/react-dialog": "^1.1.0",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.24",
|
||||
"@radix-ui/react-label": "^2.1.8",
|
||||
"@radix-ui/react-scroll-area": "^1.2.10",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slider": "^1.2.0",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-switch": "^1.1.0",
|
||||
"@radix-ui/react-tabs": "^1.1.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"lucide-react": "^0.577.0",
|
||||
"next": "15.5.18",
|
||||
"radix-ui": "^1.6.7",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"sonner": "^2.0.7",
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import CreateJobButton from './CreateJobButton';
|
||||
import { getDatasets, getModels } from '@/lib/api';
|
||||
|
||||
vi.mock('./CreateJobModal', () => ({
|
||||
default: ({
|
||||
isOpen,
|
||||
workloadType,
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
workloadType: string;
|
||||
}) =>
|
||||
isOpen ? (
|
||||
<div role="dialog" data-workload-type={workloadType}>
|
||||
Create job form
|
||||
</div>
|
||||
) : null,
|
||||
vi.mock('@/lib/api', () => ({
|
||||
createJob: vi.fn(),
|
||||
getModels: vi.fn(),
|
||||
getDatasets: vi.fn(),
|
||||
uploadImage: vi.fn(),
|
||||
getSettings: vi.fn(),
|
||||
updateSettings: vi.fn(),
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
vi.mocked(getModels).mockResolvedValue([
|
||||
{ id: 'wan/t2v-1.3b', label: 'Wan T2V' },
|
||||
]);
|
||||
vi.mocked(getDatasets).mockResolvedValue([]);
|
||||
});
|
||||
|
||||
describe('CreateJobButton', () => {
|
||||
it('opens the workload menu on click and selects an item', async () => {
|
||||
const user = userEvent.setup();
|
||||
@@ -27,10 +29,9 @@ describe('CreateJobButton', () => {
|
||||
await user.click(screen.getByRole('button', { name: 'Create Job' }));
|
||||
await user.click(screen.getByRole('menuitem', { name: /I2V/i }));
|
||||
|
||||
expect(screen.getByRole('dialog')).toHaveAttribute(
|
||||
'data-workload-type',
|
||||
'i2v',
|
||||
);
|
||||
expect(
|
||||
screen.getByRole('dialog', { name: 'New Inference Job (I2V)' }),
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('opens and operates the workload menu from the keyboard', async () => {
|
||||
@@ -45,9 +46,42 @@ describe('CreateJobButton', () => {
|
||||
expect(firstItem).toHaveFocus();
|
||||
await user.keyboard('{Enter}');
|
||||
|
||||
expect(screen.getByRole('dialog')).toHaveAttribute(
|
||||
'data-workload-type',
|
||||
't2v',
|
||||
expect(
|
||||
screen.getByRole('dialog', { name: 'New Inference Job (T2V)' }),
|
||||
).toBeInTheDocument();
|
||||
await user.keyboard('{Escape}');
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument(),
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(document.body.style.pointerEvents).not.toBe('none'),
|
||||
);
|
||||
expect(trigger).toHaveFocus();
|
||||
});
|
||||
|
||||
it.each(['inference', 'finetuning', 'distillation'] as const)(
|
||||
'restores page interaction after closing the real %s dialog',
|
||||
async (jobType) => {
|
||||
const user = userEvent.setup();
|
||||
render(<CreateJobButton jobType={jobType} />);
|
||||
const trigger = screen.getByRole('button', { name: 'Create Job' });
|
||||
|
||||
// Keep the real Dialog mounted: mocking it hides conflicting Radix layers.
|
||||
for (let attempt = 0; attempt < 2; attempt++) {
|
||||
await user.click(trigger);
|
||||
await user.click(screen.getAllByRole('menuitem')[0]);
|
||||
const dialog = screen.getByRole('dialog');
|
||||
await user.click(
|
||||
within(dialog).getByRole('button', { name: 'Close' }),
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByRole('dialog')).not.toBeInTheDocument(),
|
||||
);
|
||||
await waitFor(() =>
|
||||
expect(document.body.style.pointerEvents).not.toBe('none'),
|
||||
);
|
||||
expect(trigger).toHaveFocus();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import * as React from 'react';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
||||
import { DropdownMenu } from 'radix-ui';
|
||||
|
||||
import CreateJobModal from '@/components/jobs/CreateJobModal';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -16,6 +16,7 @@ interface CreateJobButtonProps {
|
||||
|
||||
export default function CreateJobButton({ jobType }: CreateJobButtonProps) {
|
||||
const options = WORKLOAD_OPTIONS[jobType] ?? [];
|
||||
const triggerRef = React.useRef<HTMLButtonElement>(null);
|
||||
|
||||
const [modalOpen, setModalOpen] = React.useState(false);
|
||||
const [workloadType, setWorkloadType] = React.useState(
|
||||
@@ -36,7 +37,7 @@ export default function CreateJobButton({ jobType }: CreateJobButtonProps) {
|
||||
<>
|
||||
<DropdownMenu.Root>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<Button type="button" className="gap-1.5">
|
||||
<Button ref={triggerRef} type="button" className="gap-1.5">
|
||||
Create Job
|
||||
<ChevronDown className="size-3.5 opacity-85" aria-hidden />
|
||||
</Button>
|
||||
@@ -66,6 +67,11 @@ export default function CreateJobButton({ jobType }: CreateJobButtonProps) {
|
||||
<CreateJobModal
|
||||
isOpen={modalOpen}
|
||||
onClose={() => setModalOpen(false)}
|
||||
onCloseAutoFocus={(event) => {
|
||||
// This dialog opens from a menu item, so it has no DialogTrigger.
|
||||
event.preventDefault();
|
||||
triggerRef.current?.focus();
|
||||
}}
|
||||
onSuccess={handleSuccess}
|
||||
jobType={jobType}
|
||||
workloadType={workloadType}
|
||||
|
||||
@@ -55,6 +55,9 @@ import { jobToFormFields, type JobLike } from '@/lib/jobToFields';
|
||||
export interface CreateJobModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onCloseAutoFocus?: React.ComponentProps<
|
||||
typeof DialogContent
|
||||
>['onCloseAutoFocus'];
|
||||
onSuccess: () => void;
|
||||
jobType: JobType;
|
||||
workloadType: string;
|
||||
@@ -67,6 +70,7 @@ export interface CreateJobModalProps {
|
||||
export default function CreateJobModal({
|
||||
isOpen,
|
||||
onClose,
|
||||
onCloseAutoFocus,
|
||||
onSuccess,
|
||||
jobType,
|
||||
workloadType,
|
||||
@@ -644,6 +648,7 @@ export default function CreateJobModal({
|
||||
>
|
||||
<DialogContent
|
||||
className="max-h-[90vh] w-[90vw] max-w-[850px] overflow-y-auto"
|
||||
onCloseAutoFocus={onCloseAutoFocus}
|
||||
onEscapeKeyDown={(e) => {
|
||||
if (isSubmitting) e.preventDefault();
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import * as React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
|
||||
import { Button } from './button';
|
||||
import { Input } from './input';
|
||||
@@ -8,6 +10,24 @@ import { Slider } from './slider';
|
||||
import { Switch } from './switch';
|
||||
|
||||
describe('shared control accessibility', () => {
|
||||
it('forwards refs and click handlers to the asChild button', async () => {
|
||||
const user = userEvent.setup();
|
||||
const ref = React.createRef<HTMLButtonElement>();
|
||||
const onClick = vi.fn();
|
||||
render(
|
||||
<Button asChild ref={ref} onClick={onClick}>
|
||||
<button type="button">Slotted action</button>
|
||||
</Button>,
|
||||
);
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Slotted action' });
|
||||
expect(screen.getAllByRole('button')).toHaveLength(1);
|
||||
expect(ref.current).toBe(button);
|
||||
await user.click(button);
|
||||
expect(onClick).toHaveBeenCalledTimes(1);
|
||||
expect(button).toHaveFocus();
|
||||
});
|
||||
|
||||
it('keeps button, input, and select targets at least 44px tall', () => {
|
||||
render(
|
||||
<>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { Slot } from "@radix-ui/react-slot";
|
||||
import { Slot } from "radix-ui";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -37,7 +37,7 @@ export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElemen
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
const Comp = asChild ? Slot.Root : "button";
|
||||
return <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />;
|
||||
});
|
||||
Button.displayName = "Button";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
||||
import { Dialog as DialogPrimitive } from 'radix-ui';
|
||||
import { X } from 'lucide-react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as LabelPrimitive from '@radix-ui/react-label';
|
||||
import { Label as LabelPrimitive } from 'radix-ui';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
|
||||
import { ScrollArea as ScrollAreaPrimitive } from 'radix-ui';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as SelectPrimitive from '@radix-ui/react-select';
|
||||
import { Select as SelectPrimitive } from 'radix-ui';
|
||||
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
||||
import { Separator as SeparatorPrimitive } from 'radix-ui';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as SliderPrimitive from '@radix-ui/react-slider';
|
||||
import { Slider as SliderPrimitive } from 'radix-ui';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as SwitchPrimitives from '@radix-ui/react-switch';
|
||||
import { Switch as SwitchPrimitives } from 'radix-ui';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import * as TabsPrimitive from '@radix-ui/react-tabs';
|
||||
import { Tabs as TabsPrimitive } from 'radix-ui';
|
||||
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": 8,
|
||||
"version": 11,
|
||||
"recipes": [
|
||||
{
|
||||
"id": "fastwan21-t2v",
|
||||
@@ -445,13 +445,13 @@
|
||||
{
|
||||
"id": "fasth3-preview-cuda",
|
||||
"group": "fasth3-preview",
|
||||
"group_label": "FastH3 Preview",
|
||||
"group_label": "FastH3 V1",
|
||||
"group_task": "4-step text to video + audio",
|
||||
"family": "minimax_h3",
|
||||
"stage": "inference",
|
||||
"task": "Few-step text to video (with audio)",
|
||||
"label": "FastH3 Preview on CUDA",
|
||||
"summary": "Run the DMD2-distilled FastH3 Preview with four DiT forwards, trained H3 sparse attention, compiled decode, and synchronized audio.",
|
||||
"label": "FastH3 V1 on CUDA",
|
||||
"summary": "Run FastH3 V1 with four DiT forwards, trained H3 sparse attention, compiled decode, and synchronized audio.",
|
||||
"model": "FastVideo/FastVideo-Minimax-FastH3-Preview-v0.2",
|
||||
"source": "examples/inference/basic/basic_fasth3.py",
|
||||
"serving": {
|
||||
@@ -484,13 +484,13 @@
|
||||
"prepare": "hf download FastVideo/FastVideo-Minimax-FastH3-Preview-v0.2 --local-dir ./FastH3-Preview-v0.2\npython scripts/checkpoint_conversion/convert_minimax_h3_mlx.py --model-root ./FastH3-Preview-v0.2/transformer --out ./FastH3-MLX --formats \"int6\""
|
||||
},
|
||||
"group": "fasth3-preview",
|
||||
"group_label": "FastH3 Preview",
|
||||
"group_label": "FastH3 V1",
|
||||
"group_task": "4-step text to video + audio",
|
||||
"family": "minimax_h3",
|
||||
"stage": "inference",
|
||||
"task": "Few-step text to video (with audio)",
|
||||
"label": "FastH3 Preview on MLX",
|
||||
"summary": "Run FastH3 Preview on Apple Silicon with a locally converted INT6 DiT, streamed Qwen3-VL conditioning, and native MLX video and audio VAEs.",
|
||||
"label": "FastH3 V1 on MLX",
|
||||
"summary": "Run FastH3 V1 on Apple Silicon with a locally converted INT6 DiT, streamed Qwen3-VL conditioning, and native MLX video and audio VAEs.",
|
||||
"model": "FastVideo/FastVideo-Minimax-FastH3-Preview-v0.2",
|
||||
"source": "examples/inference/basic/mlx_fasth3.py",
|
||||
"command": "hf download FastVideo/FastVideo-Minimax-FastH3-Preview-v0.2 --local-dir ./FastH3-Preview-v0.2\npython scripts/checkpoint_conversion/convert_minimax_h3_mlx.py --model-root ./FastH3-Preview-v0.2/transformer --out ./FastH3-MLX --formats \"int6\"\npython examples/inference/basic/mlx_fasth3.py --model-root ./FastH3-Preview-v0.2 --mlx-checkpoint ./FastH3-MLX/int6 --prompt \"(S1) A presenter says <d>[English] FastVideo runs FastH3.</d>\" --height 480 --width 832 --num-frames 124 --seed 2026 --output-path ./outputs/fasth3_int6.mp4",
|
||||
@@ -516,13 +516,13 @@
|
||||
{
|
||||
"id": "fasth3-preview-spark",
|
||||
"group": "fasth3-preview",
|
||||
"group_label": "FastH3 Preview",
|
||||
"group_label": "FastH3 V1",
|
||||
"group_task": "4-step text to video + audio",
|
||||
"family": "minimax_h3",
|
||||
"stage": "inference",
|
||||
"task": "Few-step text to video (with audio)",
|
||||
"label": "FastH3 Preview on one DGX Spark",
|
||||
"summary": "Run FastH3 Preview on one GB10 with Triton VSA, FA4 off, and lazy module load. Height, width, frames, and steps in the YAML are examples.",
|
||||
"label": "FastH3 V1 on one DGX Spark",
|
||||
"summary": "Run FastH3 V1 on one GB10 with Triton VSA, FA4 off, and lazy module load. Height, width, frames, and steps in the YAML are examples.",
|
||||
"model": "FastVideo/FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree",
|
||||
"source": "examples/inference/basic/basic_fasth3_spark.yaml",
|
||||
"serving": {
|
||||
@@ -551,12 +551,12 @@
|
||||
{
|
||||
"id": "fasth3-spark-pair",
|
||||
"group": "fasth3-preview",
|
||||
"group_label": "FastH3 Preview",
|
||||
"group_label": "FastH3 V1",
|
||||
"group_task": "4-step text to video + audio",
|
||||
"family": "minimax_h3",
|
||||
"stage": "inference",
|
||||
"task": "Few-step text to video (with audio)",
|
||||
"label": "FastH3 Preview on two DGX Sparks",
|
||||
"label": "FastH3 V1 on two DGX Sparks",
|
||||
"summary": "Run one FastH3 clip across two GB10s with Ray sequence parallel over QSFP RoCE. Sequential load and lazy module load stay on because SP replicates the DiT on each node.",
|
||||
"model": "FastVideo/FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree",
|
||||
"source": "examples/inference/basic/basic_fasth3_spark_pair.yaml",
|
||||
@@ -578,6 +578,83 @@
|
||||
"Height, width, frames, and steps in the YAML are examples. Edit them or pass CLI flags. See docs/getting_started/installation/spark_pair.md."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "fasth3-8step-v2-cuda",
|
||||
"group": "fasth3-8step-v2",
|
||||
"group_label": "FastH3 V2",
|
||||
"group_task": "8-step text to video + audio",
|
||||
"family": "minimax_h3",
|
||||
"stage": "inference",
|
||||
"task": "Few-step text to video (with audio)",
|
||||
"label": "FastH3 V2 on CUDA",
|
||||
"summary": "Run FastH3 V2, the eight-forward checkpoint (video/audio shifts 10/3, VSA 0.8, 64-token tiles) with the trained DMD ladder loaded from the checkpoint's fastvideo_inference.json.",
|
||||
"model": "FastVideo/FastVideo-FastH3-8-Step-V2",
|
||||
"source": "examples/inference/basic/basic_fasth3_8step.py",
|
||||
"serving": {
|
||||
"source": "examples/serving/openai_fasth3_8step.yaml",
|
||||
"install": "UV_TORCH_BACKEND=cu130 uv pip install -e \".[fasth3]\""
|
||||
},
|
||||
"command": "UV_TORCH_BACKEND=cu130 uv pip install -e \".[fasth3]\"\npython examples/inference/basic/basic_fasth3_8step.py --prompt \"(S1) A presenter says <d>[English] FastVideo runs FastH3.</d>\" --profile strict --no-inference-torch-compile --no-compile-vae",
|
||||
"gpu_types": ["NVIDIA"],
|
||||
"hardware": {
|
||||
"platform": "cuda",
|
||||
"gpu_count": 4,
|
||||
"accelerator": "NVIDIA GB200",
|
||||
"evidence": "validated",
|
||||
"evidence_url": "https://github.com/hao-ai-lab/FastVideo/pull/1852"
|
||||
},
|
||||
"evidence": "Verified",
|
||||
"expected_artifact": "Warmup and measured MP4 files under outputs/fasth3_8step/",
|
||||
"modes": ["T2VA", "8-step FastH3"],
|
||||
"knobs": [
|
||||
{"key": "num_gpus", "label": "GPUs", "hint": "Sequence-parallel degree", "flag": "--num-gpus", "options": [1, 2, 4, 8], "default": 4},
|
||||
{"key": "video_decode_backend", "label": "VAE decode", "hint": "Fidelity vs. speed", "flag": "--video-decode-backend", "options": [{"value": "h3-vae", "label": "Full H3 VAE"}, {"value": "taeh3", "label": "TAEH3 preview"}], "default": "h3-vae"}
|
||||
],
|
||||
"limitations": [
|
||||
"Nine sigma-grid points (eight transformer forwards) are fixed by the checkpoint's trained ladder; the example rejects any other --steps.",
|
||||
"Validated with the eager strict route (--profile strict --no-inference-torch-compile --no-compile-vae). The compiled all profile has not been measured for this checkpoint.",
|
||||
"T2AV only; no FL2VA/Ref2VA distillation and no matching LoRA. V2 uses eight forwards rather than V1's four."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "fasth3-8step-v2-mlx",
|
||||
"serving": {
|
||||
"source": "examples/serving/mlx_fasth3_8step.yaml",
|
||||
"install": "uv pip install -e \".[mlx]\"",
|
||||
"prepare": "hf download FastVideo/FastVideo-FastH3-8-Step-V2 --local-dir ./FastH3-8-Step-V2\npython scripts/checkpoint_conversion/convert_minimax_h3_mlx.py --model-root ./FastH3-8-Step-V2/transformer --out ./FastH3-8-Step-V2-MLX --formats \"int8\" --include-vsa"
|
||||
},
|
||||
"group": "fasth3-8step-v2",
|
||||
"group_label": "FastH3 V2",
|
||||
"group_task": "8-step text to video + audio",
|
||||
"family": "minimax_h3",
|
||||
"stage": "inference",
|
||||
"task": "Few-step text to video (with audio)",
|
||||
"label": "FastH3 V2 on MLX",
|
||||
"summary": "Run FastH3 V2 on Apple Silicon with a locally converted INT8 DiT, the checkpoint's DMD contract ladder, and trained VSA (sparsity 0.8, 64-token tiles).",
|
||||
"model": "FastVideo/FastVideo-FastH3-8-Step-V2",
|
||||
"source": "examples/inference/basic/mlx_fasth3_8step.py",
|
||||
"command": "hf download FastVideo/FastVideo-FastH3-8-Step-V2 --local-dir ./FastH3-8-Step-V2\npython scripts/checkpoint_conversion/convert_minimax_h3_mlx.py --model-root ./FastH3-8-Step-V2/transformer --out ./FastH3-8-Step-V2-MLX --formats \"int8\" --include-vsa\npython examples/inference/basic/mlx_fasth3_8step.py --model-root ./FastH3-8-Step-V2 --mlx-checkpoint ./FastH3-8-Step-V2-MLX/int8 --prompt \"(S1) A presenter says <d>[English] FastVideo runs FastH3.</d>\" --height 480 --width 832 --num-frames 124 --seed 2026 --output-path ./outputs/fasth3_8step_int8.mp4",
|
||||
"gpu_types": ["Apple Silicon"],
|
||||
"hardware": {
|
||||
"platform": "mlx",
|
||||
"accelerator": "Apple M4 Max",
|
||||
"system_memory": "36 GB unified memory",
|
||||
"peak_memory": "27.39 GiB peak MLX memory during denoising",
|
||||
"evidence": "validated",
|
||||
"evidence_url": "https://github.com/hao-ai-lab/FastVideo/pull/1863"
|
||||
},
|
||||
"evidence": "Verified",
|
||||
"expected_artifact": "MP4 with H.264 video and stereo AAC audio at outputs/fasth3_8step_int8.mp4",
|
||||
"modes": ["T2VA", "8-step FastH3", "trained VSA"],
|
||||
"knobs": [
|
||||
{"key": "video_decode_backend", "label": "VAE decode", "hint": "Fidelity vs. speed", "flag": "--video-decode-backend", "options": [{"value": "h3-vae", "label": "Full H3 VAE"}, {"value": "taeh3", "label": "TAEH3 preview"}], "default": "h3-vae"}
|
||||
],
|
||||
"limitations": [
|
||||
"Convert with --include-vsa. mlx_fasth3_8step.py turns VSA on (sparsity 0.8, tile 64). A dense export fails at configure_vsa.",
|
||||
"--steps 8 or 9 both run the eight trained forwards. Reuse the preview VAE, audio VAE, text encoder, and tokenizer if those directories already exist.",
|
||||
"The MLX path supports T2VA only. FL2VA, Ref2VA, and two-pass refinement are not wired."
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "minimax-h3-fl2va",
|
||||
"family": "minimax_h3",
|
||||
|
||||
@@ -136,7 +136,7 @@
|
||||
if (platform === "mps") {
|
||||
return {
|
||||
id: "mps",
|
||||
label: "Apple Silicon · MPS",
|
||||
label: "Apple Silicon · PyTorch MPS",
|
||||
hint: recipe.hardware?.minimum_memory || recipe.hardware?.system_memory || "Memory not recorded",
|
||||
};
|
||||
}
|
||||
@@ -519,10 +519,10 @@
|
||||
option.setAttribute("aria-pressed", String(selected));
|
||||
});
|
||||
servingAvailability.textContent = profile
|
||||
? "The playground and API clients share one server process. Both workflows can run on your own machine."
|
||||
? "The playground and the OpenAI Python client share one server process. Both workflows can run on your own machine."
|
||||
: servingLoadFailed
|
||||
? "Server examples could not be loaded. Open the H3 server guide below, or use Python directly."
|
||||
: "This recipe uses Python directly. For the playground and API clients, choose FastH3 Preview with CUDA, MLX, or one Spark.";
|
||||
: "This recipe uses Python directly. FastH3 V1 and FastH3 V2 can also run a local server for the playground and the OpenAI Python client.";
|
||||
servingPanel.hidden = !useServer;
|
||||
commandBlock.hidden = useServer;
|
||||
root.querySelector("[data-cookbook-python-note]").hidden = useServer;
|
||||
@@ -537,7 +537,7 @@
|
||||
? "Start once, then change prompts in the playground or your app. On a DGX Spark, lazy module load still reloads Qwen3-VL and the DiT between phases of each request, so later prompts are not a free hot cache."
|
||||
: "Start once, then change prompts in the playground or your app. CUDA requests reuse the loaded model. The Python SDK can also reuse a generator within one process.";
|
||||
servingPanel.querySelector("[data-cookbook-install-guide]").href = isMLX
|
||||
? "../../getting_started/installation/mps/#run-fasth3-preview"
|
||||
? "../../getting_started/installation/mlx/"
|
||||
: isSpark
|
||||
? "../../getting_started/installation/spark/"
|
||||
: "../../getting_started/installation/gpu/";
|
||||
@@ -574,7 +574,7 @@
|
||||
});
|
||||
|
||||
description.textContent = useServer
|
||||
? `FastH3 Preview generates video with audio. This server profile uses the checked-in ${runtime.label} configuration.`
|
||||
? `${recipe.group_label || recipe.label} generates video with audio. Start the local server, then use the playground or the OpenAI Python client. This profile uses the checked-in ${runtime.label} configuration.`
|
||||
: recipe.summary;
|
||||
label.textContent = useServer ? `${recipe.group_label || recipe.label} · Server` : recipe.label;
|
||||
model.textContent = recipe.model;
|
||||
|
||||
@@ -18,8 +18,8 @@ the CUDA `fastvideo-kernel` package:
|
||||
- **Dense-only checkpoints** (the default converter) drop the 50 gate
|
||||
matrices and keep fused SDPA. They remain valid for dense inference.
|
||||
- **VSA-capable checkpoints** retain those gates, quantize them on the same
|
||||
affine grid, and record `vsa.capable` in `mlx_h3_dit.json`. Runtime VSA is
|
||||
still off until you pass `--vsa`.
|
||||
affine grid, and record `vsa.capable` in `mlx_h3_dit.json`. Preview leaves
|
||||
runtime VSA off until you pass `--vsa`. `mlx_fasth3_8step.py` turns it on.
|
||||
- **Tile sizes** 64 `(4, 4, 4)` and 256 `(4, 8, 8)`. Prefix keys can be
|
||||
`exempt` or `compete`. `--vsa-dense-first-n-steps` and `--vsa-dense-layers`
|
||||
force dense SDPA on the selected steps or blocks.
|
||||
@@ -32,9 +32,12 @@ the CUDA `fastvideo-kernel` package:
|
||||
but does not yet match reference video. `--vsa-impl reference` is the same
|
||||
as `auto`.
|
||||
|
||||
See the [Apple Silicon guide](../../getting_started/installation/mps.md) for
|
||||
conversion and `mlx_fasth3.py` flags. Do not enable VSA on a dense-only
|
||||
checkpoint; reconvert with `--include-vsa` first.
|
||||
See the [MLX install guide](../../getting_started/installation/mlx.md)
|
||||
and the [MiniMax H3 cookbook](../../cookbook/minimax-h3.md) for conversion
|
||||
and `mlx_fasth3.py` / `mlx_fasth3_8step.py` flags. Do not enable
|
||||
VSA on a dense-only checkpoint; reconvert with `--include-vsa` first. V1
|
||||
VSA is opt-in. FastH3 V2 converts with `--include-vsa` and turns VSA on
|
||||
by default.
|
||||
|
||||
H3 uses fused MLX RMSNorm by default, including dense inference. This can
|
||||
change BF16 rounding relative to the older explicit normalization path.
|
||||
|
||||
@@ -30,7 +30,7 @@ FastVideo and the reference model first produce different numbers?"
|
||||
| General logging | `init_logger(__name__)` |
|
||||
| Per-stage timing | `FASTVIDEO_STAGE_LOGGING` |
|
||||
| Profiling kernel timings | `FASTVIDEO_TORCH_PROFILER_DIR` (see [Profiling](profiling.md)) |
|
||||
| Function-call tracing | `FASTVIDEO_TRACE_FUNCTION` (heavy) |
|
||||
| Function-call tracing | `fastvideo.logger.enable_trace_function_call()` (heavy) |
|
||||
|
||||
## Quickstart
|
||||
|
||||
|
||||
@@ -4,6 +4,11 @@ This is the canonical reference for FastVideo's CI/CD system. Contributor-facing
|
||||
PR steps live in [Pull Requests](pull_requests.md), and test-authoring guidance
|
||||
lives in [Testing](testing.md).
|
||||
|
||||
The existing Slurm route below remains the default. Operators can also install
|
||||
the [selectable GPU dispatcher](gpu_ci_backends.md) to run the same lane scripts
|
||||
on Modal or Kubernetes in the `vllm` namespace. That opt-in path enforces two
|
||||
active PRs, four GPUs per PR, and eight total, with separate backend statuses.
|
||||
|
||||
## Overview
|
||||
|
||||
FastVideo splits validation across GitHub Actions, Buildkite, Slinky Slurm,
|
||||
@@ -435,8 +440,11 @@ before later jobs consume the updated image pin.
|
||||
The same workflow publishes a single-architecture ARM64, CUDA 13, SM100 image
|
||||
for the self-hosted CI runner under the
|
||||
`py3.12-cuda13.0.0-sm100-{latest,sha-*}` tags. It carries the matching prebuilt
|
||||
kernel wheel so runner jobs can validate and install the exact source and ABI
|
||||
match instead of recompiling it in every lane.
|
||||
kernel wheel compiled with `TORCH_CUDA_ARCH_LIST=10.0a` to include the GB200
|
||||
VSA CUDA extensions. Runtime kernel detection uses the same target, so runner
|
||||
jobs can validate and install the exact source and ABI match instead of
|
||||
recompiling it in every lane. Older artifacts built for `10.0` have a different
|
||||
cache key and trigger a local rebuild when the worker detects `10.0a`.
|
||||
|
||||
The optional Dreamverse matrix builds backend and UI images for CUDA 12.6 and
|
||||
CUDA 13 on `amd64`. Dreamverse remains `amd64`-only because its FA4 dependency
|
||||
|
||||
@@ -0,0 +1,242 @@
|
||||
# Environment Variables
|
||||
|
||||
FastVideo reads environment variables for expert switches, debugging, profiling, and the settings that launchers such
|
||||
as `torchrun` provide. This page is the policy for those variables. The contract test
|
||||
`fastvideo/tests/contract/test_env_policy.py` enforces the policy in the unit CI lane, and the coding-agent skill
|
||||
`.agents/skills/env-var-conventions/SKILL.md` points here. When the policy changes, update this page and the contract
|
||||
test in the same pull request.
|
||||
|
||||
## Rules
|
||||
|
||||
1. **Register every FastVideo variable in `fastvideo/envs.py`.** Each entry declares a type, a default, a category,
|
||||
and a description. Variables that other tools own (CUDA, NCCL, PyTorch, launchers) are not registered; code reads
|
||||
them directly with `os.environ.get("NAME")`, and the name must be in the external-variable allowlist
|
||||
(`EXTERNAL_ALLOWLIST` in the contract test). When FastVideo sets such a variable for the other tool, it calls
|
||||
`envs.set_external`, `envs.setdefault_external`, or `envs.unset_external`, and the name must be in
|
||||
`EXTERNAL_WRITE_ALLOWLIST`.
|
||||
2. **Read with `envs.NAME.get()`, write with `envs.NAME.set()`, and change a value in tests with
|
||||
`envs.NAME.override()`.** Each type has one parsing rule. A value that the rule rejects raises
|
||||
`fastvideo.envs.EnvVarError` instead of falling back to the default.
|
||||
3. **Name FastVideo variables with the `FASTVIDEO_` prefix.** The second word states the purpose where one applies:
|
||||
`ENABLE_`, `DISABLE_`, `USE_`, `FORCE_`, `DEBUG_`, `TEST_`.
|
||||
4. **Keep a renamed variable as a deprecated alias until the next minor release.** Setting the old name logs a
|
||||
warning. Delete a variable that no code reads, and list it in `DEPRECATED_VARIABLES` so that setting it logs a
|
||||
warning.
|
||||
5. **Give each setting one source: an argument or an environment variable.** Settings that users change per
|
||||
deployment are arguments (CLI or YAML). Expert switches, emergency off switches, and debugging and test switches
|
||||
are environment variables.
|
||||
6. **Read variables inside functions.** `envs.NAME.get()` runs when the function runs, so a changed value takes
|
||||
effect without re-importing a module. Module level, class bodies, decorators, and default argument values run at
|
||||
import time.
|
||||
7. **Do not write the environment to pass values between parts of FastVideo.** Pass an argument instead. Tests use
|
||||
`envs.NAME.override()`.
|
||||
|
||||
## Field types
|
||||
|
||||
| Class | Value type | Parsing rule |
|
||||
| ------------ | ---------------- | ----------------------------------------------------------------------------------- |
|
||||
| `EnvBool` | `bool` | `1`, `true`, `yes`, `on` are true; `0`, `false`, `no`, `off`, and `""` are false. |
|
||||
| | | Case-insensitive; surrounding whitespace is ignored. |
|
||||
| `EnvInt` | `int` | `int(value)` |
|
||||
| `EnvFloat` | `float` | `float(value)` |
|
||||
| `EnvStr` | `str` or `None` | The raw string. A `None` default means that the variable has no default. |
|
||||
| `EnvPath` | `str` or `None` | The raw string with a leading `~` expanded. |
|
||||
| `EnvChoice` | `str` | Stripped and lower-cased, then checked against the declared `choices`. |
|
||||
|
||||
A default can be a zero-argument function; `get()` calls it on each read while the variable is unset. The path roots
|
||||
use this to follow `XDG_CONFIG_HOME` and `XDG_CACHE_HOME`.
|
||||
|
||||
Using a field without a method, as in `if envs.FASTVIDEO_FA4:`, raises `TypeError`.
|
||||
|
||||
## Add a variable
|
||||
|
||||
1. Declare the variable in the matching section of `fastvideo/envs.py`:
|
||||
|
||||
```python
|
||||
FASTVIDEO_DEBUG_MY_STAGE = EnvBool(False, category="debug", doc="Log the inputs of MyStage.")
|
||||
```
|
||||
|
||||
The category is one of the values in `envs.CATEGORIES`.
|
||||
|
||||
2. Read the variable inside a function:
|
||||
|
||||
```python
|
||||
import fastvideo.envs as envs
|
||||
|
||||
def forward(self, batch):
|
||||
if envs.FASTVIDEO_DEBUG_MY_STAGE.get():
|
||||
logger.info("MyStage inputs: %s", batch.keys())
|
||||
```
|
||||
|
||||
3. Regenerate the table at the end of this page:
|
||||
|
||||
```bash
|
||||
python fastvideo/tests/contract/test_env_policy.py
|
||||
```
|
||||
|
||||
4. Run the contract test:
|
||||
|
||||
```bash
|
||||
pytest fastvideo/tests/contract/test_env_policy.py
|
||||
```
|
||||
|
||||
In a test, change the value with `override`, which restores the previous value on exit:
|
||||
|
||||
```python
|
||||
with envs.FASTVIDEO_DEBUG_MY_STAGE.override(True):
|
||||
run_stage()
|
||||
```
|
||||
|
||||
## Rename or remove a variable
|
||||
|
||||
To rename a variable, declare it under the new name and list the old name in `deprecated_names`:
|
||||
|
||||
```python
|
||||
FASTVIDEO_LTX2_USE_DISTILLED_SIGMAS = EnvBool(True,
|
||||
category="sampling",
|
||||
doc="...",
|
||||
deprecated_names=("LTX2_USE_DISTILLED_SIGMAS", ))
|
||||
```
|
||||
|
||||
`get()` reads an old name only when the new name is unset, and logs a warning once. Update the uses of the old name
|
||||
in `examples/`, `scripts/`, `docs/`, `apps/`, and the tests in the same pull request. Delete the old name in the next
|
||||
minor release.
|
||||
|
||||
To remove a variable that no code reads, delete its entry and add the name to `DEPRECATED_VARIABLES` in
|
||||
`fastvideo/envs.py` with a reason. `FastVideoArgs` calls `envs.warn_deprecated_variables()`, which logs a warning for
|
||||
each listed variable that is set. Delete the entry in the next minor release.
|
||||
|
||||
## What the contract test checks
|
||||
|
||||
The test parses every Python file under `fastvideo/`, including `fastvideo/tests/`, with Python's `ast` module. It
|
||||
skips `fastvideo/third_party/`, which is copied from upstream projects, and the registry `fastvideo/envs.py`. It does
|
||||
not check `apps/`, `examples/`, `scripts/`, `fastvideo-kernel/`, or `docs/`.
|
||||
|
||||
It reports each violation as `<path>: <kind> <name>`:
|
||||
|
||||
| Kind | Code that triggers it | Fix |
|
||||
| ------------------ | ------------------------------------------------------------ | -------------------------------------------- |
|
||||
| `read` | `os.getenv`, `os.environ.get`, `os.environ[...]`, or | Register the variable and call |
|
||||
| | `"NAME" in os.environ` with a name outside the allowlist, or | `envs.NAME.get()`. For a variable that |
|
||||
| | with a name built at runtime (`<dynamic>`) | another tool owns, add it to |
|
||||
| | | `EXTERNAL_ALLOWLIST` with a reason. |
|
||||
| `write` | `os.environ[...] = ...`, `setdefault`, `pop`, `del`, | Pass an argument instead. In tests, use |
|
||||
| | `os.putenv`, `os.unsetenv`, `monkeypatch.setenv`/`delenv`, | `envs.NAME.override()`. For a variable that |
|
||||
| | or an `envs.*_external` call with a name outside | another tool reads, call an |
|
||||
| | `EXTERNAL_WRITE_ALLOWLIST` | `envs.*_external` helper and add the name to |
|
||||
| | | `EXTERNAL_WRITE_ALLOWLIST` with a reason. |
|
||||
| `whole-environ` | `os.environ.copy()`, `dict(os.environ)`, iteration, | Read the specific variables that the code |
|
||||
| | `mock.patch.dict(os.environ, ...)`, `os.environ.update` | needs. |
|
||||
| `bare-field` | A registry field used without calling one of its methods, | Call `envs.NAME.get()`. |
|
||||
| | as in `envs.NAME == "auto"` or `getter = envs.NAME.get` | |
|
||||
| `import-time-read` | `envs.NAME.get()` outside a function | Move the read into the function that uses |
|
||||
| | | the value. |
|
||||
| `prefix` | A registry entry without the `FASTVIDEO_` prefix | Rename the variable and keep the old name in |
|
||||
| | | `deprecated_names`. |
|
||||
| `unread` | A registry entry that no code reads with `get()` or | Delete the variable and add it to |
|
||||
| | `is_set()` | `DEPRECATED_VARIABLES`. |
|
||||
|
||||
The test recognizes `os` imported under another name, `from os import environ, getenv`, and a name held in a
|
||||
module-level string constant. Code that reaches the environment through `importlib` or `getattr(os, "environ")` is
|
||||
left to code review.
|
||||
|
||||
The test also checks that every registry entry has a category from `envs.CATEGORIES` and a description, and that the
|
||||
table at the end of this page matches the registry.
|
||||
|
||||
**Known violations.** `KNOWN_VIOLATIONS` in the contract test lists the violations that existed when the policy was
|
||||
introduced. The list only shrinks. A violation that is not in the list fails the test. A listed violation that no
|
||||
longer exists also fails the test, so the fixing pull request deletes its entry.
|
||||
|
||||
## Registered variables
|
||||
|
||||
<!-- BEGIN GENERATED ENV TABLE: python fastvideo/tests/contract/test_env_policy.py -->
|
||||
| Variable | Type | Default | Category | Description |
|
||||
| ---------------------------------------------------------- | ------------------------------ | ----------------------------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `FASTVIDEO_CONFIG_ROOT` | path | computed | path | Root directory for FastVideo configuration files, at runtime and at installation. Defaults to ~/.config/fastvideo, or $XDG_CONFIG_HOME/fastvideo when XDG_CONFIG_HOME is set. |
|
||||
| `FASTVIDEO_CACHE_ROOT` | path | computed | path | Root directory for FastVideo cache files. Defaults to ~/.cache/fastvideo, or $XDG_CACHE_HOME/fastvideo when XDG_CACHE_HOME is set. |
|
||||
| `FASTVIDEO_REASON1_WEIGHTS_PATH` | str | unset | path | Local path or Hugging Face id of Reason1 weights to load instead of the checkpoint's own. |
|
||||
| `FASTVIDEO_HOST_IP` | str | `""` | distributed | IP address of this node when the node has several network interfaces. Set it on each node for multi-node inference. |
|
||||
| `FASTVIDEO_LOOPBACK_IP` | str | `""` | distributed | Loopback IP address to use instead of the detected one. |
|
||||
| `FASTVIDEO_RAY_PER_WORKER_GPUS` | float | `1.0` | distributed | GPUs per Ray worker. A fraction lets Ray schedule several actors on one GPU, so other actors can share the GPUs with FastVideo. |
|
||||
| `FASTVIDEO_NCCL_SO_PATH` | str | unset | distributed | Path to the NCCL library file. Needed because the nccl>=2.19 that PyTorch ships has a bug (https://github.com/NVIDIA/nccl/issues/1234). |
|
||||
| `FASTVIDEO_HCCL_SO_PATH` | str | unset | distributed | Path to the HCCL library file on Ascend NPUs. Deprecated names: `HCCL_SO_PATH`. |
|
||||
| `FASTVIDEO_WORKER_MULTIPROC_METHOD` | one of spawn, fork, forkserver | `spawn` | distributed | Multiprocessing start method for worker processes. |
|
||||
| `FASTVIDEO_ULYSSES_A2A` | one of off, auto | `off` | distributed | Sequence-parallel all-to-all backend. off uses the NCCL path in DistributedAutograd.AllToAll4D. auto uses the fused NVLink kernel when the group is a load-store accessible mesh of 2, 4, 6, or 8 ranks in eager execution, and the NCCL path otherwise. |
|
||||
| `FASTVIDEO_CONFIGURE_LOGGING` | bool | `1` | logging | Configure logging at import. When true, FastVideo uses its default logging configuration or the file in FASTVIDEO_LOGGING_CONFIG_PATH. |
|
||||
| `FASTVIDEO_LOGGING_CONFIG_PATH` | str | unset | logging | Path to a JSON logging configuration file. |
|
||||
| `FASTVIDEO_LOGGING_LEVEL` | str | `INFO` | logging | Default logging level. |
|
||||
| `FASTVIDEO_LOGGING_PREFIX` | str | `""` | logging | Prefix prepended to every log message. |
|
||||
| `FASTVIDEO_STAGE_LOGGING` | bool | `0` | logging | Log the time that each pipeline stage takes. |
|
||||
| `FASTVIDEO_ATTENTION_BACKEND` | str | unset | attention | Attention backend, as an AttentionBackendEnum name such as TORCH_SDPA, FLASH_ATTN, VIDEO_SPARSE_ATTN, SAGE_ATTN, or SAGE_ATTN_THREE. FastVideoArgs uses it when FastVideoArgs.attention_backend is unset. |
|
||||
| `FASTVIDEO_FA4` | bool | `0` | attention | The FLASH_ATTN backend uses FlashAttention-4 (flash_attn.cute) instead of FA3 or FA2. |
|
||||
| `FASTVIDEO_MINIMAX_H3_FA4_PACKED_VARLEN` | bool | `0` | attention | MiniMax-H3 dense DiT self-attention uses the FlashAttention-4 packed-varlen entry point. This changes the floating-point reduction order, so it is an inference-only opt-in. |
|
||||
| `FASTVIDEO_VSA_SM100A` | bool | `0` | attention | VIDEO_SPARSE_ATTN_H3 sends no-grad tile-64 forwards to the data-center Blackwell (sm_100a) kernel. fastvideo-kernel reads the same variable with the same rule. |
|
||||
| `FASTVIDEO_NVFP4_FA4` | bool | `0` | attention | FlashAttention-4 quantizes Q and K to NVFP4. An explicit nvfp4_fa4 attention implementation argument takes precedence. |
|
||||
| `FASTVIDEO_DISABLE_ATTENTION_COMPILE` | bool | `1` | attention | Keep attention forward out of torch.compile graphs (torch.compiler.disable). Set it to 0 to let attention constructed under that setting be traced. Setting it explicitly to true also blocks regional compile. |
|
||||
| `FASTVIDEO_MLX_WINDOW` | int | `0` | attention | MLX FastWan windowed attention size in tokens. 0 uses full attention. |
|
||||
| `FASTVIDEO_MLX_WINDOW_SINK` | int | `0` | attention | Number of sink tokens that MLX windowed attention always attends to. |
|
||||
| `FASTVIDEO_INFERENCE_TORCH_COMPILE` | bool | `0` | performance | Compile each DiT transformer block with fullgraph torch.compile at inference. Same as FastVideoArgs.inference_torch_compile=True. |
|
||||
| `FASTVIDEO_VAE_PARALLEL_DECODE` | bool | `0` | performance | MiniMax-H3 VAE decode splits its temporal chunks across the sequence-parallel ranks instead of running serially on the output rank. Same as FastVideoArgs.vae_parallel_decode=True. |
|
||||
| `FASTVIDEO_VAE_PARALLEL_ENCODE` | bool | `0` | performance | MiniMax-H3 reference-video VAE encode splits its temporal chunks across the sequence-parallel ranks. Same as FastVideoArgs.vae_parallel_encode=True. |
|
||||
| `FASTVIDEO_VAE_PARALLEL_DECODE_STRATEGY` | str | unset | performance | Collective that moves chunks in parallel VAE decode: gather (used when unset) or all_gather. |
|
||||
| `FASTVIDEO_MINIMAX_H3_FUSIONS` | str | `""` | performance | MiniMax-H3 inference-only Triton fusions: all, 1, or a comma-separated subset of modulate,qknorm_rope,swiglu. Empty, 0, or none keeps the eager implementation. |
|
||||
| `FASTVIDEO_FSDP2_AUTOWRAP` | bool | `0` | performance | FSDP2 shards modules by parameter count instead of the model's shard conditions. Not supported by self-forcing distillation. |
|
||||
| `FASTVIDEO_FSDP2_MIN_PARAMS` | int | `10000000` | performance | Minimum parameter count of a module that FASTVIDEO_FSDP2_AUTOWRAP shards. |
|
||||
| `FASTVIDEO_MLX_COMPILE` | bool | `0` | performance | Compile the MLX DiT forward with mx.compile. |
|
||||
| `FASTVIDEO_MLX_FAST_NORM` | bool | `0` | performance | Use MLX fast normalization kernels. |
|
||||
| `FASTVIDEO_MLX_DQ_GEMM` | str | `1` | performance | MLX dequantized GEMM for affine-quantized weights: 0 turns it off, 1 uses the measured minimum row count, and an integer sets the minimum row count. |
|
||||
| `FASTVIDEO_LTX2_VAE_CHANNELS_LAST_3D` | bool | `1` | performance | LTX-2 VAE uses the channels_last_3d memory format. |
|
||||
| `FASTVIDEO_LTX2_DISABLE_AUDIO_AUTOCAST` | bool | `1` | performance | LTX-2 audio decoding runs without CUDA autocast. Deprecated names: `LTX2_DISABLE_AUDIO_AUTOCAST`. |
|
||||
| `FASTVIDEO_FLUX2_DISABLE_BF16_REDUCED_PRECISION_REDUCTION` | bool | `0` | performance | Flux denoising disables reduced-precision reductions in bf16 matmuls, which tightens accumulation for the 4-step Klein model. |
|
||||
| `FASTVIDEO_FFMPEG_BIN` | str | `ffmpeg` | output | ffmpeg executable used to save video with audio. |
|
||||
| `FASTVIDEO_VIDEO_CODEC` | str | `libx264` | output | ffmpeg video codec for saved videos. |
|
||||
| `FASTVIDEO_NVENC_PRESET` | str | `p1` | output | NVENC preset when the codec is an \*_nvenc codec. |
|
||||
| `FASTVIDEO_NVENC_TUNE` | str | `ull` | output | NVENC tune option. |
|
||||
| `FASTVIDEO_NVENC_RC` | str | `constqp` | output | NVENC rate-control mode. |
|
||||
| `FASTVIDEO_NVENC_QP` | str | `28` | output | NVENC quantization parameter. |
|
||||
| `FASTVIDEO_NVENC_BF` | str | `0` | output | NVENC number of B-frames. |
|
||||
| `FASTVIDEO_X264_PRESET` | str | `ultrafast` | output | x264 preset for non-NVENC codecs. |
|
||||
| `FASTVIDEO_OUTPUT_PIX_FMT` | str | `yuv420p` | output | ffmpeg pixel format for saved videos. |
|
||||
| `FASTVIDEO_NVTX_PROFILE` | bool | `0` | profiling | Emit NVTX ranges for external profilers such as Nsight Systems. |
|
||||
| `FASTVIDEO_TORCH_PROFILER_DIR` | path | unset | profiling | Enables the torch profiler and sets the directory for its traces. Must be an absolute path. |
|
||||
| `FASTVIDEO_TORCH_PROFILER_RECORD_SHAPES` | bool | `0` | profiling | Torch profiler records shapes. |
|
||||
| `FASTVIDEO_TORCH_PROFILER_WITH_PROFILE_MEMORY` | bool | `0` | profiling | Torch profiler profiles memory. |
|
||||
| `FASTVIDEO_TORCH_PROFILER_WITH_STACK` | bool | `0` | profiling | Torch profiler captures stacks. Costs about 1.5x runtime and 1.4x trace size. |
|
||||
| `FASTVIDEO_TORCH_PROFILER_WITH_FLOPS` | bool | `0` | profiling | Torch profiler profiles FLOPs. |
|
||||
| `FASTVIDEO_TORCH_PROFILE_REGIONS` | str | `""` | profiling | Comma-separated profiler regions to record. The torch profiler requires at least one region. |
|
||||
| `FASTVIDEO_TRACE_ACTIVATIONS` | bool | `0` | debug | Enable activation trace hooks. |
|
||||
| `FASTVIDEO_TRACE_LAYERS` | str | `""` | debug | Regex filter for traced module names. Empty means all. |
|
||||
| `FASTVIDEO_TRACE_STATS` | str | `abs_mean,sum` | debug | Comma-separated activation statistics dumped for each output tensor. |
|
||||
| `FASTVIDEO_TRACE_OUTPUT` | str | `/tmp/fv_trace_<pid>.jsonl` | debug | JSONL path for activation traces. The literal <pid> is replaced at runtime. |
|
||||
| `FASTVIDEO_TRACE_STEPS` | str | `""` | debug | Comma-separated denoising step indices. Empty means all. |
|
||||
| `FASTVIDEO_H3_VSA_PROBE` | str | unset | debug | Output directory for the VSA-H3 attention-mass probe, which writes one .pt file per step, layer, and rank. Keeps the model out of regional compile. |
|
||||
| `FASTVIDEO_LTX2_GEMMA_LOG` | str | `""` | debug | Log file for LTX-2 Gemma text-encoder hidden states, used by parity tests. Deprecated names: `LTX2_FASTVIDEO_GEMMA_LOG`. |
|
||||
| `FASTVIDEO_COSMOS25_LOG_KNOBS` | bool | `0` | debug | Log the Cosmos 2.5 latent-preparation conditioning inputs. |
|
||||
| `FASTVIDEO_CFG_GATE_STEP` | float | `1.0` | sampling | CFG gating fraction in [0, 1]. Steps before len(timesteps) \* X run the conditional and unconditional forwards; later steps reuse the cached difference. 1.0 disables gating. |
|
||||
| `FASTVIDEO_LTX2_USE_DISTILLED_SIGMAS` | bool | `1` | sampling | LTX-2 uses the distilled sigma schedule when FastVideoArgs.ltx2_use_distilled_sigmas is also true. Deprecated names: `LTX2_USE_DISTILLED_SIGMAS`. |
|
||||
| `FASTVIDEO_EVAL_CACHE` | path | computed | eval | Cache directory for evaluation models and datasets. Defaults to $FASTVIDEO_CACHE_ROOT/eval. |
|
||||
| `FASTVIDEO_PHYSICS_IQ_BUCKET_URL` | str | `https://storage.googleapis.com/physics-iq-benchmark` | eval | Base URL of the Physics-IQ benchmark bucket. |
|
||||
| `FASTVIDEO_VBENCH_FULL_INFO_JSON` | str | unset | eval | Path to VBench_full_info.json, used instead of the vendored copy. Deprecated names: `VBENCH_FULL_INFO_JSON`. |
|
||||
| `FASTVIDEO_FVD_REF_FEATURES` | str | unset | eval | Cached reference-feature file for the FVD metric. |
|
||||
| `FASTVIDEO_FAD_REF_FEATURES` | str | unset | eval | Cached reference-feature file for the audio Frechet distance metric. |
|
||||
| `FASTVIDEO_TEST_LTX2_OVERFIT_DATA_DIR` | str | `data/cats` | test | Raw data directory for preprocess_ltx2_overfit.py. Deprecated names: `LTX2_OVERFIT_DATA_DIR`. |
|
||||
| `FASTVIDEO_TEST_LTX2_OVERFIT_CAPTION_JSON` | str | `videos2caption_1_sample.json` | test | Caption file, relative to the raw data directory. Deprecated names: `LTX2_OVERFIT_CAPTION_JSON`. |
|
||||
| `FASTVIDEO_TEST_LTX2_OVERFIT_VIDEO_SUBDIR` | str | `video` | test | Video subdirectory, relative to the raw data directory. Deprecated names: `LTX2_OVERFIT_VIDEO_SUBDIR`. |
|
||||
| `FASTVIDEO_TEST_LTX2_OVERFIT_OUTPUT_DIR` | str | `data/ltx2_overfit_preprocessed` | test | Output directory for preprocess_ltx2_overfit.py. Deprecated names: `LTX2_OVERFIT_OUTPUT_DIR`. |
|
||||
| `FASTVIDEO_TEST_LTX2_OVERFIT_MODEL` | str | `FastVideo/LTX2-Distilled-Diffusers` | test | Model repository whose encoders preprocess_ltx2_overfit.py uses. Deprecated names: `LTX2_OVERFIT_MODEL`. |
|
||||
| `FASTVIDEO_TEST_LTX2_OVERFIT_NUM_COPIES` | int | `4` | test | Number of copies of the overfit sample in the parquet file. Deprecated names: `LTX2_OVERFIT_NUM_COPIES`. |
|
||||
| `FASTVIDEO_TEST_KANDINSKY5_OVERFIT_DATA_DIR` | str | `data/kandinsky5_overfit` | test | Raw data directory for preprocess_kandinsky5_overfit.py. Deprecated names: `KANDINSKY5_OVERFIT_DATA_DIR`. |
|
||||
| `FASTVIDEO_TEST_KANDINSKY5_OVERFIT_OUTPUT_DIR` | str | `data/kandinsky5_overfit_preprocessed` | test | Output directory for preprocess_kandinsky5_overfit.py. Deprecated names: `KANDINSKY5_OVERFIT_OUTPUT_DIR`. |
|
||||
|
||||
Variables that FastVideo no longer reads; setting one logs a warning:
|
||||
|
||||
| Deprecated variable | Reason |
|
||||
| ----------------------------------------- | ---------------- |
|
||||
| `FASTVIDEO_TARGET_DEVICE` | no code reads it |
|
||||
| `FASTVIDEO_USE_PRECOMPILED` | no code reads it |
|
||||
| `FASTVIDEO_RINGBUFFER_WARNING_INTERVAL` | no code reads it |
|
||||
| `FASTVIDEO_ENGINE_ITERATION_TIMEOUT_S` | no code reads it |
|
||||
| `FASTVIDEO_SERVER_DEV_MODE` | no code reads it |
|
||||
| `FASTVIDEO_TEST_DYNAMO_FULLGRAPH_CAPTURE` | no code reads it |
|
||||
| `FASTVIDEO_TRACE_FUNCTION` | no code reads it |
|
||||
<!-- END GENERATED ENV TABLE -->
|
||||
@@ -0,0 +1,252 @@
|
||||
# Selectable GPU CI Backends
|
||||
|
||||
GPU CI can use the existing Slurm dispatcher, Modal, or GB200 Kubernetes Jobs
|
||||
in the `vllm` namespace. GitHub and Buildkite remain the trigger and reporting
|
||||
systems. `vllm` names the cluster namespace here; tests run FastVideo's existing
|
||||
lane scripts rather than a vLLM inference server.
|
||||
|
||||
The default remains Slurm. The existing `.buildkite/pipeline.yml`, private
|
||||
Slurm uploader, and dormant Modal launchers are preserved. Deploying the new
|
||||
trusted uploader is an operator step; merging these files does not change the
|
||||
live Buildkite pipelines or create cluster resources. See
|
||||
[CI/CD Architecture](ci_architecture.md) for the existing installation.
|
||||
|
||||
## Execution and Limits
|
||||
|
||||
```text
|
||||
GitHub PR / slash command / schedule
|
||||
-> trusted Buildkite bootstrap
|
||||
-> slurm: existing validated graph and private dispatcher
|
||||
-> modal or vllm: one trusted suite coordinator
|
||||
-> shared PR admission and GPU reservations
|
||||
-> isolated GPU worker per existing lane
|
||||
-> lane results, logs, and backend-specific suite status
|
||||
```
|
||||
|
||||
All new Modal and `vllm` coordinators share one admission database. It enforces:
|
||||
|
||||
- At most two distinct PRs with admitted GPU work.
|
||||
- At most four reserved GPUs per PR across its concurrent builds and lanes.
|
||||
- At most eight reserved GPUs across both backends combined.
|
||||
- Additional PRs wait without creating GPU workers.
|
||||
|
||||
A PR is identified by repository and PR number, so Fastcheck, merge builds,
|
||||
manual reruns, and separate attempts share its allowance. Its slot remains
|
||||
occupied between lanes until all admitted builds for that PR finish. A
|
||||
non-PR build, including a schedule on `main`, consumes its own slot and the
|
||||
same GPU budget. The preserved Slurm dispatcher has its existing independent
|
||||
limits; this new admission database does not control legacy Slurm jobs.
|
||||
|
||||
Lanes retain their existing one-, two-, or four-GPU requirements. Four-GPU
|
||||
SSIM and training lanes wait for that PR's other lanes to release capacity.
|
||||
Selected integration lanes wait for the golden gate and are skipped if it
|
||||
fails. CPU-only GitHub checks are unaffected. Pending admission is bounded by
|
||||
`queue_timeout_seconds`, initially six hours. The coordinator's Buildkite
|
||||
command timeout is eight hours, including admission and lane waits after the
|
||||
command starts; time waiting for a Buildkite agent is outside that timeout.
|
||||
|
||||
Reservations persist before a worker is created. Cancellation releases them
|
||||
only after worker termination is confirmed. A lost coordinator, uncertain
|
||||
create response, or unavailable backend retains its reservations until
|
||||
recovery confirms cleanup. There is no heartbeat expiry that silently frees
|
||||
GPUs while an old worker could still be running.
|
||||
|
||||
## Operator Installation
|
||||
|
||||
Use an operator-reviewed immutable checkout under
|
||||
`/opt/fastvideo-gpu-ci/source`. Install the supplied `scripts/gpu_ci/run` and
|
||||
`scripts/gpu_ci/upload` wrappers as `/opt/fastvideo-gpu-ci/run` and
|
||||
`/opt/fastvideo-gpu-ci/upload`. They invoke the reviewed Python entrypoint
|
||||
with isolated import mode. Install the supplied files instead of generating
|
||||
shell wrappers from build metadata:
|
||||
|
||||
```bash
|
||||
install -m 755 /opt/fastvideo-gpu-ci/source/scripts/gpu_ci/run /opt/fastvideo-gpu-ci/run
|
||||
install -m 755 /opt/fastvideo-gpu-ci/source/scripts/gpu_ci/upload /opt/fastvideo-gpu-ci/upload
|
||||
```
|
||||
|
||||
The controller needs Python 3.10 or newer, PyYAML,
|
||||
the Buildkite agent, and `kubectl`; Modal additionally needs the reviewed
|
||||
Modal SDK and a controller-side Modal credential.
|
||||
|
||||
Copy [the configuration example](../../scripts/gpu_ci/config.example.json)
|
||||
to `/etc/fastvideo-gpu-ci.json`. Keep the installation and configuration
|
||||
operator-owned and unwritable by workers. Configure the actual legacy
|
||||
`slurm_uploader` command, replace each enabled backend's image placeholder
|
||||
with a reviewed registry digest, and leave `default_backend` as `slurm`
|
||||
during canaries. The checked-in placeholders deliberately cannot start jobs.
|
||||
|
||||
The permanent dispatcher must reach the Kubernetes API independently of a
|
||||
developer laptop, SSH tunnel, or Tailscale session. The existing CPU development
|
||||
pod is useful for investigating access, but is not a production CI service.
|
||||
Use a dedicated controller identity and a separate `gpu-ci-dispatch` Buildkite
|
||||
queue. The trusted bootstrap also needs access to the existing Slurm uploader
|
||||
while that backend remains enabled.
|
||||
|
||||
Run all coordinators on **one host**, with `state_path` and `artifacts_dir` on
|
||||
its persistent local disk. The implementation uses SQLite transactions and
|
||||
local file locks. Do not place the database or locks on Lustre/NFS, run
|
||||
independent database copies, or scale controller replicas across nodes. Those
|
||||
configurations would invalidate the global limits. Multiple Buildkite agent
|
||||
processes on the same host can share the installation and ledger; enough
|
||||
agents are needed for concurrent suite coordinators and queued work.
|
||||
|
||||
Configure agent-owned hooks to skip repository checkout and reject commands
|
||||
other than the trusted uploader and coordinator. Disable repository hooks
|
||||
and plugins on this queue. Do not use a PR checkout to load `scripts/gpu_ci`,
|
||||
its configuration, or its worker entrypoint. Build metadata is validated by
|
||||
the dispatcher; arbitrary environment variables are not forwarded into GPU
|
||||
workers. Buildkite, Kubernetes, and Modal control-plane credentials stay on
|
||||
the dispatcher.
|
||||
|
||||
For Kubernetes, provision namespace-scoped permissions to create/get/delete
|
||||
Jobs and read Pods and their logs. Worker Pods disable service-account token
|
||||
mounting and request the exact GPU count on ARM64 GB200 nodes. The image must
|
||||
include the expected `/opt/venv` runtime, CUDA/SM100 support, and the reviewed
|
||||
FA4 dependencies. Use a separate AMD64 image digest for Modal. The Modal
|
||||
profile preserves H100 for encoder, custom-kernel, and VSA lanes, and L40S
|
||||
for the remaining lanes. Its reviewed image must support both SM89 and
|
||||
SM90a. Attention settings are selected per backend and lane to preserve the
|
||||
existing Modal and GB200 FA4 profiles; do not replace them with one global
|
||||
attention override. Validate the images against their respective hardware
|
||||
before enabling merge gates.
|
||||
|
||||
Optional Kubernetes configuration includes `context`, `hf_secret`,
|
||||
`hf_secret_key`, `cache_pvc`, `cache_subpath`, `artifacts_pvc`, and
|
||||
`artifacts_subpath`. Only provide a read-only Hugging Face credential when
|
||||
private/gated downloads require it. PR workers are untrusted and can access
|
||||
any credential supplied to them, so never provide a reference-publication
|
||||
or other write-capable token. Prefer a pre-populated read-only cache.
|
||||
|
||||
Use dedicated CI PVCs and relative CI subpaths. The personal
|
||||
`lustre-pvc-vllm` and `nfs-pvc-vllm` claims are rejected. The cache is mounted
|
||||
read-only; mutable references and locks stay inside the worker. The artifact
|
||||
init container prepares each Job's dedicated artifact subdirectory before
|
||||
the worker mounts it. An
|
||||
operator quota or admission policy scoped to CI can provide a second GPU
|
||||
ceiling; do not apply an eight-GPU quota to the shared `vllm` namespace if it
|
||||
would also cap other users' work.
|
||||
|
||||
## Wire Every Buildkite Entry Pipeline
|
||||
|
||||
Set each pipeline's operator-owned bootstrap command to
|
||||
`/opt/fastvideo-gpu-ci/upload`. Apply this to all three entry pipelines:
|
||||
|
||||
| Pipeline | Trigger and scope |
|
||||
|---|---|
|
||||
| `pr-fastcheck` | Automatic PR webhook; `TEST_SCOPE=fastcheck` or unset. |
|
||||
| `ci` | Existing API triggers for Fastcheck, full, merge, direct, and scheduled SSIM. Keep its incoming PR webhook disabled to avoid duplicate builds. |
|
||||
| `fastvideo-performance-lane` | Existing schedule; `TEST_SCOPE=direct`, `TEST_TYPE=performance`. |
|
||||
|
||||
The wrapper resolves `CI_GPU_BACKEND` from the build environment, falling
|
||||
back to `default_backend` in the trusted configuration. Allowed values are
|
||||
exactly `slurm`, `modal`, and `vllm`; unknown values fail. For Slurm, upload
|
||||
delegates to the unchanged private uploader. For Modal or `vllm`, it uploads
|
||||
one fixed `/opt/fastvideo-gpu-ci/run` command on the dedicated queue, with
|
||||
the selected backend and scope pinned in step environment.
|
||||
|
||||
Set `CI_GPU_BACKEND=vllm` in the Buildkite build environment for a rack canary,
|
||||
or `modal` for a Modal canary. The existing API build payload can carry the
|
||||
same string in its `env` object. Automatic PR webhook builds inherit the
|
||||
operator's configured default unless pipeline/build configuration explicitly
|
||||
overrides it. Do not put backend selection inside a PR-controlled command.
|
||||
|
||||
Keep the existing exact `BUILDKITE_COMMIT`, repository, PR identity, and
|
||||
`TEST_SCOPE` metadata. Direct runs also need an allowlisted `TEST_TYPE`.
|
||||
Merge runs need the trusted base-branch planner's `MERGE_TEST_PLAN`,
|
||||
`MERGE_GOLDEN_TESTS`, and `MERGE_SSIM_TESTS`. Full/direct/scheduled quality
|
||||
runs keep their complete matrices. The worker fetches and verifies the
|
||||
immutable commit before installing dependencies or invoking a lane script.
|
||||
|
||||
The new Modal adapter uses bounded one-to-four-GPU sandboxes and the same
|
||||
lane scripts as Kubernetes. It does not reactivate `pr_test.sh` or the old
|
||||
Modal SSIM fan-out, which cannot enforce this shared four-GPU-per-PR budget.
|
||||
The legacy files remain available for their existing manual workflows.
|
||||
|
||||
## Statuses and Default-Backend Cutover
|
||||
|
||||
The selected adapter reports separate suite contexts:
|
||||
|
||||
| Scope | GitHub context |
|
||||
|---|---|
|
||||
| Fastcheck | `gpu-ci/<backend>/fastcheck-passed` |
|
||||
| Merge or explicit full suite | `gpu-ci/<backend>/full-suite-passed` |
|
||||
| Direct lane | `gpu-ci/<backend>/direct-test-completed` |
|
||||
| Scheduled SSIM | `gpu-ci/<backend>/scheduled-ssim-passed` |
|
||||
|
||||
The repository variable `CI_GPU_BACKEND` controls which new backend can
|
||||
publish the existing required `fastcheck-passed` and `full-suite-passed`
|
||||
contexts. Empty or `slurm` preserves the existing status behavior. `modal`
|
||||
or `vllm` enables `ci-gpu-backend-status.yml`, which reads the latest statuses
|
||||
for that one backend and mirrors both required contexts. A missing result
|
||||
becomes pending; failure and error remain failures. It never combines one
|
||||
backend's Fastcheck with another backend's full-suite result. Late status
|
||||
events re-read current state instead of replaying stale event payloads.
|
||||
|
||||
Direct tests are diagnostic and do not promote a whole suite on the new
|
||||
backends. Rerun the matching Fastcheck or merge/full suite to clear its gate.
|
||||
Per-build tests on the other new backend remain separate diagnostics. The
|
||||
legacy direct-test aggregation workflow is disabled while a new backend is
|
||||
selected. These workflows retain the existing trust assumption that only
|
||||
authorized status-writing integrations can publish CI status contexts.
|
||||
|
||||
For production cutover, drain existing Slurm builds: its preserved pipeline
|
||||
still emits canonical status contexts. The new uploader rejects a Slurm
|
||||
override when `default_backend` is `modal` or `vllm`, preventing later legacy
|
||||
builds from overwriting the promoted backend's checks. Ensure no old bootstrap
|
||||
bypasses the new uploader. Synchronize the operator `default_backend` and
|
||||
the GitHub repository variable, then clear or rerun required checks for all
|
||||
open PRs. Old green contexts do not become new-backend validation merely
|
||||
because a setting changed. Run both Fastcheck and the merge/full suite on
|
||||
the promoted backend before allowing merge. Apply the same drain and rerun
|
||||
procedure when rolling back to Slurm.
|
||||
|
||||
## Validation and Recovery
|
||||
|
||||
Inspect the rendered opt-in pipeline without submitting a build:
|
||||
|
||||
```bash
|
||||
CI_GPU_BACKEND=vllm TEST_SCOPE=fastcheck \
|
||||
/opt/fastvideo-gpu-ci/venv/bin/python -I \
|
||||
/opt/fastvideo-gpu-ci/source/scripts/gpu_ci/entrypoint.py render \
|
||||
--config /etc/fastvideo-gpu-ci.json
|
||||
```
|
||||
|
||||
Validate a one-GPU lane, then a two-/four-GPU lane and the complete Fastcheck
|
||||
suite. Exercise two PRs plus a third waiter, concurrent builds of the same
|
||||
PR, cancellation, retries, and coordinator restart. Confirm observed GPU
|
||||
reservations never exceed two PRs, four per PR, or eight in total. A GPU
|
||||
reservation includes a pending worker, so unavailable nodes cannot cause
|
||||
the controller to submit more work than its allowance.
|
||||
|
||||
Run SSIM, training, and performance canaries separately. References must
|
||||
match the effective GPU/runtime/attention backend; do not silently reuse
|
||||
L40S performance results as GB200 baselines or reseed references as part of
|
||||
routine CI. Workers keep W&B offline and disable reference publication.
|
||||
|
||||
Inspect the persistent ledger and recover an abandoned attempt with:
|
||||
|
||||
```bash
|
||||
/opt/fastvideo-gpu-ci/venv/bin/python -I \
|
||||
/opt/fastvideo-gpu-ci/source/scripts/gpu_ci/entrypoint.py status \
|
||||
--config /etc/fastvideo-gpu-ci.json
|
||||
|
||||
/opt/fastvideo-gpu-ci/venv/bin/python -I \
|
||||
/opt/fastvideo-gpu-ci/source/scripts/gpu_ci/entrypoint.py recover \
|
||||
--config /etc/fastvideo-gpu-ci.json --build-id BUILD_ID.JOB_ID
|
||||
```
|
||||
|
||||
Use the exact ledger ID from `status`; each Buildkite retry has a distinct
|
||||
job ID. Recovery refuses a live coordinator, persists cancellation, stops
|
||||
owned resources, and releases reservations only after confirming termination.
|
||||
If creation or cleanup is ambiguous, investigate the recorded handle on its
|
||||
backend and retain the reservation until the outcome is known. Do not delete
|
||||
the database or manually zero counters to unblock the queue.
|
||||
|
||||
The dispatcher uploads controller logs, per-lane numeric results, request
|
||||
metadata, and the suite summary to Buildkite. These are the sources for its
|
||||
exit status. Generated videos and JUnit files stay in the worker unless a
|
||||
dedicated Kubernetes artifact PVC is configured; automatic publication of
|
||||
those worker files and Modal worker artifacts is not implemented. This is
|
||||
a deployment limitation to account for before replacing existing artifact
|
||||
review workflows.
|
||||
@@ -5,7 +5,7 @@ hide:
|
||||
|
||||
# Cosmos recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="cosmos" data-recipes="../../assets/cookbook-recipes.json?v=8">
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="cosmos" data-recipes="../../assets/cookbook-recipes.json?v=11">
|
||||
<header class="cookbook-family-header">
|
||||
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
|
||||
<div class="cookbook-family-header__body">
|
||||
|
||||
@@ -5,7 +5,7 @@ hide:
|
||||
|
||||
# FLUX recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="flux" data-recipes="../../assets/cookbook-recipes.json?v=8">
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="flux" data-recipes="../../assets/cookbook-recipes.json?v=11">
|
||||
<header class="cookbook-family-header">
|
||||
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
|
||||
<div class="cookbook-family-header__body">
|
||||
|
||||
@@ -5,7 +5,7 @@ hide:
|
||||
|
||||
# GLM-Image recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="glm_image" data-recipes="../../assets/cookbook-recipes.json?v=8">
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="glm_image" data-recipes="../../assets/cookbook-recipes.json?v=11">
|
||||
<header class="cookbook-family-header">
|
||||
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
|
||||
<div class="cookbook-family-header__body">
|
||||
|
||||
@@ -5,7 +5,7 @@ hide:
|
||||
|
||||
# Hunyuan recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="hunyuan" data-recipes="../../assets/cookbook-recipes.json?v=8">
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="hunyuan" data-recipes="../../assets/cookbook-recipes.json?v=11">
|
||||
<header class="cookbook-family-header">
|
||||
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
|
||||
<div class="cookbook-family-header__body">
|
||||
|
||||
@@ -41,7 +41,7 @@ hide:
|
||||
<span class="cookbook-family-tile__footer">
|
||||
<span class="cookbook-family-tile__footer-top">
|
||||
<span><strong>MiniMax H3</strong><small>Video + stereo audio</small></span>
|
||||
<span class="cookbook-count">8 recipes</span>
|
||||
<span class="cookbook-count">9 recipes</span>
|
||||
</span>
|
||||
<ul class="cookbook-mode-row">
|
||||
<li>T2VA</li>
|
||||
|
||||
@@ -5,7 +5,7 @@ hide:
|
||||
|
||||
# Kandinsky 5 recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="kandinsky5" data-recipes="../../assets/cookbook-recipes.json?v=8">
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="kandinsky5" data-recipes="../../assets/cookbook-recipes.json?v=11">
|
||||
<header class="cookbook-family-header">
|
||||
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
|
||||
<div class="cookbook-family-header__body">
|
||||
|
||||
@@ -5,7 +5,7 @@ hide:
|
||||
|
||||
# LongCat recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="longcat" data-recipes="../../assets/cookbook-recipes.json?v=8">
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="longcat" data-recipes="../../assets/cookbook-recipes.json?v=11">
|
||||
<header class="cookbook-family-header">
|
||||
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
|
||||
<div class="cookbook-family-header__body">
|
||||
|
||||
@@ -5,7 +5,7 @@ hide:
|
||||
|
||||
# LTX recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="ltx2" data-recipes="../../assets/cookbook-recipes.json?v=8">
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="ltx2" data-recipes="../../assets/cookbook-recipes.json?v=11">
|
||||
<header class="cookbook-family-header">
|
||||
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
|
||||
<div class="cookbook-family-header__body">
|
||||
|
||||
@@ -5,7 +5,7 @@ hide:
|
||||
|
||||
# Matrix Game recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="matrixgame" data-recipes="../../assets/cookbook-recipes.json?v=8">
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="matrixgame" data-recipes="../../assets/cookbook-recipes.json?v=11">
|
||||
<header class="cookbook-family-header">
|
||||
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
|
||||
<div class="cookbook-family-header__body">
|
||||
|
||||
+18
-12
@@ -5,7 +5,13 @@ hide:
|
||||
|
||||
# MiniMax H3 recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="minimax_h3" data-default-recipe="fasth3-preview-cuda" data-recipes="../../assets/cookbook-recipes.json?v=8">
|
||||
FastH3 is two distilled MiniMax-H3 checkpoints. **V1** is the four-step
|
||||
launch. Some Hub repo names still say Preview. That name is historical. V1 is
|
||||
a full model, not a demo. **V2** is the eight-step checkpoint. More forwards
|
||||
is why V2 is the higher-quality FastH3. The V2 schedule contract is in
|
||||
[FastH3 distilled checkpoint schedules](../inference/fasth3-distilled.md).
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="minimax_h3" data-default-recipe="fasth3-preview-cuda" data-recipes="../../assets/cookbook-recipes.json?v=11">
|
||||
<header class="cookbook-family-header">
|
||||
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
|
||||
<div class="cookbook-family-header__body">
|
||||
@@ -16,7 +22,7 @@ hide:
|
||||
<p class="cookbook-eyebrow">Primary focus · Inference</p>
|
||||
<h2>MiniMax H3 recipes</h2>
|
||||
<p>Generate video and audio with H3. Run a server on CUDA, one DGX Spark, or Apple Silicon MLX to iterate on prompts, or call the pipeline directly from Python.</p>
|
||||
<span class="cookbook-count" data-cookbook-count>8 maintained recipes</span>
|
||||
<span class="cookbook-count" data-cookbook-count>9 maintained recipes</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cookbook-lifecycle" aria-label="Lifecycle stages">
|
||||
@@ -41,9 +47,9 @@ hide:
|
||||
<h2 id="h3-modes-heading">Supported modes</h2>
|
||||
<p>
|
||||
CUDA covers T2VA, FL2VA, and Ref2VA on the full checkpoint, plus FastH3
|
||||
Preview and FastH3 LoRA. FastH3 Preview also has a DGX Spark runtime with
|
||||
a 1-Spark or 2-Spark device row. MLX is T2VA only. Temporal <code>--fast</code>,
|
||||
spatial <code>--fast-spatial</code>, and opt-in VSA are flags on the same
|
||||
V1 and FastH3 V2. FastH3 V1 also has a DGX Spark runtime with
|
||||
a 1-Spark or 2-Spark device row. MLX is T2VA only: V1 and V2.
|
||||
Temporal <code>--fast</code>, spatial <code>--fast-spatial</code>, and opt-in VSA are flags on the same
|
||||
MLX script, not extra recipes.
|
||||
</p>
|
||||
<div class="cookbook-modes__table-wrap">
|
||||
@@ -58,8 +64,8 @@ hide:
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>T2VA</td>
|
||||
<td>Full H3, FastH3 Preview, FastH3 LoRA</td>
|
||||
<td>FastH3 Preview after a local DiT conversion</td>
|
||||
<td>Full H3, FastH3 V1, FastH3 LoRA, FastH3 V2</td>
|
||||
<td>FastH3 V1 or FastH3 V2 after a local DiT conversion</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>FL2VA</td>
|
||||
@@ -93,7 +99,7 @@ hide:
|
||||
</tr>
|
||||
<tr>
|
||||
<td>DGX Spark</td>
|
||||
<td>FastH3 Preview on one GB10, or two Sparks with Ray sequence parallel (<code>sp_size=2</code>) over QSFP RoCE. Select NVIDIA DGX Spark, then 1 Spark or 2 Sparks.</td>
|
||||
<td>FastH3 V1 on one GB10, or two Sparks with Ray sequence parallel (<code>sp_size=2</code>) over QSFP RoCE. Select NVIDIA DGX Spark, then 1 Spark or 2 Sparks.</td>
|
||||
<td>Not wired</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -251,7 +257,7 @@ cd FastVideo</code></pre>
|
||||
<pre><code>UV_TORCH_BACKEND=cu130 uv pip install -e ".[fasth3]"</code></pre>
|
||||
<p class="cookbook-eyebrow">Apple Silicon</p>
|
||||
<pre><code>uv pip install -e ".[mlx]"</code></pre>
|
||||
<p>Follow the <a href="../../getting_started/installation/mps/#run-fasth3-preview">Apple Silicon guide</a> for the download, conversion, and storage requirements.</p>
|
||||
<p>Follow the <a href="../../getting_started/installation/mlx/">MLX install guide</a> for the extra, <code>ffmpeg</code>, and a clone. Then pick FastH3 V1 or V2 in the builder above.</p>
|
||||
<p class="cookbook-eyebrow">NVIDIA DGX Spark</p>
|
||||
<pre><code>UV_TORCH_BACKEND=cu130 uv pip install -e .</code></pre>
|
||||
<p>Follow the <a href="../../getting_started/installation/spark/">DGX Spark install guide</a> for ARM64 CUDA 13. One Spark is a local process. Two Sparks need Ray on the QSFP link:</p>
|
||||
@@ -266,10 +272,10 @@ cd FastVideo</code></pre>
|
||||
<ul>
|
||||
<li>The full CUDA H3 examples request four GPUs by default. Their sources do not claim a GPU model or memory minimum.</li>
|
||||
<li>The FastH3 CUDA performance profile was measured on four GB200 GPUs. Use its strict profile when exact operation order matters more than the measured performance configuration.</li>
|
||||
<li>The MLX source runtime supports T2VA, optional temporal <code>--fast</code>, optional spatial <code>--fast-spatial</code>, and opt-in VSA on <code>--include-vsa</code> checkpoints. FL2VA, Ref2VA, and two-pass refinement are not wired.</li>
|
||||
<li>The MLX source runtime supports T2VA, optional temporal <code>--fast</code>, optional spatial <code>--fast-spatial</code>, and opt-in VSA on <code>--include-vsa</code> checkpoints. FastH3 V2 MLX converts with <code>--include-vsa</code> and runs eight forwards. FL2VA, Ref2VA, and two-pass refinement are not wired.</li>
|
||||
<li>GPU count and VAE decode backend are configurable in the builder above for FastH3 CUDA recipes. Only the value shown by default has a recorded run; other supported values are unmeasured here.</li>
|
||||
<li>DGX Spark is a runtime on FastH3 Preview, not a separate family card. Select NVIDIA DGX Spark, then 1 Spark or 2 Sparks. The CUDA GPU-count knob does not apply to Spark.</li>
|
||||
<li>GB10 has no FA4 / sm_100a VSA kernel. Keep <code>FASTVIDEO_FA4=0</code> and <code>FASTVIDEO_VSA_SM100A=0</code>. Legal <code>num_frames</code> values are <code>17n+5</code>, capped at 345 (15 s). A 345-frame request on one Spark can OOM.</li>
|
||||
<li>DGX Spark is a runtime on FastH3 V1, not a separate family card. Select NVIDIA DGX Spark, then 1 Spark or 2 Sparks. The CUDA GPU-count knob does not apply to Spark.</li>
|
||||
<li>GB10 has no FA4 / sm_100a VSA kernel. Keep <code>FASTVIDEO_FA4=0</code> and <code>FASTVIDEO_VSA_SM100A=0</code>. Legal <code>num_frames</code> values are <code>17n+5</code>, capped at 362 (15.08 s). A 345-frame request on one Spark can OOM.</li>
|
||||
<li>Gated or missing checkpoints: run <code>huggingface-cli login</code> and confirm you accepted the model's license on Hugging Face.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@ hide:
|
||||
|
||||
# MMAudio recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="mmaudio" data-recipes="../../assets/cookbook-recipes.json?v=8">
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="mmaudio" data-recipes="../../assets/cookbook-recipes.json?v=11">
|
||||
<header class="cookbook-family-header">
|
||||
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
|
||||
<div class="cookbook-family-header__body">
|
||||
|
||||
@@ -8,8 +8,9 @@ JavaScript client examples use that interface, but requests go to FastVideo.
|
||||
You do not need an OpenAI account or cloud key.
|
||||
|
||||
The [H3 recipe selector](minimax-h3.md) provides the same workflow with runtime
|
||||
selection. This guide covers FastH3 Preview text-to-video/audio. Other H3
|
||||
recipes keep their direct Python commands.
|
||||
selection. This guide covers FastH3 V1 (four forwards) and FastH3 V2
|
||||
(eight forwards). Start a server, then iterate in the playground or with the
|
||||
OpenAI Python client. Other H3 recipes keep their direct Python commands.
|
||||
|
||||
CUDA requests reuse one loaded `VideoGenerator`. The Python SDK can do the same
|
||||
when you reuse the generator across `generate()` calls. MLX keeps one
|
||||
@@ -36,6 +37,14 @@ advertises it as `fasth3`. It configures four CUDA GPUs but does not record
|
||||
a GPU model or VRAM requirement. This is a source-backed server profile, not
|
||||
the measured GB200 Python performance profile. Compilation is disabled.
|
||||
|
||||
For FastH3 V2, use the same install and the 8-step config (nine sigma
|
||||
points, eight DiT forwards, VSA sparsity 0.8):
|
||||
|
||||
```bash
|
||||
UV_TORCH_BACKEND=cu130 uv pip install -e ".[fasth3]"
|
||||
fastvideo serve --config examples/serving/openai_fasth3_8step.yaml --server.host 127.0.0.1
|
||||
```
|
||||
|
||||
Keep the server running. In another terminal, check readiness:
|
||||
|
||||
```bash
|
||||
@@ -58,7 +67,7 @@ FASTVIDEO_VSA_SM100A=0 FASTVIDEO_FA4=0 FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_
|
||||
The configuration loads `FastVideo/FastVideo-FastH3-4-step-Preview-v1-VSA-DataFree`
|
||||
on one GB10 and advertises it as `fasth3`. Lazy module load still reloads
|
||||
Qwen3-VL and the DiT between phases of each request. Legal `num_frames` values
|
||||
are `17n+5`, capped at 345 (15 s); a 345-frame request on one Spark can OOM.
|
||||
are `17n+5`, capped at 362 (15.08 s); a 345-frame request on one Spark can OOM.
|
||||
There is no cookbook server for two Sparks; use the generate YAML after
|
||||
[pairing two Sparks](../getting_started/installation/spark_pair.md).
|
||||
|
||||
@@ -72,7 +81,7 @@ After model loading completes, the response is `{"status":"ok"}`.
|
||||
|
||||
### Apple Silicon MLX
|
||||
|
||||
Complete the [Apple Silicon installation](../getting_started/installation/mps.md#run-fasth3-preview),
|
||||
Complete the [MLX install](../getting_started/installation/mlx.md),
|
||||
including `ffmpeg`. From your FastVideo clone, install the MLX extra:
|
||||
|
||||
```bash
|
||||
@@ -111,6 +120,22 @@ The MLX server has no recorded device or unified-memory requirement. The
|
||||
direct Python recipe's M4 Max measurements are not a server benchmark or a
|
||||
minimum-memory claim.
|
||||
|
||||
For FastH3 V2, convert that checkpoint's transformer with `--include-vsa`
|
||||
and start the 8-step config. Do not overwrite a V1 export. The
|
||||
[MiniMax H3 cookbook](minimax-h3.md) has the same download and convert
|
||||
commands as the Python recipe.
|
||||
|
||||
```bash
|
||||
hf download FastVideo/FastVideo-FastH3-8-Step-V2 --local-dir ./FastH3-8-Step-V2
|
||||
python scripts/checkpoint_conversion/convert_minimax_h3_mlx.py --model-root ./FastH3-8-Step-V2/transformer --out ./FastH3-8-Step-V2-MLX --formats "int8" --include-vsa
|
||||
python -m fastvideo.entrypoints.openai.mlx_server --config examples/serving/mlx_fasth3_8step.yaml
|
||||
```
|
||||
|
||||
That adapter passes `num_steps=8` when the HTTP field is `num_inference_steps=9`,
|
||||
and it enables the trained VSA recipe (sparsity 0.8, 64-token tiles). Reuse the
|
||||
preview VAE, audio VAE, text encoder, and tokenizer if those directories already
|
||||
exist; edit the YAML paths if your files live elsewhere.
|
||||
|
||||
## Open the playground
|
||||
|
||||
Open [the local H3 playground](http://127.0.0.1:8000/playground/) after startup.
|
||||
@@ -136,9 +161,11 @@ or manage a GPU server for you.
|
||||
## Generate with cURL or an SDK
|
||||
|
||||
These examples use the server's resolution, frame count, and sampling defaults.
|
||||
Do not copy Sora-specific durations or resolutions onto H3. Both server configs
|
||||
use 124 frames, 24 fps, and the five-point distilled sigma schedule with four
|
||||
DiT forwards. CUDA and one Spark use 1344 × 768; MLX uses 832 × 480.
|
||||
Do not copy Sora-specific durations or resolutions onto H3. V1 configs use
|
||||
124 frames, 24 fps, and the five-point distilled sigma schedule with four DiT
|
||||
forwards. V2 configs use nine sigma points and eight DiT forwards. CUDA
|
||||
and one Spark use 1344 × 768; MLX uses 832 × 480. The OpenAI Python client is
|
||||
the same for every FastH3 server that advertises `fasth3`.
|
||||
|
||||
Each client submits a job, checks for completion or failure, and downloads an
|
||||
MP4 named after the job ID. Polling stops after 30 minutes; a timeout does not
|
||||
|
||||
@@ -5,7 +5,7 @@ hide:
|
||||
|
||||
# Stable Audio recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="stable_audio" data-recipes="../../assets/cookbook-recipes.json?v=8">
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="stable_audio" data-recipes="../../assets/cookbook-recipes.json?v=11">
|
||||
<header class="cookbook-family-header">
|
||||
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
|
||||
<div class="cookbook-family-header__body">
|
||||
|
||||
@@ -5,7 +5,7 @@ hide:
|
||||
|
||||
# Stable Diffusion recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="sd35" data-recipes="../../assets/cookbook-recipes.json?v=8">
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="sd35" data-recipes="../../assets/cookbook-recipes.json?v=11">
|
||||
<header class="cookbook-family-header">
|
||||
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
|
||||
<div class="cookbook-family-header__body">
|
||||
|
||||
@@ -5,7 +5,7 @@ hide:
|
||||
|
||||
# TurboDiffusion recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="turbodiffusion" data-recipes="../../assets/cookbook-recipes.json?v=8">
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="turbodiffusion" data-recipes="../../assets/cookbook-recipes.json?v=11">
|
||||
<header class="cookbook-family-header">
|
||||
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
|
||||
<div class="cookbook-family-header__body">
|
||||
|
||||
@@ -5,7 +5,7 @@ hide:
|
||||
|
||||
# Wan recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="wan" data-recipes="../../assets/cookbook-recipes.json?v=8">
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="wan" data-recipes="../../assets/cookbook-recipes.json?v=11">
|
||||
<header class="cookbook-family-header">
|
||||
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
|
||||
<div class="cookbook-family-header__body">
|
||||
@@ -186,7 +186,7 @@ cd FastVideo</code></pre>
|
||||
<ul>
|
||||
<li>Out of memory on the A14B recipes: the checked-in sources already enable CPU offload; see <a href="../../inference/configuration/">Configuration</a> for the offload surface before reducing resolution or frames.</li>
|
||||
<li>The FastWan2.1 recipe requires <code>VIDEO_SPARSE_ATTN</code>; confirm the environment variable in the command was set in the same shell.</li>
|
||||
<li>FastMetal MLX: install with <code>uv pip install -e ".[mlx]"</code>, then follow the <a href="../../getting_started/installation/mps/">Apple Silicon guide</a>. CUDA FastWan-QAD checkpoints are refused on the MLX runtime.</li>
|
||||
<li>FastMetal MLX: install with the <a href="../../getting_started/installation/mlx/">MLX install guide</a>, then pick a FastMetal recipe in the builder. CUDA FastWan-QAD checkpoints are refused on the MLX runtime.</li>
|
||||
<li>FastMetal 5B uses <code>mlx_wan22_generate.py</code>. 1.3B and 14B use <code>mlx_wan_prompt_to_video.py</code>.</li>
|
||||
<li>Gated or missing checkpoints: run <code>huggingface-cli login</code> and confirm you accepted the model's license on Hugging Face.</li>
|
||||
</ul>
|
||||
|
||||
@@ -5,7 +5,7 @@ hide:
|
||||
|
||||
# Z-Image recipes
|
||||
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="zimage" data-recipes="../../assets/cookbook-recipes.json?v=8">
|
||||
<div class="cookbook-shell cookbook-family-page" data-cookbook data-family="zimage" data-recipes="../../assets/cookbook-recipes.json?v=11">
|
||||
<header class="cookbook-family-header">
|
||||
<a class="cookbook-back-link" href="../"><span aria-hidden="true">←</span> All model families</a>
|
||||
<div class="cookbook-family-header__body">
|
||||
|
||||
@@ -25,11 +25,13 @@ requests. HTTP handling and job polling remain asynchronous.
|
||||
| `GET` | `/v1/videos/{id}/content` | Download a completed MP4 |
|
||||
| `DELETE` | `/v1/videos/{id}` | Delete a job and its completed artifact |
|
||||
| `POST` | `/v1/images` | Generate an image |
|
||||
| `POST` | `/v1/images/generations` | OpenAI-compatible alias for image generation |
|
||||
| `POST` | `/v1/images/edits` | Generate an image from image references |
|
||||
| `GET` | `/v1/images/{id}/content` | Download a generated image |
|
||||
| `GET` | `/health` | Liveness probe |
|
||||
|
||||
`POST /v1/videos/generations` remains an alias for older FastVideo clients.
|
||||
`POST /v1/videos/generations` remains an alias for older FastVideo clients, and
|
||||
`POST /v1/images/generations` is the OpenAI Python client's image generation path.
|
||||
The OpenAI Python and JavaScript clients can create, retrieve, list, download,
|
||||
and delete video jobs. Use the [H3 server cookbook](../../cookbook/openai-api.md)
|
||||
for pinned client versions and executable examples. Download variants other
|
||||
|
||||
@@ -7,7 +7,7 @@ FastVideo supports the following hardware platforms:
|
||||
- **NVIDIA DGX Spark / GB10 (ARM64 + CUDA 13)** — [install](installation/spark.md),
|
||||
[performance](installation/spark_performance.md),
|
||||
[pair two Sparks](installation/spark_pair.md)
|
||||
- [Apple silicon](installation/mps.md)
|
||||
- [Apple silicon (MLX)](installation/mlx.md)
|
||||
|
||||
## Quick Installation
|
||||
|
||||
@@ -15,7 +15,7 @@ FastVideo supports the following hardware platforms:
|
||||
|
||||
Use uv as the default environment manager for faster and more stable installs.
|
||||
The commands below target NVIDIA CUDA 12; use `UV_TORCH_BACKEND=cu130` on
|
||||
CUDA 13. Apple silicon users should follow the [MPS guide](installation/mps.md).
|
||||
CUDA 13. Apple silicon users should follow the [MLX install guide](installation/mlx.md).
|
||||
|
||||
```bash
|
||||
# Create and activate a new uv environment
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
# Install FastVideo with MLX
|
||||
|
||||
Install FastVideo on a Mac, then generate from the cookbook. Local video uses
|
||||
the native MLX runtime, not CUDA, and not the old PyTorch MPS demo at
|
||||
`examples/inference/basic/basic_mps.py`.
|
||||
|
||||
## Requirements
|
||||
|
||||
- macOS 14 or newer
|
||||
- Python 3.12
|
||||
- `ffmpeg` (`brew install ffmpeg`)
|
||||
|
||||
## Install
|
||||
|
||||
Cookbook commands run from a clone.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/hao-ai-lab/FastVideo.git && cd FastVideo
|
||||
uv venv --python 3.12 --seed
|
||||
source .venv/bin/activate
|
||||
brew install ffmpeg
|
||||
uv pip install -e ".[mlx]"
|
||||
```
|
||||
|
||||
Conda is optional. After you activate a Conda env, still install with
|
||||
`uv pip` as above.
|
||||
|
||||
`uv pip install "fastvideo[mlx]"` from PyPI installs the extra only. It does
|
||||
not ship the example scripts the cookbook copies.
|
||||
|
||||
## Generate a video
|
||||
|
||||
Open the cookbook. Select Apple Silicon as the runtime. Each recipe has a
|
||||
Python command. FastH3 also has a server path for the playground and the
|
||||
OpenAI Python client.
|
||||
|
||||
- [Wan recipes](../../cookbook/wan.md) for FastMetal 1.3B, 5B, and 14B
|
||||
- [MiniMax H3 recipes](../../cookbook/minimax-h3.md) for FastH3 V1 and FastH3 V2
|
||||
- [H3 server guide](../../cookbook/openai-api.md) for the playground, cURL, and SDKs
|
||||
|
||||
FastH3 is two distilled MiniMax-H3 checkpoints. V1 is the four-step launch.
|
||||
Some Hub repo names still say Preview. That name is historical. V1 is a full
|
||||
model, not a demo. V2 is the eight-step checkpoint. More forwards is why V2
|
||||
is the higher-quality FastH3.
|
||||
|
||||
Recorded shapes and evidence live in the
|
||||
[support matrix](../../inference/support_matrix.md#apple-silicon-native-runtime).
|
||||
|
||||
## Hardware
|
||||
|
||||
- FastMetal 1.3B and 5B: 16 GB unified memory and up
|
||||
- FastMetal 14B: 36 GB unified memory and up
|
||||
- FastH3 V1 and V2: validated on an M4 Max with 36 GB unified memory
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **`basic_mps.py` is the wrong path.** That script is PyTorch MPS. Use an
|
||||
Apple Silicon recipe in the cookbook.
|
||||
- **Muxing fails.** Install `ffmpeg` with Homebrew.
|
||||
- **A cookbook command cannot find a script.** Run it from the FastVideo
|
||||
clone after `uv pip install -e ".[mlx]"`.
|
||||
|
||||
If that does not match what you see, open an issue on the
|
||||
[GitHub repository](https://github.com/hao-ai-lab/FastVideo) or ask in the
|
||||
[Slack community](https://join.slack.com/t/fastvideo/shared_invite/zt-3f4lao1uq-u~Ipx6Lt4J27AlD2y~IdLQ).
|
||||
@@ -1,260 +0,0 @@
|
||||
# MPS (Apple Silicon)
|
||||
|
||||
Install FastVideo on Apple Silicon and run FastMetal-QAD or FastH3 Preview.
|
||||
|
||||
Apple Silicon uses the MLX runtime. FastMetal-QAD ships ready-to-run MLX
|
||||
checkpoints; FastH3 Preview currently requires a local MLX DiT conversion.
|
||||
See the [FastMetal-QAD blog](https://haoailab.com/blogs/fastmetal/) and the
|
||||
[FastMetal collection](https://huggingface.co/collections/FastVideo/fastmetal).
|
||||
|
||||
## Requirements
|
||||
|
||||
- **OS: macOS 14 or newer**
|
||||
- **Python: 3.12.4**
|
||||
|
||||
## Set up using Python
|
||||
|
||||
### Create a new Python environment
|
||||
|
||||
#### uv
|
||||
Recommended default: use [uv](https://docs.astral.sh/uv/) for faster and more stable environment setup.
|
||||
|
||||
Please follow the [documentation](https://docs.astral.sh/uv/#getting-started) to install `uv`. After installing `uv`, create a new environment using:
|
||||
|
||||
```console
|
||||
# (Recommended) Create a new uv environment. Use `--seed` to install `pip` and `setuptools`.
|
||||
uv venv --python 3.12 --seed
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
#### Conda (alternative)
|
||||
|
||||
You can also create a Python environment using [Conda](https://docs.conda.io/projects/conda/en/stable/user-guide/getting-started.html).
|
||||
|
||||
##### 1. Install Miniconda (if not already installed)
|
||||
|
||||
```bash
|
||||
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-MacOSX-arm64.sh
|
||||
bash Miniconda3-latest-MacOSX-arm64.sh
|
||||
source ~/.zshrc
|
||||
```
|
||||
|
||||
##### 2. Create and activate a Conda environment for FastVideo
|
||||
|
||||
```bash
|
||||
conda create -n fastvideo python=3.12.4 -y
|
||||
conda activate fastvideo
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
```
|
||||
brew install ffmpeg
|
||||
```
|
||||
|
||||
### Installation
|
||||
|
||||
FastMetal's native Apple Silicon runtime requires the `mlx` extra.
|
||||
|
||||
#### With uv (recommended)
|
||||
|
||||
```bash
|
||||
uv pip install "fastvideo[mlx]"
|
||||
```
|
||||
|
||||
#### With Conda environment (alternative)
|
||||
|
||||
`uv` works inside an active conda env too, so prefer `uv pip` for the actual install:
|
||||
|
||||
```bash
|
||||
uv pip install "fastvideo[mlx]"
|
||||
```
|
||||
|
||||
### Installation from Source
|
||||
|
||||
#### 1. Clone the FastVideo repository
|
||||
|
||||
```bash
|
||||
git clone https://github.com/hao-ai-lab/FastVideo.git && cd FastVideo
|
||||
```
|
||||
|
||||
#### 2. Install FastVideo
|
||||
|
||||
Basic installation:
|
||||
|
||||
```bash
|
||||
uv pip install -e ".[mlx]"
|
||||
```
|
||||
|
||||
Alternative with Conda environment:
|
||||
|
||||
```bash
|
||||
uv pip install -e ".[mlx]"
|
||||
```
|
||||
|
||||
## Run FastMetal-QAD
|
||||
|
||||
Each release is self-contained. Download one checkpoint and point both
|
||||
`--model-root` and `--mlx-checkpoint` at it (the example also auto-detects
|
||||
`mlx_dit.json` under `--model-root`).
|
||||
|
||||
| Checkpoint | Script | Mac tier |
|
||||
| --- | --- | --- |
|
||||
| [`FastVideo/FastMetal-1.3B-QAD`](https://huggingface.co/FastVideo/FastMetal-1.3B-QAD) | `mlx_wan_prompt_to_video.py` | 16 GB+ |
|
||||
| [`FastVideo/FastMetal-5B-QAD`](https://huggingface.co/FastVideo/FastMetal-5B-QAD) | `mlx_wan22_generate.py` | 16 GB+ |
|
||||
| [`FastVideo/FastMetal-14B-QAD`](https://huggingface.co/FastVideo/FastMetal-14B-QAD) | `mlx_wan_prompt_to_video.py` | 36 GB+ |
|
||||
|
||||
```bash
|
||||
hf download FastVideo/FastMetal-1.3B-QAD --local-dir ./FastMetal-1.3B-QAD
|
||||
|
||||
python examples/inference/basic/mlx_wan_prompt_to_video.py \
|
||||
--model-root ./FastMetal-1.3B-QAD \
|
||||
--mlx-checkpoint ./FastMetal-1.3B-QAD \
|
||||
--height 480 --width 832 --num-frames 81 \
|
||||
--prompt "A bird's-eye view of a misty forest valley at dawn."
|
||||
```
|
||||
|
||||
14B uses the same script. Point both flags at `./FastMetal-14B-QAD`. That repo also ships an EMA variant: keep `--model-root` at the repo root and set `--mlx-checkpoint ./FastMetal-14B-QAD/ema`.
|
||||
|
||||
Wan2.2 5B uses a different latent layout, so it has its own entrypoint:
|
||||
|
||||
```bash
|
||||
hf download FastVideo/FastMetal-5B-QAD --local-dir ./FastMetal-5B-QAD
|
||||
|
||||
python examples/inference/basic/mlx_wan22_generate.py \
|
||||
--mlx-checkpoint ./FastMetal-5B-QAD \
|
||||
--text-encoder-root ./FastMetal-5B-QAD \
|
||||
--vae-root ./FastMetal-5B-QAD/vae \
|
||||
--height 704 --width 1280 --num-frames 81 \
|
||||
--prompt "A cinematic portrait with soft neon lighting and smooth camera motion."
|
||||
```
|
||||
|
||||
CUDA FastWan-QAD (`FastVideo/FastWan-QAD-1.3B`, `FastVideo/FastWan-QAD-FP8-1.3B`) is a separate NVIDIA release. The MLX examples look for FastMetal packed weights (`mlx_dit.json`).
|
||||
|
||||
`basic_mps.py` is a generic PyTorch MPS demo. For local video on Mac, use the FastMetal commands above.
|
||||
|
||||
## Run FastH3 Preview
|
||||
|
||||
FastH3 Preview uses the existing MLX runtime for text-to-video-with-audio
|
||||
(T2VA). The runtime streams the Qwen3-VL text conditioner, loads one
|
||||
heavyweight component at a time, denoises synchronized video and audio
|
||||
latents with a converted INT8, INT6, or INT4 DiT, and decodes both modalities
|
||||
with native MLX VAEs.
|
||||
|
||||
Download the FastH3 snapshot, then convert one or more DiT formats:
|
||||
|
||||
```bash
|
||||
hf download FastVideo/FastVideo-Minimax-FastH3-Preview-v0.2 \
|
||||
--local-dir ./FastH3-Preview-v0.2
|
||||
|
||||
python scripts/checkpoint_conversion/convert_minimax_h3_mlx.py \
|
||||
--model-root ./FastH3-Preview-v0.2/transformer \
|
||||
--out ./FastH3-MLX \
|
||||
--formats "int6"
|
||||
```
|
||||
|
||||
Dense conversion drops the trained VSA gate projections. To keep them (INT6
|
||||
weight-only, same affine grid as the other linear matrices) write a **new**
|
||||
directory:
|
||||
|
||||
```bash
|
||||
python scripts/checkpoint_conversion/convert_minimax_h3_mlx.py \
|
||||
--model-root ./FastH3-Preview-v0.2/transformer \
|
||||
--out ./FastH3-MLX-vsa \
|
||||
--formats "int6" \
|
||||
--include-vsa
|
||||
```
|
||||
|
||||
Do not overwrite an existing dense export such as `./FastH3-MLX/int6`.
|
||||
|
||||
Run the baseline path:
|
||||
|
||||
```bash
|
||||
python examples/inference/basic/mlx_fasth3.py \
|
||||
--model-root ./FastH3-Preview-v0.2 \
|
||||
--mlx-checkpoint ./FastH3-MLX/int6 \
|
||||
--prompt "(S1) A presenter says <d>[English] Fast H3 is amazing.</d>" \
|
||||
--height 480 --width 832 --num-frames 124 --seed 2026 \
|
||||
--output-path ./outputs/fasth3_int6.mp4
|
||||
```
|
||||
|
||||
Add `--fast` for temporal fast mode. It denoises a shorter video sequence,
|
||||
uses MLX RIFE to restore the requested frame count, and keeps the audio
|
||||
sequence at full duration:
|
||||
|
||||
```bash
|
||||
python examples/inference/basic/mlx_fasth3.py \
|
||||
--model-root ./FastH3-Preview-v0.2 \
|
||||
--mlx-checkpoint ./FastH3-MLX/int6 \
|
||||
--prompt "(S1) A presenter says <d>[English] Fast H3 is even faster.</d>" \
|
||||
--height 720 --width 1280 --num-frames 124 --seed 2027 \
|
||||
--fast \
|
||||
--output-path ./outputs/fasth3_int6_fast_720p.mp4
|
||||
```
|
||||
|
||||
Add `--fast-spatial` for spatial fast mode, `--fast`'s spatial twin. It
|
||||
denoises and decodes on the smallest 32px-aligned canvas covering the
|
||||
requested size divided by `--fast-spatial-scale` (a 480x832 request runs on a
|
||||
256x416 canvas), then resamples the decoded frames up to the requested size
|
||||
in pixel space. It composes with `--fast`. This is a speed/quality trade-off
|
||||
and stays off by default: the output carries the reduced canvas's detail
|
||||
budget, so it reads softer than a native-resolution render, with the unsharp
|
||||
pass countering some but not all of the difference:
|
||||
|
||||
```bash
|
||||
python examples/inference/basic/mlx_fasth3.py \
|
||||
--model-root ./FastH3-Preview-v0.2 \
|
||||
--mlx-checkpoint ./FastH3-MLX/int6 \
|
||||
--prompt "(S1) A presenter says <d>[English] Fast H3 is fastest.</d>" \
|
||||
--height 480 --width 832 --num-frames 124 --seed 2028 \
|
||||
--fast --fast-spatial \
|
||||
--output-path ./outputs/fasth3_int6_fast_spatial.mp4
|
||||
```
|
||||
|
||||
VSA is off by default. A dense-only checkpoint (no `--include-vsa`) keeps the
|
||||
existing fused-SDPA path. After converting with `--include-vsa`, enable the
|
||||
sparse path explicitly:
|
||||
|
||||
```bash
|
||||
python examples/inference/basic/mlx_fasth3.py \
|
||||
--model-root ./FastH3-Preview-v0.2 \
|
||||
--mlx-checkpoint ./FastH3-MLX-vsa/int6 \
|
||||
--vsa --vsa-sparsity 0.9 --vsa-tile-size 64 --vsa-prefix-mode exempt \
|
||||
--prompt "(S1) A presenter says <d>[English] Fast H3 is amazing.</d>" \
|
||||
--height 720 --width 1280 --num-frames 124 --seed 2026 \
|
||||
--output-path ./outputs/fasth3_int6_vsa_720p.mp4
|
||||
```
|
||||
|
||||
`--vsa-impl auto` uses the chunked gather+SDPA **reference** path.
|
||||
`--vsa-impl simd` is an opt-in SIMD-group kernel (tile 64, head dim 128) that
|
||||
falls back to reference on unsupported shapes. It is not the default.
|
||||
`--vsa-impl reference` is the same as `auto`.
|
||||
|
||||
!!! note "Current MLX scope"
|
||||
This source runtime supports T2VA, temporal `--fast`, spatial
|
||||
`--fast-spatial`, and opt-in VSA. FL2VA, Ref2VA, two-pass refinement, and
|
||||
`VideoGenerator` registry dispatch are not wired yet. INT8/INT6/INT4
|
||||
quantization is **weight-only**; VSA attention Q/K/V stay BF16. Old dense
|
||||
MLX checkpoints remain valid for dense inference and raise a reconvert
|
||||
error if `--vsa` is set. The checkpoint uses the MiniMax H3
|
||||
Community License; review the model card before use or redistribution.
|
||||
|
||||
## Development Environment Setup
|
||||
|
||||
If you're planning to contribute to FastVideo please see the following page:
|
||||
[Contributor Guide](../../contributing/overview.md)
|
||||
|
||||
## Hardware Requirements
|
||||
|
||||
- **1.3B / 5B:** 16 GB unified memory and up (M1 and later)
|
||||
- **14B:** 36 GB unified memory and up
|
||||
- **FastH3 Preview:** validated on an M4 Max with 36 GB unified memory; use one
|
||||
converted DiT format at a time and leave substantial free disk space for the
|
||||
source snapshot plus the converted checkpoint
|
||||
- Fanless 13-inch MacBook Air can run 1.3B and 5B at the same resolutions
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If you encounter any issues during installation, please open an issue on our [GitHub repository](https://github.com/hao-ai-lab/FastVideo).
|
||||
|
||||
You can also join our [Slack community](https://join.slack.com/t/fastvideo/shared_invite/zt-3f4lao1uq-u~Ipx6Lt4J27AlD2y~IdLQ) for additional support.
|
||||
@@ -147,7 +147,7 @@ that can't move here.
|
||||
Two Sparks with QSFP cables: [Pair two NVIDIA DGX Sparks](spark_pair.md) for
|
||||
one FastH3 clip across both GPUs (`sp_size=2` over Ray). Copy-paste commands
|
||||
for one or two Sparks also live on the
|
||||
[MiniMax H3 cookbook](../../cookbook/minimax-h3.md): pick FastH3 Preview,
|
||||
[MiniMax H3 cookbook](../../cookbook/minimax-h3.md): pick FastH3 V1,
|
||||
then NVIDIA DGX Spark, then 1 Spark or 2 Sparks.
|
||||
|
||||
## Development Environment Setup
|
||||
|
||||
@@ -7,7 +7,7 @@ by the QSFP ConnectX-7 cables can run **one clip faster** and can hold a
|
||||
**longer clip** (up to the FastH3 15 s cap).
|
||||
|
||||
Copy-paste commands for both counts are on the
|
||||
[MiniMax H3 cookbook](../../cookbook/minimax-h3.md): FastH3 Preview → NVIDIA DGX
|
||||
[MiniMax H3 cookbook](../../cookbook/minimax-h3.md): FastH3 V1 → NVIDIA DGX
|
||||
Spark → 1 Spark or 2 Sparks.
|
||||
|
||||
This is FastVideo sequence parallel (`sp_size=2`) over Ray, not a third-party
|
||||
@@ -109,7 +109,7 @@ Run the driver on the **head**, same venv, same QSFP IP.
|
||||
`basic_fasth3.py` defaults target a four-GPU GB200 profile: 768×1344, `sm100a`
|
||||
VSA, FA4, four GPUs. On Sparks you must override the kernel flags. Height,
|
||||
width, frames, steps, seed, and prompt are yours. Change them. Legal
|
||||
`num_frames` values are `17n+5`, capped at 345.
|
||||
`num_frames` values are `17n+5`, capped at 362.
|
||||
|
||||
GB10 has no FA4 / sm_100a VSA kernel, so `--vsa-kernel triton --no-fa4` stays
|
||||
required on this box. `--execution-backend ray` is optional when `RAY_ADDRESS`
|
||||
@@ -152,8 +152,8 @@ Stop the cluster when you are done: `ray stop` on both nodes.
|
||||
## FastH3 frame counts
|
||||
|
||||
H3 is 24 fps. Legal `num_frames` values are `17n+5`. The pipeline rejects
|
||||
clips longer than **15 s**. The longest legal length is **345 frames**
|
||||
(14.375 s). 360 frames aligns to 362 and fails the duration check.
|
||||
clips longer than **15 s**. The longest legal length is **362 frames**
|
||||
(15.083 s). 360 frames aligns to 362 and is accepted.
|
||||
|
||||
## Measured on two GB10s (2026-08-31)
|
||||
|
||||
@@ -179,7 +179,7 @@ VAE, same 4-step schedule:
|
||||
| Two Sparks, SP=2 | 2 | 124 | **215.2 s** | 72.4 s |
|
||||
|
||||
Those medians used `--height` / `--width` / `--num-frames` as CLI flags. Swap
|
||||
them. Native 480p on this model is 480×832, 124 frames. The 15 s cap is 345
|
||||
them. Native 480p on this model is 480×832, 124 frames. The 15 s cap is 362
|
||||
frames.
|
||||
|
||||
The first VAE decode still pays `torch.compile`. Later `generate()` calls in
|
||||
|
||||
@@ -181,6 +181,9 @@ is power-cycled. To avoid it:
|
||||
and frame counts are valid. Weights stay replicated, so lazy module load
|
||||
(auto on GB10) is still required on each box. Bring-up and knobs:
|
||||
[Pair two NVIDIA DGX Sparks](spark_pair.md).
|
||||
- A worker's SIGTERM log and traceback show where it was interrupted, not why it
|
||||
was selected; confirm the cause in the `earlyoom` service or system logs. A
|
||||
later SIGKILL or kernel OOM kill cannot be caught and reported by Python.
|
||||
|
||||
## Gotchas specific to the GB10
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
# FastH3 distilled checkpoint schedules
|
||||
|
||||
Base MiniMax-H3 still uses the scheduler shifts in its checkpoint (video 12,
|
||||
audio 3), BF16 text encoding, and the existing uniform schedule. The default
|
||||
`basic_fasth3.py` example still targets the four-forward preview. Selecting a
|
||||
shift-10 eight-forward checkpoint is an explicit choice of model and recipe;
|
||||
it does not change either default or enable NVFP4.
|
||||
|
||||
## Eight-forward T2AV recipe
|
||||
|
||||
The public checkpoint is
|
||||
[`FastVideo/FastVideo-FastH3-8-Step-V2`](https://huggingface.co/FastVideo/FastVideo-FastH3-8-Step-V2)
|
||||
(MiniMax H3 Community License), trained with video/audio shifts 10/3, VSA
|
||||
sparsity 0.8, 64-token tiles, and the DMD rungs
|
||||
`[999, 874, 749, 624, 500, 375, 250, 125]`. `basic_fasth3_8step.py` pins that
|
||||
checkpoint and recipe as defaults; it shares the preview example's CLI, so every
|
||||
other flag works unchanged:
|
||||
|
||||
```bash
|
||||
python examples/inference/basic/basic_fasth3_8step.py \
|
||||
--prompt 'A slow cinematic drone shot glides over a coastal town; gulls call over the harbor.' \
|
||||
--num-gpus 4 --vsa-kernel sm100a \
|
||||
--profile strict --no-inference-torch-compile --no-compile-vae \
|
||||
--height 768 --width 1344 --num-frames 124 \
|
||||
--output outputs/fasth3-8step
|
||||
```
|
||||
|
||||
Pass `--model-path` to use a local snapshot of the full export (not just its
|
||||
`transformer` subdirectory). `--steps` is the number of sigma-grid points,
|
||||
including the terminal zero; nine points run exactly eight transformer
|
||||
forwards, and the script rejects any other value because the checkpoint's
|
||||
ladder has eight rungs. The rungs are unshifted noise levels on the 1000-step training
|
||||
clock; each scheduler applies its own shift once, and the transformer receives
|
||||
H3 clean-time values (`1 - sigma`). A uniform nine-point grid is not a substitute
|
||||
for those rungs.
|
||||
|
||||
Compilation and H3 fusions are disabled above to establish an eager reference;
|
||||
they can be evaluated separately. On hardware without the sm100a extension,
|
||||
use `--vsa-kernel triton`; compare outputs and performance before adopting that
|
||||
backend. This recipe is T2AV-only, not a distilled `transformer_ref` model.
|
||||
|
||||
## Export metadata and validation
|
||||
|
||||
The export's `fastvideo_inference.json` supplies the trained ladder. The
|
||||
schedule fields of `fasth3-inference-contract-v1` are:
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": "fasth3-inference-contract-v1",
|
||||
"dmd_denoising_steps": [999, 874, 749, 624, 500, 375, 250, 125],
|
||||
"num_inference_steps": 9,
|
||||
"transformer_forwards": 8,
|
||||
"video_scheduler_shift": 10.0,
|
||||
"audio_scheduler_shift": 3.0
|
||||
}
|
||||
```
|
||||
|
||||
The loader keeps this file when downloading the selected H3 components from
|
||||
Hugging Face. It checks that the two declared shifts agree with
|
||||
`scheduler/scheduler_config.json` and `audio_scheduler/scheduler_config.json`.
|
||||
Missing/invalid rungs, inconsistent counts, or an explicit conflicting ladder
|
||||
are errors. The denoiser rejects a request with the wrong number of grid points.
|
||||
The metadata does not silently change request dimensions, step count, attention
|
||||
backend, sparsity, precision, or offload/compile settings: set those explicitly
|
||||
as above.
|
||||
|
||||
For exports without this sidecar, an explicit ladder is supported via
|
||||
`MiniMaxH3PipelineConfig.dmd_denoising_steps`, or through the typed API's
|
||||
`PipelineSelection(experimental={"dmd_denoising_steps": [...]})`. The shifts
|
||||
still come from the checkpoint scheduler configs. Keep generic `flow_shift`
|
||||
unset: H3 has separate video and audio shifts, not one shared shift.
|
||||
|
||||
This documents execution support for the published checkpoint. It is not a
|
||||
quality claim: compare video/audio output against base MiniMax-H3 on your own
|
||||
prompts before adopting it.
|
||||
|
||||
## Apple Silicon
|
||||
|
||||
`mlx_fasth3.py` stays on FastH3 V1 and its uniform AdaLN cache.
|
||||
`mlx_fasth3_8step.py` is the eight-forward MLX recipe for FastH3 V2. It
|
||||
reads the same `fastvideo_inference.json` rungs and shifts, and it expects an
|
||||
MLX DiT whose AdaLN cache was converted from that contract. Reuse the V1
|
||||
snapshot's VAE, audio VAE, text encoder, and tokenizer; only the DiT and the
|
||||
sidecar change. Rank-reduced AdaLN checkpoints are unchanged and are not
|
||||
produced by the MLX converter.
|
||||
|
||||
Install is in the
|
||||
[MLX install guide](../getting_started/installation/mlx.md). Generation and
|
||||
serving are in the [MiniMax H3 cookbook](../cookbook/minimax-h3.md) and the
|
||||
[H3 server guide](../cookbook/openai-api.md).
|
||||
@@ -5,10 +5,10 @@ This page contains step-by-step instructions to get you quickly started with vid
|
||||
## Requirements
|
||||
|
||||
- **OS**: Linux (tested on Ubuntu 22.04+), or macOS on Apple silicon via the
|
||||
[MPS installation guide](../getting_started/installation/mps.md)
|
||||
[MLX install guide](../getting_started/installation/mlx.md)
|
||||
- **Python**: 3.10-3.12
|
||||
- **CUDA**: 12.6 or 13.0 (NVIDIA GPUs)
|
||||
- **GPU**: At least one NVIDIA GPU, or an Apple silicon chip with MPS
|
||||
- **GPU**: At least one NVIDIA GPU, or an Apple silicon chip with the MLX runtime
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
@@ -10,7 +10,9 @@ that tradeoff. It is not a lossless acceleration of the full VAE.
|
||||
|
||||
## Generate a video
|
||||
|
||||
Use your existing MLX FastH3 environment and converted checkpoint:
|
||||
Use your existing MLX FastH3 environment and converted checkpoint. The same
|
||||
`--video-decode-backend taeh3` flag works on `mlx_fasth3.py` (V1) and
|
||||
`mlx_fasth3_8step.py` (V2):
|
||||
|
||||
```bash
|
||||
python examples/inference/basic/mlx_fasth3.py \
|
||||
|
||||
@@ -294,6 +294,9 @@ automatically.
|
||||
### Requirements
|
||||
|
||||
- **GPU**: sm89+ (H100, L40S, RTX 4090, or newer) for hardware FP8 compute
|
||||
- **ROCm**: CDNA4 (MI350X / MI355X, gfx950) runs the FP8 `_scaled_mm` path through
|
||||
hipBLASLt (OCP e4m3fn). MI300X (gfx942) only exposes the `fnuz` FP8 formats and
|
||||
takes the bf16 dequant fallback like a pre-sm89 GPU.
|
||||
- No additional packages required beyond the base FastVideo install
|
||||
|
||||
### Usage
|
||||
|
||||
@@ -185,13 +185,14 @@ optimizations: absence means **untested**, not incompatible.
|
||||
| MLX FastMetal T2V 1.3B | [`FastVideo/FastMetal-1.3B-QAD`](https://huggingface.co/FastVideo/FastMetal-1.3B-QAD) | 480x832, 81 frames, 3-step DMD, INT8 DiT + TAEHV decode | Apple M4 Max, 16 GB+ unified memory | Released |
|
||||
| MLX FastMetal T2V 5B | [`FastVideo/FastMetal-5B-QAD`](https://huggingface.co/FastVideo/FastMetal-5B-QAD) | 480p / 720p, 81 frames, 3-step DMD, INT8 DiT + TAEHV decode; optional `--fast`, `--fast-spatial`, `--refine`. The checked-in example is T2V. CUDA Wan2.2 TI2V 5B is the image-capable path. | Apple M4 Max, 16 GB+ unified memory | Released |
|
||||
| MLX FastMetal T2V 14B | [`FastVideo/FastMetal-14B-QAD`](https://huggingface.co/FastVideo/FastMetal-14B-QAD) | 480p / 720p, 81 frames, 3-step DMD, INT8 DiT + TAEHV decode | Apple M4 Max, 36 GB+ unified memory | Released |
|
||||
| MLX FastH3 Preview T2VA | [`FastVideo/FastVideo-Minimax-FastH3-Preview-v0.2`](https://huggingface.co/FastVideo/FastVideo-Minimax-FastH3-Preview-v0.2) + locally converted DiT | 480p / 720p, 124 frames, 4-step DMD2, INT8/INT6/INT4 **weight-only** DiT, native video + audio VAE; optional temporal RIFE fast mode; optional spatial fast mode; optional VSA (tile 64/256, exempt/compete) on `--include-vsa` checkpoints | Apple M4 Max, 36 GB unified memory | Source runtime; T2VA only |
|
||||
| MLX FastH3 V1 T2VA | [`FastVideo/FastVideo-Minimax-FastH3-Preview-v0.2`](https://huggingface.co/FastVideo/FastVideo-Minimax-FastH3-Preview-v0.2) + locally converted DiT | 480p / 720p, 124 frames, 4-step DMD2, INT8/INT6/INT4 **weight-only** DiT, native video + audio VAE; optional temporal RIFE fast mode; optional spatial fast mode; optional VSA (tile 64/256, exempt/compete) on `--include-vsa` checkpoints | Apple M4 Max, 36 GB unified memory | Source runtime; T2VA only |
|
||||
| MLX FastH3 V2 T2VA | [`FastVideo/FastVideo-FastH3-8-Step-V2`](https://huggingface.co/FastVideo/FastVideo-FastH3-8-Step-V2) + locally converted DiT | 480p / 720p, 124 frames, 8-step DMD2, INT8 **weight-only** DiT with `--include-vsa`, trained VSA 0.8 / tile 64, native video + audio VAE; AdaLN cache from `fastvideo_inference.json` | Apple M4 Max, 36 GB unified memory | Source runtime; T2VA only |
|
||||
|
||||
Apple Silicon uses the native MLX runtime. FastMetal-QAD is the packaged Wan
|
||||
release, while FastH3 Preview currently uses a source checkout and local DiT
|
||||
conversion. CUDA FastWan-QAD (`FastVideo/FastWan-QAD-1.3B`,
|
||||
release. FastH3 V1 and FastH3 V2 use a source checkout and local DiT conversion.
|
||||
CUDA FastWan-QAD (`FastVideo/FastWan-QAD-1.3B`,
|
||||
`FastVideo/FastWan-QAD-FP8-1.3B`) is the NVIDIA release. See the
|
||||
[Apple Silicon guide](../getting_started/installation/mps.md) and the
|
||||
[MLX install guide](../getting_started/installation/mlx.md) and the
|
||||
[FastMetal-QAD blog](https://haoailab.com/blogs/fastmetal/).
|
||||
|
||||
**Note**: Wan2.2 TI2V 5B has some quality issues when performing I2V generation. We are working on fixing this issue.
|
||||
@@ -223,8 +224,9 @@ Per the installation guides:
|
||||
the [DGX Spark install guide](../getting_started/installation/spark.md).
|
||||
Two Sparks over QSFP use Ray sequence parallel; see
|
||||
[Pair two NVIDIA DGX Sparks](../getting_started/installation/spark_pair.md).
|
||||
- **Apple silicon** — macOS 14 or newer; FastMetal-QAD via the MLX runtime. See the
|
||||
[Apple Silicon guide](../getting_started/installation/mps.md). The older
|
||||
- **Apple silicon** — macOS 14 or newer; MLX runtime for FastMetal-QAD, FastH3
|
||||
V1, and FastH3 V2. See the
|
||||
[MLX install guide](../getting_started/installation/mlx.md). The older
|
||||
[`basic_mps.py`](https://github.com/hao-ai-lab/FastVideo/blob/main/examples/inference/basic/basic_mps.py)
|
||||
demo is PyTorch MPS only.
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ python examples/inference/basic/basic.py
|
||||
### Apple Silicon (FastMetal-QAD)
|
||||
|
||||
Use the MLX runtime with FastMetal-QAD. See the
|
||||
[Apple Silicon guide](https://hao-ai-lab.github.io/FastVideo/getting_started/installation/mps/).
|
||||
[MLX install guide](https://hao-ai-lab.github.io/FastVideo/getting_started/installation/mlx/).
|
||||
|
||||
```bash
|
||||
hf download FastVideo/FastMetal-1.3B-QAD --local-dir ./FastMetal-1.3B-QAD
|
||||
@@ -39,7 +39,7 @@ with
|
||||
|
||||
`examples/inference/basic/basic_mps.py` is the older PyTorch MPS demo.
|
||||
|
||||
FastH3 Preview T2VA also runs through the native MLX runtime. Convert the DiT
|
||||
FastH3 V1 T2VA also runs through the native MLX runtime. Convert the DiT
|
||||
to INT8, INT6, or INT4 first, then run:
|
||||
|
||||
```bash
|
||||
@@ -51,17 +51,21 @@ python examples/inference/basic/mlx_fasth3.py \
|
||||
--output-path ./outputs/fasth3_int6.mp4
|
||||
```
|
||||
|
||||
FastH3 V2 is a separate checkpoint and script. Convert with
|
||||
`--include-vsa` and run `mlx_fasth3_8step.py`. Do not reuse a V1 DiT.
|
||||
|
||||
Pass `--fast` for temporal RIFE fast mode and `--fast-spatial` for spatial
|
||||
fast mode (reduced-canvas denoise + pixel-space upsample); the two compose.
|
||||
VSA is opt-in: convert with `--include-vsa` and pass `--vsa` (see the
|
||||
[Apple Silicon guide](https://hao-ai-lab.github.io/FastVideo/getting_started/installation/mps/)).
|
||||
This MLX entrypoint currently supports T2VA only; FL2VA, Ref2VA, and
|
||||
V1 VSA is opt-in: convert with `--include-vsa` and pass `--vsa`. V2
|
||||
turns VSA on by default. See the
|
||||
[MLX install guide](https://hao-ai-lab.github.io/FastVideo/getting_started/installation/mlx/).
|
||||
This MLX entrypoint supports T2VA only; FL2VA, Ref2VA, and
|
||||
two-pass refinement remain follow-up work. INT6/INT8/INT4 are
|
||||
weight-only; VSA attention activations stay BF16. Dense-only checkpoints keep
|
||||
working for dense inference.
|
||||
weight-only; VSA attention activations stay BF16. Dense-only V1
|
||||
checkpoints keep working for dense inference.
|
||||
|
||||
The complete setup and conversion commands are in the
|
||||
[Apple Silicon guide](https://hao-ai-lab.github.io/FastVideo/getting_started/installation/mps/).
|
||||
[MLX install guide](https://hao-ai-lab.github.io/FastVideo/getting_started/installation/mlx/).
|
||||
|
||||
For an example running DMD+VSA inference:
|
||||
```
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Eight-forward video+audio generation with the FastH3 8-Step V2 checkpoint.
|
||||
|
||||
``FastVideo/FastVideo-FastH3-8-Step-V2`` is a DMD-distilled MiniMax H3 T2AV
|
||||
student trained with video/audio scheduler shifts 10/3, VSA sparsity 0.8 and
|
||||
64-token tiles, on the explicit rung ladder ``[999, 874, 749, 624, 500, 375,
|
||||
250, 125]``. Its ``fastvideo_inference.json`` sidecar carries that ladder; the
|
||||
pipeline loads it, checks the declared shifts against the checkpoint scheduler
|
||||
configs, and runs exactly eight transformer forwards from nine sigma-grid
|
||||
points. This script only sets those defaults; it does not change the
|
||||
four-forward preview example.
|
||||
|
||||
``--steps`` must stay at 9 for this checkpoint: the trained ladder has eight
|
||||
rungs, and a different grid is not a valid recipe for it.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
from collections.abc import Sequence
|
||||
|
||||
try:
|
||||
from . import basic_fasth3
|
||||
except ImportError:
|
||||
# Direct script execution puts this directory, rather than ``examples``, on
|
||||
# sys.path. Keep both ``python file.py`` and module/importlib use working.
|
||||
import basic_fasth3 # type: ignore[no-redef]
|
||||
|
||||
MODEL = "FastVideo/FastVideo-FastH3-8-Step-V2"
|
||||
GRID_POINTS = 9 # nine sigma-grid points = eight DiT forwards
|
||||
VSA_SPARSITY = 0.8
|
||||
|
||||
|
||||
def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
||||
parser = basic_fasth3.build_parser(description=__doc__)
|
||||
parser.set_defaults(
|
||||
model_path=MODEL,
|
||||
output="outputs/fasth3_8step",
|
||||
steps=GRID_POINTS,
|
||||
vsa_sparsity=VSA_SPARSITY,
|
||||
vsa_tile_size=64,
|
||||
)
|
||||
args = basic_fasth3.validate_args(parser, parser.parse_args(argv))
|
||||
if args.steps != GRID_POINTS:
|
||||
parser.error(f"--steps must be {GRID_POINTS} for {MODEL}: the checkpoint's trained ladder has "
|
||||
f"{GRID_POINTS - 1} rungs and the pipeline rejects any other grid")
|
||||
return args
|
||||
|
||||
|
||||
def main() -> None:
|
||||
basic_fasth3.run(parse_args())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -28,7 +28,8 @@ detail budget and reads softer than a native-resolution render, so it stays
|
||||
off by default.
|
||||
|
||||
This entrypoint currently supports text-to-video-with-audio only. It does not
|
||||
yet wire FL2VA, Ref2VA, or two-pass refinement.
|
||||
yet wire FL2VA, Ref2VA, or two-pass refinement. The four-step preview stays
|
||||
the default. FastH3 8-Step V2 uses ``mlx_fasth3_8step.py``.
|
||||
|
||||
VSA is off by default; existing dense MLX checkpoints remain supported.
|
||||
H3 uses fused MLX RMSNorm, which can change BF16 rounding compared with the
|
||||
@@ -51,7 +52,7 @@ def _dense_layers(value: str) -> tuple[int, ...]:
|
||||
return layers
|
||||
|
||||
|
||||
def parse_args() -> argparse.Namespace:
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description=__doc__,
|
||||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument("--model-root", type=Path, default=Path.home() / "models/FastH3-Preview-v0.2",
|
||||
@@ -164,11 +165,20 @@ def parse_args() -> argparse.Namespace:
|
||||
"(simd is opt-in)"
|
||||
),
|
||||
)
|
||||
return parser.parse_args()
|
||||
parser.add_argument("--vae-tile-height", type=int, default=256,
|
||||
help="overlapping video-VAE tile height in pixels")
|
||||
parser.add_argument("--vae-tile-width", type=int, default=256,
|
||||
help="overlapping video-VAE tile width in pixels")
|
||||
parser.add_argument("--inter-step-cooldown-s", type=float, default=0.0,
|
||||
help="optional pause between denoise steps")
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = parse_args()
|
||||
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
|
||||
return build_parser().parse_args(argv)
|
||||
|
||||
|
||||
def run(args: argparse.Namespace) -> None:
|
||||
from fastvideo.mlx_runtime.minimax_h3_pipeline import MiniMaxH3MLXPipeline
|
||||
|
||||
pipeline = MiniMaxH3MLXPipeline(
|
||||
@@ -189,6 +199,9 @@ def main() -> None:
|
||||
seed=args.seed,
|
||||
num_steps=args.steps,
|
||||
tiled_video_decode=args.tiled_video_decode,
|
||||
vae_tile_height=args.vae_tile_height,
|
||||
vae_tile_width=args.vae_tile_width,
|
||||
inter_step_cooldown_s=args.inter_step_cooldown_s,
|
||||
fast=args.fast,
|
||||
fast_factor=args.fast_factor,
|
||||
fast_sharpen=args.fast_sharpen,
|
||||
@@ -215,5 +228,9 @@ def main() -> None:
|
||||
}, indent=2))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
run(parse_args())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Eight-forward FastH3 8-Step V2 generation on Apple Silicon.
|
||||
|
||||
``FastVideo/FastVideo-FastH3-8-Step-V2`` trains eight DMD forwards at video
|
||||
shift 10 and audio shift 3, VSA sparsity 0.8, and 64-token tiles. Point
|
||||
``--model-root`` at a snapshot that contains that checkpoint's
|
||||
``fastvideo_inference.json`` plus the shared VAE, audio VAE, text encoder, and
|
||||
tokenizer. Point ``--mlx-checkpoint`` at an MLX DiT converted from this
|
||||
checkpoint (the AdaLN cache must be the contract ladder, not the four-step
|
||||
uniform grid).
|
||||
|
||||
``--steps`` accepts 8 transformer forwards or 9 sigma-grid points. Either
|
||||
value runs the trained rungs. The four-step preview entrypoint is unchanged.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Sequence
|
||||
|
||||
try:
|
||||
from . import mlx_fasth3
|
||||
except ImportError:
|
||||
import mlx_fasth3 # type: ignore[no-redef]
|
||||
|
||||
FORWARDS = 8
|
||||
GRID_POINTS = FORWARDS + 1
|
||||
|
||||
|
||||
def parse_args(argv: Sequence[str] | None = None):
|
||||
parser = mlx_fasth3.build_parser()
|
||||
parser.set_defaults(steps=FORWARDS, vsa=True, vsa_sparsity=0.8, vsa_tile_size=64)
|
||||
args = parser.parse_args(argv)
|
||||
if args.steps not in (FORWARDS, GRID_POINTS):
|
||||
parser.error(f"--steps must be {FORWARDS} or {GRID_POINTS} for FastH3 8-Step V2")
|
||||
return args
|
||||
|
||||
|
||||
def main() -> None:
|
||||
mlx_fasth3.run(parse_args())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -37,7 +37,8 @@ from collections.abc import Sequence
|
||||
from pathlib import Path
|
||||
|
||||
# MiniMax-H3 generates 5-15 s at 24 fps, on a latent grid that only admits frame counts
|
||||
# of the form 17n + 5. 124 is the 5-second point the FastH3 profile is measured at.
|
||||
# of the form 17n + 5. 124 is the 5-second point the FastH3 profile is measured at;
|
||||
# the 15-second cap pads to 362 frames.
|
||||
FRAMES_PER_CHUNK = 17
|
||||
LATENTS_PER_CHUNK = 5
|
||||
FPS = 24
|
||||
@@ -52,6 +53,10 @@ def align_num_frames(num_frames: int) -> int:
|
||||
return LATENTS_PER_CHUNK + chunks * FRAMES_PER_CHUNK
|
||||
|
||||
|
||||
MIN_ALIGNED_FRAMES = align_num_frames(int(MIN_DURATION * FPS))
|
||||
MAX_ALIGNED_FRAMES = align_num_frames(int(MAX_DURATION * FPS))
|
||||
|
||||
|
||||
def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
||||
parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||
parser.add_argument("--model-path", default="MiniMaxAI/MiniMax-H3", help="the BASE checkpoint the adapter targets")
|
||||
@@ -86,9 +91,10 @@ def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
||||
# community adapters built against the ComfyUI layout carry no gate. Checked in
|
||||
# main(), once the path has been resolved.
|
||||
aligned = align_num_frames(args.num_frames)
|
||||
if not MIN_DURATION <= aligned / FPS <= MAX_DURATION:
|
||||
parser.error(f"MiniMax-H3 generates {MIN_DURATION}-{MAX_DURATION}s at {FPS} fps; "
|
||||
f"aligned num_frames={aligned} is {aligned / FPS:.1f}s")
|
||||
if not MIN_ALIGNED_FRAMES <= aligned <= MAX_ALIGNED_FRAMES:
|
||||
parser.error(f"MiniMax-H3 generates {MIN_DURATION:g}-{MAX_DURATION:g}s at {FPS} fps; "
|
||||
f"aligned num_frames={aligned} is {aligned / FPS:.1f}s, outside the accepted "
|
||||
f"{MIN_ALIGNED_FRAMES}-{MAX_ALIGNED_FRAMES} frame range")
|
||||
args.num_frames = aligned
|
||||
if not math.isfinite(args.lora_strength):
|
||||
parser.error("--lora-strength must be finite")
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
The REST serving engine is model-agnostic. Any model supported by
|
||||
`VideoGenerator` can use the same `/v1/models`, `/v1/videos`, and `/v1/images`
|
||||
surface. The two FastH3 configs here are source-backed examples, not recorded
|
||||
serving benchmarks. Both configure four CUDA GPUs without a GPU model or VRAM claim.
|
||||
surface. The two CUDA FastH3 configs here are source-backed examples, not
|
||||
recorded serving benchmarks. Both configure four CUDA GPUs without a GPU model
|
||||
or VRAM claim. The Wan configs are copied from the working generate config or
|
||||
example for each checkpoint.
|
||||
|
||||
Use the [H3 server cookbook](https://haoailab.com/FastVideo/cookbook/openai-api/)
|
||||
for the guided install, server, and client workflow.
|
||||
@@ -39,10 +41,39 @@ and API clients share the same server; both can run locally.
|
||||
|
||||
For native Apple Silicon MLX, use
|
||||
`python -m fastvideo.entrypoints.openai.mlx_server --config examples/serving/mlx_fasth3.yaml`
|
||||
after preparing the weights and editing their paths in that configuration.
|
||||
The same playground and clients work with MLX. Its pipeline still loads and
|
||||
releases components between phases to limit unified-memory use. See the
|
||||
cookbook for setup and the supported text-to-video/audio request fields.
|
||||
(Preview) or `examples/serving/mlx_fasth3_8step.yaml` (8-Step V2) after preparing
|
||||
the weights and editing their paths in that configuration. The same playground
|
||||
and OpenAI Python client work with MLX. Its pipeline still loads and releases
|
||||
components between phases to limit unified-memory use. See the cookbook for
|
||||
setup and the supported text-to-video/audio request fields.
|
||||
|
||||
CUDA FastH3 8-Step V2 serving uses `examples/serving/openai_fasth3_8step.yaml`.
|
||||
|
||||
## Wan configs
|
||||
|
||||
Four Wan CUDA configs mirror the working generate config or example for each
|
||||
checkpoint:
|
||||
|
||||
| Config | Model | GPUs | Served alias |
|
||||
| --- | --- | --- | --- |
|
||||
| `openai_fastwan21_1_3b.yaml` | FastWan2.1 T2V 1.3B (DMD, VSA) | 1 | `fastwan21-1.3b` |
|
||||
| `openai_wan21_i2v_14b.yaml` | Wan2.1 I2V 14B 480P | 2 | `wan21-i2v-14b` |
|
||||
| `openai_wan22_t2v_a14b.yaml` | Wan2.2 T2V A14B | 2 | `wan22-t2v-a14b` |
|
||||
| `openai_wan22_ti2v_5b.yaml` | Wan2.2 TI2V 5B | 1 | `wan22-ti2v-5b` |
|
||||
|
||||
The 1.3B config needs the VSA attention backend:
|
||||
|
||||
```bash
|
||||
FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN \
|
||||
fastvideo serve --config examples/serving/openai_fastwan21_1_3b.yaml
|
||||
```
|
||||
|
||||
The I2V 14B and TI2V 5B servers accept image-conditioned requests. Supply the
|
||||
source image through the OpenAI-compatible reference fields, for example
|
||||
`{"input_reference": "/path/to/first-frame.png"}` or
|
||||
`{"image_reference": [{"image_url": "https://example.com/first-frame.png"}]}`.
|
||||
The I2V 14B server requires one on every request; the TI2V 5B server treats
|
||||
its absence as text-to-video.
|
||||
|
||||
Or submit, poll, and download with the OpenAI Python client:
|
||||
|
||||
@@ -53,7 +84,7 @@ python examples/serving/clients/video.py
|
||||
|
||||
The `clients/` directory also contains cURL and OpenAI JavaScript examples.
|
||||
They default to model `fasth3`; set `FASTVIDEO_MODEL` to the advertised alias
|
||||
when using the LoRA config. Set `FASTVIDEO_BASE_URL` for another endpoint.
|
||||
when using the LoRA or Wan configs. Set `FASTVIDEO_BASE_URL` for another endpoint.
|
||||
These clients call your FastVideo server, not OpenAI's cloud. The SDK key
|
||||
`local` is a placeholder, not authentication. Keep the server on loopback or
|
||||
use an authenticated proxy for remote access.
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# Native MLX FastH3 8-Step V2 server. Convert the DiT with --include-vsa first.
|
||||
runtime: mlx
|
||||
generator:
|
||||
model_path: FastVideo/FastVideo-FastH3-8-Step-V2
|
||||
model_root: ./FastH3-8-Step-V2
|
||||
mlx_checkpoint: ./FastH3-8-Step-V2-MLX/int8
|
||||
prompt_cache_dir: ./outputs/h3_prompt_cache
|
||||
vae_dtype: fp32
|
||||
vsa: true
|
||||
vsa_sparsity: 0.8
|
||||
vsa_tile_size: 64
|
||||
server:
|
||||
host: 127.0.0.1
|
||||
port: 8000
|
||||
served_model_name: fasth3
|
||||
output_dir: outputs/mlx_fasth3_8step
|
||||
default_request:
|
||||
sampling:
|
||||
height: 480
|
||||
width: 832
|
||||
num_frames: 124
|
||||
fps: 24
|
||||
# HTTP convention: nine sigma points, eight DiT forwards. Native MLX uses num_steps=8.
|
||||
num_inference_steps: 9
|
||||
guidance_scale: 1.0
|
||||
seed: 2026
|
||||
@@ -0,0 +1,48 @@
|
||||
# OpenAI-compatible FastH3 8-Step V2 server.
|
||||
generator:
|
||||
model_path: FastVideo/FastVideo-FastH3-8-Step-V2
|
||||
engine:
|
||||
num_gpus: 4
|
||||
use_fsdp_inference: false
|
||||
parallelism:
|
||||
tp_size: 1
|
||||
sp_size: 4
|
||||
offload:
|
||||
dit: false
|
||||
dit_layerwise: false
|
||||
text_encoder: true
|
||||
image_encoder: true
|
||||
vae: true
|
||||
pin_cpu_memory: true
|
||||
compile:
|
||||
enabled: false
|
||||
vae_enabled: false
|
||||
pipeline:
|
||||
workload_type: t2v
|
||||
experimental:
|
||||
attention_backend: VIDEO_SPARSE_ATTN_H3
|
||||
VSA_sparsity: 0.8
|
||||
VSA_tile_size: 64
|
||||
inference_torch_compile: false
|
||||
vae_parallel_decode: true
|
||||
vae_parallel_decode_strategy: gather
|
||||
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
port: 8000
|
||||
output_dir: outputs/openai_fasth3_8step
|
||||
served_model_name: fasth3
|
||||
|
||||
default_request:
|
||||
negative_prompt: ""
|
||||
sampling:
|
||||
height: 768
|
||||
width: 1344
|
||||
num_frames: 124
|
||||
fps: 24
|
||||
num_inference_steps: 9
|
||||
guidance_scale: 1.0
|
||||
batch_cfg: false
|
||||
seed: 1000
|
||||
output:
|
||||
return_frames: false
|
||||
@@ -0,0 +1,44 @@
|
||||
# OpenAI-compatible server for FastWan2.1 T2V 1.3B (DMD-distilled, VSA sparse attention).
|
||||
# Requires: FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN
|
||||
# FASTVIDEO_ATTENTION_BACKEND=VIDEO_SPARSE_ATTN fastvideo serve --config examples/serving/openai_fastwan21_1_3b.yaml
|
||||
generator:
|
||||
model_path: FastVideo/FastWan2.1-T2V-1.3B-Diffusers
|
||||
engine:
|
||||
num_gpus: 1
|
||||
parallelism:
|
||||
tp_size: 1
|
||||
sp_size: 1
|
||||
offload:
|
||||
dit: false
|
||||
vae: false
|
||||
pin_cpu_memory: false
|
||||
compile:
|
||||
enabled: true
|
||||
pipeline:
|
||||
experimental:
|
||||
VSA_sparsity: 0.8
|
||||
dmd_denoising_steps: [1000, 757, 522]
|
||||
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
port: 8000
|
||||
output_dir: outputs/openai_fastwan21_1_3b
|
||||
served_model_name: fastwan21-1.3b
|
||||
|
||||
default_request:
|
||||
negative_prompt: >-
|
||||
Bright tones, overexposed, static, blurred details, subtitles, style,
|
||||
works, paintings, images, static, overall gray, worst quality, low
|
||||
quality, JPEG compression residue, ugly, incomplete, extra fingers,
|
||||
poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen
|
||||
limbs, fused fingers, still picture, messy background, three legs, many
|
||||
people in the background, walking backwards
|
||||
sampling:
|
||||
seed: 1024
|
||||
num_frames: 81
|
||||
height: 480
|
||||
width: 832
|
||||
fps: 16
|
||||
num_inference_steps: 3
|
||||
output:
|
||||
return_frames: false
|
||||
@@ -0,0 +1,48 @@
|
||||
# OpenAI-compatible server for Wan2.1 I2V 14B 480P. Each request must supply
|
||||
# its own source image through the OpenAI-compatible reference fields, e.g.
|
||||
# {"input_reference": "/path/to/first-frame.png"} or
|
||||
# {"image_reference": [{"image_url": "https://example.com/first-frame.png"}]};
|
||||
# there is no default image.
|
||||
# fastvideo serve --config examples/serving/openai_wan21_i2v_14b.yaml
|
||||
generator:
|
||||
model_path: Wan-AI/Wan2.1-I2V-14B-480P-Diffusers
|
||||
engine:
|
||||
num_gpus: 2
|
||||
parallelism:
|
||||
tp_size: 2
|
||||
sp_size: 2
|
||||
offload:
|
||||
dit: true
|
||||
vae: true
|
||||
text_encoder: true
|
||||
image_encoder: true
|
||||
pin_cpu_memory: true
|
||||
pipeline:
|
||||
workload_type: i2v
|
||||
experimental:
|
||||
flow_shift: 3.0
|
||||
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
port: 8000
|
||||
output_dir: outputs/openai_wan21_i2v_14b
|
||||
served_model_name: wan21-i2v-14b
|
||||
|
||||
default_request:
|
||||
negative_prompt: >-
|
||||
Bright tones, overexposed, static, blurred details, subtitles, style,
|
||||
works, paintings, images, static, overall gray, worst quality, low
|
||||
quality, JPEG compression residue, ugly, incomplete, extra fingers,
|
||||
poorly drawn hands, poorly drawn faces, deformed, disfigured, misshapen
|
||||
limbs, fused fingers, still picture, messy background, three legs, many
|
||||
people in the background, walking backwards
|
||||
sampling:
|
||||
seed: 1024
|
||||
num_frames: 77
|
||||
height: 480
|
||||
width: 832
|
||||
fps: 16
|
||||
num_inference_steps: 40
|
||||
guidance_scale: 5.0
|
||||
output:
|
||||
return_frames: false
|
||||
@@ -0,0 +1,29 @@
|
||||
# OpenAI-compatible server for Wan2.2 T2V A14B (mixture-of-experts DiT).
|
||||
# fastvideo serve --config examples/serving/openai_wan22_t2v_a14b.yaml
|
||||
generator:
|
||||
model_path: Wan-AI/Wan2.2-T2V-A14B-Diffusers
|
||||
engine:
|
||||
num_gpus: 2
|
||||
use_fsdp_inference: false
|
||||
parallelism:
|
||||
tp_size: 1
|
||||
sp_size: 2
|
||||
offload:
|
||||
dit: true
|
||||
vae: false
|
||||
text_encoder: true
|
||||
pin_cpu_memory: true
|
||||
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
port: 8000
|
||||
output_dir: outputs/openai_wan22_t2v_a14b
|
||||
served_model_name: wan22-t2v-a14b
|
||||
|
||||
default_request:
|
||||
sampling:
|
||||
num_frames: 81
|
||||
height: 720
|
||||
width: 1280
|
||||
output:
|
||||
return_frames: false
|
||||
@@ -0,0 +1,27 @@
|
||||
# OpenAI-compatible server for Wan2.2 TI2V 5B. Supports both text-to-video
|
||||
# (omit the image reference) and image-to-video (set input_reference or
|
||||
# image_reference) from the same model.
|
||||
# fastvideo serve --config examples/serving/openai_wan22_ti2v_5b.yaml
|
||||
generator:
|
||||
model_path: Wan-AI/Wan2.2-TI2V-5B-Diffusers
|
||||
engine:
|
||||
num_gpus: 1
|
||||
use_fsdp_inference: false
|
||||
parallelism:
|
||||
tp_size: 1
|
||||
sp_size: 1
|
||||
offload:
|
||||
dit: true
|
||||
vae: false
|
||||
text_encoder: true
|
||||
pin_cpu_memory: true
|
||||
|
||||
server:
|
||||
host: 0.0.0.0
|
||||
port: 8000
|
||||
output_dir: outputs/openai_wan22_ti2v_5b
|
||||
served_model_name: wan22-ti2v-5b
|
||||
|
||||
default_request:
|
||||
output:
|
||||
return_frames: false
|
||||
@@ -27,7 +27,7 @@ layout) with:
|
||||
```bash
|
||||
# from the repo root; reads data/kandinsky5_overfit and writes
|
||||
# data/kandinsky5_overfit_preprocessed by default -- override with
|
||||
# KANDINSKY5_OVERFIT_DATA_DIR / KANDINSKY5_OVERFIT_OUTPUT_DIR env vars
|
||||
# FASTVIDEO_TEST_KANDINSKY5_OVERFIT_DATA_DIR / FASTVIDEO_TEST_KANDINSKY5_OVERFIT_OUTPUT_DIR env vars
|
||||
python -m fastvideo.pipelines.preprocess.preprocess_kandinsky5_overfit
|
||||
```
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
# (see docs/training/data_preprocess.md;
|
||||
# fastvideo/pipelines/preprocess/preprocess_ltx2_overfit.py is a minimal
|
||||
# LTX-2 reference to adapt — set
|
||||
# LTX2_OVERFIT_MODEL=FastVideo/LTX-2.3-Distilled-Diffusers). Data paths,
|
||||
# FASTVIDEO_TEST_LTX2_OVERFIT_MODEL=FastVideo/LTX-2.3-Distilled-Diffusers). Data paths,
|
||||
# step counts, and learning rate below are placeholders you must adapt
|
||||
# to your workload.
|
||||
#
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
# (see docs/training/data_preprocess.md;
|
||||
# fastvideo/pipelines/preprocess/preprocess_ltx2_overfit.py is a minimal
|
||||
# LTX-2 reference to adapt — set
|
||||
# LTX2_OVERFIT_MODEL=FastVideo/LTX-2.3-Distilled-Diffusers).
|
||||
# FASTVIDEO_TEST_LTX2_OVERFIT_MODEL=FastVideo/LTX-2.3-Distilled-Diffusers).
|
||||
#
|
||||
# Run:
|
||||
# NUM_GPUS=4 bash examples/train/run.sh \
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
#
|
||||
# Preprocess data first (writes data/ltx2_3_overfit_preprocessed):
|
||||
# CUDA_VISIBLE_DEVICES=0 \
|
||||
# LTX2_OVERFIT_MODEL=FastVideo/LTX-2.3-Distilled-Diffusers \
|
||||
# LTX2_OVERFIT_OUTPUT_DIR=data/ltx2_3_overfit_preprocessed \
|
||||
# FASTVIDEO_TEST_LTX2_OVERFIT_MODEL=FastVideo/LTX-2.3-Distilled-Diffusers \
|
||||
# FASTVIDEO_TEST_LTX2_OVERFIT_OUTPUT_DIR=data/ltx2_3_overfit_preprocessed \
|
||||
# python fastvideo/pipelines/preprocess/preprocess_ltx2_overfit.py
|
||||
#
|
||||
# Run:
|
||||
|
||||
@@ -16,14 +16,55 @@ if(NOT GPU_BACKEND)
|
||||
set(GPU_BACKEND "CUDA")
|
||||
endif()
|
||||
|
||||
if(GPU_BACKEND STREQUAL "ROCM")
|
||||
enable_language(HIP)
|
||||
# Compiled extension: AUTO/ON/OFF. On ROCm, AUTO falls back to the Python + Triton
|
||||
# package when HIP cannot be configured (#1850: pip-SDK images without hip-lang).
|
||||
if(NOT DEFINED FASTVIDEO_KERNEL_BUILD_EXTENSION AND DEFINED ENV{FASTVIDEO_KERNEL_BUILD_EXTENSION})
|
||||
set(FASTVIDEO_KERNEL_BUILD_EXTENSION "$ENV{FASTVIDEO_KERNEL_BUILD_EXTENSION}")
|
||||
endif()
|
||||
set(FASTVIDEO_KERNEL_BUILD_EXTENSION "AUTO" CACHE STRING "Build the compiled extension: AUTO/ON/OFF")
|
||||
set_property(CACHE FASTVIDEO_KERNEL_BUILD_EXTENSION PROPERTY STRINGS AUTO ON OFF)
|
||||
string(TOUPPER "${FASTVIDEO_KERNEL_BUILD_EXTENSION}" FASTVIDEO_KERNEL_BUILD_EXTENSION)
|
||||
if(NOT FASTVIDEO_KERNEL_BUILD_EXTENSION MATCHES "^(AUTO|ON|OFF)$")
|
||||
message(FATAL_ERROR
|
||||
"fastvideo-kernel: FASTVIDEO_KERNEL_BUILD_EXTENSION must be AUTO, ON or OFF "
|
||||
"(got '${FASTVIDEO_KERNEL_BUILD_EXTENSION}').")
|
||||
endif()
|
||||
|
||||
set(BUILD_CXX_KERNELS ON)
|
||||
if(FASTVIDEO_KERNEL_BUILD_EXTENSION STREQUAL "OFF")
|
||||
set(BUILD_CXX_KERNELS OFF)
|
||||
elseif(GPU_BACKEND STREQUAL "ROCM")
|
||||
include(CheckLanguage)
|
||||
check_language(HIP)
|
||||
if(CMAKE_HIP_COMPILER)
|
||||
enable_language(HIP)
|
||||
elseif(FASTVIDEO_KERNEL_BUILD_EXTENSION STREQUAL "ON")
|
||||
message(FATAL_ERROR
|
||||
"fastvideo-kernel: FASTVIDEO_KERNEL_BUILD_EXTENSION=ON, but CMake found no usable HIP "
|
||||
"toolchain (it needs the hip-lang CMake package under ROCM_PATH or CMAKE_HIP_COMPILER). "
|
||||
"Install the ROCm CMake packages, or build the Python + Triton package with "
|
||||
"FASTVIDEO_KERNEL_BUILD_EXTENSION=OFF (build.sh --rocm --python-only).")
|
||||
else()
|
||||
message(STATUS
|
||||
"fastvideo-kernel: no usable HIP toolchain; building the Python + Triton package "
|
||||
"without the compiled extension (FASTVIDEO_KERNEL_BUILD_EXTENSION=AUTO)")
|
||||
set(BUILD_CXX_KERNELS OFF)
|
||||
endif()
|
||||
else()
|
||||
enable_language(CUDA)
|
||||
# Ensure CUDA toolkit targets (CUDA::cudart, CUDA::cuda_driver, etc.) are available.
|
||||
find_package(CUDAToolkit REQUIRED)
|
||||
endif()
|
||||
|
||||
if(NOT BUILD_CXX_KERNELS)
|
||||
message(STATUS "============== fastvideo-kernel build summary ==============")
|
||||
message(STATUS "host / backend: ${CMAKE_SYSTEM_PROCESSOR} / ${GPU_BACKEND}")
|
||||
message(STATUS "fastvideo_kernel_ops: OFF (Python + Triton package only; FASTVIDEO_KERNEL_BUILD_EXTENSION=${FASTVIDEO_KERNEL_BUILD_EXTENSION})")
|
||||
message(STATUS "Triton fallbacks ship in python/fastvideo_kernel/triton_kernels regardless.")
|
||||
message(STATUS "============================================================")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# Import common utils if needed, but we keep it simple for now
|
||||
|
||||
# Find Python and Torch
|
||||
@@ -307,9 +348,6 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Always try to build the extension if CUDA is available, but conditionally add sources/flags
|
||||
set(BUILD_CXX_KERNELS ON)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Per-arch split for the Blackwell FP4 (attn_qat_infer) build
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -396,27 +434,43 @@ if(BUILD_CXX_KERNELS)
|
||||
# exported (build.sh, and `pip install` with it set) the branch above only prints it --
|
||||
# the cmake variable stays empty, so testing that alone silently skips the kernel and
|
||||
# leaves a build that succeeds with the op missing.
|
||||
set(ENABLE_VSA_SM100_FAMILY OFF)
|
||||
set(ENABLE_VSA_SM100A OFF)
|
||||
set(ENABLE_VSA_SM103A OFF)
|
||||
set(_VSA_ARCH_LIST "${TORCH_CUDA_ARCH_LIST}")
|
||||
if(NOT _VSA_ARCH_LIST AND DEFINED ENV{TORCH_CUDA_ARCH_LIST})
|
||||
set(_VSA_ARCH_LIST "$ENV{TORCH_CUDA_ARCH_LIST}")
|
||||
endif()
|
||||
if(_VSA_ARCH_LIST MATCHES "(^|[; ,])(10\\.(0|3)a|10(0|3)a|sm_10(0|3)a)([; ,]|$)")
|
||||
set(ENABLE_VSA_SM100_FAMILY ON)
|
||||
if(_VSA_ARCH_LIST MATCHES "(^|[; ,])(10\\.0a|100a|sm_100a)([; ,]|$)")
|
||||
set(ENABLE_VSA_SM100A ON)
|
||||
endif()
|
||||
if(ENABLE_VSA_SM100_FAMILY)
|
||||
message(STATUS "fastvideo-kernel: building block_sparse_sm100a (sm_100a/sm_103a, 64- and 128-token blocks)")
|
||||
if(_VSA_ARCH_LIST MATCHES "(^|[; ,])(10\\.3a|103a|sm_103a)([; ,]|$)")
|
||||
set(ENABLE_VSA_SM103A ON)
|
||||
endif()
|
||||
set(_VSA_FWD_GENCODE "")
|
||||
set(_VSA_FWD_ARCHS "")
|
||||
if(ENABLE_VSA_SM100A)
|
||||
list(APPEND _VSA_FWD_GENCODE "-gencode;arch=compute_100a,code=sm_100a")
|
||||
list(APPEND _VSA_FWD_ARCHS "sm_100a")
|
||||
endif()
|
||||
if(ENABLE_VSA_SM103A)
|
||||
list(APPEND _VSA_FWD_GENCODE "-gencode;arch=compute_103a,code=sm_103a")
|
||||
list(APPEND _VSA_FWD_ARCHS "sm_103a")
|
||||
endif()
|
||||
if(_VSA_FWD_GENCODE)
|
||||
message(STATUS "fastvideo-kernel: building block_sparse_sm100a (${_VSA_FWD_ARCHS}; 64- and 128-token blocks)")
|
||||
list(APPEND EXTENSION_SOURCES csrc/attention/block_sparse_sm100a.cu
|
||||
csrc/attention/block_sparse_blk128_sm100a.cu)
|
||||
set_source_files_properties(csrc/attention/block_sparse_sm100a.cu
|
||||
csrc/attention/block_sparse_blk128_sm100a.cu PROPERTIES
|
||||
COMPILE_OPTIONS "-gencode;arch=compute_100a,code=sm_100a;-gencode;arch=compute_103a,code=sm_103a;-DVSA_BHSD=true")
|
||||
# VSA block-sparse attention BACKWARD, 64-token blocks. sm_100a only for now: validated
|
||||
# on GB200, not yet on B300/GB300, so no sm_103a image is built and the Python side keeps
|
||||
# the Triton backward for sm_103a devices.
|
||||
list(APPEND EXTENSION_SOURCES csrc/attention/block_sparse_bwd_sm100a.cu)
|
||||
set_source_files_properties(csrc/attention/block_sparse_bwd_sm100a.cu PROPERTIES
|
||||
COMPILE_OPTIONS "-gencode;arch=compute_100a,code=sm_100a;-DVSA_BHSD=true")
|
||||
COMPILE_OPTIONS "${_VSA_FWD_GENCODE};-DVSA_BHSD=true")
|
||||
endif()
|
||||
# VSA block-sparse attention BACKWARD, 64- and 128-token blocks: the same images as the forward.
|
||||
if(_VSA_FWD_GENCODE)
|
||||
list(APPEND EXTENSION_SOURCES csrc/attention/block_sparse_bwd_sm100a.cu
|
||||
csrc/attention/block_sparse_bwd_blk128_sm100a.cu)
|
||||
set_source_files_properties(csrc/attention/block_sparse_bwd_sm100a.cu
|
||||
csrc/attention/block_sparse_bwd_blk128_sm100a.cu PROPERTIES
|
||||
COMPILE_OPTIONS "${_VSA_FWD_GENCODE};-DVSA_BHSD=true")
|
||||
endif()
|
||||
|
||||
Python_add_library(fastvideo_kernel_ops MODULE USE_SABI ${SKBUILD_SABI_VERSION} WITH_SOABI
|
||||
@@ -434,7 +488,7 @@ if(BUILD_CXX_KERNELS)
|
||||
|
||||
# Build compile definitions list
|
||||
set(COMPILE_DEFS TORCH_EXTENSION_NAME=fastvideo_kernel_ops)
|
||||
if(ENABLE_VSA_SM100_FAMILY)
|
||||
if(ENABLE_VSA_SM100A OR ENABLE_VSA_SM103A)
|
||||
list(APPEND COMPILE_DEFS TK_COMPILE_BLOCK_SPARSE_VSA_SM100A)
|
||||
endif()
|
||||
if(ENABLE_TK_KERNELS)
|
||||
|
||||
@@ -69,6 +69,20 @@ cd fastvideo-kernel
|
||||
./build.sh --rocm
|
||||
```
|
||||
|
||||
The compiled extension needs the HIP CMake toolchain (`hip-lang`). Images that install
|
||||
ROCm as a pip SDK, such as the `rocm/pytorch` 7.14 images, ship `hipcc` but not that
|
||||
package, so `build.sh --rocm` falls back to the Python + Triton package there and says so
|
||||
in the configure summary. That package is all the video sparse attention path needs on
|
||||
ROCm. To ask for it explicitly, or to fail instead of falling back:
|
||||
|
||||
```bash
|
||||
./build.sh --rocm --python-only # Python + Triton only
|
||||
CMAKE_ARGS="-DFASTVIDEO_KERNEL_BUILD_EXTENSION=ON" ./build.sh --rocm # error if HIP cannot be configured
|
||||
```
|
||||
|
||||
`FASTVIDEO_KERNEL_BUILD_EXTENSION` (AUTO/ON/OFF) is also read from the environment by a
|
||||
plain `pip install --no-build-isolation .`.
|
||||
|
||||
### Optional: FA4 CuTe block-sparse backend (VSA-128/256 fastpath)
|
||||
|
||||
The VSA-128/256 fastpaths (tile volume 128 or 256, on NVIDIA Blackwell / sm_100) route to the
|
||||
@@ -145,8 +159,11 @@ python benchmarks/benchmark_attn_qat_train.py
|
||||
```
|
||||
|
||||
The benchmark reports both conventional attention FLOPs and the extra matrix
|
||||
multiplications executed by the QAT straight-through path. Override
|
||||
`--peak-tflops` when running on a GPU other than RTX 5090.
|
||||
multiplications executed by the QAT straight-through path. It resolves dense
|
||||
BF16 peak throughput for the full-GPU variants listed in
|
||||
`benchmarks/device_specs.py`. Unknown or partitioned devices still report
|
||||
timing and achieved TFLOPS, with MFU shown as `N/A`; pass a positive, finite
|
||||
`--peak-tflops` value to report MFU for those devices.
|
||||
|
||||
The QAT kernel is entirely Triton and routes by architecture at runtime. SM100
|
||||
uses a large-tile forward and split 64x64 backward for the production
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
import torch
|
||||
import fp4quant
|
||||
import fp4quant_cuda as fp4quant
|
||||
from triton.tools.mxfp import MXFP4Tensor
|
||||
|
||||
from bench_utils import bench_kineto
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
import torch
|
||||
import fp4quant
|
||||
import fp4quant_cuda as fp4quant
|
||||
from triton.tools.mxfp import MXFP4Tensor
|
||||
|
||||
from bench_utils import bench_kineto
|
||||
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
import torch
|
||||
import fp4quant
|
||||
import fp4quant_cuda as fp4quant
|
||||
from triton.tools.mxfp import MXFP4Tensor
|
||||
|
||||
from bench_utils import bench_kineto
|
||||
|
||||
@@ -14,9 +14,9 @@ from collections.abc import Callable
|
||||
|
||||
import torch
|
||||
|
||||
from fastvideo_kernel.triton_kernels.attn_qat_train import attention
|
||||
from device_specs import format_tflops_with_mfu, resolve_peak_tflops
|
||||
|
||||
RTX_5090_DENSE_BF16_TFLOPS = 209.5
|
||||
from fastvideo_kernel.triton_kernels.attn_qat_train import attention
|
||||
|
||||
|
||||
def _qat_attention(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> torch.Tensor:
|
||||
@@ -62,14 +62,15 @@ def _format_result(
|
||||
timing_ms: tuple[float, float, float],
|
||||
algorithmic_flops: int,
|
||||
executed_matmul_flops: int,
|
||||
peak_tflops: float,
|
||||
peak_tflops: float | None,
|
||||
) -> str:
|
||||
median_ms, min_ms, max_ms = timing_ms
|
||||
algorithmic_tflops = algorithmic_flops / (median_ms * 1e9)
|
||||
executed_tflops = executed_matmul_flops / (median_ms * 1e9)
|
||||
algorithmic_result = format_tflops_with_mfu(algorithmic_tflops, peak_tflops)
|
||||
executed_result = format_tflops_with_mfu(executed_tflops, peak_tflops)
|
||||
return (f"{label}: {median_ms:.3f} ms (min={min_ms:.3f}, max={max_ms:.3f}), "
|
||||
f"algorithmic={algorithmic_tflops:.2f} TFLOPS/{100 * algorithmic_tflops / peak_tflops:.2f}% MFU, "
|
||||
f"executed_matmul={executed_tflops:.2f} TFLOPS/{100 * executed_tflops / peak_tflops:.2f}% MFU")
|
||||
f"algorithmic={algorithmic_result}, executed_matmul={executed_result}")
|
||||
|
||||
|
||||
def main() -> None:
|
||||
@@ -84,12 +85,16 @@ def main() -> None:
|
||||
parser.add_argument(
|
||||
"--peak-tflops",
|
||||
type=float,
|
||||
default=RTX_5090_DENSE_BF16_TFLOPS,
|
||||
help="Dense BF16 Tensor TFLOPS with FP32 accumulation; default is RTX 5090 boost-clock peak.",
|
||||
default=None,
|
||||
help="Dense BF16 Tensor TFLOPS with FP32 accumulation. Defaults to the "
|
||||
"device table in device_specs.py. Unknown devices report MFU as N/A.",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
kv_length = args.kv_length or args.query_length
|
||||
|
||||
device_name = torch.cuda.get_device_name()
|
||||
peak_tflops, peak_source = resolve_peak_tflops(device_name, args.peak_tflops)
|
||||
|
||||
torch.manual_seed(0)
|
||||
q_shape = (args.batch_size, args.heads, args.query_length, args.head_dim)
|
||||
kv_shape = (args.batch_size, args.heads, kv_length, args.head_dim)
|
||||
@@ -114,10 +119,14 @@ def main() -> None:
|
||||
# Conventional attention FLOPs are 4*base forward and 10*base backward.
|
||||
# QAT additionally computes the STE high-precision P@V path in forward and
|
||||
# the quantized-P dV path in backward, for 6*base and 14*base matmul FLOPs.
|
||||
print(f"device: {torch.cuda.get_device_name()}")
|
||||
print(f"device: {device_name}")
|
||||
if peak_tflops is None:
|
||||
print(f"peak: unavailable ({peak_source})")
|
||||
else:
|
||||
print(f"peak: {peak_tflops:.2f} dense BF16 TFLOPS ({peak_source})")
|
||||
print(f"q: {q_shape}; k/v: {kv_shape}; compile+first-forward: {compile_seconds:.3f} s")
|
||||
print(_format_result("forward", forward_ms, 4 * base_flops, 6 * base_flops, args.peak_tflops))
|
||||
print(_format_result("backward", backward_ms, 10 * base_flops, 14 * base_flops, args.peak_tflops))
|
||||
print(_format_result("forward", forward_ms, 4 * base_flops, 6 * base_flops, peak_tflops))
|
||||
print(_format_result("backward", backward_ms, 10 * base_flops, 14 * base_flops, peak_tflops))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""Per-device peak throughput used to turn benchmark timings into %MFU.
|
||||
|
||||
Values are dense (non-sparse) BF16 Tensor Core TFLOPS with FP32 accumulation.
|
||||
Only unambiguous full-GPU names with published peaks are listed. Timings and
|
||||
achieved TFLOPS remain useful on other devices, so unknown devices report MFU
|
||||
as unavailable unless the user supplies an explicit peak.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
|
||||
# Exact torch.cuda.get_device_name() values for supported full GPUs. H100 form
|
||||
# factors have different published peaks, and exact matching also prevents MIG,
|
||||
# vGPU, or future variants from silently inheriting a full-GPU denominator.
|
||||
DENSE_BF16_TFLOPS: dict[str, float] = {
|
||||
"NVIDIA H100 80GB HBM3": 989.0,
|
||||
"NVIDIA H100 NVL": 835.5,
|
||||
"NVIDIA H100 PCIe": 756.5,
|
||||
"NVIDIA GeForce RTX 5090": 209.5, # Matches the value this benchmark historically used.
|
||||
"NVIDIA A100-SXM4-80GB": 312.0,
|
||||
"NVIDIA A100-SXM4-40GB": 312.0,
|
||||
"NVIDIA A100-PCIE-80GB": 312.0,
|
||||
"NVIDIA A100-PCIE-40GB": 312.0,
|
||||
"NVIDIA L40S": 362.05,
|
||||
}
|
||||
|
||||
|
||||
def resolve_peak_tflops(device_name: str, override: float | None = None) -> tuple[float | None, str]:
|
||||
"""Return (peak_tflops, source_description) for *device_name*.
|
||||
|
||||
An explicit positive, finite *override* always wins. Unknown and partitioned
|
||||
devices return ``None`` so the benchmark can still report timing and
|
||||
achieved TFLOPS without presenting a misleading MFU value.
|
||||
"""
|
||||
if override is not None:
|
||||
if not math.isfinite(override) or override <= 0:
|
||||
raise ValueError("--peak-tflops must be finite and greater than zero")
|
||||
return float(override), "explicit --peak-tflops override"
|
||||
|
||||
normalized_name = device_name.strip()
|
||||
if "MIG" in normalized_name.upper():
|
||||
return None, "MIG/partitioned device; pass --peak-tflops for this partition to report MFU"
|
||||
|
||||
if normalized_name in DENSE_BF16_TFLOPS:
|
||||
return DENSE_BF16_TFLOPS[normalized_name], f"exact device table entry {normalized_name!r}"
|
||||
|
||||
return None, f"no dense BF16 peak for {device_name!r}; pass --peak-tflops to report MFU"
|
||||
|
||||
|
||||
def format_tflops_with_mfu(achieved_tflops: float, peak_tflops: float | None) -> str:
|
||||
"""Format achieved throughput, including MFU only when its denominator is known."""
|
||||
if peak_tflops is None:
|
||||
return f"{achieved_tflops:.2f} TFLOPS/MFU N/A"
|
||||
return f"{achieved_tflops:.2f} TFLOPS/{100 * achieved_tflops / peak_tflops:.2f}% MFU"
|
||||
+58
-30
@@ -5,9 +5,11 @@ set -ex
|
||||
# Usage:
|
||||
# ./build.sh # local build (torch-based arch detection, TK only on SM90)
|
||||
# ./build.sh --wheel-dir /tmp/wheels # build + install a reusable wheel
|
||||
# ./build.sh --rocm --python-only # Python + Triton package, no compiled extension
|
||||
# Environment overrides (if set, they win over auto-detection):
|
||||
# TORCH_CUDA_ARCH_LIST
|
||||
# CMAKE_ARGS (for FASTVIDEO_KERNEL_BUILD_TK / CMAKE_CUDA_ARCHITECTURES / GPU_BACKEND)
|
||||
# CMAKE_ARGS (for FASTVIDEO_KERNEL_BUILD_TK / CMAKE_CUDA_ARCHITECTURES / GPU_BACKEND /
|
||||
# FASTVIDEO_KERNEL_BUILD_EXTENSION)
|
||||
|
||||
echo "Building fastvideo-kernel..."
|
||||
|
||||
@@ -40,42 +42,19 @@ if [[ -n "${CONDA_PREFIX:-}" ]]; then
|
||||
unset _need_clean _host_arch
|
||||
fi
|
||||
|
||||
# Ensure only the kernel's required headers are initialized. A repository-wide
|
||||
# update also clones the unrelated VBench evaluation submodule. Skip outside a
|
||||
# git checkout (e.g. Docker contexts that exclude .git), where the submodule
|
||||
# contents must already be present.
|
||||
if git rev-parse --git-dir >/dev/null 2>&1; then
|
||||
git submodule update --init --recursive include/cutlass include/tk
|
||||
fi
|
||||
# Fail fast with a clear message if the headers are still missing (e.g. a
|
||||
# Docker context that excluded .git AND the submodule contents) instead of
|
||||
# dying later in a wall of nvcc include errors. CUTLASS is consumed by the
|
||||
# always-built turbodiffusion sources, so it is a hard error; ThunderKittens
|
||||
# only feeds the TK-gated Hopper kernels, so a missing tree just warns (the
|
||||
# TK gate resolves later, and non-SM90/ROCm builds never touch it).
|
||||
if [ ! -d include/cutlass/include ]; then
|
||||
echo "ERROR: include/cutlass/include is missing. Outside a git checkout the" >&2
|
||||
echo " CUTLASS sources must already be present (run" >&2
|
||||
echo " 'git submodule update --init --recursive include/cutlass include/tk'" >&2
|
||||
echo " in the source checkout, or include them in the build context)." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -d include/tk/include ]; then
|
||||
echo "WARNING: include/tk/include is missing; ThunderKittens (Hopper sm_90a)" >&2
|
||||
echo " kernels cannot be built. Fine for non-SM90/ROCm targets." >&2
|
||||
fi
|
||||
|
||||
# Install build dependencies
|
||||
uv pip install scikit-build-core cmake ninja
|
||||
|
||||
GPU_BACKEND=CUDA
|
||||
WHEEL_DIR=""
|
||||
PYTHON_ONLY=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--rocm)
|
||||
GPU_BACKEND=ROCM
|
||||
shift
|
||||
;;
|
||||
--python-only)
|
||||
PYTHON_ONLY=1
|
||||
shift
|
||||
;;
|
||||
--wheel-dir)
|
||||
if [[ $# -lt 2 ]]; then
|
||||
echo "ERROR: --wheel-dir requires a directory path" >&2
|
||||
@@ -96,6 +75,55 @@ has_cmake_arg() {
|
||||
[[ "${CMAKE_ARGS:-}" =~ (^|[[:space:]])-D${key}(=|$) ]]
|
||||
}
|
||||
|
||||
extension_mode_from_cmake_args() {
|
||||
[[ "${CMAKE_ARGS:-}" =~ (^|[[:space:]])-DFASTVIDEO_KERNEL_BUILD_EXTENSION=([^[:space:]]*) ]] \
|
||||
&& printf '%s' "${BASH_REMATCH[2]^^}"
|
||||
}
|
||||
if (( PYTHON_ONLY )); then
|
||||
_explicit_mode="$(extension_mode_from_cmake_args)"
|
||||
if [[ -n "${_explicit_mode}" && "${_explicit_mode}" != "OFF" ]]; then
|
||||
echo "ERROR: --python-only conflicts with CMAKE_ARGS -DFASTVIDEO_KERNEL_BUILD_EXTENSION=${_explicit_mode}" >&2
|
||||
exit 2
|
||||
fi
|
||||
if ! has_cmake_arg "FASTVIDEO_KERNEL_BUILD_EXTENSION"; then
|
||||
CMAKE_ARGS="${CMAKE_ARGS:-} -DFASTVIDEO_KERNEL_BUILD_EXTENSION=OFF"
|
||||
fi
|
||||
elif [[ "$(extension_mode_from_cmake_args)" == "OFF" ]]; then
|
||||
PYTHON_ONLY=1
|
||||
fi
|
||||
|
||||
# The vendored headers only feed the compiled extension, so a Python-only build
|
||||
# neither initializes nor checks them.
|
||||
if ! (( PYTHON_ONLY )); then
|
||||
# Ensure only the kernel's required headers are initialized. A repository-wide
|
||||
# update also clones the unrelated VBench evaluation submodule. Skip outside a
|
||||
# git checkout (e.g. Docker contexts that exclude .git), where the submodule
|
||||
# contents must already be present.
|
||||
if git rev-parse --git-dir >/dev/null 2>&1; then
|
||||
git submodule update --init --recursive include/cutlass include/tk
|
||||
fi
|
||||
# Fail fast with a clear message if the headers are still missing (e.g. a
|
||||
# Docker context that excluded .git AND the submodule contents) instead of
|
||||
# dying later in a wall of nvcc include errors. CUTLASS is consumed by the
|
||||
# always-built turbodiffusion sources, so it is a hard error; ThunderKittens
|
||||
# only feeds the TK-gated Hopper kernels, so a missing tree just warns (the
|
||||
# TK gate resolves later, and non-SM90/ROCm builds never touch it).
|
||||
if [ ! -d include/cutlass/include ]; then
|
||||
echo "ERROR: include/cutlass/include is missing. Outside a git checkout the" >&2
|
||||
echo " CUTLASS sources must already be present (run" >&2
|
||||
echo " 'git submodule update --init --recursive include/cutlass include/tk'" >&2
|
||||
echo " in the source checkout, or include them in the build context)." >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -d include/tk/include ]; then
|
||||
echo "WARNING: include/tk/include is missing; ThunderKittens (Hopper sm_90a)" >&2
|
||||
echo " kernels cannot be built. Fine for non-SM90/ROCm targets." >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install build dependencies
|
||||
uv pip install scikit-build-core cmake ninja
|
||||
|
||||
detect_with_torch() {
|
||||
# Prefer the active venv's python directly over `uv run --active --no-project`,
|
||||
# which on some uv versions provisions its own interpreter and misses packages
|
||||
@@ -113,7 +141,7 @@ mj, mn = torch.cuda.get_device_capability(0)
|
||||
print(f'{mj}.{mn}')"
|
||||
}
|
||||
|
||||
if [ "${GPU_BACKEND}" = "CUDA" ]; then
|
||||
if [ "${GPU_BACKEND}" = "CUDA" ] && ! (( PYTHON_ONLY )); then
|
||||
# Compute capability drives the arch/TK defaults below. Prefer an explicit
|
||||
# TORCH_CUDA_ARCH_LIST (works on GPU-less build machines such as CI/Docker);
|
||||
# only probe a live GPU via torch when no arch was provided.
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// block_sparse_bwd_blk128_sm100a.cu -- the 128-token-block instantiation of the torch binding.
|
||||
//
|
||||
// Same source as block_sparse_bwd_sm100a.cu with VSA_BLK128 set: the kernel and launch land in
|
||||
// namespace vsa_bwd_blk128 (distinct symbols, no ODR clash with the blk64 objects) and the
|
||||
// exported entry point becomes block_sparse_sm100a_blk128_bwd.
|
||||
#define VSA_BLK128 true
|
||||
#include "block_sparse_bwd_sm100a.cu"
|
||||
@@ -123,7 +123,9 @@ __global__ void __cluster_dims__(1, 1, 1) __launch_bounds__(N_WARPS * 32, 1) vsa
|
||||
const int* __restrict__ k2q_num, const int* __restrict__ workitem_remap,
|
||||
const int* __restrict__ variable_block_sizes, int max_q_blocks, int num_samples, int num_heads,
|
||||
int seqlen, float scale_log2, float sm_scale) {
|
||||
#if !defined(__CUDA_ARCH__) || (__CUDA_ARCH__ == 1000 && defined(__CUDA_ARCH_FEAT_SM100_ALL))
|
||||
#if !defined(__CUDA_ARCH__) || \
|
||||
((__CUDA_ARCH__ == 1000 && defined(__CUDA_ARCH_FEAT_SM100_ALL)) || \
|
||||
(__CUDA_ARCH__ == 1030 && defined(__CUDA_ARCH_FEAT_SM103_ALL)))
|
||||
using DQ = DQConfig<DQ_DTYPE>;
|
||||
const int num_kv_blocks_per_seq = seqlen / BLOCK;
|
||||
|
||||
@@ -863,7 +865,9 @@ __global__ void __launch_bounds__(ORDER_THREADS, 1)
|
||||
vsa_bwd_order_kernel(const int* __restrict__ k2q_idx, const int* __restrict__ k2q_num,
|
||||
int max_q_blocks, int num_kv_blocks_per_seq, int order_bin, bool snake,
|
||||
int* __restrict__ order_out) {
|
||||
#if !defined(__CUDA_ARCH__) || (__CUDA_ARCH__ == 1000 && defined(__CUDA_ARCH_FEAT_SM100_ALL))
|
||||
#if !defined(__CUDA_ARCH__) || \
|
||||
((__CUDA_ARCH__ == 1000 && defined(__CUDA_ARCH_FEAT_SM100_ALL)) || \
|
||||
(__CUDA_ARCH__ == 1030 && defined(__CUDA_ARCH_FEAT_SM103_ALL)))
|
||||
extern __shared__ int order_smem[];
|
||||
int* sbin = order_smem;
|
||||
int* smid = order_smem + num_kv_blocks_per_seq;
|
||||
@@ -908,7 +912,9 @@ __global__ void __launch_bounds__(256, 1)
|
||||
__nv_bfloat16* __restrict__ dk, __nv_bfloat16* __restrict__ dv,
|
||||
const int* __restrict__ k2q_num, int num_samples, int num_heads,
|
||||
int seqlen) {
|
||||
#if !defined(__CUDA_ARCH__) || (__CUDA_ARCH__ == 1000 && defined(__CUDA_ARCH_FEAT_SM100_ALL))
|
||||
#if !defined(__CUDA_ARCH__) || \
|
||||
((__CUDA_ARCH__ == 1000 && defined(__CUDA_ARCH_FEAT_SM100_ALL)) || \
|
||||
(__CUDA_ARCH__ == 1030 && defined(__CUDA_ARCH_FEAT_SM103_ALL)))
|
||||
__shared__ __align__(128) __nv_bfloat16 tile[PRE_TOKENS][SUB_COLS_BF16 + 4];
|
||||
const int num_kv_blocks_per_seq = seqlen / BLOCK;
|
||||
const int token_block_id = (int)blockIdx.x;
|
||||
@@ -1038,7 +1044,9 @@ template <bool BHSD = false, typename DQ_DTYPE = float>
|
||||
__global__ void __launch_bounds__(128, 1)
|
||||
vsa_bwd_postprocess_kernel(const DQ_DTYPE* __restrict__ dqaccum, __nv_bfloat16* __restrict__ dq,
|
||||
int num_heads, int seqlen, float sm_scale) {
|
||||
#if !defined(__CUDA_ARCH__) || (__CUDA_ARCH__ == 1000 && defined(__CUDA_ARCH_FEAT_SM100_ALL))
|
||||
#if !defined(__CUDA_ARCH__) || \
|
||||
((__CUDA_ARCH__ == 1000 && defined(__CUDA_ARCH_FEAT_SM100_ALL)) || \
|
||||
(__CUDA_ARCH__ == 1030 && defined(__CUDA_ARCH_FEAT_SM103_ALL)))
|
||||
const int q_block_id = (int)blockIdx.x;
|
||||
const int batch_head = (int)blockIdx.y;
|
||||
const int batch = batch_head / num_heads, head = batch_head % num_heads;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,231 @@
|
||||
// block_sparse_bwd_launch_sm100a_blk128.cuh -- host surface of the 128-token VSA block-sparse
|
||||
// backward drop: argument struct, workspace sizes, the support predicate and the stream-chained
|
||||
// launch (preprocess -> main -> postprocess, each a programmatic dependent launch of the one
|
||||
// before). Tensor maps are encoded per call (a torch caller hands us fresh pointers every time).
|
||||
#ifndef BLOCK_SPARSE_VSA_BWD_LAUNCH_SM100A_BLK128_CUH
|
||||
#define BLOCK_SPARSE_VSA_BWD_LAUNCH_SM100A_BLK128_CUH
|
||||
|
||||
#include <cmath>
|
||||
#include "block_sparse_bwd_kernel_sm100a_blk128.cuh"
|
||||
|
||||
#ifndef VSA_BHSD
|
||||
#define VSA_BHSD false
|
||||
#endif
|
||||
|
||||
namespace vsa_bwd_blk128 {
|
||||
|
||||
using dq_accum_t = float;
|
||||
|
||||
struct BlockSparseVsaBwdArgs {
|
||||
// Activations are bf16, contiguous, [B, H, S, 128] under VSA_BHSD, else [B*S, H, 128].
|
||||
// nb below = num_kv_blocks_per_seq = S / 128.
|
||||
|
||||
// Forward operands and results.
|
||||
const __nv_bfloat16* q;
|
||||
const __nv_bfloat16* k;
|
||||
const __nv_bfloat16* v;
|
||||
const __nv_bfloat16* o;
|
||||
// Gradient of the forward output.
|
||||
const __nv_bfloat16* dout;
|
||||
// [B, H, S] fp32 log-sum-exp in Triton's M form: max(qk * sm_scale * log2e) + log2(l).
|
||||
const float* lse;
|
||||
|
||||
// Sparsity metadata, FastVideo's invert_indices layout, in 128-token blocks.
|
||||
// [B*H*nb, max_q_blocks] int32: q blocks selecting each kv block; entries past the count unread.
|
||||
const int* k2q_idx;
|
||||
// [B*H*nb] int32: valid entries per k2q_idx row (0 allowed: the kernel writes zero dk/dv rows).
|
||||
const int* k2q_num;
|
||||
// [nb] int32: valid kv tokens per block (<= 128); kv rows at or past the count are masked.
|
||||
const int* variable_block_sizes;
|
||||
|
||||
// Outputs, inputs' layout; every row is written, including the rows of unselected kv blocks.
|
||||
__nv_bfloat16* dq;
|
||||
__nv_bfloat16* dk;
|
||||
__nv_bfloat16* dv;
|
||||
|
||||
// Scratch, caller-allocated; byte sizes from the block_sparse_bwd_*_bytes helpers below.
|
||||
// [B*H*S*128] fp32, drain-native; preprocess zeroes, main reduce-adds, postprocess reads.
|
||||
dq_accum_t* dqaccum;
|
||||
// [B*H*S] fp32 rowsum(bf16(o) * dout), written by the preprocess.
|
||||
float* delta;
|
||||
|
||||
int batch;
|
||||
int num_heads;
|
||||
// S = nb * 128.
|
||||
int seqlen;
|
||||
// Must be 128.
|
||||
int head_dim;
|
||||
// nb = seqlen / 128.
|
||||
int num_kv_blocks_per_seq;
|
||||
// k2q_idx row stride (FastVideo passes nb).
|
||||
int max_q_blocks;
|
||||
// Softmax scale; dq and dk carry it, dv does not.
|
||||
float sm_scale;
|
||||
};
|
||||
|
||||
__host__ inline size_t block_sparse_bwd_dqaccum_bytes(int batch, int num_heads, int seqlen) {
|
||||
return (size_t)batch * num_heads * seqlen * HEAD_DIM * sizeof(dq_accum_t);
|
||||
}
|
||||
__host__ inline size_t block_sparse_bwd_delta_bytes(int batch, int num_heads, int seqlen) {
|
||||
return (size_t)batch * num_heads * seqlen * sizeof(float);
|
||||
}
|
||||
|
||||
__host__ inline cudaError_t block_sparse_bwd_supported(const BlockSparseVsaBwdArgs& args) {
|
||||
if (args.head_dim != HEAD_DIM) {
|
||||
return cudaErrorInvalidValue;
|
||||
}
|
||||
if (args.num_kv_blocks_per_seq < 1) {
|
||||
return cudaErrorInvalidValue;
|
||||
}
|
||||
if (args.seqlen != args.num_kv_blocks_per_seq * BLOCK) {
|
||||
return cudaErrorInvalidValue;
|
||||
}
|
||||
if (args.batch < 1 || args.num_heads < 1 || args.max_q_blocks < 1) {
|
||||
return cudaErrorInvalidValue;
|
||||
}
|
||||
// The main grid is (kv block, batch * head): gridDim.y is limited to 65535.
|
||||
if ((long)args.batch * args.num_heads > 65535) {
|
||||
return cudaErrorInvalidValue;
|
||||
}
|
||||
if (!std::isfinite(args.sm_scale)) {
|
||||
return cudaErrorInvalidValue;
|
||||
}
|
||||
if (!args.q || !args.k || !args.v || !args.o || !args.dout || !args.lse) {
|
||||
return cudaErrorInvalidValue;
|
||||
}
|
||||
if (!args.dq || !args.dk || !args.dv) {
|
||||
return cudaErrorInvalidValue;
|
||||
}
|
||||
if (!args.k2q_idx || !args.k2q_num || !args.variable_block_sizes) {
|
||||
return cudaErrorInvalidValue;
|
||||
}
|
||||
if (!args.dqaccum || !args.delta) {
|
||||
return cudaErrorInvalidValue;
|
||||
}
|
||||
return cudaSuccess;
|
||||
}
|
||||
|
||||
// Q, K, V, dO, dK, dV tensor maps, one box_tokens x 64-hd box per TMA (two per tile):
|
||||
// BSHD: 3D [64 hd, B*S tokens, H*2 hd units], strides {H*128*2, 128} bytes.
|
||||
// BHSD: 4D [64 hd, S tokens, 2 hd units, B*H], strides {128*2, 128, S*128*2} bytes.
|
||||
__host__ inline cudaError_t make_tma_tile_units(CUtensorMap* map, const __nv_bfloat16* ptr, int B,
|
||||
int H, int S, int box_tokens) {
|
||||
CUresult r;
|
||||
if (VSA_BHSD) {
|
||||
uint64_t gd[4] = {(uint64_t)SUB_COLS_BF16, (uint64_t)S, (uint64_t)HD_SUBTILES, (uint64_t)B * H};
|
||||
uint64_t gs[3] = {(uint64_t)HEAD_DIM * 2, (uint64_t)SUB_COLS_BYTES, (uint64_t)S * HEAD_DIM * 2};
|
||||
uint32_t bd[4] = {(uint32_t)SUB_COLS_BF16, (uint32_t)box_tokens, 1u, 1u};
|
||||
uint32_t es[4] = {1u, 1u, 1u, 1u};
|
||||
r = cuTensorMapEncodeTiled(
|
||||
map, CU_TENSOR_MAP_DATA_TYPE_BFLOAT16, 4, const_cast<__nv_bfloat16*>(ptr), gd, gs, bd, es,
|
||||
CU_TENSOR_MAP_INTERLEAVE_NONE, CU_TENSOR_MAP_SWIZZLE_128B,
|
||||
CU_TENSOR_MAP_L2_PROMOTION_L2_128B, CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE);
|
||||
} else {
|
||||
uint64_t gd[3] = {(uint64_t)SUB_COLS_BF16, (uint64_t)B * S, (uint64_t)H * HD_SUBTILES};
|
||||
uint64_t gs[2] = {(uint64_t)H * HEAD_DIM * 2, (uint64_t)SUB_COLS_BYTES};
|
||||
uint32_t bd[3] = {(uint32_t)SUB_COLS_BF16, (uint32_t)box_tokens, 1u};
|
||||
uint32_t es[3] = {1u, 1u, 1u};
|
||||
r = cuTensorMapEncodeTiled(
|
||||
map, CU_TENSOR_MAP_DATA_TYPE_BFLOAT16, 3, const_cast<__nv_bfloat16*>(ptr), gd, gs, bd, es,
|
||||
CU_TENSOR_MAP_INTERLEAVE_NONE, CU_TENSOR_MAP_SWIZZLE_128B,
|
||||
CU_TENSOR_MAP_L2_PROMOTION_L2_128B, CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE);
|
||||
}
|
||||
return (r == CUDA_SUCCESS) ? cudaSuccess : cudaErrorInvalidValue;
|
||||
}
|
||||
|
||||
// Every launch carries the programmatic-stream-serialization attribute when the kernels are
|
||||
// built with KERNEL_PDL (their griddepcontrol.wait guards the predecessor's data).
|
||||
__host__ inline cudaLaunchConfig_t pdl_launch_config(dim3 grid, dim3 block, size_t smem,
|
||||
cudaStream_t stream, cudaLaunchAttribute* at) {
|
||||
cudaLaunchConfig_t cfg = {};
|
||||
cfg.gridDim = grid;
|
||||
cfg.blockDim = block;
|
||||
cfg.dynamicSmemBytes = smem;
|
||||
cfg.stream = stream;
|
||||
at->id = cudaLaunchAttributeProgrammaticStreamSerialization;
|
||||
at->val.programmaticStreamSerializationAllowed = 1;
|
||||
cfg.attrs = at;
|
||||
cfg.numAttrs = KERNEL_PDL ? 1 : 0;
|
||||
return cfg;
|
||||
}
|
||||
|
||||
__host__ inline cudaError_t launch_block_sparse_bwd_sm100a(const BlockSparseVsaBwdArgs& args,
|
||||
cudaStream_t stream) {
|
||||
const cudaError_t supported = block_sparse_bwd_supported(args);
|
||||
if (supported != cudaSuccess) {
|
||||
return supported;
|
||||
}
|
||||
const int B = args.batch, H = args.num_heads, S = args.seqlen;
|
||||
|
||||
CUtensorMap tq, tk, tv, tdo, tdk, tdv;
|
||||
if (make_tma_tile_units(&tq, args.q, B, H, S, Q_TILE) != cudaSuccess ||
|
||||
make_tma_tile_units(&tk, args.k, B, H, S, KV_TILE) != cudaSuccess ||
|
||||
make_tma_tile_units(&tv, args.v, B, H, S, KV_TILE) != cudaSuccess ||
|
||||
make_tma_tile_units(&tdo, args.dout, B, H, S, Q_TILE) != cudaSuccess ||
|
||||
make_tma_tile_units(&tdk, args.dk, B, H, S, KV_TILE) != cudaSuccess ||
|
||||
make_tma_tile_units(&tdv, args.dv, B, H, S, KV_TILE) != cudaSuccess) {
|
||||
return cudaErrorInvalidValue;
|
||||
}
|
||||
|
||||
auto main_kernel = vsa_bwd_main_kernel<VSA_BHSD, Sched::NON_PERSISTENT>;
|
||||
auto postprocess_kernel = vsa_bwd_postprocess_kernel<VSA_BHSD>;
|
||||
constexpr int POST_SMEM_BYTES = DQ::DQ_BLOCK_ELEMS * (int)sizeof(float);
|
||||
cudaError_t e =
|
||||
cudaFuncSetAttribute(main_kernel, cudaFuncAttributeMaxDynamicSharedMemorySize, SMEM_TOTAL);
|
||||
if (e != cudaSuccess) {
|
||||
return e;
|
||||
}
|
||||
e = cudaFuncSetAttribute(postprocess_kernel, cudaFuncAttributeMaxDynamicSharedMemorySize,
|
||||
POST_SMEM_BYTES);
|
||||
if (e != cudaSuccess) {
|
||||
return e;
|
||||
}
|
||||
|
||||
// Preprocess: dqaccum zero + Delta, one CTA per (q block, batch * head).
|
||||
cudaLaunchAttribute pre_at[1];
|
||||
cudaLaunchConfig_t pre_cfg = pdl_launch_config(
|
||||
dim3((unsigned)(S / Q_TILE), (unsigned)(B * H), 1), dim3(256, 1, 1), 0, stream, pre_at);
|
||||
e = cudaLaunchKernelEx(&pre_cfg, vsa_bwd_preprocess_kernel<VSA_BHSD>, args.o, args.dout,
|
||||
args.delta, args.dqaccum, H, S);
|
||||
if (e != cudaSuccess) {
|
||||
return e;
|
||||
}
|
||||
|
||||
// Main: one CTA per (kv block, batch * head).
|
||||
const float scale_log2 = args.sm_scale * 1.4426950408889634f;
|
||||
cudaLaunchAttribute main_at[2];
|
||||
cudaLaunchConfig_t main_cfg = {};
|
||||
main_cfg.gridDim = dim3((unsigned)args.num_kv_blocks_per_seq, (unsigned)(B * H), 1);
|
||||
main_cfg.blockDim = dim3(N_WARPS * 32, 1, 1);
|
||||
main_cfg.dynamicSmemBytes = SMEM_TOTAL;
|
||||
main_cfg.stream = stream;
|
||||
main_at[0].id = cudaLaunchAttributeClusterDimension;
|
||||
main_at[0].val.clusterDim.x = 1;
|
||||
main_at[0].val.clusterDim.y = 1;
|
||||
main_at[0].val.clusterDim.z = 1;
|
||||
main_at[1].id = cudaLaunchAttributeProgrammaticStreamSerialization;
|
||||
main_at[1].val.programmaticStreamSerializationAllowed = 1;
|
||||
main_cfg.attrs = main_at;
|
||||
main_cfg.numAttrs = KERNEL_PDL ? 2 : 1;
|
||||
const int* workitem_remap = nullptr;
|
||||
e = cudaLaunchKernelEx(&main_cfg, main_kernel, tq, tk, tv, tdo, tdk, tdv, args.dqaccum, args.lse,
|
||||
args.delta, args.k2q_idx, args.k2q_num, args.max_q_blocks, workitem_remap,
|
||||
args.variable_block_sizes, B, H, S, scale_log2, args.sm_scale);
|
||||
if (e != cudaSuccess) {
|
||||
return e;
|
||||
}
|
||||
|
||||
// Postprocess: drain-native dqaccum -> bf16 dq, one CTA per (q block, batch * head).
|
||||
cudaLaunchAttribute post_at[1];
|
||||
cudaLaunchConfig_t post_cfg =
|
||||
pdl_launch_config(dim3((unsigned)(S / Q_TILE), (unsigned)(B * H), 1), dim3(128, 1, 1),
|
||||
POST_SMEM_BYTES, stream, post_at);
|
||||
return cudaLaunchKernelEx(&post_cfg, postprocess_kernel, args.dqaccum, args.dq, H, S,
|
||||
args.sm_scale);
|
||||
}
|
||||
|
||||
} // namespace vsa_bwd_blk128
|
||||
|
||||
using namespace vsa_bwd_blk128;
|
||||
|
||||
#endif // BLOCK_SPARSE_VSA_BWD_LAUNCH_SM100A_BLK128_CUH
|
||||
@@ -10,6 +10,9 @@
|
||||
//
|
||||
// The layout is fixed at compile time: VSA_BHSD true -> [B, H, S, 128] (FastVideo's build),
|
||||
// false -> [B, S, H, 128] (repo native; the kernel addresses it as [B*S tokens, H, 128]).
|
||||
// VSA_BLK128 picks the kernel: false -> 64-token sparse blocks (block_sparse_sm100a_bwd), true ->
|
||||
// 128-token (block_sparse_sm100a_blk128_bwd; block_sparse_bwd_blk128_sm100a.cu re-includes this
|
||||
// file with it set). The python backend picks by the metadata's block size.
|
||||
#include <torch/extension.h>
|
||||
|
||||
#include <ATen/cuda/CUDAContext.h>
|
||||
@@ -17,7 +20,16 @@
|
||||
|
||||
#include <vector>
|
||||
|
||||
#ifndef VSA_BLK128
|
||||
#define VSA_BLK128 false
|
||||
#endif
|
||||
#if VSA_BLK128
|
||||
#include "block_sparse_bwd_launch_sm100a_blk128.cuh"
|
||||
#define BLOCK_SPARSE_SM100A_BWD block_sparse_sm100a_blk128_bwd
|
||||
#else
|
||||
#include "block_sparse_bwd_launch_sm100a.cuh"
|
||||
#define BLOCK_SPARSE_SM100A_BWD block_sparse_sm100a_bwd
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -49,7 +61,7 @@ __nv_bfloat16* bf16_ptr(const torch::Tensor& t) {
|
||||
} // namespace
|
||||
|
||||
// Returns {dq, dk, dv}: bf16, each with the shape and layout of q, k, v respectively.
|
||||
std::vector<torch::Tensor> block_sparse_sm100a_bwd(torch::Tensor grad_o, torch::Tensor q,
|
||||
std::vector<torch::Tensor> BLOCK_SPARSE_SM100A_BWD(torch::Tensor grad_o, torch::Tensor q,
|
||||
torch::Tensor k, torch::Tensor v,
|
||||
torch::Tensor o, torch::Tensor lse,
|
||||
torch::Tensor k2q_idx, torch::Tensor k2q_num,
|
||||
@@ -105,20 +117,23 @@ std::vector<torch::Tensor> block_sparse_sm100a_bwd(torch::Tensor grad_o, torch::
|
||||
auto dv = torch::empty_like(v);
|
||||
|
||||
// Workspace: torch::empty is enough. The preprocess kernel zeroes dqaccum and fully writes
|
||||
// qt, dot and delta before the main kernel reads them; it also zeroes the dk/dv rows of kv
|
||||
// blocks that no q block selects, so the empty_like outputs above come back fully defined.
|
||||
// delta (and, at 64-token blocks, qt and dot) before the main kernel reads them. The dk/dv rows
|
||||
// of kv blocks that no q block selects are zeroed by the preprocess (64) or by the main kernel
|
||||
// (128), so the empty_like outputs above come back fully defined.
|
||||
const auto bytes = q.options().dtype(at::kByte);
|
||||
const int b = (int)B, h = (int)H, s = (int)S;
|
||||
auto dqaccum = torch::empty({(int64_t)block_sparse_bwd_dqaccum_bytes(b, h, s)}, bytes);
|
||||
auto qt = torch::empty({(int64_t)block_sparse_bwd_transposed_bytes(b, h, s)}, bytes);
|
||||
auto dot = torch::empty({(int64_t)block_sparse_bwd_transposed_bytes(b, h, s)}, bytes);
|
||||
auto delta = torch::empty({(int64_t)block_sparse_bwd_delta_bytes(b, h, s)}, bytes);
|
||||
#if !VSA_BLK128
|
||||
auto qt = torch::empty({(int64_t)block_sparse_bwd_transposed_bytes(b, h, s)}, bytes);
|
||||
auto dot = torch::empty({(int64_t)block_sparse_bwd_transposed_bytes(b, h, s)}, bytes);
|
||||
torch::Tensor order;
|
||||
const bool device_order = num_kv_blocks_per_seq >= ORDER_MIN_KV_BLOCKS;
|
||||
if (device_order) {
|
||||
order = torch::empty({(int64_t)block_sparse_bwd_order_bytes(b, h, (int)num_kv_blocks_per_seq)},
|
||||
bytes);
|
||||
}
|
||||
#endif
|
||||
|
||||
BlockSparseVsaBwdArgs a{};
|
||||
a.q = bf16_ptr(q);
|
||||
@@ -133,12 +148,14 @@ std::vector<torch::Tensor> block_sparse_sm100a_bwd(torch::Tensor grad_o, torch::
|
||||
a.k2q_idx = k2q_idx.data_ptr<int>();
|
||||
a.k2q_num = k2q_num.data_ptr<int>();
|
||||
a.variable_block_sizes = variable_block_sizes.data_ptr<int>();
|
||||
a.workitem_remap = nullptr;
|
||||
a.order_workspace = device_order ? reinterpret_cast<int*>(order.data_ptr()) : nullptr;
|
||||
a.dqaccum = reinterpret_cast<dq_accum_t*>(dqaccum.data_ptr());
|
||||
a.qt = bf16_ptr(qt);
|
||||
a.dot = bf16_ptr(dot);
|
||||
a.delta = reinterpret_cast<float*>(delta.data_ptr());
|
||||
#if !VSA_BLK128
|
||||
a.workitem_remap = nullptr;
|
||||
a.order_workspace = device_order ? reinterpret_cast<int*>(order.data_ptr()) : nullptr;
|
||||
a.qt = bf16_ptr(qt);
|
||||
a.dot = bf16_ptr(dot);
|
||||
#endif
|
||||
a.batch = b;
|
||||
a.num_heads = h;
|
||||
a.seqlen = s;
|
||||
@@ -149,16 +166,17 @@ std::vector<torch::Tensor> block_sparse_sm100a_bwd(torch::Tensor grad_o, torch::
|
||||
|
||||
// Report an unsupported regime loudly rather than returning plausible-looking wrong values.
|
||||
TORCH_CHECK(block_sparse_bwd_supported(a) == cudaSuccess,
|
||||
"block_sparse_sm100a_bwd: unsupported configuration -- requires head_dim==", HEAD_DIM,
|
||||
"block_sparse_sm100a_bwd (", BLOCK,
|
||||
"-token blocks): unsupported configuration -- requires head_dim==", HEAD_DIM,
|
||||
", seqlen == num_kv_blocks_per_seq*", BLOCK,
|
||||
" with seqlen % 128 == 0, "
|
||||
" with seqlen % 128 == 0, batch*heads <= 65535, "
|
||||
"max_q_blocks >= 1 and a finite sm_scale. Got head_dim=",
|
||||
D, " num_kv_blocks_per_seq=", num_kv_blocks_per_seq, " seqlen=", S,
|
||||
" max_q_blocks=", max_q_blocks, " sm_scale=", sm_scale);
|
||||
|
||||
const cudaError_t err = launch_block_sparse_bwd_sm100a(a, at::cuda::getCurrentCUDAStream());
|
||||
TORCH_CHECK(err == cudaSuccess,
|
||||
"block_sparse_sm100a_bwd launch failed: ", cudaGetErrorString(err));
|
||||
TORCH_CHECK(err == cudaSuccess, "block_sparse_sm100a_bwd (", BLOCK,
|
||||
"-token blocks) launch failed: ", cudaGetErrorString(err));
|
||||
|
||||
return {dq, dk, dv};
|
||||
}
|
||||
|
||||
@@ -985,3 +985,30 @@ uint64_t make_l2cache_policy_fractional_evict_last_unchanged(float fraction_f32)
|
||||
: "f"(fraction_f32));
|
||||
return policy;
|
||||
}
|
||||
|
||||
__device__ __forceinline__
|
||||
void cp_async_cg_16(uint32_t smem_dst, const void* gmem_src) {
|
||||
asm volatile("cp.async.cg.shared.global [%0], [%1], 16;\n"
|
||||
:: "r"(smem_dst), "l"(gmem_src) : "memory");
|
||||
}
|
||||
|
||||
__device__ __forceinline__
|
||||
void cp_async_commit_group() {
|
||||
asm volatile("cp.async.commit_group;\n" ::: "memory");
|
||||
}
|
||||
|
||||
template <int N>
|
||||
__device__ __forceinline__
|
||||
void cp_async_wait_group() {
|
||||
asm volatile("cp.async.wait_group %0;\n" :: "n"(N) : "memory");
|
||||
}
|
||||
|
||||
__device__ __forceinline__
|
||||
void griddepcontrol_wait() {
|
||||
asm volatile("griddepcontrol.wait;\n" ::: "memory");
|
||||
}
|
||||
|
||||
__device__ __forceinline__
|
||||
void griddepcontrol_launch_dependents() {
|
||||
asm volatile("griddepcontrol.launch_dependents;\n" ::: "memory");
|
||||
}
|
||||
|
||||
@@ -46,6 +46,10 @@ extern std::vector<torch::Tensor> block_sparse_sm100a_bwd(
|
||||
torch::Tensor grad_o, torch::Tensor q, torch::Tensor k, torch::Tensor v, torch::Tensor o,
|
||||
torch::Tensor lse, torch::Tensor k2q_idx, torch::Tensor k2q_num,
|
||||
torch::Tensor variable_block_sizes, double sm_scale);
|
||||
extern std::vector<torch::Tensor> block_sparse_sm100a_blk128_bwd(
|
||||
torch::Tensor grad_o, torch::Tensor q, torch::Tensor k, torch::Tensor v, torch::Tensor o,
|
||||
torch::Tensor lse, torch::Tensor k2q_idx, torch::Tensor k2q_num,
|
||||
torch::Tensor variable_block_sizes, double sm_scale);
|
||||
#endif
|
||||
|
||||
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
|
||||
@@ -60,7 +64,10 @@ PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
|
||||
"VSA block-sparse attention forward, 128-token blocks (Blackwell sm100a/sm103a)");
|
||||
m.def("block_sparse_sm100a_bwd",
|
||||
torch::wrap_pybind_function(block_sparse_sm100a_bwd),
|
||||
"VSA block-sparse attention backward, 64-token blocks (Blackwell sm100a)");
|
||||
"VSA block-sparse attention backward, 64-token blocks (Blackwell sm100a/sm103a)");
|
||||
m.def("block_sparse_sm100a_blk128_bwd",
|
||||
torch::wrap_pybind_function(block_sparse_sm100a_blk128_bwd),
|
||||
"VSA block-sparse attention backward, 128-token blocks (Blackwell sm100a/sm103a)");
|
||||
#endif
|
||||
|
||||
#ifdef TK_COMPILE_ST_ATTN
|
||||
|
||||
@@ -31,6 +31,11 @@ auto quant(
|
||||
std::optional<torch::Tensor>& Output_S
|
||||
) {
|
||||
|
||||
TORCH_CHECK(
|
||||
Input.scalar_type() == torch::kHalf || Input.scalar_type() == torch::kBFloat16,
|
||||
"Unsupported input data type for quant_cuda (int8 quantization)."
|
||||
);
|
||||
|
||||
using ElementOut = int8_t;
|
||||
static constexpr int BlockSize = 128;
|
||||
static constexpr int NumThrPerCta = 256;
|
||||
@@ -62,8 +67,7 @@ auto quant(
|
||||
}
|
||||
|
||||
default: {
|
||||
std::cerr << "Observing: " << Input.scalar_type() << " for the input datatype which is invalid";
|
||||
throw std::runtime_error("Unsupported input data type for quantize_to_fp4.");
|
||||
TORCH_CHECK(false, "Unsupported input data type for quant_cuda (int8 quantization).");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,15 +52,31 @@ def _force_tk() -> bool:
|
||||
def _force_sm100a() -> bool:
|
||||
"""True iff the data-center Blackwell forward is explicitly opted into.
|
||||
|
||||
Opt-in only (same legacy-named env the H3 backend honors): the extension is
|
||||
forward-only, so this routing pairs it with the Triton backward -- its lse
|
||||
is already in Triton's M format. Honored only when
|
||||
``block_sparse_attn_sm100a.is_supported`` passes. Unsupported 64-token
|
||||
metadata falls through to the default selection; unsupported 128-token
|
||||
metadata raises because Triton has no compatible fallback.
|
||||
``FASTVIDEO_VSA_TRITON`` still wins.
|
||||
Opt-in only (same legacy-named env the H3 backend honors). Honored only when
|
||||
``block_sparse_attn_sm100a.is_supported`` passes; the backward then runs the
|
||||
sm_100a/sm_103a CUDA backward when ``block_sparse_attn_bwd_sm100a.is_supported``
|
||||
passes and Triton otherwise (the forward's lse is already in Triton's M
|
||||
format). Unsupported 64-token metadata falls through to the default
|
||||
selection; unsupported 128-token metadata raises because Triton has no
|
||||
compatible fallback. ``FASTVIDEO_VSA_TRITON`` still wins.
|
||||
"""
|
||||
return os.environ.get("FASTVIDEO_VSA_SM100A", "0") == "1"
|
||||
return _env_bool("FASTVIDEO_VSA_SM100A")
|
||||
|
||||
|
||||
def _env_bool(name: str) -> bool:
|
||||
"""Parse a boolean environment variable with the rule of ``fastvideo.envs.EnvBool``.
|
||||
|
||||
fastvideo-kernel cannot import fastvideo, so the rule is repeated here to keep
|
||||
the kernel and the package in agreement: 1, true, yes, on are true; 0, false,
|
||||
no, off, and the empty string are false; case-insensitive; anything else raises.
|
||||
"""
|
||||
value = os.environ.get(name, "0").strip().lower()
|
||||
if value in ("1", "true", "yes", "on"):
|
||||
return True
|
||||
if value in ("0", "false", "no", "off", ""):
|
||||
return False
|
||||
raise ValueError(f"Invalid value {value!r} for {name}: expected 1, true, yes, on, 0, false, no, off, "
|
||||
"or an empty string")
|
||||
|
||||
|
||||
def _sm100a_is_supported(q: torch.Tensor, variable_block_sizes: torch.Tensor) -> bool:
|
||||
@@ -373,11 +389,12 @@ block_sparse_attn_sm90.register_autograd(_backward_sm90, setup_context=_setup_co
|
||||
# Data-center Blackwell backend custom op (index-native; legacy sm100a API name)
|
||||
#
|
||||
# Forward runs the sm_100a/sm_103a CUDA extension. Backward runs the sm_100a CUDA
|
||||
# backward when block_sparse_attn_bwd_sm100a.is_supported passes (64-token blocks,
|
||||
# sm_100a device, extension built with the op) and the Triton kernels otherwise.
|
||||
# The native forward emits lse in exactly Triton's M format (max*log2e +
|
||||
# log2(l)), so either pairing needs no conversion. Both backwards are
|
||||
# hardcoded to 64-token blocks, hence the block-size assert below.
|
||||
# backward when block_sparse_attn_bwd_sm100a.is_supported passes (64- or 128-token
|
||||
# blocks, data-center Blackwell device, extension built with that block's op) and
|
||||
# the Triton kernels otherwise. The native forward emits lse in exactly Triton's M
|
||||
# format (max*log2e + log2(l)), so either pairing needs no conversion. The Triton
|
||||
# backward is hardcoded to 64-token blocks, so unsupported 128-token metadata
|
||||
# raises instead of falling back.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@@ -477,16 +494,17 @@ def _setup_context_sm100a(ctx, inputs, output):
|
||||
|
||||
def _backward_sm100a(ctx, grad_o, grad_M):
|
||||
q, k, v, o, M, q2k_idx, q2k_num, variable_block_sizes = ctx.saved_tensors
|
||||
block = q.shape[2] // variable_block_sizes.numel()
|
||||
if block != 64:
|
||||
raise RuntimeError(
|
||||
"block_sparse_attn_sm100a backward pairs the sm_100a/sm_103a forward with a "
|
||||
f"backward that is hardcoded to 64-token blocks; got {block}. "
|
||||
"Run 128-token-block metadata without grad, or use the Triton forward.")
|
||||
if _sm100a_backward_is_supported(q, variable_block_sizes):
|
||||
dq, dk, dv = block_sparse_attn_backward_sm100a(grad_o, q, k, v, o, M, q2k_idx,
|
||||
q2k_num, variable_block_sizes)
|
||||
else:
|
||||
block = q.shape[2] // variable_block_sizes.numel()
|
||||
if block != 64:
|
||||
raise RuntimeError(
|
||||
"block_sparse_attn_sm100a backward: no sm_100a/sm_103a backward for "
|
||||
f"{block}-token blocks on this build/device, and the Triton backward is "
|
||||
"hardcoded to 64-token blocks. Run this metadata without grad, or build "
|
||||
"the extension with block_sparse_sm100a_blk128_bwd.")
|
||||
dq, dk, dv = block_sparse_attn_backward_triton(grad_o, q, k, v, o, M, q2k_idx,
|
||||
q2k_num, variable_block_sizes)
|
||||
return dq, dk, dv, None, None, None
|
||||
@@ -520,8 +538,9 @@ def block_sparse_attn_from_indices(
|
||||
|
||||
# Backend resolution:
|
||||
# - FASTVIDEO_VSA_TRITON forces Triton everywhere.
|
||||
# - FASTVIDEO_VSA_SM100A opts into the data-center Blackwell forward
|
||||
# (Triton backward). The environment name is retained for compatibility.
|
||||
# - FASTVIDEO_VSA_SM100A opts into the data-center Blackwell forward (CUDA
|
||||
# backward when supported, else Triton). The environment name is retained
|
||||
# for compatibility.
|
||||
# Unsupported 64-token metadata falls through; unsupported 128-token
|
||||
# metadata raises because Triton cannot consume it.
|
||||
# - FASTVIDEO_VSA_TK requests sm_90 TK; honored only when it's actually
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
"""sm_100a (Blackwell) CUDA block-sparse VSA backward.
|
||||
"""sm_100a/sm_103a (data-center Blackwell) CUDA block-sparse VSA backward.
|
||||
|
||||
Companion of ``block_sparse_attn_sm100a`` (the forward): consumes the forward's ``lse`` in the
|
||||
Triton "M format" (``max(qk * sm_scale * log2e) + log2(l)``, ``[B, H, S]`` fp32) unchanged and
|
||||
FastVideo's k2q index metadata, returns ``(dq, dk, dv)`` in bf16 with the inputs' layout and the
|
||||
Triton backward's scaling (dq and dk carry sm_scale, dv does not). 64-token blocks only; any
|
||||
other configuration falls back to Triton via ``is_supported``.
|
||||
Triton backward's scaling (dq and dk carry sm_scale, dv does not). Two kernels, picked by the
|
||||
metadata's block size: 64-token blocks (``block_sparse_sm100a_bwd``) and 128-token blocks
|
||||
(``block_sparse_sm100a_blk128_bwd``); any other configuration falls back to Triton via
|
||||
``is_supported``.
|
||||
"""
|
||||
|
||||
from typing import Tuple
|
||||
@@ -16,62 +18,83 @@ try:
|
||||
# The pybind symbols live on fastvideo_kernel_ops, NOT on the _C package that contains it
|
||||
# (its __init__ is empty, so hasattr on the package fails with the kernel built and present).
|
||||
from fastvideo_kernel._C import fastvideo_kernel_ops as _C
|
||||
_BWD = getattr(_C, "block_sparse_sm100a_bwd", None)
|
||||
_HAS_VSA_BWD_SM100A = _BWD is not None
|
||||
_BWD_BY_BLOCK = {
|
||||
64: getattr(_C, "block_sparse_sm100a_bwd", None),
|
||||
128: getattr(_C, "block_sparse_sm100a_blk128_bwd", None),
|
||||
}
|
||||
_HAS_VSA_BWD_SM100A = any(_BWD_BY_BLOCK.values())
|
||||
except ImportError: # pragma: no cover - extension not built
|
||||
_C = None
|
||||
_BWD = None
|
||||
_BWD_BY_BLOCK = {}
|
||||
_HAS_VSA_BWD_SM100A = False
|
||||
|
||||
_SM100 = (10, 0)
|
||||
_SUPPORTED_COMPUTE_CAPABILITIES = {(10, 0), (10, 3)}
|
||||
HEAD_DIM = 128
|
||||
BLOCK = 64
|
||||
BLOCKS = (64, 128)
|
||||
# Must match the -DVSA_BHSD the extension was compiled with (FastVideo builds with true).
|
||||
BHSD = True
|
||||
|
||||
|
||||
def set_extension(module) -> None:
|
||||
"""Use an already-loaded extension module exposing ``block_sparse_sm100a_bwd``.
|
||||
"""Use an already-loaded extension module exposing ``block_sparse_sm100a_bwd`` and / or
|
||||
``block_sparse_sm100a_blk128_bwd``.
|
||||
|
||||
A standalone build of ``block_sparse_bwd_sm100a.cu`` (for example through
|
||||
A standalone build of the binding .cu files (for example through
|
||||
``torch.utils.cpp_extension.load`` with a ten-line pybind wrapper) can be injected here, so
|
||||
the backend can be exercised without rebuilding the fastvideo_kernel wheel.
|
||||
"""
|
||||
global _C, _BWD, _HAS_VSA_BWD_SM100A
|
||||
global _C, _BWD_BY_BLOCK, _HAS_VSA_BWD_SM100A
|
||||
_C = module
|
||||
_BWD = getattr(module, "block_sparse_sm100a_bwd", None)
|
||||
_HAS_VSA_BWD_SM100A = _BWD is not None
|
||||
_BWD_BY_BLOCK = {
|
||||
64: getattr(module, "block_sparse_sm100a_bwd", None),
|
||||
128: getattr(module, "block_sparse_sm100a_blk128_bwd", None),
|
||||
}
|
||||
_HAS_VSA_BWD_SM100A = any(_BWD_BY_BLOCK.values())
|
||||
|
||||
|
||||
def _seqlen(q: torch.Tensor) -> int:
|
||||
return q.shape[2] if BHSD else q.shape[1]
|
||||
|
||||
|
||||
def _block_size(q: torch.Tensor, variable_block_sizes: torch.Tensor) -> int:
|
||||
"""The block size the metadata implies (seqlen / num_blocks), 0 when it is not integral.
|
||||
|
||||
A plain int: under dynamic-shape torch.compile the shapes are SymInts, which cannot key
|
||||
``_BWD_BY_BLOCK``; ``int()`` specializes the value with a shape guard.
|
||||
"""
|
||||
num_blocks = variable_block_sizes.numel()
|
||||
seqlen = _seqlen(q)
|
||||
return 0 if num_blocks == 0 or seqlen % num_blocks else int(seqlen // num_blocks)
|
||||
|
||||
|
||||
def is_supported(q: torch.Tensor, variable_block_sizes: torch.Tensor) -> bool:
|
||||
"""True iff this build can run these tensors; otherwise the caller uses Triton.
|
||||
|
||||
Static facts only (shapes, dtypes, arch, layout), never tensor contents, so it is cheap
|
||||
enough for a per-layer dispatch path. The kernel is fixed at 64-token blocks with
|
||||
head_dim 128 and needs seqlen == 64 * num_blocks with an even num_blocks (its preprocess
|
||||
works in 128-token blocks). Per-row k2q counts may be anything in [0, num_q_blocks],
|
||||
including 0: unselected kv blocks get exactly-zero dk/dv rows.
|
||||
enough for a per-layer dispatch path. Both kernels take head_dim 128 and seqlen == block *
|
||||
num_blocks; the 64-token kernel also needs an even num_blocks (its preprocess works in
|
||||
128-token blocks); batch * heads is the launch grid's y extent, so it must be <= 65535.
|
||||
Per-row k2q counts may be anything in [0, num_q_blocks], including 0: unselected kv blocks
|
||||
get exactly-zero dk/dv rows.
|
||||
"""
|
||||
if not _HAS_VSA_BWD_SM100A or not q.is_cuda:
|
||||
return False
|
||||
if torch.cuda.get_device_capability(q.device) != _SM100:
|
||||
if torch.cuda.get_device_capability(q.device) not in _SUPPORTED_COMPUTE_CAPABILITIES:
|
||||
return False
|
||||
if q.dtype != torch.bfloat16 or q.dim() != 4 or q.shape[-1] != HEAD_DIM:
|
||||
return False
|
||||
if not q.is_contiguous():
|
||||
return False
|
||||
if q.shape[0] * (q.shape[1] if BHSD else q.shape[2]) > 65535:
|
||||
return False
|
||||
# Metadata must be integer-typed so the wrapper's int32 conversion is value-preserving.
|
||||
if not variable_block_sizes.is_cuda or variable_block_sizes.dtype not in (torch.int32,
|
||||
torch.int64):
|
||||
return False
|
||||
num_blocks = variable_block_sizes.numel()
|
||||
if num_blocks == 0 or num_blocks % 2 != 0:
|
||||
block = _block_size(q, variable_block_sizes)
|
||||
if _BWD_BY_BLOCK.get(block) is None:
|
||||
return False
|
||||
if _seqlen(q) != BLOCK * num_blocks:
|
||||
if block == 64 and variable_block_sizes.numel() % 2 != 0:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -89,15 +112,21 @@ def block_sparse_attn_backward_sm100a_from_k2q(
|
||||
) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
"""Backward from k2q metadata already in hand (``invert_indices`` layout).
|
||||
|
||||
``k2q_idx`` is ``[B, H, num_kv_blocks, max_q_blocks]`` (or the flat 2-D view) of LOCAL q64
|
||||
block ids, ``k2q_num`` ``[B, H, num_kv_blocks]``; entries past a row's count are never read.
|
||||
``k2q_idx`` is ``[B, H, num_kv_blocks, max_q_blocks]`` (or the flat 2-D view) of LOCAL q
|
||||
block ids (64- or 128-token blocks, as the metadata's block size), ``k2q_num``
|
||||
``[B, H, num_kv_blocks]``; entries past a row's count are never read.
|
||||
"""
|
||||
block = _block_size(q, variable_block_sizes)
|
||||
backward = _BWD_BY_BLOCK.get(block)
|
||||
if backward is None:
|
||||
raise RuntimeError(f"block_sparse_attn_backward_sm100a: no kernel for {block}-token "
|
||||
f"blocks (built: {sorted(b for b, f in _BWD_BY_BLOCK.items() if f)})")
|
||||
sm_scale = 1.0 / (q.shape[-1]**0.5)
|
||||
idx = k2q_idx.to(torch.int32).contiguous()
|
||||
num = k2q_num.to(torch.int32).contiguous()
|
||||
vbs = variable_block_sizes.to(torch.int32).contiguous()
|
||||
res = _BWD(grad_o.contiguous(), q.contiguous(), k.contiguous(), v.contiguous(),
|
||||
o.contiguous(), lse.contiguous(), idx, num, vbs, sm_scale)
|
||||
res = backward(grad_o.contiguous(), q.contiguous(), k.contiguous(), v.contiguous(),
|
||||
o.contiguous(), lse.contiguous(), idx, num, vbs, sm_scale)
|
||||
return res[0], res[1], res[2]
|
||||
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ A third backend behind the same VSA op as the Triton and CuTe-DSL paths. This mo
|
||||
forward: it returns ``(out, lse)`` with ``lse`` in exactly the form
|
||||
``triton_block_sparse_attn_forward`` writes -- ``max(qk * qk_scale) + log2(l)``, ``[B, H, S]``
|
||||
fp32 -- so both ``block_sparse_attn_backward_triton`` and the sm_100a CUDA backward
|
||||
(``block_sparse_attn_bwd_sm100a``, 64-token blocks, sm_100a devices only) run against it
|
||||
(``block_sparse_attn_bwd_sm100a``, 64- and 128-token blocks, sm_100a / sm_103a) run against it
|
||||
unchanged.
|
||||
|
||||
The extension carries TWO instantiations of the kernel, for 64- and 128-token sparse blocks
|
||||
@@ -43,9 +43,10 @@ BHSD = True
|
||||
|
||||
|
||||
def _block_size(q: torch.Tensor, variable_block_sizes: torch.Tensor) -> int:
|
||||
# A plain int: dynamic-shape torch.compile hands SymInt shapes, which cannot key _FWD_BY_BLOCK.
|
||||
num_blocks = variable_block_sizes.numel()
|
||||
seqlen = q.shape[2] if BHSD else q.shape[1]
|
||||
return 0 if num_blocks == 0 or seqlen % num_blocks else seqlen // num_blocks
|
||||
return 0 if num_blocks == 0 or seqlen % num_blocks else int(seqlen // num_blocks)
|
||||
|
||||
|
||||
def is_supported(q: torch.Tensor, variable_block_sizes: torch.Tensor) -> bool:
|
||||
|
||||
@@ -1054,6 +1054,15 @@ class _attention(torch.autograd.Function):
|
||||
use_global_sf_P=True,
|
||||
use_global_sf_QKV=True,
|
||||
):
|
||||
if smooth_q:
|
||||
# Not implemented, not merely disabled: only the backward kernels
|
||||
# carry a SMOOTH_Q path (_attn_fwd has none), so restoring the
|
||||
# triton_group_mean wiring would yield silently wrong gradients
|
||||
# rather than a feature. The flag is kept for signature
|
||||
# compatibility only.
|
||||
raise NotImplementedError("smooth_q=True is not supported: the forward kernel has no "
|
||||
"SMOOTH_Q path, so enabling it would produce incorrect gradients")
|
||||
|
||||
# shape constraints
|
||||
HEAD_DIM_Q, HEAD_DIM_K = q.shape[-1], k.shape[-1]
|
||||
# when v is in float8_e5m2 it is transposed.
|
||||
@@ -1364,10 +1373,8 @@ class _attention(torch.autograd.Function):
|
||||
BLOCK_M=PRE_BLOCK,
|
||||
HEAD_DIM=ctx.HEAD_DIM)
|
||||
|
||||
# smooth_q is rejected in forward(); ctx.smooth_q is always False here.
|
||||
q_m = None
|
||||
if ctx.smooth_q:
|
||||
# _, q_m = triton_group_mean(q)
|
||||
q_m = q_m.repeat_interleave(q.shape[2] // q_m.shape[2], dim=2) # B,H,L,D
|
||||
|
||||
sm100_optimized_backward = (getattr(ctx, "sm100_optimized", False) and ctx.use_qat_qkv_backward
|
||||
and not ctx.smooth_k and not ctx.smooth_q and N_CTX_KV % 16 == 0)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user