#[cfg(all(
kernel_support = "amx_fp16",
target_arch = "x86_64",
target_os = "linux"
))]
use crate::distance::dot_f16::strided_len;
#[cfg(all(
kernel_support = "amx_fp16",
target_arch = "x86_64",
target_os = "linux"
))]
use half::f16;
#[cfg(all(
kernel_support = "amx_fp16",
target_arch = "x86_64",
target_os = "linux"
))]
unsafe extern "C" {
fn lance_amx_fp16_request_perm() -> i32;
fn lance_amx_dot_f16_batch_16(
query: *const u16,
candidates: *const *const u16,
count: usize,
dim: usize,
out: *mut f32,
);
fn lance_amx_dot_f16_gemm(
data: *const u16,
m: usize,
data_stride: usize,
packed_b: *const u16,
centroids: *const u16,
n: usize,
dim: usize,
out: *mut f32,
out_stride: usize,
);
#[cfg(test)]
fn lance_amx_tilecfg_image(cfg_kind: i32, out: *mut u8) -> i32;
#[cfg(test)]
fn lance_amx_tile_clobber_for_test();
#[cfg(test)]
fn lance_amx_tilecfg_current_for_test(out: *mut u8);
}
#[cfg(all(
test,
kernel_support = "amx_fp16",
target_arch = "x86_64",
target_os = "linux"
))]
pub(crate) unsafe fn clobber_tile_state_for_test() {
unsafe { lance_amx_tile_clobber_for_test() };
}
#[cfg(all(
test,
kernel_support = "amx_fp16",
target_arch = "x86_64",
target_os = "linux"
))]
pub(crate) fn tile_config_is_live_for_test() -> bool {
let mut image = [0u8; 64];
unsafe { lance_amx_tilecfg_current_for_test(image.as_mut_ptr()) };
image[0] != 0
}
#[cfg(all(
test,
kernel_support = "amx_fp16",
target_arch = "x86_64",
target_os = "linux"
))]
pub(crate) const AMX_CFG_SEARCH: i32 = 0;
#[cfg(all(
test,
kernel_support = "amx_fp16",
target_arch = "x86_64",
target_os = "linux"
))]
pub(crate) const AMX_CFG_GEMM: i32 = 1;
#[cfg(all(
test,
kernel_support = "amx_fp16",
target_arch = "x86_64",
target_os = "linux"
))]
pub(crate) fn tilecfg_image(cfg_kind: i32) -> Option<[u8; 64]> {
let mut image = [0u8; 64];
let rc = unsafe { lance_amx_tilecfg_image(cfg_kind, image.as_mut_ptr()) };
(rc == 0).then_some(image)
}
#[cfg(all(
kernel_support = "amx_fp16",
target_arch = "x86_64",
target_os = "linux"
))]
pub(crate) fn amx_supported() -> bool {
static SUPPORTED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*SUPPORTED.get_or_init(|| {
if !detect_amx_fp16() {
return false;
}
unsafe { lance_amx_fp16_request_perm() == 0 }
})
}
#[cfg(all(
kernel_support = "amx_fp16",
target_arch = "x86_64",
target_os = "linux"
))]
fn detect_amx_fp16() -> bool {
use std::arch::x86_64::__cpuid_count;
#[allow(unused_unsafe)]
let leaf7_0 = unsafe { __cpuid_count(7, 0) };
let amx_tile = (leaf7_0.edx & (1 << 24)) != 0;
#[allow(unused_unsafe)]
let leaf7_1 = unsafe { __cpuid_count(7, 1) };
let amx_fp16 = (leaf7_1.eax & (1 << 21)) != 0;
amx_tile && amx_fp16
}
#[cfg(all(
kernel_support = "amx_fp16",
target_arch = "x86_64",
target_os = "linux"
))]
pub(crate) unsafe fn dot_f16_batch_16_amx(
query: &[f16],
candidates: &[&[f16]; 16],
len: usize,
) -> [f32; 16] {
debug_assert!((1..=16).contains(&len), "len ({len}) must be in 1..=16");
let dim = query.len();
let mut rows = [std::ptr::null::<u16>(); 16];
for (i, cand) in candidates.iter().enumerate() {
debug_assert_eq!(
cand.len(),
dim,
"candidate {i} length must equal query length"
);
rows[i] = cand.as_ptr() as *const u16;
}
let mut out = [0f32; 16];
unsafe {
lance_amx_dot_f16_batch_16(
query.as_ptr() as *const u16,
rows.as_ptr(),
len,
dim,
out.as_mut_ptr(),
);
}
out
}
#[cfg(all(
kernel_support = "amx_fp16",
target_arch = "x86_64",
target_os = "linux"
))]
const GEMM_B_BLOCK: usize = 512;
#[cfg(all(
kernel_support = "amx_fp16",
target_arch = "x86_64",
target_os = "linux"
))]
pub(crate) fn packed_centroids_len(n: usize, dim: usize) -> usize {
(dim / 32) * (n / 16) * GEMM_B_BLOCK
}
#[cfg(all(
kernel_support = "amx_fp16",
target_arch = "x86_64",
target_os = "linux"
))]
pub(crate) fn pack_centroids_vnni(centroids: &[f16], n: usize, dim: usize, out: &mut Vec<f16>) {
debug_assert_eq!(n % 16, 0, "n ({n}) must be a multiple of 16");
debug_assert_eq!(
centroids.len(),
n * dim,
"centroids must hold n*dim = {} values",
n * dim
);
out.clear();
out.reserve(packed_centroids_len(n, dim));
for kb in 0..dim / 32 {
for jb in 0..n / 16 {
for k in 0..16 {
for nn in 0..16 {
for p in 0..2 {
out.push(centroids[(jb * 16 + nn) * dim + kb * 32 + 2 * k + p]);
}
}
}
}
}
}
#[cfg(all(
kernel_support = "amx_fp16",
target_arch = "x86_64",
target_os = "linux"
))]
#[allow(clippy::too_many_arguments)]
pub(crate) unsafe fn dot_f16_gemm_amx(
data: &[f16],
m: usize,
data_stride: usize,
packed_b: &[f16],
centroids: &[f16],
n: usize,
dim: usize,
out: &mut [f32],
out_stride: usize,
) {
debug_assert_eq!(m % 32, 0, "m ({m}) must be a multiple of 32");
debug_assert_eq!(n % 32, 0, "n ({n}) must be a multiple of 32");
debug_assert!(
data_stride >= dim,
"data_stride ({data_stride}) < dim ({dim})"
);
debug_assert!(out_stride >= n, "out_stride ({out_stride}) < n ({n})");
debug_assert!(
strided_len(m, data_stride, dim).is_some_and(|need| data.len() >= need),
"data too short"
);
debug_assert!(
strided_len(m, out_stride, n).is_some_and(|need| out.len() >= need),
"out too short"
);
debug_assert_eq!(
Some(centroids.len()),
n.checked_mul(dim),
"centroids must hold n*dim values"
);
debug_assert_eq!(
packed_b.len(),
packed_centroids_len(n, dim),
"packed_b must be pack_centroids_vnni's output for this n and dim"
);
unsafe {
lance_amx_dot_f16_gemm(
data.as_ptr() as *const u16,
m,
data_stride,
packed_b.as_ptr() as *const u16,
centroids.as_ptr() as *const u16,
n,
dim,
out.as_mut_ptr(),
out_stride,
);
}
}