Skip to content

Commit c561bdc

Browse files
committed
added SIMD support
1 parent 760b04a commit c561bdc

11 files changed

Lines changed: 968 additions & 53 deletions

File tree

.cargo/config.toml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,7 @@
11
[target.wasm32-unknown-unknown]
2-
rustflags = ["--cfg", "getrandom_backend=\"wasm_js\""]
2+
rustflags = [
3+
"--cfg",
4+
"getrandom_backend=\"wasm_js\"",
5+
"-C",
6+
"target-feature=+simd128",
7+
]

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
- name: Setup Rust
2323
uses: dtolnay/rust-toolchain@v1
2424
with:
25-
toolchain: stable
25+
toolchain: nightly
2626

2727
- name: Cache cargo
2828
uses: Swatinem/rust-cache@v2

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ jobs:
2929
- name: Setup Rust
3030
uses: dtolnay/rust-toolchain@v1
3131
with:
32-
toolchain: stable
32+
toolchain: nightly
3333

3434
- name: Cache cargo
3535
uses: Swatinem/rust-cache@v2

Cargo.lock

Lines changed: 33 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ readme = "README.md"
1111
keywords = ["curve-fitting", "spline", "optimization", "egui", "wasm"]
1212
categories = ["science", "mathematics", "visualization", "gui"]
1313

