Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
361 changes: 299 additions & 62 deletions crates/rocm-core/src/examine.rs

Large diffs are not rendered by default.

1,301 changes: 1,301 additions & 0 deletions crates/rocm-core/src/examine_gpu_proptests.rs

Large diffs are not rendered by default.

179 changes: 167 additions & 12 deletions crates/rocm-core/src/host_gpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ const fn detect_linux_primary_gpu_name() -> Option<String> {
None
}

fn parse_windows_display_gfx_target(text: &str) -> Option<String> {
pub(crate) fn parse_windows_display_gfx_target(text: &str) -> Option<String> {
let mut name_fallback = None;
let mut unsupported_match = None;
for line in text.lines().map(str::trim).filter(|line| !line.is_empty()) {
Expand All @@ -625,8 +625,17 @@ fn parse_windows_display_gfx_target(text: &str) -> Option<String> {
}
return Some(target.to_owned());
}
if name_fallback.is_none() {
name_fallback = gfx_target_from_amd_marketing_name(name).map(str::to_owned);
if name_fallback.is_none()
&& let Some(target) = gfx_target_from_amd_marketing_name(name)
{
// The same rule for a name: the marketing table names gfx90c by its
// codenames, and an iGPU recognised that way must not hide a
// discrete card further down the list either.
if gfx_target_has_no_rocm_build(target) {
unsupported_match = unsupported_match.or(Some(target));
} else {
name_fallback = Some(target.to_owned());
}
}
}
name_fallback.or_else(|| unsupported_match.map(str::to_owned))
Expand Down Expand Up @@ -683,7 +692,7 @@ fn amd_pci_device_id_from_pnp_id(pnp_id: &str) -> Option<String> {
}
}

fn gfx_target_from_amd_pci_device_id(device_id: &str) -> Option<&'static str> {
pub(crate) fn gfx_target_from_amd_pci_device_id(device_id: &str) -> Option<&'static str> {
match device_id.to_ascii_lowercase().as_str() {
// Navi 21 / 22 / 23 / 24: Radeon RX 6000 desktop and mobile ASICs.
"73a0" | "73a1" | "73a2" | "73a3" | "73a5" | "73a8" | "73a9" | "73ab" | "73ac" | "73ad"
Expand Down Expand Up @@ -720,7 +729,13 @@ fn gfx_target_from_amd_pci_device_id(device_id: &str) -> Option<&'static str> {
}
}

fn gfx_target_from_amd_marketing_name(name: &str) -> Option<&'static str> {
/// The gfx target an AMD marketing name or `lspci` codename names, or `None`
/// when no entry in [`AMD_MARKETING_GFX_TARGETS`] matches.
///
/// This is the crate's one name-to-target table: the install-side display
/// probes and `examine`'s GPU probes both answer from it, so a name resolves to
/// the same target whichever command reads it.
pub(crate) fn gfx_target_from_amd_marketing_name(name: &str) -> Option<&'static str> {
let lower = name.to_ascii_lowercase();
let normalized = normalize_marketing_name_for_match(&lower);
for entry in AMD_MARKETING_GFX_TARGETS {
Expand All @@ -732,12 +747,12 @@ fn gfx_target_from_amd_marketing_name(name: &str) -> Option<&'static str> {
}

