Hard refactor — no shims, no compat layers. Candle removed from Cargo.toml and all source files in 6 crates: - ml-core: MlDevice enum, checkpoint.rs (safetensors direct), cudarc imports fixed from candle re-export to direct, AdamWConfig lr_decay, cuda_compat gutted. Net -7,341 lines. - ml-ppo: All 16 files rewritten. LSTM→CudaLSTM, VarMap→GpuVarStore, PPOAgent 2306→700 lines, checkpoint→binary format. - ml-ensemble: GPU-resident sigmoid via custom CUDA kernel. - ml-explainability: Integrated gradients via GPU finite-difference kernels. - ml-labeling: Device→MlDevice. - ml-hyperopt: Cargo.toml only. Remaining: ml-dqn (24 files), ml-supervised (4 files), ml crate (104 files). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
60 lines
1.9 KiB
Rust
60 lines
1.9 KiB
Rust
//! Tensor operations and utilities for ML models (Candle-free).
|
|
//!
|
|
//! With the Candle elimination, tensor operations now live in
|
|
//! `cuda_autograd::GpuTensor` and `cuda_autograd::ActivationKernels`.
|
|
//!
|
|
//! This module retains only lightweight CPU-side helper functions that
|
|
//! downstream crates use and that do not require a GPU tensor backend.
|
|
|
|
/// Tensor operation utilities (CPU-side helpers).
|
|
#[derive(Debug)]
|
|
pub struct TensorOps;
|
|
|
|
impl TensorOps {
|
|
/// Apply softmax to a slice with numerical stability.
|
|
///
|
|
/// Returns a new vector of the same length where values sum to 1.
|
|
pub fn stable_softmax_cpu(input: &[f64]) -> Vec<f64> {
|
|
if input.is_empty() {
|
|
return Vec::new();
|
|
}
|
|
let max_val = input.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
|
|
let exp_vals: Vec<f64> = input.iter().map(|&x| (x - max_val).exp()).collect();
|
|
let sum_exp: f64 = exp_vals.iter().sum();
|
|
if sum_exp == 0.0 {
|
|
return vec![1.0 / input.len() as f64; input.len()];
|
|
}
|
|
exp_vals.iter().map(|&x| x / sum_exp).collect()
|
|
}
|
|
|
|
/// Clamp values in a slice between min and max.
|
|
pub fn clamp_cpu(input: &[f64], min_val: f64, max_val: f64) -> Vec<f64> {
|
|
input.iter().map(|&x| x.clamp(min_val, max_val)).collect()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_stable_softmax_cpu() {
|
|
let data = vec![1.0, 2.0, 3.0];
|
|
let result = TensorOps::stable_softmax_cpu(&data);
|
|
let sum: f64 = result.iter().sum();
|
|
assert!((sum - 1.0).abs() < 1e-10);
|
|
// Probabilities should be monotonically increasing
|
|
assert!(result[0] < result[1]);
|
|
assert!(result[1] < result[2]);
|
|
}
|
|
|
|
#[test]
|
|
fn test_clamp_cpu() {
|
|
let data = vec![-2.0, -1.0, 0.0, 1.0, 2.0];
|
|
let result = TensorOps::clamp_cpu(&data, -1.0, 1.0);
|
|
for val in &result {
|
|
assert!(*val >= -1.0 && *val <= 1.0);
|
|
}
|
|
}
|
|
}
|