14+
[features]
15+
default = ["portable-simd"]
16+
portable-simd = []
17+
1418
[dependencies]
1519
argmin = { version = "0.11.0", default-features = false }
1620
argmin-math = { version = "0.5.1", default-features = false, features = [

rust-toolchain.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[toolchain]
2+
channel = "nightly"

src/fit.rs

Lines changed: 147 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ use argmin::solver::quasinewton::LBFGS;
1313

1414
use crate::domain::{
1515
CurveFamily, CurveParams, FitResult, InputError, LbfgsConfig, NelderMeadConfig,
16-
OptimizerConfig, Point, Points, SteepestDescentConfig,
16+
OptimizerConfig, Points, SteepestDescentConfig,
1717
};
1818

1919
mod curve;
20+
mod simd;
2021
mod spline;
2122

2223
#[cfg(not(target_arch = "wasm32"))]
@@ -34,6 +35,94 @@ pub use spline::{
3435
fit_natural_cubic_spline_with_config, fit_natural_cubic_spline_with_optimizer_config,
3536
};
3637

38+
#[cfg(feature = "portable-simd")]
39+
#[doc(hidden)]
40+
pub mod simd_bench {
41+
use super::{OptimizationLossMetric, simd};
42+
43+
pub fn polynomial_cost_scalar(
44+
param: &[f64],
45+
x_values: &[f64],
46+
y_values: &[f64],
47+
loss_metric: OptimizationLossMetric,
48+
) -> f64 {
49+
simd::polynomial_cost_scalar(param, x_values, y_values, loss_metric)
50+
}
51+
52+
pub fn polynomial_cost_simd(
53+
param: &[f64],
54+
x_values: &[f64],
55+
y_values: &[f64],
56+
loss_metric: OptimizationLossMetric,
57+
) -> f64 {
58+
simd::polynomial_cost_simd(param, x_values, y_values, loss_metric)
59+
}
60+
61+
pub fn inverse_cost_scalar(
62+
param: &[f64],
63+
x_values: &[f64],
64+
y_values: &[f64],
65+
loss_metric: OptimizationLossMetric,
66+
) -> f64 {
67+
simd::inverse_cost_scalar(param, x_values, y_values, loss_metric)
68+
}
69+
70+
pub fn inverse_cost_simd(
71+
param: &[f64],
72+
x_values: &[f64],
73+
y_values: &[f64],
74+
loss_metric: OptimizationLossMetric,
75+
) -> f64 {
76+
simd::inverse_cost_simd(param, x_values, y_values, loss_metric)
77+
}
78+
79+
pub fn polynomial_gradient_scalar(
80+
x_values: &[f64],
81+
y_values: &[f64],
82+
param: &[f64],
83+
loss_metric: OptimizationLossMetric,
84+
gradient: &mut [f64],
85+
) {
86+
simd::accumulate_polynomial_gradient_scalar(
87+
x_values,
88+
y_values,
89+
param,
90+
loss_metric,
91+
gradient,
92+
);
93+
}
94+
95+
pub fn polynomial_gradient_simd(
96+
x_values: &[f64],
97+
y_values: &[f64],
98+
param: &[f64],
99+
loss_metric: OptimizationLossMetric,
100+
gradient: &mut [f64],
101+
) {
102+
simd::accumulate_polynomial_gradient_simd(x_values, y_values, param, loss_metric, gradient);
103+
}
104+
105+
pub fn inverse_gradient_scalar(
106+
x_values: &[f64],
107+
y_values: &[f64],
108+
param: &[f64],
109+
loss_metric: OptimizationLossMetric,
110+
gradient: &mut [f64],
111+
) {
112+
simd::accumulate_inverse_gradient_scalar(x_values, y_values, param, loss_metric, gradient);
113+
}
114+
115+
pub fn inverse_gradient_simd(
116+
x_values: &[f64],
117+
y_values: &[f64],
118+
param: &[f64],
119+
loss_metric: OptimizationLossMetric,
120+
gradient: &mut [f64],
121+
) {
122+
simd::accumulate_inverse_gradient_simd(x_values, y_values, param, loss_metric, gradient);
123+
}
124+
}
125+
37126
const PARAM_EPS: f64 = 1e-9;
38127
const LARGE_COST: f64 = 1e24;
39128
const LN_2: f64 = std::f64::consts::LN_2;
@@ -174,42 +263,29 @@ pub struct IterationMetricSnapshot {
174263
struct CurveProblem {
175264
family: CurveFamily,
176265
points: Points,
266+
point_x: Box<[f64]>,
267+
point_y: Box<[f64]>,
177268
loss_metric: OptimizationLossMetric,
178269
}
179270

180271
impl CurveProblem {
181272
fn new(family: CurveFamily, points: &Points, loss_metric: OptimizationLossMetric) -> Self {
273+
let mut point_x = Vec::with_capacity(points.len());
274+
let mut point_y = Vec::with_capacity(points.len());
275+
for point in points.as_slice() {
276+
point_x.push(point.x());
277+
point_y.push(point.y());
278+
}
182279
Self {
183280
family,
184281
points: points.clone(),
282+
point_x: point_x.into_boxed_slice(),
283+
point_y: point_y.into_boxed_slice(),
185284
loss_metric,
186285
}
187286
}
188287
}
189288

190-
fn accumulate_polynomial_gradient(
191-
points: &[Point],
192-
param: &[f64],
193-
loss_metric: OptimizationLossMetric,
194-
gradient: &mut [f64],
195-
) {
196-
debug_assert_eq!(gradient.len(), param.len());
197-
for point in points {
198-
let x = point.x();
199-
let model = param
200-
.iter()
201-
.copied()
202-
.fold(0.0, |acc, coefficient| acc * x + coefficient);
203-
let residual = loss_metric.residual_derivative(model - point.y());
204-
205-
let mut basis = 1.0;
206-
for gradient_value in gradient.iter_mut().rev() {
207-
*gradient_value += residual * basis;
208-
basis *= x;
209-
}
210-
}
211-
}
212-
213289
#[derive(Debug, Clone, PartialEq)]
214290
/// Подробный результат подгонки сплайна.
215291
pub struct SplineResult {
@@ -259,6 +335,21 @@ fn soft_l1_from_residuals(residuals: &[[f64; 2]]) -> f64 {
259335
/ residuals.len() as f64
260336
}
261337

338+
fn is_polynomial_family(family: CurveFamily) -> bool {
339+
matches!(
340+
family,
341+
CurveFamily::Linear
342+
| CurveFamily::Quadratic
343+
| CurveFamily::Cubic
344+
| CurveFamily::Quartic
345+
| CurveFamily::Quintic
346+
| CurveFamily::Sextic
347+
| CurveFamily::Septic
348+
| CurveFamily::Octic
349+
| CurveFamily::Nonic
350+
)
351+
}
352+
262353
/// Число узлов сплайна по умолчанию.
263354
pub const DEFAULT_SPLINE_KNOTS: usize = 8;
264355
/// Число сэмплов кривой для визуализации по умолчанию.
@@ -1069,6 +1160,24 @@ impl CostFunction for CurveProblem {
10691160
type Output = f64;
10701161

10711162
fn cost(&self, param: &Self::Param) -> Result<Self::Output, argmin::core::Error> {
1163+
if is_polynomial_family(self.family) {
1164+
return Ok(simd::polynomial_cost(
1165+
param,
1166+
self.point_x.as_ref(),
1167+
self.point_y.as_ref(),
1168+
self.loss_metric,
1169+
));
1170+
}
1171+
1172+
if self.family == CurveFamily::Inverse {
1173+
return Ok(simd::inverse_cost(
1174+
param,
1175+
self.point_x.as_ref(),
1176+
self.point_y.as_ref(),
1177+
self.loss_metric,
1178+
));
1179+
}
1180+
10721181
let sample_count = self.points.len() as f64;
10731182
let mut sum = 0.0;
10741183

@@ -1106,9 +1215,13 @@ impl Gradient for CurveProblem {
11061215
| CurveFamily::Sextic
11071216
| CurveFamily::Septic
11081217
| CurveFamily::Octic
1109-
| CurveFamily::Nonic => {
1110-
accumulate_polynomial_gradient(points, param, self.loss_metric, &mut gradient);
1111-
}
1218+
| CurveFamily::Nonic => simd::accumulate_polynomial_gradient(
1219+
self.point_x.as_ref(),
1220+
self.point_y.as_ref(),
1221+
param,
1222+
self.loss_metric,
1223+
&mut gradient,
1224+
),
11121225
CurveFamily::Arrhenius => {
11131226
for point in points {
11141227
let x = positive_x(point.x());
@@ -1119,16 +1232,13 @@ impl Gradient for CurveProblem {
11191232
gradient[1] += residual * (param[0] * exp_term / x);
11201233
}
11211234
}
1122-
CurveFamily::Inverse => {
1123-
for point in points {
1124-
let x = positive_x(point.x());
1125-
let residual = self
1126-
.loss_metric
1127-
.residual_derivative(param[0] + param[1] / x - point.y());
1128-
gradient[0] += residual;
1129-
gradient[1] += residual / x;
1130-
}
1131-
}
1235+
CurveFamily::Inverse => simd::accumulate_inverse_gradient(
1236+
self.point_x.as_ref(),
1237+
self.point_y.as_ref(),
1238+
param,
1239+
self.loss_metric,
1240+
&mut gradient,
1241+
),
11321242
CurveFamily::Logistic => {
11331243
for point in points {
11341244
let z = param[1] * (point.x() - param[2]);

0 commit comments

Comments
 (0)