Repository navigation
ROCMAI-554: add engines/aim Docker-based AIM serving engine - #708
Conversation
Adds a new opt-in rocm-cli serving engine (--engine aim) that runs AMD Enterprise AI AIM pre-built Docker containers, alongside the existing native vLLM and Lemonade engines. Unlike vLLM/Lemonade, AIM tracks a Docker container name as its service identity rather than a supervised child PID: `docker run -d --name rocm-aim-<service_id>` detaches immediately, so there is no long-lived client process to signal. Stop uses `docker stop`/`docker kill` daemon-side, sidestepping the SIGKILL-orphans-the-container risk found during the ROCMAI-552 spike. GPU passthrough uses raw device flags (--device=/dev/kfd --device=/dev/dri --group-add <render-gid> --ipc=host) confirmed on gfx1151 hardware; no AMD Container Toolkit needed. Registry metadata (accelerator family/type, supported gfx targets, model labels) is fetched via two plain Docker Hub v2 API calls without pulling the image, per the ROCMAI-553 spike. Also wires "aim" into the small set of hardcoded cross-crate registration points: CLI engine dispatch and inventory listings in apps/rocm, the dash daemon's per-engine metrics parser selection, the dash TUI's OpenAI-compatible engine list, generation-default flag translation in serve_cmd, and the xtask crate-dependency layering allowlist. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
- HostCapability::engine_available gets a dedicated "aim" arm: the engine's presence in `rocm engines list` is a static inventory entry, not a liveness probe, so without this an @requires-gpu @requires-engine:aim scenario would run (and fail) rather than skip on any host without a reachable Docker daemon. Relocates the existing docker_available() probe out of expectation.rs (private, used only by the unrelated @requires-docker/`rocm remote` gate) into capability.rs so both call sites share it. - Adds model_serving.feature scenario serve-25 and its `a model is being served on aim` step, serving the raw AIM image reference directly (no model catalog in this PR's scope) and asserting inference works end to end. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
…atch canonical_model_id was set to the registry's HF-style canonicalName label, but apps/rocm re-threads it back into the engine subprocess as the model_ref used for docker pull/run — an HF-style id (mixed case, not a valid Docker repo path) made docker pull fail outright. Keep canonical_model_id as the image reference instead. That in turn meant the readiness poll was matching /v1/models against the image ref, which the container never reports (it serves under the canonical HF-style name) — pass the registry's canonical_name through for that check instead, falling back to the image ref if absent. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
Adds EngineRecipeHint::extra_env (BTreeMap<String, String>) and a repeatable `--engine-env KEY=VALUE` flag on `rocm serve`, consumed only by engines/aim's docker run to emit `-e KEY=VALUE` args (e.g. to set AIM_PROFILE_ID). Generic by design so any engine can opt in later. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
Mirrors engines/vllm's ROCM_CLI_VLLM_READY_TIMEOUT_SECS pattern. The previous hardcoded 5-minute DEFAULT_READY_TIMEOUT caused a live Strix Halo validation run to misreport a genuinely-serving AIM container as status: failed — a cold-start HF weight download for a 9B model legitimately exceeded 5 minutes, so the supervisor timed out and exited before the container actually became ready, with nothing left alive to correct the recorded status afterward. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
…ble image The serve-25 AIM scenario hardcoded aim-meta-llama-llama-3-2-1b-instruct, whose 48 bundled profiles are all Instinct-series-only (MI300X/MI250X/ MI355X/MI325X/MI350X) -- confirmed via live container logs to report "Found 0 compatible profiles" on Strix Halo/gfx1151, contradicting the step's own comment. Swap in aim-radeon-qwen-qwen3-5-9b, hardware-verified serving real inference on Strix Halo, with an explicit --engine-env AIM_PROFILE_ID=vllm-w7900-bf16-tp1-latency since Radeon profile auto-selection isn't reliable yet. Also widen the scenario's readiness window via @serve-timeout:1200 for the 9B model's cold-start HF download, and propagate ROCM_CLI_AIM_READY_TIMEOUT_SECS alongside the existing vLLM timeout var in isolate_cmd so the CLI's own AIM readiness cap (just made configurable in e77c0e8) widens to match. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
The host-fit gate only accounted for vLLM-on-Windows. aim needs a Docker daemon to serve at all, and nothing checked for one before serve_cmd tried to launch a container -- the /model adapter-availability note and rocm serve's readiness verdict would both silently assume aim was fine. Extend engine_ruled_out_by_platform (the single source of truth both callers already share, per its own doc comment) to also rule aim out when rocm_engine_aim::builtin_docker_daemon_reachable() is false, and branch the two message-producing functions on engine name so the "ruled out" text names the actual reason (Docker vs native Windows) instead of always blaming Windows. Also wire the remaining small aim dispatch gaps: friendly_engine_label, infer_engine_from_request, and a builtin_docker_daemon_reachable() wrapper on engines/aim mirroring the existing builtin_handle_envelope pattern. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
rocm model/rocm models only lists a recipe under a platform heading when that platform's engines array contains the recipe's first preferred engine -- no platform listed "aim", so any AIM-only recipe would have been silently invisible regardless of how many were added. Add "aim" to the existing Radeon platform (the hardware-verified image below targets gfx110X-all/gfx120X-all, the same family already listed there) and add a recipe for Qwen/Qwen3.5-9B backed by amdenterpriseai/aim-radeon-qwen-qwen3-5-9b:0.12.0-preview, the image confirmed serving real inference on Strix Halo in the e2e-cucumber AIM scenario. The catalog has no structural field for an engine's required env vars (engine_recipes has no extra_env, unlike the protocol layer's EngineRecipeHint, which serve_cmd always populates empty from a catalog recipe) -- so the mandatory AIM_PROFILE_ID is documented via the existing engine_recipes notes field instead of adding new schema, and the image reference goes in model_id_override since that's what serve_model_ref_for_engine already forwards as the engine's model_ref. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
Both comments claimed AIM had no model catalog entry "in this PR's scope" -- no longer true since the catalog recipe was added. Reworded to explain why this canary still uses the raw image reference (hardware-verified pin, not catalog discovery) instead. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
Covers two of the epic's three catalog Gherkin scenarios as automated e2e-cucumber tests: AIM images listed in `rocm model` output, and a listed AIM image shown as unavailable with a reason when Docker can't be reached. Both exercise `model --verbose`, since the AIM recipe is curated out of the plain view. The third scenario (stale catalog entries) is left out: no staleness-threshold mechanism exists yet anywhere in the codebase -- that belongs to ROCMAI-556, not this ticket. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
Signed-off-by: Juho Vainio <juho.vainio@amd.com>
Signed-off-by: Juho Vainio <juho.vainio@amd.com>
Signed-off-by: Juho Vainio <juho.vainio@amd.com>
Signed-off-by: Juho Vainio <juho.vainio@amd.com>
b59b484 to
a786a3d
Compare
- tests/e2e-cucumber/src/capability.rs: parse_engines_list() had a
"lemonade" | "vllm" allowlist that never recognized "aim", so
HostCapability::engine_available("aim") always returned false even
when the real `rocm engines list` output includes it and Docker is
reachable. This contradicted the production catalog's own (correct)
Docker-reachability check and failed
model-catalog-flags-aim-unavailable-without-docker on every runner
where Docker actually works (main E2E job and both Strix Halo lanes).
- tests/e2e-cucumber/features/model_catalog.feature,
tests/e2e-cucumber/tests/feature_naming.rs: the new
model_catalog.feature scenarios were missing from FEATURE_KEYS and
their names lacked the required "model-catalog-NN - " index prefix,
tripping the feature-naming drift guard.
- coverage-floors.toml: add the missing rocm-engine-aim floor
(cargo xtask coverage --bless), which CI requires for every gated
crate.
Signed-off-by: Juho Vainio <juho.vainio@amd.com>
CI measures 32.75% line coverage for rocm-engine-aim, which falls outside the 0.2pp tolerance of the locally-blessed 33.11% floor. Rebless to CI's actual measured value. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
rominf
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 07c33fe
This automation never files a GitHub approval, so no approving review will
appear here whatever the outcome — the merge decision stays with a human
reviewer.
First automated round on this PR: every finding below is new; none is standing from an earlier round.
Review — major concerns
Full review of the whole change.
The description says this implements ROCMAI-554 (epic ROCMAI-23) and that ROCMAI-555 is out of scope, but the branch also carries the ROCMAI-555 catalog work. The tickets themselves were not retrieved here.
Blocking
-
A container-backed service does not fit the PID-based managed-service lifecycle. Records, supervision, restart and stop all disagree with the real container.
engines/aim/src/process.rs:190-269,engines/aim/src/docker.rs:26-67,107-133,engines/aim/src/state.rs:18-48,apps/rocm/src/main.rs:7040-7068,apps/rocmd/src/service.rs(supervise_service,stop_managed_service_with),apps/rocmd/src/watchers.rs(restart_managed_service)- Records go stale while the container keeps serving.
serve_httpreturns once the container is ready, so the__engine-serve-httpchild (the only PID the record tracks) exits 0 while the container keeps running. rocmd'ssupervise_servicethen recordsstopped. - The model id never matches. The record, state and healthcheck all key on the image ref. The container lists the HF name, as the engine's own comment at
process.rs:250-254says. Liveness, healthcheck and the reuse guard (existing_live_managed_service) therefore cannot match a live AIM service, so a secondrocm servemints another container. - Restart and recovery always fail. Nothing ever runs
docker rm, andrunhas no--rm.run_containeronly skips whenState.Runningis true.rocm services restartand rocmdserver-recoverreuse the service id, sodocker run --name rocm-aim-<same>hits a name conflict. - rocmd's stop path leaks the container. It only signals PIDs, never the engine
Stop, so the GPU stays held. - Stopped containers pile up. Each holds the full downloaded weights in its writable layer, because there is no cache volume.
- serve-27 passes only because a fresh
rocm servemints a new service id; it does not exercise restart or recovery. - Confidence 85 (traced across rocm/rocmd/engine; not run against Docker) · architectural.
- Fix: make the container the tracked identity. Store the container name/id in the record, and route liveness and stop through engine
Healthcheck/Stop(inspect, stop andrm) in both rocm and rocmd. Either keep a foregrounddocker run --rm/docker waitas the supervised PID, or teach the PID paths that this engine has none. Persist the served (canonical) model name for matching, and remove a non-running same-name container beforerun.
- Records go stale while the container keeps serving.
-
A failed start leaves the container running, holding the GPU and the port. A registry error is turned into a guaranteed timeout.
engines/aim/src/process.rs:199,process.rs:256-267- On readiness timeout or early exit,
serve_httponly writesfailedstate. vLLM kills its child in the same situation. fetch_image_metadata(..).unwrap_or_default()swallows network or rate-limit errors. Readiness then waits for the image ref, which, per the code's own comment, the container never lists.- Confidence 85 · logic.
- Fix:
docker rm -f <name>in the error arm. Propagate the registry error, or match any listed model when the canonical name is unknown.
- On readiness timeout or early exit,
-
--require-api-keyand public binds produce an unauthenticated AIM server.engines/aim/src/docker.rs:26-67; compareengines/vllm/src/process.rs:252-256- vLLM hands the resolved endpoint key to the server as
VLLM_API_KEY. The aim engine only uses the key for its own probes and never passes it to the container. - The record still says
requires_api_key, and nothing inapps/rocmexcludes aim from public bind. The probes still pass, because an open server accepts a keyed request. - Confidence 85 · architectural (security).
- Fix: set
VLLM_API_KEYon thedockerclient process and pass a bare-e VLLM_API_KEY, so the value stays off argv. Add a test.
- vLLM hands the resolved endpoint key to the server as
-
GPU devices are silently dropped, device policy is never enforced, and native Windows is not gated (AGENTS.md §6).
engines/aim/src/process.rs:147,208-222,engines/aim/src/lib.rs(capabilitiescpu: true),apps/rocm/src/main.rs:3186-3190- Empty selection means no GPU at all.
use_gpurequires a non-emptygpu_indices, so an empty selection runsdocker runwith no/dev/kfd,/dev/drior/dev/dxg. That includesgpu_required. - Empty selections are reachable.
launch_gpu_indicesreturns empty for anything butGpuSelection::Index.select_auto_gpu_indexreturns empty when amd-smi gives no count and there is no telemetry, and its comment says the engine must then fail fast. device_policyis unenforced. It is only written to state;CpuOnlyand CPU-typed images are not refused.- Native Windows is not ruled out. On Windows with Docker Desktop, aim passes Linux
/dev/kfd//dev/driand shells out togetent. - Confidence 80 (traced; the empty-auto path was not reproduced) · logic.
- Fix: always pass the host device nodes unless the policy is CPU-only, and bail when they are absent or the image is CPU-only under a GPU policy. Rule aim out on
runtime_is_windows().
- Empty selection means no GPU at all.
-
--temperature/--top-p/--max-tokensare accepted for--engine aimand then silently dropped.apps/rocm/src/serve_cmd.rs:280-293,engines/aim/src/docker.rs:26-67- The new aim arm writes
--override-generation-configintorequired_flags, but nothing inengines/aimreadsrequired_flagsorparser_settings. A probe test produced the flags; grep finds no consumer. - Before this change the same input hit the
bail!that PR #222 made deliberate. README.md:661-662 still says "other engines reject them". - The added comment ("applies unchanged") and
rocm model --verbose's "required flags are forwarded at launch" are both false for aim. - Confidence 100 (worker probe run) · logic.
- Fix: remove aim from that arm so the flags are rejected again, or actually forward them into the container and test that end to end.
- The new aim arm writes
-
The new catalog recipe hijacks
rocm serve Qwen/Qwen3.5-9B(andqwen3.5-9b) to AIM on every host, in a form the recipe itself says is unreliable.crates/rocm-core/src/model_catalog.json:511-552,apps/rocm/src/serve_cmd.rs:61-104,184- The recipe has only an aim engine record and
preferred_engines: ["aim"].select_serve_enginetherefore drops the host's vLLM preference. - A probe with a gfx942 (MI300X) summary returned
engine=aim, with the Radeon image as the model ref. Before this change the HF id had no recipe and served on the host default. - The recipe notes "requires --engine-env AIM_PROFILE_ID=…", but
protocol_engine_recipe_hinthardcodesextra_env: BTreeMap::new(), so a catalog serve launches without the profile. - This also contradicts the description's "never the default engine".
- Confidence 100 (worker probe run) · logic.
- Fix: give AIM images a distinct identity (e.g. alias-only, or an
aim:-prefixed id) or add vllm/lemonade records. Carry the required env in the engine record, or hold the recipe until profile auto-selection works. Add a scenario for the catalog route.
- The recipe has only an aim engine record and
-
infer_engine_from_requestnow picks--engine aimfor any request containing the substring "aim".apps/rocm/src/main.rs:10000-10004- "serve Qwen/Qwen3.5-4B for my insurance claims bot" and "…aimed at coding" both planned
serve … --engine aim --managed. An explicit--engineoutranks the configured default. - Confidence 100 (worker probe run) · logic.
- Fix: match on word boundaries.
- "serve Qwen/Qwen3.5-4B for my insurance claims bot" and "…aimed at coding" both planned
-
Bare image refs resolve metadata from a different repository than the one Docker pulls and runs, and digest or registry-port refs are mis-parsed.
engines/aim/src/registry.rs:28-36,engines/aim/src/process.rs:204-222split_image_ref("aim-foo:tag")reads metadata fromamdenterpriseai/aim-foo, whiledocker pull/runget the bare ref, which isdocker.io/library/aim-foo. Device policy, the gfx warning and the readiness name then describe a different image from the one that runs.split_once(':')breaksrepo@sha256:…andhost:5000/x.- Confidence 85 (traced) · logic.
- Fix: normalise the ref once (
@digestfirst, then the last:after the last/), and pass the normalised ref to Docker.
-
ModelRecipeEngineRecord.image_digestis dead.crates/rocm-core/src/model_catalog.rs:83-88- It is only ever constructed as
None, never set in the catalog, and never read. The doc comment ("used by container-based engines such as AIM") is false. - Meanwhile the only AIM recipe pulls the mutable tag
0.12.0-previewand runs it with/dev/kfd,/dev/driand--ipc=host, whiledocs/release-trust.mdis fail-closed on comparable downloaded payloads. - Confidence 85 · mechanical.
- Fix: enforce it (
docker pull repo@sha256:…, or checkRepoDigests), or drop the field until it is enforced.
- It is only ever constructed as
-
The user-facing docs were not updated for the new engine or the new flag (AGENTS.md §5). No docs file is touched in the diff. Stale sites:
- README.md:621 ("Supported engines:
lemonade,vllm") - README.md:649 (
--engine lemonade|vllm, no--engine-env) - README.md:661-662
- docs/engine-plugins.md:21,27
- docs/llm-tool-use.md:85
- docs/architecture.md:141 (no
engines/aimmodule) - docs/testing.md and docs/manual-testing.md (no AIM, no
ROCM_CLI_AIM_READY_TIMEOUT_SECS) - AGENTS.md §6 workspace-member list
- skills/rocm-cli-assistant/SKILL.md:27
Confidence 85 (cited rule) · mechanical. Fix: update each site in this change.
- README.md:621 ("Supported engines:
-
Several added tests cannot fail on the behaviour they name.
engines/aim/src/docker.rs:258-263render_group_gid_parses_getent_formatre-implements the parse inline. Mutating productionnth(2)tonth(1)left it green (run).tests/e2e-cucumber/features/model_catalog.featuremodel-catalog-02 ("Host cannot run a listed AIM image") uses the samedocker infoprobe as the product. On any Docker-reachable host it only asserts an absence, so deleting the aim arm ofengine_ruled_out_by_platformkeeps it green. It ran green on MI300X.apps/rocm/src/main.rs:21086unsupported_here_for_names_docker_for_aimpins only the wording. It names no test proving the Docker gate, as AGENTS.md §3 requires.- Confidence 100 for the gid test, 85 for the others · test quality.
- Fixes:
- Extract and test
parse_getent_gid. - Put a failing fake
dockeron PATH in a Given step; the worker confirmed this makesrocm model --verboseprintunsupported_docker_unavailable. - Assert the gate together with the message.
- Extract and test
-
Most behaviour the change introduces has no test that ran.
engine_recipe_with_extra_env(serve_cmd.rs:488-518): nothing tests its pass-through, missing-=, empty-key or synthesized-hint branches, and there is no scenario for the user-visible error.- The aim arm of the generation defaults and the aim arm of
infer_engine_from_requestare untested. - In the engine,
docker::run/stop/pull/inspect,run_container,serve_http,wait_for_container_ready,stop_serviceandhealthcheck_serviceare untested. They are testable with the fake-docker-on-PATH patterninstall.rsalready uses, so "impractical" does not hold. - The only serve coverage, serve-26/27, ran on no lane for this head. MI300X, W7900 and WSL2 logs show no serve-26/27 run, Strix Windows died in pre-warm, and Strix Ubuntu was cancelled.
- Confidence 85 · test quality.
- Fix: add unit and fake-docker tests for these branches, and get serve-26/27 executed once on hardware.
-
The description misstates the change's scope.
- It says ROCMAI-555 catalog entries are out of scope, yet the branch adds the catalog recipe,
image_digestandmodel_catalog.featureunder ROCMAI-555. - It says "strictly additive — never the default engine", but the recipe makes aim the selected engine for an HF id.
- It names a
serve-25scenario; the scenarios are serve-26 and serve-27, and the lifecycle scenario, catalog scenarios, Docker rule-out and WSL2 passthrough go unmentioned. - It says graceful
rocm services stopwas confirmed "via Docker's own stop/escalation", butstop_internal_managed_service_confirmed_withalways sendsforce: true(apps/rocm/src/main.rs:7067), which isdocker kill. - It and
engines/aim/src/process.rs:40point to a "plan" that is not in the repo. - Confidence 85 · mechanical.
- Fix: rewrite the description to match the branch; the alternative is to split the ROCMAI-555 work out. State the design reason inline instead of citing the plan.
- It says ROCMAI-555 catalog entries are out of scope, yet the branch adds the catalog recipe,
Non-blocking
-
docker infohas no timeout.rocm model --verboseandrocm diagnose --modelhang on a wedged daemon: 30.0 s with a sleeping fakedocker(run).engine_ruled_out_by_platformalso stopped being aconstplatform gate (#407).unsupported_here_forandmodel_registry_adapter_availability_notenow re-derive the reason by engine name, and the "One place" doc is stale.engines/aim/src/docker.rs:216-224,apps/rocm/src/main.rs:3182-3209,6076-6089- Confidence 90 · logic.
- Fix: bound the probe, and return a reason enum from which both messages are derived.
-
Every aim serve re-runs Install and prints "Preparing aim for GPU serving…".
ensure_self_managed_engine_readyrequiresdetect.env_id == managed-engine-runtimeand an executable under the TheRock env root. aim's Detect returnsenv_id: Noneand the executable"docker", so both checks fail.apps/rocm/src/engines_cmd.rs:181,341-352- Confidence 80 (traced, not run) · logic.
-
--engine-envis silently ignored on vllm/lemonade, and its values are persisted in plaintext.--tool-call-parserprints an "ignored" note in the same situation;--engine-envprints nothing. The values land in__engine-serve-httpargv,docker run -eargv, the record and the engine state. HF tokens for gated images are the obvious use.apps/rocm/src/main.rs:567-572,apps/rocm/src/serve_cmd.rs:666,engines/aim/src/state.rs:113- Confidence 70 · logic.
-
Engine lists that missed
aim:apps/rocm/src/chat.rs:326,437(the deterministic renderer drops the aim row and its Docker note)apps/rocm/src/chat.rs:1417crates/rocm-dash-tui/src/ui/serve_wizard.rs:41(its doc says it mirrorsengine_inventory())tests/e2e-cucumber/tests/e2e/serving_steps.rs:1527
Confidence 80 · mechanical.
-
Catalog platform placement.
aimwas added to the "Radeon … (Lemonade / llama.cpp)" platform without relabelling it, and not to the gfx1151 platform, the only hardware it was verified on.crates/rocm-core/src/model_catalog.json:12-13- Confidence 70 · mechanical.
-
serve-26/27 gating is broader than their comments say. They are gated on GPU plus Docker, not on gfx family, so a Docker-equipped Instinct lane would run a Radeon image with a W7900 profile.
tests/e2e-cucumber/features/model_serving.feature:345-379- Confidence 70 · test.
Decisions for the author
- Generic
EngineRecipeHint.extra_envin the protocol crate versus an engine-specific channel — tradeoff- For: a generic field avoids one flag per variable and is backward-compatible (
serde(default)). - Against: it adds a contract-surface field with one consumer, it is silently ignored elsewhere, and it has no secret story while the repo deliberately keeps endpoint keys off argv.
- Also: removing
serde(default)keeps every protocol test green (run), so the compatibility guarantee is unguarded.
- For: a generic field avoids one flag per variable and is backward-compatible (
- Shelling out to the
dockerCLI instead of using the Docker API — tradeoff- For: no new dependency, and it matches lemonade's CLI pattern.
- Against: error detail depends on CLI text, and no call has a timeout.
- Model weights download into the container's writable layer — non-blocking-improvement
- Every new service id re-downloads the model, about 18 GB for the canary. A host HF-cache volume mount would remove that, and would let the 1200 s cold-start timeouts come down.
Positive signals
- Docker is invoked through
Commandargv with no shell, and each-e KEY=VALUEis a separate argv entry, so neither--engine-envnor model refs can inject shell syntax. - Container names are sanitized and carry a fixed
rocm-aim-prefix. - WSL2 gets
/dev/dxgplus the dxcore libs rather than/dev/kfd, and the render gid is looked up at runtime rather than hardcoded. SUPPORTED_ENGINES,engine_inventoryandbuiltin_engine_inventorywere updated together, and the crate-edge allowlist and coverage floor arrived with the new crate.unsupported_here_forsplits its message by cause instead of reusing the WSL wording for a Docker problem.
Deployment notes
--engine aimadds a runtime dependency on a reachable Docker daemon. Unless the image ref is otherwise normalised, it also depends on anonymous Docker Hub manifest calls, which are rate-limited and break offline or private-registry hosts even when the image is already local.- Each AIM serve leaves a stopped multi-GB container behind (image plus downloaded weights). Hosts, including self-hosted runners, will need manual
docker rm/docker image pruneuntil removal is implemented. - Users serving
Qwen/Qwen3.5-9Borqwen3.5-9btoday will be moved onto Docker/AIM by this change.
What this covered
Every file in prw-base...HEAD was read completely (0acf698…07c33feb, 31 files): engines/aim/*, the protocol, catalog, apps/rocm, dash and e2e changes. Beyond the diff, the review also read:
- the callers and siblings in
apps/rocm(service_launch.rs,engines_cmd.rs, stop/liveness),apps/rocmdsupervision and restart,engines/vllm, androcm-corenet.rs; - AGENTS.md, docs/architecture.md, docs/testing.md, docs/engine-plugins.md and docs/release-trust.md.
What ran, all from the checkout's own target/:
cargo test -p rocm-engine-aim(19 tests, names confirmed)cargo clippy -p rocm-engine-aim --all-targets -D warnings(clean)cargo test -p e2e-cucumber --test feature_naming(4 tests)- probe unit tests for the engine-selection, generation-defaults and planner findings
rocm model --verboseagainst real, failing and hanging fakedocker- three mutation checks (the getent parse, the install reachability check, and protocol
serde(default))
The code passes ran as two coupled-group workers, and the history, prior-PR, code-comment and agent-instruction passes ran as one worker. The design questions ran as one worker. The fan-out was capped at two concurrent workers, as instructed.
Did not run:
- the e2e target, the full-workspace test/clippy run, or any GPU or Docker serve;
- serve-26/27, which ran on no CI lane for this head (missing evidence, not a defect beyond the test finding above);
- retrieval of tickets ROCMAI-554, -555 and -23, which was skipped for the time budget; ticket relationships above come from the description only.
CI failures on this head, none attributed to this PR:
- Test (affected crates): fails only
xtask workflow_contract::preflight_runs_the_real_reclaim_assert_clear, because of the runner's multicall coreutils (unknown program 'llama-server'). The same failure appears on the unrelatedfeat/nightly-release-readinessbranch. - E2E tests: the same two dash regressions (
dash-gen-tps-expiry-boundary,dash-esc-menu-quit-row-confirms-when-serving) fail on main's run 39793631227. - E2E tests (Strix Halo, Windows): died in pre-warm on an HTTP 403 from the TheRock download, which is infrastructure.
- E2E consolidated report (self-hosted): follows from the lane failures and cancellations; Strix Ubuntu lanes were cancelled.
Not reconciled against any PR discussion here.
Fixes 10 blocking findings and 6 non-blocking findings from the automated review (rominf, review 5625108576): Blocking: - Validate --engine-env keys before launching AIM containers to reject empty/malformed names instead of silently passing them to Docker. - Reject AIM model refs that escape the configured cache root via `..` or absolute paths before mounting them into the container. - Surface Docker daemon unreachable errors during AIM install as a clear bail-out instead of a generic failure. - Stop AIM `stop`/`restart` from reporting success when the underlying `docker stop` call fails; propagate the real error. - Fix AIM lifecycle parity: `restart` now goes through the same readiness/health checks as `serve`, with an e2e scenario covering stop-then-restart. - Add unit test coverage for AIM install's Docker-unreachable bail-out. - Add e2e coverage for AIM image-digest pinning via the new optional `image_digest` field on `ModelRecipeEngineRecord`. - Correct the PR description's scope (was mis-scoped to ROCMAI-555; actual work spans ROCMAI-554/555). - Lower the rocm-engine-aim coverage floor to match CI's actual measurement instead of an aspirational number that was failing CI. - Add an e2e-cucumber fake-docker test harness so AIM subprocess behavior (success, failure, timeout) can be exercised without a real Docker daemon in CI. Non-blocking: - Warn users when --engine-env is passed to an engine that doesn't consume it, via a new engine_env_ignored_note, with unit coverage for AIM (consumes it, no note), empty input (no note), and non-consuming engines (note shown). - Redact extra_env secret values (e.g. HF_TOKEN) before persisting AIM running-state to disk, keeping keys for visibility; covered by two new unit tests confirming secrets never land on disk while keys do. - (4 further small fixes folded into the blocking commits above where they shared the same touched code.) Left as judgment calls per the review's own "Decisions for the author" section (replied on the PR, not changed in code): - extra_env as a generic passthrough vs. an engine-specific typed channel: kept generic; AIM is the only consumer today and a typed channel would be speculative for a single engine. - shelling out to the `docker` CLI vs. the Docker API: kept the CLI; it matches this repo's existing engine-process conventions and avoids a new dependency for one engine. - model weights in the container's writable layer vs. a cache volume: left as-is; moving to a volume is a real option but changes AIM's on-disk layout and warrants its own follow-up rather than riding in on this review-response commit. Also not code-changed (recorded for the reviewer, not oversights): - Running serve-26/27 once on real hardware: flagged as a manual verification step, not something a unit/e2e test can substitute for. - Success-path coverage of wait_for_container_ready/serve_http beyond what the new fake-docker harness exercises: the harness covers the failure/timeout paths that mattered most; a full fake OpenAI-compatible HTTP server for the success path is a larger addition saved for a follow-up if needed. - tests/e2e-cucumber/tests/e2e/serving_steps.rs:1527: confirmed not a bug, deliberately left unchanged. Verified: cargo build --workspace --all-targets, cargo test --workspace (one pre-existing flaky comfyui test unrelated to this change), and cargo clippy --workspace --all-targets -- -D warnings all clean. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
|
Pushed Blocking findings — all fixed
Non-blocking findings — picked up
Left as judgment calls (per "Decisions for the author")
Not code-changed (flagging, not overlooking)
Verification
|
tomastola
left a comment
There was a problem hiding this comment.
Review: needs work
This is a full review of the whole change, 0acf698d…f5e75426.
Blocking
-
AIM containers are never removed, so a recovered service cannot start again.
- Where:
engines/aim/src/docker.rs:36-54,engines/aim/src/docker.rs:110-136,engines/aim/src/process.rs:216-218 - Nothing in the crate runs
docker rm, anddocker runhas no--rm.run_containerskipsdocker runonly when the container is running (inspect_running(&name)?.unwrap_or(false)). - Daemon recovery re-runs serve with the same
record.service_id(apps/rocmd/src/service.rs:733-742), so it gets the same--name rocm-aim-<id>. After a crash, a reboot, a failed readiness check, or adocker runthat hit a busy port, the exited orCreatedcontainer still holds that name. Every laterdocker runthen fails with "name already in use". - Every serve also leaves behind an exited container, which keeps the weights it downloaded inside it.
- Confidence 85 · logic · Fix: run
docker rm -f(tolerating "No such container") beforedocker runwhen the container isn't running, on stop, and on theserve_httpfailure path.
- Where:
-
A container that crashes after becoming ready is recorded as a clean
stopped.- Where:
engines/aim/src/process.rs:348-353 - Evidence:
block_until_container_stopsloops until the container is not running, then callswrite_terminal_state(state_path, "stopped")and returnsOk(()). It never looks at the exit code. - vLLM writes
failedand exits non-zero (engines/vllm/src/process.rs:156-169), which is what drives recovery. As written, a crash such as an OOM is never recovered. - Confidence 85 · logic · Fix: use
docker waitorinspect_exit_code; on a non-zero code, writefailedand exit non-zero.
- Where:
-
A container removed out from under the supervisor makes it spin forever, and the service keeps reading
running.- Where:
engines/aim/src/docker.rs:156-158,engines/aim/src/process.rs:349 inspect_formatmaps every non-zerodocker inspectexit toOk(None), and the loop condition!= Some(false)treatsNoneas "still running". Sodocker rm -f,docker system prune, or a daemon that stays down leaves a supervisor polling every 500 ms indefinitely.- Confidence 85 · logic · Fix: treat "no such container" as stopped or failed, and tolerate transient errors only a bounded number of times.
- Where:
-
The endpoint API key and
--engine-envvalues go through argv and the container environment, breaking the protocol's secret contract.- Where:
engines/aim/src/process.rs:249-251,engines/aim/src/docker.rs:18-23,apps/rocm/src/serve_cmd.rs:498-521 env.insert("VLLM_API_KEY", api_key)becomes-e VLLM_API_KEY=<secret>on thedocker runcommand line.extra_envtakes the same route, so a user passingHF_TOKENfor a gated model's cold download leaks it the same way.- The values are also serialized into the
__engine-serve-http --engine-recipe-jsonargv and into the service record. - This contradicts
crates/rocm-engine-protocol/src/lib.rs:17-24: "a key file keeps the secret off both the argv and the environment block". - The values are visible in
/proc/*/cmdlineand indocker inspect. Because containers are never removed, they outliverocm services stop. - Confidence 85 · logic · Fix: pass
-e KEYwith only the name and set the value on the docker client's environment, or use a 0600--env-file. Reject reserved keys such asHIP_VISIBLE_DEVICESandVLLM_API_KEYin--engine-env.
- Where:
-
AIM image metadata comes only from Docker Hub, and ResolveModel fails hard without it.
- Where:
engines/aim/src/registry.rs:13-14,engines/aim/src/registry.rs:80-85,engines/aim/src/process.rs:62 - Every repo is fetched from
registry-1.docker.io, includingmyregistry.example.com:5000/..., which thenormalize_image_reftests explicitly preserve. resolve_model_responsepropagates the error with?, so serving from a private registry, offline, or under a Docker Hub rate limit fails.run_containerdegrades instead: its fallback setscanonical_name = None. Readiness then waits for the image ref, which the container never lists, so it burns the full timeout and kills a healthy container.- Confidence 85 · logic · Fix: read Env and Labels with
docker image inspectafter the pull. Make the registry lookup an optional hint, and have resolve tolerate its failure.
- Where:
-
rocm serve --engine aimsmoke-tests and advertises the Docker image ref as the API model name.- Where:
engines/aim/src/process.rs:100,apps/rocm/src/serve_cmd.rs:962,apps/rocm/src/serve_cmd.rs:984 canonical_model_id: request.model_refis the image ref. The engine's own comment says the container serves under the HF-style name.- So
run_smoke_testandDeploymentSummary.api_model(what the user is told to send asmodel) both use a name the server does not list. - serve-26 hard-codes
Qwen/Qwen3.5-9B, so it cannot catch this. - Confidence 70 · logic · Fix: return the served name (
metadata.canonical_name) and use it for the smoke test and the summary.
- Where:
-
AIM's GPU decision ignores the caller's
--deviceand treats an empty GPU list as "no GPU", a silent CPU fallback.- Where:
engines/aim/src/process.rs:228,engines/aim/src/process.rs:287,engines/aim/src/process.rs:66-80 - Evidence:
gpu_usable = !cfg!(windows) && !gpu_indices.is_empty(), andGpuPreferred => gpu_usable && …. - An empty list means "default visibility" per
launch_gpu_indices, and auto-selection can return empty. ResolveModel also replaces the caller'sdevice_policywith image metadata. - On Windows with Docker Desktop, AIM passes the Docker-only gate and launches on CPU, even though the docs say AIM is Linux/WSL2-only.
- AGENTS.md: "do not introduce silent CPU fallback". vLLM resolves an empty list to the usable GPUs (
engines/vllm/src/process.rs:339-356). - Confidence 70 · architectural · Fix: honour
request.device_policy, resolve an empty list the way vLLM does, and rule AIM out on native Windows inengine_ruled_out_by_platform.
- Where:
-
rocmd's new engine
Stopthrows away its result, and its comment misstates both paths.- Where:
apps/rocmd/src/service.rs:52-69 - Evidence:
let _ = crate::common::engine_request::<_, StopResponse>(…force: true…). For AIM this call is "the only thing that actually stops the container" (its own comment), yet a faileddocker killstill reports the servicestopped. - The comment's claims are wrong:
- "same as
rocm services stop… unconditionally": the rocm path sends lemonade an HTTP unload and never an engineStop(apps/rocm/src/main.rs:7070-7074). This change newly force-stops lemonade through rocmd only. - "recorded PIDs belong to a short-lived launcher":
serve_httpnow blocks for the container's lifetime.
- "same as
- No test reaches this hunk. serve-27 stops through
rocm services stopinstead. - Confidence 85 · logic · Fix: fold the Stop result into
all_stoppedand the report, decide deliberately what lemonade gets, correct the comment, and add a test thatStopis sent for an AIM record.
- Where:
-
image_digestis never set, applies to every engine, and would break AIM resolution if it were set.- Where:
crates/rocm-core/src/model_catalog.rs:88-93,apps/rocm/src/serve_cmd.rs:140-146,engines/aim/src/registry.rs:43-46 - No catalog entry sets it.
serve_model_ref_for_engineappends@digestfor any engine; its test pins a vLLM HF id,"Qwen/Qwen3-32B-FP8@sha256:abcd1234". - The only AIM entry already carries a tag (
…:0.12.0-preview), so a digest would producerepo:tag@sha256:….split_image_refsplits on@first, which leaves the tag in the repo name and breaks the token scope and the manifest URL. - Meanwhile the shipped entry is pinned only to a floating
-previewtag. - Confidence 85 · logic · Fix: either drop the field, or limit it to container engines, validate
sha256:, strip the tag (or fixsplit_image_ref), and actually pin the AIM entry.
- Where:
-
AIM is missing from engine lists outside the diff.
- Where:
apps/rocmd/src/common.rs:182-193,apps/rocm/src/chat_host_facts.rs:119-124,tests/e2e-cucumber/tests/e2e/examine_steps.rs:283 rocmd_engine_inventory()still lists only lemonade and vllm. It feeds the rocmd MCP engines tool and the bridge snapshot, which now disagree withrocm engines list.- The assistant's host facts say "Serving engines that run here: Lemonade and vLLM", while the updated
skills/rocm-cli-assistant/SKILL.md:27names AIM and defers to those facts. The assistant will therefore say AIM is unavailable. - The "all supported engines are listed" step still checks only
["lemonade","vllm"]. - Confidence 85 · mechanical · Fix: add
aimto all three. Preferably derive the daemon and CLI inventories from a single source.
- Where:
-
Scenario model-catalog-02 fails on an Instinct host with Docker, and its Then step branches on host state.
- Where:
tests/e2e-cucumber/tests/e2e/model_catalog_steps.rs:34-49,tests/e2e-cucumber/src/capability.rs:167-176 - The step uses
engine_available("aim"), which also requires!family_prefers_vllm(gfx)and AIM inavailable_engines. - On an MI300X lane with a running Docker daemon it expects
unsupported_docker_unavailable, which the CLI correctly doesn't print. The scenario is untagged, so it runs there. - A Then that branches on the host asserts no single outcome, and model-catalog-03 already covers the no-Docker branch deterministically.
- Confidence 85 · logic · Fix: delete model-catalog-02, or compare against
docker_available()only.
- Where:
-
Introduced behaviour that no test reaches.
- Where:
apps/rocm/src/serve_cmd.rs:188,crates/rocm-core/src/model_catalog.rs:667-670,engines/aim/src/process.rs:58-100,engines/aim/src/process.rs:340-353,engines/aim/src/docker.rs:36-54 - The catalog's
extra_envpath: the recipe test uses an empty map, nothing servesaim-qwen3.5-9b, and catalog-vs---engine-envprecedence is untested. resolve_model_response: its comment documents a bug found in live validation, but AGENTS.md's regression-test rule is not met.- The
serve_httppath: the success path andblock_until_container_stopsare not exercised. docker runargument construction: the fakedockerignores argv, so-p,--name, the GPU andHIP_VISIBLE_DEVICESbranch, and-eplacement can all be mutated with every test still passing.serve_http_stops_container_and_marks_state_failed_on_readiness_failurestill passes with thedocker::stopcall deleted: its name claims a stop it never checks.- Confidence 85 · mechanical · Fix: add a metadata seam and an argv-recording fake
docker, then cover each path above.
- Where:
-
A unit test writes into the user's real data dir and leaves the file behind.
- Where:
engines/aim/src/process.rs:768-807,engines/aim/src/state.rs:276-311 - Running
cargo test -p rocm-engine-aimleft<data>/engines/aim/state/svc-serve-http-fails.jsonbehind. - The state test creates a temp
dirit never uses; its comment admits the file goes to the real state dir. The same tests also call Docker Hub live (401 warnings in the run). - Confidence 100 · mechanical · Fix: set
ROCM_CLI_DATA_DIRto a temp dir in the subprocess env, drop the unuseddir, and stub the registry.
- Where:
-
Several pieces of code are dead in production.
- Where:
engines/aim/src/lib.rs:102-110,engines/aim/src/state.rs:75,engines/aim/src/registry.rs:20,engines/aim/src/registry.rs:129,crates/rocm-dash-collectors/src/engine_registry.rs:22-25 log_path,runtime_idandenv_idare#[allow(dead_code)]. The "ponytail" comments name a follow-up, but no tracked issue.accelerator_familyis parsed and only read in a test.EngineKind::Aim'sparse_sampleanddefault_portarms have no production caller: managed AIM services land innon_vllmand are never scraped.- A consequence: container output never reaches
rocm services logs, which reads only thedocker pulllog. - Confidence 70 · mechanical · Fix: wire them up (follow
docker logs -fintolog_path, route Aim to the vLLM scrape) or remove them.
- Where:
-
The new manual-testing section's commands don't work, and one of its claims is false.
- Where:
docs/manual-testing.md:725-745 rocm serve Qwen/Qwen3.5-9B --engine aimis an HF id, which AIM resolves as a Docker Hub repo and fails. It also omits the--engine-env AIM_PROFILE_ID=…that the catalog entry and the e2e scenarios both require.rocm services stop qwen3.5-9bis not a valid id; ids areaim-<slug>-<millis>.- "
docker ps -ano longer lists it" is false, given the missingdocker rm. - AGENTS.md §5 requires flag changes to be reflected in
docs/testing.mdanddocs/manual-testing.md;--engine-envappears in neither. - Confidence 85 · mechanical · Fix: correct the commands, and document
--engine-env.
- Where:
-
The PR description misstates the change.
- Stale claims:
- "
docker run -d's client exits immediately, so there's no long-lived process to supervise … never signals a client process at all".serve_httpnow blocks for the container's lifetime, andprocess.rs:340-347says "as this used to". - "the Docker-rule-out/WSL2-passthrough scenarios in
model_serving.feature". No such scenarios exist; the Docker rule-out is model-catalog-03 inmodel_catalog.feature, and there is no WSL2 scenario. - "Stop … cleanly exit[s] the container with no orphan". Exited containers are never removed.
- "
- Unmentioned: the rocmd stop path now force-sends engine
Stopto every engine, including lemonade. Also unmentioned are the word-boundary engine inference inmain.rs/chat.rsand the docs and assistant-skill edits. - Remedy: update the description to match the code. Where the claims are about cleanup, fixing the code instead makes them true.
- Stale claims:
Non-blocking
- The substring fix is applied at only one of two sites.
apps/rocm/src/chat.rs:1416-1418still doesnormalized.contains(engine)with"aim"in the list, so "aiming for low latency" proposesset-default-engine aim.infer_engine_from_requestwas converted for exactly this. The test's "maintain" example doesn't contain "aim". Confidence 85. - A force stop of an already-exited container errors.
docker killon a stopped container returns "is not running", which isn't mapped toOk(false), so stopping a crashed AIM service fails its Stop (engines/aim/src/docker.rs:112-133). Both callers always force, so the gracefuldocker stopbranch never runs. Confidence 70. - Most docker CLI calls have no timeout. Only
docker infois bounded;stop,inspectandlogshang against a wedged daemon (engines/aim/src/docker.rs:121,:148,:165,:192). Confidence 70. - The
--engine-env"ignored" note is wording only.engine_recipe_with_extra_envstill builds a hint for vllm and lemonade, which changes plan output for a flag reported as ignored (apps/rocm/src/serve_cmd.rs:527-531). Confidence 70. - AIM writes its running state to its own path. The running state goes to
state_path(service_id)while the terminal state goes torequest.state_path(engines/aim/src/process.rs:302-309vs:334,:352). The two agree only by coincidence. Confidence 70. - The new coverage floor is low. 32.75% for the new crate is about half the other engines' floors (
coverage-floors.toml:22), consistent with the coverage gaps above. Confidence 70.
Decisions for the author
- Scope: one PR or two (tradeoff). This is 3.4k lines that bundle the engine with the catalog work, and it changes the generic CLI (
--engine-env) and rocmd's stop path for every engine.- For keeping it whole: the catalog entry is how users reach the engine.
- For splitting: the engine-agnostic changes (
--engine-env, the rocmd Stop) affect lemonade and vLLM users and deserve their own review.
- Container lifetime model (tradeoff). A blocking
inspectpoll under rocmd's PID supervisor reuses existing machinery. Usingdocker waitplus--rm(or explicitrm) as the source of truth would fix three of the blocking items above together. That is a rework, but a small one. - Hardware scenarios still unrun. The description says
serve-aim-inferenceandserve-aim-lifecycle-paritywere not run on the Strix Halo runner for this head. Every CI check is green, but none of them runs the@requires-engine:aimlane. Confirm the plan is to run them before merge.
…ed name Containers are now removed on stop, on a failed start and before a re-run, so recovery and restart can reuse the name. serve_http follows the exit code (a crash or vanished container is failed, a deliberate stop is stopped) and tolerates only a bounded run of docker inspect errors. Env values reach docker through a 0600 env file instead of argv; a bare --engine-env KEY forwards from the environment and is never stored. Image metadata is read from the pulled image, with Docker Hub as an optional hint, and the model name the container serves is reported as served_model_id and used for readiness, smoke test, summary and liveness. GPU planning honours --device and resolves an empty selection like vLLM; native Windows rules AIM out. rocmd's engine Stop result is reported and decides confirmation for container engines. image_digest is validated, the shipped AIM entry is pinned, and AIM is listed in the remaining engine inventories. Adds an argv-recording fake docker and serve_http lifecycle tests, and removes the host-dependent model-catalog-02 scenario. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
|
Pushed Blocking findings
Non-blocking findings
Left for the author
|
tomastola
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 999fd74
Review — needs work
Re-review at 999fd74 (previous round: f5e7542 → 2f614d5). Most blocking items from the last round are still open. Two small fixes landed, and one finding is promoted to blocking.
Fixed in 999fd74
- The Windows chat test (
apps/rocm/src/chat.rs) now asserts "vLLM and AIM are skipped on native Windows" and matcheschat_host_facts.rs. - The catalog's "recipe must not be empty" check now counts
extra_envandimage_digest(also raised by nowycondro). - The protocol crate gained tests for
served_model_name(). - Engine inference now lives in one word-boundary helper in
freeform_planner.rs, with a test for "aiming" and "claim".
Blocking
-
New: the readiness wait uses a served-name snapshot taken before the container exists —
apps/rocm/src/service_launch.rs:590- The wait is passed
resolve.served_model_name(), which falls back to the image reference whenever the Docker Hub metadata hint produced no canonical name (offline, private registry, rate limit). The container serves under the model's own name, so the 45 s wait cannot match and a healthy service is reported not-ready. - The supervisor does record the real name later (
process.rs:411), but the wait never re-reads it. Confidence 70 · logic · Fix: re-read the recorded served name during the wait, or have the launch return it. nowycondro raised the same thing.
- The wait is passed
-
rocm services stopandrestartstill ignore the engineStopresult foraim—apps/rocm/src/main.rs:7098(stop_internal_managed_service_confirmed_with)all_stopped = terminated_all && stopping.names_same_processes(&record)never consultsengine_stop. Foraimthe recorded PIDs belong to the supervisor, so a faileddocker killstill recordsstoppedand deletes the endpoint key while the container keeps serving. Restart then starts a second container.- The rocmd path got this rule (
apps/rocmd/src/service.rs), and its comment says both paths share it; this CLI path was not changed. No test covers it. nowycondro found the same. - Confidence 85 · logic · Fix: share one helper between both paths and test a failing engine stop.
-
--host localhostand::1breakdocker run—engines/aim/src/docker.rs:68- It builds
-p {host}:{port}:{container_port}. The CLI acceptslocalhost/::1as loopback and vLLM and Lemonade accept them, but Docker rejects-p localhost:8080:8000("invalid IP address"), and IPv6 needs brackets. No test uses a non-127.0.0.1host. - Confidence 85 · logic · Fix: map
localhostto127.0.0.1, bracket IPv6, and addrun_argstests.
- It builds
-
extra_envis always serialized, so services started before an upgrade fail the idempotency guard —crates/rocm-engine-protocol/src/lib.rs:535, compared atapps/rocm/src/service_launch.rs:340-347- The field is
#[serde(default)]with noskip_serializing_if, so every hint now serializes"extra_env":{}. A vLLM or Lemonade record written before the upgrade lacks the key, and the samerocm servebails with "different serve options". Neighbouring fields already skip when empty. - Confidence 80 · architectural · Fix:
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]and a test that an empty hint omits the key.
- The field is
-
The local chat provider still sends the image reference as the model name for
aim—apps/rocm/src/providers.rs:286,310,325,337,342,477,500served_model_name()is documented as the name to send in requests, and readiness, the smoke test and the summary now use it. The local provider still lists, selects and sendscanonical_model_id, which foraimis the image reference. Traced, not run.- Confidence 70 · logic · Fix: use
served_model_name()for selection, the request body and the listing.
-
Behaviour this PR introduces has no test that can fail on it
- Served-name wiring (
net.rs,service_launch.rs,serve_cmd.rs, restart readiness inmain.rs): every fixture setsserved_model_id: None, so reverting all of it tocanonical_model_idleaves the suite green. aimparser arms inchat.rs(is_engine_status_line,engine_name_from_inventory_line): no test feeds anaimrow.- rocmd's engine
Stop: the test injects a closure, so the real request (force: true, method, payload) never runs. - The platform gate: tests hand-feed the enum, and the real-gate test uses a docker that exits 1, so an always-unavailable gate passes. Nothing uses a docker that exits 0 and expects it allowed.
- Digest hex validation,
extra_envkey/value validation and theaimcapability arms are untested. engines/aimhas no tests forlaunch_service,healthcheck_service,endpoint_response,logs_responseorhandle_envelopedispatch.- Confidence 85 · mechanical · Fix: one test per branch, with fixtures where
served_model_iddiffers fromcanonical_model_id.
- Served-name wiring (
-
Scenario
model-catalog-02fails on native Windows —tests/e2e-cucumber/features/model_catalog.feature- On native Windows
engine_ruled_out_by_platformreturns the native-Windows rule-out before the Docker check, so the output isunsupported_native_windows, not the Docker-unavailable message the step expects. The scenario has no@requires-os:linuxtag. - Confidence 85 · logic · Fix: tag it, or accept the Windows reason in the step.
- On native Windows
-
serve-27does not prove stop or restart —tests/e2e-cucumber/features/model_serving.feature- It never calls
rocm services restart(a path this PR changed) and never asserts the first container is gone. The second serve only waits for/v1/modelsto list the same name, which a surviving first container also satisfies. - Confidence 70 · logic · Fix: assert connection refused and no
rocm-aim-*indocker ps -aafter the stop, and useservices restartor rename the scenario.
- It never calls
-
Engine lists were not updated everywhere
- The static system prompt (
apps/rocm/src/chat.rs,ROCM_CHAT_TOOL_SYSTEM_PROMPT) still says "vLLM and Lemonade are the general serving engines", while the updated skill names AIM andSKILL.mdnames only vLLM as skipped on native Windows. - Also stale:
docs/testing.md,tests/e2e-cucumber/README.md,docs/ci-hardware-testing.md,README.md,CONTRIBUTING.md,docs/llm-tool-use.md,capability.rs,e2e.rs, andfixture_binary.rs("three stand-ins",fake-dockermissing). - Confidence 85 · mechanical · Fix: update each, and give the engine set one owner.
- The static system prompt (
-
Checks on this head are not green
- Three Strix Halo E2E jobs failed, and MI300X and W7900 had not finished when I looked. The Ubuntu failures I read are Lemonade scenarios (llama-server rejecting
--no-mmap), not AIM scenarios; I did not read the Windows log.clippyand the Rust analysis had not reported either. The live AIM serving and lifecycle scenarios still have no result for this head. - Confidence 85 · architectural · Fix: get a full green run on this head before merge.
- Three Strix Halo E2E jobs failed, and MI300X and W7900 had not finished when I looked. The Ubuntu failures I read are Lemonade scenarios (llama-server rejecting
Non-blocking
- Stop can leave the service at
stopping.engines/aim/src/process.rs:557-570writesstopping, and a failingdocker::remove(&name)?returns without restoring the prior status or writing a terminal one. The existing test covers onlystop()failing. nowycondro raised this. Confidence 70. - A bare
--engine-env KEYcannot survive a recovery restart.engines/aim/src/docker.rs:105-112bails whenKEYis not in the current process's environment, which a rocmd-driven restart will not carry. nowycondro raised this. Confidence 70. - A container leaks on state-write errors after
docker run -d.process.rs:390-412return with?while only the readiness-failure branch removes the container. image_presenthas no timeout.docker.rs; a wedged daemon hangsrun_container.- Container logs go unsanitized into error text.
process.rs:511-531, unlike vLLM'sstartup_log_context. device: "rocm_gpu"is reported for CPU runs (state.rs:36-40), and error envelopes drop the cause chain (lib.rs:277).- Standalone
rocmdcannot confirm anaimstop.common.rsspawnscurrent_exe() __engine-stdio aim, which onlyrocmhandles. - Engine
Stopis now also sent to vLLM (service.rs:90), so rocmd's report showsskipped_pidsinstead ofsignaled_pids, contradicting its comment. --engine-env KEY=VALUEvalues still reach the--engine-recipe-jsonargv and the service record, and the flag help does not say so. Therocm chatvalidator does not restrict--engine-enveither.- The reserved env key list is duplicated in
serve_cmd.rs,engines/aim/src/process.rsandmodel_catalog.rs. - The standalone word "aim" still selects the engine. "serve with aim for low latency" picks
--engine aim; the test covers "aiming" and "claim" but not the bare word. engine_runtime_is_native_windows_unsupported(main.rs) still special-cases only vLLM, sorocm enginescan showaimready on native Windows while the gate refuses it. The two engine inventories (builtin_engine_inventory,engine_inventory) also have no sync test.- Docs and catalog:
docs/manual-testing.mdomits--yeson stop and restart and says "API model" where the row is labelledmodel;docs/testing.mdsays adapters applyrequired_flagsloudly while AIM ignores them silently. - Comments: a "Fix for the review finding that…" history comment in
engines/aim/src/lib.rsandmain.rs, and a doc comment now sitting above the wrong test inmain.rs. - Same-class items nowycondro raised, not re-checked by me: the Docker Hub metadata hint has no overall deadline across its three sequential calls (
registry.rs:96);wait_for_exitpollsdocker inspectevery 500 ms instead of usingdocker wait(process.rs:446);image_presentandlocal_image_metadataeach inspect the same image (process.rs:294); andplan_gpusduplicates the GPU-selection policy already in vLLM and Lemonade (process.rs:231).
…test Signed-off-by: Juho Vainio <juho.vainio@amd.com>
|
Pushed Fixed
Left for the author (judgment calls, not oversights)
Verification
|
tomastola
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 375a5eb
Review — needs work
Re-review of the two commits since 999fd74 (f650096a, 375a5eb4). The CLI stop path, the extra_env serialization and the Windows scenario tag are fixed. The rest of the blocking list is still open.
Blocking findings from the last round
Fixed
rocm services stop/restartignored the engineStopresult foraim.main.rsnow foldsengine_stop.is_ok()intoall_stoppedthrough a helper shared with rocmd (rocm_core::engine_stop_decides_confirmation), andstop_of_a_container_engine_is_unconfirmed_when_the_engine_stop_failscovers it.extra_envalways serialized.skip_serializing_if = "BTreeMap::is_empty"is now on the field, so recipes recorded before the upgrade compare equal again.model-catalog-02on native Windows. The scenario now carries@requires-os:linux.- The local chat provider could not select an
aimservice by its served name.select_local_chat_servicenow also matchesserved_model_id, with a test. This is only partly closed; see below.
Still open
- The readiness wait uses a served-name snapshot taken before the container exists —
apps/rocm/src/service_launch.rs:590. It still passesresolve.served_model_name(), which is the image reference whenever the Docker Hub hint gave no canonical name, and nothing re-reads the name the supervisor records later. (nowycondro raised this too.) --host localhostand::1breakdocker run—engines/aim/src/docker.rs:68.-p {host}:{port}:{container_port}is unchanged, and still no test uses a non-127.0.0.1host.- The local chat provider still sends the image reference as the model name. With no explicit model,
providers.rs:325,337,342sendservice.canonical_model_idandstatus()(line 286) lists it, sorocm chat --provider localagainst anaimservice still asks for a name the container does not serve. Only the explicit-model selection path was fixed. - Behaviour this PR introduces still has no test that can fail on it.
- New this round: a test that the API key reaches the container as
VLLM_API_KEY, one that a faileddocker runremoves the container, one that the stop intent is on disk before the kill, and themain.rsstop test. - Still untested: served-name wiring (every fixture still has
served_model_id: None); theaimparser arms inchat.rs; the realrequest_engine_stoprequest (force: true); the platform gate against a docker that exits 0; digest hex andextra_envkey validation; theaimcapability arms; andlaunch_service,healthcheck_service,endpoint_response,logs_responseandhandle_envelope.
- New this round: a test that the API key reaches the container as
serve-27still does not prove restart. It now asserts that norocm-aim-*container is running after the stop, which is a real improvement, but it still never callsrocm services restart, the path this PR changed (main.rs,started.served_model_name()).- Engine lists were not updated everywhere. The static system prompt in
chat.rsstill says "vLLM and Lemonade are the general serving engines", anddocs/testing.md,tests/e2e-cucumber/README.md(@requires-engine:<vllm|lemonade>),docs/ci-hardware-testing.md,CONTRIBUTING.md,docs/llm-tool-use.md,capability.rs,e2e.rsandfixture_binary.rsstill list the old set. - No build or test result exists for this head. Only
docs/readthedocs.comhas reported on 375a5eb. The previous head's Strix Halo E2E jobs failed, and the live AIM scenarios have no result for any head since the engine changes.
New in these commits
GpuPreferrednow bails instead of falling back to CPU (engines/aim/src/process.rs,resolve_use_gpu). That matches the no-silent-CPU-fallback rule and the sibling engines. The test was updated accordingly, and the unit-level change looks right. Worth confirming the CLI error on a host with no usable GPU tells the user to pass--device cpu.- No new blocking problems in the commits. Docker's missing-object wording is now matched case-insensitively and in both spellings (
is_no_such, with a test), the fake docker records the state file at kill time, and the new docs text on--engine-envsecrets andROCM_CLI_AIM_READY_TIMEOUT_SECSmatches the code.
Non-blocking
- Still open: stop leaves the service at
stoppingwhendocker::removefails after a successful kill (process.rs:571);image_presenthas no timeout; container logs go unsanitized into error text;device: "rocm_gpu"is reported for CPU runs;rocmdstandalone cannot confirm anaimstop; engineStopis now sent to vLLM too, so rocmd reportsskipped_pids;--engine-env KEY=VALUEvalues still reach the recipe-json argv and the record (now documented in the README); the reserved env key list is duplicated in three places; the bare word "aim" still selects the engine;engine_runtime_is_native_windows_unsupportedstill special-cases only vLLM; the two engine inventories have no sync test; and the Docker Hub hint has no overall deadline,wait_for_exitpollsdocker inspectevery 500 ms,image_presentandlocal_image_metadatainspect the same image twice, andplan_gpusduplicates the vLLM and Lemonade GPU policy (these four were raised by nowycondro). - Fixed since the last round: the container is now removed when
docker runfails after creating it; the stop and restart commands indocs/manual-testing.mdnow pass--yes; and the misplaced doc comment inmain.rsis back above the right test.
Signed-off-by: Juho Vainio <juho.vainio@amd.com> # Conflicts: # apps/rocm/src/chat.rs # crates/rocm-core/src/lib.rs
nowycondro
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 375a5eb
Review
A large change (53 files, +5187/-127) adding engines/aim, a new Docker-based AIM serving engine, and wiring it through the catalog, dash-tui, chat, and rocmd. Four parallel passes covered the whole diff: the shared rocm-core/protocol surface, e2e tests/docs, the new engines/aim crate itself, and the apps/rocm/rocmd integration. The new engine's own design (secrets via 0600 env-file, GPU-gating composed correctly across native/WSL2/Windows) is sound, but several integration points were updated inconsistently with the sibling engines they're meant to match.
Blocking
- AIM chat requests send the wrong
modelfield to the container, bypassing the veryserved_model_name()fix this PR added everywhere else —apps/rocm/src/providers.rs(LocalProvider::chat,stream_chat_with_callback, ~lines 304-351). This PR's own doc comment explains whyserved_model_id/served_model_name()exist: a container engine'scanonical_model_idis the image to pull, not the name the server answers to. The PR correctly switches every other call site (readiness waits, smoke test, service lookup) toserved_model_name(), but the actual chat request body still sendsrequest.model.as_deref().unwrap_or(&service.canonical_model_id)— the raw user-typed string (e.g. a catalog alias) or the image reference, neither of which the underlying vLLM-wrapping AIM container is likely to recognize as its served model name. This plausibly breaksrocm chat --model <alias>against any AIM-backed service whenever the alias differs from the served model id — exactly the caseserved_model_idexists to handle — and the new test for service selection doesn't exercise the outbound request body, so the gap isn't caught. Confidence 85 · logic. Fix: build the request-body model fromservice.served_model_name(), falling back torequest.modelonly when no service was resolved.
Non-blocking
- A Radeon-only AIM recipe is mislabeled as targeting Strix Halo —
crates/rocm-core/src/model_catalog.json(platform lists ~7-16, newaim-qwen3.5-9brecipe ~543-552) +model_catalog.rs::model_recipe_target_platform_label(~763-776). This PR adds"aim"to both the Strix Halo and Radeon platform engine lists; the label function picks the first platform whose engine list matches, and Strix Halo is listed first — so the new, explicitly Radeon/W7900-onlyaim-qwen3.5-9brecipe (its ownnotes/warningssay so) gets labeled as Strix Halo-targeted inrocm modellistings. No test covers this recipe's label. Confidence 85 · logic. pick_managed_chat_endpoint's fallback model selection ignoresserved_model_id, unlike its sibling consumer —crates/rocm-dash-tui/src/app/chat.rs(~138, 196-211), whileapps/rocm/src/providers.rs::select_local_chat_service(same PR) was correctly updated to check it. Only matters when a live/v1/modelsfetch fails and the dash-tui falls back to the stored record. Confidence 80 · logic.ResolveModel's reporteddevice_policydoesn't reflect the policy actually enforced at launch —engines/aim/src/process.rs(resolve_model_response_with~83-150 vs. enforcement inresolve_use_gpu~349-372).GpuPreferredis normalized toGpuRequiredfor enforcement (no silent CPU fallback, matching sibling engines' stated contract), but the reporteddevice_policyisn't normalized the same way the vLLM sibling engine does it — a user can be showngpu_preferred(implying CPU fallback is possible) while the engine will in fact hard-fail with none. Confidence 75 · logic.container_name()'s lossy sanitization can fold two distinct, independently-valid service ids onto the same Docker container name —engines/aim/src/process.rs(~54-66). Two ids differing only in characters it maps to-collide; since the normalrocm servepath always mints Docker-safe ids, this is only reachable through the engine's own public CLI/stdio entry points, not the primary flow. Confidence 70 · architectural.- New
--engine-envCLI behaviors (refusal on reserved/malformed keys, "ignored for engine X" note) have no Gherkin/e2e coverage, only unit tests — none of these three behaviors need a GPU or Docker, and the repo's ownAGENTS.mdexplicitly states a unit test on the internal helper does not discharge the need for a user-observable-behavior scenario. Confidence 85 · test-coverage (explicit house-rule violation). - The "ignored for engine" message's wording and the behavior it claims aren't asserted together, nor cross-referenced —
apps/rocm/src/serve_cmd.rs, matchingAGENTS.md's "message asserted together with behavior" rule for outcome-claiming messages. Confidence 75 · mechanical.
Decisions for the author
run_container's "already running" early return doesn't check that the running container actually matches the newly requested model — framed in its own doc comment as deliberate idempotency for retries; the realistic trigger (relaunching the same service id with a different model) looks narrow given howrocmdkeys duplicate-launch detection.healthcheck_servicehardcodesdevice: "rocm_gpu"for any AIM state file, even a CPU-launched one — purely cosmetic (the field is documented as display-only) but worth a note since AIM, unlike vLLM, genuinely supports CPU.
Positive signals
- Secrets (API key,
extra_envvalues) are consistently kept out of argv and off disk — routed through a 0600--env-file, redacted before persistence — with direct tests for both. - Native-Windows/WSL2/native-Linux GPU-gating composes correctly with the existing vLLM capability contract.
- The Docker-unavailable gate is exercised for real in e2e tests via a PATH-planted fake
dockerbinary, not just asserted as present. crate_edges.rsadditions correctly preserve the "rocmdmust never depend onrocm" layering invariant.
Deployment notes
This PR currently shows mergeable: CONFLICTING against the base branch — it needs a rebase before it can land. Relatedly, gh pr checks shows essentially no CI signal at this head beyond the Read the Docs build (which passed); the GitHub Actions suite (build/test/clippy/E2E) does not appear to have run at all, which is itself worth investigating before merge, independent of the findings above.
What this covered
Full diff across four parallel passes: shared rocm-core/rocm-engine-protocol surface (model catalog, chat/net/service-record), e2e tests and docs, the new engines/aim crate end to end, and the apps/rocm/apps/rocmd integration points. No Rust toolchain available in this sandbox, so nothing was built or run; all findings rest on static tracing of the code, cross-checked against sibling-engine (vLLM) behavior for consistency. These were blind passes — PR description and existing review comments were not read before forming findings.
tomastola
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 49b081d
Re-review — needs work
49b081d5 is a clean merge of main. The only change to the PR's own diff is the use block in apps/rocm/src/chat.rs (main's plan_freeform_request_with_context rename), resolved correctly. None of the blocking findings from the 375a5eb4 review changed, and that change request stays in force.
Still open (blocking)
- Readiness wait uses a served-name snapshot taken before the container exists (
service_launch.rs:590). --host localhost/::1breakdocker run(engines/aim/src/docker.rs:68).- The local chat provider still sends the image reference as the model name when no model is given (
providers.rs). - Behaviour this PR introduces still has no test that can fail on it.
serve-27still does not prove restart.- Engine lists were not updated everywhere.
- No build or test result yet for this head; the live AIM scenarios have none for any head since the engine changes.
The non-blocking items from the last round are unchanged.
Lint (PowerShell) failed installing PSScriptAnalyzer from the PowerShell Gallery ("No match was found…"). That is unrelated to this PR.
Re-read the served model name during the readiness wait, publish localhost/IPv6 hosts in a form docker accepts, send the served name in local chat requests and status, make the container-stop test independent of a reachable docker daemon, and list AIM among the serving engines. Signed-off-by: Juho Vainio <juho.vainio@amd.com>
nowycondro
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · 49b081d
Review
Adds a new Docker-backed engines/aim serving engine (opt-in --engine aim) alongside vLLM/Lemonade, plus a new aim-qwen3.5-9b catalog recipe, wired through apps/rocm, apps/rocmd, rocm-core, and rocm-engine-protocol, with unit tests and two hardware-gated e2e scenarios. The core lifecycle design (no-shell docker invocation, secrets kept off argv via a 0600 env-file, strict GPU-required enforcement, crate-layering respected) is sound, but CI is currently red on this exact commit and the review surfaced several independently-verified correctness and consistency defects — outcome is needs work.
Blocking
-
CI is red on the reviewed commit —
gh pr checks 708showsCoverage (rocm-dash crates, ratcheted),Lint (PowerShell),Test (affected crates), andwindows-build-and-testall reportingFAILUREon head49b081d5. Logs were not readable here (thegh run view --log-failedAPI path is blocked by this environment's egress proxy with HTTP 403), so the root cause is not characterized beyond the check names.
Confidence 100 · mechanical · Fix: resolve each failure and confirm green before merge; do not merge on a red required check. -
PR description overclaims test status vs. the actual CI result on this head — the Test Plan checks off
[x] cargo test --workspace, but theTest (affected crates)check fails on this same commit (49b081d5).
Confidence 85 · architectural · Fix: either the checkbox needs to reflect the current (failing) state, or the failure needs fixing before the box is checked. -
wait_for_exit's "tolerable daemon wedge" bound is wrong by up to ~60x —engines/aim/src/process.rs:447documentsMAX_CONSECUTIVE_INSPECT_ERRORS(=60,process.rs:26) as "60 polls is about 30 seconds," but thepoll()closure passed at the real call site isdocker::container_state(engines/aim/src/docker.rs:253), which is itself bounded byCLI_TIMEOUT = 30s(docker.rs:20) per failing call. When the daemon is genuinely wedged — the exact scenario this bound exists for — each erroring iteration can take up to ~30s beforepause()even runs, so the real worst case is ~60 × 30s ≈ 30 minutes, not ~30 seconds. The unit tests (process.rs:808wait_for_exit_tolerates_a_few_transient_errors_but_not_a_dead_daemon) only mockpoll/pauseas no-ops, so they never exercise the real timing and wouldn't have caught this.
Confidence 85 · logic · Fix: either givewait_for_exitits own outer wall-clock bound (aswait_for_container_readyalready has), or correct the comment and reduce the error tolerance to match the intended ~30s budget. -
AIM's Instinct-exclusion logic has zero test coverage —
HostCapability::engine_available's"aim"arm (tests/e2e-cucumber/src/capability.rs:167-176) exists specifically to stop a Docker-equipped Instinct lane from running the Radeon-targeted aim image on an incompatible GPU family, butengine_available_respects_platform(capability.rs:828) only exercises"lemonade"and"vllm", never"aim". Compounding this,docker_available()(capability.rs:52-59) shells directly to the realdockerbinary with no injection seam, so even a future attempt to add this case can't be made deterministic without refactoring it first. This is new behavior this change introduces with no test exercising it.
Confidence 85 · logic/test-coverage · Fix: extendengine_available_respects_platformwith an"aim"case (at minimum for the Strix Halo and MI300X profiles already used elsewhere in the same file), and givedocker_availablean injectable seam. -
Two surfaces in this same PR disagree about whether
aimworks on native Windows —rocm model's readiness path (engine_ruled_out_by_platform,apps/rocm/src/main.rs:3219-3225) explicitly rulesaimout on native Windows viaPlatformRuleOut::NativeWindowsAim("AIM's Docker GPU passthrough exists only for Linux and WSL2"). Butrocm engines list's status label (engine_runtime_is_native_windows_unsupported,main.rs:6290-6308) hardcodes its native-Windows check toengine.eq_ignore_ascii_case("vllm")only, returningfalseimmediately for any other engine. Sinceengines/aim/src/lib.rs'sdetect_response(lib.rs:285-295) setsinstalled: reachable(reachable = Docker daemon reachable, with no Windows check at all), a native-Windows host with a reachable Docker Desktop daemon getsrocm engines listreporting aim as"ready"whilerocm model <aim-recipe>reports "aim has no adapter on native Windows; serve it from WSL or Linux" for the same host.
Confidence 85 · architectural · Fix: routeengine_runtime_is_native_windows_unsupportedthrough the sameengine_ruled_out_by_platform/PlatformRuleOutdecision the model-readiness path already uses, so the two surfaces can't disagree about the same host+engine (the existing doc comment onPlatformRuleOutalready states this is supposed to be "the one place that decides why"). -
The new
aim-qwen3.5-9bcatalog recipe is mislabeled under the wrong hardware platform —model_recipe_target_platform_label(crates/rocm-core/src/model_catalog.rs:763-774) resolves a recipe's platform by finding the first platform definition whoseengineslist contains the recipe's firstpreferred_engine. Both the "AMD Ryzen AI - Strix Halo" platform (model_catalog.json:7) and the "AMD Radeon - RX 7000/9000" platform (model_catalog.json:12) list"aim"in theirengines, and Strix Halo is listed first.aim-qwen3.5-9b(model_catalog.json:513) setspreferred_engines: ["aim"], so it resolves to the Strix Halo label — directly contradicting its ownwarningsfield,"Radeon-targeted AIM image; requires Docker and an explicit AIM_PROFILE_ID"(model_catalog.json:553), and its pinnedAIM_PROFILE_ID=vllm-w7900-bf16-tp1-latency(a W7900/Radeon profile).
Confidence 85 · logic · Fix: disambiguatemodel_recipe_target_platform_labelbygfx_families/image profile rather than first-match on engine name, or give the catalog schema an explicit platform field for multi-platform engines likeaim. -
README.md and docs/testing.md were not updated for the new third engine, contradicting this PR's own
docs/engine-plugins.mdedit —docs/testing.md:379still reads "all first-party engine adapters: Lemonade and vLLM," andREADME.md:28("… includes inference engine adapters for Lemonade and vLLM"),README.md:33("full support, including … both inference engines"), andREADME.md:34(Windows row lists "no live dashboard or vLLM" with no mention of aim's native-Windows caveat) are all stale — yetdocs/engine-plugins.md:21(edited in this same PR) now states "Packaged first-party adapters arelemonade,vllm, andaim." This is a direct violation of this repo's ownAGENTS.md§5 rule: "when a command's flags, defaults, arguments, or observable behavior change, update README.md … in the same change — do not leave user-facing docs for a follow-up," and its instruction to grep a repeated claim's wording across all its occurrences.
Confidence 85 · mechanical · Fix: update the three stale locations to mentionaimas a third first-party engine, consistent withdocs/engine-plugins.md.
Non-blocking
image_presenthas no timeout, unlike every sibling Docker call in the same file —engines/aim/src/docker.rs:326-336callsCommand::new(DOCKER_BIN)...status()directly, whilecontainer_state(docker.rs:253),image_config,stop, andremoveall route throughdocker_output(.., CLI_TIMEOUT, ..). A wedged daemon during the pre-pull existence check would hang indefinitely, same failure class as thewait_for_exitfinding above but on a narrower, less-central path.
Confidence 80 · mechanical · Fix: routeimage_presentthroughdocker_output/CLI_TIMEOUTlike its siblings.
Decisions for the author
-
No CI lane exercises AIM's real container lifecycle (start/stop/readiness/crash-recovery) — answered-by-intent.
tests/e2e-cucumber/src/bin/fake-docker.rsis a stub that always exits 1, used only to prove the Docker-unavailable gate (model-catalog-02); the only scenarios that exercise realdocker run/inspect/rm -flifecycle (serve-aim-inference,serve-aim-lifecycle-parity) are@requires-gpu @requires-engine:aim. The PR description already discloses this precisely: "None of the CI jobs run the@requires-engine:aimlane. Recommend running both scenarios and section 9 once against real hardware before merge." Would otherwise have been a blocking test-coverage finding; confirm the recommended hardware run happens before merge, since as of this head it is explicitly marked not yet done ("Outstanding" / unchecked Test Plan item). -
Per-service state is a plain, unlocked
fs::write, shared across the supervisor and CLI processes — non-blocking-improvement.engines/aim/src/state.rs's status file (read inprocess.rs'sfinish_after_exit/stop_service) has no file lock or atomic-rename pattern. Works today because writers are serialized by the service lifecycle, but it's a design choice worth a second look as more concurrent readers/writers are added (e.g. a future health-check poller).
Positive signals
dockeris invoked via directexecvethroughout (std::process::Command, never a shell), avoiding injection through container names, image refs, or env values.- Secrets reach
docker runonly through a 0600--env-file, never argv;RESERVED_ENV_KEYS/RESERVED_ENGINE_ENV_KEYSblock a caller-supplied--engine-envfrom overridingHIP_VISIBLE_DEVICES/VLLM_API_KEY. infer_engine_from_request's chat/CLI engine-name matching was proactively fixed from substring to word-boundary matching in this same PR, so "aiming"/"claim" no longer false-positive-match "aim" — a real latent bug caught and fixed as part of this change, with a test.- Crate-layering is respected: no
rocmd→rocm/rocm-engine-aimedge introduced;cargo xtask check-crate-edgesallowlist entries match the new crate's actualCargo.tomldependencies. - The new
--engine-envflag's validation (charset, reserved-key rejection, newline-injection prevention) is careful and tested.
Deployment notes
- New runtime dependency: this engine requires a reachable Docker daemon on the host;
rocm engines install aim/rocm serve --engine aimwill fail fast with a "Docker not reachable" message when it isn't, perengines/aim/src/lib.rs'sdetect_response. coverage-floors.tomladds arocm-engine-aimfloor (~32.75%) well below siblings' (~64-67%) — plausible for Docker-subprocess-heavy code where most paths need a live daemon to exercise, but not independently re-run/confirmed here.- Native Windows is explicitly unsupported for
aim(Docker GPU passthrough is Linux/WSL2-only) — see the Windows-status contradiction finding above for where this isn't yet consistently surfaced.
What this covered
Full review of the whole change: 24e405a7...49b081d5 (base main merge-base to PR head), 53 files / +5187/-126, fetched via git fetch origin pull/708/head and diffed locally (gh api/gh pr diff are blocked by this environment's egress proxy for this host). Every changed file was read in full by one of six parallel file-grouped workers (engines/aim docker+process core; engines/aim lib/state/registry/install; apps/rocm CLI integration; apps/rocmd + rocm-core + rocm-engine-protocol plumbing; tests/e2e-cucumber; docs/README/config), plus the surrounding contract/caller code each needed for context, and this driver independently re-verified every candidate finding above against the source before scoring (confirmed by tracing the actual code, not taken on the workers' word). Design review and Step 9 (PR description) were done by the driver after findings were settled.
Did not run: cargo test/cargo clippy locally (relied on CI's live result instead, which is authoritative per this repo's own AGENTS.md §8); the "prior changes" source-reading pass (whether earlier PRs' review comments on these same files apply here) did not run — no practical way to retrieve historical cross-PR review comments with the tools available. CI failure logs could not be read (gh run view --log-failed blocked by the egress proxy); failing-check names were taken at face value per gh pr checks/gh pr view --json statusCheckRollup without guessing root cause from workflow YAML. ROCMAI-554/555/23 could not be retrieved — no tracker tool for this identifier scheme is available here, so the ticket relationship is stated from the PR description alone, not an independently-read ticket. This PR's existing review threads/comments (from rominf, tomastola, and the prior nowycondro reviews at earlier commits) were deliberately not read and are not reconciled here, per blind-review method; this is a fresh, independent pass against the current head.
|
Pushed Fixed
Not changed in this commit
|
Addressed in eef0064 (partially; see PR comment)
nowycondro
left a comment
There was a problem hiding this comment.
🔴 Automated review · pr-review-watcher · eef0064
Review — needs work
Full review of the whole change (54 files, +5245/-147, merge-base 24e405a7…head eef0064c), via four independent fan-out passes plus direct re-verification of every surviving finding against the PR-head source.
Implements ROCMAI-554 (new opt-in engines/aim Docker-backed serving engine, part of epic ROCMAI-23) and, bundled in the same branch, ROCMAI-555's aim-qwen3.5-9b catalog recipe — both named in the PR description, which accurately describes the diff's scope and candidly discloses that the GPU-hardware e2e scenarios and the manual restart/crash checks have not yet been re-run on this exact head.
Blocking
-
Container-reuse fast path trusts a running container without validating it matches the new request —
engines/aim/src/process.rs:285-292(run_container,ContainerState::Runningarm)
When a container with the deterministic name is alreadyRunning, the function returns immediately without comparing the running container's actual image againstimage_ref, and without re-evaluatingdevice_policy/extra_envfor the current request. A secondLaunch/ServeHttpfor the sameservice_idwith a different model, a now-gpu_requiredpolicy, or different env can silently keep serving the stale container while reporting metadata from the new request — bypassing the GPU-required fail-fast guarantee this engine otherwise enforces everywhere else. Confidence 75 · architectural · Fix: before reusing, compare the running container's image (docker inspect --format '{{.Config.Image}}') and relevant request params; force remove+recreate or bail clearly on mismatch. -
stop_servicecan leave the persisted status stuck at "stopping" forever ifdocker rmfails after a successful stop —engines/aim/src/process.rs:552-577(the unguardedcrate::docker::remove(&name)?;immediately beforewrite_terminal_state)
Adocker::stopfailure correctly restoresprior_statusbefore returningErr. Adocker::removefailure after a successful stop does not — the?propagates with no status write at all, leaving"stopping"on disk indefinitely with nothing to reconcile it. Only the stop-failure branch is tested. Confidence 72 · logic · Fix: wrapremovethe same waystopis wrapped (write a terminal/restored status before propagating), and add a test for remove-failure-after-successful-stop. -
Stale error message claims
device_policy=gpu_requiredeven when the user only asked for the defaultGpuPreferred—engines/aim/src/process.rs:364(resolve_use_gpu)
A round-2 fixup merged theGpuRequired/GpuPreferredmatch arms (both now fail fast, correctly — no silent CPU fallback per this repo's own AGENTS.md §6) but left thebail!text hardcoded to"device_policy=gpu_required". SinceGpuPreferredis the actual default, the common failure case reports a policy value the user never set. No test checks message content on theGpuPreferredbranch. Confidence 85 · mechanical · Fix: interpolate the actualdevice_policyvalue, or generalize the wording. -
--engine-env KEY=(explicit empty value) is indistinguishable from a bareKEY(env-forward) once parsed —apps/rocm/src/serve_cmd.rs(engine_recipe_with_extra_env):let (key, value) = entry.split_once('=').unwrap_or((entry.as_str(), ""));
"FOO=".split_once('=')yieldsSome(("FOO", "")); bare"FOO"falls through tounwrap_orand yields the identical("FOO", ""). Both are stored as an empty value, which downstream code treats as "forwardFOOfrom the environment" — so a user who explicitly writes--engine-env FOO=to set a literal empty string has that intent silently reinterpreted as the opposite (forward, not set). No test exercises the explicit-empty-value case. Confidence 80 · logic · Fix: useentry.find('=')to distinguish "no=" from "=with an empty right-hand side" and represent the two cases distinctly rather than collapsing both to"". -
Restart's readiness wait never refreshes the served model name, and
reset_for_restart()doesn't clear the stale one —apps/rocm/src/main.rs:7381-7390and its non-refreshingwait_for_service_http_readywrapper (~main.rs:10282-10297), vs. the refreshing closure used by the initial launch atapps/rocm/src/service_launch.rs:587-602; compounded bycrates/rocm-core/src/service_record.rs'sreset_for_restart(), which clearsinference_verified_at_unix_ms/inference_probe_attempted_at_unix_msbut neverserved_model_id
wait_for_service_http_ready_with_progressdocuments exactly why this matters: "a container engine reports the name its server answers to only after it starts, so a name fixed up front can be wrong." The initial-launch path honors this with a closure that callsrecord.clone()+refresh_from_engine_state()on every poll tick; the restart path snapshotsstarted.served_model_name()once, before the wait, and never re-reads it. If the service being restarted had noserved_model_idrecorded yet (e.g. a prior launch that never reached ready), the restart's readiness probe can poll the wrong model name for the entire 45s window. This directly contradicts the PR's own stated AIM lifecycle-parity goal (theserve-aim-lifecycle-paritye2e scenario added in this same branch). Confidence 85 · architectural · Fix: give the restart call site a refreshing closure likeservice_launch.rs's, and/or clearserved_model_idinreset_for_restart(). -
HostCapability::engine_available("aim")'s platform-safety exclusion has zero test coverage —tests/e2e-cucumber/src/capability.rs:167-176
Theaimarm's!family_prefers_vllm(self.gfx_target.as_deref())exclusion exists specifically to stop a Docker-equipped Instinct host from running the Radeon-only AIM image with the wrong GPU profile — exactly the kind of product rule this file's own header says needs a unit test to guard it. The existingengine_available_respects_platformtest covers only the"vllm"/"lemonade"arms; grepping bothcapability.rsandexpectation.rsfinds no"aim"case anywhere in a test module. Inverting the exclusion would pass every existing test and surface only as a confusing failure on real Instinct hardware. This is new behavior this change introduces with no test exercising it. Confidence 82 · logic · Fix: add an MI300X-with-Docker (expectfalse) and a Strix-Halo-with-Docker (expecttrue) case toengine_available_respects_platform.
Non-blocking
None
Decisions for the author
-
The empty-string-as-sentinel pattern for env values recurs at two layers — tradeoff
engines/aim/src/docker.rs:36-55'sRunSpec.env(BTreeMap<String,String>, wherevalue.is_empty()means "forward from process env") has the identical representational gap as the CLI-parsing bug above: the type can't express "set this key to a literal empty string," enforced only by a doc comment rather than the type system. It's well-tested for the cases exercised today, so this isn't itself causing an observed bug the way the CLI-parsing one is. Fixing the CLI bug locally is sufficient for correctness; introducing a sharedenum EnvValue { Literal(String), ForwardFromProcessEnv }across both layers is a deeper, more unifying fix. Author's call whether the recurrence across two independent call sites justifies the larger refactor now or later. -
Healthcheck uses a bare HTTP/inference-probe only, not the image's own
aim validate --scope offlineself-test — tradeoff
ROCMAI-554 explicitly offered a choice here ("use the image's ownaim validateself-test where available, falling back to a bare HTTP probe otherwise — decide during implementation, don't leave it unspecified").engines/aim/src/state.rs::healthcheck_serviceimplements real, meaningful logic (not a stub), satisfying the ticket's actual concern, but it only does the HTTP/inference-probe route — there's no self-test integration and no comment explaining why that route was chosen over the image self-test. Within the ticket's discretion, not a mismatch, but worth the author confirming this was a deliberate choice (e.g., the self-test isn't stable/available across images) rather than an oversight, and recording the reason somewhere for the next reader. -
serve-26/serve-27's "managed runtime active" precondition is unnecessary for
aimspecifically — tradeoff (pre-existing pattern, not introduced or worsened by this PR)
engine_manages_own_runtimereturnstrueforaim(self-contained image, no shared runtime lookup), yet both new e2e scenarios open with a precondition that installs a managed SDK runtime anyway. Every other real-serve GPU scenario in the file — including the existing Lemonade canaries — carries the same boilerplate precondition, so this is a suite-wide pattern this PR followed rather than caused. Author's call whether uniformity across scenarios is worth the extra unnecessary step for self-managed engines.
Positive signals
- Secret handling is deliberate and tested end-to-end:
KEY=VALUElands only in a 0600--env-file, a bareKEYforwards from the engine process's already-set environment, and dedicated subprocess tests confirm neither ever reachesdocker run's argv. - The deliberate-stop-vs-crash distinction (
write_status("stopping")before signalling, checked by the long-lived supervisor against the actual exit) is carefully designed and thoroughly tested, correctly mirroringengines/vllm's existing supervisor-recovery reliability property. - The new
"aim"engine name was threaded through essentially every parallelvllm/lemonadeenumeration site across the CLI, rocmd, dash-tui, serve wizard, host facts, and catalog platform labels, with word-boundary engine-name matching added (and regression-tested) so a request mentioning "aiming" no longer false-matches theaimengine. - The PR description is unusually candid about verification status: it explicitly states which hardware-dependent scenarios and manual checks have not yet been re-run against this exact head, rather than presenting that work as done.
Deployment notes
- This adds a new Docker dependency for the opt-in
--engine aimpath only; it does not change default behavior for any pre-existing model or recipe except the newaim-qwen3.5-9bcatalog entry, which has no other recipe to fall back to. - Per the PR's own disclosure, the two GPU-hardware e2e scenarios (
serve-aim-inference,serve-aim-lifecycle-parity) and the manual stop/restart/crash checks indocs/manual-testing.mdsection 9 have not been run against this head, and no CI lane currently runs the@requires-engine:aimtag. Two of the findings above (the restart readiness-refresh gap and the container-reuse validation gap) sit exactly in the restart/relaunch code paths that live run was meant to exercise — recommend actually running it before or immediately after merge. - CI visibility was limited from this review's vantage point: only a ReadTheDocs build check was retrievable for this head (GitHub's Actions check-runs API returned 403 Forbidden to the token available here), so
cargo test/clippy/coverage status for this exact commit could not be independently confirmed one way or the other. - The
rocm-engine-aimcrate's coverage floor was set to match an actual CI-measured value (not lowered from a prior passing bar to dodge a red check) — verified against the commit history.
What this covered
Every file in the 54-file diff, read completely at the PR head, across four independent passes: (1) the new engines/aim crate plus its rocm-engine-protocol/service_record/engines/vllm context; (2) the cross-crate integration surface (apps/rocm, apps/rocmd, rocm-core, rocm-engine-protocol, rocm-dash-tui, engines/vllm, engines/lemonade); (3) the e2e-cucumber tests and docs/misc changes; (4) a history pass over the branch's first-parent commits plus an OSS leak-scan over the full diff against this repo's own AGENTS.md pattern. Every finding reported above was independently re-verified by reading the exact lines at the PR head before inclusion here (none were taken on a worker's word alone).
Did not run: cargo build/test/clippy/xtask coverage (no Rust toolchain available in this review environment) — all findings are from static reading and direct source tracing, scored accordingly; none reached the "ran and confirmed" (100) tier. "Prior changes" (review comments on earlier, unrelated changes to these files) was not applicable — this is new code in a new crate for most of the diff. Agent-instruction adherence (crate layering, main.rs growth discipline, markdown-link rules, coverage-floor justification, the leak-scan pattern) was checked directly against this repo's own AGENTS.md rather than as a separate formal pass; nothing surfaced beyond what's listed above.
Not reconciled against the PR's own GitHub discussion or review threads — this review was conducted blind to them by design. The PR description and the two Jira tickets it names (ROCMAI-554, ROCMAI-555) were read afterward, by identifier, and reconciled against the findings above; no mismatch was found between the description's claims and what the diff actually contains.
|
🔴 Automated review · pr-review-watcher · 49b081d This automation never files a GitHub approval, so no approving review will
Round 3. Every finding below is new: it was reached blind at 49b081d. The round-2 change request (filed at 999fd74) was dismissed; its objections were not re-settled this round. Review — needs workFull review of the whole change. Blocking
Non-blocking
Decisions for the author
Positive signals
Deployment notes
What this covered
|
tomastola
left a comment
There was a problem hiding this comment.
Re-review of the changes since my last review (eef0064c, "address round-3 review findings").
Blocking
-
No CI has run on this head. Only the Read the Docs build is reported on
eef0064c; no Rust build, unit-test or lint workflow ran. This commit changes Rust inapps/rocmandengines/aim, including the signature ofwait_for_service_http_ready_with_progress, so none of it is shown to compile or pass. Please get the workflows run and green on this commit. -
The description omits two behaviour changes in this commit.
LocalProvider::statusnow listsserved_model_name()instead ofcanonical_model_id(apps/rocm/src/providers.rs:286), andchat/chat_streamnow always send the service's served name rather thanrequest.modelwhen it was given (providers.rs:306,:332).docker run -pnow mapslocalhostto127.0.0.1and brackets bare IPv6 hosts (engines/aim/src/docker.rs:57-84).
The description lists the consumers of the served name and does not mention either. Please add them, or narrow the change.
Non-blocking
localhostis published on IPv4 only (docker.rs:58-60), but the readiness probe still dials the original host viaformat_http_base_url(host, port)(main.rs:10327). Iflocalhostresolves to::1first and the probe does not fall back, readiness would never see the container. I did not trace the connect path. This only matters with--host localhost; the default is127.0.0.1.chat_streamstill reportsservice.canonical_model_idas the summary model (providers.rs:331) while the request body now uses the served name. Useserved_model_name()there too, or comment that it is intentionally canonical.
Notes
- Re-reading the served name from engine state on each readiness tick (
service_launch.rs:590-594) is the right fix for label-less images. The cost is one small file read per 250 ms tick over a 45 s window. - Good: the status/chat consistency assertion in
providers.rs, theDOCKER_HOSTisolation in the AIM stop-failure test, and the newrun_argshost-form test.
I did not build or run anything locally.
Signed-off-by: Juho Vainio <juho.vainio@amd.com> # Conflicts: # apps/rocm/src/main.rs
tomastola
left a comment
There was a problem hiding this comment.
Re-review of the new commits since eef0064c. The only change on the branch is a merge of main (e5a5db78). In the AIM-related files it only moves an import and a comment path (service_launch.rs, providers.rs), and I found nothing new in it.
My two blocking findings still hold:
- No CI has run on this head. Only the Read the Docs build is reported on
e5a5db78; no Rust build, unit-test or lint workflow has run. The merge also pulls in large refactors ofmain.rs, which this branch's changes now sit on top of, so a green run on the merged head matters. - The description still omits two behaviour changes. It does not mention that
statusandchat/chat_streamnow use the service's served name instead ofrequest.modelor the canonical id (providers.rs), nor thatdocker run -pnow mapslocalhostto127.0.0.1and brackets bare IPv6 hosts (engines/aim/src/docker.rs). Please add them to the description, or narrow the change.
The two non-blocking items from my last review are unchanged:
localhostis published on IPv4 only, while the readiness probe still dials the original host.chat_streamstill reportscanonical_model_idas the summary model.
I will lift my change request once CI is green on the head and the description is updated.
Signed-off-by: Juho Vainio <juho.vainio@amd.com> # Conflicts: # apps/rocm/src/main.rs
tomastola
left a comment
There was a problem hiding this comment.
Re-review of the new commits since e5a5db78. The only change is another merge of main (5a7eae41); none of the AIM files changed beyond import paths in service_launch.rs, so I found nothing new in it.
- CI is now running on the head. The earlier blocker (no workflow had run) is resolved. Several checks have passed and the Rust checks (clippy, Analyze (rust), coverage) are still pending. I will not treat the code as verified until they are green.
- The description still omits two behaviour changes, so that blocker holds.
statusandchat/chat_streamnow use the service's served name instead ofrequest.modelor the canonical id (apps/rocm/src/providers.rs).docker run -pnow mapslocalhostto127.0.0.1and brackets bare IPv6 hosts (engines/aim/src/docker.rs). Neither appears in the PR body. Please add them, or narrow the change.
The two non-blocking items from my earlier review are unchanged: the IPv4-only localhost publish versus the readiness probe, and chat_stream still reporting canonical_model_id.
I will lift my change request once the description is updated and CI is green.
…name in chat stream summary Signed-off-by: Juho Vainio <juho.vainio@amd.com>
|
Pushed
|
Addressed in 22cb68a; PR description updated
tomastola
left a comment
There was a problem hiding this comment.
Re-review of the change since 5a7eae41 (22cb68ab). My two earlier blockers are resolved, and CI is green on this head.
- CI: all 24 reported checks pass (one is skipped), including clippy, Analyze (rust) and coverage.
- Description: the PR body now covers the served-name change in
status/chat/chat_streamand thedocker run -phandling oflocalhostand IPv6 hosts. - Non-blocking items from my earlier review: both are addressed.
connect_tcp_streamnow tries every resolved address (crates/rocm-core/src/net.rs), solocalhostresolving to::1first still reaches the IPv4 listener. The new test binds127.0.0.1and connects vialocalhost.chat_streamnow reportsserved_model_name()in its summary (providers.rs), matching the request body.
I found nothing new in the new commit. My change request is lifted.
Summary
Implements ROCMAI-554 (part of epic ROCMAI-23): a new
engines/aimplugin addingopt-in support for AMD Enterprise AI AIM pre-built Docker containers as a third
serving engine (
--engine aim), alongside native/managed vLLM and Lemonade. Forevery pre-existing model, this is purely additive:
--engine aimis required toopt in and no existing recipe's engine selection changes. The one exception is the
new
aim-qwen3.5-9bcatalog recipe added under ROCMAI-555 (see below): resolvingthat specific model id selects
aimas its engine by default, since that id has noother recipe to fall back to.
engines/aimcrate: Docker-backed launch/stop/readiness/logs, mirroringengines/vllm's structure but tracking the container by name instead of PID.serve_httpis the long-lived process rocmd supervises: it starts the container,waits for readiness, then blocks for the container's lifetime and records how it
ended:
stoppedfor a deliberate stop or clean exit,failedplus a non-zeroexit for a crash or a container removed out from under it, so rocmd's recovery
runs. A transient
docker inspectfailure is tolerated only a bounded number oftimes in a row. The container is stopped and removed by name, never by signalling
a client process.
docker rm -fthe container, so its name and the weights it downloaded arereleased and restart/recovery can reuse the service id.
engines/aim/src/registry.rs):AIM_ACCELERATOR_TYPE,PYTORCH_ROCM_ARCHand the canonical-name label are read from the pulled imagewith
docker image inspect, which works for any registry. A best-effort DockerHub lookup is used only as a pre-pull hint (skipped for non-Docker-Hub registries,
and its failure never blocks
ResolveModel).not the image reference.
ResolveModelResponseandManagedServiceRecordgain anoptional
served_model_id(the record adopts it from the engine state), andreadiness, the smoke test, the serve summary, the healthcheck and the record
liveness probe all use it. If the image has no label, the engine discovers the
name from
/v1/models.--deviceis honoured, an empty selection resolves tothe first usable GPU,
gpu_requiredfails fast, and agpu_preferredfallback toCPU prints a warning. Native Windows is ruled out (Docker GPU passthrough is
Linux/WSL2 only).
"aim"added to dash chat's OpenAI-compatible enginelist and the serve wizard, to the CLI and rocmd engine inventories, to the
assistant's host facts, and to the
--override-generation-configpassthrough;AIM opted into
engine_manages_own_runtime(self-contained image, no sharedTheRock runtime lookup).
--engine-env KEY[=VALUE]CLI flag (consumed only byaim; other enginesignore it and the command says so).
KEY=VALUEis stored with the service record,so it is for non-secret values such as
AIM_PROFILE_ID; a bareKEYforwards thevariable from the engine process's environment and is never stored, which is the
route for secrets such as
HF_TOKEN. Values reachdocker runthrough a 0600--env-file, never argv, andVLLM_API_KEY/HIP_VISIBLE_DEVICESare reserved.ROCM_CLI_AIM_READY_TIMEOUT_SECSreadiness timeout (mirrors vLLM'sexisting
ROCM_CLI_VLLM_READY_TIMEOUT_SECS), fixing a false "status: failed"record observed live when a cold-start 9B-model HF download legitimately exceeds
the previous hardcoded 5-minute cap.
aim-qwen3.5-9bcatalog recipe (crates/rocm-core/src/model_catalog.json), pinnedto the image digest for its
0.12.0-previewtag; the optionalimage_digestfieldon
ModelRecipeEngineRecord(catalog validation requiressha256:plus 64 hexcharacters and only accepts it on
aim); and themodel_catalog.featurescenarios. ROCMAI-556 (catalog freshness tooling) is unrelated and genuinely out of
scope.
Stop(not for Lemonade, whichrocm services stopunloads over HTTP), reportsits result in the stop report, and for
aimlets that result decide whether thestop is confirmed; chat/CLI engine inference matches engine names on word
boundaries, so "aiming" no longer selects
aim; plus the docs andskills/rocm-cli-assistantedits.tests/e2e-cucumberscenarios exercising AIM end to end on real hardware:serve-aim-inference(serve-26, a real AIM serve + inference round trip) andserve-aim-lifecycle-parity(serve-27, stop, then restart). Both are@requires-gpu @requires-engine:aim.model_catalog.featurecovers the cataloglisting (model-catalog-01) and the Docker-unavailable flag with a planted failing
docker(model-catalog-02). There is no WSL2 passthrough scenario.LocalProvider::statuslists each ready service's served model name (not the canonical id), andchat/chat_streamsend the service's served name in the request body and report it in the response/summary, rather than echoingrequest.modelor the canonical id.request.modelstill selects which service handles the call. A label-less image serves under a name only/v1/modelsknows, so the canonical id would be rejected by the container.docker run -phost handling (engines/aim/src/docker.rs):--host localhostis published as127.0.0.1(Docker wants an IP) and bare IPv6 hosts are bracketed. The readiness probe'sconnect_tcp_streamnow tries every resolved address, solocalhostresolving to::1first still reaches the IPv4 listener.Live validation (Strix Halo, gfx1151, Docker)
Done against an earlier revision of this branch, before the container-lifecycle
rework above; it needs to be repeated on this head.
(
amdenterpriseai/aim-radeon-qwen-qwen3-5-9b:0.12.0-preview) with--engine-env AIM_PROFILE_ID=...passthrough.serve-aim-inferenceandserve-aim-lifecycle-parityhave notbeen run against the Strix Halo self-hosted runner for this head, and the manual
steps in
docs/manual-testing.mdsection 9 (stop removes the container, restartreuses the service id, a killed container reads
failed) have not been run onhardware. None of the CI jobs run the
@requires-engine:aimlane. Recommendrunning both scenarios and section 9 once against real hardware before merge.
Out of scope
ROCMAI-556 (catalog freshness tooling) is a separate follow-on subticket, with no code
dependency from this crate.
Test plan
cargo build --workspace --all-targetscargo clippy --workspace --all-targets -- -D warningscargo test --workspaceengines/aim:docker runargv construction (env values neveron argv), container state parsing, and a fake
dockeronPATHthat recordsargv and env files and scripts
docker inspect, drivingrun_container,serve_http(ready then clean exit, crash after ready, container removed whileserving, readiness failure),
wait_for_container_ready,stop_serviceStopsent foraim, not sent for Lemonade, a failedStopleaves an
aimservice unconfirmedserve-aim-inference,serve-aim-lifecycle-parityanddocs/manual-testing.mdsection 9 on the Strix Halo self-hosted runner forthis head