Add Buildkite CI pipeline (CPU jobs + manual-gated GPU suites) and remove PR test on GHA - #239
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a Buildkite pipeline configuration and generator script to port always-on CPU CI jobs from GitHub Actions to Buildkite. The review feedback suggests making the docker_command helper more robust by handling trailing spaces in extra_args and centrally passing GIT_CONFIG_PARAMETERS to avoid Git ownership errors. Consequently, the manual git config workaround in pre_commit_step and the trailing space in unit_step can be cleaned up. Additionally, a minor typo in the README documentation should be corrected.
| def docker_command(image: str, script: str, extra_args: str = "") -> str: | ||
| """`docker run` wrapper matching the GHA jobs' raw-docker style.""" | ||
| return ( | ||
| "docker run --rm --ipc=host --shm-size=4g " | ||
| f"{extra_args}" | ||
| f'-v "$PWD:{REPO_MOUNT}" -w {REPO_MOUNT} ' | ||
| f"{image} bash -c {shlex.quote(script)}" | ||
| ) |
There was a problem hiding this comment.
The current implementation of docker_command is fragile because it directly concatenates extra_args without ensuring proper spacing. If a caller passes extra_args without a trailing space (e.g., "--network host"), it will result in a malformed command (e.g., --network host-v "$PWD:/workspace").
Additionally, running git commands inside containers (which run as root) on a workspace owned by the host's buildkite-agent user will trigger Git's "dubious ownership" error. Centrally passing GIT_CONFIG_PARAMETERS as an environment variable in docker_command solves this globally for all steps, eliminating the need for manual git config workarounds in individual steps.
def docker_command(image: str, script: str, extra_args: str = "") -> str:
"""`docker run` wrapper matching the GHA jobs' raw-docker style."""
# Pass GIT_CONFIG_PARAMETERS to avoid "dubious ownership" errors since the container runs as root
# while the workspace is owned by the host's buildkite-agent user.
extra = f"{extra_args.strip()} " if extra_args.strip() else ""
return (
"docker run --rm --ipc=host --shm-size=4g "
'-e GIT_CONFIG_PARAMETERS="\\'safe.directory=*\\'" '
f"{extra}"
f'-v "$PWD:{REPO_MOUNT}" -w {REPO_MOUNT} '
f"{image} bash -c {shlex.quote(script)}"
)| def pre_commit_step() -> dict: | ||
| script = "\n".join( | ||
| [ | ||
| "set -euo pipefail", | ||
| # The checkout is owned by the host's buildkite-agent user; the | ||
| # container runs as root, so git refuses to touch it without this. | ||
| f"git config --global --add safe.directory {REPO_MOUNT}", | ||
| "pip install -q pre-commit", | ||
| "pre-commit run --all-files --show-diff-on-failure --color=always", | ||
| ] | ||
| ) |
There was a problem hiding this comment.
Since safe.directory is now centrally handled via GIT_CONFIG_PARAMETERS in docker_command, we can clean up and simplify pre_commit_step by removing the manual git config workaround.
def pre_commit_step() -> dict:
script = "\n".join(
[
"set -euo pipefail",
"pip install -q pre-commit",
"pre-commit run --all-files --show-diff-on-failure --color=always",
]
)
return step(
":lint-roller: pre-commit",
"pre-commit",
CPU_QUEUE_SMALL,
docker_command(PYTHON_IMAGE, script),
timeout=15,
gated=False,
)| return step( | ||
| ":pytest: unit & utils tests (in-image)", | ||
| "unit", | ||
| CPU_QUEUE_MEDIUM, | ||
| docker_command(CI_IMAGE, script, extra_args="--network host "), | ||
| timeout=45, | ||
| ) |
There was a problem hiding this comment.
With the updated docker_command handling trailing spaces robustly, we can remove the manual trailing space from the extra_args parameter here.
| return step( | |
| ":pytest: unit & utils tests (in-image)", | |
| "unit", | |
| CPU_QUEUE_MEDIUM, | |
| docker_command(CI_IMAGE, script, extra_args="--network host "), | |
| timeout=45, | |
| ) | |
| return step( | |
| ":pytest: unit & utils tests (in-image)", | |
| "unit", | |
| CPU_QUEUE_MEDIUM, | |
| docker_command(CI_IMAGE, script, extra_args="--network host"), | |
| timeout=45, | |
| ) |
| | `agent-adapter` | `agent-adapter-test` | `small_cpu_queue_premerge` | PR / manual builds | | ||
| | `unit` | `e2e-test-unit` | `medium_cpu_queue_premerge` | PR / manual builds | | ||
|
|
||
| All non-gate steps `depend_on` the pre-commit gate, matching the GHA |
There was a problem hiding this comment.
055f361 to
5fbcdeb
Compare
|
shrink the max_batched_tokens to 2048 as it will oom in h100, it's slime's problem and will optimized in the future |
b78da15 to
fa6af9f
Compare
Port the always-on jobs from .github/workflows/pr-test.yml.j2 (pre-commit gate, plugin contracts, agent adapter, in-image unit tests) to a single dynamically generated Buildkite pipeline targeting the vLLM elastic-stack CPU queues. GitHub Actions keeps running in parallel and stays authoritative; GPU suites are not migrated yet. https://claude.ai/code/session_01BSqHKH1FafdRobA7ZeRzBQ Signed-off-by: aoshen02 <aoshen@inferact.ai>
Drop generate_pipeline.py in favor of a plain static .buildkite/pipeline.yml defining the four always-on CPU steps directly (pre-commit gate, plugin contracts, agent adapter, in-image unit tests). Simpler to read and review for a first cut; the GHA workflow stays authoritative and GPU suites are still out of scope. Pass GIT_CONFIG_PARAMETERS into every container so git (in pre-commit) doesn't abort with "dubious ownership" on the host-owned checkout, and fix the depends_on typo in the README. https://claude.ai/code/session_01BSqHKH1FafdRobA7ZeRzBQ Signed-off-by: aoshen02 <aoshen@inferact.ai>
plugin-contracts failed in build #3 on tests/utils/test_hf_checkpoint_saver.py (ModuleNotFoundError: safetensors): the dep list predated the slime sync in #232 which added requests/ray/safetensors to the GHA template. Mirror it. GitHub PR labels can't trigger Buildkite jobs, so expose the run-ci-* GPU suites behind a block step instead: unblocking offers a multi-select of suites (short / vllm-config / megatron / precision / ckpt) and gpu_suites.py uploads one step per test with the same gpu_lock_exec + docker invocations and per-test DEEPEP/FP8/EVAL env combos as the GHA jobs. blocked_state: passed keeps the commit status green when the gate is left untouched. GPU steps target a new vime-gpu agent queue (self-hosted hosts; see README). https://claude.ai/code/session_01BSqHKH1FafdRobA7ZeRzBQ Signed-off-by: aoshen02 <aoshen@inferact.ai>
Build #4's unblock test showed the CI cluster rejects uploads targeting a nonexistent queue, and rather than minting a new queue, follow the pattern vllm-omni already uses for mithril-h100-pool: each GPU job is a Kubernetes pod (agent-stack-k8s kubernetes plugin) on an H100 SXM node with nvidia.com/gpu limits (4 or 8), memory-backed /dev/shm, and /mnt/hf-cache mounted as HF_HOME. vime tests hf-download their models, so the warm HF cache replaces the GHA runners' /mnt/nvme0n1/vime_ci mounts; the docker-run wrapper goes away since the pod runs the vime CI image directly. Also pin GLOO/TP_SOCKET_IFNAME=lo in the plugin-contracts container: test_metric_report_dist hung intermittently (build #4 timed out at 30 min) because gloo can pick a non-loopback interface inside a bridge-network container. https://claude.ai/code/session_01BSqHKH1FafdRobA7ZeRzBQ Signed-off-by: aoshen02 <aoshen@inferact.ai>
test_qwen3.5_0.8B_gsm8k_async_short OOMed in compute_log_probs on the mithril pool's 80 GB H100s (build #6) with 7 GiB reserved-but-unallocated — the allocator-fragmentation case PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True exists for. Scope it to this test's pod only (vLLM sleep-mode CuMemAllocator can conflict with expandable segments) via verbatim pass-through of non-VIME env overrides. The other short tests passed on H100 pods unchanged. https://claude.ai/code/session_01BSqHKH1FafdRobA7ZeRzBQ Signed-off-by: aoshen02 <aoshen@inferact.ai>
Builds #6/#7 isolated two test-level failures on the mithril 80 GB H100s, neither a pipeline issue: - gsm8k_async_short OOMs as tuned (67 GiB live on the actor GPU after expandable_segments eliminated fragmentation; its sync twin passes). - parallel_check's CP=2 grad norm diverges ~4% from the same-node baseline recording, a topology-sensitive numerical invariance question. Mark exactly these two soft_fail so they keep running and stay visible on Buildkite without failing the build; their authoritative gate remains the GHA label jobs. https://claude.ai/code/session_01BSqHKH1FafdRobA7ZeRzBQ Signed-off-by: aoshen02 <aoshen@inferact.ai>
Revert the soft_fail: per review, the gsm8k_async_short OOM and the parallel_check CP-invariance divergence should stay visible as hard failures on Buildkite until the underlying issues are fixed. Keep the diagnostic comments and the test-scoped expandable_segments setting. https://claude.ai/code/session_01BSqHKH1FafdRobA7ZeRzBQ Signed-off-by: aoshen02 <aoshen@inferact.ai>
Re-apply b334784 (reverted in 0a98010): per the follow-up decision, mark gsm8k_async_short and parallel_check soft_fail so they keep running visibly on mithril without failing the build, with the GHA label jobs as their authoritative gate until the OOM tuning and CP-invariance questions are resolved. https://claude.ai/code/session_01BSqHKH1FafdRobA7ZeRzBQ Signed-off-by: aoshen02 <aoshen@inferact.ai>
Root cause: Qwen3.5's 248K vocab produces [T, 248320] fp32 logits tensors. calculate_log_probs_and_entropy holds 5 copies simultaneously (2 clones + 2 intermediates + original). At max-tokens-per-gpu=9216, each copy is ~8.5 GB → 42.6 GB from logits alone, exceeding H100 80 GB with activations and reserved pool fragmentation. Fix: reduce max-tokens-per-gpu from 9216 to 2048. Peak drops from 117.6 GB to 39.6 GB (measured on H200), well within H100's 80 GB. GSM8K's longest sequence is ~1200 tokens, so 2048 still fits all samples. Also removes gsm8k_async_short from SOFT_FAIL_ON_H100 (no longer needed) and the expandable_segments workaround. parallel_check remains soft-fail: ~11% flake rate on TP4+per-token-loss, confirmed same behavior in slime (Megatron FP reduction-order issue). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: aoshen02 <aoshen@inferact.ai>
Signed-off-by: aoshen02 <aoshen@inferact.ai>
fa6af9f to
2f01009
Compare
Signed-off-by: khluu <khluu000@gmail.com>
Summary
Buildkite port of vime CI, mirroring the jobs in
.github/workflows/pr-test.yml.j2. GitHub Actions keeps running in parallel and stays authoritative until Buildkite proves itself..buildkite/pipeline.yml— always-on CPU steps (pre-commit,plugin-contracts,agent-adapter,unit), run on every PR and push tomain..buildkite/gpu_suites.py— therun-ci-*GPU suites (short,vllm-config,megatron,precision,ckpt) behind a manual block-step gate (PR labels can't trigger Buildkite), generating one K8s pod per test on themithril-h100-poolH100 queue..buildkite/README.md— one-time Buildkite setup and how to keep the test lists in sync with the GHA matrix.test_arguments.pyunit tests left behind by the sync: complete slime #1920, #1967, #1985 #232 arguments refactor.gsm8k_async_short(80 GB OOM) andparallel_check(CP-invariance) are markedsoft_failpending follow-up.Also deleted PR workflows on GHA
https://claude.ai/code/session_01BSqHKH1FafdRobA7ZeRzBQ