Skip to content

Commit c08541c

Browse files
authored
Optimize computation with Eigen. (#2928)
This pull request introduces the Eigen library to optimize various numerical computations within the project. It systematically replaces existing manual, loop-based implementations for operations such as calculating mean and inverse standard deviation, applying feature normalization, scaling, windowing, and log-mel transformations. The primary goal of this refactoring is to enhance the overall computational performance and improve the readability and maintainability of the codebase by utilizing Eigen's highly optimized matrix and array functionalities.
1 parent 27a0bf4 commit c08541c

11 files changed

Lines changed: 208 additions & 131 deletions

sherpa-onnx/csrc/math-test.cc

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,76 @@ TEST(Scale, Case2InPlace) {
6565
EXPECT_EQ(src, expected);
6666
}
6767

68+
/*
69+
70+
import numpy as np
71+
72+
def compute_mean_and_inv_std(p: np.ndarray):
73+
mean = p.mean(axis=0)
74+
var = np.maximum((p**2).mean(axis=0) - mean**2, 0.0)
75+
std = np.sqrt(var)
76+
inv_std = 1.0 / (std + 1e-5)
77+
return mean.astype(np.float32), inv_std.astype(np.float32)
78+
79+
def dump_cpp_vector(name: str, arr: np.ndarray):
80+
flat = arr.flatten()
81+
print(f"std::vector<float> {name} = {{")
82+
line = ""
83+
for i, v in enumerate(flat):
84+
line += f"{v:.8f}f, "
85+
if (i + 1) % 8 == 0:
86+
print(" " + line)
87+
line = ""
88+
if line:
89+
print(" " + line)
90+
print("};\n")
91+
92+
np.random.seed(42)
93+
num_rows, num_cols = 4, 6
94+
x = np.random.randn(num_rows, num_cols).astype(np.float32)
95+
96+
mean, inv_std = compute_mean_and_inv_std(x)
97+
98+
dump_cpp_vector("x", x)
99+
dump_cpp_vector("mean", mean)
100+
dump_cpp_vector("inv_std", inv_std)
101+
102+
*/
103+
104+
TEST(ComputeMeanAndInvStd, Case1) {
105+
std::vector<float> x = {
106+
0.49671414f, -0.13826430f, 0.64768857f, 1.52302980f, -0.23415338f,
107+
-0.23413695f, 1.57921278f, 0.76743472f, -0.46947438f, 0.54256004f,
108+
-0.46341768f, -0.46572974f, 0.24196227f, -1.91328025f, -1.72491789f,
109+
-0.56228751f, -1.01283109f, 0.31424734f, -0.90802407f, -1.41230369f,
110+
1.46564877f, -0.22577630f, 0.06752820f, -1.42474818f,
111+
};
112+
113+
std::vector<float> expected_mean = {
114+
0.35246629f, -0.67410338f, -0.02026373f,
115+
0.31938151f, -0.41071847f, -0.45259190f,
116+
};
117+
118+
std::vector<float> expected_inv_std = {
119+
1.13103926f, 0.94854516f, 0.83320111f,
120+
1.24679470f, 2.52932906f, 1.59057319f,
121+
};
122+
123+
std::vector<float> mean;
124+
std::vector<float> inv_std;
125+
126+
int32_t num_rows = 4;
127+
int32_t num_cols = 6;
128+
129+
ComputeMeanAndInvStd(x.data(), num_rows, num_cols, &mean, &inv_std);
130+
131+
ASSERT_EQ(mean.size(), num_cols);
132+
ASSERT_EQ(inv_std.size(), num_cols);
133+
134+
for (int32_t i = 0; i < num_cols; ++i) {
135+
EXPECT_NEAR(mean[i], expected_mean[i], 1e-6f) << "at index " << i;
136+
EXPECT_NEAR(inv_std[i], expected_inv_std[i], 1e-6f) << "at index " << i;
137+
}
138+
}
139+
68140
} // namespace sherpa_onnx

