Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
05a256c
ROCMAI-554: add engines/aim Docker-based AIM serving engine
juhovainio Oct 16, 2026
de3b9bb
ROCMAI-554: add e2e-cucumber coverage for the aim engine
juhovainio Oct 16, 2026
ed9fe91
ROCMAI-554: fix AIM model-id handling for docker pull and readiness m…
juhovainio Oct 16, 2026
2db4bac
ROCMAI-554: add generic --engine-env passthrough for engine recipes
juhovainio Oct 16, 2026
1ae2386
ROCMAI-554: make AIM container ready-timeout configurable
juhovainio Oct 16, 2026
b69971b
ROCMAI-554: fix e2e-cucumber AIM scenario to use a Strix-Halo-compati…
juhovainio Oct 16, 2026
cf3d3b2
ROCMAI-554: rule the aim engine out when Docker is unreachable
juhovainio Oct 19, 2026
18e9969
ROCMAI-555: surface a hardware-verified AIM recipe in the model catalog
juhovainio Oct 19, 2026
bed4a2f
ROCMAI-555: correct stale AIM comments after catalog entry landed
juhovainio Oct 19, 2026
96d8532
ROCMAI-555: add e2e coverage for AIM catalog discovery and availability
juhovainio Oct 19, 2026
be35c7e
ROCMAI-554: add WSL2 GPU passthrough args to AIM's docker run
juhovainio Oct 19, 2026
2729160
ROCMAI-554: add unit test for AIM install's Docker-unreachable bail-out
juhovainio Oct 19, 2026
a0d4233
ROCMAI-555: add optional image_digest field to ModelRecipeEngineRecord
juhovainio Oct 19, 2026
a786a3d
ROCMAI-554: add AIM lifecycle-parity e2e scenario (stop, then restart)
juhovainio Oct 19, 2026
893a363
ROCMAI-554: fix CI failures from the AIM engine addition
juhovainio Oct 22, 2026
07c33fe
Lower rocm-engine-aim coverage floor to match CI's measurement
juhovainio Oct 22, 2026
f5e7542
Address PR #708 review feedback (CHANGES_REQUESTED)
juhovainio Oct 26, 2026
2f614d5
Address PR #708 second review: AIM container lifecycle, secrets, serv…
juhovainio Oct 26, 2026
ed8dbed
Merge remote-tracking branch 'origin/main' into fix-708
juhovainio Oct 26, 2026
999fd74
Merge origin/main into rocmai-554-engine-aim; fix PR CI/review findings
juhovainio Oct 26, 2026
f650096
fix(aim): address round-2 review findings
juhovainio Oct 27, 2026
375a5eb
test(providers): serve a models endpoint for the served-id selection …
juhovainio Oct 27, 2026
49b081d
Merge remote-tracking branch 'origin/main' into fix-708
juhovainio Oct 27, 2026
eef0064
fix(aim): address round-3 review findings
juhovainio Oct 27, 2026
e5a5db7
Merge remote-tracking branch 'origin/main' into fix-708
juhovainio Oct 28, 2026
5a7eae4
Merge remote-tracking branch 'origin/main' into fix-708
juhovainio Oct 28, 2026
22cb68a
fix(aim): try all resolved addresses on probe connect; report served …
juhovainio Oct 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ Current workspace members:

- apps: `apps/rocm`, `apps/rocmd`
- shared crates: `crates/rocm-core`, `crates/rocm-engine-protocol`
- engine crates: `engines/lemonade`, `engines/vllm`
- engine crates: `engines/lemonade`, `engines/vllm`, `engines/aim`

Shared UI components — reuse rather than hand-rolling new ones. The CLI (`apps/rocm`) and the dash-TUI (`crates/rocm-dash-tui`) each have their own set; they are not interchangeable (see the naming-collision warning below).

Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ cargo install lychee@0.24.2 --locked # pinned to match the CI docs-links job (
| `crates/rocm-core` | Core library |
| `crates/rocm-dash-*` | Dashboard terminal user interface (TUI) libraries |
| `crates/rocm-engine-protocol` | Engine IPC protocol |
| `engines/` | Inference engine adapters (lemonade, vllm) |
| `engines/` | Inference engine adapters (lemonade, vllm, aim) |

### Module organization

