Repository navigation
Restore vectorized PRelu for per-channel slopes on CPU - #33996
Chinmay Shrivastava (JonSnow1807) wants to merge 2 commits into
Conversation
microsoft#28750 replaced the branch-free PRelu formula with a per-element scalar ternary loop to stop +/-inf inputs producing NaN. The loop is not vectorized, which made per-channel PRelu (for example X [1,64,56,56] with slope [64,1,1]) roughly 20x slower on the CPU EP. Express the same per-lane selection with Eigen select() in the two span functors. select() evaluates both arms and keeps one, never adding them, so a non-finite value in the discarded arm cannot leak into the result and the inf/NaN behavior from microsoft#28750 is unchanged. Add a per-channel test with spans longer than a SIMD packet and inf, NaN and signed-zero inputs. Fixes microsoft#33831
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
🟡 Changes recommended
The signed-zero tests can miss incorrect output signs because they use EXPECT_NEAR and only positive slopes, requiring explicit signbit checks and a negative-slope case.
1 open issue
What changed in this PR
Restores vectorized CPU float PRelu processing to address #33831’s performance regression while preserving the infinity and NaN handling introduced by #28750.
Changes:
- Replaces scalar loops with Eigen
select()for scalar-slope and elementwise-slope spans. - Adds a multi-channel 4-D regression test with finite values, infinities, NaNs, and signed zeros.
| File | Description |
|---|---|
onnxruntime/test/providers/cpu/activation/activation_op_test.cc |
Adds per-channel special-value coverage across vector-length spans. |
onnxruntime/core/providers/cpu/math/element_wise_ops.cc |
Uses Eigen selection to restore vectorization in two PRelu broadcast paths. |
Review effort: Balanced
| const std::vector<float> slopes{0.5f, 2.0f}; | ||
|
|
||
| std::vector<float> inputs; | ||
| std::vector<float> outputs; | ||
| const float specials[] = {pos_inf, neg_inf, nan_val, 0.0f, -0.0f, 3.0f, -3.0f}; | ||
| for (int i = 0; i < 50; ++i) { | ||
| const float x = (i % 3 == 0) ? specials[(i / 3) % 7] : static_cast<float>(i) - 25.0f; | ||
| inputs.push_back(x); | ||
| // NaN compares false, so it takes the multiply branch and stays NaN. | ||
| outputs.push_back(x >= 0.0f ? x : x * slopes[i / 25]); | ||
| } | ||
|
|
||
| OpTester test("PRelu"); | ||
| test.AddInput<float>("X", x_dims, inputs); | ||
| test.AddInput<float>("slope", slope_dims, slopes); | ||
| test.AddOutput<float>("Y", x_dims, outputs); |
There was a problem hiding this comment.
Agreed, the float checker treats +0 and -0 as equal and both slopes were positive. The second slope is now negative, and a custom verifier checks every value (NaN, exact infinity, the rest with EXPECT_FLOAT_EQ) plus, on the CPU EP, the sign of each zero output. The sign check is CPU-only because CUDA's PRelu (a > 0 ? a : a * b) gives -0 for a +0 input with a negative slope. I checked that the test fails when the kernel gets the sign of zero wrong and passes with this PR.
|
@microsoft-github-policy-service agree |
The default float checker compares with EXPECT_NEAR, so +0 and -0 are equal and a kernel that multiplies a zero input by the slope passed the test. Make the second slope negative so such a multiply flips the sign, and verify the output in a custom verifier: shape, NaN, exact infinity, the other values with EXPECT_FLOAT_EQ, and on CPU the sign of each zero.

Description
Fixes #33831. Since #28750, the float PRelu span functors use a per-element ternary to keep
x * slopefrom turning ±inf into NaN. The ternary doesn’t vectorize, so PRelu with a per-channel slope became ~21x slower. This replaces both functors with Eigenselect(), which keeps #28750’s inf/NaN behaviour and vectorizes again.Motivation and Context
Measured with the issue’s repro (4-D input, per-channel slope), one thread,
taskseton one core, median of 9 fresh processes, GCC 14, x86-64: 1345 µs → 65 µs. The same-shape slope case goes 1349 µs → 63 µs. Output is bit-exact againstnumpy.where(x >= 0, x, slope * x)including inf, NaN and -0. New testActivationOpTest.PRelu_MultiChannel4D_SpecialValues; the PRelu, activation and broadcast tests pass, 548 tests, 0 failures. MSVC was not measured.AI usage: code drafted by both human and AI, and reviewed by human.