#[derive(Debug, Clone, Copy)]
struct AmdMarketingGfxTarget {
pattern: &'static str,
gfx_target: &'static str,
pub(crate) struct AmdMarketingGfxTarget {
pub(crate) pattern: &'static str,
pub(crate) gfx_target: &'static str,
}

const AMD_MARKETING_GFX_TARGETS: &[AmdMarketingGfxTarget] = &[
pub(crate) const AMD_MARKETING_GFX_TARGETS: &[AmdMarketingGfxTarget] = &[
// RDNA4 discrete.
AmdMarketingGfxTarget {
pattern: "ai pro r9700",
Expand Down Expand Up @@ -886,6 +901,17 @@ const AMD_MARKETING_GFX_TARGETS: &[AmdMarketingGfxTarget] = &[
pattern: "8040s",
gfx_target: "gfx1151",
},
// The codename and the product line, as `lspci` and `rocminfo` print them.
// There is no "8045S": the three Strix Halo SKU names are 8060S, 8050S and
// 8040S (the Ryzen AI Max PRO 380's), so this table never carried one.
AmdMarketingGfxTarget {
pattern: "strix halo",
gfx_target: "gfx1151",
},
AmdMarketingGfxTarget {
pattern: "ryzen ai max",
gfx_target: "gfx1151",
},
// RDNA3.5 APUs.
AmdMarketingGfxTarget {
pattern: "890m",
Expand All @@ -895,6 +921,10 @@ const AMD_MARKETING_GFX_TARGETS: &[AmdMarketingGfxTarget] = &[
pattern: "880m",
gfx_target: "gfx1150",
},
AmdMarketingGfxTarget {
pattern: "strix point",
gfx_target: "gfx1150",
},
AmdMarketingGfxTarget {
pattern: "860m",
gfx_target: "gfx1152",
Expand All @@ -903,6 +933,13 @@ const AMD_MARKETING_GFX_TARGETS: &[AmdMarketingGfxTarget] = &[
pattern: "840m",
gfx_target: "gfx1152",
},
// Krackan Point is gfx1152, as its device id (`1114`) and the 860M/840M it
// sells as both say. gfx1150 and gfx1152 are separate TheRock families, so
// calling it gfx1150 selects the wrong runtime build.
AmdMarketingGfxTarget {
pattern: "krackan",
gfx_target: "gfx1152",
},
AmdMarketingGfxTarget {
pattern: "820m",
gfx_target: "gfx1153",
Expand All @@ -920,6 +957,32 @@ const AMD_MARKETING_GFX_TARGETS: &[AmdMarketingGfxTarget] = &[
pattern: "740m",
gfx_target: "gfx1103",
},
// `lspci` prints the Phoenix codename with a die number (`Phoenix1`,
// `Phoenix3`), which is a different token from `phoenix`.
AmdMarketingGfxTarget {
pattern: "phoenix",
gfx_target: "gfx1103",
},
AmdMarketingGfxTarget {
pattern: "phoenix1",
gfx_target: "gfx1103",
},
AmdMarketingGfxTarget {
pattern: "phoenix2",
gfx_target: "gfx1103",
},
AmdMarketingGfxTarget {
pattern: "phoenix3",
gfx_target: "gfx1103",
},
AmdMarketingGfxTarget {
pattern: "phoenix4",
gfx_target: "gfx1103",
},
AmdMarketingGfxTarget {
pattern: "hawk point",
gfx_target: "gfx1103",
},
// RDNA2 APUs.
AmdMarketingGfxTarget {
pattern: "680m",
Expand All @@ -929,10 +992,26 @@ const AMD_MARKETING_GFX_TARGETS: &[AmdMarketingGfxTarget] = &[
pattern: "660m",
gfx_target: "gfx1035",
},
AmdMarketingGfxTarget {
pattern: "rembrandt",
gfx_target: "gfx1035",
},
// Mendocino (Ryzen 7020 series) sells as a Radeon 610M too, which the row
// below answers as gfx1036; only the codename tells the two apart, so it is
// listed ahead of that row for `lspci`'s sake. A Windows Mendocino, named
// only "Radeon(TM) 610M", still reads as gfx1036 by name.
AmdMarketingGfxTarget {
pattern: "mendocino",
gfx_target: "gfx1037",
},
AmdMarketingGfxTarget {
pattern: "610m",
gfx_target: "gfx1036",
},
AmdMarketingGfxTarget {
pattern: "raphael",
gfx_target: "gfx1036",
},
AmdMarketingGfxTarget {
pattern: "steam deck",
gfx_target: "gfx1033",
Expand All @@ -941,6 +1020,37 @@ const AMD_MARKETING_GFX_TARGETS: &[AmdMarketingGfxTarget] = &[
pattern: "van gogh",
gfx_target: "gfx1033",
},
// `lspci` spells it as one word, and the Steam Deck's own display name is
// the part number rather than any Radeon model.
AmdMarketingGfxTarget {
pattern: "vangogh",
gfx_target: "gfx1033",
},
AmdMarketingGfxTarget {
pattern: "custom gpu 0405",
gfx_target: "gfx1033",
},
// Vega iGPUs in Ryzen 4000/5000/7030 parts. They sell as a bare "AMD
// Radeon Graphics", so the codename `lspci` prints is the only name that
// identifies them. gfx90c has no ROCm build (`gfx_target_has_no_rocm_build`),
// which is why the install-side probes rank a name answering it below every
// other recognised adapter.
AmdMarketingGfxTarget {
pattern: "renoir",
gfx_target: "gfx90c",
},
AmdMarketingGfxTarget {
pattern: "cezanne",
gfx_target: "gfx90c",
},
AmdMarketingGfxTarget {
pattern: "lucienne",
gfx_target: "gfx90c",
},
AmdMarketingGfxTarget {
pattern: "barcelo",
gfx_target: "gfx90c",
},
];

fn normalize_marketing_name_for_match(value: &str) -> String {
Expand Down Expand Up @@ -1715,9 +1825,10 @@ impl WindowsExamineInventory {
.iter()
.find(|display| pci_target(display).is_some_and(|t| !gfx_target_has_no_rocm_build(t)))
.or_else(|| {
self.displays
.iter()
.find(|display| gfx_target_from_amd_marketing_name(&display.name).is_some())
self.displays.iter().find(|display| {
gfx_target_from_amd_marketing_name(&display.name)
.is_some_and(|t| !gfx_target_has_no_rocm_build(t))
})
})
// A recognized-but-unsupported iGPU (gfx90c) ranks below every
// other recognized adapter, so it cannot hide a discrete card.
Expand Down Expand Up @@ -3607,6 +3718,50 @@ mod tests {
);
}

/// A gfx90c iGPU recognised by its *name* must not hide a discrete card
/// either.
///
/// The name table now answers gfx90c for the Vega iGPUs' codenames, which
/// is what `examine` needs; on the install side that answer has to rank
/// where the PCI-id answer for the same iGPU already does, below every
/// other recognised adapter, or the first adapter's name would decide the
/// install family for the whole host.
#[test]
fn windows_display_parser_does_not_let_a_named_vega_igpu_hide_a_discrete_card() {
let text = "AMD Radeon(TM) Graphics (Renoir)\t\n\
AMD Radeon RX 6800M\tPCI\\VEN_1002&DEV_9999";
assert_eq!(
parse_windows_display_gfx_target(text),
Some("gfx1031".to_owned())
);
let text = "AMD Radeon(TM) Graphics (Renoir)\t";
assert_eq!(
parse_windows_display_gfx_target(text),
Some("gfx90c".to_owned()),
"alone, the iGPU is still reported as gfx90c"
);

let inventory = WindowsExamineInventory {
displays: vec![
WindowsDisplayAdapter {
name: "AMD Radeon(TM) Graphics (Renoir)".to_owned(),
driver_version: None,
pnp_device_id: None,
},
WindowsDisplayAdapter {
name: "AMD Radeon RX 6800M".to_owned(),
driver_version: None,
pnp_device_id: None,
},
],
..WindowsExamineInventory::default()
};
assert_eq!(
inventory.amd_display_name().as_deref(),
Some("AMD Radeon RX 6800M")
);
}

#[test]
fn known_therock_families_all_round_trip() {
for family in known_therock_families() {
Expand Down
23 changes: 23 additions & 0 deletions docs/manual-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,29 @@ fallback paired an architecture line with the wrong GPU. On that machine
`rocm diagnose` must **not** report `info-20-gpu-no-rocm-support` at all — the
discrete card does have ROCm support, so the whole-machine claim is false.

### 2b. Integrated or discrete

On a machine with an AMD integrated GPU, check what `rocm examine --json`
reports for it under `gpus`:

```bash
rocm examine --framework skip --json
```

Expected result:

- Each AMD GPU carries a `gfx_target`, and an integrated one carries
`is_apu: true`. On a Ryzen 7000 or later desktop the iGPU is `gfx1036`; on a
Ryzen 6000 laptop `gfx1035`; on a Krackan Point laptop (Radeon 860M or 840M)
`gfx1152`, never `gfx1150`; on a Ryzen 4000 or 5000 machine `gfx90c`.
- A discrete card beside it carries `is_apu: false`, and `has_apu` and
`has_discrete_amd` are both `true`. An iGPU reported with `is_apu: false`, or
an iGPU-only machine with `has_discrete_amd: true`, is a regression unless
the GPU's `gfx_target` is empty (an unidentified row is reported as discrete
on purpose).
- On a machine whose only GPU is the iGPU, `rocm serve` does not warn about low
VRAM.

## 3. Lemonade GPU Verification

Lemonade is the default local assistant/server engine. Serve a small assistant
Expand Down
31 changes: 31 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -1845,6 +1845,37 @@ for them, including for the fallback; their parsing is covered by the unit
tests above, and the fallback itself by `hardware_root`'s own unit tests,
since both now read through the same helper with an identity resolver.

## Integrated Or Discrete GPU

`rocm examine` reports each AMD GPU's `gfx_target` and `is_apu`, and two things
a user sees follow from `is_apu`: `rocm serve` withholds its low-VRAM warning
on a single-APU host, whose "VRAM" is a BIOS carve-out of system RAM, and
`rocm diagnose` raises `fix-9-igpu-dgpu` only when a host has both an
integrated and a discrete AMD GPU.

`examine-29` in [examine.feature](../tests/e2e-cucumber/features/examine.feature)
covers the PCI scan end to end on every Linux lane, GPU or not. It puts a
stand-in `lspci` and an empty `rocminfo` on `PATH` and points the simulated host
root at an empty tree, so the five planted rows are the only GPUs: one each
classified by marketing name, by device id alone (the `Device` row `lspci`
prints when `pci.ids` has no name), by both, and by codename.

```bash
RUSTC_WRAPPER= cargo xtask e2e -- -n 'examine-29 '
```

What `rocminfo` and `hipInfo` add is covered one level down, in
[examine_gpu_proptests.rs](../crates/rocm-core/src/examine_gpu_proptests.rs):
a target nothing classifies must leave an earlier verdict standing
(`gfx_apu_verdict` answers `None` for it). That rule cannot be reached end to
end today, because a real `rocminfo` prints ISA `Name:` lines that make the
parser drop every agent (#393). The property harness asserts floors on how much
of each corpus its generators reach, and prints the measured rates:

```bash
cargo test -p rocm-core --lib -- examine::gpu_proptests --nocapture
```

## Windows Runtime Preflight

On Windows, `rocm diagnose` compares the Visual C++ runtime in `System32`
Expand Down
23 changes: 23 additions & 0 deletions tests/e2e-cucumber/features/examine.feature
Original file line number Diff line number Diff line change
Expand Up @@ -428,3 +428,26 @@ Feature: GPU detection and system inspection
Given a GPU machine whose release file cannot be read
When the user inspects that machine in machine-readable form
Then the inspection's verdict is degraded, and its only failure says the release file could not be read

# Whether a GPU is integrated decides two things a user sees: `rocm serve`
# withholds its low-VRAM warning on an APU (whose "VRAM" is a BIOS carve-out
# of system RAM), and `rocm diagnose` raises `fix-9-igpu-dgpu` only on a host
# with both an integrated and a discrete AMD GPU. Every AMD GPU on this bus
# used to be misreported: the PCI scan ignored the `[1002:xxxx]` device id,
# so a GPU `pci.ids` cannot name ("Device") got no architecture; `examine`
# kept its own name table, which called Krackan Point gfx1150 (it is gfx1152)
# and knew the Renoir and Mendocino codenames only as "some APU"; and gfx1037
# was not classified as integrated at all.
#
# The host is simulated, so this runs on every Linux lane, GPU or not. A
# stand-in `lspci` on PATH lists the five GPUs as `lspci -nn -D` prints them,
# a stand-in `rocminfo` lists no agents, and the simulated host root has no
# kernel topology, so the PCI scan alone names every card. (What `rocminfo`
# adds is covered one level down: on a real host its agents are dropped by
# ROCm/rocm-cli#393, so the folds it feeds cannot be reached from here.)
@id:examine-tells-integrated-gpus-from-discrete-ones @requires-os:linux
Scenario: examine-29 - The inspection tells integrated GPUs from discrete ones by the part they are
Given a machine whose PCI bus lists a Radeon RX 7900 XTX and four integrated AMD GPUs
When the user inspects that machine in machine-readable form
Then the inspection names each GPU's architecture and reports only the RX 7900 XTX as discrete
And the inspection reports the machine as having both an integrated and a discrete AMD GPU
Loading
Loading