Merge commit 'd803bfe2b1fe7f5e219e50ac20d6801a0a58ac75' as 'vendor/ruvector'

This commit is contained in:
ruv
2026-02-28 14:39:40 -05:00
7854 changed files with 3522914 additions and 0 deletions
@@ -0,0 +1,481 @@
//! CPU backend with portable SIMD optimizations
use super::Backend;
use crate::config::ActivationType;
use ndarray::Array2;
use std::sync::OnceLock;
#[cfg(target_arch = "x86_64")]
use std::arch::x86_64::*;
#[cfg(target_arch = "aarch64")]
use std::arch::aarch64::*;
/// Cached SIMD feature detection for x86_64
#[cfg(target_arch = "x86_64")]
static SIMD_FEATURES: OnceLock<SimdFeatures> = OnceLock::new();
#[cfg(target_arch = "x86_64")]
#[derive(Debug, Clone, Copy)]
struct SimdFeatures {
has_avx2: bool,
has_sse41: bool,
has_fma: bool,
}
#[cfg(target_arch = "x86_64")]
fn get_simd_features() -> SimdFeatures {
*SIMD_FEATURES.get_or_init(|| SimdFeatures {
has_avx2: is_x86_feature_detected!("avx2"),
has_sse41: is_x86_feature_detected!("sse4.1"),
has_fma: is_x86_feature_detected!("fma"),
})
}
/// CPU backend using portable SIMD
pub struct CpuBackend;
impl Backend for CpuBackend {
fn dot_product(&self, a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
#[cfg(target_arch = "x86_64")]
{
let features = get_simd_features();
if features.has_avx2 {
return unsafe { dot_product_avx2(a, b) };
} else if features.has_sse41 {
return unsafe { dot_product_sse(a, b) };
}
return dot_product_scalar(a, b);
}
#[cfg(target_arch = "aarch64")]
return unsafe { dot_product_neon(a, b) };
// Fallback scalar
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
dot_product_scalar(a, b)
}
fn sparse_matmul(&self, matrix: &Array2<f32>, input: &[f32], rows: &[usize]) -> Vec<f32> {
let mut output = Vec::with_capacity(rows.len());
for &row_idx in rows {
let row = matrix.row(row_idx);
let dot = self.dot_product(row.as_slice().unwrap(), input);
output.push(dot);
}
output
}
fn sparse_matmul_accumulate(
&self,
matrix: &Array2<f32>,
input: &[f32],
cols: &[usize],
output: &mut [f32],
) {
for (i, &col_idx) in cols.iter().enumerate() {
let col = matrix.column(col_idx);
let scalar = input[i];
// Column view may not be contiguous, iterate element-by-element
for (j, &val) in col.iter().enumerate() {
output[j] += val * scalar;
}
}
}
fn activation(&self, data: &mut [f32], activation_type: ActivationType) {
#[cfg(target_arch = "x86_64")]
let features = get_simd_features();
match activation_type {
ActivationType::Relu => {
#[cfg(target_arch = "x86_64")]
if features.has_avx2 {
return unsafe { relu_avx2(data) };
}
relu_scalar(data);
}
ActivationType::Gelu => {
#[cfg(target_arch = "x86_64")]
if features.has_avx2 {
return unsafe { gelu_avx2(data) };
}
gelu_scalar(data);
}
ActivationType::Silu | ActivationType::Swish => {
#[cfg(target_arch = "x86_64")]
if features.has_avx2 {
return unsafe { silu_avx2(data) };
}
silu_scalar(data);
}
ActivationType::Identity => { /* no-op */ }
}
}
fn add(&self, a: &mut [f32], b: &[f32]) {
debug_assert_eq!(a.len(), b.len());
#[cfg(target_arch = "x86_64")]
if get_simd_features().has_avx2 {
return unsafe { add_avx2(a, b) };
}
for (x, y) in a.iter_mut().zip(b.iter()) {
*x += y;
}
}
fn axpy(&self, a: &mut [f32], b: &[f32], scalar: f32) {
debug_assert_eq!(a.len(), b.len());
#[cfg(target_arch = "x86_64")]
if get_simd_features().has_avx2 {
return unsafe { axpy_avx2(a, b, scalar) };
}
for (x, y) in a.iter_mut().zip(b.iter()) {
*x += y * scalar;
}
}
fn name(&self) -> &'static str {
#[cfg(target_arch = "x86_64")]
{
let features = get_simd_features();
if features.has_avx2 {
return "CPU-AVX2";
} else if features.has_sse41 {
return "CPU-SSE4.1";
}
return "CPU-Scalar";
}
#[cfg(target_arch = "aarch64")]
return "CPU-NEON";
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
"CPU-Scalar"
}
fn simd_width(&self) -> usize {
#[cfg(target_arch = "x86_64")]
{
let features = get_simd_features();
if features.has_avx2 {
return 8;
}
if features.has_sse41 {
return 4;
}
return 1;
}
#[cfg(target_arch = "aarch64")]
return 4;
#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
1
}
}
// ============ AVX2 Implementations ============
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn dot_product_avx2(a: &[f32], b: &[f32]) -> f32 {
let n = a.len();
let chunks = n / 8;
let mut sum = _mm256_setzero_ps();
for i in 0..chunks {
let va = _mm256_loadu_ps(a.as_ptr().add(i * 8));
let vb = _mm256_loadu_ps(b.as_ptr().add(i * 8));
sum = _mm256_fmadd_ps(va, vb, sum);
}
// Horizontal sum
let sum128 = _mm_add_ps(_mm256_extractf128_ps(sum, 0), _mm256_extractf128_ps(sum, 1));
let sum64 = _mm_add_ps(sum128, _mm_movehl_ps(sum128, sum128));
let sum32 = _mm_add_ss(sum64, _mm_shuffle_ps(sum64, sum64, 1));
let mut result = _mm_cvtss_f32(sum32);
// Handle remainder
for i in (chunks * 8)..n {
result += a[i] * b[i];
}
result
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn relu_avx2(data: &mut [f32]) {
let zero = _mm256_setzero_ps();
let chunks = data.len() / 8;
for i in 0..chunks {
let ptr = data.as_mut_ptr().add(i * 8);
let v = _mm256_loadu_ps(ptr);
let result = _mm256_max_ps(v, zero);
_mm256_storeu_ps(ptr, result);
}
// Handle remainder
for i in (chunks * 8)..data.len() {
data[i] = data[i].max(0.0);
}
}
/// SIMD GELU using polynomial approximation
/// GELU(x) ≈ 0.5 * x * (1 + tanh(sqrt(2/π) * (x + 0.044715 * x³)))
/// Using fast tanh approximation for SIMD
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2", enable = "fma")]
unsafe fn gelu_avx2(data: &mut [f32]) {
let chunks = data.len() / 8;
// Constants for GELU approximation
let half = _mm256_set1_ps(0.5);
let one = _mm256_set1_ps(1.0);
let sqrt_2_over_pi = _mm256_set1_ps(0.7978845608); // sqrt(2/π)
let coef = _mm256_set1_ps(0.044715);
// Constants for fast tanh approximation: tanh(x) ≈ x * (27 + x²) / (27 + 9x²)
let c27 = _mm256_set1_ps(27.0);
let c9 = _mm256_set1_ps(9.0);
for i in 0..chunks {
let ptr = data.as_mut_ptr().add(i * 8);
let x = _mm256_loadu_ps(ptr);
// x³
let x2 = _mm256_mul_ps(x, x);
let x3 = _mm256_mul_ps(x2, x);
// inner = sqrt(2/π) * (x + 0.044715 * x³)
let inner = _mm256_mul_ps(sqrt_2_over_pi, _mm256_fmadd_ps(coef, x3, x));
// Fast tanh approximation
let inner2 = _mm256_mul_ps(inner, inner);
let num = _mm256_fmadd_ps(inner2, one, c27); // 27 + inner²
let den = _mm256_fmadd_ps(inner2, c9, c27); // 27 + 9*inner²
let tanh_approx = _mm256_mul_ps(inner, _mm256_div_ps(num, den));
// 0.5 * x * (1 + tanh)
let result = _mm256_mul_ps(half, _mm256_mul_ps(x, _mm256_add_ps(one, tanh_approx)));
_mm256_storeu_ps(ptr, result);
}
// Handle remainder with scalar
for i in (chunks * 8)..data.len() {
let x = data[i];
let x3 = x * x * x;
let inner = 0.7978845608 * (x + 0.044715 * x3);
data[i] = 0.5 * x * (1.0 + inner.tanh());
}
}
/// SIMD SiLU (Swish) using fast sigmoid approximation
/// SiLU(x) = x * sigmoid(x) = x / (1 + exp(-x))
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2", enable = "fma")]
unsafe fn silu_avx2(data: &mut [f32]) {
let chunks = data.len() / 8;
// For sigmoid, use: 1/(1+e^-x) ≈ 0.5 + 0.5*tanh(x/2)
let half = _mm256_set1_ps(0.5);
let c27 = _mm256_set1_ps(27.0);
let c9 = _mm256_set1_ps(9.0);
let one = _mm256_set1_ps(1.0);
for i in 0..chunks {
let ptr = data.as_mut_ptr().add(i * 8);
let x = _mm256_loadu_ps(ptr);
// Use sigmoid(x) = 0.5 + 0.5 * tanh(x/2)
let x_half = _mm256_mul_ps(x, half);
// Fast tanh(x/2)
let xh2 = _mm256_mul_ps(x_half, x_half);
let num = _mm256_fmadd_ps(xh2, one, c27);
let den = _mm256_fmadd_ps(xh2, c9, c27);
let tanh_approx = _mm256_mul_ps(x_half, _mm256_div_ps(num, den));
// sigmoid = 0.5 + 0.5 * tanh
let sigmoid = _mm256_fmadd_ps(half, tanh_approx, half);
// silu = x * sigmoid
let result = _mm256_mul_ps(x, sigmoid);
_mm256_storeu_ps(ptr, result);
}
// Handle remainder with scalar
for i in (chunks * 8)..data.len() {
let x = data[i];
data[i] = x / (1.0 + (-x).exp());
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn add_avx2(a: &mut [f32], b: &[f32]) {
let chunks = a.len() / 8;
for i in 0..chunks {
let pa = a.as_mut_ptr().add(i * 8);
let pb = b.as_ptr().add(i * 8);
let va = _mm256_loadu_ps(pa);
let vb = _mm256_loadu_ps(pb);
_mm256_storeu_ps(pa, _mm256_add_ps(va, vb));
}
for i in (chunks * 8)..a.len() {
a[i] += b[i];
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn axpy_avx2(a: &mut [f32], b: &[f32], scalar: f32) {
let vs = _mm256_set1_ps(scalar);
let chunks = a.len() / 8;
for i in 0..chunks {
let pa = a.as_mut_ptr().add(i * 8);
let pb = b.as_ptr().add(i * 8);
let va = _mm256_loadu_ps(pa);
let vb = _mm256_loadu_ps(pb);
let result = _mm256_fmadd_ps(vb, vs, va);
_mm256_storeu_ps(pa, result);
}
for i in (chunks * 8)..a.len() {
a[i] += b[i] * scalar;
}
}
// ============ SSE4.1 Implementations ============
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "sse4.1")]
unsafe fn dot_product_sse(a: &[f32], b: &[f32]) -> f32 {
let n = a.len();
let chunks = n / 4;
let mut sum = _mm_setzero_ps();
for i in 0..chunks {
let va = _mm_loadu_ps(a.as_ptr().add(i * 4));
let vb = _mm_loadu_ps(b.as_ptr().add(i * 4));
sum = _mm_add_ps(sum, _mm_mul_ps(va, vb));
}
// Horizontal sum
let sum2 = _mm_add_ps(sum, _mm_movehl_ps(sum, sum));
let sum1 = _mm_add_ss(sum2, _mm_shuffle_ps(sum2, sum2, 1));
let mut result = _mm_cvtss_f32(sum1);
for i in (chunks * 4)..n {
result += a[i] * b[i];
}
result
}
// ============ NEON Implementations (ARM) ============
#[cfg(target_arch = "aarch64")]
unsafe fn dot_product_neon(a: &[f32], b: &[f32]) -> f32 {
let n = a.len();
let chunks = n / 4;
let mut sum = vdupq_n_f32(0.0);
for i in 0..chunks {
let va = vld1q_f32(a.as_ptr().add(i * 4));
let vb = vld1q_f32(b.as_ptr().add(i * 4));
sum = vfmaq_f32(sum, va, vb);
}
// Horizontal sum
let mut result = vaddvq_f32(sum);
for i in (chunks * 4)..n {
result += a[i] * b[i];
}
result
}
// ============ Scalar Fallbacks ============
fn dot_product_scalar(a: &[f32], b: &[f32]) -> f32 {
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}
fn relu_scalar(data: &mut [f32]) {
for x in data.iter_mut() {
*x = x.max(0.0);
}
}
fn gelu_scalar(data: &mut [f32]) {
const SQRT_2_OVER_PI: f32 = 0.7978845608;
const GELU_COEF: f32 = 0.044715;
for x in data.iter_mut() {
let x3 = *x * *x * *x;
let inner = SQRT_2_OVER_PI * (*x + GELU_COEF * x3);
*x = 0.5 * *x * (1.0 + inner.tanh());
}
}
fn silu_scalar(data: &mut [f32]) {
for x in data.iter_mut() {
*x = *x / (1.0 + (-*x).exp());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dot_product() {
let backend = CpuBackend;
let a = vec![1.0, 2.0, 3.0, 4.0];
let b = vec![2.0, 3.0, 4.0, 5.0];
let result = backend.dot_product(&a, &b);
assert!((result - 40.0).abs() < 1e-5);
}
#[test]
fn test_relu() {
let backend = CpuBackend;
let mut data = vec![-1.0, 0.0, 1.0, 2.0];
backend.activation(&mut data, ActivationType::Relu);
assert_eq!(data, vec![0.0, 0.0, 1.0, 2.0]);
}
#[test]
fn test_add() {
let backend = CpuBackend;
let mut a = vec![1.0, 2.0, 3.0, 4.0];
let b = vec![5.0, 6.0, 7.0, 8.0];
backend.add(&mut a, &b);
assert_eq!(a, vec![6.0, 8.0, 10.0, 12.0]);
}
#[test]
fn test_axpy() {
let backend = CpuBackend;
let mut a = vec![1.0, 2.0, 3.0, 4.0];
let b = vec![1.0, 1.0, 1.0, 1.0];
backend.axpy(&mut a, &b, 2.0);
assert_eq!(a, vec![3.0, 4.0, 5.0, 6.0]);
}
}
@@ -0,0 +1,60 @@
//! Backend abstraction for hardware-specific optimizations
use crate::config::ActivationType;
use ndarray::Array2;
pub mod cpu;
pub mod wasm;
#[cfg(feature = "npu")]
pub mod npu;
/// Backend trait for SIMD/vectorized operations
pub trait Backend: Send + Sync {
/// Dot product of two vectors
fn dot_product(&self, a: &[f32], b: &[f32]) -> f32;
/// Sparse matrix-vector multiplication
/// Only computes rows specified in `rows`
fn sparse_matmul(&self, matrix: &Array2<f32>, input: &[f32], rows: &[usize]) -> Vec<f32>;
/// Sparse matrix-vector multiplication with column-major accumulation
fn sparse_matmul_accumulate(
&self,
matrix: &Array2<f32>,
input: &[f32],
cols: &[usize],
output: &mut [f32],
);
/// Apply activation function in-place
fn activation(&self, data: &mut [f32], activation_type: ActivationType);
/// Vectorized addition
fn add(&self, a: &mut [f32], b: &[f32]);
/// Vectorized multiply-add: a[i] += b[i] * scalar
fn axpy(&self, a: &mut [f32], b: &[f32], scalar: f32);
/// Backend name for debugging
fn name(&self) -> &'static str;
/// SIMD width (number of f32s per vector register)
fn simd_width(&self) -> usize;
}
/// Get the best available backend for the current platform
pub fn get_backend() -> Box<dyn Backend> {
#[cfg(target_arch = "wasm32")]
return Box::new(wasm::WasmBackend);
#[cfg(not(target_arch = "wasm32"))]
{
#[cfg(feature = "npu")]
if npu::is_available() {
return Box::new(npu::NpuBackend::new());
}
Box::new(cpu::CpuBackend)
}
}
@@ -0,0 +1,86 @@
//! NPU (Neural Processing Unit) backend - placeholder for future hardware acceleration
use crate::config::ActivationType;
use ndarray::Array2;
use super::Backend;
/// Check if NPU hardware is available
pub fn is_available() -> bool {
false
}
/// NPU Backend for hardware-accelerated inference
pub struct NpuBackend;
impl NpuBackend {
pub fn new() -> Self {
Self
}
}
impl Backend for NpuBackend {
fn dot_product(&self, a: &[f32], b: &[f32]) -> f32 {
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}
fn sparse_matmul(&self, matrix: &Array2<f32>, input: &[f32], rows: &[usize]) -> Vec<f32> {
// Fallback to CPU implementation
rows.iter()
.map(|&r| {
matrix
.row(r)
.iter()
.zip(input.iter())
.map(|(m, i)| m * i)
.sum()
})
.collect()
}
fn sparse_matmul_accumulate(
&self,
matrix: &Array2<f32>,
input: &[f32],
cols: &[usize],
output: &mut [f32],
) {
for &c in cols {
let val = input[c];
for (i, o) in output.iter_mut().enumerate() {
*o += matrix[[i, c]] * val;
}
}
}
fn activation(&self, data: &mut [f32], activation_type: ActivationType) {
for x in data.iter_mut() {
*x = match activation_type {
ActivationType::ReLU => x.max(0.0),
ActivationType::Sigmoid => 1.0 / (1.0 + (-*x).exp()),
ActivationType::Tanh => x.tanh(),
ActivationType::None => *x,
};
}
}
fn add(&self, a: &mut [f32], b: &[f32]) {
for (x, y) in a.iter_mut().zip(b.iter()) {
*x += y;
}
}
fn axpy(&self, a: &mut [f32], b: &[f32], scalar: f32) {
for (x, y) in a.iter_mut().zip(b.iter()) {
*x += y * scalar;
}
}
fn name(&self) -> &'static str {
"npu"
}
fn simd_width(&self) -> usize {
1
}
}
@@ -0,0 +1,226 @@
//! WebAssembly backend with portable SIMD
use super::Backend;
use crate::config::ActivationType;
use ndarray::Array2;
#[cfg(target_arch = "wasm32")]
use std::arch::wasm32::*;
/// WASM backend using wasm32 SIMD instructions
pub struct WasmBackend;
impl Backend for WasmBackend {
fn dot_product(&self, a: &[f32], b: &[f32]) -> f32 {
debug_assert_eq!(a.len(), b.len());
#[cfg(target_arch = "wasm32")]
return dot_product_wasm_simd(a, b);
#[cfg(not(target_arch = "wasm32"))]
dot_product_scalar(a, b)
}
fn sparse_matmul(&self, matrix: &Array2<f32>, input: &[f32], rows: &[usize]) -> Vec<f32> {
rows.iter()
.map(|&row_idx| {
let row = matrix.row(row_idx);
self.dot_product(row.as_slice().unwrap(), input)
})
.collect()
}
fn sparse_matmul_accumulate(
&self,
matrix: &Array2<f32>,
input: &[f32],
cols: &[usize],
output: &mut [f32],
) {
for (i, &col_idx) in cols.iter().enumerate() {
let col = matrix.column(col_idx);
self.axpy(output, col.as_slice().unwrap(), input[i]);
}
}
fn activation(&self, data: &mut [f32], activation_type: ActivationType) {
match activation_type {
ActivationType::Relu => {
#[cfg(target_arch = "wasm32")]
relu_wasm_simd(data);
#[cfg(not(target_arch = "wasm32"))]
relu_scalar(data);
}
ActivationType::Gelu => gelu_scalar(data),
ActivationType::Silu | ActivationType::Swish => silu_scalar(data),
ActivationType::Identity => { /* no-op */ }
}
}
fn add(&self, a: &mut [f32], b: &[f32]) {
#[cfg(target_arch = "wasm32")]
add_wasm_simd(a, b);
#[cfg(not(target_arch = "wasm32"))]
for (x, y) in a.iter_mut().zip(b.iter()) {
*x += y;
}
}
fn axpy(&self, a: &mut [f32], b: &[f32], scalar: f32) {
#[cfg(target_arch = "wasm32")]
axpy_wasm_simd(a, b, scalar);
#[cfg(not(target_arch = "wasm32"))]
for (x, y) in a.iter_mut().zip(b.iter()) {
*x += y * scalar;
}
}
fn name(&self) -> &'static str {
"WASM-SIMD"
}
fn simd_width(&self) -> usize {
4 // 128-bit SIMD = 4 x f32
}
}
// ============ WASM SIMD Implementations ============
#[cfg(target_arch = "wasm32")]
fn dot_product_wasm_simd(a: &[f32], b: &[f32]) -> f32 {
let n = a.len();
let chunks = n / 4;
let mut sum = f32x4_splat(0.0);
for i in 0..chunks {
let va = v128_load(a[i * 4..].as_ptr() as *const v128);
let vb = v128_load(b[i * 4..].as_ptr() as *const v128);
sum = f32x4_add(sum, f32x4_mul(va, vb));
}
// Horizontal sum
let sum_arr = [
f32x4_extract_lane::<0>(sum),
f32x4_extract_lane::<1>(sum),
f32x4_extract_lane::<2>(sum),
f32x4_extract_lane::<3>(sum),
];
let mut result: f32 = sum_arr.iter().sum();
// Handle remainder
for i in (chunks * 4)..n {
result += a[i] * b[i];
}
result
}
#[cfg(target_arch = "wasm32")]
fn relu_wasm_simd(data: &mut [f32]) {
let zero = f32x4_splat(0.0);
let chunks = data.len() / 4;
for i in 0..chunks {
let ptr = data[i * 4..].as_ptr() as *const v128;
let v = v128_load(ptr);
let result = f32x4_max(v, zero);
v128_store(data[i * 4..].as_mut_ptr() as *mut v128, result);
}
for i in (chunks * 4)..data.len() {
data[i] = data[i].max(0.0);
}
}
#[cfg(target_arch = "wasm32")]
fn add_wasm_simd(a: &mut [f32], b: &[f32]) {
let chunks = a.len() / 4;
for i in 0..chunks {
let pa = a[i * 4..].as_ptr() as *const v128;
let pb = b[i * 4..].as_ptr() as *const v128;
let va = v128_load(pa);
let vb = v128_load(pb);
let result = f32x4_add(va, vb);
v128_store(a[i * 4..].as_mut_ptr() as *mut v128, result);
}
for i in (chunks * 4)..a.len() {
a[i] += b[i];
}
}
#[cfg(target_arch = "wasm32")]
fn axpy_wasm_simd(a: &mut [f32], b: &[f32], scalar: f32) {
let vs = f32x4_splat(scalar);
let chunks = a.len() / 4;
for i in 0..chunks {
let pa = a[i * 4..].as_ptr() as *const v128;
let pb = b[i * 4..].as_ptr() as *const v128;
let va = v128_load(pa);
let vb = v128_load(pb);
let result = f32x4_add(va, f32x4_mul(vb, vs));
v128_store(a[i * 4..].as_mut_ptr() as *mut v128, result);
}
for i in (chunks * 4)..a.len() {
a[i] += b[i] * scalar;
}
}
// ============ Scalar Fallbacks ============
#[cfg(not(target_arch = "wasm32"))]
fn dot_product_scalar(a: &[f32], b: &[f32]) -> f32 {
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}
#[cfg(not(target_arch = "wasm32"))]
fn relu_scalar(data: &mut [f32]) {
for x in data.iter_mut() {
*x = x.max(0.0);
}
}
fn gelu_scalar(data: &mut [f32]) {
const SQRT_2_OVER_PI: f32 = 0.7978845608;
const GELU_COEF: f32 = 0.044715;
for x in data.iter_mut() {
let x3 = *x * *x * *x;
let inner = SQRT_2_OVER_PI * (*x + GELU_COEF * x3);
*x = 0.5 * *x * (1.0 + inner.tanh());
}
}
fn silu_scalar(data: &mut [f32]) {
for x in data.iter_mut() {
*x = *x / (1.0 + (-*x).exp());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dot_product() {
let backend = WasmBackend;
let a = vec![1.0, 2.0, 3.0, 4.0];
let b = vec![2.0, 3.0, 4.0, 5.0];
let result = backend.dot_product(&a, &b);
assert!((result - 40.0).abs() < 1e-5);
}
#[test]
fn test_add() {
let backend = WasmBackend;
let mut a = vec![1.0, 2.0, 3.0, 4.0];
let b = vec![5.0, 6.0, 7.0, 8.0];
backend.add(&mut a, &b);
assert_eq!(a, vec![6.0, 8.0, 10.0, 12.0]);
}
}