[feat]: add selectable Modal and GB200 Kubernetes GPU CI

- Preserve existing CI and add configurable GPU backend routing
- Limit execution to 2 active PRs, 4 GPUs per PR, and 8 GPUs total
- Add persistent admission, cancellation recovery, and backend statuses
- Document deployment and add infrastructure regression tests
This commit is contained in:
Satyam Srivastava
2026-10-01 14:44:14 -07:00
parent e1f3904799
commit e71d01648c
22 changed files with 2924 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
"""Trusted, opt-in GPU CI dispatch. Never import this package from a PR checkout."""
+98
View File
@@ -0,0 +1,98 @@
"""Operator CLI. Use the isolated entrypoint from an immutable installation."""
from __future__ import annotations
import argparse
import json
import os
import subprocess
import sys
import threading
from pathlib import Path
from typing import Any
from .dispatcher import (cancellation_signals, execute, make_store, recover,
run_directory, upload_artifacts)
from .pipeline import render_pipeline
from .policy import DEFAULT_REPOSITORY, backend_name, build_request
ROOT = Path(__file__).resolve().parents[2]
def load_config(path: Path) -> dict[str, Any]:
config = json.loads(path.read_text())
if not isinstance(config, dict):
raise ValueError("Operator config must be a JSON object")
for key in ("state_path", "artifacts_dir"):
value = config.get(key)
if not isinstance(value, str) or not Path(value).is_absolute():
raise ValueError(f"{key} must be an absolute local path")
for key, default in (("max_active_prs", 2), ("max_gpus_per_pr", 4), ("max_gpus", 8),
("queue_timeout_seconds", 21600)):
value = config.get(key, default)
if type(value) is not int or value < 1:
raise ValueError(f"{key} must be a positive integer")
config[key] = value
# The initial rollout deliberately cannot raise the user's agreed ceilings.
for key, ceiling in (("max_active_prs", 2), ("max_gpus_per_pr", 4), ("max_gpus", 8)):
if config[key] > ceiling:
raise ValueError(f"{key} exceeds the reviewed ceiling {ceiling}")
if config.get("repository", DEFAULT_REPOSITORY) != DEFAULT_REPOSITORY:
raise ValueError("This worker installation is allowlisted only for hao-ai-lab/FastVideo")
backend_name(config.get("default_backend"))
return config
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(dest="command", required=True)
for name in ("run", "render", "upload", "status", "recover"):
sub = subparsers.add_parser(name)
sub.add_argument("--config", type=Path, required=True)
if name in ("render", "upload"):
sub.add_argument("--canonical", type=Path, default=ROOT / ".buildkite/pipeline.yml")
if name == "recover":
sub.add_argument("--build-id", required=True)
args = parser.parse_args(argv)
try:
config = load_config(args.config)
if args.command == "status":
print(json.dumps(make_store(config).snapshot(), indent=2))
return 0
if args.command == "recover":
recover(config, args.build_id)
return 0
backend = backend_name(os.environ.get("CI_GPU_BACKEND"), config.get("default_backend", "slurm"))
if args.command == "upload" and backend == "slurm":
if config.get("default_backend", "slurm") != "slurm":
raise ValueError("Slurm overrides are disabled after promoting another backend: legacy statuses are unqualified")
legacy = config.get("slurm_uploader")
if (not isinstance(legacy, list) or not legacy or
not all(isinstance(x, str) and x for x in legacy) or not Path(legacy[0]).is_absolute()):
raise ValueError("Configure slurm_uploader as the existing trusted uploader argv")
return subprocess.run(legacy, check=False).returncode
if args.command in ("render", "upload"):
import yaml
canonical = yaml.safe_load(args.canonical.read_text())
rendered = render_pipeline(canonical, backend, os.environ.get("TEST_SCOPE", ""),
config.get("queue", "gpu-ci-dispatch"))
payload = json.dumps(rendered, indent=2) + "\n"
if args.command == "render":
print(payload, end="")
return 0
return subprocess.run(["buildkite-agent", "pipeline", "upload", "--no-interpolation"],
input=payload, text=True, check=False).returncode
request = build_request(os.environ, config, ROOT)
cancel = threading.Event()
with cancellation_signals(cancel):
code = execute(request, config, cancel=cancel)
if config.get("upload_artifacts", True):
upload_artifacts(run_directory(config, request["build_id"]))
return code
except (OSError, ValueError, RuntimeError, KeyError, subprocess.SubprocessError) as error:
print(f"GPU CI dispatcher error: {error}", file=sys.stderr)
return 97
if __name__ == "__main__":
raise SystemExit(main())
+307
View File
@@ -0,0 +1,307 @@
# SPDX-License-Identifier: Apache-2.0
"""Persistent GPU admission for cooperating processes on one trusted host.
Keep this SQLite database on local disk, not a shared/network filesystem.
Reservations include backend handles before external creation. Neither stale
heartbeats nor cancellation free active reservations: the caller must stop the
backend worker, confirm it stopped, and then call ``release``. The dispatcher
must also serialize creation and recovery for each build outside this ledger.
"""
from __future__ import annotations
import json
import sqlite3
import time
from collections.abc import Iterator
from contextlib import contextmanager
from pathlib import Path
from typing import Any
class AdmissionStore:
"""Bound distinct PRs and GPU reservations across all selected backends.
``pr_key`` identifies a repository and PR together. ``None`` or an empty
string gives a non-PR build its own slot. Once admitted, all unfinished
builds for a PR share and retain its slot, even between lanes. Completed
build and lane records remain as tombstones; retries must use new lane IDs.
"""
def __init__(self, path: str | Path, max_prs: int = 2, max_gpus_per_pr: int = 4, max_gpus: int = 8):
for name, value in (("max_prs", max_prs), ("max_gpus_per_pr", max_gpus_per_pr), ("max_gpus", max_gpus)):
if type(value) is not int or value < 1:
raise ValueError(f"{name} must be a positive integer")
if str(path) == ":memory:":
raise ValueError("Admission requires a persistent database on local disk")
self.path = Path(path)
self.path.parent.mkdir(parents=True, exist_ok=True)
self.max_prs = max_prs
self.max_gpus_per_pr = max_gpus_per_pr
self.max_gpus = max_gpus
with self._transaction() as connection:
connection.execute("""
CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY CHECK (id = 1),
schema_version INTEGER NOT NULL,
max_prs INTEGER NOT NULL,
max_gpus_per_pr INTEGER NOT NULL,
max_gpus INTEGER NOT NULL
)
""")
connection.execute("INSERT OR IGNORE INTO settings VALUES (1, 1, ?, ?, ?)",
(max_prs, max_gpus_per_pr, max_gpus))
settings = connection.execute("SELECT * FROM settings WHERE id = 1").fetchone()
expected = (1, max_prs, max_gpus_per_pr, max_gpus)
actual = tuple(settings[name] for name in ("schema_version", "max_prs", "max_gpus_per_pr", "max_gpus"))
if actual != expected:
raise ValueError("Admission database schema or limits conflict with this process")
connection.execute("""
CREATE TABLE IF NOT EXISTS builds (
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
build_id TEXT NOT NULL UNIQUE,
pr_key TEXT,
workload_key TEXT NOT NULL,
backend TEXT NOT NULL,
commit_sha TEXT NOT NULL,
state TEXT NOT NULL CHECK (state IN ('queued', 'admitted', 'finished')),
cancelled INTEGER NOT NULL DEFAULT 0 CHECK (cancelled IN (0, 1)),
created_at REAL NOT NULL,
heartbeat_at REAL NOT NULL,
admitted_at REAL,
finished_at REAL
)
""")
connection.execute("""
CREATE TABLE IF NOT EXISTS allocations (
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
build_id TEXT NOT NULL REFERENCES builds(build_id),
lane_id TEXT NOT NULL,
gpus INTEGER NOT NULL CHECK (gpus > 0),
handle_json TEXT NOT NULL,
state TEXT NOT NULL CHECK (state IN ('waiting', 'active', 'released', 'cancelled')),
created_at REAL NOT NULL,
acquired_at REAL,
released_at REAL,
UNIQUE (build_id, lane_id)
)
""")
connection.execute("CREATE INDEX IF NOT EXISTS builds_workload ON builds(workload_key, state)")
connection.execute("CREATE INDEX IF NOT EXISTS allocations_state ON allocations(state, sequence)")
@contextmanager
def _transaction(self) -> Iterator[sqlite3.Connection]:
connection = sqlite3.connect(str(self.path), timeout=30, isolation_level=None)
connection.row_factory = sqlite3.Row
try:
connection.execute("PRAGMA foreign_keys = ON")
connection.execute("BEGIN IMMEDIATE")
yield connection
connection.commit()
except BaseException:
connection.rollback()
raise
finally:
connection.close()
@staticmethod
def _identifier(value: str, name: str) -> None:
if not isinstance(value, str) or not value.strip():
raise ValueError(f"{name} must be a nonempty string")
@staticmethod
def _build(connection: sqlite3.Connection, build_id: str) -> sqlite3.Row:
build = connection.execute("SELECT * FROM builds WHERE build_id = ?", (build_id, )).fetchone()
if build is None:
raise KeyError(f"Unknown build: {build_id}")
return build
def register(self, build_id: str, pr_key: str | None, backend: str, commit: str) -> None:
"""Register once; an identical duplicate never resets state or queue age."""
for name, value in (("build_id", build_id), ("backend", backend), ("commit", commit)):
self._identifier(value, name)
if pr_key is not None and not isinstance(pr_key, str):
raise ValueError("pr_key must be a string or None")
pr_key = pr_key or None
if pr_key is not None:
self._identifier(pr_key, "pr_key")
workload_key = f"pr:{pr_key}" if pr_key is not None else f"build:{build_id}"
with self._transaction() as connection:
existing = connection.execute("SELECT * FROM builds WHERE build_id = ?", (build_id, )).fetchone()
if existing is not None:
if (existing["pr_key"], existing["backend"], existing["commit_sha"]) != (pr_key, backend, commit):
raise ValueError(f"Build identity changed: {build_id}")
return
active = connection.execute("SELECT 1 FROM builds WHERE workload_key = ? AND state = 'admitted'",
(workload_key, )).fetchone()
now = time.time()
connection.execute("""
INSERT INTO builds (build_id, pr_key, workload_key, backend, commit_sha,
state, created_at, heartbeat_at, admitted_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""", (build_id, pr_key, workload_key, backend, commit,
"admitted" if active else "queued", now, now, now if active else None))
def try_admit(self, build_id: str) -> bool:
"""Admit the oldest waiting PRs, reserving space for earlier waiters."""
with self._transaction() as connection:
build = self._build(connection, build_id)
if build["cancelled"] or build["state"] == "finished":
return False
if build["state"] == "admitted":
return True
active_count = connection.execute(
"SELECT COUNT(DISTINCT workload_key) FROM builds WHERE state = 'admitted'").fetchone()[0]
available = self.max_prs - active_count
if available <= 0:
return False
eligible = connection.execute("""
SELECT workload_key FROM builds WHERE state = 'queued' AND cancelled = 0
GROUP BY workload_key ORDER BY MIN(sequence) LIMIT ?
""", (available, )).fetchall()
if build["workload_key"] not in {row["workload_key"] for row in eligible}:
return False
connection.execute("""
UPDATE builds SET state = 'admitted', admitted_at = ?
WHERE workload_key = ? AND state = 'queued' AND cancelled = 0
""", (time.time(), build["workload_key"]))
return True
def try_acquire(self, build_id: str, lane_id: str, gpus: int, handle: dict[str, Any]) -> bool:
"""Persist a handle and reserve GPUs before the caller creates a worker.
Pending requests keep FIFO order. A PR whose oldest request cannot fit
its own budget does not block the other PR. Once a request fits its PR
budget it waits for global capacity without smaller requests bypassing
it. Repeating an active request returns True with the same reservation;
released/cancelled lane IDs cannot acquire again.
"""
self._identifier(lane_id, "lane_id")
if type(gpus) is not int or not 1 <= gpus <= min(self.max_gpus_per_pr, self.max_gpus):
raise ValueError("Requested GPUs must fit both the per-PR and total GPU limits")
if not isinstance(handle, dict):
raise ValueError("Backend handle must be a JSON object")
try:
encoded_handle = json.dumps(handle, sort_keys=True, separators=(",", ":"), allow_nan=False)
except (TypeError, ValueError) as error:
raise ValueError("Backend handle must be JSON serializable") from error
with self._transaction() as connection:
build = self._build(connection, build_id)
allocation = connection.execute("SELECT * FROM allocations WHERE build_id = ? AND lane_id = ?",
(build_id, lane_id)).fetchone()
if allocation is not None:
if allocation["gpus"] != gpus or allocation["handle_json"] != encoded_handle:
raise ValueError(f"Lane reservation identity changed: {build_id}/{lane_id}")
if build["cancelled"] or build["state"] != "admitted":
return False
if allocation is not None and allocation["state"] != "waiting":
return allocation["state"] == "active"
if allocation is None:
connection.execute("""
INSERT INTO allocations (build_id, lane_id, gpus, handle_json, state, created_at)
VALUES (?, ?, ?, ?, 'waiting', ?)
""", (build_id, lane_id, gpus, encoded_handle, time.time()))
usage = connection.execute("""
SELECT b.workload_key, SUM(a.gpus) AS gpus FROM allocations a
JOIN builds b ON b.build_id = a.build_id WHERE a.state = 'active'
GROUP BY b.workload_key
""").fetchall()
gpu_usage = {row["workload_key"]: row["gpus"] for row in usage}
total_gpus = sum(gpu_usage.values())
waiting = connection.execute("""
SELECT a.*, b.workload_key FROM allocations a
JOIN builds b ON b.build_id = a.build_id
WHERE a.state = 'waiting' AND b.state = 'admitted' AND b.cancelled = 0
ORDER BY a.sequence
""").fetchall()
seen_workloads = set()
for request in waiting:
workload_key = request["workload_key"]
if workload_key in seen_workloads:
continue
seen_workloads.add(workload_key)
if gpu_usage.get(workload_key, 0) + request["gpus"] > self.max_gpus_per_pr:
continue
if (request["build_id"], request["lane_id"]) != (build_id, lane_id):
return False
if total_gpus + gpus > self.max_gpus:
return False
connection.execute("""
UPDATE allocations SET state = 'active', acquired_at = ?
WHERE build_id = ? AND lane_id = ?
""", (time.time(), build_id, lane_id))
return True
return False
def release(self, build_id: str, lane_id: str) -> None:
"""Release only after the caller confirms the worker has stopped.
Missing/already released reservations are harmless for reconciliation.
This method does not stop workers or infer their status from timestamps.
"""
with self._transaction() as connection:
self._build(connection, build_id)
connection.execute("""
UPDATE allocations SET state = 'released', released_at = ?
WHERE build_id = ? AND lane_id = ? AND state IN ('active', 'waiting')
""", (time.time(), build_id, lane_id))
def finish(self, build_id: str) -> None:
"""Finish or cancel a queued build, refusing any active reservations."""
with self._transaction() as connection:
build = self._build(connection, build_id)
if build["state"] == "finished":
return
active = connection.execute("SELECT 1 FROM allocations WHERE build_id = ? AND state = 'active'",
(build_id, )).fetchone()
if active is not None:
raise RuntimeError(f"Cannot finish build with active GPU allocations: {build_id}")
now = time.time()
connection.execute("""
UPDATE allocations SET state = 'cancelled', released_at = ?
WHERE build_id = ? AND state = 'waiting'
""", (now, build_id))
connection.execute("UPDATE builds SET state = 'finished', finished_at = ? WHERE build_id = ?",
(now, build_id))
def heartbeat(self, build_id: str) -> None:
"""Record liveness for operators; no timeout ever frees a reservation."""
with self._transaction() as connection:
self._build(connection, build_id)
connection.execute("UPDATE builds SET heartbeat_at = ? WHERE build_id = ? AND state != 'finished'",
(time.time(), build_id))
def request_cancel(self, build_id: str) -> None:
"""Block further admission/launch reservations without freeing GPUs."""
with self._transaction() as connection:
self._build(connection, build_id)
connection.execute("UPDATE builds SET cancelled = 1 WHERE build_id = ?", (build_id, ))
connection.execute("""
UPDATE allocations SET state = 'cancelled', released_at = ?
WHERE build_id = ? AND state = 'waiting'
""", (time.time(), build_id))
def is_cancelled(self, build_id: str) -> bool:
with self._transaction() as connection:
return bool(self._build(connection, build_id)["cancelled"])
def snapshot(self) -> dict[str, Any]:
"""Return a consistent reconciliation view, including terminal records."""
with self._transaction() as connection:
builds = [dict(row) for row in connection.execute("SELECT * FROM builds ORDER BY sequence")]
allocations = [dict(row) for row in connection.execute("SELECT * FROM allocations ORDER BY sequence")]
for build in builds:
build["commit"] = build.pop("commit_sha")
build["cancelled"] = bool(build["cancelled"])
for allocation in allocations:
allocation["handle"] = json.loads(allocation.pop("handle_json"))
return {
"limits": {
"max_prs": self.max_prs,
"max_gpus_per_pr": self.max_gpus_per_pr,
"max_gpus": self.max_gpus,
},
"builds": builds,
"allocations": allocations,
}
+395
View File
@@ -0,0 +1,395 @@
"""Trusted execution adapters. No PR code executes in the controller process.
Modal API: https://modal.com/docs/sdk/py/latest/Sandbox
The controller persists deterministic handles before calling ``run``.
"""
from __future__ import annotations
import hashlib
import importlib
import json
import re
import subprocess
import threading
import time
from pathlib import Path, PurePosixPath
from typing import Any
REPOSITORY = "https://github.com/hao-ai-lab/FastVideo.git"
ENV_ALLOWLIST = {
"FASTVIDEO_GOLDEN_TEST_FILES", "FASTVIDEO_SSIM_TEST_FILES", "BUILDKITE_BRANCH",
"BUILDKITE_SOURCE", "BUILDKITE_PULL_REQUEST", "BUILDKITE_BUILD_NUMBER", "BUILDKITE_BUILD_URL",
"BUILDKITE_COMMIT", "BUILDKITE_REPO", "BUILDKITE_BUILD_ID", "BUILDKITE_JOB_ID",
}
def _image(value: str) -> str:
if not re.fullmatch(r"[A-Za-z0-9._/:\-]+@sha256:[0-9a-f]{64}", value):
raise ValueError("Backend image must be pinned by sha256 digest")
return value
def _subpath(value: str) -> str:
path = PurePosixPath(value)
if not value or path.is_absolute() or ".." in path.parts or value in {".", "/"}:
raise ValueError("PVC mounts require a dedicated relative CI subpath")
return value
class Backend:
name = "abstract"
def __init__(self, config: dict, trusted_dir: Path, run_dir: Path):
self.config = config
self.trusted_dir = trusted_dir
self.run_dir = run_dir
self.image = _image(config["image"])
self.run_dir.mkdir(parents=True, exist_ok=True)
def handle(self, build_id: str, lane_id: str) -> dict:
identity = hashlib.sha256(f"{build_id}\0{lane_id}".encode()).hexdigest()[:32]
return {"backend": self.name, "name": f"fv-ci-{identity}", "run_id": identity}
def _environment(self, request: dict, lane: dict) -> dict[str, str]:
if request["repository"] != REPOSITORY or not re.fullmatch(r"[0-9a-f]{40}", request["commit"]):
raise ValueError("Worker requires the fixed repository and an immutable commit")
if type(lane["gpus"]) is not int or lane["gpus"] not in range(1, 5):
raise ValueError("A lane must request between one and four GPUs")
script = lane["script"]
if script != ".buildkite/scripts/unit_test.sh" and not re.fullmatch(
r"\.buildkite/scripts/lanes/[a-z_]+\.sh", script):
raise ValueError("Invalid lane script")
extras = lane.get("extras", ["test"])
if not extras or any(not re.fullmatch(r"[a-z][a-z0-9-]*", extra) for extra in extras):
raise ValueError("Invalid dependency extras")
if str(lane["fa4"]) not in {"0", "1"}:
raise ValueError("Invalid attention policy")
build_id = str(request.get("buildkite_build_id") or request["build_id"])
job_id = str(request.get("buildkite_job_id") or request.get("env", {}).get("BUILDKITE_JOB_ID")
or request["build_id"])
if any(not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]{0,199}", value) for value in (build_id, job_id)):
raise ValueError("Invalid Buildkite build/job identity")
pr_number = str(request.get("pr_number") or "false")
if pr_number != "false" and not re.fullmatch(r"[1-9][0-9]*", pr_number):
raise ValueError("Invalid PR number")
env = {key: str(value) for key, value in request.get("env", {}).items() if key in ENV_ALLOWLIST}
env.update({
"BUILDKITE_COMMIT": request["commit"], "BUILDKITE_REPO": REPOSITORY,
"BUILDKITE_BUILD_ID": build_id, "BUILDKITE_JOB_ID": job_id,
"BUILDKITE_PULL_REQUEST": pr_number, "FASTVIDEO_CONTAINER_IMAGE_REF": self.image,
"FASTVIDEO_CI_REPOSITORY": REPOSITORY, "FASTVIDEO_CI_COMMIT": request["commit"],
"FASTVIDEO_CI_PR_NUMBER": str(request.get("pr_number") or ""),
"FASTVIDEO_CI_GPUS": str(lane["gpus"]), "FASTVIDEO_CI_SCRIPT": script,
"FASTVIDEO_CI_EXTRAS": ",".join(extras), "FASTVIDEO_FA4": str(lane["fa4"]),
"FASTVIDEO_CI_KERNEL": "1" if lane.get("kernel", True) else "0",
"FASTVIDEO_CI_LOCAL_ONLY": "1", "FASTVIDEO_SSIM_BOOTSTRAP_MODE": "0",
"TEST_SCOPE": request.get("scope", "direct"),
})
return env
def run(self, handle: dict, request: dict, lane: dict, cancel: threading.Event) -> int:
raise NotImplementedError
def stop(self, handle: dict) -> bool:
raise NotImplementedError
class KubernetesBackend(Backend):
name = "kubernetes"
def __init__(self, config: dict, trusted_dir: Path, run_dir: Path):
super().__init__(config, trusted_dir, run_dir)
if config.get("namespace", "vllm") != "vllm":
raise ValueError("Kubernetes CI is restricted to namespace vllm")
for kind in ("cache", "artifacts"):
claim = config.get(f"{kind}_pvc")
if claim:
if claim in {"lustre-pvc-vllm", "nfs-pvc-vllm"}:
raise ValueError("Personal development PVCs cannot be mounted in CI")
_subpath(config.get(f"{kind}_subpath", ""))
def handle(self, build_id: str, lane_id: str) -> dict:
result = super().handle(build_id, lane_id)
result.update({"namespace": "vllm", "context": self.config.get("context")})
return result
def _command(self, handle: dict, *args: str) -> list[str]:
if handle.get("namespace") != "vllm" or handle.get("context") != self.config.get("context"):
raise ValueError("Handle does not match configured Kubernetes target")
command = ["kubectl", "--request-timeout=20s", "--namespace=vllm"]
if handle.get("context"):
command += ["--context", handle["context"]]
return [*command, *args]
def _kubectl(self, handle: dict, *args: str, body: dict | None = None) -> str:
result = subprocess.run(self._command(handle, *args), input=json.dumps(body) if body else None,
text=True, capture_output=True, timeout=35, check=False)
if result.returncode:
raise RuntimeError(f"kubectl {args[0]} failed: {result.stderr.strip()}")
return result.stdout
def _job(self, handle: dict) -> dict | None:
raw = self._kubectl(handle, "get", "job", handle["name"], "--ignore-not-found", "-o", "json")
return json.loads(raw) if raw.strip() else None
def _pods(self, handle: dict) -> list[dict]:
raw = self._kubectl(handle, "get", "pods", "-l", f"job-name={handle['name']}", "-o", "json")
return json.loads(raw)["items"]
def manifest(self, handle: dict, request: dict, lane: dict) -> dict:
env = [{"name": key, "value": value} for key, value in self._environment(request, lane).items()]
if self.config.get("hf_secret"):
env.append({"name": "HF_TOKEN", "valueFrom": {"secretKeyRef": {
"name": self.config["hf_secret"], "key": self.config.get("hf_secret_key", "HF_TOKEN")}}})
volumes = [{"name": "workspace", "emptyDir": {}}, {"name": "shm", "emptyDir": {
"medium": "Memory", "sizeLimit": self.config.get("shared_memory", "8Gi")}}]
mounts = [{"name": "workspace", "mountPath": "/workspace"}, {"name": "shm", "mountPath": "/dev/shm"}]
for kind, path in (("cache", "/ci-cache"), ("artifacts", "/workspace/artifacts")):
if self.config.get(f"{kind}_pvc"):
subpath = _subpath(self.config[f"{kind}_subpath"])
if kind == "artifacts":
subpath += "/" + handle["name"]
volumes.append({"name": kind, "persistentVolumeClaim": {"claimName": self.config[f"{kind}_pvc"]}})
mounts.append({"name": kind, "mountPath": path, "subPath": subpath, "readOnly": kind == "cache"})
resources = {"cpu": str(self.config.get("cpu", "8")), "memory": self.config.get("memory", "64Gi"),
"nvidia.com/gpu": str(lane["gpus"])}
labels = {"app.kubernetes.io/managed-by": "fastvideo-gpu-ci", "fastvideo-ci/run-id": handle["run_id"]}
manifest = {"apiVersion": "batch/v1", "kind": "Job", "metadata": {
"name": handle["name"], "namespace": "vllm", "labels": labels}, "spec": {
"backoffLimit": 0, "activeDeadlineSeconds": int(lane["timeout_seconds"]),
"ttlSecondsAfterFinished": 86400, "template": {"metadata": {"labels": labels}, "spec": {
"restartPolicy": "Never", "automountServiceAccountToken": False,
"hostNetwork": False, "hostPID": False, "hostIPC": False,
"nodeSelector": {"kubernetes.io/arch": "arm64", "nvidia.com/gpu.product": "NVIDIA-GB200"},
"tolerations": [{"key": "nvidia.com/gpu", "operator": "Exists", "effect": "NoSchedule"}],
"containers": [{"name": "worker", "image": self.image,
"command": ["/bin/bash", "-c", (self.trusted_dir / "worker.sh").read_text()],
"env": env, "resources": {"requests": resources, "limits": resources},
"securityContext": {"privileged": False, "allowPrivilegeEscalation": False,
"capabilities": {"drop": ["ALL"], "add": [
"CHOWN", "DAC_OVERRIDE", "FOWNER", "SETGID", "SETUID"]}},
"volumeMounts": mounts}], "volumes": volumes}}}}
if self.config.get("artifacts_pvc"):
# Only this fixed init command sees the CI artifact parent. PR code
# mounts its own run directory and cannot alter adjacent runs.
manifest["spec"]["template"]["spec"]["initContainers"] = [{
"name": "prepare-artifacts", "image": self.image,
"command": ["mkdir", "-p", "/ci-artifacts/" + handle["name"]],
"securityContext": {"allowPrivilegeEscalation": False, "capabilities": {"drop": ["ALL"]}},
"volumeMounts": [{"name": "artifacts", "mountPath": "/ci-artifacts",
"subPath": _subpath(self.config["artifacts_subpath"])}]}]
return manifest
@staticmethod
def _owned(job: dict, handle: dict) -> bool:
return job.get("metadata", {}).get("labels", {}).get("fastvideo-ci/run-id") == handle["run_id"]
def run(self, handle: dict, request: dict, lane: dict, cancel: threading.Event) -> int:
if self._job(handle) is not None or self._pods(handle):
raise RuntimeError("Resource already exists; explicit reconciliation is required")
manifest = self.manifest(handle, request, lane)
(self.run_dir / f"{handle['name']}.job.json").write_text(json.dumps(manifest, indent=2))
if not self.config.get("artifacts_pvc"):
(self.run_dir / f"{handle['name']}.artifacts.txt").write_text(
"No dedicated artifacts PVC configured. Controller logs/status survive; worker artifacts are ephemeral.\n")
if cancel.is_set():
return 130
marker = self.run_dir / f"{handle['name']}.creating"
marker.touch()
self._kubectl(handle, "create", "-f", "-", body=manifest)
marker.unlink()
deadline = time.monotonic() + int(lane["timeout_seconds"])
stream = None
log_file = (self.run_dir / f"{handle['name']}.log").open("w")
try:
while True:
if cancel.is_set() or time.monotonic() >= deadline:
if not self.stop(handle):
raise RuntimeError("Cannot confirm Kubernetes job cancellation")
return 130 if cancel.is_set() else 124
job = self._job(handle)
if job is None or not self._owned(job, handle):
raise RuntimeError("Kubernetes job disappeared or its ownership changed")
pods = self._pods(handle)
if len(pods) > 1:
raise RuntimeError("Unexpected replacement or duplicate CI pods")
for pod in pods:
owners = pod.get("metadata", {}).get("ownerReferences", [])
if not any(owner.get("uid") == job["metadata"]["uid"] for owner in owners):
raise RuntimeError("Pod does not belong to the expected job")
statuses = pod.get("status", {}).get("containerStatuses", [])
worker = next((status for status in statuses if status["name"] == "worker"), {})
state = worker.get("state", {})
if stream is None and ("running" in state or "terminated" in state):
stream = subprocess.Popen(self._command(handle, "logs", "-f", pod["metadata"]["name"],
"-c", "worker", "--timestamps"),
stdout=log_file, stderr=subprocess.STDOUT, text=True)
terminated = state.get("terminated")
if terminated is not None:
exit_code = terminated.get("exitCode")
if type(exit_code) is not int:
raise RuntimeError("Worker termination has no numeric exit status")
(self.run_dir / f"{handle['name']}.status.json").write_text(json.dumps(pod["status"], indent=2))
# Streaming may fail independently of the pod. Fetch a
# final complete log before accepting even a zero exit.
final_log = self._kubectl(handle, "logs", pod["metadata"]["name"], "-c", "worker", "--timestamps")
if not final_log.strip():
raise RuntimeError("Worker completed but Kubernetes logs are empty")
(self.run_dir / f"{handle['name']}.final.log").write_text(final_log)
return exit_code
if pod.get("status", {}).get("phase") == "Failed":
raise RuntimeError("Pod failed without a worker exit status")
if any(condition.get("type") == "Failed" and condition.get("status") == "True"
for condition in job.get("status", {}).get("conditions", [])):
raise RuntimeError("Kubernetes job failed before worker completion")
cancel.wait(2)
finally:
if stream is not None:
try:
stream.wait(timeout=10)
except subprocess.TimeoutExpired:
stream.terminate()
try:
stream.wait(timeout=5)
except subprocess.TimeoutExpired:
stream.kill()
stream.wait()
log_file.close()
def stop(self, handle: dict) -> bool:
job = self._job(handle)
if job is not None:
if not self._owned(job, handle):
return False
self._kubectl(handle, "delete", "job", handle["name"], "--cascade=foreground",
"--wait=true", "--timeout=20s")
(self.run_dir / f"{handle['name']}.creating").unlink(missing_ok=True)
elif (self.run_dir / f"{handle['name']}.creating").exists():
# A timed-out create may still commit remotely. Keep its GPU lease.
return False
# A missing Job alone is insufficient: orphaned Pods may still use GPUs.
return self._job(handle) is None and not self._pods(handle)
class ModalBackend(Backend):
name = "modal"
def __init__(self, config: dict, trusted_dir: Path, run_dir: Path):
super().__init__(config, trusted_dir, run_dir)
if config.get("gpu", "L40S") != "L40S":
raise ValueError("Modal default must remain L40S; Hopper lanes are selected by reviewed lane policy")
self._sandboxes: dict[str, Any] = {}
@staticmethod
def _modal() -> Any:
return importlib.import_module("modal")
@staticmethod
def _gpu_type(lane: dict) -> str:
gpu_type = lane.get("modal_gpu", "L40S")
if gpu_type not in ("L40S", "H100"):
raise ValueError("Reviewed Modal hardware must be L40S or H100")
return gpu_type
def _environment(self, request: dict, lane: dict) -> dict[str, str]:
self._gpu_type(lane)
# Attention/reference policy differs from the GB200 lanes. Copy the
# lane record so concurrent tasks never mutate shared policy or image.
modal_lane = {**lane, "fa4": lane.get("modal_fa4", lane["fa4"])}
return super()._environment(request, modal_lane)
def handle(self, build_id: str, lane_id: str) -> dict:
result = super().handle(build_id, lane_id)
result["app"] = self.config.get("app", "fastvideo-gpu-ci")
return result
def _lookup(self, handle: dict) -> Any:
if handle["app"] != self.config.get("app", "fastvideo-gpu-ci"):
raise ValueError("Handle does not match configured Modal app")
modal = self._modal()
try:
return modal.Sandbox.from_name(handle["app"], handle["name"])
except modal.exception.NotFoundError:
return None
def run(self, handle: dict, request: dict, lane: dict, cancel: threading.Event) -> int:
env = self._environment(request, lane)
if self._lookup(handle) is not None:
raise RuntimeError("Sandbox already exists; explicit reconciliation is required")
modal = self._modal()
image = modal.Image.from_registry(self.image).add_local_file(
str(self.trusted_dir / "worker.sh"), "/opt/fastvideo-ci/worker.sh")
app = modal.App.lookup(handle["app"], create_if_missing=True)
secrets = [modal.Secret.from_name(self.config["hf_secret"])] if self.config.get("hf_secret") else []
(self.run_dir / f"{handle['name']}.artifacts.txt").write_text(
"Modal adapter retains controller logs/status. Worker artifacts are ephemeral.\n")
if cancel.is_set():
return 130
marker = self.run_dir / f"{handle['name']}.creating"
marker.touch()
sandbox = modal.Sandbox.create(
"/bin/bash", "/opt/fastvideo-ci/worker.sh", app=app, name=handle["name"], image=image,
env=env, secrets=secrets, gpu=f"{self._gpu_type(lane)}:{lane['gpus']}",
timeout=int(lane["timeout_seconds"]),
cpu=float(self.config.get("cpu", 8)), memory=int(self.config.get("memory_mib", 65536)),
tags={"fastvideo-ci-run-id": handle["run_id"]})
self._sandboxes[handle["name"]] = sandbox
marker.unlink()
(self.run_dir / f"{handle['name']}.sandbox.json").write_text(json.dumps({"id": sandbox.object_id}))
return self._watch(sandbox, handle, lane, cancel)
def _watch(self, sandbox: Any, handle: dict, lane: dict, cancel: threading.Event) -> int:
errors: list[Exception] = []
def capture(stream: Any, suffix: str) -> None:
try:
with (self.run_dir / f"{handle['name']}.{suffix}.log").open("w") as output:
for line in stream:
output.write(line)
output.flush()
except Exception as error:
errors.append(error)
threads = [threading.Thread(target=capture, args=(sandbox.stdout, "stdout"), daemon=True),
threading.Thread(target=capture, args=(sandbox.stderr, "stderr"), daemon=True)]
for thread in threads:
thread.start()
deadline = time.monotonic() + int(lane["timeout_seconds"])
try:
while True:
if cancel.is_set() or time.monotonic() >= deadline:
if not self.stop(handle):
raise RuntimeError("Cannot confirm Modal sandbox cancellation")
return 130 if cancel.is_set() else 124
code = sandbox.poll()
if code is not None:
if type(code) is not int:
raise RuntimeError("Sandbox returned a nonnumeric exit status")
(self.run_dir / f"{handle['name']}.status.json").write_text(json.dumps({"exit_code": code}))
for thread in threads:
thread.join(timeout=5)
if errors or any(thread.is_alive() for thread in threads):
raise RuntimeError("Modal log collection did not complete")
return code
cancel.wait(2)
finally:
# Do not detach a running sandbox: root must confirm stop before
# freeing its shared GPU lease, including after SDK/network errors.
for thread in threads:
thread.join(timeout=1)
def stop(self, handle: dict) -> bool:
sandbox = self._sandboxes.get(handle["name"]) or self._lookup(handle)
marker = self.run_dir / f"{handle['name']}.creating"
if sandbox is None:
return not marker.exists()
if sandbox.get_tags().get("fastvideo-ci-run-id") != handle["run_id"]:
return False
code = sandbox.terminate(wait=True)
confirmed = type(code) is int and sandbox.poll() is not None
if confirmed:
marker.unlink(missing_ok=True)
sandbox.detach()
self._sandboxes.pop(handle["name"], None)
return confirmed
+25
View File
@@ -0,0 +1,25 @@
{
"repository": "https://github.com/hao-ai-lab/FastVideo.git",
"default_backend": "slurm",
"slurm_uploader": ["/opt/fastvideo-ci-runner/pipeline-upload"],
"queue": "gpu-ci-dispatch",
"state_path": "/var/lib/fastvideo-gpu-ci/admission.sqlite3",
"artifacts_dir": "/var/lib/fastvideo-gpu-ci/runs",
"max_active_prs": 2,
"max_gpus_per_pr": 4,
"max_gpus": 8,
"queue_timeout_seconds": 21600,
"upload_artifacts": true,
"kubernetes": {
"namespace": "vllm",
"image": "ghcr.io/hao-ai-lab/fastvideo/fastvideo-dev@sha256:REPLACE_WITH_REVIEWED_ARM64_SM100_DIGEST",
"cpu": "8",
"memory": "64Gi",
"shared_memory": "8Gi"
},
"modal": {
"app": "fastvideo-gpu-ci",
"gpu": "L40S",
"image": "ghcr.io/hao-ai-lab/fastvideo/fastvideo-dev@sha256:REPLACE_WITH_REVIEWED_AMD64_SM89_DIGEST"
}
}
+217
View File
@@ -0,0 +1,217 @@
"""Suite lifecycle and local-host recovery for opt-in Buildkite GPU backends."""
from __future__ import annotations
import fcntl
import hashlib
import json
import signal
import subprocess
import threading
import time
from concurrent.futures import Future, ThreadPoolExecutor
from contextlib import contextmanager
from pathlib import Path
from typing import Any, Iterator
from .admission import AdmissionStore
INFRASTRUCTURE_FAILURE = 97
def run_directory(config: dict[str, Any], build_id: str) -> Path:
digest = hashlib.sha256(build_id.encode()).hexdigest()[:32]
return Path(config["artifacts_dir"]) / digest
@contextmanager
def build_lock(directory: Path) -> Iterator[None]:
"""Serialize launch and recovery; never stop a resource during its creation."""
directory.mkdir(parents=True, exist_ok=True, mode=0o700)
with (directory / "dispatcher.lock").open("a") as lock:
try:
fcntl.flock(lock.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError as error:
raise RuntimeError("A dispatcher is still running; cancel its Buildkite job first") from error
try:
yield
finally:
fcntl.flock(lock.fileno(), fcntl.LOCK_UN)
def make_store(config: dict[str, Any]) -> AdmissionStore:
return AdmissionStore(config["state_path"], config.get("max_active_prs", 2),
config.get("max_gpus_per_pr", 4), config.get("max_gpus", 8))
def make_backend(config: dict[str, Any], backend: str, directory: Path) -> Any:
from .backends import KubernetesBackend, ModalBackend
cls = {"vllm": KubernetesBackend, "modal": ModalBackend}.get(backend)
if cls is None:
raise ValueError("The new dispatcher supports only modal and vllm")
backend_config = config["kubernetes" if backend == "vllm" else "modal"]
return cls(backend_config, Path(__file__).resolve().parent, directory)
def _run_lane(backend: Any, handle: dict[str, Any], request: dict[str, Any], lane: dict[str, Any],
cancel: threading.Event) -> tuple[int, bool, str]:
error = ""
try:
code = backend.run(handle, request, lane, cancel)
if type(code) is not int or not 0 <= code <= 255:
raise RuntimeError("Backend did not return a valid process exit status")
except Exception as exception:
code, error = INFRASTRUCTURE_FAILURE, str(exception)
try:
stopped = backend.stop(handle)
except Exception as exception:
stopped = False
error += f"; stop failed: {exception}"
if not stopped:
code = INFRASTRUCTURE_FAILURE
error += "; worker termination unconfirmed; reservation retained"
return code, stopped, error
def execute(request: dict[str, Any], config: dict[str, Any], *, backend: Any = None,
cancel: threading.Event | None = None, poll_seconds: float = 2.0) -> int:
"""Run one suite; independent processes share the PR/GPU admission ledger."""
cancel = cancel or threading.Event()
build_id = request["build_id"]
directory = run_directory(config, build_id)
store = make_store(config)
if any(lane["gpus"] > min(store.max_gpus_per_pr, store.max_gpus) for lane in request["lanes"]):
raise ValueError("Selected lane does not fit the configured GPU budget")
backend = backend or make_backend(config, request["backend"], directory)
results: dict[str, Any] = {}
code = 0
with build_lock(directory):
# Never reattach to an ambiguous prior launch. Buildkite retries have
# a new job ID; abandoned attempts need explicit backend reconciliation.
if any(item["build_id"] == build_id for item in store.snapshot()["builds"]):
raise RuntimeError("Build attempt already exists; inspect/recover it instead of replaying it")
store.register(build_id, request["pr_key"], request["backend"], request["commit"])
(directory / "request.json").write_text(json.dumps(request, indent=2) + "\n")
deadline = time.monotonic() + config.get("queue_timeout_seconds", 21600)
# A docs-only merge plan requires no GPUs and must not wait behind
# unrelated PRs. Arrival order is not evidence of commit recency, so
# never cancel other SHAs merely because this request arrived later.
admitted = not request["lanes"]
while not admitted:
store.heartbeat(build_id)
if cancel.is_set() or store.is_cancelled(build_id) or time.monotonic() >= deadline:
cancel.set()
store.request_cancel(build_id)
store.finish(build_id)
code = INFRASTRUCTURE_FAILURE
break
admitted = store.try_admit(build_id)
if not admitted:
cancel.wait(poll_seconds)
pending = list(request["lanes"]) if admitted else []
golden_selected = any(lane["key"] == "golden-gate" for lane in pending)
running: dict[Future[tuple[int, bool, str]], dict[str, Any]] = {}
# Lane concurrency is additionally bounded by transactional GPU leases.
with ThreadPoolExecutor(max_workers=4) as pool:
while pending or running:
store.heartbeat(build_id)
if store.is_cancelled(build_id):
cancel.set()
if cancel.is_set():
store.request_cancel(build_id)
for lane in pending:
results[lane["key"]] = {"state": "cancelled"}
pending.clear()
code = code or INFRASTRUCTURE_FAILURE
for future, lane in list(running.items()):
if not future.done():
continue
lane_code, stopped, error = future.result()
if stopped:
store.release(build_id, lane["key"])
else:
cancel.set()
results[lane["key"]] = {"state": "passed" if lane_code == 0 else "failed",
"exit_code": lane_code, "stopped": stopped, "error": error}
print(f"--- {lane['key']}: exit {lane_code}", flush=True)
code = code or lane_code
del running[future]
for lane in list(pending):
if cancel.is_set() or len(running) >= 4:
break
if golden_selected and not lane["fastcheck"] and lane["key"] != "golden-gate":
golden = results.get("golden-gate")
if golden is None:
continue
if golden["state"] != "passed":
results[lane["key"]] = {"state": "skipped", "reason": "golden gate failed"}
pending.remove(lane)
continue
handle = backend.handle(build_id, lane["key"])
if store.try_acquire(build_id, lane["key"], lane["gpus"], handle):
print(f"--- Starting {lane['key']} on {request['backend']} ({lane['gpus']} GPUs)", flush=True)
future = pool.submit(_run_lane, backend, handle, request, lane, cancel)
running[future] = lane
pending.remove(lane)
if pending or running:
# wait() on an already-set Event spins; cancellation still
# polls bounded backend shutdowns at the normal interval.
time.sleep(poll_seconds)
active = [a for a in store.snapshot()["allocations"] if a["build_id"] == build_id and a["state"] == "active"]
if not active:
store.finish(build_id)
else:
code = INFRASTRUCTURE_FAILURE
summary = {"build_id": build_id, "backend": request["backend"], "commit": request["commit"],
"exit_code": code, "recovery_required": bool(active), "lanes": results}
(directory / "summary.json").write_text(json.dumps(summary, indent=2) + "\n")
print(json.dumps(summary, indent=2), flush=True)
return code
def recover(config: dict[str, Any], build_id: str, *, backend: Any = None) -> None:
"""Stop abandoned workers first, then release reservations. Never TTL-unlock."""
store = make_store(config)
build = next((b for b in store.snapshot()["builds"] if b["build_id"] == build_id), None)
if build is None:
raise ValueError("Unknown build ID")
directory = run_directory(config, build_id)
with build_lock(directory):
store.request_cancel(build_id)
backend = backend or make_backend(config, build["backend"], directory)
uncertain = []
for allocation in store.snapshot()["allocations"]:
if allocation["build_id"] != build_id or allocation["state"] != "active":
continue
try:
stopped = backend.stop(allocation["handle"])
except Exception:
stopped = False
if stopped:
store.release(build_id, allocation["lane_id"])
else:
uncertain.append(allocation["lane_id"])
if uncertain:
raise RuntimeError(f"Could not confirm termination; reservations retained: {uncertain}")
store.finish(build_id)
def upload_artifacts(directory: Path) -> None:
"""Only upload controller-owned logs/metadata, never arbitrary PR globs."""
for path in sorted(directory.iterdir()):
if path.is_file() and not path.is_symlink() and path.suffix in (".json", ".log"):
subprocess.run(["buildkite-agent", "artifact", "upload", path.name], cwd=directory, check=True)
@contextmanager
def cancellation_signals(event: threading.Event) -> Iterator[None]:
previous: dict[signal.Signals, Any] = {}
def handler(signum: int, frame: Any) -> None:
event.set()
for signum in (signal.SIGINT, signal.SIGTERM):
previous[signum] = signal.signal(signum, handler)
try:
yield
finally:
for signum, old_handler in previous.items():
signal.signal(signum, old_handler)
+11
View File
@@ -0,0 +1,11 @@
"""Launch with python -I to exclude job-controlled PYTHONPATH and user packages."""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from scripts.gpu_ci.__main__ import main # noqa: E402
if __name__ == "__main__":
raise SystemExit(main())
+69
View File
@@ -0,0 +1,69 @@
# SPDX-License-Identifier: Apache-2.0
"""Render an opt-in GPU backend without changing the canonical Slurm graph.
The caller is the operator-owned pipeline uploader. It must load this module
from its reviewed installation, never from a pull request checkout.
"""
from __future__ import annotations
import copy
import re
from typing import Any
BACKENDS = frozenset({"slurm", "modal", "vllm"})
STATUS_SUFFIXES = {
"fastcheck": "fastcheck-passed",
"full": "full-suite-passed",
"merge": "full-suite-passed",
"direct": "direct-test-completed",
"scheduled": "scheduled-ssim-passed",
}
def render_pipeline(
canonical: dict[str, Any],
backend: str,
scope: str,
queue: str = "gpu-ci-dispatch",
) -> dict[str, Any]:
"""Return the original graph or one trusted, backend-specific suite step.
Only enumerated backend and scope values reach the generated step. Request
SHA, PR identity, direct lane, and merge plan remain build metadata and are
validated independently by the trusted dispatcher. Arbitrary canonical
graph commands, environment, hooks, and notifications are not copied into
the new control plane.
"""
backend = backend or "slurm"
scope = scope or "fastcheck"
if backend not in BACKENDS:
raise ValueError(f"Unsupported GPU CI backend: {backend!r}")
if scope not in STATUS_SUFFIXES:
raise ValueError(f"Unsupported GPU CI scope: {scope!r}")
if backend == "slurm":
return copy.deepcopy(canonical)
if not re.fullmatch(r"[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}", queue):
raise ValueError("GPU CI queue must be an operator-configured queue name")
return {
"notify": [{
"github_commit_status": {
"context": f"gpu-ci/{backend}/{STATUS_SUFFIXES[scope]}"
}
}],
"steps": [{
"label": f"GPU CI ({backend}, {scope})",
"key": f"gpu-ci-{backend}-{scope}",
"command": "/opt/fastvideo-gpu-ci/run",
# Admission waiting happens inside this command and uses this
# budget. An unscheduled Buildkite job has no command timeout yet.
"timeout_in_minutes": 480,
"env": {
"CI_GPU_BACKEND": backend,
"TEST_SCOPE": scope,
},
"agents": {
"queue": queue
},
}],
}
+145
View File
@@ -0,0 +1,145 @@
"""Reviewed lane policy and validation at the trusted dispatcher boundary."""
from __future__ import annotations
import re
from pathlib import Path
from typing import Any, Mapping
BACKENDS = ("slurm", "modal", "vllm")
SCOPES = ("fastcheck", "full", "merge", "direct", "scheduled")
DEFAULT_REPOSITORY = "https://github.com/hao-ai-lab/FastVideo.git"
def _lane(key: str, public_type: str, script: str, gpus: int = 1, *, fastcheck: bool = False,
extras: tuple[str, ...] = ("test",), fa4: str = "0") -> dict[str, Any]:
return {"key": key, "public_type": public_type, "script": script, "gpus": gpus,
"fastcheck": fastcheck, "extras": list(extras), "fa4": fa4, "timeout_seconds": 5400,
"kernel": key != "dreamverse",
"modal_gpu": "H100" if key in {"encoder", "kernel-tests", "training-vsa"} else "L40S",
"modal_fa4": "0" if key in {"transformer", "training", "distillation", "self-forcing",
"lora-training", "training-vsa", "train-framework"} else "1"}
LANES = (
_lane("encoder", "encoder", "lanes/encoder.sh", fastcheck=True),
_lane("vae", "vae", "lanes/vae.sh", fastcheck=True),
_lane("transformer", "transformer", "lanes/transformer.sh", fastcheck=True),
_lane("kernel-tests", "kernel_tests", "lanes/kernel_tests.sh", fastcheck=True),
_lane("unit", "unit_test", "unit_test.sh", fastcheck=True),
_lane("dreamverse", "dreamverse_app", "lanes/dreamverse.sh", fastcheck=True,
extras=("test", "dreamverse")),
_lane("golden-gate", "golden_gate", "lanes/golden_gate.sh"),
_lane("ssim", "ssim", "lanes/ssim.sh", 4, fa4="1"),
_lane("lora-inference", "inference_lora", "lanes/inference_lora.sh"),
_lane("lora-extraction", "lora_extraction", "lanes/lora_extraction.sh"),
_lane("training", "training", "lanes/training.sh", 4),
_lane("distillation", "distillation_dmd", "lanes/distillation_dmd.sh", 2),
_lane("self-forcing", "self_forcing", "lanes/self_forcing.sh", 2),
_lane("lora-training", "training_lora", "lanes/training_lora.sh", 2),
_lane("training-vsa", "training_vsa", "lanes/training_vsa.sh", 2),
_lane("inference-vmoba", "inference_vmoba", "lanes/inference_vmoba.sh"),
_lane("performance", "performance", "lanes/performance.sh", 2),
_lane("api-server", "api_server", "lanes/api_server.sh"),
_lane("train-framework", "train_framework", "lanes/train_framework.sh"),
_lane("eval", "eval", "lanes/eval.sh", extras=("test", "eval-full")),
)
LANES_BY_KEY = {lane["key"]: lane for lane in LANES}
def backend_name(value: str | None, default: str = "slurm") -> str:
backend = value or default
if backend not in BACKENDS:
raise ValueError(f"Unknown CI_GPU_BACKEND: {backend!r}")
return backend
def _identifier(value: str, label: str) -> str:
if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_-]{0,99}", value):
raise ValueError(f"Invalid {label}")
return value
def build_request(env: Mapping[str, str], config: Mapping[str, Any], root: Path) -> dict[str, Any]:
"""Copy only the explicitly accepted Buildkite fields; never forward its token."""
backend = backend_name(env.get("CI_GPU_BACKEND"), config.get("default_backend", "slurm"))
if backend == "slurm":
raise ValueError("Slurm uses the existing trusted uploader and dispatcher")
repository = config.get("repository", DEFAULT_REPOSITORY)
if not re.fullmatch(r"https://github\.com/[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+\.git", repository):
raise ValueError("repository must be a fixed HTTPS GitHub .git URL")
supplied_repo = env.get("BUILDKITE_REPO", "")
ssh_repo = "git@github.com:" + repository.removeprefix("https://github.com/")
if supplied_repo not in (repository, ssh_repo):
raise ValueError("Buildkite repository does not match operator policy")
commit = env.get("BUILDKITE_COMMIT", "")
if not re.fullmatch(r"[0-9a-f]{40}", commit):
raise ValueError("BUILDKITE_COMMIT must be an immutable 40-character SHA")
build_id = _identifier(env.get("BUILDKITE_BUILD_ID", ""), "BUILDKITE_BUILD_ID")
job_id = _identifier(env.get("BUILDKITE_JOB_ID", ""), "BUILDKITE_JOB_ID")
pr = env.get("BUILDKITE_PULL_REQUEST", "false")
if pr == "false" and env.get("PR_NUMBER"):
pr = env["PR_NUMBER"]
if pr not in ("", "false") and not re.fullmatch(r"[1-9][0-9]*", pr):
raise ValueError("Invalid PR number")
scope = env.get("TEST_SCOPE") or "fastcheck"
if scope not in SCOPES:
raise ValueError(f"Invalid TEST_SCOPE: {scope!r}")
# Reference publication requires a separately trusted publishing job.
if env.get("FASTVIDEO_SSIM_BOOTSTRAP_MODE", "0") not in ("", "0", "false"):
raise ValueError("SSIM bootstrap publication is not supported by the isolated GPU runner")
request = {"build_id": f"{build_id}.{job_id}", "buildkite_build_id": build_id,
"repository": repository, "commit": commit, "pr_number": pr or "false",
"pr_key": f"{repository}#{pr}" if pr not in ("", "false") else None,
"backend": backend, "scope": scope, "env": {"TEST_SCOPE": scope,
"BUILDKITE_PULL_REQUEST": pr or "false", "BUILDKITE_COMMIT": commit,
"BUILDKITE_REPO": repository, "BUILDKITE_BUILD_ID": build_id,
"BUILDKITE_JOB_ID": job_id}}
for key in ("BUILDKITE_BRANCH", "BUILDKITE_SOURCE", "BUILDKITE_BUILD_URL"):
value = env.get(key, "")
if len(value) > 2048 or any(ord(c) < 32 for c in value):
raise ValueError(f"Invalid {key}")
request["env"][key] = value
if scope == "direct":
requested = env.get("TEST_TYPE", "").removesuffix("_ci")
selected = [lane for lane in LANES if requested in (lane["public_type"], lane["key"])]
if len(selected) != 1:
raise ValueError(f"Unknown direct TEST_TYPE: {requested!r}")
elif scope == "merge":
encoded = env.get("MERGE_TEST_PLAN", "")
if not re.fullmatch(r",(?:none|[a-z]+(?:-[a-z]+)*(?:,[a-z]+(?:-[a-z]+)*)*),", encoded):
raise ValueError("Missing or malformed trusted MERGE_TEST_PLAN")
names = encoded[1:-1].split(",")
if names == ["none"]:
names = []
if any(name not in LANES_BY_KEY or LANES_BY_KEY[name]["fastcheck"] for name in names):
raise ValueError("MERGE_TEST_PLAN contains an unknown or non-integration lane")
if len(names) != len(set(names)):
raise ValueError("Duplicate MERGE_TEST_PLAN lane")
selected = [lane for lane in LANES if lane["key"] in names]
elif scope == "scheduled":
requested = env.get("TEST_TYPE", "ssim").removesuffix("_ci")
if requested != "ssim":
raise ValueError("Scheduled scope supports SSIM; performance uses direct scope")
selected = [LANES_BY_KEY[requested]]
else:
selected = [lane for lane in LANES if scope == "full" or lane["fastcheck"]]
for key, lane_key, directory in (("FASTVIDEO_GOLDEN_TEST_FILES", "golden-gate", "golden_gate"),
("FASTVIDEO_SSIM_TEST_FILES", "ssim", "ssim")):
if lane_key not in {lane["key"] for lane in selected}:
continue
merge_key = "MERGE_GOLDEN_TESTS" if lane_key == "golden-gate" else "MERGE_SSIM_TESTS"
value = env.get(merge_key, "") if scope == "merge" else "all"
if not value:
raise ValueError(f"Missing trusted {key} for merge scope")
if value != "all":
names = value.split(",")
if len(names) != len(set(names)) or any(
not re.fullmatch(r"test_[a-z0-9_]+\.py", name) for name in names
):
raise ValueError(f"Invalid or unknown {key}")
# Existence belongs to the isolated exact-SHA checkout. The trusted
# controller need not be redeployed whenever a PR adds a test file.
request["env"][key] = value
request["lanes"] = [{**lane, "script": ".buildkite/scripts/" + lane["script"]} for lane in selected]
return request
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
# Install this file at /opt/fastvideo-gpu-ci/run on the trusted dispatch host.
set -eu
exec /opt/fastvideo-gpu-ci/venv/bin/python -I \
/opt/fastvideo-gpu-ci/source/scripts/gpu_ci/entrypoint.py run \
--config /etc/fastvideo-gpu-ci.json
+6
View File
@@ -0,0 +1,6 @@
#!/bin/sh
# Install as the trusted bootstrap for each participating Buildkite pipeline.
set -eu
exec /opt/fastvideo-gpu-ci/venv/bin/python -I \
/opt/fastvideo-gpu-ci/source/scripts/gpu_ci/entrypoint.py upload \
--config /etc/fastvideo-gpu-ci.json
+109
View File
@@ -0,0 +1,109 @@
#!/usr/bin/env bash
# Installed with the trusted controller; never load this entrypoint from a PR.
set -euo pipefail
umask 077
[[ ${FASTVIDEO_CI_REPOSITORY:-} == https://github.com/hao-ai-lab/FastVideo.git ]]
[[ ${FASTVIDEO_CI_COMMIT:-} =~ ^[0-9a-f]{40}$ ]]
[[ ${FASTVIDEO_CI_GPUS:-} =~ ^[1-4]$ ]]
[[ ${FASTVIDEO_CI_EXTRAS:-} =~ ^[a-z][a-z0-9-]*(,[a-z][a-z0-9-]*)*$ ]]
[[ ${FASTVIDEO_CI_SCRIPT:-} == .buildkite/scripts/unit_test.sh ||
${FASTVIDEO_CI_SCRIPT:-} =~ ^\.buildkite/scripts/lanes/[a-z_]+\.sh$ ]]
[[ ${FASTVIDEO_CI_KERNEL:-} =~ ^[01]$ ]]
[[ ${FASTVIDEO_FA4:-} =~ ^[01]$ ]]
export PATH="/opt/venv/bin:/root/.local/bin:$PATH"
export VIRTUAL_ENV=/opt/venv
export HF_HOME=/workspace/hf
export HF_HUB_CACHE="$HF_HOME/hub"
export UV_CACHE_DIR=/workspace/uv-cache
export UV_LINK_MODE=copy
export WANDB_MODE=offline
export FASTVIDEO_CI_LOCAL_ONLY=1
export FASTVIDEO_SSIM_BOOTSTRAP_MODE=0
export MASTER_ADDR=127.0.0.1
export MASTER_PORT=29500
export PYTHONUNBUFFERED=1
mkdir -p /workspace/artifacts "$HF_HUB_CACHE"
# Results are diagnostic only. The controller trusts the container exit status.
finish() {
worker_rc=$?
trap - EXIT
printf '{"exit_code":%s,"commit":"%s"}\n' "$worker_rc" "$FASTVIDEO_CI_COMMIT" \
> /workspace/artifacts/worker-result.json
if [ -d /workspace/repository/fastvideo/tests/ssim/generated_videos ]; then
cp -R /workspace/repository/fastvideo/tests/ssim/generated_videos \
/workspace/artifacts/ssim-generated 2>/dev/null || true
fi
exit "$worker_rc"
}
trap finish EXIT
trap 'exit 143' TERM
trap 'exit 130' INT
# The optional cache is a dedicated, read-only HF Hub subtree. Keep mutable
# refs and locks private; link immutable blobs instead of duplicating weights.
if [ -d /ci-cache ]; then
python - <<'PY'
import os
import shutil
from pathlib import Path
source = Path('/ci-cache').resolve()
destination = Path('/workspace/hf/hub')
for current, directories, filenames in os.walk(source, followlinks=False):
if Path(current) == source:
directories[:] = [name for name in directories if name.startswith(('models--', 'datasets--', 'spaces--'))]
filenames = [] # Never copy a token file from a misconfigured cache root.
directories[:] = [name for name in directories if name != '.locks'
and not (Path(current) / name).is_symlink()]
relative = Path(current).relative_to(source)
target_dir = destination / relative
target_dir.mkdir(parents=True, exist_ok=True)
for name in filenames:
original = Path(current) / name
resolved = original.resolve()
if not resolved.is_relative_to(source) or not resolved.is_file():
raise RuntimeError('Cache contains a file outside its mounted subtree')
target = target_dir / name
if 'blobs' in relative.parts or original.is_symlink():
target.symlink_to(resolved)
else:
shutil.copyfile(original, target)
PY
fi
mkdir /workspace/repository
cd /workspace/repository
git init --quiet
git remote add origin "$FASTVIDEO_CI_REPOSITORY"
if ! git fetch --no-tags --depth=1 origin "$FASTVIDEO_CI_COMMIT"; then
[[ ${FASTVIDEO_CI_PR_NUMBER:-} =~ ^[1-9][0-9]*$ ]]
git fetch --no-tags --depth=1 origin "refs/pull/${FASTVIDEO_CI_PR_NUMBER}/head"
fi
git checkout --quiet --detach FETCH_HEAD
test "$(git rev-parse HEAD)" = "$FASTVIDEO_CI_COMMIT"
git -c protocol.file.allow=never submodule update --init --recursive --depth=1
# Installing dependencies after the kernel can silently replace the PR kernel.
printf 'fastvideo-kernel\n' > /workspace/kernel-excludes
uv pip install --excludes /workspace/kernel-excludes -e ".[${FASTVIDEO_CI_EXTRAS}]"
if [ "$FASTVIDEO_CI_KERNEL" = 1 ]; then
python fastvideo/tests/modal/kernel_build_cache.py install
fi
python - <<'PY'
import os
import torch
expected = int(os.environ['FASTVIDEO_CI_GPUS'])
if not torch.cuda.is_available() or torch.cuda.device_count() != expected:
raise RuntimeError(f'Expected exactly {expected} CUDA devices')
for index in range(expected):
print(f'CUDA {index}: {torch.cuda.get_device_name(index)}', flush=True)
PY
# SSIM runs concurrent pytest subprocesses; a single global JUnit destination
# would race. Preserve each lane's native metrics plus the controller logs.
bash "$FASTVIDEO_CI_SCRIPT"