#[cfg(target_arch = "x86_64")]
pub mod simd_x86 {
use core::arch::x86_64::*;
use super::scalar_wht;
#[inline]
#[target_feature(enable = "avx,avx2")]
unsafe fn wht_h1h2_in_ymm(v: __m256d) -> __m256d {
let v_swap1 = _mm256_permute_pd(v, 0b0101);
let h1 = _mm256_permute_pd(_mm256_addsub_pd(v, v_swap1), 0b0101);
let v_swap2 = _mm256_permute2f128_pd(h1, h1, 0x01);
let plus = _mm256_add_pd(h1, v_swap2);
let minus = _mm256_sub_pd(v_swap2, h1);
_mm256_blend_pd(plus, minus, 0b1100)
}
#[target_feature(enable = "avx,avx2")]
pub unsafe fn wht_avx2(x: &mut [f64]) {
let n = x.len();
debug_assert!(n.is_power_of_two(), "WHT requires power-of-2 length");
if n < 8 {
scalar_wht(x);
return;
}
unsafe {
let mut i = 0;
while i + 4 <= n {
let p = x.as_mut_ptr().add(i);
_mm256_storeu_pd(p, wht_h1h2_in_ymm(_mm256_loadu_pd(p)));
i += 4;
}
outer_butterfly_stages(x, 4);
}
}
#[inline]
#[target_feature(enable = "avx,avx2")]
unsafe fn radix16_block(p: *mut f64) {
unsafe {
let r0 = _mm256_loadu_pd(p);
let r1 = _mm256_loadu_pd(p.add(4));
let r2 = _mm256_loadu_pd(p.add(8));
let r3 = _mm256_loadu_pd(p.add(12));
let r0 = wht_h1h2_in_ymm(r0);
let r1 = wht_h1h2_in_ymm(r1);
let r2 = wht_h1h2_in_ymm(r2);
let r3 = wht_h1h2_in_ymm(r3);
let s0 = _mm256_add_pd(r0, r1);
let s1 = _mm256_sub_pd(r0, r1);
let s2 = _mm256_add_pd(r2, r3);
let s3 = _mm256_sub_pd(r2, r3);
let t0 = _mm256_add_pd(s0, s2);
let t1 = _mm256_add_pd(s1, s3);
let t2 = _mm256_sub_pd(s0, s2);
let t3 = _mm256_sub_pd(s1, s3);
_mm256_storeu_pd(p, t0);
_mm256_storeu_pd(p.add(4), t1);
_mm256_storeu_pd(p.add(8), t2);
_mm256_storeu_pd(p.add(12), t3);
}
}
#[inline]
#[target_feature(enable = "avx,avx2")]
unsafe fn outer_butterfly_stages(x: &mut [f64], start_h: usize) {
unsafe {
let n = x.len();
let mut h = start_h;
while h < n {
let stride = 2 * h;
let mut block = 0;
while block < n {
let mut j = 0;
while j < h {
let pa = x.as_mut_ptr().add(block + j);
let pb = x.as_mut_ptr().add(block + j + h);
let a = _mm256_loadu_pd(pa);
let b = _mm256_loadu_pd(pb);
_mm256_storeu_pd(pa, _mm256_add_pd(a, b));
_mm256_storeu_pd(pb, _mm256_sub_pd(a, b));
j += 4;
}
block += stride;
}
h *= 2;
}
}
}
#[inline]
#[target_feature(enable = "avx,avx2")]
unsafe fn outer_butterfly_stages_4x(x: &mut [f64], start_h: usize) {
unsafe {
let n = x.len();
let mut h = start_h;
while h < n {
let stride = 2 * h;
let mut block = 0;
while block < n {
let mut j = 0;
while j < h {
let p = x.as_mut_ptr().add(block + j);
let q = p.add(h);
let a0 = _mm256_loadu_pd(p);
let a1 = _mm256_loadu_pd(p.add(4));
let a2 = _mm256_loadu_pd(p.add(8));
let a3 = _mm256_loadu_pd(p.add(12));
let b0 = _mm256_loadu_pd(q);
let b1 = _mm256_loadu_pd(q.add(4));
let b2 = _mm256_loadu_pd(q.add(8));
let b3 = _mm256_loadu_pd(q.add(12));
_mm256_storeu_pd(p, _mm256_add_pd(a0, b0));
_mm256_storeu_pd(p.add(4), _mm256_add_pd(a1, b1));
_mm256_storeu_pd(p.add(8), _mm256_add_pd(a2, b2));
_mm256_storeu_pd(p.add(12), _mm256_add_pd(a3, b3));
_mm256_storeu_pd(q, _mm256_sub_pd(a0, b0));
_mm256_storeu_pd(q.add(4), _mm256_sub_pd(a1, b1));
_mm256_storeu_pd(q.add(8), _mm256_sub_pd(a2, b2));
_mm256_storeu_pd(q.add(12), _mm256_sub_pd(a3, b3));
j += 16;
}
block += stride;
}
h *= 2;
}
}
}
#[target_feature(enable = "avx,avx2")]
pub unsafe fn wht_avx2_radix16_4x(x: &mut [f64]) {
let n = x.len();
debug_assert!(n.is_power_of_two(), "WHT requires power-of-2 length");
if n < 8 {
scalar_wht(x);
return;
}
unsafe {
if n == 8 {
let p = x.as_mut_ptr();
let v0 = wht_h1h2_in_ymm(_mm256_loadu_pd(p));
let v1 = wht_h1h2_in_ymm(_mm256_loadu_pd(p.add(4)));
_mm256_storeu_pd(p, _mm256_add_pd(v0, v1));
_mm256_storeu_pd(p.add(4), _mm256_sub_pd(v0, v1));
return;
}
let mut i = 0;
while i + 16 <= n {
radix16_block(x.as_mut_ptr().add(i));
i += 16;
}
outer_butterfly_stages_4x(x, 16);
}
}
}
#[cfg(target_arch = "aarch64")]
pub mod simd_arm {
use core::arch::aarch64::*;
use super::scalar_wht;
#[inline]
#[target_feature(enable = "neon")]
unsafe fn wht_h1h2_block_neon(p: *mut f64) -> [float64x2_t; 2] {
unsafe {
let val = vld2q_f64(p);
let e = vaddq_f64(val.0, val.1);
let o = vsubq_f64(val.0, val.1);
let lo = vuzp1q_f64(e, o);
let hi = vuzp2q_f64(e, o);
[vaddq_f64(lo, hi), vsubq_f64(lo, hi)]
}
}
#[target_feature(enable = "neon")]
pub unsafe fn wht_neon(x: &mut [f64]) {
let n = x.len();
debug_assert!(n.is_power_of_two(), "WHT requires power-of-2 length");
if n < 8 {
scalar_wht(x);
return;
}
unsafe {
let mut i = 0;
while i + 4 <= n {
let p = x.as_mut_ptr().add(i);
let [r0, r1] = wht_h1h2_block_neon(p);
vst1q_f64(p, r0);
vst1q_f64(p.add(2), r1);
i += 4;
}
outer_butterfly_stages(x, 4);
}
}
#[inline]
#[target_feature(enable = "neon")]
unsafe fn radix8_block(p: *mut f64) {
unsafe {
let [r0, r1] = wht_h1h2_block_neon(p);
let [r2, r3] = wht_h1h2_block_neon(p.add(4));
let t0 = vaddq_f64(r0, r2);
let t1 = vaddq_f64(r1, r3);
let t2 = vsubq_f64(r0, r2);
let t3 = vsubq_f64(r1, r3);
vst1q_f64(p, t0);
vst1q_f64(p.add(2), t1);
vst1q_f64(p.add(4), t2);
vst1q_f64(p.add(6), t3);
}
}
#[inline]
#[target_feature(enable = "neon")]
unsafe fn radix16_block(p: *mut f64) {
unsafe {
let [r0, r1] = wht_h1h2_block_neon(p);
let [r2, r3] = wht_h1h2_block_neon(p.add(4));
let [r4, r5] = wht_h1h2_block_neon(p.add(8));
let [r6, r7] = wht_h1h2_block_neon(p.add(12));
let s0 = vaddq_f64(r0, r2);
let s1 = vaddq_f64(r1, r3);
let s2 = vsubq_f64(r0, r2);
let s3 = vsubq_f64(r1, r3);
let s4 = vaddq_f64(r4, r6);
let s5 = vaddq_f64(r5, r7);
let s6 = vsubq_f64(r4, r6);
let s7 = vsubq_f64(r5, r7);
let t0 = vaddq_f64(s0, s4);
let t1 = vaddq_f64(s1, s5);
let t2 = vaddq_f64(s2, s6);
let t3 = vaddq_f64(s3, s7);
let t4 = vsubq_f64(s0, s4);
let t5 = vsubq_f64(s1, s5);
let t6 = vsubq_f64(s2, s6);
let t7 = vsubq_f64(s3, s7);
vst1q_f64(p, t0);
vst1q_f64(p.add(2), t1);
vst1q_f64(p.add(4), t2);
vst1q_f64(p.add(6), t3);
vst1q_f64(p.add(8), t4);
vst1q_f64(p.add(10), t5);
vst1q_f64(p.add(12), t6);
vst1q_f64(p.add(14), t7);
}
}
#[inline]
#[target_feature(enable = "neon")]
unsafe fn outer_butterfly_stages(x: &mut [f64], start_h: usize) {
unsafe {
let n = x.len();
let mut h = start_h;
while h < n {
let stride = 2 * h;
let mut block = 0;
while block < n {
let mut j = 0;
while j < h {
let pa = x.as_mut_ptr().add(block + j);
let pb = x.as_mut_ptr().add(block + j + h);
let a = vld1q_f64(pa);
let b = vld1q_f64(pb);
vst1q_f64(pa, vaddq_f64(a, b));
vst1q_f64(pb, vsubq_f64(a, b));
j += 2;
}
block += stride;
}
h *= 2;
}
}
}
#[inline]
#[target_feature(enable = "neon")]
unsafe fn outer_butterfly_stages_4x(x: &mut [f64], start_h: usize) {
unsafe {
let n = x.len();
let mut h = start_h;
while h < n {
let stride = 2 * h;
let mut block = 0;
while block < n {
let mut j = 0;
while j < h {
let p = x.as_mut_ptr().add(block + j);
let q = p.add(h);
let a0 = vld1q_f64(p);
let a1 = vld1q_f64(p.add(2));
let a2 = vld1q_f64(p.add(4));
let a3 = vld1q_f64(p.add(6));
let b0 = vld1q_f64(q);
let b1 = vld1q_f64(q.add(2));
let b2 = vld1q_f64(q.add(4));
let b3 = vld1q_f64(q.add(6));
vst1q_f64(p, vaddq_f64(a0, b0));
vst1q_f64(p.add(2), vaddq_f64(a1, b1));
vst1q_f64(p.add(4), vaddq_f64(a2, b2));
vst1q_f64(p.add(6), vaddq_f64(a3, b3));
vst1q_f64(q, vsubq_f64(a0, b0));
vst1q_f64(q.add(2), vsubq_f64(a1, b1));
vst1q_f64(q.add(4), vsubq_f64(a2, b2));
vst1q_f64(q.add(6), vsubq_f64(a3, b3));
j += 8;
}
block += stride;
}
h *= 2;
}
}
}
#[target_feature(enable = "neon")]
pub unsafe fn wht_neon_radix16_4x(x: &mut [f64]) {
let n = x.len();
debug_assert!(n.is_power_of_two(), "WHT requires power-of-2 length");
if n < 8 {
scalar_wht(x);
return;
}
unsafe {
if n == 8 {
radix8_block(x.as_mut_ptr());
return;
}
let mut i = 0;
while i + 16 <= n {
radix16_block(x.as_mut_ptr().add(i));
i += 16;
}
outer_butterfly_stages_4x(x, 16);
}
}
}
pub(crate) fn scalar_wht(x: &mut [f64]) {
let n = x.len();
let mut h = 1;
while h < n {
for i in (0..n).step_by(h * 2) {
for j in i..i + h {
let a = x[j];
let b = x[j + h];
x[j] = a + b;
x[j + h] = a - b;
}
}
h *= 2;
}
}
#[inline]
pub fn wht_dispatch(x: &mut [f64]) {
#[cfg(target_arch = "x86_64")]
{
if std::is_x86_feature_detected!("avx2") {
unsafe { simd_x86::wht_avx2_radix16_4x(x) };
return;
}
scalar_wht(x);
}
#[cfg(target_arch = "aarch64")]
{
if std::arch::is_aarch64_feature_detected!("neon") {
unsafe { simd_arm::wht_neon_radix16_4x(x) };
return;
}
scalar_wht(x);
}
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
scalar_wht(x);
}
#[cfg(test)]
mod tests {
use rand::prelude::StdRng;
use rand::{RngExt, SeedableRng};
use super::*;
use crate::quantization::turboquant::rotation::in_place_walsh_hadamard_transform;
const SIZES: &[usize] = &[
1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192,
];
const SEEDS: &[u64] = &[0xCAFE, 0xBEEF, 0xDEAD_BEEF_F00D];
#[allow(unused_mut)]
#[allow(clippy::type_complexity)]
fn simd_variants() -> Vec<(&'static str, fn(&mut [f64]))> {
let mut v: Vec<(&'static str, fn(&mut [f64]))> = Vec::new();
#[cfg(target_arch = "x86_64")]
{
if std::is_x86_feature_detected!("avx2") {
v.push(("wht_avx2", |x| unsafe { simd_x86::wht_avx2(x) }));
v.push(("wht_avx2_radix16_4x", |x| unsafe {
simd_x86::wht_avx2_radix16_4x(x)
}));
}
}
#[cfg(target_arch = "aarch64")]
{
if std::arch::is_aarch64_feature_detected!("neon") {
v.push(("wht_neon", |x| unsafe { simd_arm::wht_neon(x) }));
v.push(("wht_neon_radix16_4x", |x| unsafe {
simd_arm::wht_neon_radix16_4x(x)
}));
}
}
v
}
fn assert_bit_equal(scalar: &[f64], simd: &[f64], label: &str) {
for (i, (s, r)) in scalar.iter().zip(simd.iter()).enumerate() {
assert_eq!(
s.to_bits(),
r.to_bits(),
"{label}: mismatch at i={i}: scalar={s}, simd={r}",
);
}
}
#[test]
fn simd_wht_matches_scalar_bit_equal() {
let variants = simd_variants();
if variants.is_empty() {
eprintln!("skipping: no SIMD variants available on this CPU");
return;
}
for &seed in SEEDS {
let mut rng = StdRng::seed_from_u64(seed);
for &n in SIZES {
let input: Vec<f64> = (0..n).map(|_| rng.random_range(-1.0f64..1.0)).collect();
let mut scalar = input.clone();
in_place_walsh_hadamard_transform(&mut scalar);
for (name, f) in &variants {
let mut simd = input.clone();
f(&mut simd);
assert_bit_equal(&scalar, &simd, &format!("{name} at seed=0x{seed:X}, n={n}"));
}
}
}
}
#[test]
fn simd_wht_matches_scalar_edge_values() {
let variants = simd_variants();
if variants.is_empty() {
eprintln!("skipping: no SIMD variants available on this CPU");
return;
}
#[allow(clippy::type_complexity)]
let cases: &[(&str, fn(usize) -> Vec<f64>)] = &[
("zeros", |n| vec![0.0; n]),
("ones", |n| vec![1.0; n]),
("neg_ones", |n| vec![-1.0; n]),
("alternating", |n| {
(0..n)
.map(|i| if i % 2 == 0 { 1.0 } else { -1.0 })
.collect()
}),
("huge", |n| vec![1e150; n]),
("tiny", |n| vec![1e-300; n]),
("denormals", |n| vec![f64::MIN_POSITIVE / 2.0; n]),
("mixed_sign_huge", |n| {
(0..n)
.map(|i| if i % 2 == 0 { 1e150 } else { -1e150 })
.collect()
}),
];
for (case_name, build) in cases {
for &n in SIZES {
let input = build(n);
let mut scalar = input.clone();
in_place_walsh_hadamard_transform(&mut scalar);
for (variant_name, f) in &variants {
let mut simd = input.clone();
f(&mut simd);
assert_bit_equal(
&scalar,
&simd,
&format!("{variant_name} on '{case_name}' at n={n}"),
);
}
}
}
}
#[test]
fn wht_dispatch_matches_scalar_bit_equal() {
let mut rng = StdRng::seed_from_u64(0x1234_5678);
for &n in SIZES {
let input: Vec<f64> = (0..n).map(|_| rng.random_range(-1.0f64..1.0)).collect();
let mut scalar = input.clone();
in_place_walsh_hadamard_transform(&mut scalar);
let mut dispatched = input.clone();
wht_dispatch(&mut dispatched);
assert_bit_equal(&scalar, &dispatched, &format!("wht_dispatch at n={n}"));
}
}
#[test]
fn scalar_wht_fallback_matches_reference() {
let mut rng = StdRng::seed_from_u64(0xFEED_FACE);
for &n in SIZES {
let input: Vec<f64> = (0..n).map(|_| rng.random_range(-1.0f64..1.0)).collect();
let mut reference = input.clone();
in_place_walsh_hadamard_transform(&mut reference);
let mut fallback = input.clone();
scalar_wht(&mut fallback);
assert_bit_equal(&reference, &fallback, &format!("scalar_wht at n={n}"));
}
}
}