From e6131523f2333be2d1142d6ba43f0b2464cc1008 Mon Sep 17 00:00:00 2001 From: Ralf Biedert Date: Sat, 14 Dec 2024 15:51:40 +0100 Subject: [PATCH] Make it work with latest published simd_aligned. --- .github/workflows/rust.yml | 46 ++++++++++++++++++++++++++++++++------ Cargo.toml | 4 ++-- src/lib.rs | 26 +++++++++++---------- src/svm/class.rs | 18 ++++++++------- src/svm/core/dense.rs | 25 +++++++++++---------- src/svm/features.rs | 35 ++++++++++++++++------------- src/svm/kernel/linear.rs | 4 ++-- src/svm/kernel/mod.rs | 9 +++----- src/svm/kernel/poly.rs | 4 ++-- src/svm/kernel/rbf.rs | 18 +++++++-------- src/svm/kernel/sigmoid.rs | 4 ++-- 11 files changed, 115 insertions(+), 78 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 7866e49..0eb7718 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -2,18 +2,50 @@ name: Rust on: push: - branches: [ master ] + branches: [ '**' ] pull_request: - branches: [ master ] + branches: [ '**' ] env: CARGO_TERM_COLOR: always jobs: test: - name: Test - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + target: + - x86_64-unknown-linux-gnu + - aarch64-apple-darwin + include: + - target: x86_64-unknown-linux-gnu + runs-on: ubuntu-latest + style: true + test: true + - target: aarch64-apple-darwin + runs-on: macos-latest + test: true + runs-on: ${{ matrix.runs-on }} steps: - - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@nightly - - run: cargo test + - name: Checkout + uses: actions/checkout@v4 + with: + lfs: true + - name: Rust - Install + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt,clippy + targets: ${{ matrix.target }} + - name: Rust - Version + run: rustc -Vv + - name: Rust - Build + run: cargo build --verbose --target=${{ matrix.target }} --tests --all-features + - name: Rust - Style + if: matrix.style == true + run: cargo fmt --check + - name: Rust - Clippy + if: matrix.style == true + run: cargo clippy -- -D warnings + - name: Rust - Test + if: matrix.test == true + run: cargo test --verbose --target=${{ matrix.target }} --all-features -- --test-threads=1 --nocapture diff --git a/Cargo.toml b/Cargo.toml index b589cfe..e14df48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,8 +20,8 @@ path = "src/lib.rs" crate-type = ["rlib"] [dependencies] -#simd_aligned = "0.5" -simd_aligned = { path = "../simd_aligned" } +simd_aligned = "0.6.1" +#simd_aligned = { path = "../simd_aligned" } [dev-dependencies] rand = "0.8.5" diff --git a/src/lib.rs b/src/lib.rs index d66a0d4..61c9ea2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,8 @@ -//! [![Latest Version]][crates.io] -//! [![Rust](https://github.com/ralfbiedert/ffsvm-rust/actions/workflows/rust.yml/badge.svg?branch=master)](https://github.com/ralfbiedert/ffsvm-rust/actions/workflows/rust.yml) -//! [![deps.svg]][deps] -//! [![docs]][docs.rs] -//! ![MIT] +//! [![crates.io-badge]][crates.io-url] +//! [![docs.rs-badge]][docs.rs-url] +//! ![license-badge] +//! [![rust-version-badge]][rust-version-url] +//! [![rust-build-badge]][rust-build-url] //! //! # In One Sentence //! @@ -80,13 +80,15 @@ //! //! [See here for details.](https://github.com/ralfbiedert/ffsvm-rust/blob/master/docs/FAQ.md) //! -//! [Latest Version]: https://img.shields.io/crates/v/ffsvm.svg -//! [crates.io]: https://crates.io/crates/ffsvm -//! [MIT]: https://img.shields.io/badge/license-MIT-blue.svg -//! [docs]: https://docs.rs/ffsvm/badge.svg -//! [docs.rs]: https://docs.rs/ffsvm/ -//! [deps]: https://deps.rs/repo/github/ralfbiedert/ffsvm-rust -//! [deps.svg]: https://deps.rs/repo/github/ralfbiedert/ffsvm-rust/status.svg +//! [crates.io-badge]: https://img.shields.io/crates/v/ffsvm.svg +//! [crates.io-url]: https://crates.io/crates/ffsvm +//! [license-badge]: https://img.shields.io/badge/license-BSD2-blue.svg +//! [docs.rs-badge]: https://docs.rs/ffsvm/badge.svg +//! [docs.rs-url]: https://docs.rs/ffsvm/ +//! [rust-version-badge]: https://img.shields.io/badge/rust-1.83%2B-blue.svg?maxAge=3600 +//! [rust-version-url]: https://github.com/ralfbiedert/ffsvm +//! [rust-build-badge]: https://github.com/ralfbiedert/ffsvm/actions/workflows/rust.yml/badge.svg +//! [rust-build-url]: https://github.com/ralfbiedert/ffsvm/actions/workflows/rust.yml #![warn(clippy::all)] // Enable ALL the warnings ... #![warn(clippy::nursery)] #![warn(clippy::pedantic)] diff --git a/src/svm/class.rs b/src/svm/class.rs index 52db997..cdc2080 100644 --- a/src/svm/class.rs +++ b/src/svm/class.rs @@ -1,5 +1,8 @@ -use crate::{sparse::SparseMatrix}; -use simd_aligned::{f32x8, f64x4, MatD, Rows}; +use crate::sparse::SparseMatrix; +use simd_aligned::{ + arch::{f32x8, f64x4}, + MatSimd, Rows, +}; /// Represents one class of the SVM model. #[derive(Clone, Debug)] @@ -10,21 +13,20 @@ pub(crate) struct Class { // /// The number of support vectors in this class // pub(crate) num_support_vectors: usize, - /// Coefficients between this class and n-1 other classes. - pub(crate) coefficients: MatD, + pub(crate) coefficients: MatSimd, /// All support vectors in this class. pub(crate) support_vectors: M32, } -impl Class> { +impl Class> { /// Creates a new class with the given parameters. pub fn with_parameters(classes: usize, support_vectors: usize, attributes: usize, label: i32) -> Self { Self { label, - coefficients: MatD::with_dimension(classes - 1, support_vectors), - support_vectors: MatD::with_dimension(support_vectors, attributes), + coefficients: MatSimd::with_dimension(classes - 1, support_vectors), + support_vectors: MatSimd::with_dimension(support_vectors, attributes), } } } @@ -34,7 +36,7 @@ impl Class> { pub fn with_parameters(classes: usize, support_vectors: usize, _attributes: usize, label: i32) -> Self { Self { label, - coefficients: MatD::with_dimension(classes - 1, support_vectors), + coefficients: MatSimd::with_dimension(classes - 1, support_vectors), support_vectors: SparseMatrix::with(support_vectors), } } diff --git a/src/svm/core/dense.rs b/src/svm/core/dense.rs index e15f9ae..4efa208 100644 --- a/src/svm/core/dense.rs +++ b/src/svm/core/dense.rs @@ -1,6 +1,3 @@ -use simd_aligned::{f32x8, traits::Simd, MatD, Rows, VecD}; -use std::convert::TryFrom; - use crate::{ errors::Error, parser::ModelFile, @@ -14,6 +11,8 @@ use crate::{ util::{find_max_index, set_all, sigmoid_predict}, vectors::Triangular, }; +use simd_aligned::{arch::f32x8, traits::Simd, MatSimd, Rows, VecSimd}; +use std::convert::TryFrom; /// An SVM using [SIMD](https://en.wikipedia.org/wiki/SIMD) intrinsics optimized for speed. /// @@ -44,7 +43,7 @@ pub struct DenseSVM { pub(crate) kernel: Box, /// All classes - pub(crate) classes: Vec>>, + pub(crate) classes: Vec>>, } impl DenseSVM { @@ -94,7 +93,7 @@ impl DenseSVM { } /// Computes the kernel values for this problem - pub(crate) fn compute_kernel_values(&self, problem: &mut FeatureVector>) { + pub(crate) fn compute_kernel_values(&self, problem: &mut FeatureVector>) { // Get current problem and decision values array let features = &problem.features; let kernel_values = &mut problem.kernel_values; @@ -112,13 +111,15 @@ impl DenseSVM { // based on Method 2 from the paper "Probability Estimates for Multi-class // Classification by Pairwise Coupling", Journal of Machine Learning Research 5 (2004) 975-1005, // by Ting-Fan Wu, Chih-Jen Lin and Ruby C. Weng. - pub(crate) fn compute_multiclass_probabilities(&self, problem: &mut FeatureVector>) -> Result<(), Error> { compute_multiclass_probabilities_impl!(self, problem) } + pub(crate) fn compute_multiclass_probabilities(&self, problem: &mut FeatureVector>) -> Result<(), Error> { + compute_multiclass_probabilities_impl!(self, problem) + } /// Based on kernel values, computes the decision values for this problem. - pub(crate) fn compute_classification_values(&self, problem: &mut FeatureVector>) { compute_classification_values_impl!(self, problem) } + pub(crate) fn compute_classification_values(&self, problem: &mut FeatureVector>) { compute_classification_values_impl!(self, problem) } /// Based on kernel values, computes the decision values for this problem. - pub(crate) fn compute_regression_values(&self, problem: &mut FeatureVector>) { + pub(crate) fn compute_regression_values(&self, problem: &mut FeatureVector>) { let class = &self.classes[0]; let coef = class.coefficients.row(0); let kvalues = problem.kernel_values.row(0); @@ -137,9 +138,9 @@ impl DenseSVM { pub fn classes(&self) -> usize { self.classes.len() } } -impl Predict> for DenseSVM { +impl Predict> for DenseSVM { // Predict the value for one problem. - fn predict_value(&self, fv: &mut FeatureVector>) -> Result<(), Error> { + fn predict_value(&self, fv: &mut FeatureVector>) -> Result<(), Error> { match self.svm_type { SVMType::CSvc | SVMType::NuSvc => { // Compute kernel, decision values and eventually the label @@ -160,7 +161,7 @@ impl Predict> for DenseSVM { } } - fn predict_probability(&self, problem: &mut FeatureVector>) -> Result<(), Error> { predict_probability_impl!(self, problem) } + fn predict_probability(&self, problem: &mut FeatureVector>) -> Result<(), Error> { predict_probability_impl!(self, problem) } } impl<'a> TryFrom<&'a str> for DenseSVM { @@ -176,7 +177,7 @@ impl<'a, 'b> TryFrom<&'a ModelFile<'b>> for DenseSVM { type Error = Error; fn try_from(raw_model: &'a ModelFile<'_>) -> Result { - let (mut svm, nr_sv) = prepare_svm!(raw_model, dyn KernelDense, MatD, Self); + let (mut svm, nr_sv) = prepare_svm!(raw_model, dyn KernelDense, MatSimd, Self); let vectors = &raw_model.vectors(); diff --git a/src/svm/features.rs b/src/svm/features.rs index e26ec00..b78b62a 100644 --- a/src/svm/features.rs +++ b/src/svm/features.rs @@ -4,12 +4,15 @@ use crate::{ vectors::Triangular, }; -use simd_aligned::{f32x8, f64x4, MatD, Rows, VecD}; +use simd_aligned::{ + arch::{f32x8, f64x4}, + MatSimd, Rows, VecSimd, +}; /// Feature vectors produced for [`DenseSVM`]s. /// /// Also see [`FeatureVector`] for more methods for this type. -pub type DenseFeatures = FeatureVector>; +pub type DenseFeatures = FeatureVector>; /// Feature vectors produced for [`SparseSVM`]s. /// @@ -69,7 +72,7 @@ pub struct FeatureVector { pub(crate) features: T, /// KernelDense values. A vector for each class. - pub(crate) kernel_values: MatD, + pub(crate) kernel_values: MatSimd, /// All votes for a given class label. pub(crate) vote: Vec, @@ -78,17 +81,17 @@ pub struct FeatureVector { pub(crate) decision_values: Triangular, /// Pairwise probabilities - pub(crate) pairwise: MatD, + pub(crate) pairwise: MatSimd, /// Needed for multi-class probability estimates replicating libSVM. - pub(crate) q: MatD, + pub(crate) q: MatSimd, /// Needed for multi-class probability estimates replicating libSVM. pub(crate) qp: Vec, /// Probability estimates that will be updated after this problem was processed /// by `predict_probability`. - pub(crate) probabilities: VecD, + pub(crate) probabilities: VecSimd, /// Computed label that will be updated after this problem was processed. pub(crate) result: Label, @@ -102,7 +105,7 @@ impl FeatureVector { pub fn probabilities(&self) -> &[f64] { self.probabilities.flat() } } -impl FeatureVector> { +impl FeatureVector> { /// Returns the features. You must set them first and classify the problem before you can get a solution. pub fn features(&mut self) -> &mut [f32] { self.features.flat_mut() } } @@ -116,14 +119,14 @@ impl DenseFeatures { /// Creates a new problem with the given parameters. pub(crate) fn with_dimension(total_sv: usize, num_classes: usize, num_attributes: usize) -> Self { Self { - features: VecD::with(0.0, num_attributes), - kernel_values: MatD::with_dimension(num_classes, total_sv), - pairwise: MatD::with_dimension(num_classes, num_classes), - q: MatD::with_dimension(num_classes, num_classes), + features: VecSimd::with(0.0, num_attributes), + kernel_values: MatSimd::with_dimension(num_classes, total_sv), + pairwise: MatSimd::with_dimension(num_classes, num_classes), + q: MatSimd::with_dimension(num_classes, num_classes), qp: vec![Default::default(); num_classes], decision_values: Triangular::with_dimension(num_classes, Default::default()), vote: vec![Default::default(); num_classes], - probabilities: VecD::with(0.0, num_classes), + probabilities: VecSimd::with(0.0, num_classes), result: Label::None, } } @@ -137,13 +140,13 @@ impl SparseFeatures { pub(crate) fn with_dimension(total_sv: usize, num_classes: usize, _num_attributes: usize) -> Self { Self { features: SparseVector::new(), - kernel_values: MatD::with_dimension(num_classes, total_sv), - pairwise: MatD::with_dimension(num_classes, num_classes), - q: MatD::with_dimension(num_classes, num_classes), + kernel_values: MatSimd::with_dimension(num_classes, total_sv), + pairwise: MatSimd::with_dimension(num_classes, num_classes), + q: MatSimd::with_dimension(num_classes, num_classes), qp: vec![Default::default(); num_classes], decision_values: Triangular::with_dimension(num_classes, Default::default()), vote: vec![Default::default(); num_classes], - probabilities: VecD::with(0.0, num_classes), + probabilities: VecSimd::with(0.0, num_classes), result: Label::None, } } diff --git a/src/svm/kernel/linear.rs b/src/svm/kernel/linear.rs index 7f151ce..1714afc 100644 --- a/src/svm/kernel/linear.rs +++ b/src/svm/kernel/linear.rs @@ -6,14 +6,14 @@ use crate::{ sparse::{SparseMatrix, SparseVector}, }; -use simd_aligned::{f32x8, MatD, Rows, VecD, traits::Simd}; +use simd_aligned::{arch::f32x8, traits::Simd, MatSimd, Rows, VecSimd}; #[derive(Copy, Clone, Debug, Default)] #[doc(hidden)] pub struct Linear {} impl KernelDense for Linear { - fn compute(&self, vectors: &MatD, feature: &VecD, output: &mut [f64]) { + fn compute(&self, vectors: &MatSimd, feature: &VecSimd, output: &mut [f64]) { for (i, sv) in vectors.row_iter().enumerate() { let mut sum = f32x8::splat(0.0); let feature: &[f32x8] = feature; diff --git a/src/svm/kernel/mod.rs b/src/svm/kernel/mod.rs index 983c630..08bef0f 100644 --- a/src/svm/kernel/mod.rs +++ b/src/svm/kernel/mod.rs @@ -3,12 +3,9 @@ mod poly; mod rbf; mod sigmoid; -use crate::{ - sparse::{SparseMatrix, SparseVector}, -}; -use simd_aligned::{f32x8, MatD, Rows, VecD}; - pub use self::{linear::*, poly::*, rbf::*, sigmoid::*}; +use crate::sparse::{SparseMatrix, SparseVector}; +use simd_aligned::{arch::f32x8, MatSimd, Rows, VecSimd}; /// Base trait for kernels #[doc(hidden)] @@ -16,7 +13,7 @@ pub trait KernelDense where Self: Send + Sync, { - fn compute(&self, vectors: &MatD, feature: &VecD, output: &mut [f64]); + fn compute(&self, vectors: &MatSimd, feature: &VecSimd, output: &mut [f64]); } /// Base trait for kernels diff --git a/src/svm/kernel/poly.rs b/src/svm/kernel/poly.rs index d389cad..8a97e79 100644 --- a/src/svm/kernel/poly.rs +++ b/src/svm/kernel/poly.rs @@ -7,7 +7,7 @@ use crate::{ sparse::{SparseMatrix, SparseVector}, }; -use simd_aligned::{f32x8, traits::Simd, MatD, Rows, VecD}; +use simd_aligned::{arch::f32x8, traits::Simd, MatSimd, Rows, VecSimd}; #[derive(Copy, Clone, Debug, Default)] #[doc(hidden)] @@ -18,7 +18,7 @@ pub struct Poly { } impl KernelDense for Poly { - fn compute(&self, vectors: &MatD, feature: &VecD, output: &mut [f64]) { + fn compute(&self, vectors: &MatSimd, feature: &VecSimd, output: &mut [f64]) { for (i, sv) in vectors.row_iter().enumerate() { let mut sum = f32x8::splat(0.0); let feature: &[f32x8] = feature; diff --git a/src/svm/kernel/rbf.rs b/src/svm/kernel/rbf.rs index 5e8ec8b..1b018ad 100644 --- a/src/svm/kernel/rbf.rs +++ b/src/svm/kernel/rbf.rs @@ -7,7 +7,7 @@ use crate::{ sparse::{SparseMatrix, SparseVector}, }; -use simd_aligned::{f32x8, traits::Simd, MatD, Rows, VecD}; +use simd_aligned::{arch::f32x8, traits::Simd, MatSimd, Rows, VecSimd}; #[derive(Copy, Clone, Debug, Default)] #[doc(hidden)] @@ -16,7 +16,7 @@ pub struct Rbf { } #[inline] -fn compute_core(rbf: Rbf, vectors: &MatD, feature: &VecD, output: &mut [f64]) { +fn compute_core(rbf: Rbf, vectors: &MatSimd, feature: &VecSimd, output: &mut [f64]) { // According to Instruments, for realistic SVMs and feature vectors, the VAST majority of our // CPU time is spent in this loop. for (i, sv) in vectors.row_iter().enumerate() { @@ -37,21 +37,21 @@ fn compute_core(rbf: Rbf, vectors: &MatD, feature: &VecD, ou #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] #[target_feature(enable = "avx")] #[inline] -unsafe fn compute_avx(rbf: Rbf, vectors: &MatD, feature: &VecD, output: &mut [f64]) { compute_core(rbf, vectors, feature, output); } +unsafe fn compute_avx(rbf: Rbf, vectors: &MatSimd, feature: &VecSimd, output: &mut [f64]) { compute_core(rbf, vectors, feature, output); } #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] #[target_feature(enable = "avx2")] #[inline] -unsafe fn compute_avx2(rbf: Rbf, vectors: &MatD, feature: &VecD, output: &mut [f64]) { compute_core(rbf, vectors, feature, output); } +unsafe fn compute_avx2(rbf: Rbf, vectors: &MatSimd, feature: &VecSimd, output: &mut [f64]) { compute_core(rbf, vectors, feature, output); } #[cfg(target_arch = "aarch64")] #[target_feature(enable = "neon")] #[inline] -unsafe fn compute_neon(rbf: Rbf, vectors: &MatD, feature: &VecD, output: &mut [f64]) { compute_core(rbf, vectors, feature, output); } +unsafe fn compute_neon(rbf: Rbf, vectors: &MatSimd, feature: &VecSimd, output: &mut [f64]) { compute_core(rbf, vectors, feature, output); } #[cfg(any(target_arch = "x86_64", target_arch = "x86"))] #[inline] -fn compute(rbf: Rbf, vectors: &MatD, feature: &VecD, output: &mut [f64]) { +fn compute(rbf: Rbf, vectors: &MatSimd, feature: &VecSimd, output: &mut [f64]) { if is_x86_feature_detected!("avx2") { unsafe { compute_avx2(rbf, vectors, feature, output) } } else if is_x86_feature_detected!("avx") { @@ -63,7 +63,7 @@ fn compute(rbf: Rbf, vectors: &MatD, feature: &VecD, output: #[cfg(target_arch = "aarch64")] #[inline] -fn compute(rbf: Rbf, vectors: &MatD, feature: &VecD, output: &mut [f64]) { +fn compute(rbf: Rbf, vectors: &MatSimd, feature: &VecSimd, output: &mut [f64]) { if std::arch::is_aarch64_feature_detected!("neon") { unsafe { compute_neon(rbf, vectors, feature, output) } } else { @@ -73,10 +73,10 @@ fn compute(rbf: Rbf, vectors: &MatD, feature: &VecD, output: #[cfg(not(any(target_arch = "x86_64", target_arch = "x86", target_arch = "aarch64")))] #[inline] -fn compute(rbf: Rbf, vectors: &MatD, feature: &VecD, output: &mut [f64]) { compute_core(rbf, vectors, feature, output) } +fn compute(rbf: Rbf, vectors: &MatSimd, feature: &VecSimd, output: &mut [f64]) { compute_core(rbf, vectors, feature, output) } impl KernelDense for Rbf { - fn compute(&self, vectors: &MatD, feature: &VecD, output: &mut [f64]) { compute(*self, vectors, feature, output); } + fn compute(&self, vectors: &MatSimd, feature: &VecSimd, output: &mut [f64]) { compute(*self, vectors, feature, output); } } impl KernelSparse for Rbf { diff --git a/src/svm/kernel/sigmoid.rs b/src/svm/kernel/sigmoid.rs index ffa5734..579ad09 100644 --- a/src/svm/kernel/sigmoid.rs +++ b/src/svm/kernel/sigmoid.rs @@ -7,7 +7,7 @@ use crate::{ sparse::{SparseMatrix, SparseVector}, }; -use simd_aligned::{f32x8, traits::Simd, MatD, Rows, VecD}; +use simd_aligned::{arch::f32x8, traits::Simd, MatSimd, Rows, VecSimd}; #[derive(Copy, Clone, Debug, Default)] #[doc(hidden)] @@ -17,7 +17,7 @@ pub struct Sigmoid { } impl KernelDense for Sigmoid { - fn compute(&self, vectors: &MatD, feature: &VecD, output: &mut [f64]) { + fn compute(&self, vectors: &MatSimd, feature: &VecSimd, output: &mut [f64]) { for (i, sv) in vectors.row_iter().enumerate() { let mut sum = f32x8::splat(0.0); let feature: &[f32x8] = feature;