517 lines
21 KiB
Python
517 lines
21 KiB
Python
import os
|
|
import re
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
import modal
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
try:
|
|
from modal_image_utils import ( # noqa: E402
|
|
resolve_image_ref, resolve_uv_torch_backend)
|
|
except ModuleNotFoundError:
|
|
# Remote Modal containers re-import this module but mount only the
|
|
# entrypoint file; the digest resolution already happened at local
|
|
# launch time, so a passthrough is correct there.
|
|
def resolve_image_ref(image_ref: str) -> str:
|
|
return image_ref
|
|
|
|
def resolve_uv_torch_backend(image_tag: str) -> str | None:
|
|
return os.environ.get("UV_TORCH_BACKEND")
|
|
|
|
|
|
app = modal.App()
|
|
|
|
model_vol = modal.Volume.from_name("hf-model-weights")
|
|
image_version = os.getenv("IMAGE_VERSION", "latest")
|
|
image_tag = f"ghcr.io/hao-ai-lab/fastvideo/fastvideo-dev:{image_version}"
|
|
image_ref = resolve_image_ref(image_tag)
|
|
print(f"Using image: {image_ref}")
|
|
|
|
# Mutable tags inherit the registry image's baked backend, keeping a latest-tag
|
|
# transition safe. Explicit CUDA tags also work with older images that predate
|
|
# the baked setting, and a caller override always wins.
|
|
uv_torch_backend_override = resolve_uv_torch_backend(image_tag)
|
|
|
|
# INVARIANT: this image definition must be byte-identical for every CI job at
|
|
# a given base image digest -- one build, shared cache across all concurrent
|
|
# lanes. Never put a per-job/per-commit value (BUILDKITE_*, TEST_SCOPE,
|
|
# env-derived overrides) into the image via .env()/run_commands: it becomes an
|
|
# image layer, so whenever the base digest changes every concurrent job
|
|
# rebuilds its own image variant (~15-20 min each), blowing the Buildkite job
|
|
# budget. `image_ref` is the only env-derived input allowed here, because it
|
|
# *selects* the base digest. Per-job values arrive at runtime via
|
|
# `ci_env_secret` below.
|
|
image = (modal.Image.from_registry(image_ref, 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",
|
|
"HF_REPO_ID": "FastVideo/performance-tracking",
|
|
}))
|
|
|
|
dreamverse_image = (image.run_commands("curl -fsSL https://deb.nodesource.com/setup_22.x | bash -").apt_install(
|
|
"nodejs").run_commands("node --version && npm --version"))
|
|
|
|
# Per-job/per-invocation values are injected into the container environment at
|
|
# RUNTIME via this secret (attached to every function below), so the image
|
|
# stays identical across jobs. Consumers (run_test_command's checkout,
|
|
# fastvideo/tests/performance/{compare_baseline,identity}.py, the FA4
|
|
# resolver, `uv pip install`) all read os.environ at runtime, so nothing else
|
|
# changes.
|
|
ci_env_secret = modal.Secret.from_dict({
|
|
"BUILDKITE_REPO":
|
|
os.environ.get("BUILDKITE_REPO", ""),
|
|
"BUILDKITE_COMMIT":
|
|
os.environ.get("BUILDKITE_COMMIT", ""),
|
|
"BUILDKITE_PULL_REQUEST":
|
|
os.environ.get("BUILDKITE_PULL_REQUEST", ""),
|
|
"BUILDKITE_BRANCH":
|
|
os.environ.get("BUILDKITE_BRANCH", ""),
|
|
"BUILDKITE_SOURCE":
|
|
os.environ.get("BUILDKITE_SOURCE", ""),
|
|
"BUILDKITE_BUILD_URL":
|
|
os.environ.get("BUILDKITE_BUILD_URL", ""),
|
|
"BUILDKITE_BUILD_ID":
|
|
os.environ.get("BUILDKITE_BUILD_ID", ""),
|
|
"BUILDKITE_JOB_ID":
|
|
os.environ.get("BUILDKITE_JOB_ID", ""),
|
|
"TEST_SCOPE":
|
|
os.environ.get("TEST_SCOPE", ""),
|
|
"IMAGE_VERSION":
|
|
image_version,
|
|
"FASTVIDEO_CONTAINER_IMAGE_REF":
|
|
image_ref,
|
|
**{
|
|
key: os.environ[key]
|
|
for key in (
|
|
"FASTVIDEO_ATTENTION_BACKEND",
|
|
"FASTVIDEO_PERFORMANCE_PROFILE_VERSION",
|
|
) if os.environ.get(key)
|
|
},
|
|
**({
|
|
"UV_TORCH_BACKEND": uv_torch_backend_override
|
|
} if uv_torch_backend_override else {}),
|
|
# FA4 is opt-in (FASTVIDEO_FA4). Keep the default enabled for
|
|
# inference/perf parity; model-load and training lanes that do not exercise
|
|
# FA4 explicitly set FASTVIDEO_FA4=0 in their command strings below.
|
|
# Caller override wins.
|
|
"FASTVIDEO_FA4":
|
|
os.environ.get("FASTVIDEO_FA4", "1"),
|
|
})
|
|
|
|
hf_secret = modal.Secret.from_dict({"HF_API_KEY": os.environ.get("HF_API_KEY", "")})
|
|
wandb_secret = modal.Secret.from_dict({"WANDB_API_KEY": os.environ.get("WANDB_API_KEY", "")})
|
|
|
|
|
|
def _run_git_with_retries(command: list[str], *, cwd: str, cleanup_path: str | None = None) -> None:
|
|
last_returncode = 1
|
|
for attempt in range(1, 4):
|
|
if cleanup_path is not None:
|
|
shutil.rmtree(cleanup_path, ignore_errors=True)
|
|
|
|
result = subprocess.run(command, cwd=cwd, check=False)
|
|
if result.returncode == 0:
|
|
return
|
|
|
|
last_returncode = result.returncode
|
|
if attempt < 3:
|
|
sleep_seconds = 5 * attempt
|
|
print(f"Git command failed (attempt {attempt}/3, exit {last_returncode}); "
|
|
f"retrying in {sleep_seconds}s",
|
|
flush=True)
|
|
time.sleep(sleep_seconds)
|
|
|
|
raise RuntimeError(f"Git command failed after 3 attempts with exit code {last_returncode}: " + " ".join(command))
|
|
|
|
|
|
def _checkout_repository(git_repo: str, git_commit: str, pr_number: str | None, repo_root: str = "/FastVideo") -> None:
|
|
if not git_repo or git_repo.startswith("-"):
|
|
raise RuntimeError("BUILDKITE_REPO must be a non-empty repository URL.")
|
|
|
|
if pr_number and pr_number != "false":
|
|
try:
|
|
pr_id = int(pr_number)
|
|
except ValueError as error:
|
|
raise RuntimeError(f"Invalid BUILDKITE_PULL_REQUEST value: {pr_number}") from error
|
|
if pr_id <= 0:
|
|
raise RuntimeError(f"Invalid BUILDKITE_PULL_REQUEST value: {pr_number}")
|
|
target = f"refs/pull/{pr_id}/head"
|
|
print(f"Using PR ref for checkout: {target}")
|
|
else:
|
|
if not git_commit or re.fullmatch(r"[0-9a-fA-F]{7,64}", git_commit) is None:
|
|
raise RuntimeError(f"Invalid BUILDKITE_COMMIT value: {git_commit}")
|
|
target = git_commit
|
|
print(f"Using direct commit checkout: {target}")
|
|
|
|
clone_command = [
|
|
"git",
|
|
"-c",
|
|
"http.version=HTTP/1.1",
|
|
"clone",
|
|
"--config",
|
|
"http.version=HTTP/1.1",
|
|
"--depth=1",
|
|
"--filter=blob:none",
|
|
"--no-checkout",
|
|
git_repo,
|
|
repo_root,
|
|
]
|
|
_run_git_with_retries(clone_command, cwd="/", cleanup_path=repo_root)
|
|
|
|
git_prefix = ["git", "-c", "http.version=HTTP/1.1"]
|
|
_run_git_with_retries(git_prefix + [
|
|
"fetch",
|
|
"--prune",
|
|
"--no-tags",
|
|
"--depth=1",
|
|
"--filter=blob:none",
|
|
"origin",
|
|
target,
|
|
],
|
|
cwd=repo_root)
|
|
_run_git_with_retries(git_prefix + ["checkout", "--detach", "FETCH_HEAD"], cwd=repo_root)
|
|
_run_git_with_retries(git_prefix + ["submodule", "update", "--init", "--recursive"], cwd=repo_root)
|
|
|
|
|
|
def run_test(pytest_command: str):
|
|
"""Helper function to run a test suite with custom pytest command"""
|
|
run_test_command(pytest_command, build_kernel=True)
|
|
|
|
|
|
def run_test_command(test_command: str, build_kernel: bool, install_command: str = 'uv pip install -e ".[test]"'):
|
|
"""Helper function to run a test suite with custom test command.
|
|
|
|
Most FastVideo CI suites need the custom kernel build. App-level tests like
|
|
DreamVerse's mock-backend UI checks do not, so keep the kernel build
|
|
optional to avoid unrelated CUDA/kernel setup in that CI path.
|
|
|
|
The dependency install runs BEFORE the kernel build: pyproject pins the
|
|
PyPI fastvideo-kernel wheel, so an install after the build silently
|
|
replaces the just-built in-tree kernel with the (older) wheel -- every
|
|
lane would then test stale kernels. Pass install_command="" for commands
|
|
that manage their own installs.
|
|
"""
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
|
|
git_repo = os.environ.get("BUILDKITE_REPO", "")
|
|
git_commit = os.environ.get("BUILDKITE_COMMIT", "")
|
|
pr_number = os.environ.get("BUILDKITE_PULL_REQUEST")
|
|
|
|
print(f"Cloning repository: {git_repo}")
|
|
print(f"Target commit: {git_commit}")
|
|
if pr_number:
|
|
print(f"PR number: {pr_number}")
|
|
_checkout_repository(git_repo, git_commit, pr_number)
|
|
|
|
setup_steps = [
|
|
"source $HOME/.local/bin/env",
|
|
"source /opt/venv/bin/activate",
|
|
"cd /FastVideo",
|
|
]
|
|
if install_command:
|
|
setup_steps.append(install_command)
|
|
if build_kernel:
|
|
setup_steps.append("python fastvideo/tests/modal/kernel_build_cache.py install")
|
|
setup_command = " &&\n ".join(setup_steps)
|
|
|
|
setup_result = subprocess.run(["/bin/bash", "-c", setup_command], stdout=sys.stdout, stderr=sys.stderr, check=False)
|
|
if setup_result.returncode != 0:
|
|
raise RuntimeError(f"Setup command failed with exit code {setup_result.returncode}")
|
|
|
|
command = " &&\n ".join([
|
|
"source $HOME/.local/bin/env",
|
|
"source /opt/venv/bin/activate",
|
|
"cd /FastVideo",
|
|
test_command,
|
|
])
|
|
result = subprocess.run(["/bin/bash", "-c", command], stdout=sys.stdout, stderr=sys.stderr, check=False)
|
|
|
|
# Modal containers crash on sys.exit(0); raise on failure, return on success.
|
|
if result.returncode != 0:
|
|
raise RuntimeError(f"Test command failed with exit code {result.returncode}")
|
|
|
|
|
|
@app.function(gpu="H100:1",
|
|
image=image,
|
|
timeout=1200,
|
|
secrets=[hf_secret, ci_env_secret],
|
|
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=[hf_secret, ci_env_secret],
|
|
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=[hf_secret, ci_env_secret],
|
|
volumes={"/root/data": model_vol})
|
|
def run_golden_gate_tests():
|
|
# Single-layer bitwise DiT fingerprints (~40s/model on GPU): a green gate
|
|
# means the compute path is bit-identical to the golden, so the expensive
|
|
# SSIM generation for that model cannot have regressed. Downloads only the
|
|
# shards holding the gated layer, never full checkpoints.
|
|
run_test(
|
|
"export HF_HOME='/root/data/.cache' && hf auth login --token $HF_API_KEY && pytest ./fastvideo/tests/golden_gate -vs"
|
|
)
|
|
|
|
|
|
@app.function(gpu="L40S:1",
|
|
image=image,
|
|
timeout=900,
|
|
secrets=[hf_secret, ci_env_secret],
|
|
volumes={"/root/data": model_vol})
|
|
def run_transformer_tests():
|
|
run_test("export HF_HOME='/root/data/.cache' && hf auth login --token $HF_API_KEY && "
|
|
"FASTVIDEO_FA4=0 pytest ./fastvideo/tests/transformers -vs")
|
|
|
|
|
|
@app.function(gpu="L40S:4",
|
|
cpu=8.0,
|
|
memory=32768,
|
|
image=image,
|
|
timeout=900,
|
|
secrets=[wandb_secret, ci_env_secret],
|
|
volumes={"/root/data": model_vol})
|
|
def run_training_tests():
|
|
run_test("export HF_HOME='/root/data/.cache' && wandb login $WANDB_API_KEY && "
|
|
"FASTVIDEO_FA4=0 pytest ./fastvideo/tests/training/Vanilla -srP")
|
|
|
|
|
|
@app.function(gpu="L40S:2",
|
|
cpu=8.0,
|
|
memory=32768,
|
|
image=image,
|
|
timeout=900,
|
|
secrets=[wandb_secret, ci_env_secret],
|
|
volumes={"/root/data": model_vol})
|
|
def run_training_lora_tests():
|
|
run_test("export HF_HOME='/root/data/.cache' && wandb login $WANDB_API_KEY && "
|
|
"FASTVIDEO_FA4=0 pytest ./fastvideo/tests/training/lora/test_lora_training.py -srP")
|
|
|
|
|
|
@app.function(gpu="H100!:2", image=image, timeout=900, secrets=[wandb_secret, ci_env_secret])
|
|
def run_training_tests_VSA():
|
|
run_test("wandb login $WANDB_API_KEY && FASTVIDEO_FA4=0 pytest ./fastvideo/tests/training/VSA -srP")
|
|
|
|
|
|
@app.function(gpu="H100:1", image=image, timeout=900, secrets=[ci_env_secret])
|
|
def run_kernel_tests():
|
|
run_test("pytest fastvideo-kernel/tests/ -vs")
|
|
|
|
|
|
# @app.function(gpu="H100:1", image=image, timeout=900, secrets=[ci_env_secret])
|
|
# def run_precision_tests_VSA():
|
|
# # VSA correctness is covered by the same file now
|
|
# run_test("pytest fastvideo-kernel/tests/test_correctness.py")
|
|
|
|
# @app.function(gpu="L40S:1", image=image, timeout=900, secrets=[ci_env_secret])
|
|
# def run_precision_tests_vmoba():
|
|
# run_test("pytest fastvideo-kernel/tests/test_vmoba_correctness.py")
|
|
|
|
|
|
@app.function(gpu="L40S:1", image=image, timeout=900, secrets=[ci_env_secret])
|
|
def run_inference_tests_vmoba():
|
|
run_test('python fastvideo/tests/inference/vmoba/test_vmoba_inference.py')
|
|
|
|
|
|
@app.function(gpu="L40S:1", image=image, timeout=1200, secrets=[ci_env_secret])
|
|
def run_inference_lora_tests():
|
|
run_test("pytest ./fastvideo/tests/inference/lora/test_lora_inference_similarity.py -vs")
|
|
|
|
|
|
@app.function(gpu="L40S:2", image=image, timeout=900, secrets=[ci_env_secret])
|
|
def run_distill_dmd_tests():
|
|
run_test("FASTVIDEO_FA4=0 pytest ./fastvideo/tests/training/distill/test_distill_dmd.py -vs")
|
|
|
|
|
|
@app.function(gpu="L40S:2", image=image, timeout=900, secrets=[wandb_secret, ci_env_secret])
|
|
def run_self_forcing_tests():
|
|
run_test("wandb login $WANDB_API_KEY && "
|
|
"FASTVIDEO_FA4=0 pytest ./fastvideo/tests/training/self-forcing/test_self_forcing.py -vs")
|
|
|
|
|
|
@app.function(gpu="L40S:1", image=image, timeout=900, secrets=[ci_env_secret])
|
|
def run_unit_test():
|
|
run_test("pytest ./fastvideo/tests/api/ ./fastvideo/tests/contract/ ./fastvideo/tests/dataset/ "
|
|
"./fastvideo/tests/workflow/ ./fastvideo/tests/entrypoints/ ./fastvideo/tests/train/ "
|
|
"./fastvideo/tests/stages/ ./fastvideo/tests/ops/ ./fastvideo/tests/worker/ "
|
|
"./fastvideo/tests/training/test_trackers.py "
|
|
"./fastvideo/tests/attention/test_sdpa_metadata_mask_contract.py "
|
|
"./fastvideo/tests/modal/test_kernel_build_cache.py ./fastvideo/tests/modal/test_pr_test.py "
|
|
"./fastvideo/tests/modal/test_ssim_test.py "
|
|
"--ignore=./fastvideo/tests/entrypoints/test_openai_api_integration.py "
|
|
"--ignore=./fastvideo/tests/train/models --ignore=./fastvideo/tests/train/methods -vs")
|
|
|
|
|
|
# TODO: David: GPU only used to resolve import time requirement (not needed for this test). Maybe make those imports lazy?
|
|
@app.function(gpu="L40S:1", image=dreamverse_image, timeout=1800, secrets=[ci_env_secret])
|
|
def run_dreamverse_app_tests():
|
|
run_test_command(install_command="",
|
|
build_kernel=False,
|
|
test_command="""
|
|
uv pip install -e ".[test,dreamverse]" &&
|
|
export PYTHONPATH=/FastVideo/apps/dreamverse:$PYTHONPATH &&
|
|
pytest apps/dreamverse/dreamverse/tests -q &&
|
|
cd apps/dreamverse/web &&
|
|
npm ci &&
|
|
npm run typecheck &&
|
|
npm test &&
|
|
npx playwright install --with-deps chromium webkit firefox &&
|
|
bash -c '
|
|
set -e
|
|
BACKEND_PORT="${BACKEND_PORT:-8009}"
|
|
python -m uvicorn dreamverse.mock_server:app --host 127.0.0.1 --port "$BACKEND_PORT" &
|
|
MOCK_SERVER_PID=$!
|
|
trap "kill $MOCK_SERVER_PID 2>/dev/null || true" EXIT
|
|
for i in {1..30}; do
|
|
curl -fsS "http://127.0.0.1:$BACKEND_PORT/healthz" && break
|
|
sleep 1
|
|
done
|
|
curl -fsS "http://127.0.0.1:$BACKEND_PORT/healthz"
|
|
BACKEND_HOST=127.0.0.1 BACKEND_PORT="$BACKEND_PORT" CI=1 \
|
|
npm run e2e -- \
|
|
--project=chromium \
|
|
--project=webkit \
|
|
--project=firefox \
|
|
--project=mobile-safari \
|
|
--project=mobile-chromium
|
|
'
|
|
""")
|
|
|
|
|
|
@app.function(gpu="L40S:1",
|
|
cpu=8.0,
|
|
memory=32768,
|
|
image=image,
|
|
timeout=1800,
|
|
secrets=[hf_secret, ci_env_secret],
|
|
volumes={"/root/data": model_vol})
|
|
def run_train_framework_tests():
|
|
run_test("export HF_HOME='/root/data/.cache' && hf auth login --token $HF_API_KEY && "
|
|
"FASTVIDEO_FA4=0 pytest ./fastvideo/tests/train/models ./fastvideo/tests/train/methods -vs")
|
|
|
|
|
|
@app.function(gpu="L40S:1",
|
|
image=image,
|
|
timeout=1800,
|
|
secrets=[hf_secret, ci_env_secret],
|
|
volumes={"/root/data": model_vol})
|
|
def seed_grad_norm_references():
|
|
"""Record the per-method grad-norm reference for the **CI GPU (L40S only)**.
|
|
|
|
Phase 2 / 5a-ii one-off seeding entrypoint. Pinned to ``gpu="L40S:1"`` (the
|
|
Modal CI runner), so this function only seeds the ``L40S`` key in
|
|
``fastvideo/tests/train/methods/grad_norm_refs.json``.
|
|
|
|
``FASTVIDEO_GRADNORM_UPDATE=1`` makes ``check_grad_norm_regression`` record
|
|
the measured norm instead of asserting; ``-rs`` surfaces the recorded value
|
|
in the log so it can be copied into the JSON.
|
|
|
|
To seed any other device (e.g. our local Blackwell dev box → ``GB200``
|
|
key), run the same env-var + pytest invocation directly on that
|
|
workstation — see the module docstring of ``grad_norm_regression.py`` for
|
|
the local command and the ``_DEVICE_MAPPINGS`` table.
|
|
"""
|
|
run_test("export HF_HOME='/root/data/.cache' && hf auth login --token $HF_API_KEY && "
|
|
"FASTVIDEO_FA4=0 FASTVIDEO_GRADNORM_UPDATE=1 pytest ./fastvideo/tests/train/methods -vs -rs")
|
|
|
|
|
|
@app.function(gpu="L40S:1",
|
|
image=image,
|
|
timeout=3600,
|
|
secrets=[hf_secret, ci_env_secret],
|
|
volumes={"/root/data": model_vol})
|
|
def run_eval_tests():
|
|
# Eval metric regression: drives the high-level fastvideo.eval API on a
|
|
# fixed asset and asserts each score matches the upstream reference number
|
|
# checked into fastvideo/tests/eval/reference_scores/. Pulls several scorer
|
|
# checkpoints (VideoScore2 VLM, VBench nets, audio models) on first run;
|
|
# they cache on the hf-model-weights volume thereafter.
|
|
#
|
|
# Installs [eval-full] (eval + vbench + audio extras) on top of [test]:
|
|
# the dev image only ships [dev], and without the extras skip_missing_deps
|
|
# in conftest would silently drop nearly every metric and the lane would
|
|
# pass vacuously. detectron2-backed vbench metrics remain skipped by
|
|
# design (not pip-installable; see fastvideo/eval/README.md).
|
|
run_test_command(
|
|
"export HF_HOME='/root/data/.cache' && hf auth login --token $HF_API_KEY && pytest ./fastvideo/tests/eval -vs",
|
|
build_kernel=True,
|
|
install_command='uv pip install -e ".[test,eval-full]"')
|
|
|
|
|
|
@app.function(gpu="L40S:1", image=image, timeout=3600, secrets=[hf_secret, ci_env_secret])
|
|
def run_lora_extraction_tests():
|
|
run_test("hf auth login --token $HF_API_KEY && pytest ./fastvideo/tests/lora_extraction/test_lora_extraction.py")
|
|
|
|
|
|
@app.function(gpu="L40S:2",
|
|
cpu=8.0,
|
|
memory=32768,
|
|
image=image,
|
|
timeout=1800,
|
|
secrets=[hf_secret, ci_env_secret],
|
|
volumes={"/root/data": model_vol})
|
|
def run_performance_tests():
|
|
# PR/direct records are uploaded only on pass; scheduled main uploads pass
|
|
# and fail so the dashboard records every canonical baseline attempt.
|
|
run_test(
|
|
"export HF_HOME='/root/data/.cache' && "
|
|
"export PERFORMANCE_TRACKING_ROOT='/tmp/perf-tracking' && "
|
|
"hf auth login --token $HF_API_KEY && "
|
|
"if [ -n \"${BUILDKITE_PULL_REQUEST:-}\" ] && [ \"${BUILDKITE_PULL_REQUEST:-false}\" != 'false' ]; then "
|
|
"export PERF_RUN_SOURCE='pr'; "
|
|
"export PERF_UPLOAD_POLICY='pass'; "
|
|
"elif [ \"${BUILDKITE_BRANCH:-}\" = 'main' ] && ( [ \"${BUILDKITE_SOURCE:-}\" = 'schedule' ] || [ \"${TEST_SCOPE:-}\" = 'full' ] ); then "
|
|
"export PERF_RUN_SOURCE='scheduled_main'; "
|
|
"export PERF_UPLOAD_POLICY='always'; "
|
|
"elif [ \"${TEST_SCOPE:-}\" = 'direct' ]; then "
|
|
"export PERF_RUN_SOURCE='unknown'; "
|
|
"export PERF_UPLOAD_POLICY='pass'; "
|
|
"else "
|
|
"export PERF_RUN_SOURCE='unknown'; "
|
|
"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 > /tmp/gpu_telemetry.csv 2>/dev/null &); "
|
|
"pytest ./fastvideo/tests/performance -vs; "
|
|
"PYTEST_RC=$?; "
|
|
"PERF_RC=0; "
|
|
"if [ $PYTEST_RC -eq 0 ] || [ \"$PERF_UPLOAD_POLICY\" = 'always' ]; then "
|
|
"PERF_PYTEST_RC=$PYTEST_RC python ./fastvideo/tests/performance/compare_baseline.py; "
|
|
"PERF_RC=$?; "
|
|
"fi; "
|
|
"python ./fastvideo/tests/performance/dashboard.py || true; "
|
|
"echo '--- GPU telemetry (clocks.sm vs clocks.max.sm reveals capped hosts) ---'; "
|
|
"cat /tmp/gpu_telemetry.csv || true; "
|
|
"FINAL_RC=$PYTEST_RC; "
|
|
"if [ $FINAL_RC -eq 0 ]; then FINAL_RC=$PERF_RC; fi; "
|
|
"exit $FINAL_RC")
|
|
|
|
|
|
@app.function(gpu="L40S:1",
|
|
image=image,
|
|
timeout=1800,
|
|
secrets=[hf_secret, ci_env_secret],
|
|
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"
|
|
)
|