Skip to main content

hermes_simd/
cpu.rs

1/// Trait for querying runtime Intel AMX support for specific element types.
2pub trait AmxSupport {
3    /// Returns true if the current CPU supports AMX for this type.
4    fn has_amx() -> bool;
5}
6
7/// Trait for querying runtime AVX-512 support for specific element types.
8pub trait Avx512Support {
9    /// Returns true if the current CPU supports AVX-512 extensions/fallbacks for this type.
10    fn has_avx512() -> bool;
11}
12
13/// Trait for querying runtime FMA (Fused Multiply-Add) support.
14///
15/// FMA (`vfmadd*` family) is available on Intel Haswell+ and AMD Piledriver+.
16/// It performs `a * b + c` in a single instruction with one rounding for
17/// floating-point kernels that select an FMA-capable implementation.
18pub trait FmaSupport {
19    /// Returns `true` if the current CPU supports FMA3 instructions.
20    fn has_fma() -> bool;
21}
22
23/// Global OnceLock-cached FMA3 probe.
24///
25/// Separate from the per-type traits above because FMA availability is
26/// processor-wide; scalar type implementations only expose whether their
27/// operation family can use that host capability.
28#[cfg(target_arch = "x86_64")]
29#[inline]
30pub fn has_fma3() -> bool {
31    use std::sync::OnceLock;
32    static CACHED: OnceLock<bool> = OnceLock::new();
33    *CACHED.get_or_init(|| std::is_x86_feature_detected!("fma"))
34}
35
36/// FMA3 is an x86_64-specific extension; every other architecture reports
37/// no support (aarch64/NEON has its own always-available fused multiply-add
38/// instructions, dispatched separately from this x86-specific probe).
39#[cfg(not(target_arch = "x86_64"))]
40#[inline]
41pub fn has_fma3() -> bool {
42    false
43}
44
45impl FmaSupport for f32 {
46    #[inline]
47    fn has_fma() -> bool {
48        has_fma3()
49    }
50}
51
52impl FmaSupport for f64 {
53    #[inline]
54    fn has_fma() -> bool {
55        has_fma3()
56    }
57}
58
59impl FmaSupport for Bf16 {
60    #[inline]
61    fn has_fma() -> bool {
62        has_fma3()
63    }
64}
65
66impl AmxSupport for Bf16 {
67    #[inline]
68    fn has_amx() -> bool {
69        #[cfg(target_arch = "x86_64")]
70        {
71            has_amx_bf16()
72        }
73        #[cfg(not(target_arch = "x86_64"))]
74        {
75            false
76        }
77    }
78}
79
80impl AmxSupport for i8 {
81    #[inline]
82    fn has_amx() -> bool {
83        #[cfg(target_arch = "x86_64")]
84        {
85            has_amx_int8()
86        }
87        #[cfg(not(target_arch = "x86_64"))]
88        {
89            false
90        }
91    }
92}
93
94impl Avx512Support for Bf16 {
95    #[inline]
96    fn has_avx512() -> bool {
97        #[cfg(target_arch = "x86_64")]
98        {
99            has_avx512_bf16_tile()
100        }
101        #[cfg(not(target_arch = "x86_64"))]
102        {
103            false
104        }
105    }
106}
107
108impl Avx512Support for i8 {
109    #[inline]
110    fn has_avx512() -> bool {
111        #[cfg(target_arch = "x86_64")]
112        {
113            has_avx512_vnni_tile()
114        }
115        #[cfg(not(target_arch = "x86_64"))]
116        {
117            false
118        }
119    }
120}
121
122// AMX / AVX-512 tile-kernel capability probes.
123//
124// AMX dispatch is disabled until Hermes has a stable, permission-aware probe
125// that verifies hardware support, XCR0 OS state, and Linux XTILEDATA process
126// permission before reporting true. Raw CPUID is insufficient because it misses
127// OS enablement and can alias unsupported leaves; the stable Rust feature macro
128// does not currently accept AMX feature strings on this toolchain. Returning
129// false preserves the safe-dispatch contract instead of risking a #UD/#NM fault.
130
131/// AMX bf16 tile GEMM (`tdpbf16ps` inline asm) requires `amx-tile` + `amx-bf16`.
132#[cfg(target_arch = "x86_64")]
133#[inline]
134fn has_amx_bf16() -> bool {
135    use std::sync::OnceLock;
136    static CACHED: OnceLock<bool> = OnceLock::new();
137    *CACHED.get_or_init(|| false)
138}
139
140/// AMX int8 tile GEMM (`tdpbssd` inline asm) requires `amx-tile` + `amx-int8`.
141#[cfg(target_arch = "x86_64")]
142#[inline]
143fn has_amx_int8() -> bool {
144    use std::sync::OnceLock;
145    static CACHED: OnceLock<bool> = OnceLock::new();
146    *CACHED.get_or_init(|| false)
147}
148
149/// The AVX-512 bf16 tile kernels (`avx512_tiling`) enable
150/// `avx512f,avx512bw,avx512vl` — they widen bf16 to f32 and FMA in f32, so they
151/// need the base 512-bit + byte/word + 128/256-bit-lane extensions, **not** the
152/// `avx512bf16` dot-product ISA. Detecting the exact enabled set (rather than the
153/// old, mismatched `avx512bf16` bit) both closes the `#UD` window and stops
154/// falsely skipping the kernel on capable non-bf16 parts.
155#[cfg(target_arch = "x86_64")]
156#[inline]
157fn has_avx512_bf16_tile() -> bool {
158    use std::sync::OnceLock;
159    static CACHED: OnceLock<bool> = OnceLock::new();
160    *CACHED.get_or_init(|| {
161        std::is_x86_feature_detected!("avx512f")
162            && std::is_x86_feature_detected!("avx512bw")
163            && std::is_x86_feature_detected!("avx512vl")
164    })
165}
166
167/// 256-bit VEX-encoded AVX-VNNI (`vpdpbusd`/`vpdpwssd` on YMM without AVX-512).
168///
169/// Present on Intel Alder Lake+ client parts and AMD Zen 5 — hardware that has
170/// no AVX-512. The int8 tile kernel gated on this probe requires only the
171/// `avxvnni` feature (the signed-signed `vpdpbssd` from `avxvnniint8` is NOT
172/// assumed; the kernel bias-corrects `vpdpbusd` instead). The macro handles
173/// XCR0/OSXSAVE and the CPUID max-leaf internally.
174#[cfg(target_arch = "x86_64")]
175#[inline]
176pub fn has_avx_vnni() -> bool {
177    use std::sync::OnceLock;
178    static CACHED: OnceLock<bool> = OnceLock::new();
179    *CACHED.get_or_init(|| std::is_x86_feature_detected!("avxvnni"))
180}
181
182/// The AVX-512 int8 tile kernel enables `avx512f,avx512vnni` and implements
183/// signed-byte products through `VPDPBUSD` bias correction.
184#[cfg(target_arch = "x86_64")]
185#[inline]
186fn has_avx512_vnni_tile() -> bool {
187    use std::sync::OnceLock;
188    static CACHED: OnceLock<bool> = OnceLock::new();
189    *CACHED.get_or_init(|| {
190        std::is_x86_feature_detected!("avx512f") && std::is_x86_feature_detected!("avx512vnni")
191    })
192}
193use eunomia::Bf16;