Skip to content

[DO NOT MERGE] CI 1 - #2407

Open
fzyzcjy wants to merge 221 commits into
tom/refactor-miles/deliver-1-basefrom
tom/refactor-miles/deliver-1
Open

[DO NOT MERGE] CI 1#2407
fzyzcjy wants to merge 221 commits into
tom/refactor-miles/deliver-1-basefrom
tom/refactor-miles/deliver-1

Conversation

@fzyzcjy

@fzyzcjy fzyzcjy commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator

ci-sglang-pr: tom/refactor-miles-repo-sglang/deliver-1

Replacement for #2196, which was accidentally merged as a zero-commit PR while the base ref briefly pointed at the head tip during chain absorption.

@fzyzcjy fzyzcjy added run-ci-image bypass-fastfail Disable CI fast-fail: run all stages and all tests to surface every failure labels Aug 12, 2026
@fzyzcjy

fzyzcjy commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 Posted autonomously by Claude Code acting on the user's behalf. I downloaded the completed CPU shard's full log, identified the first root failure, and classified it before taking any rerun action. Please push back if any conclusion is off.

The stage-a-cpu (1) / run-cpu job failed with 97 cascading tokenizer-test failures after Hugging Face returned HTTP 429 Too Many Requests for the Qwen3-4B Xet token endpoint. The first root fingerprint is:

ConnectionError: Network error: Request error: HTTP status client error (429 Too Many Requests), domain: https://huggingface.co/api/models/Qwen/Qwen3-4B/xet-read-token/...

This is an external download-rate-limit failure rather than a Miles assertion failure, as shown by the same job log. GPU siblings are already active in the PR checks, so I will leave them undisturbed and rerun the failed job after the workflow settles.

@fzyzcjy

fzyzcjy commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

CI triage update from Codex (AI agent), based on the complete logs of every completed GPU failure below.

The completed GPU failures on head 8021c9073f are one code bug, not independent test regressions:

All four logs have the same primary fingerprint:

AssertionError: cli argv roundtrip mismatch on device: parsed 'None' != wanted 'cuda'

Classification: our code bug, fixed on the new head.

Root cause: _compute_server_args preserves argparse's raw device=None. ServerArgs then auto-detects and resolves that value to cuda, but the always_render_fields path preferred the present raw key even when its value was None. It consequently emitted --device None, and the next parse produced the literal string "None" rather than the resolved accelerator.

Fix: commits 0778e163a5 and a58b3f6c39 make raw None mean unspecified for always-rendered fields and fall back to the constructor-resolved value. Both tom/refactor-miles/deliver-1 and tom/refactor-miles/op13-142 now point to a58b3f6c39; tom/refactor-miles/deliver-1-base remains at the chain root.

Pre-push validation on a 1x H200 devbox:

  • Regression proof: the two new tests failed before the source fix with the exact fingerprint above, then passed after it.
  • Relevant argv/ServerArgs/RouterArgs fast tests: 624 collected, successful.
  • Full fast suite on the absorbed chain: 7,011 passed, 30 skipped, 4 xfailed.
  • pre-commit run --all-files: passed.
  • Post-fix gate: no test weakening; no documentation contract became stale.

The new final-head CI is now running on a58b3f6c390e176d17510a39afa2e2ff032c90f5. I will continue tracking it and triage any new failure independently.

@fzyzcjy

fzyzcjy commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

CI audit update (AI-assisted by Codex)

@fzyzcjy

fzyzcjy commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Final CI audit (AI-assisted by Codex)

  • Final head: a58b3f6c390e176d17510a39afa2e2ff032c90f5.
  • Authoritative [DO NOT MERGE] CI 1 #2407 checks are complete and green: pre-commit, all CPU shards, all ROCm MI300X shards, and all H100/H200 GPU shards in PR Test run 31561390573.
  • The earlier duplicate Rename the trainer group and cell to TrainerController and TrainerCell #2176 CPU failure was a superseded unpinned-SGLang context. After adding the same ci-sglang-pr pin, targeted PR Test run 31563479231 and ROCm run 31563479644 completed successfully, including the previously failing CPU shard.
  • GitHub now reports mergeStateStatus=CLEAN; there are no queued or running checks.
  • Final refs verified: deliver-1 = op13-142 = a58b3f6c390e176d17510a39afa2e2ff032c90f5; deliver-1-base = chain root d2e3ad9f197d86c138347063fdd9750dbd3d296e.
  • All temporary validation devboxes were unbound and released.

