Skip to content

[CI] Bazel: Phase 3 — manifest job + bin_pack_shards.py (demo, stage 3 of 3) - #4

Draft
Kangyan-Zhou wants to merge 4 commits into
feat/bazel-pr-test-ymlfrom
feat/bazel-manifest-job
Draft

[CI] Bazel: Phase 3 — manifest job + bin_pack_shards.py (demo, stage 3 of 3)#4
Kangyan-Zhou wants to merge 4 commits into
feat/bazel-pr-test-ymlfrom
feat/bazel-manifest-job

Conversation

@Kangyan-Zhou

Copy link
Copy Markdown
Owner

Demo stack — stage 3 of 3 (final)

This stage adds the Phase-3 building blocks: a manifest job that emits est_time-bin-packed shards consumable as a GHA matrix. Replaces (eventually) both the heuristic check-changes job and run_suite.py's auto_partition.

Stack:

Components

  • tools/bin_pack_shards.py — reads bazel cquery output, groups by sgl-suite-* tag, bin-packs via greedy LPT (same algorithm as run_suite.py:auto_partition). Emits JSON keyed by suite name.
  • .github/workflows/pr-test.yml — new compute-test-manifest top-level job. Runs cquery + bin_pack on ubuntu-latest, uploads manifest.json as artifact. Currently no downstream consumer — Phase-3.5 is the hookup that wires shard jobs to consume.

Validation on H100

  • bazel cquery 'kind(py_test, //test/...)' --output=jsonproto ran cleanly against wired BUILDs.
  • bin_pack_shards.py produced 8 shards, LPT-balanced: shard 0 = 345s (test_gpt_oss_sm120 alone), shard 1 = 134s (test_srt_endpoint alone), etc.
  • Hard error paths fire correctly:
    • sgl-suite-X tag without est_time → exits 1 (was silent drop before review)
    • malformed json file → exits 1 with size + decode error
    • missing results key (empty {}) → exits 1 with truncation hint
    • --shards configured suite has zero matching targets → exits 1
  • Stdout is pure JSON; stderr carries ::warning:: GHA annotations.

Review history (in chat)

code-reviewer: ship it (no Critical/Important).

silent-failure-hunter found two CRITICAL test-disappearance failure modes plus two IMPORTANT UX issues. All addressed in 33fb929:

  • Suite-tagged-but-est_time-missing target → silent drop → hard error with codegen-resync hint
  • Malformed est_time → silent drop → hard error
  • json.JSONDecodeError unwrapped → wrapped with file/size/cause
  • Missing 'results' key → silently empty manifest → hard error
  • INFO on stderr (invisible in GHA) → ::warning:: workflow annotation
  • bazel jsonproto stringListValue schema change → silently no-tags → hard error
  • set -euo pipefail in Build manifest step (belt-and-suspenders)

🤖 Generated with Claude Code

Kangyan-Zhou and others added 4 commits May 4, 2026 22:15
Adds the compute-test-manifest pipeline that replaces (eventually) both
sglang's heuristic check-changes job and run_suite.py's auto_partition
bin-packing. This commit lands the building blocks; wiring downstream
shard jobs to consume the manifest is Phase-3.5 work.

Components:
- tools/bin_pack_shards.py: reads `bazel cquery --output=jsonproto`,
  groups targets by sgl-suite-* tag, bin-packs each suite into N shards
  via greedy longest-processing-time-first (LPT). Same algorithm as
  python/sglang/test/ci/ci_register.py:auto_partition. Outputs JSON
  keyed by suite name, value is a list of {id, targets, est_time}
  dicts directly consumable as a GHA matrix.

- .github/workflows/pr-test.yml: new compute-test-manifest top-level
  job. Runs on ubuntu-latest, installs bazelisk, runs cquery, bin-packs
  via the script, uploads manifest.json as an artifact for inspection.
  Currently no downstream consumer — that's the Phase-3.5 hookup that
  has to wait until parity with auto_partition is verified.

Validated on H100:
- bazel cquery 'kind(py_test, //test/...)' --output=jsonproto runs
  cleanly, producing target metadata for the 9 tagged tests.
- bin_pack_shards.py spreads them into 8 shards LPT-balanced: shard 0 =
  345s (test_gpt_oss_sm120 alone), shard 1 = 134s (test_srt_endpoint
  alone), etc.
- INFO messages for unconfigured suites surface them without erroring.
- Hard error when --shards configures a suite with zero matching
  targets (typo / stale config detection).

Behavioral notes:
- Targets without both sgl-suite-* and est_time:* tags are silently
  skipped (not part of any runnable suite under our convention).
- Empty shards (shard_count > target_count) dropped from output so
  matrix expansion doesn't spawn empty jobs.
