-
Notifications
You must be signed in to change notification settings - Fork 141
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added MulAdd
and MulAddAssign
traits
#59
Conversation
Is this actually useful to you for integers? Otherwise, you can just use |
src/ops/mul_add.rs
Outdated
#[inline] | ||
fn mul_add(self, a: Self, b: Self) -> Self::Output { | ||
#![allow(unconditional_recursion)] | ||
f64::mul_add(self, a, b) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe this allow
is masking a real problem. In a #![no_std]
build, f64
doesn't have most of its inherent methods, which means this call will resolve back to MulAdd::mul_add
again -- thus the unconditional recursion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh #![no_std]
is what made the builds fail on CI, yet succeed on my local machine.
Adding the #![allow(unconditional_recursion)]
felt fishy.
How would you propose solving this? Something like this?
if cfg!(feature = "std") {
f64::mul_add(self, a, b)
} else {
(self * a) + b
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to not implement the trait at all for floats in #![no_std]
mode, so we don't mislead about the rounding errors. Either that, or do a manual implementation with appropriate rounding fixups, but I imagine that's non-trivial.
It's useful for writing code that makes use of the optimization if applicable, yet remains fully generic. A particular use case is fixed-point arithmetic on I'm currently on and off working on making japaric/fpa usable as a proper replacement for I'd like to be able to write algebraic code that makes full use of all available optimized code paths if applicable, yet remains code-compatible with environments if reduced sophistication. |
Unfortunately, in my experience this is not necessarily true. For |
This does not invalidate the desire to have a way to generically express |
No, performance is not really related to the changes in this PR. What might be problematic is that Also see rust-lang/rust#44805:
|
Good point, I'll gladly add a mention of this to the documentation. :) Any further feedback? What needs to be done to proceed? Do we want to proceed? |
Note there's Supporting the I still think we should not have a |
I just moved the impls of |
src/ops/mul_add.rs
Outdated
let x: $t = 3.4; | ||
let b: $t = 5.6; | ||
|
||
let abs_difference = (m.mul_add(x, b) - (m*x + b)).abs(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised this test didn't fail on no_std
, but I think it's because libtest
links to libstd
, so the compiler still has the inherent impls of f32
/f64::mul_add
available. Please change these tests to explicitly call MulAdd::mul_add(m, x, b)
, and then this test will need a cfg
gate.
bors r+ |
59: Added `MulAdd` and `MulAddAssign` traits r=cuviper a=regexident Both `f32` and `f64` implement fused multiply-add, which computes `(self * a) + b` with only one rounding error. This produces a more accurate result with better performance than a separate multiplication operation followed by an add: ```rust fn mul_add(self, a: f32, b: f32) -> f32[src] ``` It is however not possible to make use of this in a generic context by abstracting over a trait. My concrete use-case is machine learning, [gradient descent](https://en.wikipedia.org/wiki/Gradient_descent) to be specific, where the core operation of updating the gradient could make use of `mul_add` for both its `weights: Vector` as well as its `bias: f32`: ```rust struct Perceptron { weights: Vector, bias: f32, } impl MulAdd<f32, Self> for Vector { // ... } impl Perceptron { fn learn(&mut self, example: Vector, expected: f32, learning_rate: f32) { let alpha = self.error(example, expected, learning_rate); self.weights = example.mul_add(alpha, self.weights); self.bias = self.bias.mul_add(alpha, self.bias) } } ``` (The actual impl of `Vector` would be generic over its value type: `Vector<T>`, thus requiring the trait.) Co-authored-by: Vincent Esche <[email protected]> Co-authored-by: Josh Stone <[email protected]>
Build succeeded |
Both
f32
andf64
implement fused multiply-add, which computes(self * a) + b
with only one rounding error. This produces a more accurate result with better performance than a separate multiplication operation followed by an add:It is however not possible to make use of this in a generic context by abstracting over a trait.
My concrete use-case is machine learning, gradient descent to be specific,
where the core operation of updating the gradient could make use of
mul_add
for both itsweights: Vector
as well as itsbias: f32
:(The actual impl of
Vector
would be generic over its value type:Vector<T>
, thus requiring the trait.)