sherpa-onnx/csrc/math.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
#include "sherpa-onnx/csrc/math.h"
55

66
#include <vector>
7+
8+
#include "Eigen/Dense"
9+
710
namespace sherpa_onnx {
811

912
void ScaleAdd(const float *src, float scale, int32_t n, float *in_out) {
@@ -72,4 +75,27 @@ std::vector<float> Transpose(const float *input, int32_t rows, int32_t cols) {
7275
return output;
7376
}
7477

78+
void ComputeMeanAndInvStd(const float *p, int32_t num_rows, int32_t num_cols,
79+
std::vector<float> *mean,
80+
std::vector<float> *inv_stddev) {
81+
using RowMajorMat =
82+
Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
83+
84+
Eigen::Map<const RowMajorMat> X(p, num_rows, num_cols);
85+
86+
Eigen::RowVectorXf mean_vec = X.colwise().mean();
87+
88+
Eigen::RowVectorXf mean_sq = X.array().square().colwise().mean();
89+
90+
Eigen::RowVectorXf var = mean_sq.array() - mean_vec.array().square();
91+
92+
Eigen::RowVectorXf stddev = var.array().max(0.0f).sqrt();
93+
94+
Eigen::RowVectorXf inv_std = (stddev.array() + 1e-5f).inverse();
95+
96+
mean->assign(mean_vec.data(), mean_vec.data() + num_cols);
97+
98+
inv_stddev->assign(inv_std.data(), inv_std.data() + num_cols);
99+
}
100+
75101
} // namespace sherpa_onnx

sherpa-onnx/csrc/math.h

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,5 +147,19 @@ std::vector<float> ComputeAcousticEmbedding(
147147
// Transpose a 2-D matrix in row-major
148148
std::vector<float> Transpose(const float *input, int32_t rows, int32_t cols);
149149

150+
/* Compute mean and inverse stddev over rows.
151+
*
152+
* @param p A pointer to a 2-d array of shape (num_rows, num_cols)
153+
* @param num_rows Number of rows
154+
* @param num_cols Number of columns
155+
* @param mean On return, it contains p.mean(axis=0). You don't need to
156+
* pre-allocate space for it.
157+
* @param inv_stddev On return, it contains 1/p.std(axis=0) You don't need to
158+
* pre-allocate space for it.
159+
*/
160+
void ComputeMeanAndInvStd(const float *p, int32_t num_rows, int32_t num_cols,
161+
std::vector<float> *mean,
162+
std::vector<float> *inv_stddev);
163+
150164
} // namespace sherpa_onnx
151165
#endif // SHERPA_ONNX_CSRC_MATH_H_

sherpa-onnx/csrc/offline-dolphin-model.cc

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "rawfile/raw_file_manager.h"
2020
#endif
2121

22+
#include "Eigen/Dense"
2223
#include "sherpa-onnx/csrc/file-utils.h"
2324
#include "sherpa-onnx/csrc/macros.h"
2425
#include "sherpa-onnx/csrc/onnx-utils.h"
@@ -65,16 +66,15 @@ class OfflineDolphinModel::Impl {
6566

6667
void NormalizeFeatures(float *features, int32_t num_frames,
6768
int32_t feat_dim) const {
68-
auto p = features;
69-
const auto &mean = meta_data_.mean;
70-
const auto &invstd = meta_data_.inv_stddev;
71-
72-
for (int32_t f = 0; f < num_frames; ++f) {
73-
for (int32_t d = 0; d < feat_dim; ++d) {
74-
p[d] = (p[d] - mean[d]) * invstd[d];
75-
}
76-
p += feat_dim;
77-
}
69+
using RowMajorMat =
70+
Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;
71+
Eigen::Map<RowMajorMat> x(features, num_frames, feat_dim);
72+
73+
Eigen::Map<const Eigen::RowVectorXf> mean(meta_data_.mean.data(), feat_dim);
74+
Eigen::Map<const Eigen::RowVectorXf> inv_std(meta_data_.inv_stddev.data(),
75+
feat_dim);
76+
x.array() =
77+
(x.array().rowwise() - mean.array()).rowwise() * inv_std.array();
7878
}
7979

8080
OrtAllocator *Allocator() { return allocator_; }

sherpa-onnx/csrc/offline-omnilingual-asr-ctc-model.cc

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "rawfile/raw_file_manager.h"
2121
#endif
2222

23+
#include "Eigen/Dense"
2324
#include "sherpa-onnx/csrc/file-utils.h"
2425
#include "sherpa-onnx/csrc/macros.h"
2526
#include "sherpa-onnx/csrc/onnx-utils.h"
@@ -85,19 +86,14 @@ class OfflineOmnilingualAsrCtcModel::Impl {
8586
return;
8687
}
8788

88-
double s = 0;
89-
double sq = 0;
90-
for (int32_t i = 0; i < feat_dim; ++i) {
91-
s += features[i];
92-
sq += features[i] * features[i];
93-
}
94-
95-
double mean = s / feat_dim;
96-
double inv_stddev = 1 / std::sqrt(sq / feat_dim - mean * mean + 1e-5);
89+
// Map the single-row feature vector
90+
Eigen::Map<Eigen::ArrayXf> x(features, feat_dim);
91+
float mean = x.mean();
92+
float var = (x.square().mean() - mean * mean);
93+
var = std::max(var, 0.0f);
94+
float inv_stddev = 1.0f / std::sqrt(var + 1e-5f);
9795

98-
for (int32_t i = 0; i < feat_dim; ++i) {
99-
features[i] = (features[i] - mean) * inv_stddev;
100-
}
96+
x = (x - mean) * inv_stddev;
10197
}
10298

10399
private:

sherpa-onnx/csrc/offline-recognizer-fire-red-asr-impl.h

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <utility>
1313
#include <vector>
1414

15+
#include "Eigen/Dense"
1516
#include "sherpa-onnx/csrc/offline-fire-red-asr-decoder.h"
1617
#include "sherpa-onnx/csrc/offline-fire-red-asr-greedy-search-decoder.h"
1718
#include "sherpa-onnx/csrc/offline-fire-red-asr-model.h"
@@ -131,20 +132,19 @@ class OfflineRecognizerFireRedAsrImpl : public OfflineRecognizerImpl {
131132

132133
void ApplyCMVN(std::vector<float> *v) const {
133134
const auto &meta_data = model_->GetModelMetadata();
134-
const auto &mean = meta_data.mean;
135-
const auto &inv_stddev = meta_data.inv_stddev;
136-
int32_t feat_dim = static_cast<int32_t>(mean.size());
135+
const auto &mean_vec = meta_data.mean;
136+
const auto &inv_stddev_vec = meta_data.inv_stddev;
137+
int32_t feat_dim = static_cast<int32_t>(mean_vec.size());
137138
int32_t num_frames = static_cast<int32_t>(v->size()) / feat_dim;
138-
139-
float *p = v->data();
140-
141-
for (int32_t i = 0; i != num_frames; ++i) {
142-
for (int32_t k = 0; k != feat_dim; ++k) {
143-
p[k] = (p[k] - mean[k]) * inv_stddev[k];
144-
}
145-
146-
p += feat_dim;
147-
}
139+
Eigen::Map<
140+
Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>
141+
mat(v->data(), num_frames, feat_dim);
142+
Eigen::Map<const Eigen::RowVectorXf> mean(mean_vec.data(), feat_dim);
143+
Eigen::Map<const Eigen::RowVectorXf> inv_std(inv_stddev_vec.data(),
144+
feat_dim);
145+
146+
mat.array() =
147+
(mat.array().rowwise() - mean.array()).rowwise() * inv_std.array();
148148
}
149149

150150
private:

sherpa-onnx/csrc/offline-recognizer-paraformer-impl.h

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <utility>
1212
#include <vector>
1313

14+
#include "Eigen/Dense"
1415
#include "sherpa-onnx/csrc/offline-model-config.h"
1516
#include "sherpa-onnx/csrc/offline-paraformer-decoder.h"
1617
#include "sherpa-onnx/csrc/offline-paraformer-greedy-search-decoder.h"
@@ -242,19 +243,18 @@ class OfflineRecognizerParaformerImpl : public OfflineRecognizerImpl {
242243
void ApplyCMVN(std::vector<float> *v) const {
243244
const std::vector<float> &neg_mean = model_->NegativeMean();
244245
const std::vector<float> &inv_stddev = model_->InverseStdDev();
246+
int32_t dim = static_cast<int32_t>(neg_mean.size());
247+
int32_t num_frames = static_cast<int32_t>(v->size()) / dim;
245248

246-
int32_t dim = neg_mean.size();
247-
int32_t num_frames = v->size() / dim;
249+
Eigen::Map<
250+
Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>
251+
mat(v->data(), num_frames, dim);
248252

249-
float *p = v->data();
253+
Eigen::Map<const Eigen::RowVectorXf> neg_mean_vec(neg_mean.data(), dim);
254+
Eigen::Map<const Eigen::RowVectorXf> inv_stddev_vec(inv_stddev.data(), dim);
250255

251-
for (int32_t i = 0; i != num_frames; ++i) {
252-
for (int32_t k = 0; k != dim; ++k) {
253-
p[k] = (p[k] + neg_mean[k]) * inv_stddev[k];
254-
}
255-
256-
p += dim;
257-
}
256+
mat.array() = (mat.array().rowwise() + neg_mean_vec.array()).rowwise() *
257+
inv_stddev_vec.array();
258258
}
259259

260260
OfflineRecognizerConfig config_;

sherpa-onnx/csrc/offline-recognizer-sense-voice-impl.h

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <utility>
1212
#include <vector>
1313

14+
#include "Eigen/Dense"
1415
#include "sherpa-onnx/csrc/macros.h"
1516
#include "sherpa-onnx/csrc/offline-ctc-greedy-search-decoder.h"
1617
#include "sherpa-onnx/csrc/offline-model-config.h"
@@ -402,22 +403,18 @@ class OfflineRecognizerSenseVoiceImpl : public OfflineRecognizerImpl {
402403

403404
void ApplyCMVN(std::vector<float> *v) const {
404405
const auto &meta_data = model_->GetModelMetadata();
405-
406406
const std::vector<float> &neg_mean = meta_data.neg_mean;
407407
const std::vector<float> &inv_stddev = meta_data.inv_stddev;
408-
409-
int32_t dim = neg_mean.size();
410-
int32_t num_frames = v->size() / dim;
411-
412-
float *p = v->data();
413-
414-
for (int32_t i = 0; i != num_frames; ++i) {
415-
for (int32_t k = 0; k != dim; ++k) {
416-
p[k] = (p[k] + neg_mean[k]) * inv_stddev[k];
417-
}
418-
419-
p += dim;
420-
}
408+
int32_t dim = static_cast<int32_t>(neg_mean.size());
409+
int32_t num_frames = static_cast<int32_t>(v->size()) / dim;
410+
Eigen::Map<
411+
Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>
412+
mat(v->data(), num_frames, dim);
413+
Eigen::Map<const Eigen::RowVectorXf> neg_mean_vec(neg_mean.data(), dim);
414+
415+
Eigen::Map<const Eigen::RowVectorXf> inv_stddev_vec(inv_stddev.data(), dim);
416+
mat.array() = (mat.array().rowwise() + neg_mean_vec.array()).rowwise() *
417+
inv_stddev_vec.array();
421418
}
422419

423420
SymbolTable symbol_table_;

0 commit comments

Comments
 (0)