Add ARM v8.7 FP8 ISA feature detection and reporting to cpuinfo. (#413)
Expose ARM v8.7 / Armv9.6-A FP8 instructions across public API structs, Linux HWCAP2 / /proc/cpuinfo parsing, Apple sysctls, Windows processor feature checks, and isa_info:
- Base FP8 (cpuinfo_has_arm_fp8 / FEAT_FP8 / HWCAP2_F8CVT / F8E4M3 / F8E5M2)
- FP8 4-way Dot Product (cpuinfo_has_arm_f8dot / FEAT_FP8DOT4 / HWCAP2_F8DP4)
- FP8 8-way Matrix Multiply-Accumulate into FP32 (cpuinfo_has_arm_f8mm / FEAT_F8F32MM / HWCAP_F8MM8)
Fixes https://github.com/pytorch/cpuinfo/issues/412
diff --git a/include/cpuinfo-mock.h b/include/cpuinfo-mock.h
index 7bb6d1e..24b9768 100644
--- a/include/cpuinfo-mock.h
+++ b/include/cpuinfo-mock.h
@@ -57,7 +57,7 @@
ssize_t CPUINFO_ABI cpuinfo_mock_read(int fd, void* buffer, size_t capacity);
#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64
-void CPUINFO_ABI cpuinfo_set_hwcap(uint32_t hwcap);
+void CPUINFO_ABI cpuinfo_set_hwcap(uint64_t hwcap);
#endif
#if CPUINFO_ARCH_ARM
void CPUINFO_ABI cpuinfo_set_hwcap2(uint64_t hwcap2);
diff --git a/include/cpuinfo.h b/include/cpuinfo.h
index bee3c23..ed05330 100644
--- a/include/cpuinfo.h
+++ b/include/cpuinfo.h
@@ -1771,6 +1771,9 @@
bool sme_bi32i32;
bool sme_b16b16;
bool sme_f16f16;
+ bool fp8;
+ bool f8dot;
+ bool f8mm;
uint32_t svelen;
uint32_t smelen;
#endif
@@ -2081,6 +2084,30 @@
#endif
}
+static inline bool cpuinfo_has_arm_fp8(void) {
+#if CPUINFO_ARCH_ARM64
+ return cpuinfo_isa.fp8;
+#else
+ return false;
+#endif
+}
+
+static inline bool cpuinfo_has_arm_f8dot(void) {
+#if CPUINFO_ARCH_ARM64
+ return cpuinfo_isa.f8dot;
+#else
+ return false;
+#endif
+}
+
+static inline bool cpuinfo_has_arm_f8mm(void) {
+#if CPUINFO_ARCH_ARM64
+ return cpuinfo_isa.f8mm;
+#else
+ return false;
+#endif
+}
+
static inline bool cpuinfo_has_arm_aes(void) {
#if CPUINFO_ARCH_ARM || CPUINFO_ARCH_ARM64
return cpuinfo_isa.aes;
diff --git a/src/arm/linux/aarch32-isa.c b/src/arm/linux/aarch32-isa.c
index 5a9f2cc..5ed5fc6 100644
--- a/src/arm/linux/aarch32-isa.c
+++ b/src/arm/linux/aarch32-isa.c
@@ -23,7 +23,7 @@
#endif
void cpuinfo_arm_linux_decode_isa_from_proc_cpuinfo(
- uint32_t features,
+ uint64_t features,
uint64_t features2,
uint32_t midr,
uint32_t architecture_version,
diff --git a/src/arm/linux/aarch64-isa.c b/src/arm/linux/aarch64-isa.c
index 8e83d51..bd09a9b 100644
--- a/src/arm/linux/aarch64-isa.c
+++ b/src/arm/linux/aarch64-isa.c
@@ -6,7 +6,7 @@
#include <sys/prctl.h>
void cpuinfo_arm64_linux_decode_isa_from_proc_cpuinfo(
- uint32_t features,
+ uint64_t features,
uint64_t features2,
uint32_t midr,
const struct cpuinfo_arm_chipset chipset[restrict static 1],
@@ -174,6 +174,15 @@
if (features & CPUINFO_ARM_LINUX_FEATURE_ASIMDFHM) {
isa->fhm = true;
}
+ if (features2 & CPUINFO_ARM_LINUX_FEATURE2_FP8) {
+ isa->fp8 = true;
+ }
+ if (features2 & CPUINFO_ARM_LINUX_FEATURE2_F8DOT) {
+ isa->f8dot = true;
+ }
+ if (features & CPUINFO_ARM_LINUX_FEATURE_F8MM) {
+ isa->f8mm = true;
+ }
#ifndef PR_SVE_GET_VL
#define PR_SVE_GET_VL 51
diff --git a/src/arm/linux/api.h b/src/arm/linux/api.h
index 43bbbc3..733a9dd 100644
--- a/src/arm/linux/api.h
+++ b/src/arm/linux/api.h
@@ -119,6 +119,7 @@
#define CPUINFO_ARM_LINUX_FEATURE_SB UINT32_C(0x20000000)
#define CPUINFO_ARM_LINUX_FEATURE_PACA UINT32_C(0x40000000)
#define CPUINFO_ARM_LINUX_FEATURE_PACG UINT32_C(0x80000000)
+#define CPUINFO_ARM_LINUX_FEATURE_F8MM UINT64_C(0x0000000800000000)
#define CPUINFO_ARM_LINUX_FEATURE2_DCPODP UINT32_C(0x00000001)
#define CPUINFO_ARM_LINUX_FEATURE2_SVE2 UINT32_C(0x00000002)
@@ -145,6 +146,8 @@
#define CPUINFO_ARM_LINUX_FEATURE2_SME_BI32I32 UINT64_C(0x0000010000000000)
#define CPUINFO_ARM_LINUX_FEATURE2_SME_B16B16 UINT64_C(0x0000020000000000)
#define CPUINFO_ARM_LINUX_FEATURE2_SME_F16F16 UINT64_C(0x0000040000000000)
+#define CPUINFO_ARM_LINUX_FEATURE2_FP8 UINT64_C(0x0008000000000000)
+#define CPUINFO_ARM_LINUX_FEATURE2_F8DOT UINT64_C(0x0020000000000000)
#endif
#define CPUINFO_ARM_LINUX_VALID_ARCHITECTURE UINT32_C(0x00010000)
@@ -179,7 +182,7 @@
uint32_t architecture_flags;
struct cpuinfo_arm_linux_proc_cpuinfo_cache proc_cpuinfo_cache;
#endif
- uint32_t features;
+ uint64_t features;
uint64_t features2;
/**
* Main ID Register value.
@@ -302,14 +305,14 @@
#if CPUINFO_ARCH_ARM
CPUINFO_INTERNAL bool cpuinfo_arm_linux_hwcap_from_getauxval(
- uint32_t hwcap[restrict static 1],
+ uint64_t hwcap[restrict static 1],
uint64_t hwcap2[restrict static 1]);
CPUINFO_INTERNAL bool cpuinfo_arm_linux_hwcap_from_procfs(
- uint32_t hwcap[restrict static 1],
+ uint64_t hwcap[restrict static 1],
uint64_t hwcap2[restrict static 1]);
CPUINFO_INTERNAL void cpuinfo_arm_linux_decode_isa_from_proc_cpuinfo(
- uint32_t features,
+ uint64_t features,
uint64_t features2,
uint32_t midr,
uint32_t architecture_version,
@@ -318,11 +321,11 @@
struct cpuinfo_arm_isa isa[restrict static 1]);
#elif CPUINFO_ARCH_ARM64
CPUINFO_INTERNAL void cpuinfo_arm_linux_hwcap_from_getauxval(
- uint32_t hwcap[restrict static 1],
+ uint64_t hwcap[restrict static 1],
uint64_t hwcap2[restrict static 1]);
CPUINFO_INTERNAL void cpuinfo_arm64_linux_decode_isa_from_proc_cpuinfo(
- uint32_t features,
+ uint64_t features,
uint64_t features2,
uint32_t midr,
const struct cpuinfo_arm_chipset chipset[restrict static 1],
diff --git a/src/arm/linux/cpuinfo.c b/src/arm/linux/cpuinfo.c
index 081d9aa..c627dc8 100644
--- a/src/arm/linux/cpuinfo.c
+++ b/src/arm/linux/cpuinfo.c
@@ -237,6 +237,12 @@
} else if (memcmp(feature_start, "lrcpc", feature_length) == 0) {
#if CPUINFO_ARCH_ARM64
processor->features |= CPUINFO_ARM_LINUX_FEATURE_LRCPC;
+ } else if (memcmp(feature_start, "f8cvt", feature_length) == 0) {
+ processor->features2 |= CPUINFO_ARM_LINUX_FEATURE2_FP8;
+ } else if (memcmp(feature_start, "f8dp4", feature_length) == 0) {
+ processor->features2 |= CPUINFO_ARM_LINUX_FEATURE2_F8DOT;
+ } else if (memcmp(feature_start, "f8mm8", feature_length) == 0) {
+ processor->features |= CPUINFO_ARM_LINUX_FEATURE_F8MM;
#endif
#if CPUINFO_ARCH_ARM
} else if (memcmp(feature_start, "thumb", feature_length) == 0) {
diff --git a/src/arm/linux/hwcap.c b/src/arm/linux/hwcap.c
index 7f7b4df..0ffc635 100644
--- a/src/arm/linux/hwcap.c
+++ b/src/arm/linux/hwcap.c
@@ -26,8 +26,8 @@
#endif
#if CPUINFO_MOCK
-static uint32_t mock_hwcap = 0;
-void cpuinfo_set_hwcap(uint32_t hwcap) {
+static uint64_t mock_hwcap = 0;
+void cpuinfo_set_hwcap(uint64_t hwcap) {
mock_hwcap = hwcap;
}
@@ -40,7 +40,7 @@
#if CPUINFO_ARCH_ARM
typedef unsigned long (*getauxval_function_t)(unsigned long);
-bool cpuinfo_arm_linux_hwcap_from_getauxval(uint32_t hwcap[restrict static 1], uint64_t hwcap2[restrict static 1]) {
+bool cpuinfo_arm_linux_hwcap_from_getauxval(uint64_t hwcap[restrict static 1], uint64_t hwcap2[restrict static 1]) {
#if CPUINFO_MOCK
*hwcap = mock_hwcap;
*hwcap2 = mock_hwcap2;
@@ -63,8 +63,8 @@
goto cleanup;
}
- *hwcap = getauxval(AT_HWCAP);
- *hwcap2 = getauxval(AT_HWCAP2);
+ *hwcap = (uint64_t)getauxval(AT_HWCAP);
+ *hwcap2 = (uint64_t)getauxval(AT_HWCAP2);
cleanup:
if (libc != NULL) {
@@ -74,8 +74,8 @@
return getauxval != NULL;
#elif defined(__GLIBC__) && defined(__GLIBC_MINOR__) && (__GLIBC__ > 2 || __GLIBC__ == 2 && __GLIBC_MINOR__ >= 16)
/* GNU/Linux: getauxval is supported since glibc-2.16 */
- *hwcap = getauxval(AT_HWCAP);
- *hwcap2 = getauxval(AT_HWCAP2);
+ *hwcap = (uint64_t)getauxval(AT_HWCAP);
+ *hwcap2 = (uint64_t)getauxval(AT_HWCAP2);
return true;
#else
return false;
@@ -83,7 +83,7 @@
}
#ifdef __ANDROID__
-bool cpuinfo_arm_linux_hwcap_from_procfs(uint32_t hwcap[restrict static 1], uint64_t hwcap2[restrict static 1]) {
+bool cpuinfo_arm_linux_hwcap_from_procfs(uint64_t hwcap[restrict static 1], uint64_t hwcap2[restrict static 1]) {
#if CPUINFO_MOCK
*hwcap = mock_hwcap;
*hwcap2 = mock_hwcap2;
@@ -110,7 +110,7 @@
if (bytes_read == sizeof(elf_auxv)) {
switch (elf_auxv.a_type) {
case AT_HWCAP:
- hwcaps[0] = (uint32_t)elf_auxv.a_un.a_val;
+ hwcaps[0] = elf_auxv.a_un.a_val;
break;
case AT_HWCAP2:
hwcaps[1] = (uint64_t)elf_auxv.a_un.a_val;
@@ -141,12 +141,12 @@
}
#endif /* __ANDROID__ */
#elif CPUINFO_ARCH_ARM64
-void cpuinfo_arm_linux_hwcap_from_getauxval(uint32_t hwcap[restrict static 1], uint64_t hwcap2[restrict static 1]) {
+void cpuinfo_arm_linux_hwcap_from_getauxval(uint64_t hwcap[restrict static 1], uint64_t hwcap2[restrict static 1]) {
#if CPUINFO_MOCK
*hwcap = mock_hwcap;
*hwcap2 = mock_hwcap2;
#else
- *hwcap = (uint32_t)getauxval(AT_HWCAP);
+ *hwcap = (uint64_t)getauxval(AT_HWCAP);
*hwcap2 = (uint64_t)getauxval(AT_HWCAP2);
return;
#endif
diff --git a/src/arm/linux/init.c b/src/arm/linux/init.c
index 534230d..48c9f4a 100644
--- a/src/arm/linux/init.c
+++ b/src/arm/linux/init.c
@@ -335,7 +335,7 @@
#endif
#if CPUINFO_ARCH_ARM
- uint32_t isa_features = 0;
+ uint64_t isa_features = 0;
uint64_t isa_features2 = 0;
#ifdef __ANDROID__
/*
@@ -388,7 +388,7 @@
&chipset,
&cpuinfo_isa);
#elif CPUINFO_ARCH_ARM64
- uint32_t isa_features = 0;
+ uint64_t isa_features = 0;
uint64_t isa_features2 = 0;
/* getauxval is always available on ARM64 Android */
cpuinfo_arm_linux_hwcap_from_getauxval(&isa_features, &isa_features2);
diff --git a/src/arm/mach/init.c b/src/arm/mach/init.c
index 11de4ae..ca89023 100644
--- a/src/arm/mach/init.c
+++ b/src/arm/mach/init.c
@@ -491,6 +491,9 @@
cpuinfo_isa.sme_bi32i32 = get_sys_info_by_name("hw.optional.arm.SME_BI32I32") != 0;
cpuinfo_isa.sme_b16b16 = get_sys_info_by_name("hw.optional.arm.FEAT_SME_B16B16") != 0;
cpuinfo_isa.sme_f16f16 = get_sys_info_by_name("hw.optional.arm.FEAT_SME_F16F16") != 0;
+ cpuinfo_isa.fp8 = get_sys_info_by_name("hw.optional.arm.FEAT_FP8") != 0;
+ cpuinfo_isa.f8dot = get_sys_info_by_name("hw.optional.arm.FEAT_FP8DOT4") != 0;
+ cpuinfo_isa.f8mm = get_sys_info_by_name("hw.optional.arm.FEAT_F8F32MM") != 0;
cpuinfo_isa.smelen = get_sys_info_by_name("hw.optional.arm.sme_max_svl_b");
diff --git a/src/arm/windows/init.c b/src/arm/windows/init.c
index a07fbe4..528ae32 100644
--- a/src/arm/windows/init.c
+++ b/src/arm/windows/init.c
@@ -235,6 +235,15 @@
// - sme_bi32i32
cpuinfo_isa.bf16 = IsProcessorFeaturePresent(PF_ARM_V86_BF16_INSTRUCTIONS_AVAILABLE) != 0;
+#if defined(PF_ARM_V87_FP8_INSTRUCTIONS_AVAILABLE)
+ cpuinfo_isa.fp8 = IsProcessorFeaturePresent(PF_ARM_V87_FP8_INSTRUCTIONS_AVAILABLE) != 0;
+#endif
+#if defined(PF_ARM_V87_FP8_DOT4_INSTRUCTIONS_AVAILABLE)
+ cpuinfo_isa.f8dot = IsProcessorFeaturePresent(PF_ARM_V87_FP8_DOT4_INSTRUCTIONS_AVAILABLE) != 0;
+#endif
+#if defined(PF_ARM_V87_FP8_F32MM_INSTRUCTIONS_AVAILABLE)
+ cpuinfo_isa.f8mm = IsProcessorFeaturePresent(PF_ARM_V87_FP8_F32MM_INSTRUCTIONS_AVAILABLE) != 0;
+#endif
// TODO: This is not available in the Windows SDK yet , so conservatively go with the lowest value (128 bits)
// https://developer.arm.com/documentation/101427/0102/Register-descriptions/Scalable-vector-extensions--SVE--registers/ZCR-EL1--SVE-Control-Register--EL1
diff --git a/test/mock/pixel-8.cc b/test/mock/pixel-8.cc
index a0235b4..b04a98b 100644
--- a/test/mock/pixel-8.cc
+++ b/test/mock/pixel-8.cc
@@ -582,6 +582,18 @@
ASSERT_TRUE(cpuinfo_has_arm_crc32());
}
+TEST(ISA, fp8) {
+ ASSERT_FALSE(cpuinfo_has_arm_fp8());
+}
+
+TEST(ISA, f8dot) {
+ ASSERT_FALSE(cpuinfo_has_arm_f8dot());
+}
+
+TEST(ISA, f8mm) {
+ ASSERT_FALSE(cpuinfo_has_arm_f8mm());
+}
+
TEST(L1I, count) {
ASSERT_EQ(9, cpuinfo_get_l1i_caches_count());
}
diff --git a/tools/isa-info.c b/tools/isa-info.c
index 7ec3eec..0cc2146 100644
--- a/tools/isa-info.c
+++ b/tools/isa-info.c
@@ -170,6 +170,9 @@
printf("\tARM v8.2 Int8 matrix multiplication: %s\n", cpuinfo_has_arm_i8mm() ? "yes" : "no");
printf("\tARM v8.3 JS conversion: %s\n", cpuinfo_has_arm_jscvt() ? "yes" : "no");
printf("\tARM v8.3 complex: %s\n", cpuinfo_has_arm_fcma() ? "yes" : "no");
+ printf("\tARM v8.7 FP8: %s\n", cpuinfo_has_arm_fp8() ? "yes" : "no");
+ printf("\tARM v8.7 FP8 dot product: %s\n", cpuinfo_has_arm_f8dot() ? "yes" : "no");
+ printf("\tARM v8.7 FP8 matrix multiplication: %s\n", cpuinfo_has_arm_f8mm() ? "yes" : "no");
printf("SIMD extensions:\n");
printf("\tARM SVE: %s\n", cpuinfo_has_arm_sve() ? "yes" : "no");