@fzyzcjy
fzyzcjy force-pushed the tom/refactor-miles/deliver-1 branch from a58b3f6 to 0963e0e Compare August 12, 2026 09:32
@fzyzcjy

fzyzcjy commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

@fzyzcjy

fzyzcjy commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

Final CI audit for head 0963e0e:

  • Ref integrity: tom/refactor-miles/deliver-1 and tom/refactor-miles/op13-142 both point to this head; tom/refactor-miles/deliver-1-base remains at chain root d2e3ad9.
  • Remote validation: targeted argv suite passed with 620 passed and 4 xfailed; the full fast suite passed with 6890 passed, 30 skipped, and 4 xfailed; all pre-commit hooks passed.
  • Authoritative full workflow run: https://github.com/radixark/miles/actions/runs/31583550632
  • Every H100/H200 lane was validated from its complete log, not merely from the check conclusion. Each selected a nonzero test count and ended with a matching N/N Test Summary.
  • GPU summaries: stage-b 2-GPU H200 2/2; stage-c 2-GPU H200 4/4 and 5/5; stage-c 4-GPU H200 8/8, 9/9, and 9/9; stage-c 8-GPU H200 3/3 and 4/4; stage-c 8-GPU H100 10/10 and 12/12.
  • The initial stage-b CPU job failed while downloading from Hugging Face with HTTP 429. Its rerun job 94118753909 passed with 86 passed and 18 skipped.
  • Run 31583560405 came from another PR/branch sharing the same SHA. Its seconds-long GPU jobs reported No tests found / No tests to run, so they were treated as no-op checks and were not used as coverage evidence.

Final rollup: 40 successful checks, 2 skipped approval-holder checks, no pending or failing checks. PR is MERGEABLE/CLEAN.

@fzyzcjy
fzyzcjy force-pushed the tom/refactor-miles/deliver-1 branch 3 times, most recently from 53e2412 to b27e9a0 Compare August 12, 2026 14:36
@fzyzcjy

fzyzcjy commented Aug 12, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 Posted autonomously by Claude Code acting on the user's behalf. CI is green on 4802a93e9a. This comment records the two real failures that were fixed, and the three GPU failures that were diagnosed as infra flakes and cleared by a rerun. Please push back if any conclusion is off.

Green on 4802a93e9a

All 20 non-skipped checks pass; mergeStateStatus=CLEAN. Run: https://github.com/radixark/miles/actions/runs/31619024942 (checks view)

Run pre-commit, resolve-ci-policy, docker-paths, docker-build, resolve-ci-image, stage-a-cpu (0..3), stage-b-cpu, stage-b-2-gpu-h200, stage-c-2-gpu-h200 (0,1), stage-c-4-gpu-h200 (0,1,2), stage-c-8-gpu-h100 (0,1), stage-c-8-gpu-h200 (0,1).

Real failures that were fixed

1. pre-commit — formatting only

ruff-check (5), isort (4 files), black (7 files), all files were modified by this hook, no remaining errors, all under tests/. Fixed in bf85b6be65.

2. Two stale tests

  • tests/fast/ray/rollout/test_cell_state.pyStateInitializing gained a required start_time field but the test's _STATE_FACTORIES was not updated. Fixed in 6834694247.
  • tests/fast/utils/api_server/test_server.py — the test still stubbed uvicorn.run and server.threading, but _start_api_server_raw had moved to uvicorn.Server(uvicorn.Config(...)) plus _start_and_wait_thread. Rewritten to assert against the real startup path in 85dc42f52b.

3. stage-a-cpu shard 0 — missing optional dependency at collection

Failing job: https://github.com/radixark/miles/actions/runs/31612662504/job/94184605724

ERROR collecting tests/fast/backends/megatron_utils/update_weight/test_connection_contract.py
  .../p2p_transfer_utils.py:11: from mooncake.engine import TransferEngine
E ModuleNotFoundError: No module named 'mooncake'
!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!

mooncake is an optional RDMA dependency present in neither requirements.txt nor tests/ci/requirements-ci-cpu.txt, so it does not exist on the GitHub-hosted CPU runner. The module-scope import predates this branch; what is new is test_connection_contract.py, the first CPU-side test to import the p2p updater. Moving the import to its single use site in create_transfer_engine() fixes it, matching how miles/utils/object_store.py already treats mooncake as optional. Fixed in 4802a93e9a.

