diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index b2263063..6188e13c 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -77,7 +77,12 @@ `python3 scripts/nvx.py test-microvm --backend kvm --scenario NAME`, passing only the `--processors` counts the change needs, and `aci_edge_sandboxes` backend changes with `python3 scripts/nvx.py test-aci-edge-sandboxes --backend kvm`. - MSHV and WHP are never available there. + MSHV and WHP are never available there. The setup summary names the runner's + CPU. Built-in CPU profiles serve the runner CPUs met so far, the AMD EPYC + 7763, 9V74, and 9V45 and the Intel Xeon 8573C, among the generations that + `doc/usage.md` lists. On a CPU that no built-in profile serves, such as the + Xeon 6973P-C (Granite Rapids), every microVM cold boot fails with + `E_PROFILE_HOST_UNKNOWN`, so leave the microVM scenarios to CI there. ## Pull Requests And CI diff --git a/doc/benchmarks.md b/doc/benchmarks.md index f912150a..f4408678 100644 --- a/doc/benchmarks.md +++ b/doc/benchmarks.md @@ -47,9 +47,10 @@ coordinator fills the `.sh.in` templates before use. The series names carry no CPU vendor: every CI microVM runner is an Azure virtual machine with an Intel Xeon CPU (Ice Lake-SP or Emerald Rapids), so each series has one vendor's samples. No CI microVM runner has an AMD CPU, so no AMD series exists yet. An AMD runner needs a built-in AMD -[CPU profile](usage.md#cpu-profiles), because CI never uses host profiles, and only Milan CPUs have -one so far, `amd.milan.v1`. Such a runner boots its guests on that profile through AMD-V, so its -timings would skew an Intel series' baseline. Before an AMD runner joins CI, give it series of its +[CPU profile](usage.md#cpu-profiles), because CI never uses host profiles, and only Milan, Genoa, +and Turin CPUs have one so far. Such a runner boots its guests on that profile through AMD-V, so its +timings would skew an Intel series' baseline. [#410](https://github.com/microsoft/nvx/issues/410) +tracks AMD runners. Before an AMD runner joins CI, give it series of its own, named with an `-amd` suffix such as `linux-kvm-virtual-machine-amd`, in `PLATFORM_NAMES` and `OPENVMM_BACKENDS` in `scripts/nvx_tools/performance.py` and in the CI matrix, so their history lives in separate `data/` files. Local measurements on AMD hosts can use the names above, because diff --git a/doc/ci.md b/doc/ci.md index bffd376f..6ef8b491 100644 --- a/doc/ci.md +++ b/doc/ci.md @@ -337,7 +337,7 @@ microcode and the invariant-TSC flags, may read `unknown`. | Check | Implementation | | --- | --- | | H1 | `/dev/kvm` or `/dev/mshv` is readable and writable, and a KVM host has no `/dev/mshv`; on Windows, `WHvGetCapability` reports a hypervisor | -| H2 | Vendor, family, model, stepping, microcode, and OS build from `/proc/cpuinfo` or the Windows registry. Then `openvmm --hypervisor --cpu-fingerprint ` writes the host's CPU fingerprint (by default `nvx-cpu-fingerprint-.json` in the probe directory; `--cpu-fingerprint` overrides it), checks it against the profile that `auto` selects from OpenVMM's catalog, which shares one profile per generation across backends, and prints one `NVX-CPU-PROFILE:` line. H2 reports the generation and the profile from that line, so a profile that an OpenVMM pin adds qualifies its hosts without an NVX change. H2 requires exit status 0, `status=pass`, the same backend, and a catalog profile of the reported generation, reports the profile and surface digests, and otherwise fails with OpenVMM's code, for example `[E_PROFILE_HOST_UNKNOWN]` on a CPU that no profile serves or `[E_PROFILE_UNSUPPORTED]` naming every unsupported CPUID bit. On WHP, OpenVMM checks the entries outside the profile on a probe partition configured from the profile, as a cold boot does. With `--no-openvmm`, H2 maps the host with NVX's copy of the catalog instead: `skylake-sp` (6/85, steppings 0 to 4, `intel.skylake-sp.v1`), `icelake-sp` (6/106, `intel.icelake-sp.v1`), `emeraldrapids` (6/207, `intel.emeraldrapids.v1`), `alderlake` (6/151 and 6/154, `intel.alderlake.v1`), or `milan` (AMD 25/1, `amd.milan.v1`). Any other CPU, including Cascade Lake and Cooper Lake, fails with `E_PROFILE_HOST_UNKNOWN`. A unit test keeps the copy equal to the profiles of the OpenVMM submodule's pinned revision, which it reads from the gitlink's commit, so the CI jobs validate the NVX CLI after they check out OpenVMM. The host OS's invariant-TSC flags (`constant_tsc nonstop_tsc`, or the CPUID bit on Windows) are recorded as evidence and never fail the check, because they don't decide what a guest observes: Azure WHP hosts show the CPUID bit but cannot offer invariant TSC to partitions, and their guests measure tens of nanoseconds of skew | +| H2 | Vendor, family, model, stepping, microcode, and OS build from `/proc/cpuinfo` or the Windows registry. Then `openvmm --hypervisor --cpu-fingerprint ` writes the host's CPU fingerprint (by default `nvx-cpu-fingerprint-.json` in the probe directory; `--cpu-fingerprint` overrides it), checks it against the profile that `auto` selects from OpenVMM's catalog, which shares one profile per generation across backends, and prints one `NVX-CPU-PROFILE:` line. H2 reports the generation and the profile from that line, so a profile that an OpenVMM pin adds qualifies its hosts without an NVX change. H2 requires exit status 0, `status=pass`, the same backend, and a catalog profile of the reported generation, reports the profile and surface digests, and otherwise fails with OpenVMM's code, for example `[E_PROFILE_HOST_UNKNOWN]` on a CPU that no profile serves or `[E_PROFILE_UNSUPPORTED]` naming every unsupported CPUID bit. On MSHV and WHP, OpenVMM checks the entries outside the profile on a probe partition configured from the profile, as a cold boot does. With `--no-openvmm`, H2 maps the host with NVX's copy of the catalog instead: `skylake-sp` (6/85, steppings 0 to 4, `intel.skylake-sp.v1`), `icelake-sp` (6/106, `intel.icelake-sp.v1`), `emeraldrapids` (6/207, `intel.emeraldrapids.v1`), `alderlake` (6/151 and 6/154, `intel.alderlake.v1`), `milan` (AMD 25/1, `amd.milan.v1`), `genoa` (AMD 25/17, `amd.genoa.v1`), or `turin` (AMD 26/2, `amd.turin.v1`). Any other CPU, including Cascade Lake and Cooper Lake, fails with `E_PROFILE_HOST_UNKNOWN`. A unit test keeps the copy equal to the profiles of the OpenVMM submodule's pinned revision, which it reads from the gitlink's commit, so the CI jobs validate the NVX CLI after they check out OpenVMM. The host OS's invariant-TSC flags (`constant_tsc nonstop_tsc`, or the CPUID bit on Windows) are recorded as evidence and never fail the check, because they don't decide what a guest observes: Azure WHP hosts show the CPUID bit but cannot offer invariant TSC to partitions, and their guests measure tens of nanoseconds of skew | | H3 | `openvmm --x-time-abi-verify` builds the partition and runs the time ABI preflight without running the guest. Its `NVX-TIME-ABI-VERIFY:` line must report `status=ok` for the backend, plausible declared and native TSC rates, the backend's LAPIC rate, and a `cpu_profile` that is a catalog profile, `..v` of any generation but a host profile's `host`, and, when H2 runs too, a revision of the profile H2 names. A failed preflight reports OpenVMM's code, for example `[E_TSC_SYNC_UNSUPPORTED]` | | H4 | Samples of the TSC against the host's monotonic clocks, with sleeps between them so the host's CPUs idle: 13 samples 10 s apart, or 3 samples 1 s apart with `--ci-schedule`. Each sample reads the TSC between two reads of a clock, keeping the tightest of 64 brackets; its uncertainty is half the bracket plus half the clock's resolution. A clock that returns the same value to consecutive reads is coarser than one read, so the probe takes its smallest step as its resolution: Hyper-V's reference TSC page advances the Linux clocks in 100 ns steps although `clock_getres` reports 1 ns. The interval stability is judged against a clock that time synchronization never steers, `CLOCK_MONOTONIC_RAW` on Linux and `QueryPerformanceCounter` on Windows: every interval between consecutive samples must be conclusive within 0.25 ppm, and the interval rates must agree within 1 ppm. chrony's frequency updates move `CLOCK_MONOTONIC`'s rate by up to several ppm between seconds on the Azure runners, which says nothing about the TSC. The rate over the whole window is measured against the disciplined clock, `CLOCK_MONOTONIC` on Linux, and must lie within 100 ppm of the rate H3 reports when H3 runs in the same invocation. A Linux host's clocksource is recorded as evidence | | H5 | Pinned-thread ping-pong rounds over every pair of host CPUs; `max_abs_offset_ns` is at most 1,000, the measurement is conclusive, and no pair stalls | diff --git a/doc/design/time-abi.md b/doc/design/time-abi.md index e66f69f4..f82673bd 100644 --- a/doc/design/time-abi.md +++ b/doc/design/time-abi.md @@ -298,8 +298,8 @@ settled cell on the registered hosts. | Obligation | KVM | MSHV | WHP | | --- | --- | --- | --- | | Identity CPUID (exact leaves, out-of-range rule) | `KVM_SET_CPUID2` with the identity and explicit zero leaves; every KVM `0x4xxxxxxx` entry removed. Other leaves in the range return KVM's out-of-range result: Intel's under an Intel profile, and zero under an AMD one, as KVM answers a guest whose CPUID vendor is AMD | No synthetic processor features, so the hypervisor reports no `0x400000xx` leaves; CPUID intercept results (`always_override`) for the identity leaves, while the explicit zero leaves already read zero in VP 0's view and get none (see the next row); preflight reads every entry back and requires VP 0 to read zero at six sentinel leaves (`0x40000006`, `0x40000081`, `0x400000ff`, `0x40000100`, `0x40000200`, and `0x4000ff00`) | CPUID exits for the six identity leaves, served from OpenVMM's table; with synthetic features off, WHP returns zero natively for `0x40000006..=0x400000ff` and the bases from `0x40000100`, which preflight asserts | -| Profile CPUID and time bits | `KVM_SET_CPUID2` with the effective CPUID. KVM 6.3 and newer hide invariant TSC while KVM's own `HV_X64_MSR_TSC_INVARIANT_CONTROL` is 0, so the backend mirrors the guest-visible value, which core saves with the VM, into it with a host-initiated `KVM_SET_MSRS` after every accepted guest write and before a VP runs after a restore or reset; without it the guest loses `constant_tsc` and `nonstop_tsc` and boots about 160 ms slower | Processor feature banks derived from the profile (`hv_banks`), plus a CPUID intercept result (`always_override`, subleaf-specific where the entry has a subleaf) for every partition-wide entry of the effective CPUID that VP 0's own view does not already present under its mask. Once VP 0 exists, one bulk `HvCallGetVpCpuidValues` reads every partition-wide entry before any result is registered, and a failed bulk read registers every entry. That leaves 11 of 61 entries on the bare-metal host (the hypervisor bit, one leaf-2 descriptor byte, ARAT, the six identity leaves, and two brand leaves) and 12 of 65 on the nested Azure host (the same plus invariant TSC), at every vCPU count, because VP 0's own view already presents leaf 4's shared L3; registering them, the read included, takes 63 to 71 µs and about 90 µs, against 200 and about 300 µs for every entry. Profile masks never cover runtime-owned or VM-owned bits, so a pinned value that VP 0 presents at reset it presents always. Reserved entries pass through from the hypervisor's guest view, which reads zero at every one with no result registered, and verification requires every candidate to read zero (`E_CPU_UNLISTED`). Per-VP fields cannot be left to the hypervisor: it does not implement `0xB` for these partitions and reads `EDX` as 0 on every VP, so Linux would log `APIC ID mismatch` (`G_APIC_ID_MISMATCH`). The `0xB` and `0x1F` entries are therefore per-VP intercept results with the VP's x2APIC ID in `EDX`, registered as each VP is created: the hypervisor refuses a per-VP result for a VP that does not exist yet (`InvalidVpIndex`). Under an AMD profile, so is `0x8000001E`, with the VP's extended APIC, compute unit, and node IDs (no AMD MSHV host has run it yet). Leaf 1's initial APIC ID is the hypervisor's own, which is correct | Processor feature banks derived from the profile (`hv_banks`), and every effective-CPUID entry outside the hypervisor range as a `CpuidResultList2` result (the banks cannot express the hypervisor bit, ARAT, or, on Azure, invariant TSC); reserved entries pass through from WHP's guest view, which verification requires to be zero (`E_CPU_UNLISTED`). CPUID exits only for the identity leaves and the leaves with per-VP APIC fields (`1`, `0xB`, and, where listed, `0x1F` and `0x8000001E`), 8 exits for every Intel v1 profile and 9 for `amd.milan.v1`: each exit-list entry adds about 25 µs to partition setup, and the legacy 269-entry list cost about 7 ms per restore. The exit handler presents the effective CPUID's `0xB` terminator, with the VP's x2APIC ID, instead of zeroing subleaves from 2 | -| Identity MSRs routed to OpenVMM | `KVM_CAP_X86_USER_SPACE_MSR` (`UNKNOWN`, `FILTER`) and `KVM_X86_SET_MSR_FILTER` denying reads and writes of `0x40000000..=0x400001ff`, and under an AMD profile of `HWCR` and `DE_CFG` (see [MSRs](#msrs)); the filter takes precedence over KVM's in-kernel Hyper-V MSRs. Under an Intel profile, a Linux boot takes four MSR exits, all on the BSP, at any vCPU count; under an AMD profile, it also reads `HWCR` once, on the BSP, and `DE_CFG` once on each vCPU, so it takes 4 + 1 + N exits on N vCPUs (counted on WHP; no AMD KVM host has run it) | MSR-index intercepts (`HV_INTERCEPT_TYPE_X64_MSR_INDEX`, `READ_WRITE`) for `0x40000002`, `0x40000022`, `0x40000023`, and `0x40000118`, and under an AMD profile for `HWCR` and `DE_CFG`. The hypervisor itself raises #GP for writes to the three read-only MSRs and for every other MSR in the range. Native synthetic MSRs are forbidden: they pre-empt the intercepts, and the native `HV_X64_MSR_TSC_FREQUENCY` returns the destination's rate instead of `F`. Verified on hypervisor builds 26100.30000 (bare metal) and 26100.9444 (Azure) | `X64MsrExitBitmap` with `UnhandledMsrs` (capability `0x3f` on every host) and the offloaded APIC, no synthetic features and no `hv1_emulator`: the identity MSRs exit to OpenVMM, which raises #GP for every other MSR in the range; `HWCR` and `DE_CFG` exit too, and OpenVMM serves them under an AMD profile | +| Profile CPUID and time bits | `KVM_SET_CPUID2` with the effective CPUID. KVM 6.3 and newer hide invariant TSC while KVM's own `HV_X64_MSR_TSC_INVARIANT_CONTROL` is 0, so the backend mirrors the guest-visible value, which core saves with the VM, into it with a host-initiated `KVM_SET_MSRS` after every accepted guest write and before a VP runs after a restore or reset; without it the guest loses `constant_tsc` and `nonstop_tsc` and boots about 160 ms slower | Processor feature banks derived from the profile (`hv_banks`), plus a CPUID intercept result (`always_override`, subleaf-specific where the entry has a subleaf) for every partition-wide entry of the effective CPUID that VP 0's own view does not already present under its mask. Once VP 0 exists, one bulk `HvCallGetVpCpuidValues` reads every partition-wide entry before any result is registered, and a failed bulk read registers every entry. That leaves 11 of 61 entries on the bare-metal host (the hypervisor bit, one leaf-2 descriptor byte, ARAT, the six identity leaves, and two brand leaves) and 12 of 65 on the nested Azure host (the same plus invariant TSC), at every vCPU count, because VP 0's own view already presents leaf 4's shared L3; registering them, the read included, takes 63 to 71 µs and about 90 µs, against 200 and about 300 µs for every entry. Profile masks never cover runtime-owned or VM-owned bits, so a pinned value that VP 0 presents at reset it presents always. Reserved entries pass through from the hypervisor's guest view, which reads zero at every one with no result registered, and verification requires every candidate to read zero (`E_CPU_UNLISTED`). Per-VP fields cannot be left to the hypervisor: it does not implement `0xB` for these partitions and reads `EDX` as 0 on every VP, so Linux would log `APIC ID mismatch` (`G_APIC_ID_MISMATCH`). The `0xB` and `0x1F` entries are therefore per-VP intercept results with the VP's x2APIC ID in `EDX`, registered as each VP is created: the hypervisor refuses a per-VP result for a VP that does not exist yet (`InvalidVpIndex`). Under an AMD profile, so is `0x8000001E`, with the VP's extended APIC, compute unit, and node IDs (no AMD MSHV host has run it yet). Leaf 1's initial APIC ID is the hypervisor's own, which is correct | Processor feature banks derived from the profile (`hv_banks`), and every effective-CPUID entry outside the hypervisor range as a `CpuidResultList2` result (the banks cannot express the hypervisor bit, ARAT, or, on Azure, invariant TSC); reserved entries pass through from WHP's guest view, which verification requires to be zero (`E_CPU_UNLISTED`). CPUID exits only for the identity leaves and the leaves with per-VP APIC fields (`1`, `0xB`, and, where listed, `0x1F` and `0x8000001E`), 8 exits for every Intel v1 profile and 9 for every AMD v1 profile: each exit-list entry adds about 25 µs to partition setup, and the legacy 269-entry list cost about 7 ms per restore. The exit handler presents the effective CPUID's `0xB` terminator, with the VP's x2APIC ID, instead of zeroing subleaves from 2 | +| Identity MSRs routed to OpenVMM | `KVM_CAP_X86_USER_SPACE_MSR` (`UNKNOWN`, `FILTER`) and `KVM_X86_SET_MSR_FILTER` denying reads and writes of `0x40000000..=0x400001ff`, and under an AMD profile of `HWCR` and `DE_CFG` (see [MSRs](#msrs)); the filter takes precedence over KVM's in-kernel Hyper-V MSRs. Under an Intel profile, a Linux boot takes four MSR exits, all on the BSP, at any vCPU count; under an AMD profile, it also reads `HWCR` once, on the BSP, and `DE_CFG` once on each vCPU, so it takes 4 + 1 + N exits on N vCPUs (counted on WHP; GitHub-hosted runners with EPYC 7763, 9V74, and 9V45 CPUs boot it on KVM) | MSR-index intercepts (`HV_INTERCEPT_TYPE_X64_MSR_INDEX`, `READ_WRITE`) for `0x40000002`, `0x40000022`, `0x40000023`, and `0x40000118`, and under an AMD profile for `HWCR` and `DE_CFG`. The hypervisor itself raises #GP for writes to the three read-only MSRs and for every other MSR in the range. Native synthetic MSRs are forbidden: they pre-empt the intercepts, and the native `HV_X64_MSR_TSC_FREQUENCY` returns the destination's rate instead of `F`. Verified on hypervisor builds 26100.30000 (bare metal) and 26100.9444 (Azure) | `X64MsrExitBitmap` with `UnhandledMsrs` (capability `0x3f` on every host) and the offloaded APIC, no synthetic features and no `hv1_emulator`: the identity MSRs exit to OpenVMM, which raises #GP for every other MSR in the range; `HWCR` and `DE_CFG` exit too, and OpenVMM serves them under an AMD profile | | Native rate `F_d` | `KVM_GET_TSC_KHZ` × 1000 on VP 0 (1 kHz granularity) | `ProcessorClockFrequency` partition property | `WHvCapabilityCodeProcessorClockFrequency` | | No TSC scaling | Never `KVM_SET_TSC_KHZ`; every vCPU reports the host rate | No frequency override | No `ProcessorClockFrequency` partition property; the 1 GHz request is removed | | LAPIC rate `L` | In-kernel LAPIC at 1 GHz; `KVM_CAP_X86_APIC_BUS_CYCLES_NS` never set | The hypervisor's LAPIC at 200 MHz | Offloaded APIC at its fixed 200 MHz, verified at preflight (setting `InterruptClockFrequency` is not supported); the emulated APIC is not used | @@ -386,11 +386,11 @@ document in pretty form. | `id` | `..v`, each component `[a-z0-9-]+`, for example `intel.icelake-sp.v1` | | `description` | Free text | | `vendor` | The 12-byte CPUID vendor string | -| `generation` | The generation `name` (`skylake-sp`, `icelake-sp`, `emeraldrapids`, `alderlake`, or `milan`; `host` for a host profile, see **Host profiles**) and its `cpus`: `(family, model, stepping range)` display signatures; stepping ranges separate model 85's Skylake-SP (0 to 4), Cascade Lake, and Cooper Lake | +| `generation` | The generation `name` (`skylake-sp`, `icelake-sp`, `emeraldrapids`, `alderlake`, `milan`, `genoa`, or `turin`; `host` for a host profile, see **Host profiles**) and its `cpus`: `(family, model, stepping range)` display signatures; stepping ranges separate model 85's Skylake-SP (0 to 4), Cascade Lake, and Cooper Lake | | `cpuid` | A dense table of every leaf and subleaf in `[0, max basic]` and `[0x80000000, max extended]` with a value and a mask per register; mask bit 1 pins the value, mask bit 0 marks a VMM-owned or runtime-owned bit | | `xcr0`, `xss`, `xsave_components` | The XSAVE features the guest may enable, and the size, offset, and flags of every enabled component | | `physical_address_width` | The guest physical address width | -| `msrs` | Pinned MSR values with masks; v1 pins `IA32_ARCH_CAPABILITIES` only, under the mask of the bits that Hyper-V's feature banks derive plus `ITS_NO`. KVM presents it as a feature MSR; MSHV and WHP present it through their banks' `*_NO` bits, because neither has a register to set or read it back | +| `msrs` | Pinned MSR values with masks; v1 pins `IA32_ARCH_CAPABILITIES` only, under the mask of the bits that Hyper-V's feature banks derive plus `ITS_NO`, and AMD profiles pin none, because AMD CPUs have no `IA32_ARCH_CAPABILITIES`. KVM presents it as a feature MSR; MSHV and WHP present it through their banks' `*_NO` bits, because neither has a register to set or read it back | | `provenance` | The derivation method and the source fingerprints' backends, host counts, and surface digests (informative) | VMM-owned bits are a code table, not profile data: `CPUID.1:EBX[31:16]` @@ -513,7 +513,8 @@ Informational fields: `Intel(R) Xeon(R) Processor ()` with the generation's display name (`Skylake-SP`, `Ice Lake-SP`, or `Emerald Rapids`), `Intel(R) Core(TM) Processor (Alder Lake)`, or - `AMD EPYC Processor (Milan)`, zero-padded, without a + `AMD EPYC Processor ()` with `Milan`, `Genoa`, or `Turin`, + zero-padded, without a frequency. Every host of a generation presents it whatever its SKU, and derivation needs no common brand across the source hosts. Host profiles present `Intel(R) Processor (host profile)` or @@ -612,15 +613,17 @@ reports every violation at once, naming the leaf, subleaf, register, and bit: 6. On MSHV and WHP, which pass reserved entries through, the hypervisor presents no non-zero CPUID entry outside the profile's tables, apart from the identity range and the topology leaves (`E_CPU_UNLISTED`). - - `--cpu-fingerprint` checks what a cold boot checks. On WHP, it creates a - second probe partition whose feature banks and XSAVE features derive - from the profile, as a cold boot's partition's do, and reads its VP 0 at - the host's candidates (below). The fingerprint's own probe partition - enables every available feature, so on a CET-capable host, such as a - twelfth-generation Core, it presents CET's XSAVE components `0xD.11` and - `0xD.12`, which no profile enables. On MSHV, the check still reads the - fingerprint's probe partition, at the entries its own enumeration - reaches. + - `--cpu-fingerprint` checks what a cold boot checks. On MSHV and WHP, it + creates a second probe partition whose feature banks and XSAVE + features derive from the profile, as a cold boot's partition's do, and + reads its VP 0 at the host's candidates (below). The fingerprint's own + probe partition enables every available feature, so on a CET-capable + host, such as a twelfth-generation Core on WHP, it presents CET's XSAVE + components `0xD.11` and `0xD.12`, which no profile enables. No + CET-capable MSHV host has run it: the Skylake-SP hosts predate CET, and + Azure withholds CET from the roots of its Emerald Rapids VMs. On MSHV, + the configured partition is created as a microVM's is, without x2APIC + or SMT, and reads zero at the Skylake-SP host's 7 candidates. - At every cold boot and restore, step 5's check covers it on VP 0's view. The candidates are the entries that the host's CPUID enumerates outside the profile's tables (`cpu_profile::unlisted_cpuid_candidates`), @@ -640,16 +643,18 @@ reports every violation at once, naming the leaf, subleaf, register, and bit: - KVM answers reserved entries from the effective CPUID itself, so it needs no check. -The catalog has five profiles, derived from the fingerprints of three -bare-metal hosts (one per backend), sixteen Azure hosts, and one laptop: +The catalog has seven profiles, derived from the fingerprints of three +bare-metal hosts (one per backend), eighteen Azure hosts, and one laptop: | ID | Generation | Hosts | Source backends | | --- | --- | --- | --- | | `intel.skylake-sp.v1` | 6/85, steppings 0 to 4 | The bare-metal hosts | KVM, MSHV, WHP | | `intel.icelake-sp.v1` | 6/106 | Xeon Platinum 8370C runners | KVM, MSHV, WHP | -| `intel.emeraldrapids.v1` | 6/207 | Xeon Platinum 8573C runners | MSHV, WHP (no KVM host exists) | +| `intel.emeraldrapids.v1` | 6/207 | Xeon Platinum 8573C runners | MSHV, WHP (GitHub-hosted 8573C runners support it on KVM) | | `intel.alderlake.v1` | 6/151 and 6/154 | One Core i9-12900H laptop (6/154, stepping 3) | WHP | | `amd.milan.v1` | 25/1 | One Azure Standard_D16as_v5 VM with an EPYC 7763 (25/1, stepping 1) and nested Hyper-V | WHP | +| `amd.genoa.v1` | 25/17 | One GitHub-hosted Actions runner, an Azure VM with an EPYC 9V74 (25/17, stepping 1) | KVM | +| `amd.turin.v1` | 26/2 | One GitHub-hosted Actions runner, an Azure VM with an EPYC 9V45 (26/2, stepping 1) | KVM | The Alder Lake profile serves development hosts, not CI. Derived from one WHP fingerprint, it pins XCR0 `0x7`, because WHP offers that host only x87, @@ -659,19 +664,21 @@ with `E_PROFILE_UNSUPPORTED` until a later revision intersects their fingerprints. The policy needs nothing for the hybrid cores: it clears the hybrid flag, `CPUID.(7,0):EDX[15]`, and keeps no data in leaf `0x1A`. -The Milan profile, the only AMD one, also serves development hosts, not CI; -no CI microVM runner has an AMD CPU. Its generation takes every stepping of -family 25 model 1, Milan-X's 2 included, which differs only in descriptive -fields. -Derived from one nested WHP fingerprint, it presents what that WHP offers -its guests: XCR0 `0x7`, TOPOEXT with AMD's cache and topology leaves, and -CLZERO, but none of the speculation controls of `0x80000008` EBX and -nothing in `0x80000021`, which that WHP does not offer. Linux guests of the -profile therefore mitigate Spectre v2 with retpolines and report SSB, SRSO, -and TSA as vulnerable on every host. The policy's AMD rules keep data in -`0x80000005`, `0x8000001D`, `0x8000001E`, and `0x80000021`, keep AMD's -speculation controls and immunities where every source host offers them, -except what KVM cannot present, and clear 3DNowPrefetch, which the root +The AMD profiles serve development hosts, and the GitHub-hosted runners where +Copilot cloud agent sessions run, not CI; no CI microVM runner has an AMD +CPU. Each generation takes every stepping of its model: family 25 model 1 for +Milan, Milan-X's 2 included, which differs only in descriptive fields; +family 25 model 17 for Genoa, Genoa-X's included; and family 26 model 2 for +Turin. +Derived from one nested WHP fingerprint, the Milan profile presents what +that WHP offers its guests: XCR0 `0x7`, TOPOEXT with AMD's cache and +topology leaves, and CLZERO, but none of the speculation controls of +`0x80000008` EBX and nothing in `0x80000021`, which that WHP does not offer. +Linux guests of the profile therefore mitigate Spectre v2 with retpolines and +report SSB, SRSO, and TSA as vulnerable on every host. The policy's AMD rules +keep data in `0x80000005`, `0x8000001D`, `0x8000001E`, and `0x80000021`, keep +AMD's speculation controls and immunities where every source host offers +them, except what KVM cannot present, and clear 3DNowPrefetch, which the root partition of a nested Azure host does not see although WHP presents it to guests, so that host's cold-boot support check would fail a profile that pinned it; Linux infers `PREFETCHW` from long mode. The WHP fingerprint @@ -681,13 +688,36 @@ RETBLEED, which is all the bit decides; PSFD is a bit of `SPEC_CTRL`, which KVM lets a guest access only beside IBRS, STIBP, or SSBD, and stays only with one of them, as OpenVMM's KVM backend keeps it in KVM's supported surface. A Milan host on KVM therefore fails the profile on -neither bit. Milan hosts on KVM and MSHV, bare-metal ones, and other SKUs -are still unverified, and so are the AMD paths of the KVM and MSHV -backends: the AMD MSR contract and MSHV's per-VP `0x8000001E`. A host that -lacks one of the profile's features fails with `E_PROFILE_UNSUPPORTED`, -which names `--cpu-profile host`, until a later revision intersects their -fingerprints, which could also add the speculation controls, and PSFD -beside them, that bare-metal hosts offer. +neither bit: GitHub's EPYC 7763 runners, Azure VMs whose KVM offers more +than that WHP, pass doctor H1 to H4 and the full default `test-microvm` +suite on it, and the host-profile scenarios on `amd.host.v1`. + +The Genoa and Turin profiles each derive from one KVM fingerprint of a +GitHub-hosted Actions runner, an Azure VM with an EPYC 9V74 or 9V45, and +present what Azure offers that VM's KVM. Genoa's XCR0 is `0x7`, and Turin's +is `0xe7`, with AVX-512, AVX-VNNI, and AVX512-BF16. Both have TOPOEXT, +CLZERO, `LFENCE` serialization in `0x80000021` EAX, and the TSA immunities of +`0x80000021` ECX, which Azure presents. They have none of AMD's speculation +controls, which Azure withholds, and, by the policy, neither PSFD, which +their KVM offers without a `SPEC_CTRL` control, nor `BTC_NO`. KVM also +enumerates Intel's speculation controls and `IA32_ARCH_CAPABILITIES` on AMD +hosts (`CPUID.(7,0):EDX` bits 26, 27, 29, and 31), and emulates the MSR; the +runners' KVM sets bit 29. The policy's AMD rules clear those bits, so no AMD +profile pins `IA32_ARCH_CAPABILITIES`, and a profile derived from KVM alone +pins none of them: no AMD CPU has them, and the Milan host's WHP does not +present them. Every GitHub runner of the two generations that ran them, +four with Genoa and one with Turin, passes doctor H1 to H4 and the full +default `test-microvm` suite on its profile, and the host-profile scenarios. + +Milan hosts on MSHV, Genoa and Turin hosts on MSHV and WHP, bare-metal AMD +hosts, and other SKUs are still unverified, and so are the AMD paths of the +MSHV backend: the AMD MSR contract and MSHV's per-VP `0x8000001E` (#409). +`amd.genoa.v1` fails a Genoa host whose hypervisor does not report the TSA +immunities, such as a bare-metal one whose kernel applies the `VERW` +mitigation. A host that lacks one of a profile's features fails with +`E_PROFILE_UNSUPPORTED`, which names `--cpu-profile host`, until a later +revision intersects their fingerprints, which could also add the speculation +controls, and PSFD beside them, that bare-metal hosts offer. Sharing costs nothing on Ice Lake-SP and Emerald Rapids, where every backend offers the same features. On Skylake-SP, used only by the bare-metal @@ -1903,8 +1933,8 @@ most 2.1 ns. Generation names used in logs, reports, and job summaries are `skylake-sp` (family 6, model 85), `icelake-sp` (6/106), `emeraldrapids` (6/207), -`alderlake` (6/151 and 6/154), and `milan` (25/1); no CI microVM runner has -the last two. +`alderlake` (6/151 and 6/154), `milan` (25/1), `genoa` (25/17), and `turin` +(26/2); no CI microVM runner has the last four. `validate-runner` and `nvx.py doctor` detect the generation at run time and report it, together with the selected profile, `F_d`, `L`, the `H4` rate metrics, and the skew metrics, in their log and the job summary; an unknown diff --git a/doc/setup.md b/doc/setup.md index 74ad75cd..93edd559 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -32,7 +32,8 @@ Python 3.10 or newer and Git are required on every platform. By default, a microVM boots on the built-in [CPU profile](usage.md#cpu-profiles) of its host's CPU, so the host needs a CPU that one of OpenVMM's built-in profiles serves, four Intel generations and -AMD's EPYC Milan, on a hypervisor that supports that profile. On another +AMD's EPYC Milan, Genoa, and Turin, on a hypervisor that supports that +profile. On another Intel or AMD CPU, or where the hypervisor does not support the built-in profile (`E_PROFILE_UNSUPPORTED`, whose message names this option), `nvx.py run --cpu-profile host` opts in to a development profile derived from diff --git a/doc/usage.md b/doc/usage.md index bc213926..74856709 100644 --- a/doc/usage.md +++ b/doc/usage.md @@ -590,19 +590,28 @@ built-in profile of the host's CPU generation: | `intel.emeraldrapids.v1` | `emeraldrapids` | Intel Xeon Scalable, fifth generation (6/207) | | `intel.alderlake.v1` | `alderlake` | Intel Core, twelfth generation (6/151 and 6/154) | | `amd.milan.v1` | `milan` | AMD EPYC, third generation (25/1, Milan-X included) | - -Any other CPU, including Cascade Lake, Sapphire Rapids, Tiger Lake, Raptor -Lake, and AMD's Genoa and Ryzen CPUs, fails with `E_PROFILE_HOST_UNKNOWN`. A -host of a listed generation can still fail with `E_PROFILE_UNSUPPORTED` if its -SKU or hypervisor lacks a feature of the profile, and on an Intel or AMD CPU -OpenVMM's error then names `--cpu-profile host`: `intel.alderlake.v1` derives -from one Core i9-12900H on WHP, and `amd.milan.v1` from one EPYC 7763 Azure -VM on WHP, so Alder Lake and Milan hosts on KVM and MSHV, and SKUs without -their features, are unverified. The Milan profile leaves out what KVM cannot -present, `BTC_NO` and PSFD without a `SPEC_CTRL` control, and presents none -of the speculation controls that the Azure VM's WHP withholds, so its Linux -guests use retpolines and report SSB, SRSO, and TSA as vulnerable on every -host. +| `amd.genoa.v1` | `genoa` | AMD EPYC, fourth generation (25/17, Genoa-X included) | +| `amd.turin.v1` | `turin` | AMD EPYC, fifth generation (26/2) | + +Any other CPU, including Cascade Lake, Sapphire Rapids, Granite Rapids, Tiger +Lake, Raptor Lake, and AMD's Ryzen CPUs, fails with `E_PROFILE_HOST_UNKNOWN`. +A host of a listed generation can still fail with `E_PROFILE_UNSUPPORTED` if +its SKU or hypervisor lacks a feature of the profile, and on an Intel or AMD +CPU OpenVMM's error then names `--cpu-profile host`. `intel.alderlake.v1` +derives from one Core i9-12900H on WHP, `amd.milan.v1` from one EPYC 7763 +Azure VM on WHP, and `amd.genoa.v1` and `amd.turin.v1` from KVM on one +GitHub-hosted Actions runner each, an Azure VM with an EPYC 9V74 or 9V45. +GitHub's runners with an EPYC 7763, 9V74, or 9V45 pass their profiles' full +`test-microvm` suite on KVM. Alder Lake hosts on KVM and MSHV, Milan, Genoa, +and Turin hosts on MSHV, Genoa and Turin hosts on WHP, bare-metal AMD hosts, +and SKUs without the profiles' features are unverified. The AMD profiles leave +out what KVM cannot present, `BTC_NO` and PSFD without a `SPEC_CTRL` control, +and the enumerations of Intel's speculation controls that KVM adds on AMD +hosts. They present none of AMD's speculation controls, `IBRS`, `STIBP`, and +`SSBD`, because the Azure VMs' hypervisors withhold them; `amd.milan.v1`'s +Linux guests therefore use retpolines and report SSB, SRSO, and TSA as +vulnerable on every host. `amd.genoa.v1` pins the TSA immunities that Azure +presents on Genoa, which a bare-metal Genoa host's KVM does not report. On an Intel or AMD development host that no built-in profile serves, or whose hypervisor does not support its built-in profile, `run --cpu-profile host` @@ -622,11 +631,12 @@ the time ABI requires. A host profile is for development only: - `doctor` never qualifies it, so benchmark and CI hosts need a built-in profile. -[#390](https://github.com/microsoft/nvx/issues/390) tracks built-in profiles -for more CPUs, and [#396](https://github.com/microsoft/nvx/issues/396) for -more AMD CPUs; host profiles serve no CPU of another vendor than Intel and -AMD. A profile derives from fingerprints of its generation's hosts on every -backend that it serves; see `vmm_core/cpu_profile` in the OpenVMM submodule. +[#408](https://github.com/microsoft/nvx/issues/408) tracks built-in profiles +for more Intel CPUs, such as Tiger Lake and Meteor Lake, and +[#409](https://github.com/microsoft/nvx/issues/409) for more AMD CPUs; host +profiles serve no CPU of another vendor than Intel and AMD. A profile derives +from fingerprints of its generation's hosts on every backend that it serves; +see `vmm_core/cpu_profile` in the OpenVMM submodule. ## Benchmarking diff --git a/openvmm b/openvmm index 07850d27..3951c841 160000 --- a/openvmm +++ b/openvmm @@ -1 +1 @@ -Subproject commit 07850d27af783ea8765111741763cb6d326228df +Subproject commit 3951c84158a9b65a2540002682332addd94e42e1 diff --git a/scripts/nvx_tools/time_abi.py b/scripts/nvx_tools/time_abi.py index 12e65bf4..366257fc 100644 --- a/scripts/nvx_tools/time_abi.py +++ b/scripts/nvx_tools/time_abi.py @@ -123,14 +123,17 @@ def describe(self) -> str: # the submodule. Doctor uses it only without OpenVMM (--no-openvmm), and run # to explain a failed cold boot on a CPU that it lacks; otherwise OpenVMM # reports the generation and the profile itself. Model 85 also covers Cascade -# Lake (steppings 5-7) and Cooper Lake (10-11), which have no profile; AMD's -# family 25 model 1 is Milan's in every stepping, Milan-X's 2 included. +# Lake (steppings 5-7) and Cooper Lake (10-11), which have no profile. AMD's +# family 25 model 1 is Milan's in every stepping, Milan-X's 2 included, family +# 25 model 17 Genoa's, Genoa-X's included, and family 26 model 2 Turin's. CPU_GENERATIONS: tuple[CpuGeneration, ...] = ( CpuGeneration("skylake-sp", "GenuineIntel", (CpuModel(6, 85, range(5)),)), CpuGeneration("icelake-sp", "GenuineIntel", (CpuModel(6, 106),)), CpuGeneration("emeraldrapids", "GenuineIntel", (CpuModel(6, 207),)), CpuGeneration("alderlake", "GenuineIntel", (CpuModel(6, 151), CpuModel(6, 154))), CpuGeneration("milan", "AuthenticAMD", (CpuModel(25, 1),)), + CpuGeneration("genoa", "AuthenticAMD", (CpuModel(25, 17),)), + CpuGeneration("turin", "AuthenticAMD", (CpuModel(26, 2),)), ) @@ -146,9 +149,13 @@ def describe_cpu_generations() -> str: # The CPU vendors whose CPUs host profiles serve, as OpenVMM's # `cpu_profile::supports_host_profiles` decides. HOST_PROFILE_VENDORS = frozenset({"GenuineIntel", "AuthenticAMD"}) -# The issues that track CPU profiles for more CPUs, and for more AMD CPUs. -CPU_SUPPORT_ISSUE = "https://github.com/microsoft/nvx/issues/390" -AMD_SUPPORT_ISSUE = "https://github.com/microsoft/nvx/issues/396" +# The issues that track built-in CPU profiles for more Intel and more AMD CPUs. +# No issue tracks the CPUs of other vendors, which the time ABI does not serve +# (doc/design/time-abi.md, "Non-goals"). +CPU_SUPPORT_ISSUES: Mapping[str, tuple[str, str]] = { + "GenuineIntel": ("Intel", "https://github.com/microsoft/nvx/issues/408"), + "AuthenticAMD": ("AMD", "https://github.com/microsoft/nvx/issues/409"), +} @dataclass(frozen=True) @@ -209,10 +216,10 @@ def host_cpu_unsupported_guidance( f"{HOST_PROFILE_GENERATION} to boot on a CPU profile derived from " 'this host; doc/usage.md ("CPU profiles") explains its limits.' ) - if host.vendor == "AuthenticAMD": - lines.append(f"nvx: {AMD_SUPPORT_ISSUE} tracks CPU profiles for more AMD CPUs.") - else: - lines.append(f"nvx: {CPU_SUPPORT_ISSUE} tracks CPU profiles for more CPUs.") + support = CPU_SUPPORT_ISSUES.get(host.vendor) + if support is not None: + vendor, issue = support + lines.append(f"nvx: {issue} tracks CPU profiles for more {vendor} CPUs.") return "\n".join(lines) diff --git a/scripts/test_nvx_tools.py b/scripts/test_nvx_tools.py index f759ae6b..b3d4e5dd 100644 --- a/scripts/test_nvx_tools.py +++ b/scripts/test_nvx_tools.py @@ -1736,7 +1736,7 @@ def run( ) self.assertIn("alderlake 6/151 and 6/154", output) self.assertIn("rerun with --cpu-profile host", output) - self.assertIn("https://github.com/microsoft/nvx/issues/390", output) + self.assertIn("https://github.com/microsoft/nvx/issues/408", output) self.assertEqual(run(1, "auto"), (1, output)) # Host profiles serve AMD CPUs too, so an AMD host that no built-in # profile serves gets the same suggestion, with the issue that tracks @@ -1745,8 +1745,8 @@ def run( status, output = run(1, None, host=zen3) self.assertIn("AuthenticAMD 25/33/0", output) self.assertIn("rerun with --cpu-profile host", output) - self.assertIn("https://github.com/microsoft/nvx/issues/396", output) - self.assertNotIn("issues/390", output) + self.assertIn("https://github.com/microsoft/nvx/issues/409", output) + self.assertNotIn("issues/408", output) self.assertEqual(run(1, "host", host=zen3), (1, "")) # Host profiles serve only Intel and AMD CPUs, so another vendor's # host gets no such suggestion, with either request. diff --git a/scripts/test_time_abi.py b/scripts/test_time_abi.py index 90fb84fc..9cf949cc 100644 --- a/scripts/test_time_abi.py +++ b/scripts/test_time_abi.py @@ -136,16 +136,31 @@ def name(model: int, stepping: int, vendor: str = "GenuineIntel"): self.assertIsNone(name(183, 1)) self.assertIsNone(name(1, 1, "AuthenticAMD")) # Milan's profile serves every stepping of AMD's family 25 model 1, - # Milan-X's 2 included, and no other vendor's CPU with its signature. - for stepping in (0, 1, 2, 15): - generation = time_abi.cpu_generation("AuthenticAMD", 25, 1, stepping) - assert generation is not None - self.assertEqual(generation.name, "milan") + # Milan-X's 2 included, Genoa's every stepping of model 17, and + # Turin's every stepping of family 26 model 2, and none of them + # another vendor's CPU with its signature. + for family, model, expected in ( + (25, 1, "milan"), + (25, 17, "genoa"), + (26, 2, "turin"), + ): + for stepping in (0, 1, 2, 15): + generation = time_abi.cpu_generation( + "AuthenticAMD", family, model, stepping + ) + assert generation is not None + self.assertEqual(generation.name, expected) + # Vermeer and Raphael (Ryzen), Turin Dense, and Granite Ridge have no + # profile, and neither has another vendor's CPU with the signature of + # an AMD generation. for vendor, family, model in ( - ("AuthenticAMD", 25, 17), ("AuthenticAMD", 25, 33), + ("AuthenticAMD", 25, 97), + ("AuthenticAMD", 26, 17), + ("AuthenticAMD", 26, 68), ("GenuineIntel", 25, 1), ("HygonGenuine", 25, 1), + ("GenuineIntel", 26, 2), ): with self.subTest(vendor=vendor, family=family, model=model): self.assertIsNone(time_abi.cpu_generation(vendor, family, model, 1)) @@ -160,12 +175,14 @@ def test_names_the_catalog_profiles(self): "intel.emeraldrapids.v1", "intel.alderlake.v1", "amd.milan.v1", + "amd.genoa.v1", + "amd.turin.v1", ], ) self.assertEqual( time_abi.describe_cpu_generations(), "skylake-sp 6/85 steppings 0-4, icelake-sp 6/106, emeraldrapids 6/207, " - "alderlake 6/151 and 6/154, milan 25/1", + "alderlake 6/151 and 6/154, milan 25/1, genoa 25/17, turin 26/2", ) def test_the_catalog_copy_matches_openvmm_pinned_profiles(self): @@ -270,7 +287,11 @@ def test_guides_hosts_that_no_built_in_profile_serves(self): ) self.assertIn(time_abi.describe_cpu_generations(), guidance) self.assertIn("rerun with --cpu-profile host", guidance) - self.assertIn("https://github.com/microsoft/nvx/issues/390", guidance) + self.assertIn( + "https://github.com/microsoft/nvx/issues/408 tracks CPU profiles for " + "more Intel CPUs.", + guidance, + ) self.assertEqual( guidance, time_abi.host_cpu_unsupported_guidance("auto", tiger_lake) ) @@ -279,29 +300,34 @@ def test_guides_hosts_that_no_built_in_profile_serves(self): self.assertIsNone(time_abi.host_cpu_unsupported_guidance("host", tiger_lake)) # Host profiles serve AMD CPUs too: an AMD CPU without a built-in - # profile, such as Genoa, gets the same suggestion as an Intel one, - # and the issue that tracks profiles for more AMD CPUs. - genoa = time_abi.HostCpu("AuthenticAMD", 25, 17, 1) - guidance = time_abi.host_cpu_unsupported_guidance(None, genoa) + # profile, such as Raphael (Ryzen 7000), gets the same suggestion as + # an Intel one, and the issue that tracks profiles for more AMD CPUs. + raphael = time_abi.HostCpu("AuthenticAMD", 25, 97, 2) + guidance = time_abi.host_cpu_unsupported_guidance(None, raphael) assert guidance is not None self.assertIn( - "this host's CPU, AuthenticAMD 25/17/1, so a cold boot with " + "this host's CPU, AuthenticAMD 25/97/2, so a cold boot with " "--cpu-profile auto, the default, fails on it with " "E_PROFILE_HOST_UNKNOWN;", guidance, ) self.assertIn(time_abi.describe_cpu_generations(), guidance) self.assertIn("rerun with --cpu-profile host", guidance) - self.assertIn("https://github.com/microsoft/nvx/issues/396", guidance) - self.assertNotIn("issues/390", guidance) - self.assertIsNone(time_abi.host_cpu_unsupported_guidance("host", genoa)) - # Another vendor gets no suggestion, with either request. + self.assertIn( + "https://github.com/microsoft/nvx/issues/409 tracks CPU profiles for " + "more AMD CPUs.", + guidance, + ) + self.assertNotIn("issues/408", guidance) + self.assertIsNone(time_abi.host_cpu_unsupported_guidance("host", raphael)) + # Another vendor gets no suggestion, with either request, and no + # issue: the time ABI does not serve its CPUs. hygon = time_abi.HostCpu("HygonGenuine", 24, 0, 1) guidance = time_abi.host_cpu_unsupported_guidance("auto", hygon) assert guidance is not None self.assertNotIn("rerun with", guidance) self.assertIn("host CPU profiles serve only Intel and AMD CPUs", guidance) - self.assertIn("https://github.com/microsoft/nvx/issues/390", guidance) + self.assertNotIn("github.com", guidance) self.assertEqual( guidance, time_abi.host_cpu_unsupported_guidance("host", hygon) ) @@ -310,13 +336,18 @@ def test_guides_hosts_that_no_built_in_profile_serves(self): # unknown CPU get none. alder_lake = time_abi.HostCpu("GenuineIntel", 6, 154, 3) milan = time_abi.HostCpu("AuthenticAMD", 25, 1, 1) + genoa = time_abi.HostCpu("AuthenticAMD", 25, 17, 1) + turin = time_abi.HostCpu("AuthenticAMD", 26, 2, 1) for cpu_profile, host in ( (None, alder_lake), ("host", alder_lake), (None, milan), ("host", milan), + (None, genoa), + (None, turin), + ("host", turin), ("intel.alderlake.v1", tiger_lake), - ("intel.alderlake.v1", genoa), + ("intel.alderlake.v1", raphael), ("intel.skylake-sp.v1", milan), (None, None), ):