RustyML v0.13.0 is a large performance-and-correctness release. The matrix-product backend moves to the pure-Rust gemm crate, model persistence switches to a compact binary format, several estimators gain new capabilities, and a broad numerics pass aligns the crate with scikit-learn / NumPy / PyTorch conventions.
Heads-up: this release contains breaking API and on-disk-format changes.
Read Breaking Changes before upgrading.
Highlights
- New matmul backend — all GEMM/GEMV now runs on the
gemmcrate's runtime-dispatched SIMD kernels, with shape-aware parallelism. - Compact binary model persistence — models and weights serialize via
postcardinstead of JSON (about 5x smaller on a fittedKMeans). - Richer estimators — data-dependent kernel
gamma, LDA probabilities, a closed-form linear-regression solver, Isolation Forest labels, and a squared-hinge LinearSVC. - Runtime-tunable parallelism — the new public
tuningmodule overrides the serial/parallel thresholds per machine, with no recompile. - scikit-learn-aligned numerics — convergence criteria, regularization scaling, and stability constants now match the reference implementations.
Breaking Changes
Model persistence: JSON to binary (postcard)
save_to_path/load_from_pathon every classical-ML model,PCA/KernelPCA, andSequentialnow write and readpostcardbinary instead ofserde_json.- Old
.jsonmodel files can no longer be loaded — re-save any persisted models. The method signatures are unchanged; only the byte format differs (a.binextension is now suggested). IoError::Json(serde_json::Error)is renamed toIoError::Serialization(postcard::Error); code matching onIoError::Jsonmust switch toIoError::Serialization.- The serialized format also changed for
LinearRegression(newsolverfield) andLinearSVC(newlossandlearning_rate_decayfields), so previously saved instances of those two models must also be re-saved.
Matrix-product backend: matrixmultiply to gemm
- The crate-internal GEMM/GEMV path now calls the
gemmcrate;matrixmultiplyis dropped. - The public matrix-product API in
math::matmul—gemm,gemv,gemm_par, andgemv_par— is removed, with no public replacement. Out-of-crate callers should usendarray'sdotor thegemmcrate directly. - Public model signatures (
Dense,PCA,KMeans, SVM, LDA, kernels) are unchanged; only the underlying kernel and its numerics differ (see Behavior Changes).
Kernel gamma is now a Gamma enum
- The
gammafield ofKernelType::Poly/RBF/Sigmoidchanges fromf64to the newGammaenum. - Update every construction site, e.g.
KernelType::RBF { gamma: 0.5 }becomesKernelType::RBF { gamma: Gamma::Value(0.5) }.
standardize drops its epsilon parameter
- The signature changes from
standardize(data, axis, epsilon)tostandardize(&data, axis). - Drop the third argument, e.g.
standardize(&data, StandardizationAxis::Column, 1e-8)becomesstandardize(&data, StandardizationAxis::Column).
LDA::get_n_components returns Option<usize>
- The default
n_componentsis nowNone(auto-resolved at fit time tomin(n_classes - 1, n_features)) instead of2. - Callers reading
get_n_components()must handleOption<usize>.
New Features
Estimators
- Data-dependent kernel
gamma— the newGammaenum addsGamma::Scale(1 / (n_features * X.var())) andGamma::Auto(1 / n_features) alongsideGamma::Value(f64);SVCandKernelPCAresolveScale/Autoagainst the training data atfit. LDA::decision_functionandLDA::predict_proba— per-class discriminant scores and a numerically-stable softmax posterior, plus automaticn_componentsselection.LinearRegressionclosed-form solver andscore— the newSolver::Normal(selectable viawith_solver) solves the ridge least-squares system by SVD, andscore(x, y)returns the coefficient of determination (R²).IsolationForest::predict_labels(x, contamination)— classifies samples as inlier (+1) or outlier (-1) by flagging the topceil(contamination * n_samples)scores.LinearSVCsquared-hinge loss and learning-rate decay — the newLossenum addsLoss::SquaredHinge, andwith_learning_rate_decayapplies inverse-scaling decay per epoch.- t-SNE
min_grad_normearly stopping —TSNE::with_min_grad_normstops optimization once the largest gradient entry drops below the threshold (default1e-7); pass0.0to keep the previous full-n_iterbehavior.
tuning module
- The new public
tuningmodule exposes a flatset_*/get_*facade (grouped intomatmul,elementwise,reduction,tree,conv,pool,norm, andmetrics) for overriding the serial-vs-parallel and GEMM-strategy thresholds at runtime, per machine, without recompiling. - Defaults are unchanged, so results stay reproducible across runs on the same machine.
Performance
- gemm-crate GEMM/GEMV backend with shape-aware parallelism: serial below a per-dtype FLOP gate, column-parallel for wide outputs, and a rayon row-split for thin / column-starved outputs and matvecs.
- Bounded nested GEMM parallelism — three hotspots that re-forked rayon inside an already-parallel loop now serialize the inner GEMM once the outer axis fills the pool: conv backward ~13% faster (batch=32), and many-class
LDA::fitand high-kKMeansfit ~6% faster each. - Matvecs routed through the gemm backend —
svc_predict-5%,knn_predict-22%,mean_shift_fit-47%. silhouette_scoresymmetric fill — each unordered pair is evaluated once instead of twice: ~33% faster for Euclidean / Manhattan, ~45% for Minkowski(3).generate_polynomial_featuresgating — small-input expansion -94.5%, while the large first-order path stays parallel.- Power-iteration eigensolver — one matvec per step (was two) and an in-place rank-1 Hotelling deflation, feeding the PCA / KernelPCA
PowerIterationsolvers. - Every performance change above is numerically unchanged and stays reproducible across runs on the same machine.
Behavior Changes
- Matrix products stay reproducible across runs on the same machine, but are no longer bit-for-bit identical to the old
matrixmultiplybackend (or identical across different machines); tests or pipelines asserting bit-exact matmul output must switch to an approximate comparison. - KMeans declares convergence on centroid shift (
<= tol * mean(var(X))) rather than inertia change, so the iteration count and final centroids can change for a giventol. - LogisticRegression no longer divides the L1/L2 penalty by the sample count (matching scikit-learn's SGD convention), so fitted coefficients change for any regularized model at a fixed
alpha. - DecisionTree scales
min_impurity_decreaseby the node's sample fraction and enforcesmin_samples_leafduring the split search, which can change the resulting tree. - PCA flips principal-axis signs deterministically, so all SVD solvers and repeated runs agree on orientation; reconstructions are unaffected, but component signs may flip relative to the previous release.
- NaN and stability alignment —
math::sigmoiddrops its±500input clamp, the softmax layer drops its1e-8denominator floor (NaN now propagates),utils::normalizeleaves near-zero lanes unchanged, andlog_loss,mean_absolute_percentage_error, and t-SNE align their stability epsilons to the reference values. Finite, well-formed inputs are numerically unchanged.
Bug Fixes
LinearSVC::fitnow rejects labels other than0.0/1.0instead of silently mishandling multi-class input.- MeanShift keeps the current center on a zero-weight window instead of teleporting it to the origin (which injected a spurious cluster).
- DecisionTree no longer discards a whole categorical split because of a single under-sized rare branch.
IsolationForest::predicthandles non-contiguous (sliced or transposed) input rows instead of panicking.- The lightweight
metrics-only build compiles again.
Dependencies
- Added
gemmandpostcard; droppedmatrixmultiplyandserde_json. - All other dependencies are at their latest stable versions.
Full Changelog
See CHANGELOG.md for the complete, commit-level list of changes.