Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
15c79dda56 | ||
|
|
f7ad5ef7e0 | ||
|
|
2cb6bdd0b2 | ||
|
|
4df8a7e209 | ||
|
|
6371f19638 | ||
|
|
78173b6741 | ||
|
|
a3e1591f93 | ||
|
|
9225604322 | ||
|
|
98f628a49c | ||
|
|
cc4889de98 | ||
|
|
05eb6ccc60 | ||
|
|
4da7beccbc |
+3
-18
@@ -5,32 +5,17 @@ env:
|
||||
notify:
|
||||
- github_commit_status:
|
||||
context: "fastcheck-passed"
|
||||
if: build.env("TEST_SCOPE") == "fastcheck" || build.env("TEST_SCOPE") == null
|
||||
- github_commit_status:
|
||||
context: "full-suite-passed"
|
||||
if: build.env("TEST_SCOPE") == "full"
|
||||
if: build.env("FULL_SUITE") == "true"
|
||||
|
||||
steps:
|
||||
# ============================================================
|
||||
- label: ":dart: Direct Test (${TEST_TYPE})"
|
||||
if: build.env("TEST_SCOPE") == "direct"
|
||||
command: "timeout 90m .buildkite/scripts/pr_test.sh"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
limit: 3
|
||||
- exit_status: -1
|
||||
limit: 2
|
||||
agents:
|
||||
queue: "default"
|
||||
|
||||
# ============================================================
|
||||
# Fastcheck: Runs on every PR (~10-15 min parallel)
|
||||
# Core component validation: encoders, VAEs, transformers,
|
||||
# CUDA kernels, and unit tests.
|
||||
# ============================================================
|
||||
- label: "Trigger Fastcheck"
|
||||
if: build.env("TEST_SCOPE") == "fastcheck" || build.env("TEST_SCOPE") == null
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
@@ -108,14 +93,14 @@ steps:
|
||||
queue: "default"
|
||||
|
||||
# ============================================================
|
||||
# Full Suite: Runs when TEST_SCOPE=full
|
||||
# Full Suite: Runs when FULL_SUITE=true
|
||||
# Triggered by adding the 'ready' label (via ci-trigger-full-suite.yml)
|
||||
# or on-demand via /test full slash command.
|
||||
# Includes integration tests, SSIM regression, training pipelines,
|
||||
# and performance benchmarks.
|
||||
# ============================================================
|
||||
- label: "Trigger Full Suite"
|
||||
if: build.env("TEST_SCOPE") == "full"
|
||||
if: build.env("FULL_SUITE") == "true"
|
||||
retry:
|
||||
automatic:
|
||||
- exit_status: 128
|
||||
|
||||
+26
-26
@@ -84,12 +84,12 @@ pull_request_rules:
|
||||
- name: "label scope: training"
|
||||
conditions:
|
||||
- or:
|
||||
- files~=^fastvideo/train/
|
||||
- files~=^fastvideo/training/
|
||||
- files~=^fastvideo/distillation/
|
||||
- files~=^examples/train/
|
||||
- files~=^examples/training/
|
||||
- files~=^examples/distill/
|
||||
- files~=^fastvideo/train/
|
||||
- files~=^fastvideo/training/
|
||||
- files~=^fastvideo/distillation/
|
||||
- files~=^examples/train/
|
||||
- files~=^examples/training/
|
||||
- files~=^examples/distill/
|
||||
- -closed
|
||||
actions:
|
||||
label:
|
||||
@@ -98,14 +98,14 @@ pull_request_rules:
|
||||
- name: "label scope: inference"
|
||||
conditions:
|
||||
- or:
|
||||
- files~=^fastvideo/pipelines/basic/
|
||||
- files~=^fastvideo/pipelines/stages/
|
||||
- files~=^fastvideo/pipelines/samplers/
|
||||
- files~=^fastvideo/entrypoints/
|
||||
- files~=^fastvideo/worker/
|
||||
- files~=^fastvideo/configs/sample/
|
||||
- files~=^fastvideo/configs/pipelines/
|
||||
- files~=^examples/inference/
|
||||
- files~=^fastvideo/pipelines/basic/
|
||||
- files~=^fastvideo/pipelines/stages/
|
||||
- files~=^fastvideo/pipelines/samplers/
|
||||
- files~=^fastvideo/entrypoints/
|
||||
- files~=^fastvideo/worker/
|
||||
- files~=^fastvideo/configs/sample/
|
||||
- files~=^fastvideo/configs/pipelines/
|
||||
- files~=^examples/inference/
|
||||
- -closed
|
||||
actions:
|
||||
label:
|
||||
@@ -122,8 +122,8 @@ pull_request_rules:
|
||||
- name: "label scope: kernel"
|
||||
conditions:
|
||||
- or:
|
||||
- files~=^fastvideo-kernel/
|
||||
- files~=^csrc/
|
||||
- files~=^fastvideo-kernel/
|
||||
- files~=^csrc/
|
||||
- -closed
|
||||
actions:
|
||||
label:
|
||||
@@ -132,9 +132,9 @@ pull_request_rules:
|
||||
- name: "label scope: data"
|
||||
conditions:
|
||||
- or:
|
||||
- files~=^fastvideo/dataset/
|
||||
- files~=^fastvideo/pipelines/preprocess/
|
||||
- files~=^examples/preprocessing/
|
||||
- files~=^fastvideo/dataset/
|
||||
- files~=^fastvideo/pipelines/preprocess/
|
||||
- files~=^examples/preprocessing/
|
||||
- -closed
|
||||
actions:
|
||||
label:
|
||||
@@ -143,10 +143,10 @@ pull_request_rules:
|
||||
- name: "label scope: infra"
|
||||
conditions:
|
||||
- or:
|
||||
- files~=^\.github/
|
||||
- files~=^\.buildkite/
|
||||
- files~=^fastvideo/tests/
|
||||
- files~=^docker/
|
||||
- files~=^\.github/
|
||||
- files~=^\.buildkite/
|
||||
- files~=^fastvideo/tests/
|
||||
- files~=^docker/
|
||||
- -closed
|
||||
actions:
|
||||
label:
|
||||
@@ -179,9 +179,9 @@ pull_request_rules:
|
||||
- name: "label scope: model"
|
||||
conditions:
|
||||
- or:
|
||||
- files~=^fastvideo/models/
|
||||
- files~=^fastvideo/layers/
|
||||
- files~=^fastvideo/configs/models/
|
||||
- files~=^fastvideo/models/
|
||||
- files~=^fastvideo/layers/
|
||||
- files~=^fastvideo/configs/models/
|
||||
- -closed
|
||||
actions:
|
||||
label:
|
||||
|
||||
@@ -14,7 +14,7 @@ permissions:
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
if: github.event_name == 'workflow_call' || github.event.pull_request.draft != true
|
||||
if: github.event.pull_request.draft != true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
@@ -7,7 +7,6 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
statuses: write
|
||||
|
||||
jobs:
|
||||
handle-merge:
|
||||
@@ -55,7 +54,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
test_type: ${{ steps.parse.outputs.test_type }}
|
||||
test_scope: ${{ steps.parse.outputs.test_scope }}
|
||||
full_suite: ${{ steps.parse.outputs.full_suite }}
|
||||
pr_sha: ${{ steps.pr.outputs.sha }}
|
||||
pr_branch: ${{ steps.pr.outputs.branch }}
|
||||
@@ -87,7 +85,7 @@ jobs:
|
||||
set -euo pipefail
|
||||
TEST_NAME=$(echo "$COMMENT" | grep -oP '(?<=/test\s)\S+' | head -1 || true)
|
||||
|
||||
VALID="encoder vae transformer kernel unit ssim training lora-inference lora-training distillation self-forcing vsa vmoba performance api full fastcheck pre-commit"
|
||||
VALID="encoder vae transformer kernel unit ssim training lora-inference lora-training distillation self-forcing vsa vmoba performance api full fastcheck"
|
||||
if [ -z "$TEST_NAME" ] || ! echo "$VALID" | grep -qw "$TEST_NAME"; then
|
||||
echo "Unknown test: '$TEST_NAME'. Valid: $VALID"
|
||||
exit 1
|
||||
@@ -104,29 +102,14 @@ jobs:
|
||||
)
|
||||
|
||||
if [ "$TEST_NAME" = "full" ]; then
|
||||
{
|
||||
echo "test_type=all"
|
||||
echo "test_scope=full"
|
||||
echo "full_suite=true"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
echo "test_type=all" >> "$GITHUB_OUTPUT"
|
||||
echo "full_suite=true" >> "$GITHUB_OUTPUT"
|
||||
elif [ "$TEST_NAME" = "fastcheck" ]; then
|
||||
{
|
||||
echo "test_type=fastcheck"
|
||||
echo "test_scope=fastcheck"
|
||||
echo "full_suite=false"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
elif [ "$TEST_NAME" = "pre-commit" ]; then
|
||||
{
|
||||
echo "test_type="
|
||||
echo "test_scope=precommit"
|
||||
echo "full_suite=false"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
echo "test_type=fastcheck" >> "$GITHUB_OUTPUT"
|
||||
echo "full_suite=false" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
{
|
||||
echo "test_type=${MAP[$TEST_NAME]}"
|
||||
echo "test_scope=direct"
|
||||
echo "full_suite=false"
|
||||
} >> "$GITHUB_OUTPUT"
|
||||
echo "test_type=${MAP[$TEST_NAME]}" >> "$GITHUB_OUTPUT"
|
||||
echo "full_suite=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Get PR details
|
||||
@@ -143,34 +126,6 @@ jobs:
|
||||
core.setOutput('sha', pr.head.sha);
|
||||
core.setOutput('branch', pr.head.ref);
|
||||
|
||||
pre-commit:
|
||||
needs: parse-command
|
||||
if: >-
|
||||
needs.parse-command.outputs.has_write == 'true'
|
||||
&& needs.parse-command.outputs.test_scope == 'precommit'
|
||||
uses: ./.github/workflows/ci-precommit.yml
|
||||
|
||||
post-precommit-status:
|
||||
needs: [parse-command, pre-commit]
|
||||
if: always() && needs.parse-command.outputs.test_scope == 'precommit'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
env:
|
||||
PR_SHA: ${{ needs.parse-command.outputs.pr_sha }}
|
||||
RESULT: ${{ needs.pre-commit.result }}
|
||||
with:
|
||||
script: |
|
||||
const state = process.env.RESULT === 'success' ? 'success' : 'failure';
|
||||
await github.rest.repos.createCommitStatus({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
sha: process.env.PR_SHA,
|
||||
state,
|
||||
context: 'pre-commit',
|
||||
description: `Triggered via /test pre-commit (${state})`,
|
||||
});
|
||||
|
||||
trigger-buildkite:
|
||||
needs: parse-command
|
||||
if: >-
|
||||
@@ -190,41 +145,14 @@ jobs:
|
||||
});
|
||||
|
||||
- name: Trigger Buildkite
|
||||
env:
|
||||
BUILDKITE_API_TOKEN: ${{ secrets.BUILDKITE_API_TOKEN }}
|
||||
PR_SHA: ${{ needs.parse-command.outputs.pr_sha }}
|
||||
PR_BRANCH: ${{ needs.parse-command.outputs.pr_branch }}
|
||||
PR_NUMBER: ${{ github.event.issue.number }}
|
||||
TEST_SCOPE: ${{ needs.parse-command.outputs.test_scope }}
|
||||
FULL_SUITE: ${{ needs.parse-command.outputs.full_suite }}
|
||||
TEST_TYPE: ${{ needs.parse-command.outputs.test_type }}
|
||||
BK_ORG: ${{ vars.BUILDKITE_ORG_SLUG }}
|
||||
BK_PIPELINE: ${{ vars.BUILDKITE_PIPELINE_SLUG }}
|
||||
run: |
|
||||
curl -sS --fail-with-body -X POST \
|
||||
"https://api.buildkite.com/v2/organizations/${BK_ORG}/pipelines/${BK_PIPELINE}/builds" \
|
||||
-H "Authorization: Bearer $BUILDKITE_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-raw "$(jq -n \
|
||||
--arg commit "$PR_SHA" \
|
||||
--arg branch "$PR_BRANCH" \
|
||||
--arg message "/test ${TEST_TYPE} on PR #${PR_NUMBER}" \
|
||||
--argjson pr_id "$PR_NUMBER" \
|
||||
--arg test_scope "$TEST_SCOPE" \
|
||||
--arg full_suite "$FULL_SUITE" \
|
||||
--arg test_type "$TEST_TYPE" \
|
||||
--arg pr_number "$PR_NUMBER" \
|
||||
'{
|
||||
commit: $commit,
|
||||
branch: $branch,
|
||||
message: $message,
|
||||
ignore_pipeline_branch_filters: true,
|
||||
pull_request_id: $pr_id,
|
||||
pull_request_base_branch: "main",
|
||||
env: {
|
||||
TEST_SCOPE: $test_scope,
|
||||
FULL_SUITE: $full_suite,
|
||||
TEST_TYPE: $test_type,
|
||||
PR_NUMBER: $pr_number
|
||||
}
|
||||
}')"
|
||||
uses: buildkite/trigger-pipeline-action@v2.4.1
|
||||
with:
|
||||
buildkite_api_access_token: ${{ secrets.BUILDKITE_API_TOKEN }}
|
||||
pipeline: "${{ vars.BUILDKITE_ORG_SLUG }}/${{ vars.BUILDKITE_PIPELINE_SLUG }}"
|
||||
branch: "${{ needs.parse-command.outputs.pr_branch }}"
|
||||
commit: "${{ needs.parse-command.outputs.pr_sha }}"
|
||||
message: "/test ${{ needs.parse-command.outputs.test_type }} on PR #${{ github.event.issue.number }}"
|
||||
build_env_vars: '{"FULL_SUITE": "${{ needs.parse-command.outputs.full_suite }}", "TEST_TYPE": "${{ needs.parse-command.outputs.test_type }}", "PR_NUMBER": "${{ github.event.issue.number }}"}'
|
||||
send_pull_request: "true"
|
||||
pull_request_id: "${{ github.event.issue.number }}"
|
||||
pull_request_base_branch: "main"
|
||||
|
||||
@@ -39,10 +39,10 @@ jobs:
|
||||
BUILDKITE_API_TOKEN: ${{ secrets.BUILDKITE_API_TOKEN }}
|
||||
PR_BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||
run: |
|
||||
# Find running builds for this branch with TEST_SCOPE=full and cancel them
|
||||
# Find running builds for this branch with FULL_SUITE=true and cancel them
|
||||
builds=$(curl -sS -H "Authorization: Bearer $BUILDKITE_API_TOKEN" \
|
||||
"https://api.buildkite.com/v2/organizations/${{ vars.BUILDKITE_ORG_SLUG }}/pipelines/${{ vars.BUILDKITE_PIPELINE_SLUG }}/builds?branch=${PR_BRANCH}&state=running,scheduled" \
|
||||
| jq -r '.[] | select(.env.TEST_SCOPE == "full") | .number')
|
||||
| jq -r '.[] | select(.env.FULL_SUITE == "true") | .number')
|
||||
for build_num in $builds; do
|
||||
echo "Cancelling Buildkite build #$build_num"
|
||||
curl -sS -X PUT -H "Authorization: Bearer $BUILDKITE_API_TOKEN" \
|
||||
@@ -51,33 +51,14 @@ jobs:
|
||||
|
||||
- name: Trigger Buildkite Full Suite
|
||||
if: steps.check.outputs.has_ready == 'true'
|
||||
env:
|
||||
BUILDKITE_API_TOKEN: ${{ secrets.BUILDKITE_API_TOKEN }}
|
||||
PR_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
PR_BRANCH: ${{ github.event.pull_request.head.ref }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
BK_ORG: ${{ vars.BUILDKITE_ORG_SLUG }}
|
||||
BK_PIPELINE: ${{ vars.BUILDKITE_PIPELINE_SLUG }}
|
||||
run: |
|
||||
curl -sS --fail-with-body -X POST \
|
||||
"https://api.buildkite.com/v2/organizations/${BK_ORG}/pipelines/${BK_PIPELINE}/builds" \
|
||||
-H "Authorization: Bearer $BUILDKITE_API_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data-raw "$(jq -n \
|
||||
--arg commit "$PR_SHA" \
|
||||
--arg branch "$PR_BRANCH" \
|
||||
--arg message "Full Suite for PR #${PR_NUMBER}" \
|
||||
--argjson pr_id "$PR_NUMBER" \
|
||||
'{
|
||||
commit: $commit,
|
||||
branch: $branch,
|
||||
message: $message,
|
||||
ignore_pipeline_branch_filters: true,
|
||||
pull_request_id: $pr_id,
|
||||
pull_request_base_branch: "main",
|
||||
env: {
|
||||
TEST_SCOPE: "full",
|
||||
FULL_SUITE: "true",
|
||||
PR_NUMBER: ($pr_id | tostring)
|
||||
}
|
||||
}')"
|
||||
uses: buildkite/trigger-pipeline-action@v2.4.1
|
||||
with:
|
||||
buildkite_api_access_token: ${{ secrets.BUILDKITE_API_TOKEN }}
|
||||
pipeline: "${{ vars.BUILDKITE_ORG_SLUG }}/${{ vars.BUILDKITE_PIPELINE_SLUG }}"
|
||||
branch: "${{ github.event.pull_request.head.ref }}"
|
||||
commit: "${{ github.event.pull_request.head.sha }}"
|
||||
message: "Full Suite for PR #${{ github.event.pull_request.number }}"
|
||||
build_env_vars: '{"FULL_SUITE": "true", "PR_NUMBER": "${{ github.event.pull_request.number }}"}'
|
||||
send_pull_request: "true"
|
||||
pull_request_id: "${{ github.event.pull_request.number }}"
|
||||
pull_request_base_branch: "main"
|
||||
|
||||
@@ -277,8 +277,8 @@ Triggers a specific Buildkite test or suite on the current PR branch.
|
||||
| `/test vmoba` | VMoBA inference tests | `inference_vmoba` |
|
||||
| `/test performance` | Performance benchmarks | `performance` |
|
||||
| `/test api` | API server integration tests | `api_server` |
|
||||
| `/test full` | Entire Full Suite | all (with `TEST_SCOPE=full`) |
|
||||
| `/test fastcheck` | Entire Fastcheck suite | fastcheck (with `TEST_SCOPE=fastcheck`) |
|
||||
| `/test full` | Entire Full Suite | all (with `FULL_SUITE=true`) |
|
||||
| `/test fastcheck` | Entire Fastcheck suite | fastcheck (with `FULL_SUITE=false`) |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -9,24 +9,22 @@ image_version = os.getenv("IMAGE_VERSION")
|
||||
image_tag = f"ghcr.io/hao-ai-lab/fastvideo/fastvideo-dev:{image_version}"
|
||||
print(f"Using image: {image_tag}")
|
||||
|
||||
image = (modal.Image.from_registry(
|
||||
image_tag, add_python="3.12"
|
||||
).run_commands("rm -rf /FastVideo").apt_install(
|
||||
"cmake", "pkg-config", "build-essential", "curl", "libssl-dev", "ffmpeg"
|
||||
).run_commands(
|
||||
"curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable"
|
||||
).run_commands("echo 'source ~/.cargo/env' >> ~/.bashrc").env({
|
||||
"PATH":
|
||||
"/root/.cargo/bin:$PATH",
|
||||
"BUILDKITE_REPO":
|
||||
os.environ.get("BUILDKITE_REPO", ""),
|
||||
"BUILDKITE_COMMIT":
|
||||
os.environ.get("BUILDKITE_COMMIT", ""),
|
||||
"BUILDKITE_PULL_REQUEST":
|
||||
os.environ.get("BUILDKITE_PULL_REQUEST", ""),
|
||||
"IMAGE_VERSION":
|
||||
os.environ.get("IMAGE_VERSION", ""),
|
||||
}))
|
||||
image = (
|
||||
modal.Image.from_registry(image_tag, add_python="3.12")
|
||||
.run_commands("rm -rf /FastVideo")
|
||||
.apt_install("cmake", "pkg-config", "build-essential", "curl", "libssl-dev", "ffmpeg")
|
||||
.run_commands("curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable")
|
||||
.run_commands("echo 'source ~/.cargo/env' >> ~/.bashrc")
|
||||
.env(
|
||||
{
|
||||
"PATH": "/root/.cargo/bin:$PATH",
|
||||
"BUILDKITE_REPO": os.environ.get("BUILDKITE_REPO", ""),
|
||||
"BUILDKITE_COMMIT": os.environ.get("BUILDKITE_COMMIT", ""),
|
||||
"BUILDKITE_PULL_REQUEST": os.environ.get("BUILDKITE_PULL_REQUEST", ""),
|
||||
"IMAGE_VERSION": os.environ.get("IMAGE_VERSION", ""),
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def run_test(pytest_command: str):
|
||||
@@ -41,8 +39,7 @@ def run_test(pytest_command: str):
|
||||
|
||||
print(f"Cloning repository: {git_repo}")
|
||||
print(f"Target commit: {git_commit}")
|
||||
if pr_number:
|
||||
print(f"PR number: {pr_number}")
|
||||
print(f"PR number: {pr_number}")
|
||||
|
||||
# For PRs (including forks), use GitHub's PR refs to get the correct commit
|
||||
if pr_number and pr_number != "false":
|
||||
@@ -66,95 +63,84 @@ def run_test(pytest_command: str):
|
||||
{pytest_command}
|
||||
"""
|
||||
|
||||
result = subprocess.run(["/bin/bash", "-c", command],
|
||||
stdout=sys.stdout,
|
||||
stderr=sys.stderr,
|
||||
check=False)
|
||||
result = subprocess.run(["/bin/bash", "-c", command], stdout=sys.stdout, stderr=sys.stderr, check=False)
|
||||
|
||||
sys.exit(result.returncode)
|
||||
|
||||
|
||||
@app.function(gpu="H100:1",
|
||||
image=image,
|
||||
timeout=1200,
|
||||
secrets=[
|
||||
modal.Secret.from_dict(
|
||||
{"HF_API_KEY": os.environ.get("HF_API_KEY", "")})
|
||||
],
|
||||
volumes={"/root/data": model_vol})
|
||||
@app.function(
|
||||
gpu="H100:1",
|
||||
image=image,
|
||||
timeout=1200,
|
||||
secrets=[modal.Secret.from_dict({"HF_API_KEY": os.environ.get("HF_API_KEY", "")})],
|
||||
volumes={"/root/data": model_vol},
|
||||
)
|
||||
def run_encoder_tests():
|
||||
run_test(
|
||||
"export HF_HOME='/root/data/.cache' && hf auth login --token $HF_API_KEY && pytest ./fastvideo/tests/encoders -vs"
|
||||
)
|
||||
|
||||
|
||||
@app.function(gpu="L40S:1",
|
||||
image=image,
|
||||
timeout=1200,
|
||||
secrets=[
|
||||
modal.Secret.from_dict(
|
||||
{"HF_API_KEY": os.environ.get("HF_API_KEY", "")})
|
||||
],
|
||||
volumes={"/root/data": model_vol})
|
||||
@app.function(
|
||||
gpu="L40S:1",
|
||||
image=image,
|
||||
timeout=1200,
|
||||
secrets=[modal.Secret.from_dict({"HF_API_KEY": os.environ.get("HF_API_KEY", "")})],
|
||||
volumes={"/root/data": model_vol},
|
||||
)
|
||||
def run_vae_tests():
|
||||
run_test(
|
||||
"export HF_HOME='/root/data/.cache' && hf auth login --token $HF_API_KEY && pytest ./fastvideo/tests/vaes -vs"
|
||||
)
|
||||
|
||||
|
||||
@app.function(gpu="L40S:1",
|
||||
image=image,
|
||||
timeout=900,
|
||||
secrets=[
|
||||
modal.Secret.from_dict(
|
||||
{"HF_API_KEY": os.environ.get("HF_API_KEY", "")})
|
||||
],
|
||||
volumes={"/root/data": model_vol})
|
||||
@app.function(
|
||||
gpu="L40S:1",
|
||||
image=image,
|
||||
timeout=900,
|
||||
secrets=[modal.Secret.from_dict({"HF_API_KEY": os.environ.get("HF_API_KEY", "")})],
|
||||
volumes={"/root/data": model_vol},
|
||||
)
|
||||
def run_transformer_tests():
|
||||
run_test(
|
||||
"export HF_HOME='/root/data/.cache' && hf auth login --token $HF_API_KEY && pytest ./fastvideo/tests/transformers -vs"
|
||||
)
|
||||
|
||||
|
||||
@app.function(gpu="L40S:4",
|
||||
image=image,
|
||||
timeout=900,
|
||||
secrets=[
|
||||
modal.Secret.from_dict(
|
||||
{"WANDB_API_KEY": os.environ.get("WANDB_API_KEY", "")})
|
||||
],
|
||||
volumes={"/root/data": model_vol})
|
||||
@app.function(
|
||||
gpu="L40S:4",
|
||||
image=image,
|
||||
timeout=900,
|
||||
secrets=[modal.Secret.from_dict({"WANDB_API_KEY": os.environ.get("WANDB_API_KEY", "")})],
|
||||
volumes={"/root/data": model_vol},
|
||||
)
|
||||
def run_training_tests():
|
||||
run_test(
|
||||
"export HF_HOME='/root/data/.cache' && wandb login $WANDB_API_KEY && pytest ./fastvideo/tests/training/Vanilla -srP"
|
||||
)
|
||||
|
||||
|
||||
@app.function(gpu="L40S:2",
|
||||
image=image,
|
||||
timeout=900,
|
||||
secrets=[
|
||||
modal.Secret.from_dict(
|
||||
{"WANDB_API_KEY": os.environ.get("WANDB_API_KEY", "")})
|
||||
],
|
||||
volumes={"/root/data": model_vol})
|
||||
@app.function(
|
||||
gpu="L40S:2",
|
||||
image=image,
|
||||
timeout=900,
|
||||
secrets=[modal.Secret.from_dict({"WANDB_API_KEY": os.environ.get("WANDB_API_KEY", "")})],
|
||||
volumes={"/root/data": model_vol},
|
||||
)
|
||||
def run_training_lora_tests():
|
||||
run_test(
|
||||
"export HF_HOME='/root/data/.cache' && wandb login $WANDB_API_KEY && pytest ./fastvideo/tests/training/lora/test_lora_training.py -srP"
|
||||
)
|
||||
|
||||
|
||||
@app.function(gpu="H100:2",
|
||||
image=image,
|
||||
timeout=900,
|
||||
secrets=[
|
||||
modal.Secret.from_dict(
|
||||
{"WANDB_API_KEY": os.environ.get("WANDB_API_KEY", "")})
|
||||
])
|
||||
@app.function(
|
||||
gpu="H100:2",
|
||||
image=image,
|
||||
timeout=900,
|
||||
secrets=[modal.Secret.from_dict({"WANDB_API_KEY": os.environ.get("WANDB_API_KEY", "")})],
|
||||
)
|
||||
def run_training_tests_VSA():
|
||||
run_test(
|
||||
"wandb login $WANDB_API_KEY && pytest ./fastvideo/tests/training/VSA -srP"
|
||||
)
|
||||
run_test("wandb login $WANDB_API_KEY && pytest ./fastvideo/tests/training/VSA -srP")
|
||||
|
||||
|
||||
@app.function(gpu="H100:1", image=image, timeout=900)
|
||||
@@ -174,33 +160,27 @@ def run_kernel_tests():
|
||||
|
||||
@app.function(gpu="L40S:1", image=image, timeout=900)
|
||||
def run_inference_tests_vmoba():
|
||||
run_test('python fastvideo/tests/inference/vmoba/test_vmoba_inference.py')
|
||||
run_test("python fastvideo/tests/inference/vmoba/test_vmoba_inference.py")
|
||||
|
||||
|
||||
@app.function(gpu="L40S:1", image=image, timeout=1200)
|
||||
def run_inference_lora_tests():
|
||||
run_test(
|
||||
"pytest ./fastvideo/tests/inference/lora/test_lora_inference_similarity.py -vs"
|
||||
)
|
||||
run_test("pytest ./fastvideo/tests/inference/lora/test_lora_inference_similarity.py -vs")
|
||||
|
||||
|
||||
@app.function(gpu="L40S:2", image=image, timeout=900)
|
||||
def run_distill_dmd_tests():
|
||||
run_test(
|
||||
"pytest ./fastvideo/tests/training/distill/test_distill_dmd.py -vs")
|
||||
run_test("pytest ./fastvideo/tests/training/distill/test_distill_dmd.py -vs")
|
||||
|
||||
|
||||
@app.function(gpu="L40S:2",
|
||||
image=image,
|
||||
timeout=900,
|
||||
secrets=[
|
||||
modal.Secret.from_dict(
|
||||
{"WANDB_API_KEY": os.environ.get("WANDB_API_KEY", "")})
|
||||
])
|
||||
@app.function(
|
||||
gpu="L40S:2",
|
||||
image=image,
|
||||
timeout=900,
|
||||
secrets=[modal.Secret.from_dict({"WANDB_API_KEY": os.environ.get("WANDB_API_KEY", "")})],
|
||||
)
|
||||
def run_self_forcing_tests():
|
||||
run_test(
|
||||
"wandb login $WANDB_API_KEY && pytest ./fastvideo/tests/training/self-forcing/test_self_forcing.py -vs"
|
||||
)
|
||||
run_test("wandb login $WANDB_API_KEY && pytest ./fastvideo/tests/training/self-forcing/test_self_forcing.py -vs")
|
||||
|
||||
|
||||
@app.function(gpu="L40S:1", image=image, timeout=900)
|
||||
@@ -210,41 +190,36 @@ def run_unit_test():
|
||||
)
|
||||
|
||||
|
||||
@app.function(gpu="L40S:1",
|
||||
image=image,
|
||||
timeout=3600,
|
||||
secrets=[
|
||||
modal.Secret.from_dict(
|
||||
{"HF_API_KEY": os.environ.get("HF_API_KEY", "")})
|
||||
])
|
||||
@app.function(
|
||||
gpu="L40S:1",
|
||||
image=image,
|
||||
timeout=3600,
|
||||
secrets=[modal.Secret.from_dict({"HF_API_KEY": os.environ.get("HF_API_KEY", "")})],
|
||||
)
|
||||
def run_lora_extraction_tests():
|
||||
run_test(
|
||||
"hf auth login --token $HF_API_KEY && pytest ./fastvideo/tests/lora_extraction/test_lora_extraction.py"
|
||||
)
|
||||
run_test("hf auth login --token $HF_API_KEY && pytest ./fastvideo/tests/lora_extraction/test_lora_extraction.py")
|
||||
|
||||
|
||||
@app.function(gpu="L40S:2",
|
||||
image=image,
|
||||
timeout=1800,
|
||||
secrets=[
|
||||
modal.Secret.from_dict(
|
||||
{"HF_API_KEY": os.environ.get("HF_API_KEY", "")})
|
||||
],
|
||||
volumes={"/root/data": model_vol})
|
||||
@app.function(
|
||||
gpu="L40S:2",
|
||||
image=image,
|
||||
timeout=1800,
|
||||
secrets=[modal.Secret.from_dict({"HF_API_KEY": os.environ.get("HF_API_KEY", "")})],
|
||||
volumes={"/root/data": model_vol},
|
||||
)
|
||||
def run_performance_tests():
|
||||
run_test(
|
||||
"export HF_HOME='/root/data/.cache' && hf auth login --token $HF_API_KEY && pytest ./fastvideo/tests/performance -vs"
|
||||
)
|
||||
|
||||
|
||||
@app.function(gpu="L40S:1",
|
||||
image=image,
|
||||
timeout=1800,
|
||||
secrets=[
|
||||
modal.Secret.from_dict(
|
||||
{"HF_API_KEY": os.environ.get("HF_API_KEY", "")})
|
||||
],
|
||||
volumes={"/root/data": model_vol})
|
||||
@app.function(
|
||||
gpu="L40S:1",
|
||||
image=image,
|
||||
timeout=1800,
|
||||
secrets=[modal.Secret.from_dict({"HF_API_KEY": os.environ.get("HF_API_KEY", "")})],
|
||||
volumes={"/root/data": model_vol},
|
||||
)
|
||||
def run_api_server_tests():
|
||||
run_test(
|
||||
"export HF_HOME='/root/data/.cache' && hf auth login --token $HF_API_KEY && pytest ./fastvideo/tests/entrypoints/test_openai_api_integration.py -vs"
|
||||
|
||||
@@ -79,13 +79,8 @@ def _assert_similarity(
|
||||
attention_backend_name: str,
|
||||
) -> None:
|
||||
if not os.path.exists(reference_folder):
|
||||
logger.error("Reference folder missing: %s", reference_folder)
|
||||
error_msg = (
|
||||
f"Reference video folder does not exist: {reference_folder}\n"
|
||||
f"To download reference videos, run:\n"
|
||||
f" python fastvideo/tests/ssim/reference_videos_cli.py download"
|
||||
)
|
||||
raise FileNotFoundError(error_msg)
|
||||
logger.error("Reference folder missing")
|
||||
raise FileNotFoundError(f"Reference video folder does not exist: {reference_folder}")
|
||||
|
||||
try:
|
||||
reference_video_path = _find_reference_video(reference_folder, prompt)
|
||||
|
||||
@@ -51,7 +51,11 @@ def _iter_video_files(root: Path) -> Iterable[Path]:
|
||||
def _discover_reference_dirs(base_dir: Path) -> list[Path]:
|
||||
if not base_dir.exists():
|
||||
return []
|
||||
dirs = [p for p in base_dir.iterdir() if p.is_dir() and p.name.endswith("_reference_videos")]
|
||||
dirs = [
|
||||
p
|
||||
for p in base_dir.iterdir()
|
||||
if p.is_dir() and p.name.endswith("_reference_videos")
|
||||
]
|
||||
return sorted(dirs)
|
||||
|
||||
|
||||
@@ -102,7 +106,10 @@ def _resolve_device_reference_folder(
|
||||
) -> str:
|
||||
if explicit_device_folder and explicit_device_folder.strip():
|
||||
return explicit_device_folder.strip()
|
||||
if reference_dir is not None and reference_dir.name.endswith("_reference_videos"):
|
||||
if (
|
||||
reference_dir is not None
|
||||
and reference_dir.name.endswith("_reference_videos")
|
||||
):
|
||||
return reference_dir.name
|
||||
return DEFAULT_DEVICE_REFERENCE_FOLDER
|
||||
|
||||
@@ -134,11 +141,6 @@ def _discover_reference_dirs_for_tier(
|
||||
|
||||
|
||||
def _has_local_reference_videos(base_dir: Path, quality_tier: str) -> bool:
|
||||
# Check for completion marker file first
|
||||
marker_path = base_dir / REFERENCE_VIDEOS_DIRNAME / quality_tier / f".download_complete_{quality_tier}"
|
||||
if not marker_path.exists():
|
||||
return False
|
||||
# Also verify at least one .mp4 exists
|
||||
for ref_dir in _discover_reference_dirs_for_tier(base_dir, quality_tier):
|
||||
for _ in _iter_video_files(ref_dir):
|
||||
return True
|
||||
@@ -159,7 +161,8 @@ def _load_hf_sdk():
|
||||
from huggingface_hub import HfApi, snapshot_download
|
||||
except ImportError as exc:
|
||||
raise RuntimeError(
|
||||
"huggingface_hub is required for download/upload.\nInstall with: pip install huggingface_hub"
|
||||
"huggingface_hub is required for download/upload.\n"
|
||||
"Install with: pip install huggingface_hub"
|
||||
) from exc
|
||||
return HfApi, snapshot_download
|
||||
|
||||
@@ -207,7 +210,9 @@ def download_reference_videos(
|
||||
for quality_tier in tiers:
|
||||
tier_prefix = f"{REFERENCE_VIDEOS_DIRNAME}/{quality_tier}"
|
||||
if device_folders:
|
||||
allow_patterns.extend(f"{tier_prefix}/{folder}/**" for folder in device_folders)
|
||||
allow_patterns.extend(
|
||||
f"{tier_prefix}/{folder}/**" for folder in device_folders
|
||||
)
|
||||
else:
|
||||
allow_patterns.append(f"{tier_prefix}/*_reference_videos/**")
|
||||
|
||||
@@ -237,12 +242,9 @@ def download_reference_videos(
|
||||
)
|
||||
if copied_roots == 0:
|
||||
raise RuntimeError(
|
||||
"HF download completed but no reference_videos content was found in downloaded artifacts."
|
||||
"HF download completed but no reference_videos content "
|
||||
"was found in downloaded artifacts."
|
||||
)
|
||||
for quality_tier in tiers:
|
||||
marker_path = local_dir / REFERENCE_VIDEOS_DIRNAME / quality_tier / f".download_complete_{quality_tier}"
|
||||
marker_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
marker_path.touch()
|
||||
|
||||
|
||||
def upload_reference_videos(
|
||||
@@ -264,8 +266,12 @@ def upload_reference_videos(
|
||||
|
||||
for quality_tier, reference_dir in reference_dirs_by_tier:
|
||||
if not reference_dir.exists():
|
||||
raise FileNotFoundError(f"Reference directory not found: {reference_dir}")
|
||||
path_in_repo = f"{REFERENCE_VIDEOS_DIRNAME}/{quality_tier}/{reference_dir.name}"
|
||||
raise FileNotFoundError(
|
||||
f"Reference directory not found: {reference_dir}"
|
||||
)
|
||||
path_in_repo = (
|
||||
f"{REFERENCE_VIDEOS_DIRNAME}/{quality_tier}/{reference_dir.name}"
|
||||
)
|
||||
print(f"Uploading {reference_dir.name} ({quality_tier}) to {repo_id} ...")
|
||||
api.upload_folder(
|
||||
repo_id=repo_id,
|
||||
@@ -288,14 +294,20 @@ def _resolve_upload_reference_dirs(
|
||||
|
||||
if explicit_reference_dirs:
|
||||
if len(tiers) != 1:
|
||||
raise RuntimeError("--reference-dir requires a single --quality-tier (default or full_quality).")
|
||||
raise RuntimeError(
|
||||
"--reference-dir requires a single --quality-tier "
|
||||
"(default or full_quality)."
|
||||
)
|
||||
resolved_tier = tiers[0]
|
||||
return [(resolved_tier, Path(p).resolve()) for p in explicit_reference_dirs]
|
||||
|
||||
resolved: list[tuple[str, Path]] = []
|
||||
for tier in tiers:
|
||||
for reference_dir in _discover_reference_dirs_for_tier(base_dir, tier):
|
||||
if selected_device_folders and reference_dir.name not in selected_device_folders:
|
||||
if (
|
||||
selected_device_folders
|
||||
and reference_dir.name not in selected_device_folders
|
||||
):
|
||||
continue
|
||||
resolved.append((tier, reference_dir.resolve()))
|
||||
return resolved
|
||||
@@ -315,38 +327,32 @@ def ensure_reference_videos_available(
|
||||
lock_path = target_dir / ".reference_videos_download.lock"
|
||||
with _exclusive_download_lock(lock_path):
|
||||
if _has_local_reference_videos(target_dir, quality_tier):
|
||||
print(f"Reference videos ({quality_tier}) already available at {target_dir}")
|
||||
return False
|
||||
|
||||
resolved_repo_id = repo_id or _default_repo_id()
|
||||
resolved_repo_type = repo_type or _default_repo_type()
|
||||
if not resolved_repo_id:
|
||||
raise RuntimeError(
|
||||
f"No local reference videos found and no HF repo configured.\nSet {HF_REPO_ENV_KEY} or pass --repo-id."
|
||||
"No local reference videos found and no HF repo configured.\n"
|
||||
f"Set {HF_REPO_ENV_KEY} or pass --repo-id."
|
||||
)
|
||||
|
||||
print(f"Repo ID: {resolved_repo_id}")
|
||||
print(f"Quality tier: {quality_tier}")
|
||||
print(f"No local {quality_tier} reference videos found under {target_dir}. Starting download...")
|
||||
try:
|
||||
download_reference_videos(
|
||||
repo_id=resolved_repo_id,
|
||||
repo_type=resolved_repo_type,
|
||||
local_dir=target_dir,
|
||||
quality_tiers=[quality_tier],
|
||||
)
|
||||
print(f"Download completed for {quality_tier} reference videos.")
|
||||
except Exception as exc:
|
||||
print(f"ERROR: Failed to download {quality_tier} reference videos from {resolved_repo_id}.")
|
||||
print(
|
||||
f"Suggested command to retry: "
|
||||
f"python fastvideo/tests/ssim/reference_videos_cli.py download "
|
||||
f"--quality-tier {quality_tier}"
|
||||
)
|
||||
raise
|
||||
print(
|
||||
f"No local {quality_tier} reference videos found under {target_dir}. "
|
||||
f"Downloading from HF repo {resolved_repo_id} ..."
|
||||
)
|
||||
download_reference_videos(
|
||||
repo_id=resolved_repo_id,
|
||||
repo_type=resolved_repo_type,
|
||||
local_dir=target_dir,
|
||||
quality_tiers=[quality_tier],
|
||||
)
|
||||
|
||||
if not _has_local_reference_videos(target_dir, quality_tier):
|
||||
raise RuntimeError(f"HF download completed but no {quality_tier} *_reference_videos content found.")
|
||||
raise RuntimeError(
|
||||
f"HF download completed but no {quality_tier} *_reference_videos "
|
||||
"content found."
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
@@ -391,12 +397,18 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
"--generated-dir",
|
||||
type=Path,
|
||||
default=None,
|
||||
help=("Source generated directory. Default: <ssim_dir>/generated_videos/<quality-tier>/<device-folder>"),
|
||||
help=(
|
||||
"Source generated directory. "
|
||||
"Default: <ssim_dir>/generated_videos/<quality-tier>/<device-folder>"
|
||||
),
|
||||
)
|
||||
copy_parser.add_argument(
|
||||
"--device-folder",
|
||||
default=None,
|
||||
help=("GPU folder name (e.g., H200_reference_videos) used to build default generated/reference paths."),
|
||||
help=(
|
||||
"GPU folder name (e.g., H200_reference_videos) used to build "
|
||||
"default generated/reference paths."
|
||||
),
|
||||
)
|
||||
copy_parser.add_argument(
|
||||
"--reference-dir",
|
||||
@@ -488,13 +500,19 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
"--device-folder",
|
||||
action="append",
|
||||
default=[],
|
||||
help=("Specific GPU reference folder to upload (repeatable), e.g. H200_reference_videos."),
|
||||
help=(
|
||||
"Specific GPU reference folder to upload (repeatable), "
|
||||
"e.g. H200_reference_videos."
|
||||
),
|
||||
)
|
||||
upload_parser.add_argument(
|
||||
"--base-dir",
|
||||
type=Path,
|
||||
default=_ssim_dir(),
|
||||
help=("Base SSIM directory that contains reference_videos/<quality-tier>/<GPU>_reference_videos."),
|
||||
help=(
|
||||
"Base SSIM directory that contains "
|
||||
"reference_videos/<quality-tier>/<GPU>_reference_videos."
|
||||
),
|
||||
)
|
||||
upload_parser.add_argument(
|
||||
"--private",
|
||||
@@ -520,7 +538,10 @@ def _build_parser() -> argparse.ArgumentParser:
|
||||
"--local-dir",
|
||||
type=Path,
|
||||
default=_ssim_dir(),
|
||||
help=("Local SSIM directory that should contain reference_videos/<quality-tier>/<GPU>_reference_videos."),
|
||||
help=(
|
||||
"Local SSIM directory that should contain "
|
||||
"reference_videos/<quality-tier>/<GPU>_reference_videos."
|
||||
),
|
||||
)
|
||||
ensure_parser.add_argument(
|
||||
"--quality-tier",
|
||||
@@ -556,20 +577,21 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
reference_dir = (
|
||||
args.reference_dir
|
||||
if args.reference_dir is not None
|
||||
else (_ssim_dir() / REFERENCE_VIDEOS_DIRNAME / args.quality_tier / device_folder)
|
||||
else (
|
||||
_ssim_dir()
|
||||
/ REFERENCE_VIDEOS_DIRNAME
|
||||
/ args.quality_tier
|
||||
/ device_folder
|
||||
)
|
||||
)
|
||||
copied = copy_generated_to_reference(
|
||||
generated_dir=generated_dir,
|
||||
reference_dir=reference_dir,
|
||||
dry_run=args.dry_run,
|
||||
)
|
||||
print(f"Done. {'Would copy' if args.dry_run else 'Copied'} {copied} video files.")
|
||||
if not args.dry_run and copied > 0:
|
||||
marker = (
|
||||
_ssim_dir() / REFERENCE_VIDEOS_DIRNAME / args.quality_tier / f".download_complete_{args.quality_tier}"
|
||||
)
|
||||
marker.parent.mkdir(parents=True, exist_ok=True)
|
||||
marker.touch()
|
||||
print(
|
||||
f"Done. {'Would copy' if args.dry_run else 'Copied'} {copied} video files."
|
||||
)
|
||||
return 0
|
||||
|
||||
if args.command == "download":
|
||||
@@ -581,14 +603,19 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
quality_tiers=quality_tiers,
|
||||
device_folders=args.device_folder or None,
|
||||
)
|
||||
print("Download complete for quality tier(s): " + ", ".join(quality_tiers) + ".")
|
||||
print(
|
||||
"Download complete for quality tier(s): "
|
||||
+ ", ".join(quality_tiers)
|
||||
+ "."
|
||||
)
|
||||
return 0
|
||||
|
||||
if args.command == "upload":
|
||||
token = _get_hf_token()
|
||||
if token is None:
|
||||
raise RuntimeError(
|
||||
"Hugging Face API key is required for upload. Set HF_API_KEY, HUGGINGFACE_HUB_TOKEN, or HF_TOKEN."
|
||||
"Hugging Face API key is required for upload. Set "
|
||||
"HF_API_KEY, HUGGINGFACE_HUB_TOKEN, or HF_TOKEN."
|
||||
)
|
||||
|
||||
reference_dirs_by_tier = _resolve_upload_reference_dirs(
|
||||
@@ -599,7 +626,8 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
)
|
||||
if not reference_dirs_by_tier:
|
||||
raise RuntimeError(
|
||||
f"No *_reference_videos directories found for the selected quality tier(s) under {args.base_dir}"
|
||||
"No *_reference_videos directories found for the selected "
|
||||
f"quality tier(s) under {args.base_dir}"
|
||||
)
|
||||
|
||||
upload_reference_videos(
|
||||
@@ -620,7 +648,10 @@ def main(argv: Sequence[str] | None = None) -> int:
|
||||
quality_tier=args.quality_tier,
|
||||
)
|
||||
if downloaded:
|
||||
print(f"{args.quality_tier} reference videos were missing and have been downloaded.")
|
||||
print(
|
||||
f"{args.quality_tier} reference videos were missing and "
|
||||
"have been downloaded."
|
||||
)
|
||||
else:
|
||||
print(f"{args.quality_tier} reference videos already exist locally.")
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user