use std::sync::OnceLock;
use std::time::Instant;
pub fn avx512_fma_units() -> u8 {
static UNITS: OnceLock<u8> = OnceLock::new();
*UNITS.get_or_init(detect_fma_units)
}
pub fn has_dual_avx512_fma() -> bool {
avx512_fma_units() >= 2
}
fn detect_fma_units() -> u8 {
if let Some(v) = crate::knobs::TRACT_AVX512_FMA_UNITS.get() {
match v.trim() {
"1" => return 1,
"2" => return 2,
other => log::warn!(
"TRACT_AVX512_FMA_UNITS={other:?} not understood (expected 1 or 2); probing"
),
}
}
if !std::is_x86_feature_detected!("avx512vnni") {
return 1;
}
let units = unsafe { probe_fma_units() };
log::info!(
"avx512_fma_units: probed {units} (512-bit FMA port{})",
if units == 1 { "" } else { "s" }
);
units
}
#[target_feature(enable = "avx512f,avx512vl,avx512vnni")]
unsafe fn vnni_zmm_burst(iters: u64) {
unsafe {
std::arch::asm!(
"vpxorq zmm0, zmm0, zmm0", "vpxorq zmm1, zmm1, zmm1",
"vpxorq zmm2, zmm2, zmm2", "vpxorq zmm3, zmm3, zmm3",
"vpxorq zmm4, zmm4, zmm4", "vpxorq zmm5, zmm5, zmm5",
"vpxorq zmm6, zmm6, zmm6", "vpxorq zmm7, zmm7, zmm7",
"vpxorq zmm8, zmm8, zmm8", "vpxorq zmm9, zmm9, zmm9",
"vpxorq zmm10, zmm10, zmm10", "vpxorq zmm11, zmm11, zmm11",
"vpxorq zmm12, zmm12, zmm12", "vpxorq zmm13, zmm13, zmm13",
"vpxorq zmm14, zmm14, zmm14", "vpxorq zmm15, zmm15, zmm15",
"vpxorq zmm16, zmm16, zmm16", "vpxorq zmm17, zmm17, zmm17",
"2:",
"vpdpbusd zmm0, zmm16, zmm17", "vpdpbusd zmm1, zmm16, zmm17",
"vpdpbusd zmm2, zmm16, zmm17", "vpdpbusd zmm3, zmm16, zmm17",
"vpdpbusd zmm4, zmm16, zmm17", "vpdpbusd zmm5, zmm16, zmm17",
"vpdpbusd zmm6, zmm16, zmm17", "vpdpbusd zmm7, zmm16, zmm17",
"vpdpbusd zmm8, zmm16, zmm17", "vpdpbusd zmm9, zmm16, zmm17",
"vpdpbusd zmm10, zmm16, zmm17", "vpdpbusd zmm11, zmm16, zmm17",
"vpdpbusd zmm12, zmm16, zmm17", "vpdpbusd zmm13, zmm16, zmm17",
"vpdpbusd zmm14, zmm16, zmm17", "vpdpbusd zmm15, zmm16, zmm17",
"dec {n}",
"jnz 2b",
n = inout(reg) iters => _,
out("zmm0") _, out("zmm1") _, out("zmm2") _, out("zmm3") _,
out("zmm4") _, out("zmm5") _, out("zmm6") _, out("zmm7") _,
out("zmm8") _, out("zmm9") _, out("zmm10") _, out("zmm11") _,
out("zmm12") _, out("zmm13") _, out("zmm14") _, out("zmm15") _,
out("zmm16") _, out("zmm17") _,
options(nostack, nomem),
);
}
}
#[target_feature(enable = "avx512f,avx512vl,avx512vnni")]
unsafe fn vnni_ymm_burst(iters: u64) {
unsafe {
std::arch::asm!(
"vpxord ymm0, ymm0, ymm0", "vpxord ymm1, ymm1, ymm1",
"vpxord ymm2, ymm2, ymm2", "vpxord ymm3, ymm3, ymm3",
"vpxord ymm4, ymm4, ymm4", "vpxord ymm5, ymm5, ymm5",
"vpxord ymm6, ymm6, ymm6", "vpxord ymm7, ymm7, ymm7",
"vpxord ymm8, ymm8, ymm8", "vpxord ymm9, ymm9, ymm9",
"vpxord ymm10, ymm10, ymm10", "vpxord ymm11, ymm11, ymm11",
"vpxord ymm12, ymm12, ymm12", "vpxord ymm13, ymm13, ymm13",
"vpxord ymm14, ymm14, ymm14", "vpxord ymm15, ymm15, ymm15",
"vpxord ymm16, ymm16, ymm16", "vpxord ymm17, ymm17, ymm17",
"2:",
"vpdpbusd ymm0, ymm16, ymm17", "vpdpbusd ymm1, ymm16, ymm17",
"vpdpbusd ymm2, ymm16, ymm17", "vpdpbusd ymm3, ymm16, ymm17",
"vpdpbusd ymm4, ymm16, ymm17", "vpdpbusd ymm5, ymm16, ymm17",
"vpdpbusd ymm6, ymm16, ymm17", "vpdpbusd ymm7, ymm16, ymm17",
"vpdpbusd ymm8, ymm16, ymm17", "vpdpbusd ymm9, ymm16, ymm17",
"vpdpbusd ymm10, ymm16, ymm17", "vpdpbusd ymm11, ymm16, ymm17",
"vpdpbusd ymm12, ymm16, ymm17", "vpdpbusd ymm13, ymm16, ymm17",
"vpdpbusd ymm14, ymm16, ymm17", "vpdpbusd ymm15, ymm16, ymm17",
"dec {n}",
"jnz 2b",
n = inout(reg) iters => _,
out("ymm0") _, out("ymm1") _, out("ymm2") _, out("ymm3") _,
out("ymm4") _, out("ymm5") _, out("ymm6") _, out("ymm7") _,
out("ymm8") _, out("ymm9") _, out("ymm10") _, out("ymm11") _,
out("ymm12") _, out("ymm13") _, out("ymm14") _, out("ymm15") _,
out("ymm16") _, out("ymm17") _,
options(nostack, nomem),
);
}
}
#[target_feature(enable = "avx512f,avx512vl,avx512vnni")]
unsafe fn probe_fma_units() -> u8 {
const TRIALS: u32 = 5;
const WINDOW_NS: u128 = 150_000; const MAX_ITERS: u64 = 1 << 22;
let mut iters: u64 = 1 << 10;
loop {
let t = Instant::now();
unsafe { vnni_ymm_burst(iters) };
if t.elapsed().as_nanos() >= WINDOW_NS || iters >= MAX_ITERS {
break;
}
iters <<= 1;
}
let best = |zmm: bool| -> u128 {
let mut best = u128::MAX;
for _ in 0..TRIALS {
let t = Instant::now();
if zmm {
unsafe { vnni_zmm_burst(iters) };
} else {
unsafe { vnni_ymm_burst(iters) };
}
best = best.min(t.elapsed().as_nanos().max(1));
}
best
};
let t_ymm = best(false);
unsafe { vnni_zmm_burst(iters) }; let t_zmm = best(true);
let mac_ratio = 2.0 * (t_ymm as f64) / (t_zmm as f64);
log::debug!(
"avx512_fma_units probe: iters={iters} t_ymm={t_ymm}ns t_zmm={t_zmm}ns mac_ratio={mac_ratio:.2}"
);
if mac_ratio >= 1.5 { 2 } else { 1 }
}