Expand Down
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ members = [
"crates/rocm-dash-daemon",
"crates/rocm-dash-tui",
"crates/e2e-report",
"engines/aim",
"engines/lemonade",
"engines/vllm",
"xtask",
Expand Down
19 changes: 16 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -748,7 +748,7 @@ rocm engines install <engine> [--runtime-id KEY] [--python-version X.Y] [--reins
rocm engines shell <engine> [--runtime-id KEY | --env-id ID] [--shell PATH]
```

Supported engines: `lemonade`, `vllm`.
Supported engines: `lemonade`, `vllm`, `aim`.

`--yes` approves required system-package installs, such as OpenMPI for vLLM,
without asking.
Expand Down Expand Up @@ -796,7 +796,7 @@ GPU memory, and preferred engines.
Start a local OpenAI-compatible model server:

```
rocm serve <model> [--engine lemonade|vllm]
rocm serve <model> [--engine lemonade|vllm|aim]
[--device gpu_required|gpu_preferred]
[--gpu auto|<index>]
[--runtime-id KEY | --env-id ID]
Expand All @@ -806,7 +806,20 @@ rocm serve <model> [--engine lemonade|vllm]
[--allow-public-bind] [--require-api-key] [--api-key KEY]
[--temperature TEMP] [--top-p PROB] [--max-tokens N]
[--gpu-memory-utilization FRACTION] [--tool-call-parser NAME]
```
[--engine-env KEY=VALUE]...
```

`--engine aim` runs the model in a Docker container instead of a host
process; it requires a reachable Docker daemon and is Linux/WSL2 only.
`--engine-env` passes an extra environment variable through to the engine's
own process/container (repeatable); currently only `aim` consumes these (e.g.
`AIM_PROFILE_ID`). `KEY=VALUE` values are visible in the detached child's
argv and in `rocm services list --json`, so do not put secrets there; pass a
bare `KEY` (e.g. `--engine-env HF_TOKEN`) to forward the value from your
current environment without storing it. A service restarted by the daemon
after a crash inherits the daemon's environment, not yours, so a forwarded
secret must also be set there. `ROCM_CLI_AIM_READY_TIMEOUT_SECS` raises the
5-minute readiness wait for a cold-start model download.

`--gpu-memory-utilization` (a fraction above 0.0 and up to 1.0 of each GPU's
total VRAM) and `--tool-call-parser` (for example `hermes`, `llama3_json`, or
Expand Down
2 changes: 2 additions & 0 deletions apps/rocm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ e2e-test-hooks = [
"rocm-core/e2e-test-hooks",
"rocm-engine-lemonade/e2e-test-hooks",
"rocm-engine-vllm/e2e-test-hooks",
"rocm-engine-aim/e2e-test-hooks",
]

[dependencies]
Expand All @@ -39,6 +40,7 @@ rocm-engine-lemonade = { path = "../../engines/lemonade" }
rocm-engine-protocol = { path = "../../crates/rocm-engine-protocol" }
rocmd = { path = "../rocmd" }
rocm-engine-vllm = { path = "../../engines/vllm" }
rocm-engine-aim = { path = "../../engines/aim" }
rpassword.workspace = true
serde.workspace = true
serde_json.workspace = true
Expand Down
58 changes: 42 additions & 16 deletions apps/rocm/src/chat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
//! above.

use crate::freeform_planner::{
format_structured_tool_call, plan_freeform_request_with_context, serve_args_request_cpu_device,
format_structured_tool_call, infer_engine_from_request, plan_freeform_request_with_context,
serve_args_request_cpu_device,
};
use crate::managed_service_status::{
friendly_engine_label, load_managed_services, managed_service_is_live,
Expand Down Expand Up @@ -326,7 +327,7 @@ fn parse_bracketed_csv(line: &str, start: &str, end: &str) -> Vec<String> {
fn is_engine_status_line(line: &str) -> bool {
matches!(
line.split_once(':').map(|(engine, _)| engine),
Some("lemonade" | "vllm")
Some("lemonade" | "vllm" | "aim")
)
}

Expand Down Expand Up @@ -437,7 +438,7 @@ fn parse_deterministic_engine_rows(tool_text: &str) -> Vec<DeterministicEngineRo
}

fn engine_name_from_inventory_line(line: &str) -> Option<&'static str> {
["lemonade", "vllm"].into_iter().find(|engine| {
["lemonade", "vllm", "aim"].into_iter().find(|engine| {
line == *engine
|| line
.strip_prefix(*engine)
Expand Down Expand Up @@ -1417,17 +1418,16 @@ fn fallback_config_tool_call_for_prompt(normalized: &str) -> Option<providers::C
});
}
if any_substring(normalized, &["default engine", "set engine", "use engine"]) {
for engine in ["lemonade", "vllm"] {
if normalized.contains(engine) {
return Some(providers::ChatToolCall {
id: Some("fallback-config-default-engine".to_owned()),
name: "rocm_command".to_owned(),
arguments: serde_json::json!({
"args": ["config", "set-default-engine", engine],
"reason": "Change ROCm CLI's default engine after the user approves it."
}),
});
}
// Word-boundary inference: a bare `contains("aim")` matched "aiming".
if let Some(engine) = infer_engine_from_request(normalized) {
return Some(providers::ChatToolCall {
id: Some("fallback-config-default-engine".to_owned()),
name: "rocm_command".to_owned(),
arguments: serde_json::json!({
"args": ["config", "set-default-engine", engine],
"reason": "Change ROCm CLI's default engine after the user approves it."
}),
});
}
}
if normalized.contains("telemetry") {
Expand Down Expand Up @@ -1801,7 +1801,7 @@ fn find_ascii_case_insensitive(haystack: &str, needle: &str) -> Option<usize> {
/// to, and a WSL user was told "on native Windows, vLLM is skipped" while vLLM
/// was in fact their supported path. Anything host-dependent now comes from
/// [`chat_host_facts::HostFacts`], which knows which machine it is describing.
const ROCM_CHAT_TOOL_SYSTEM_PROMPT: &str = "You are ROCm CLI's local assistant. Speak in simple English for non-technical users. Use the provided ROCm tools when you need to inspect this machine, preview setup, read service logs, check updates, inspect automations, install or start ROCm-managed apps, or request ROCm/TheRock, config, engine, app, and local model server changes. For simple greetings or thanks like hello, hi, hey, ok, or thank you, reply normally; do not inspect ROCm, do not call tools, and do not launch or propose a model server. Tool-use rules: inspect first with read-only tools; call rocm_command only with argv-style args and no shell text; use natural_language_plan for ROCm requests that do not fit another read-only tool; ask for a mutating tool call only after explaining why it is needed; summarize tool results after they are returned. Read-only tools may run immediately. Tools that install, launch, stop, delete, or change state require user approval; request rocm_command and explain why. For 'is X running?', 'what is running?', status, or port questions, inspect before answering and do not start, stop, install, or serve anything. For ComfyUI or port 8188 use [\"comfyui\",\"status\"] or port_status. For vLLM, Lemonade, qwen, or local model servers use [\"services\",\"list\",\"--all\"] for running state and [\"engines\",\"list\"] for installed/available engine state. Treat ready/running as running, starting/recovering as starting, failed/stopped as not running, and no matching record as unknown or not managed by ROCm CLI. Interpret Examine carefully: active_runtime_status=ready means ROCm CLI has an active managed TheRock/ROCm runtime; legacy_rocm_status=not_detected only means no global system ROCm install was found. If active_runtime_status=ready, tell the user ROCm/TheRock is installed and active for ROCm CLI. For 'is TheRock installed', 'is ROCm installed', or 'which GPU is on this machine', use examine or gpu_snapshot before answering. For 'how do I setup TheRock' or install/setup requests, guide the user to choose an install folder first; do not answer with only a status check. For 'which LLMs can this machine support', use rocm_command args [\"model\"] or natural_language_plan before answering. For TheRock installs, always let the user choose the install folder. If the user names a folder or prefix, preserve that exact folder with [\"--prefix\",\"PATH\"]; you may call path_exists first to check whether that user-provided folder or its parent exists. If the user asks you to install TheRock/ROCm but has not named a folder, ask for the folder or let the guided setup folder picker collect it; do not invent a hidden default folder and do not request an install command without --prefix. Use rocm_command args [\"install\",\"sdk\",\"--channel\",\"release\",\"--format\",\"wheel\",\"--prefix\",\"PATH\"] only when the user asks you to install it and a folder is known; for a requested build date add [\"--build-date\",\"YYYY-MM-DD\"] and for a requested exact version add [\"--version\",\"VERSION\"]. For config changes, inspect with [\"config\",\"show\"] first when useful, then request config subcommands such as [\"config\",\"set-default-engine\",\"lemonade\"], [\"config\",\"set-default-runtime\",\"RUNTIME_KEY\"], or [\"config\",\"set-telemetry\",\"local\"] only after explaining why. For ComfyUI, use rocm_command with args like [\"comfyui\",\"status\"], [\"comfyui\",\"logs\"], [\"comfyui\",\"install\"], [\"comfyui\",\"start\"], or [\"comfyui\",\"stop\"]. First-time setup is the same thing as bootstrap in ROCm CLI; it is a deterministic ROCm setup flow, not a separate model chat. The built-in local assistant is fixed to qwen, which maps to Qwen3-4B-Instruct-2507-GGUF served by Lemonade with gpu_required. vLLM and Lemonade are the general serving engines; inspect or manage them when the user asks about general model serving, but do not switch the built-in assistant away from Lemonade. Use qwen-smoke only for a quick server smoke test. For vLLM management, inspect engines first and use [\"engines\",\"install\",\"vllm\"] or [\"serve\",\"MODEL\",\"--engine\",\"vllm\",\"--device\",\"gpu_required\",\"--managed\"] only where the host supports it. Do not invent shell commands and do not request CPU fallback.";
const ROCM_CHAT_TOOL_SYSTEM_PROMPT: &str = "You are ROCm CLI's local assistant. Speak in simple English for non-technical users. Use the provided ROCm tools when you need to inspect this machine, preview setup, read service logs, check updates, inspect automations, install or start ROCm-managed apps, or request ROCm/TheRock, config, engine, app, and local model server changes. For simple greetings or thanks like hello, hi, hey, ok, or thank you, reply normally; do not inspect ROCm, do not call tools, and do not launch or propose a model server. Tool-use rules: inspect first with read-only tools; call rocm_command only with argv-style args and no shell text; use natural_language_plan for ROCm requests that do not fit another read-only tool; ask for a mutating tool call only after explaining why it is needed; summarize tool results after they are returned. Read-only tools may run immediately. Tools that install, launch, stop, delete, or change state require user approval; request rocm_command and explain why. For 'is X running?', 'what is running?', status, or port questions, inspect before answering and do not start, stop, install, or serve anything. For ComfyUI or port 8188 use [\"comfyui\",\"status\"] or port_status. For vLLM, Lemonade, AIM, qwen, or local model servers use [\"services\",\"list\",\"--all\"] for running state and [\"engines\",\"list\"] for installed/available engine state. Treat ready/running as running, starting/recovering as starting, failed/stopped as not running, and no matching record as unknown or not managed by ROCm CLI. Interpret Examine carefully: active_runtime_status=ready means ROCm CLI has an active managed TheRock/ROCm runtime; legacy_rocm_status=not_detected only means no global system ROCm install was found. If active_runtime_status=ready, tell the user ROCm/TheRock is installed and active for ROCm CLI. For 'is TheRock installed', 'is ROCm installed', or 'which GPU is on this machine', use examine or gpu_snapshot before answering. For 'how do I setup TheRock' or install/setup requests, guide the user to choose an install folder first; do not answer with only a status check. For 'which LLMs can this machine support', use rocm_command args [\"model\"] or natural_language_plan before answering. For TheRock installs, always let the user choose the install folder. If the user names a folder or prefix, preserve that exact folder with [\"--prefix\",\"PATH\"]; you may call path_exists first to check whether that user-provided folder or its parent exists. If the user asks you to install TheRock/ROCm but has not named a folder, ask for the folder or let the guided setup folder picker collect it; do not invent a hidden default folder and do not request an install command without --prefix. Use rocm_command args [\"install\",\"sdk\",\"--channel\",\"release\",\"--format\",\"wheel\",\"--prefix\",\"PATH\"] only when the user asks you to install it and a folder is known; for a requested build date add [\"--build-date\",\"YYYY-MM-DD\"] and for a requested exact version add [\"--version\",\"VERSION\"]. For config changes, inspect with [\"config\",\"show\"] first when useful, then request config subcommands such as [\"config\",\"set-default-engine\",\"lemonade\"], [\"config\",\"set-default-runtime\",\"RUNTIME_KEY\"], or [\"config\",\"set-telemetry\",\"local\"] only after explaining why. For ComfyUI, use rocm_command with args like [\"comfyui\",\"status\"], [\"comfyui\",\"logs\"], [\"comfyui\",\"install\"], [\"comfyui\",\"start\"], or [\"comfyui\",\"stop\"]. First-time setup is the same thing as bootstrap in ROCm CLI; it is a deterministic ROCm setup flow, not a separate model chat. The built-in local assistant is fixed to qwen, which maps to Qwen3-4B-Instruct-2507-GGUF served by Lemonade with gpu_required. vLLM, Lemonade, and AIM (Docker-based) are the general serving engines; inspect or manage them when the user asks about general model serving, but do not switch the built-in assistant away from Lemonade. Use qwen-smoke only for a quick server smoke test. For vLLM management, inspect engines first and use [\"engines\",\"install\",\"vllm\"] or [\"serve\",\"MODEL\",\"--engine\",\"vllm\",\"--device\",\"gpu_required\",\"--managed\"] only where the host supports it. Do not invent shell commands and do not request CPU fallback.";
const ROCM_CHAT_TOOL_SKILL: &str = include_str!("../../../skills/rocm-cli-assistant/SKILL.md");

fn rocm_chat_tool_system_prompt() -> String {
Expand Down Expand Up @@ -5148,7 +5148,7 @@ endpoint: 127.0.0.1:8001
// Stated only where it is true: present on Windows, absent elsewhere.
let grounded = rocm_chat_tool_system_prompt_for_host(None);
assert_eq!(
grounded.contains("vLLM is skipped on native Windows"),
grounded.contains("vLLM and AIM are skipped on native Windows"),
cfg!(windows),
"the vLLM caveat must track the host:\n{grounded}"
);
Expand Down Expand Up @@ -5524,6 +5524,32 @@ model recipes
assert_eq!(approval.command_title, "Config");
}

#[test]
fn default_engine_fallback_matches_aim_only_as_a_word() {
let proposed = |prompt: &str| {
fallback_config_tool_call_for_prompt(prompt).map(|call| {
call.arguments["args"][2]
.as_str()
.expect("engine arg")
.to_owned()
})
};
assert_eq!(
proposed("change the default engine to aim").as_deref(),
Some("aim")
);
assert_eq!(
proposed("change the default engine to vllm, aiming for low latency").as_deref(),
Some("vllm")
);
assert_eq!(
proposed(
"change the default engine so I can maintain my claim, aiming for low latency"
),
None
);
}

#[test]
fn setup_status_is_read_only() {
for args in [
Expand Down
13 changes: 7 additions & 6 deletions apps/rocm/src/chat_host_facts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,11 @@ impl HostFacts {
format!("- ROCm/TheRock family for this GPU: {f}\n")
});
let engines = if self.vllm_available() {
"- Serving engines that run here: Lemonade and vLLM.\n"
"- Serving engines that run here: Lemonade, vLLM and AIM (AIM runs models from Docker \
containers and needs a running Docker daemon).\n"
} else {
"- Serving engines that run here: Lemonade. vLLM is skipped on native Windows; \
tell the user to use WSL2 or Linux for that ROCm GPU engine.\n"
"- Serving engines that run here: Lemonade. vLLM and AIM are skipped on native Windows; \
tell the user to use WSL2 or Linux for those ROCm GPU engines.\n"
};
format!(
"Facts about the machine you are running on, detected by ROCm CLI. Trust these \
Expand Down Expand Up @@ -171,8 +172,8 @@ mod tests {
"the detected adapter and target must both appear:\n{block}"
);
assert!(
block.contains("Lemonade and vLLM"),
"vLLM is available under WSL:\n{block}"
block.contains("Lemonade, vLLM and AIM"),
"vLLM and AIM are available under WSL:\n{block}"
);
assert!(
!block.contains("ROCm/TheRock family"),
Expand All @@ -194,7 +195,7 @@ mod tests {
let block = facts.render();
assert!(block.contains("- Operating system: Windows"), "{block}");
assert!(
block.contains("vLLM is skipped on native Windows"),
block.contains("vLLM and AIM are skipped on native Windows"),
"the caveat must survive, now stated only where it is true:\n{block}"
);
assert!(
Expand Down
16 changes: 15 additions & 1 deletion apps/rocm/src/engine_ipc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ fn builtin_engine_request(
envelope.clone(),
)),
"vllm" => Some(rocm_engine_vllm::builtin_handle_envelope(envelope.clone())),
"aim" => Some(rocm_engine_aim::builtin_handle_envelope(envelope.clone())),
_ => None,
}
}
Expand All @@ -342,7 +343,7 @@ pub(crate) fn run_builtin_engine_stdio(engine: &str) -> Result<()> {
}

pub(crate) fn builtin_engine_available(engine: &str) -> bool {
matches!(engine, "lemonade" | "vllm")
matches!(engine, "lemonade" | "vllm" | "aim")
}

#[allow(clippy::too_many_arguments)]
Expand Down Expand Up @@ -388,6 +389,19 @@ pub(crate) fn run_builtin_engine_serve_http(
log_path,
engine_recipe,
),
"aim" => rocm_engine_aim::builtin_serve_http(
service_id,
model_ref,
host,
port,
parsed_policy,
gpu_indices,
runtime_id,
env_id,
state_path,
log_path,
engine_recipe,
),
other => bail!("engine `{other}` is not built into this rocm binary"),
}
}
Expand Down
Loading
Loading