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;