- Shard ids are deterministic (sorted by id) so matrix consumers can
  rely on shard 0 always existing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three fixes from the silent-failure-hunter agent on 44d6450, all
gated against the same failure mode (a wired BUILD target silently
disappearing from the test manifest):

CRITICAL — convert silent skip to hard error in _extract_targets:
- Target carrying sgl-suite-X but missing or malformed est_time:N is
  now a hard error with a "Re-run codegen" hint. The prior `continue`
  would have produced a "looks-wired, isn't-wired" bug: BUILD shows
  the suite tag, bazel query lists the test, but the manifest excludes
  it and CI never runs it.
- Asymmetric: missing-suite is still a legitimate skip (target isn't
  claimed by any CI suite); only suite-without-est_time is fatal.

IMPORTANT — JSON load and missing-results-key error UX:
- Wrap json.loads with json.JSONDecodeError and OSError handlers.
  Each path exits with file path, byte size, and likely cause.
- Validate "results" in payload — Bazel's jsonproto for zero matches
  is {"results": []}, never {}. Missing key is a truncated payload.

IMPORTANT — drift detection via GHA workflow annotations:
- "suite has targets but no --shards entry" was an INFO line on stderr,
  invisible in PR Checks. Promote to ::warning:: format on stderr
  (stdout is reserved for the JSON manifest). Surfaces on the GitHub
  Actions PR page.

IMPORTANT — bazel jsonproto schema fragility:
- If the tags attribute exists but lacks stringListValue, append to
  errors instead of silently treating as no-tags.

YAML belt-and-suspenders:
- Explicit `set -euo pipefail` at the top of the Build manifest step.
  GHA's bash default already includes -eo pipefail, but documents
  intent against future edits splitting the cquery + python3 pair.

Validated on H100:
- Happy path: stdout = pure JSON, stderr = ::warning:: lines.
- Synthetic sgl-suite-X-without-est_time exits 1 with attributable msg.
- Synthetic empty-{} jsonproto exits 1 with truncation hint.
- Synthetic non-JSON file exits 1 with size + decode error.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the manifest-driven matrix that PR description listed as out-of-
scope follow-up. Includes it in this PR per reviewer request since
stage-b-test-1-gpu-small is where the bulk of the wired tests live.

compute-test-manifest now exposes `shards` as a job output (in addition
to the artifact upload), so downstream matrices can read it without an
extra download-artifact round-trip.

New job stage-b-test-1-gpu-small-bazel-sharded:
- needs: [..., compute-test-manifest]
- matrix.shard expands over fromJSON(needs...outputs.shards)['stage-b-...']
- continue-on-error so a Bazel-side failure can never block the existing
  run_suite.py-driven shard jobs during shadow-running.
- Coexists with the existing stage-b-test-1-gpu-small (which still runs
  the run_suite.py 8-way partition AND the partition-0 parallel-shipping
  bazel step from feat/bazel-pr-test-yml). Both can be dropped once
  parity is verified for one or two release cycles.
- Each shard runs `bazel test ${{ matrix.shard.targets }}` — the
  space-separated label list that bin_pack_shards.py packed into this
  shard.
- Coredump suffix is `bazel-shard-<id>` so artifacts don't collide with
  the existing partition-id-suffixed uploads.

Validated on H100 by simulating one shard's worth of work:
- bazel cquery → bin_pack_shards.py → manifest.json
- Extract shard 7's targets via jq-equivalent in python
- bazel test $TARGETS --cache_test_results=no
- 2 packed tests run, 2 pass in 22.7s (est_time was 16s for the shard).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two important fixes from the code-reviewer agent on 35bea9c:

- Bazelisk install in stage-b-test-1-gpu-small-bazel-sharded was using
  `sudo mv` but the existing partition-0 install in stage-b-test-1-gpu-
  small (the proven path on this runner class) uses bare `mv`. The
  1-gpu-5090 self-hosted runners are configured with the runner user
  owning /usr/local/bin, so sudo would fail. Switched to bare `mv` to
  match. The compute-test-manifest job stays on `sudo mv` because it
  runs on ubuntu-latest (GitHub-hosted) where sudo is required.

  All three install blocks now agree by runner type. Comment added
  flagging the divergence so a future composite-action lift handles
  both variants.

- Added max-parallel cap on the new sharded job, mirroring the
  predecessor stage-b-test-1-gpu-small (`fromJson(needs.check-changes.
  outputs.max_parallel_small)` = 3 for filtered runs, 8 for full).
  Without the cap, full runs would have spawned 8 unthrottled bazel
  shards on top of the existing 8 run_suite.py partitions, starving
  the 1-gpu-5090 pool for other in-flight PRs. The cap returns the
  bazel path to roughly the same runner footprint as the existing
  flow during the shadow-running window.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant