test(e2e): explain a managed serve that exits 0 and never serves - #270
test(e2e): explain a managed serve that exits 0 and never serves#270rominf wants to merge 2 commits into
Conversation
|
Moving back to draft — this regresses the lane it was meant to make legible. On the Strix Halo Windows lane this branch produces 5 unexpected failures, against a
The four extra are Both runs are on the same self-hosted runner in the same window, so this is the branch, not Likely mechanism, not yet confirmed: Consequence for #260: the |
|
I re-ran the Windows lane and the regression does not reproduce. The lane is back to the baseline of exactly 1 unexpected failure, with no code change — the earlier run was an infrastructure flake on a different machine. Details, because the original comparison was not like-for-like: The two runs were on different runners. Job metadata:
That truncated The
Diff comparison of the re-run against Both remaining red lanes are red on Marking ready again. Posting the #260 evidence separately on that issue, since it is now trustworthy. |
A `rocm serve --managed` returns as soon as the supervisor is launched, so an engine that dies afterwards leaves a zero exit code. The serve preconditions paired `run_rocm_ok` with a bare readiness poll, and that pair has nothing to say about this case: the poll panicked with only its timeout line while the CLI's output, the engine's log and the device state were all discarded. #260 is stuck there — the failing endpoint is reproducible only on a self-hosted Windows runner, and every run destroys its own evidence. Give that path the diagnostics the GPU serve step already collected, and share one collector between them so they cannot drift apart. A stalled serve now reports the invocation and exit code, the device state it started on, both CLI streams, the tail of the engine's own log, and how stopping the service went. The log itself lives in the scenario's isolated temp dir and dies with it, while CI uploads only the results directory, so the full file is now copied there too. The tail alone cannot show the startup banner naming the backend and device that were selected, which is exactly what an engine that never answers needs to be read against. Oversized logs keep both ends with the elision marked, so a runaway log cannot inflate the artifact. This is observability only. It does not fix #260, which stays open. Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
d46c46d to
e232022
Compare
r0x0r
left a comment
There was a problem hiding this comment.
Approve — well-scoped, test-harness-only, drives #260 toward diagnosability
Reviewed the diff, the new serve_log.rs, the step-definition refactor, the tests, and the live CI. fmt, clippy, unit tests, and build-and-test are green; the only red is the self-hosted Strix Halo Windows lane, which this PR intentionally and correctly leaves red.
What it does well
- Correct root problem. A
--managedserve returns once the supervisor launches, so an engine that dies afterward exits 0 and the oldrun_rocm_ok+ readiness-poll pair panicked with a baredid not serve … within 600s, discarding CLI output, engine log, and device state. This captures all four and archives the full engine log underresults/(the only path CI uploads) before the scenarioTempDirdeletes it. - Good deduplication.
section()is lifted topub(crate)and shared betweencli_failure_reportand the newserve_attempt_report, so every bundle reads identically and empty streams stay explicitly(empty). The five preconditions share one collector (serve_failure_evidence) so they cannot drift. - Load-bearing ordering (read log → archive → then stop the service) is correct:
services stophas no timeout and can wedge on exactly the misbehaving engine, so archiving after it would risk losing the artifact. read_clampedis careful. Bounding by seeking rather than read-then-trim avoids an allocation abort mid-report on a crash-looping engine; the head/tail-with-elision preserves both the startup banner (backend/device selection) and the failure tail. Tests cover the cap boundary precisely (at-cap verbatim; one-over drops exactly one region; halves never overlap).- No
cli_outputregression. The replacedrun_rocm_oknever setworld.cli_outputfor these preconditions, andserve_and_waitdoesn't either;user_serves_default_enginestill carriescli_output/cli_stderr/cli_rcfor its deferredThen.
Non-blocking observations
- The diagnostics already paid off — and show the current red isn't #260. The failing run's newly-captured evidence is
Failed to download llama-server.exe … SSL connect error (CURL code: 35) … Lemonade backend install failed, i.e. a transient GitHub-releases download flake, not the exit-0 stall #260 describes. Might be worth a one-liner in the PR noting the lane's present red is environmental (and that surfacing it is the improvement over the old bare timeout). device state: no GPU tooling (mock/local run)on the Strix Halo Windows lane. In the same run, the device-state section rendered blank of any GPU info on the very lane where #260 reproduces. Ifensure_serve_port_free's snapshot cannot read device state on Windows, the "device it started on" pillar may come back empty exactly where #260 needs it. Reasonable follow-up, out of scope here.- Minor:
MODELS_URL/SERVE_BASE_URLhardcode11435whileSERVE_PORTexists — a small drift risk; a doc cross-reference or const composition would close it.
Approving. The remaining Strix Halo Windows red is expected and correct per the PR's own "What this does not do".
SERVE_BASE_URL and MODELS_URL hardcoded the port literal separately from SERVE_PORT, so the three could drift out of sync. Signed-off-by: Roman Inflianskas <Roman.Inflianskas@amd.com>
|
Thanks for the review. On the three non-blocking observations:
|
Observability prerequisite for #260. It does not fix #260, which stays open — see "What this does not do" below.
The problem
rocm serve <model> --managedreturns as soon as the supervisor is launched, so an engine that dies afterwards leaves a zero exit code. The E2E serve preconditions opened withrun_rocm_okplus a bare readiness poll, and that pair has nothing to say about this outcome: the poll panicked with onlywhile the CLI's output, the engine's own log, and the device state were all dropped before the panic. The engine log is the one file that holds the answer, and it lives in the scenario's isolated
TempDir— deleted with the scenario, while CI uploads onlytests/e2e-cucumber/results/. So every run destroys its own evidence.#260 is stuck exactly there: the failure reproduces only on the self-hosted Strix Halo Windows lane, and three runs have now produced nothing to reason about.
What changed
setup_gpu_modelalready collected, now shared with the four preconditions that lacked them (setup_lemonade_model,setup_lemonade_hf_checkpoint_model,setup_large_gpu_model,user_serves_default_engine) through one collector, so they cannot drift apart.TempDirtakes it. The 40-line tail cannot show the startup banner naming the backend and device that were selected, which is what an engine that never answers has to be read against. Oversized logs keep both ends with the gap marked, read by seeking rather than loading the file, so a crash-looping engine can neither inflate the artifact nor risk an allocation abort in the middle of writing the report.Reuses
cli_failure_report's section formatting rather than adding a parallel one, so every bundle reads the same way and an empty stream stays explicitly marked(empty)— a silent engine and a harness that lost the output call for opposite next steps.Non-obvious decisions
rocm services stophas no timeout, so archiving after it would put the archive behind a call that can wedge on exactly the misbehaving engine this exists to explain. The cost is that shutdown output is not archived.commands.jsonldeliberately unchanged. Recording stdout/stderr for every invocation would inflate the coverage record on every passing command, and recording it only for non-zero exits would miss this bug entirely, since it exits 0. The evidence goes into the failure report and the archived log instead.user_serves_default_enginestill does not fail on a non-zero exit code. It is aWhen; the CLI printed the plan line its scenarios are about, and failing here would pre-empt the Then step that names the actual disagreement.Verification
Run locally on Linux:
cargo fmt --check,cargo clippy --locked --workspace --all-targets -- -D warnings,cargo clippy --locked -p e2e-cucumber --test e2e -- -D warnings,cargo test -p e2e-cucumber --lib(81 pass), andcargo xtask e2e— 46 scenarios, 0 unexpected failures, identical tomain.New unit tests in
src/serve_log.rscover the parts that are testable without the hardware: that a stalled-serve report carries every piece of evidence and marks empty streams, that the full log lands underresults/byte for byte at the artifact-relative path it reports, the cap boundary (exactly at the cap is verbatim; one byte over elides exactly one byte and the halves never overlap), and that every unarchivable case explains itself instead of panicking inside a failure path.No scenario is added: this changes no user-observable CLI behaviour, only what the harness reports when a serve stalls. The changed step definitions are all
@requires-gpu, so they are exercised by the self-hosted lanes rather than the mock lane.What this does not do
What the next red run should produce
For scenario
serve-hf-checkpoint-inference, instead of the bare timeout line: the serve plan andlog_path, the device state, andservice-logs/<scenario>--<service-id>.login thee2e-gpu-strix-windows-reportartifact containing the packagedllama-server's complete stdout and stderr — the engine writes both there. That should distinguish the three candidates #260 names (directllama-serverlaunch, GGUF path resolution in the HF cache, port binding) without another round trip.Risk: low — test harness only, no production code, no dependency or config changes. The one effect outside the harness is a bounded artifact-size increase, capped at 4 MiB per archived failed serve.
Related: #248 fixed the sibling problem of discarded stderr on a non-zero exit; this path exits 0, so it was not covered.