# CI Architecture ## Overview FastVideo uses a three-tier CI pipeline designed to keep feedback fast on every push while protecting `main` through a full GPU regression suite before any merge. ``` PR push │ ├─► Tier 1: Pre-commit (~2 min) │ GitHub Actions / ubuntu-latest │ yapf, ruff, mypy, codespell, pymarkdown, actionlint, check-filenames │ └─► Tier 2: Fastcheck (~10-20 min, path-filtered) Buildkite / Modal GPU instances Only runs tests for paths you changed │ (developer comments /merge or maintainer adds 'ready' label) ▼ Tier 3: Full Suite (~60-90 min) Buildkite / Modal GPU instances All integration, SSIM, training, and performance tests Runs on the PR branch directly │ pass ──► Mergify auto-squash-merges to main, branch deleted fail ──► fix the regression, push, and /merge again ``` --- ## CI Tiers ### Tier 1: Pre-commit (every PR push) | Attribute | Value | |-----------|-------| | Triggered by | Every push to any PR branch, plus pushes to `main` | | Runs on | GitHub Actions, `ubuntu-latest` | | Duration | ~2 minutes | **Checks run** (from `.pre-commit-config.yaml`, stage: `manual`): | Hook | What it checks | |------|---------------| | `yapf` | Python code formatting | | `ruff` | Python linting and auto-fixable style issues | | `codespell` | Spelling errors in code and docs | | `pymarkdown` | Markdown formatting | | `actionlint` | GitHub Actions workflow syntax | | `mypy` | Static type checking (Python 3.10 target) | | `check-filenames` | No spaces in tracked filenames | A failure here means code style or type issues. Run `pre-commit run --all-files` locally to replicate CI results before pushing. --- ### Tier 2: Fastcheck (path-filtered, every PR push) | Attribute | Value | |-----------|-------| | Triggered by | Every push; the monorepo-diff plugin skips tests for unchanged paths | | Runs on | Buildkite, Modal GPU instances | | Duration | ~10-20 minutes per test (run in parallel) | **Tests and their path triggers:** | Buildkite label | `TEST_TYPE` | Triggers when you change | |-----------------|-------------|--------------------------| | Encoder Tests | `encoder` | `fastvideo/models/encoders/**`, `fastvideo/models/loader/**`, `fastvideo/tests/encoders/**`, `pyproject.toml`, `docker/Dockerfile.python3.12` | | VAE Tests | `vae` | `fastvideo/models/vaes/**`, `fastvideo/models/loader/**`, `fastvideo/tests/vaes/**`, `pyproject.toml`, `docker/Dockerfile.python3.12` | | Transformer Tests | `transformer` | `fastvideo/models/dits/**`, `fastvideo/models/loader/**`, `fastvideo/tests/transformers/**`, `fastvideo/layers/**`, `fastvideo/attention/**`, `pyproject.toml`, `docker/Dockerfile.python3.12` | | Kernel Tests | `kernel_tests` | `fastvideo-kernel/**`, `pyproject.toml`, `docker/Dockerfile.python3.12` | | Unit Tests | `unit_test` | `fastvideo/**`, `.buildkite/**`, `.github/**`, `pyproject.toml`, `docker/Dockerfile.python3.12` | A Fastcheck failure means a component-level regression. Check the Buildkite build log for the failing test's output. --- ### Tier 3: Full Test Suite (triggered by `ready` label) | Attribute | Value | |-----------|-------| | Triggered by | Adding the `ready` label to the PR (via `/merge` command), or a `/test full` command | | Runs on | Buildkite, Modal GPU instances | | Duration | 60-90 minutes total (tests run in parallel, path-filtered) | **Tests:** | Buildkite label | `TEST_TYPE` | Timeout | |-----------------|-------------|---------| | SSIM Tests | `ssim` | 90 min | | LoRA Inference Tests | `inference_lora` | 20 min | | Training Tests | `training` | 15 min | | Distillation DMD Tests | `distillation_dmd` | 15 min | | Self-Forcing Tests | `self_forcing` | 15 min | | LoRA Training Tests | `training_lora` | 15 min | | Training Tests VSA | `training_vsa` | 15 min | | Inference Tests VMoBA | `inference_vmoba` | 15 min | | Performance Tests | `performance` | 30 min | | API Server Tests | `api_server` | 30 min | | Train Framework Tests | `train_framework` | 30 min | If a Full Suite test fails, check the Buildkite build log for the failing step's output. Fix the regression, push, and comment `/merge` again to re-trigger. --- ## Auto-merge Flow Mergify prevents untested code from landing on `main` by gating squash-merge on the Full Suite passing directly on the PR branch. **How it works:** 1. A developer comments `/merge` on an approved PR (or a maintainer adds the `ready` label). 2. The `ready` label triggers `ci-trigger-full-suite.yml`, which calls the Buildkite API to run the Full Suite on the PR branch itself. 3. While the Full Suite runs, Mergify also auto-rebases the PR branch against `main` if it is behind and has no conflicts. 4. Once the Full Suite posts `full-suite-passed`, Mergify checks all **merge conditions**: - `pre-commit` check is green - `fastcheck-passed` check is green - `full-suite-passed` check is green - At least 1 approved review (`#approved-reviews-by>=1`) - PR title starts with a valid `[type]` tag - PR is not a draft - No merge conflicts 5. If all conditions pass, Mergify squash-merges to `main` automatically. The branch is deleted after merge. 6. If the Full Suite fails, the developer fixes the issue, pushes, and comments `/merge` again to re-trigger. **Merge conditions summary:** | Condition | Meaning | |-----------|---------| | `check-success~=pre-commit` | Tier 1 pre-commit must be green | | `check-success=fastcheck-passed` | Tier 2 Fastcheck must be green | | `check-success=full-suite-passed` | Tier 3 Full Suite must be green | | `#approved-reviews-by>=1` | At least one approved review | | `title~=(?i)^\[(feat|bugfix|...)` | PR title has a valid type tag | | `-draft` | PR is not in draft state | | `-conflict` | No merge conflicts with base branch | | `-closed` | PR is still open | --- ## Label System Labels are applied automatically. You don't need to set them manually. ### Type Labels (from PR title prefix) Applied by Mergify based on the `[tag]` at the start of the PR title. | Label | Matched title prefix | Meaning | |-------|---------------------|---------| | `type: feat` | `[feat]` or `[feature]` | New feature or capability | | `type: bugfix` | `[bugfix]` or `[fix]` | Bug fix | | `type: refactor` | `[refactor]` | Code restructuring, no behavior change | | `type: perf` | `[perf]` | Performance improvement | | `type: ci` | `[ci]` | CI/CD or tooling changes | | `type: docs` | `[doc]` or `[docs]` | Documentation only | | `type: misc` | `[misc]` or `[chore]` | Housekeeping, dependency bumps | | `type: new-model` | `[new-model]` | Adding a new model | ### Scope Labels (from changed files) Applied by Mergify based on which paths you modified. Multiple scope labels can be added. | Label | File paths that trigger it | |-------|---------------------------| | `scope: training` | `fastvideo/train/`, `fastvideo/training/`, `fastvideo/distillation/`, `examples/train/`, `examples/training/`, `examples/distill/` | | `scope: inference` | `fastvideo/pipelines/basic/`, `fastvideo/pipelines/stages/`, `fastvideo/pipelines/samplers/`, `fastvideo/entrypoints/`, `fastvideo/worker/`, `fastvideo/api/sampling_param.py`, `fastvideo/configs/pipelines/`, `examples/inference/` | | `scope: attention` | `fastvideo/attention/` | | `scope: kernel` | `fastvideo-kernel/`, `csrc/` | | `scope: data` | `fastvideo/dataset/`, `fastvideo/pipelines/preprocess/`, `examples/preprocessing/` | | `scope: infra` | `.github/`, `.buildkite/`, `fastvideo/tests/`, `docker/` | | `scope: distributed` | `fastvideo/distributed/` | | `scope: docs` | `docs/` | | `scope: ui` | `ui/` | | `scope: model` | `fastvideo/models/`, `fastvideo/layers/`, `fastvideo/configs/models/` | ### Process Labels | Label | Who sets it | Meaning | |-------|-------------|---------| | `ready` | Developer (`/merge` command) or maintainer | Triggers Full Suite and enables auto-merge | | `needs-rebase` | Mergify (automatic) | PR has merge conflicts; rebase needed | | `do-not-merge` | Maintainer | Blocks queue entry regardless of other conditions | --- ## PR Title Format All PR titles targeting `main` must start with a bracketed type tag. This is enforced by a Mergify merge protection rule and is required before a PR can be squash-merged. **Format:** ``` [type] Short description ``` **Valid type tags:** `feat`, `feature`, `bugfix`, `fix`, `refactor`, `perf`, `ci`, `doc`, `docs`, `misc`, `chore`, `kernel`, `new-model` **Valid examples:** ``` [feat] Add causal Wan pipeline [bugfix] Fix VAE temporal tiling corruption [refactor] Restructure training framework [perf] Optimize FlashAttention kernel dispatch [docs] Add inference guide for LoRA [new-model] Port HunyuanVideo 1.5 to FastVideo ``` **Invalid examples (will block merge):** ``` Add causal Wan pipeline ← missing type tag FEAT: Add pipeline ← wrong format feat: Add pipeline ← square brackets required ``` If your title is invalid, Mergify posts a comment explaining the required format and the merge protection check will remain failed until you update the title. --- ## Slash Commands Slash commands let contributors and maintainers trigger CI actions directly from PR comments. **Write permission to the repository is required.** The command is recognized within a few seconds. The workflow reacts with a 🚀 emoji to confirm. ### `/merge` ``` /merge ``` Adds the `ready` label to the PR, which triggers the Full Suite on your PR branch and enables Mergify to auto-squash-merge once all conditions pass. The command first removes the `ready` label if it is already present, then re-adds it. This ensures the `labeled` event fires and a fresh Full Suite build is started even on a re-try. ### `/test ` ``` /test ``` Triggers a specific Buildkite test or suite on the current PR branch. | Command | Runs | Maps to `TEST_TYPE` | |---------|------|---------------------| | `/test encoder` | Encoder Tests (Fastcheck) | `encoder` | | `/test vae` | VAE Tests (Fastcheck) | `vae` | | `/test transformer` | Transformer Tests (Fastcheck) | `transformer` | | `/test kernel` | Kernel Tests (Fastcheck) | `kernel_tests` | | `/test unit` | Unit Tests (Fastcheck) | `unit_test` | | `/test ssim` | SSIM regression tests | `ssim` | | `/test training` | Training pipeline tests | `training` | | `/test lora-inference` | LoRA inference tests | `inference_lora` | | `/test lora-training` | LoRA training tests | `training_lora` | | `/test distillation` | DMD distillation tests | `distillation_dmd` | | `/test self-forcing` | Self-Forcing tests | `self_forcing` | | `/test vsa` | VSA training tests | `training_vsa` | | `/test vmoba` | VMoBA inference tests | `inference_vmoba` | | `/test performance` | Performance benchmarks | `performance` | | `/test api` | API server integration tests | `api_server` | | `/test train-framework` | `fastvideo.train` GPU model loading + per-method tests | `train_framework` | | `/test full` | Entire Full Suite | all (with `TEST_SCOPE=full`) | | `/test fastcheck` | Entire Fastcheck suite | fastcheck (with `TEST_SCOPE=fastcheck`) | | `/test pre-commit` | Pre-commit checks on PR code | — (runs `ci-precommit.yml` via `workflow_call`) | **Re-running failed tests:** When you use `/test ` to re-run a specific failed test, the resulting Buildkite check uses the same name as the original (e.g., `/test encoder` creates `buildkite/ci/microscope-encoder-tests`). This overwrites the failed check status. Once all tests in a tier pass, the aggregate status (`fastcheck-passed` or `full-suite-passed`) is automatically updated to `success` by the `ci-aggregate-status.yml` workflow. **How aggregate status refresh works:** 1. `/test ` triggers a Buildkite build with `TEST_SCOPE=direct`. The test step uses the same label as its fastcheck/full-suite counterpart, so the resulting GitHub check overwrites the original. 2. When the build completes, Buildkite's `notify` posts a `direct-test-completed` commit status. This is the only signal that triggers the aggregate workflow — intermediate step status updates do not trigger it. 3. `ci-aggregate-status.yml` fires, calls `getCombinedStatusForRef` to fetch the latest status for every context on that commit (each context returns only its most recent state), groups them by prefix (`microscope-*` → fastcheck, `test-tube-*`/`bar-chart-*` → full suite), and posts `fastcheck-passed: success` or `full-suite-passed: success` if all entries in the group are `success`. 4. Tests that were never triggered (skipped by monorepo-diff) have no status entry and do not block the aggregate. --- ## Auto Branch Cleanup After a PR is squash-merged to `main`, Mergify automatically deletes the head branch. Protected branches (`main`, `master`, `release/*`) are never deleted. --- ## Workflow File Reference | Filename | Trigger | What it does | |----------|---------|-------------| | `ci-precommit.yml` | Every push / PR against `main` | Runs pre-commit hooks (yapf, ruff, mypy, codespell, pymarkdown, actionlint, check-filenames) | | `ci-trigger-full-suite.yml` | `ready` label added to a PR | Calls Buildkite API to run Full Suite on the PR branch | | `ci-slash-commands.yml` | PR comment starting with `/merge` or `/test` | Handles slash commands; adds `ready` label or triggers Buildkite | | `ci-aggregate-status.yml` | Any Buildkite commit status update | Checks if all tests in a tier passed; updates `fastcheck-passed` or `full-suite-passed` | | `community-issue-labeler.yml` | Issue opened or edited | Auto-labels issues by keyword matching against title and body | | `community-welcome.yml` | First contribution | Posts a welcome comment for first-time contributors | | `community-stale.yml` | Scheduled | Marks and closes stale issues and PRs | | `infra-build-image.yml` | Manual (`workflow_dispatch`) | Builds Docker images for CI | | `infra-docs.yml` | Changes to `docs/` merged to `main` | Builds and deploys documentation to GitHub Pages | | `publish-fastvideo.yml` | Version bump | Publishes `fastvideo` package to PyPI | | `publish-kernel.yml` | Version bump | Publishes `fastvideo-kernel` package to PyPI | | `publish-comfyui.yml` | Version bump | Publishes ComfyUI node package to PyPI |