From e4d43b0eed00290f78dc054ce219cdd653b1a7ec Mon Sep 17 00:00:00 2001 From: alanshurafa <138934986+alanshurafa@users.noreply.github.com> Date: Thu, 5 Nov 2026 22:41:59 -0500 Subject: [PATCH 1/3] Skip x86 CPU variants whose register state the OS left off The CPU backend score checked CPUID feature bits only. On a machine where CPUID advertises AVX-512 but the OS leaves the ZMM state off in XCR0 (seen on Zen 5 Windows, XCR0 = 0x7), the cascadelake variant won and the first 512-bit instruction raised an illegal-instruction fault. Read XCR0 with xgetbv when OSXSAVE is set and score a variant 0 unless the OS enables the state it needs: SSE+YMM for AVX-class variants, opmask+ZMM for AVX-512, XTILECFG+XTILEDATA for AMX. Scores are otherwise unchanged. Fixes cjpais/Handy#2402. Co-Authored-By: Claude Fable 5.1 --- ggml/src/ggml-cpu/arch/x86/cpu-feats.cpp | 60 ++++++++++++++++++++++-- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/ggml/src/ggml-cpu/arch/x86/cpu-feats.cpp b/ggml/src/ggml-cpu/arch/x86/cpu-feats.cpp index d775a0363..df74bd10a 100644 --- a/ggml/src/ggml-cpu/arch/x86/cpu-feats.cpp +++ b/ggml/src/ggml-cpu/arch/x86/cpu-feats.cpp @@ -4,8 +4,10 @@ #ifdef _MSC_VER #include +#include #endif +#include #include #include #include @@ -260,11 +262,56 @@ void test_x86_is() { } #endif -static int ggml_backend_cpu_x86_score() { - // FIXME: this does not check for OS support +// XCR0 state components the OS must enable before the matching registers can be used +// (Intel SDM Vol. 1, 13.1). CPUID only reports what the hardware supports; if the OS +// leaves a component disabled, the first instruction touching it raises #UD. +#define GGML_XCR0_SSE (1ull << 1) +#define GGML_XCR0_YMM (1ull << 2) +#define GGML_XCR0_OPMASK (1ull << 5) +#define GGML_XCR0_ZMM_HI256 (1ull << 6) +#define GGML_XCR0_HI16_ZMM (1ull << 7) +#define GGML_XCR0_XTILECFG (1ull << 17) +#define GGML_XCR0_XTILEDATA (1ull << 18) + +static uint64_t ggml_x86_xgetbv0() { +#if defined(_MSC_VER) && !defined(__clang__) + return _xgetbv(0); +#else + // inline asm rather than the intrinsic, so this builds without -mxsave + uint32_t eax, edx; + __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(0)); + return ((uint64_t) edx << 32) | eax; +#endif +} + +// XCR0 bits this build variant needs the OS to have enabled. +static uint64_t ggml_x86_required_xcr0() { + uint64_t required = 0; +#if defined(GGML_AVX) || defined(GGML_AVX2) || defined(GGML_FMA) || defined(GGML_F16C) || defined(GGML_AVX_VNNI) + required |= GGML_XCR0_SSE | GGML_XCR0_YMM; +#endif +#if defined(GGML_AVX512) || defined(GGML_AVX512_VBMI) || defined(GGML_AVX512_VNNI) || defined(GGML_AVX512_BF16) + required |= GGML_XCR0_SSE | GGML_XCR0_YMM | GGML_XCR0_OPMASK | GGML_XCR0_ZMM_HI256 | GGML_XCR0_HI16_ZMM; +#endif +#if defined(GGML_AMX_TILE) || defined(GGML_AMX_INT8) + required |= GGML_XCR0_XTILECFG | GGML_XCR0_XTILEDATA; +#endif + return required; +} + +// true if the OS state in xcr0 covers everything this build variant uses +static bool ggml_x86_os_supports_variant(uint64_t xcr0) { + const uint64_t required = ggml_x86_required_xcr0(); + return (xcr0 & required) == required; +} + +// xcr0 is passed in (0 when OSXSAVE is clear) so the decision can be tested +static int ggml_backend_cpu_x86_score_xcr0(cpuid_x86 & is, uint64_t xcr0) { + if (!ggml_x86_os_supports_variant(xcr0)) { + return 0; + } int score = 1; - cpuid_x86 is; #ifdef GGML_FMA if (!is.FMA()) { return 0; } @@ -322,6 +369,13 @@ static int ggml_backend_cpu_x86_score() { return score; } +static int ggml_backend_cpu_x86_score() { + cpuid_x86 is; + // xgetbv is only valid when the OS has set CR4.OSXSAVE + const uint64_t xcr0 = is.OSXSAVE() ? ggml_x86_xgetbv0() : 0; + return ggml_backend_cpu_x86_score_xcr0(is, xcr0); +} + GGML_BACKEND_DL_SCORE_IMPL(ggml_backend_cpu_x86_score) #endif // defined(__x86_64__) || (defined(_MSC_VER) && defined(_M_AMD64)) From d3acf84c050a206818cd2a8a102b08b114674302 Mon Sep 17 00:00:00 2001 From: alanshurafa <138934986+alanshurafa@users.noreply.github.com> Date: Thu, 5 Nov 2026 22:43:58 -0500 Subject: [PATCH 2/3] Add ggml patch 0004 so the XCR0 check survives a ggml sync ggml/ is regenerated by scripts/sync-ggml.sh from the upstream SHA plus patches/ggml/*.patch; without a patch file the cpu-feats.cpp fix would be dropped on the next sync. Co-Authored-By: Claude Fable 5.1 --- ggml/UPSTREAM | 1 + .../0004-cpu-x86-score-os-xsave-check.patch | 89 +++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 patches/ggml/0004-cpu-x86-score-os-xsave-check.patch diff --git a/ggml/UPSTREAM b/ggml/UPSTREAM index 923e32dff..80d7c8933 100644 --- a/ggml/UPSTREAM +++ b/ggml/UPSTREAM @@ -4,6 +4,7 @@ patches: patches/ggml/0001-fix-threadpool-oversubscription.patch patches/ggml/0002-backend-reg-filter.patch patches/ggml/0003-metal-mapped-buffer-null.patch + patches/ggml/0004-cpu-x86-score-os-xsave-check.patch This directory is generated from the upstream ggml tree at the SHA above, minus .github/, with the listed downstream patches applied in order. Do not edit it by diff --git a/patches/ggml/0004-cpu-x86-score-os-xsave-check.patch b/patches/ggml/0004-cpu-x86-score-os-xsave-check.patch new file mode 100644 index 000000000..c236c3f0b --- /dev/null +++ b/patches/ggml/0004-cpu-x86-score-os-xsave-check.patch @@ -0,0 +1,89 @@ +diff --git a/src/ggml-cpu/arch/x86/cpu-feats.cpp b/src/ggml-cpu/arch/x86/cpu-feats.cpp +index d775a036..df74bd10 100644 +--- a/src/ggml-cpu/arch/x86/cpu-feats.cpp ++++ b/src/ggml-cpu/arch/x86/cpu-feats.cpp +@@ -4,8 +4,10 @@ + + #ifdef _MSC_VER + #include ++#include + #endif + ++#include + #include + #include + #include +@@ -260,11 +262,56 @@ void test_x86_is() { + } + #endif + +-static int ggml_backend_cpu_x86_score() { +- // FIXME: this does not check for OS support ++// XCR0 state components the OS must enable before the matching registers can be used ++// (Intel SDM Vol. 1, 13.1). CPUID only reports what the hardware supports; if the OS ++// leaves a component disabled, the first instruction touching it raises #UD. ++#define GGML_XCR0_SSE (1ull << 1) ++#define GGML_XCR0_YMM (1ull << 2) ++#define GGML_XCR0_OPMASK (1ull << 5) ++#define GGML_XCR0_ZMM_HI256 (1ull << 6) ++#define GGML_XCR0_HI16_ZMM (1ull << 7) ++#define GGML_XCR0_XTILECFG (1ull << 17) ++#define GGML_XCR0_XTILEDATA (1ull << 18) ++ ++static uint64_t ggml_x86_xgetbv0() { ++#if defined(_MSC_VER) && !defined(__clang__) ++ return _xgetbv(0); ++#else ++ // inline asm rather than the intrinsic, so this builds without -mxsave ++ uint32_t eax, edx; ++ __asm__ __volatile__("xgetbv" : "=a"(eax), "=d"(edx) : "c"(0)); ++ return ((uint64_t) edx << 32) | eax; ++#endif ++} ++ ++// XCR0 bits this build variant needs the OS to have enabled. ++static uint64_t ggml_x86_required_xcr0() { ++ uint64_t required = 0; ++#if defined(GGML_AVX) || defined(GGML_AVX2) || defined(GGML_FMA) || defined(GGML_F16C) || defined(GGML_AVX_VNNI) ++ required |= GGML_XCR0_SSE | GGML_XCR0_YMM; ++#endif ++#if defined(GGML_AVX512) || defined(GGML_AVX512_VBMI) || defined(GGML_AVX512_VNNI) || defined(GGML_AVX512_BF16) ++ required |= GGML_XCR0_SSE | GGML_XCR0_YMM | GGML_XCR0_OPMASK | GGML_XCR0_ZMM_HI256 | GGML_XCR0_HI16_ZMM; ++#endif ++#if defined(GGML_AMX_TILE) || defined(GGML_AMX_INT8) ++ required |= GGML_XCR0_XTILECFG | GGML_XCR0_XTILEDATA; ++#endif ++ return required; ++} ++ ++// true if the OS state in xcr0 covers everything this build variant uses ++static bool ggml_x86_os_supports_variant(uint64_t xcr0) { ++ const uint64_t required = ggml_x86_required_xcr0(); ++ return (xcr0 & required) == required; ++} ++ ++// xcr0 is passed in (0 when OSXSAVE is clear) so the decision can be tested ++static int ggml_backend_cpu_x86_score_xcr0(cpuid_x86 & is, uint64_t xcr0) { ++ if (!ggml_x86_os_supports_variant(xcr0)) { ++ return 0; ++ } + + int score = 1; +- cpuid_x86 is; + + #ifdef GGML_FMA + if (!is.FMA()) { return 0; } +@@ -322,6 +369,13 @@ static int ggml_backend_cpu_x86_score() { + return score; + } + ++static int ggml_backend_cpu_x86_score() { ++ cpuid_x86 is; ++ // xgetbv is only valid when the OS has set CR4.OSXSAVE ++ const uint64_t xcr0 = is.OSXSAVE() ? ggml_x86_xgetbv0() : 0; ++ return ggml_backend_cpu_x86_score_xcr0(is, xcr0); ++} ++ + GGML_BACKEND_DL_SCORE_IMPL(ggml_backend_cpu_x86_score) + + #endif // defined(__x86_64__) || (defined(_MSC_VER) && defined(_M_AMD64)) From 9834ad70da778720d8b675fb36e3a99286a7fa9b Mon Sep 17 00:00:00 2001 From: CJ Pais Date: Fri, 6 Nov 2026 06:42:17 -0800 Subject: [PATCH 3/3] Use sysctl for AVX-512 on macOS in the x86 variant score macOS enables AVX-512 register state lazily, so XCR0 bits 5-7 are clear until a thread first uses AVX-512. The XCR0 check therefore rejected the AVX-512 variants on Intel Macs that support them. On Apple, take AVX-512 OS support from hw.optional.avx512f instead, as google/cpu_features does. Also mark `is` unused for the x64 variant, which checks no CPUID bits and otherwise warns under -Wextra. Patch 0004 regenerated to match. --- ggml/src/ggml-cpu/arch/x86/cpu-feats.cpp | 24 +++++++++++++- .../0004-cpu-x86-score-os-xsave-check.patch | 32 ++++++++++++++++--- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/ggml/src/ggml-cpu/arch/x86/cpu-feats.cpp b/ggml/src/ggml-cpu/arch/x86/cpu-feats.cpp index df74bd10a..0d742e4eb 100644 --- a/ggml/src/ggml-cpu/arch/x86/cpu-feats.cpp +++ b/ggml/src/ggml-cpu/arch/x86/cpu-feats.cpp @@ -7,6 +7,10 @@ #include #endif +#if defined(__APPLE__) +#include +#endif + #include #include #include @@ -284,6 +288,17 @@ static uint64_t ggml_x86_xgetbv0() { #endif } +#if defined(__APPLE__) +// macOS enables AVX-512 state lazily: XCR0 bits 5-7 stay clear until the thread first +// executes an AVX-512 instruction, which the kernel then traps and enables. XCR0 alone +// would wrongly reject AVX-512 variants, so ask the kernel instead (as cpu_features does). +static bool ggml_x86_darwin_avx512_enabled() { + int val = 0; + size_t len = sizeof(val); + return sysctlbyname("hw.optional.avx512f", &val, &len, nullptr, 0) == 0 && val != 0; +} +#endif + // XCR0 bits this build variant needs the OS to have enabled. static uint64_t ggml_x86_required_xcr0() { uint64_t required = 0; @@ -307,6 +322,8 @@ static bool ggml_x86_os_supports_variant(uint64_t xcr0) { // xcr0 is passed in (0 when OSXSAVE is clear) so the decision can be tested static int ggml_backend_cpu_x86_score_xcr0(cpuid_x86 & is, uint64_t xcr0) { + GGML_UNUSED(is); // the x64 variant checks no CPUID bits + if (!ggml_x86_os_supports_variant(xcr0)) { return 0; } @@ -372,7 +389,12 @@ static int ggml_backend_cpu_x86_score_xcr0(cpuid_x86 & is, uint64_t xcr0) { static int ggml_backend_cpu_x86_score() { cpuid_x86 is; // xgetbv is only valid when the OS has set CR4.OSXSAVE - const uint64_t xcr0 = is.OSXSAVE() ? ggml_x86_xgetbv0() : 0; + uint64_t xcr0 = is.OSXSAVE() ? ggml_x86_xgetbv0() : 0; +#if defined(__APPLE__) + if ((xcr0 & (GGML_XCR0_SSE | GGML_XCR0_YMM)) == (GGML_XCR0_SSE | GGML_XCR0_YMM) && ggml_x86_darwin_avx512_enabled()) { + xcr0 |= GGML_XCR0_OPMASK | GGML_XCR0_ZMM_HI256 | GGML_XCR0_HI16_ZMM; + } +#endif return ggml_backend_cpu_x86_score_xcr0(is, xcr0); } diff --git a/patches/ggml/0004-cpu-x86-score-os-xsave-check.patch b/patches/ggml/0004-cpu-x86-score-os-xsave-check.patch index c236c3f0b..852d34be8 100644 --- a/patches/ggml/0004-cpu-x86-score-os-xsave-check.patch +++ b/patches/ggml/0004-cpu-x86-score-os-xsave-check.patch @@ -1,19 +1,23 @@ diff --git a/src/ggml-cpu/arch/x86/cpu-feats.cpp b/src/ggml-cpu/arch/x86/cpu-feats.cpp -index d775a036..df74bd10 100644 +index d775a036..0d742e4e 100644 --- a/src/ggml-cpu/arch/x86/cpu-feats.cpp +++ b/src/ggml-cpu/arch/x86/cpu-feats.cpp -@@ -4,8 +4,10 @@ +@@ -4,8 +4,14 @@ #ifdef _MSC_VER #include +#include #endif ++#if defined(__APPLE__) ++#include ++#endif ++ +#include #include #include #include -@@ -260,11 +262,56 @@ void test_x86_is() { +@@ -260,11 +266,69 @@ void test_x86_is() { } #endif @@ -41,6 +45,17 @@ index d775a036..df74bd10 100644 +#endif +} + ++#if defined(__APPLE__) ++// macOS enables AVX-512 state lazily: XCR0 bits 5-7 stay clear until the thread first ++// executes an AVX-512 instruction, which the kernel then traps and enables. XCR0 alone ++// would wrongly reject AVX-512 variants, so ask the kernel instead (as cpu_features does). ++static bool ggml_x86_darwin_avx512_enabled() { ++ int val = 0; ++ size_t len = sizeof(val); ++ return sysctlbyname("hw.optional.avx512f", &val, &len, nullptr, 0) == 0 && val != 0; ++} ++#endif ++ +// XCR0 bits this build variant needs the OS to have enabled. +static uint64_t ggml_x86_required_xcr0() { + uint64_t required = 0; @@ -64,6 +79,8 @@ index d775a036..df74bd10 100644 + +// xcr0 is passed in (0 when OSXSAVE is clear) so the decision can be tested +static int ggml_backend_cpu_x86_score_xcr0(cpuid_x86 & is, uint64_t xcr0) { ++ GGML_UNUSED(is); // the x64 variant checks no CPUID bits ++ + if (!ggml_x86_os_supports_variant(xcr0)) { + return 0; + } @@ -73,14 +90,19 @@ index d775a036..df74bd10 100644 #ifdef GGML_FMA if (!is.FMA()) { return 0; } -@@ -322,6 +369,13 @@ static int ggml_backend_cpu_x86_score() { +@@ -322,6 +386,18 @@ static int ggml_backend_cpu_x86_score() { return score; } +static int ggml_backend_cpu_x86_score() { + cpuid_x86 is; + // xgetbv is only valid when the OS has set CR4.OSXSAVE -+ const uint64_t xcr0 = is.OSXSAVE() ? ggml_x86_xgetbv0() : 0; ++ uint64_t xcr0 = is.OSXSAVE() ? ggml_x86_xgetbv0() : 0; ++#if defined(__APPLE__) ++ if ((xcr0 & (GGML_XCR0_SSE | GGML_XCR0_YMM)) == (GGML_XCR0_SSE | GGML_XCR0_YMM) && ggml_x86_darwin_avx512_enabled()) { ++ xcr0 |= GGML_XCR0_OPMASK | GGML_XCR0_ZMM_HI256 | GGML_XCR0_HI16_ZMM; ++ } ++#endif + return ggml_backend_cpu_x86_score_xcr0(is, xcr0); +} +