Three GPU failures, all diagnosed as infra flakes and cleared on rerun

All three were on attempt 1 of run 31619024942 and all three pass on attempt 2. Each rerun was checked against the anti-fake-green criteria (non-zero Enabled N test(s), plausible wall-clock, explicit per-test PASS) rather than accepted on the green badge alone.

Lane Test Attempt-1 fingerprint Attempt-2
stage-c-4-gpu-h200 (1) test_dp_attention.py gloo Connection closed by peer in _send_to_colocated_engine PASS elapsed=763s, 9/9 tests
stage-c-4-gpu-h200 (2) test_r3_baseline.py trainer rank0 vanished with no output; TCPStore EOF + raylet SYSTEM_ERROR PASS elapsed=941s, 9/9 tests
stage-c-8-gpu-h100 (0) test_r3_mtp.py Cuda failure 999 'unknown error' via NCCL inside TransformerEngine CP attention PASS elapsed=802s, 10/10 tests, CI Gate TRUSTED (18 metric(s))

Attempt-1 jobs: 4-gpu (1), 4-gpu (2), 8-gpu-h100 (0).

Why these read as infra rather than regressions:

  • None of the three raised from miles Python logic. Two are a peer training process disappearing with no self-reported error (the gloo FIN and the TCPStore EOF are two transports noticing the same class of event; raylet's own message lists OOM-killer / SIGSEGV as the usual causes). The third is a CUDA driver error surfacing inside third-party TransformerEngine/NCCL code right after a sleep/wake GPU memory reload.
  • test_dp_attention failed in code this branch did not change since a green run. The same lane passed at 0963e0efc, and git diff 0963e0efc..4802a93e9a -- miles/backends/megatron_utils/update_weight/ is only the two-line mooncake change above.
  • reloadable_process_group.py, on the causal path of the CUDA-999 failure, has zero commits in deliver-1-base..HEAD.
  • Sibling tests in the same jobs passed, including other tests exercising the same wake_up / colocate pattern.
  • Where this branch is in the blast radius — the new RayWorkerHandle death-confirmation path and TrainerCell error handling — it behaved correctly: it detected the dead worker, marked the cell errored, and failed fast with a clear WorkerUnreachableError rather than hanging or silently corrupting state.

Residual uncertainty worth stating: GitHub Actions logs carry no host dmesg, so a kernel-level OOM-kill of the vanished processes can be neither confirmed nor excluded. If the "silent trainer process death" signature recurs on this branch, host memory pressure is the first thing to check.

@fzyzcjy
fzyzcjy force-pushed the tom/refactor-miles/deliver-1 branch 4 times, most recently from 2f5a8ba to 1530b0b Compare August 13, 2026 03:25
@fzyzcjy

fzyzcjy commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 Posted autonomously by Claude Code acting on the user's behalf. I compared the two duplicate PR Test runs on this head, cancelled the one that resolved to an empty label scope, verified the green CPU stages actually executed tests rather than no-opping, and ran the full stage-a-cpu suite on an H200 devbox against this exact head. Please push back if any conclusion is off.

Head under test

1530b0b4398eda9284de1dda88bd84ed4dddd75f1530b0b

Two duplicate runs on the same SHA; the no-coverage one is cancelled

Two pull_request runs were created 14s apart for this head and resolved to different policies:

Run Resolved policy Verdict
31663956556 cadence=regular labels=[] bypass_fastfail=false (policy job log) empty label scope → near-zero coverage. Cancelled.
31663968951 cadence=regular labels=[run-ci-image bypass-fastfail] bypass_fastfail=true (policy job log) authoritative

The CANCELLED entries in the checks list all belong to the first run and are intentional, not test failures. Everything below refers to the authoritative run 31663968951.

CPU gate is green, and it really executed tests

pre-commit SUCCESS: https://github.com/radixark/miles/actions/runs/31663968813/job/94334512898
resolve-ci-policy, docker-paths, docker-build, resolve-ci-image all SUCCESS: https://github.com/radixark/miles/actions/runs/31663968951

A green GPU/CPU job can still be a no-op, so each was checked for a non-zero Enabled N test(s) and a real result summary:

Job Enabled Result Duration
stage-a-cpu (0) 80 1136 passed, 12 skipped 2:59
stage-a-cpu (1) 79 2458 passed, 4 skipped 2:30
stage-a-cpu (2) 95 1953 passed, 15 skipped 5:09
stage-a-cpu (3) 100 2233 passed, 8 skipped, 4 xfailed 4:09
stage-b-cpu 3 86 passed, 18 skipped 3:55
stage-b-2-gpu-h200 2 Test Summary: 2/2 passed 1:42

stage-b-2-gpu-h200 finishing in 1:42 is short enough to look like a no-op, but it declares Enabled 2 test(s) and reports 2/2 passed, so it is a genuine — merely tiny — lane. It should not be counted as meaningful GPU coverage; that comes from the stage-c lanes.

The four stage-a-cpu shards sum to 354 enabled files, which matches the 354 the full suite enables when run unsharded, so no file silently dropped out of the suite.

The nine stage-c lanes (2-gpu-h200 ×2, 4-gpu-h200 ×3, 8-gpu-h100 ×2, 8-gpu-h200 ×2) are still in progress; I will follow up with their results.

Dependency pin is current

The PR body pins ci-sglang-pr: tom/refactor-miles-repo-sglang/deliver-1. Against its target branch:

gh api repos/sgl-project/sglang/compare/sglang-miles...tom/refactor-miles-repo-sglang/deliver-1
→ {"ahead_by": 4, "behind_by": 0}

behind_by = 0, so CI is not running against a stale sglang.

Devbox pre-verification of the same head: zero real failures

Run on an H200 devbox with radixark/miles:dev, miles at 1530b0b439 and /sgl-workspace/sglang at 637fce854f (the pinned branch tip):

Pass Command Result
1 — emulating the CPU runner CUDA_VISIBLE_DEVICES= python tests/ci/run_suite.py --hw cpu --suite stage-a-cpu --labels run-ci-image --continue-on-error 7519 passed, 22 skipped, 4 xfailed, 280 errors in 649s; zero FAILED
2 — GPU visible, re-running only the affected files pytest over the 18 files that errored in pass 1 313 passed in 1891s

All 280 errors in pass 1 collapse to one cause — ValueError: Attempting to start raylet with 8 GPU, but CUDA_VISIBLE_DEVICES contains [] — the known artifact of blanking CUDA_VISIBLE_DEVICES on a GPU host, not a test failure. Pass 2 confirms each of those tests passes with the GPU visible.

Caveat: a green devbox is not evidence for a green CPU runner. The hosted CPU runner has no accelerator and roughly 16GB of RAM, so accelerator-probing and resource-ceiling failures cannot appear on a devbox. The stage-a-cpu results above are the actual gate, and they are green.

Note on the earlier queueing

These CPU stages sat QUEUED for about 50 minutes. That was repo-wide runner starvation, not a problem with this PR: at the time the repo had 196 queued runs against 15 in progress, 167 of them PR Test on tom/refactor-miles/op<N>-<M> marker branches re-triggered by a force-push that restacked the refactor chain. The queue drained on its own and no bulk cancellation was needed.

@fzyzcjy
fzyzcjy force-pushed the tom/refactor-miles/deliver-1 branch 2 times, most recently from 0dce880 to e6999a7 Compare August 13, 2026 07:06
@fzyzcjy

fzyzcjy commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 Posted autonomously by Claude Code acting on the user's behalf. The chain was rebased onto upstream main and promoted; I fixed the three integration gaps that broke the CPU stages, then root-caused the two remaining GPU failures. Please push back if any conclusion is off.

Head under test

e6999a73d612beceaa1c51edb939ca7c9d90a462 — authoritative run 31676416822

The chain now sits on upstream main 6033ac0eafb2f86733d37c5dd2958573a88c2735 (previously d2e3ad9f197d86c138347063fdd9750dbd3d296e), so this PR's base branch moved with it.

CPU gate is green

Job Result
stage-a-cpu (0)(3) SUCCESS
stage-b-cpu SUCCESS
stage-b-2-gpu-h200 SUCCESS
stage-c-8-gpu-h200 (1) SUCCESS
pre-commit, docker-build, docker-paths, resolve-ci-{policy,image} SUCCESS

The previous run on this chain had six CPU failures. All six came from three integration gaps left by the rebase onto new main, each fixed in the commit that owns it (no new commits, chain invariant preserved):

Gap Symptom Fixed in
miles/rollout/session/core.py still called merge_samples_with_addition_r3(self.args, …) after SessionCore switched to a SessionServerConfig AttributeError: 'SessionCore' object has no attribute 'args' ff588a6d8e Avoid session server input using full args
SessionServerConfig gained the upstream fields session_message_matcher and pause_generation_mode, but the test literal was not updated ValidationError: 2 validation errors for SessionServerConfig 4b1a3e55da Render the sglang server and router argv through one renderer
Upstream #2485 moved examples/fully_async to examples/infra_features/fully_async; one of the two references was left behind ModuleNotFoundError: No module named 'examples.fully_async' 54490c3bac Move load_function into miles.utils.function_registry

Each fix landed in max(last commit touching the file, commit introducing the problem), so replaying the chain produced no conflicts and the tree diff against the pre-fix chain is exactly those three hunks.

Two GPU failures remain, and both are upstream defects this rebase inherited

1. stage-c-2-gpu-h200 (0)job 94374011017

FAILED tests/e2e/sglang/test_session_v1_v2_parity.py::test_qwen3_8b_h200_fa3_agentic_v2_drop_retries_matches_v1_training_payload_bitwise
AssertionError: sample.rollout_id is not bitwise equal
left = None, right = 0, path = 'sample.rollout_id'

miles/rollout/generate_hub/agentic_tool_call.py assigns rollout_id only on the v2 branch:

samples = result.samples
if use_v2:
    # FIXME: handle sample index issues.
    rollout_id = input.sample.rollout_id if input.sample.rollout_id is not None else input.sample.index
    assert rollout_id is not None, "v2 agentic samples require input Sample.rollout_id or Sample.index"
    for sample in samples:
        sample.rollout_id = rollout_id

so v1 keeps None while v2 gets 0, and the parity test compares every field. Evidence that this is upstream's, not the chain's:

  • That block is byte-identical between 6033ac0eaf and the chain tip — the chain never touched it.
  • It does not exist in the previous base d2e3ad9f19 (0 occurrences of rollout_id in that file there, 3 in the new base), which is why the same lane was green before the rebase.
  • tests/e2e/sglang/test_session_v1_v2_parity.py is byte-identical across the old base, the new base and both chain tips.
  • Neither the upstream nor the chain copy of tests/session_parity_utils.py mentions rollout_id, so no field-exclusion was dropped by the chain.

2. stage-c-8-gpu-h200 (0)job 94374011197

FAILED: tests/e2e/fsdp/r3/test_qwen3_5_35b_a3b_r3.py (exit code 1)
Error: 'Qwen3_5MoeTextConfig' object has no attribute 'intermediate_size'. Did you mean: 'moe_intermediate_size'?
  File "miles/backends/fsdp_utils/actor.py", line 120, in init
  File "miles/utils/flops_utils.py", line 180, in flops_args_from_hf_config

flops_utils.py:180 reads the attribute bare, while every neighbouring field goes through a fallback helper:

ffn_hidden_size=config.intermediate_size,
kv_channels=_first(config, "head_dim", default=hidden_size // num_attention_heads),
moe_router_topk=_first(config, "num_experts_per_tok", "moe_topk", default=1),

Evidence that this is upstream's:

  • miles/utils/flops_utils.py is byte-identical to 6033ac0eaf — the chain does not modify it.
  • The call site is flops_args_from_hf_config(self.hf_config) at line 120 of miles/backends/fsdp_utils/actor.py in both the base and the chain tip, and the chain's diff on that file contains no change to this path.

Both are reported here rather than patched, since patching upstream semantics inside this chain would pollute its diff. Happy to carry a workaround on deliver-1 if that is preferred.

Devbox verification

Run on a 1×H200 devbox with radixark/miles:dev, /sgl-workspace/sglang at the pinned tom/refactor-miles-repo-sglang/deliver-1 tip. tests/fast --collect-only at the chain tip collects 9331 tests with 4 collection errors; three of them (test_scenario_ft_random.py, test_misc.py, test_process_identity.py) were verified to be pre-existing — the imported symbols are absent in both the old and the new chain and the three test files are byte-identical between them — and the fourth is an artifact of the sync excluding .git, so git ls-files fails on the devbox but not in CI.

A full old-chain-versus-new-chain comparison run is still in flight; I will follow up with the attribution.

@fzyzcjy

fzyzcjy commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator Author

🤖 Posted autonomously by Claude Code acting on the user's behalf. Follow-up to the previous comment: I ran the old chain and the new chain through the same suite on the same devbox and diffed the failing test-id sets to attribute every failure. Please push back if any conclusion is off.

The rebase onto upstream main introduces zero test regressions

Same command, same devbox, same pinned sglang/Megatron, same --ignore/--deselect set, run against each chain tip:

old chain db77cc1196 new chain 30179d1317
failed 212 212
passed 8497 8736
errors 309 320

Counts alone prove nothing, so the sets were diffed by test id:

  • 211 of the 212 failures are identical on both sides.
  • Introduced: 1tests/fast/doc/test_sync_example_docs.py::test_docs_examples_matches_the_readmes
  • Fixed: 1tests/fast/utils/test_command_utils.py::TestExecuteTrain::test_lets_the_environment_override_nvls
  • The 11 extra errors are all test_train_data_conversion.py::TestPostProcessRewards, tests upstream added in the new base. They hit the same raylet artifact as every other error here, and have no counterpart on the old chain.

The one "introduced" failure is a test-environment artifact, not a regression

error: mirrored but not listed in examples/README.md — add a bullet for:
  examples/fully_async

examples/fully_async and docs/examples/fully_async contain zero files in the new base, in this PR's head, and at the chain tip — only the old chain has that directory. The devbox still had leftovers of it because the sync step does not delete removed files, and my earlier cleanup pass only swept *.py, so README.md and run-qwen3-4b-fully_async.sh survived and tripped the docs drift gate. After removing them, that file reports 19 passed.

So the attribution is introduced = 0, which matches what the rebase report claimed on static grounds.

The 320 errors on both sides are the known consequence of blanking CUDA_VISIBLE_DEVICES on a GPU host (ValueError: Attempting to start raylet with 8 GPU, but CUDA_VISIBLE_DEVICES contains []), and the 211 shared failures come from running the full pytest tests/fast surface rather than the 354 files run_suite.py --hw cpu --suite stage-a-cpu actually registers — that registered set is green in CI here. Neither set is attributable to this rebase.

CI status on e6999a73d612beceaa1c51edb939ca7c9d90a462

Run 31676416822: 13 green, 2 red, remaining GPU shards still running.

Green: stage-a-cpu (0..3), stage-b-cpu, stage-b-2-gpu-h200, stage-c-2-gpu-h200 (1), stage-c-4-gpu-h200 (1), stage-c-8-gpu-h200 (1), plus pre-commit, docker-build, docker-paths, resolve-ci-policy, resolve-ci-image.

The two red shards are the upstream defects detailed in the previous comment — the v2-only rollout_id assignment that breaks the v1/v2 parity test, and the bare config.intermediate_size read in flops_utils.py that Qwen3_5MoeTextConfig does not satisfy. Both live in code byte-identical to 6033ac0eaf, on paths this chain does not modify.

fzyzcjy added 27 commits August 14, 2026 13:50
Per the review decision, FT test actions now name their target cell with a
full cell id instead of a positional cell_index, superseding the index-based
addressing that had to be resolved at runtime.

- FTTestAction drops cell_index (and its -1 "last cell" convention) in favour
  of a required cell_id; resolve_cell_id is gone.
- Loading validates every cell_id parses, so a typo fails at startup instead
  of silently never matching.
- The group executor asserts the target's spec name and cell index fit the
  group before dispatching, then forwards the cell id verbatim to
  stop_cells/start_cells; no runtime num_cells denominator is involved.
- The actor executor no longer takes a cell_ids roster and matches by plain
  string equality on cell_id and rank.
- The two e2e FT scenarios build the cell id from their statically known
  num_cells, and the --ci-ft-test-actions help text documents the new shape.

Tests not executed (test run ban); statically checked only.
RayWorkerHandle mirrors RpcWorkerHandle: keyword-only magic dispatch
that translates actor death into WorkerUnreachableError. wait_dead
joins the handle contract so callers that kill a worker can confirm it
is gone without knowing the transport; the rpc side has no platform
death signal yet and says so.
The checkpoint conversion step in all three GLM guides still calls scripts/model_args.py,
which no longer exists, so anyone following the recipe gets a file-not-found before the
conversion starts.
…celerators

A CPU suite runs many files in one pytest process, so whichever file calls ray.init
first fixes the cluster's resources for every later file: a second initializer produces
a cluster without the logical GPUs the placement-group tests need. Route the remaining
callers through the session-scoped ray_local_mode fixture and add a test that keeps
tests/conftest.py the only caller.

Also reap the accelerator processes a suite leaves behind, give the mock engine the
inject_fault the real CommandActor has, and fix the tests that were asserting shapes
main had already moved past.
The train actor used to tell the rollout side which weights the engines
now serve via `self.rollout_manager`, but that attribute stopped being
assigned once the actor was decoupled from the rollout side, leaving two
dead call sites: the FSDP one also referenced an unimported `ray`. The
executor's `weight_version` therefore stayed None forever and every
sample it collected went to training unstamped.

The actor's `update_weights` now answers the version it broadcast, the
trainer controller passes it up, and the driver writes it into the
executor through one `update_weights` helper the three training loops
share. An actor that skipped the broadcast answers None and nothing is
published.

The driver owns the write because it is the only place that holds both
handles for the whole chain: the controller's own executor handle goes
away when the trainer controller becomes an independent worker.
The executor only learns the served weight version when the driver forwards
what TrainerController.update_weights returns. A training script that calls
update_weights directly keeps training fine while the executor's version stays
frozen, which silently disables --max-weight-staleness accounting.

Count rollouts served since the last publish and assert the count stays small,
so the broken wiring fails loudly within a few steps instead of never.
The api server ran on a daemon thread through uvicorn.run, so a startup failure
only produced a traceback nobody watched. A second concurrent job on the same
host then polled 127.0.0.1:18080 and read the first job's cell registry, and
the two runs silently drove each other's cells.

Wait for uvicorn to report started and re-raise the failure on the caller
thread. The wait itself is a small private helper, since a daemon thread that
dies alone is invisible to whoever started it.
The reset_tensors check ran after the cell had already moved to PendingWeights.
A failing check raised into the tick loop, which only logs, and no later tick
retried it because the state had moved on. Readiness accepts PendingWeights, so
a check the user explicitly asked for was silently skipped, and the run could
even pass on tensors that happened to match because they were never poisoned.

The check hung off an elif of the serve-without-weight-update branch, which tied
it to a condition it does not depend on. Spell that condition out as its own if
and move the call above the transition, so a failure leaves the cell
Initializing and the next tick retries it.
StateInitializing had no failure exit: _tick_when_initializing returned on a
failed health probe, the health checker only runs in PendingWeights and Serving,
and an initializing cell reports Pending with no Healthy condition, so the mini
ft controller read NotApplicable and skipped it. WorkerManager.alive reads a
local actor list that a dead subprocess never updates, so nothing healed the
cell either. A replacement whose engine died during startup stayed stranded
until the next hour-long readiness timeout, long after the evidence was gone.

Stamp the startup time on StateInitializing, and report Healthy=False with
reason StartupDeadlineExceeded once a cell sits there past the deadline. A
false Healthy verdict already outranks phase in the mini ft controller, so it
heals the cell through the normal channel, as does any external controller
reading the same status.
MilesRouter only served /add_worker and /list_workers, so ServerCell.dispose hit
the catch-all proxy and its error was swallowed. Every cell generation appended
a fresh URL to worker_request_counts, worker_failure_counts and dead_workers and
nothing ever removed one. list_workers kept returning dead workers, and the
synchronous class-based rollout aborts against the full list every step, paying
60 retries per dead address before the gather finally raises.

Add /remove_worker with the same URL conventions as /add_worker, and share the
URL parsing between them.
All three calls at the top of EvalFleet.pin were broken: RolloutServer no
longer has recover() or wait_all_engines_alive(), and
ServerCell.probe_and_mark_dead() referenced is_allocated/_mark_stopped,
which no longer exist, so it raised AttributeError on every call. The
surrounding try/except swallowed the failure into EvalSkip("unhealthy"),
so every eval-fleet pin silently degraded to a skipped eval.

The eval fleet does not support fault tolerance, so the path is removed
rather than reimplemented: pin now goes straight to the weight load, and
the now-unreachable RolloutServer.probe_and_mark_dead and
ServerCell.probe_and_mark_dead are deleted along with their tests.
@fzyzcjy
fzyzcjy force-pushed the tom/refactor-miles/deliver-1 branch from 66c26ce to 97bba45 Compare August 14, 2026 05:53
@fzyzcjy fzyzcjy changed the title [DO NOT MERGE] CI [DO NOT MERGE] CI 1 Aug 14, 2026
@fzyzcjy

fzyzcjy commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator Author

CI status after rebasing the chain onto latest main

Head is now 97bba45562e6a5bb5daffeb28d125be97a0fa052, rebased from base 6033ac0eaf onto 1200fca8f8f3fe011d9a95707d5a562478060843 (14 upstream commits). 356 commits replayed, 0 merge commits, 0 empty commits.

Result: 24 success / 2 failure / 9 skipped. Every CPU lane, Run pre-commit, and every CUDA GPU lane is green. The only red is the ROCm stage-c-4-gpu-mi350 pair, and the evidence below shows it is a missing sglang feature in the ROCm CI image, not something this branch introduces.

Runs: PR Test #31774484373 · PR Test (ROCm) #31774484412

Green lanes, with durations

Durations are included because a fast "success" can mean the lane never actually ran. These are real runs.

Lane Result Duration
Run pre-commit success
stage-a-cpu (0..3) success 4m / 5m / 6m / 7m
stage-b-cpu success 5m
stage-b-2-gpu-h200 success 1m
stage-c-2-gpu-h200 (0,1) success 51m / 44m
stage-c-4-gpu-h200 (0,1,2) success 77m / 69m / 93m
stage-c-8-gpu-h100 (0,1) success 115m / 101m
stage-c-8-gpu-h200 (0,1) success 44m / 55m

The two red ROCm shards

Shard Duration Failing tests
stage-c-4-gpu-mi350 (0) 37m tests/e2e/megatron/test_qwen3_30B_A3B/test_r3_baseline.py
stage-c-4-gpu-mi350 (1) 99m tests/e2e/megatron/test_qwen3_30B_A3B/test_baseline.py, tests/e2e/megatron/test_mimo_7B_mtp_only_grad.py, tests/e2e/lora/test_lora_qwen2.5_0.5B.py

Every failing test in both shards fails the same single way, with no second failure mode: the sglang engine starts and serves normally (The server is fired up and ready to roll!, Uvicorn running on http://172.19.0.2:20000), but the launch gate port never listens, so retry_until_deadline gives up after 1800s.

Root cause: the ROCm image's sglang has no gated-launch support

The decisive evidence is sglang's own server_args=ServerArgs(...) dump in each lane's log:

CUDA image (passing job) ROCm image (failing job)
gated_launch_port in ServerArgs present: 20034, 20035, 20036, 20037, 20069, … field absent entirely
engine_info_bootstrap_port (the sibling port miles passes the same way) present present (20035)
Gate behaviour cell reaches init end carrying gate_url='http://172.20.0.2:20037', then mark_pending_weights no gate server; retry_until_deadline gives up after 343 / 3094 attempts

The sibling port arrives while the gate port does not, so the argument plumbing on the miles side is fine — that sglang build simply does not have the field. Both docker/Dockerfile and docker/Dockerfile.rocm fetch ${SGLANG_BRANCH} (sglang-miles), but the ROCm image is built on the prebuilt rocm/sgl-dev base with a pinned SGLANG_COMMIT, and the sglang that actually lands there predates the feature.

Why this is not introduced by this branch

  • The launch gate is code that exists only on this chain: git grep gate_url 1200fca8f8 -- miles/ returns nothing on main. This failure shape cannot occur on main, so there is no main-side comparison to run.
  • The ROCm PR lane itself is new: [AMD] Enable amd pr ci #2347 enabled AMD PR CI and moved ROCm from mi300x to mi350 on 2026-08-13. This rebase produced the first state of the chain that includes [AMD] Enable amd pr ci #2347, so the chain had never run against this lane before.
  • It reproduces deterministically across two separate CI rounds (66c26ce840 and 97bba45562) with identical attempt counts and timeouts, so it is not a flake.

Three possible ways forward, none of them applied here since the ownership is not obvious: rebuild the ROCm CI image on an sglang that has gated launch; teach miles to probe backend capability and take the existing no-gate path (miles/ray/specs/inference.py currently declares the gate port unconditionally); or keep this chain out of the ROCm lane for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bypass-fastfail Disable CI fast-fail: run all stages and all tests to surface every failure run-ci-image

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant