Skip to content

Commit 7ba0668

Browse files
committed
supprot full reduction
Signed-off-by: kerthcet <kerthcet@gmail.com>
1 parent 064f24c commit 7ba0668

1 file changed

Lines changed: 96 additions & 7 deletions

File tree

‎crates/mlxcore/src/array.rs‎

Lines changed: 96 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,46 @@ macro_rules! impl_scalar_lhs_binop {
946946
// Every type with an `ArrayElement` impl, so the two directions stay symmetric.
947947
impl_scalar_lhs_binop!(bool, u8, u16, u32, u64, i8, i16, i32, i64, f32, f64);
948948

949+
// `a += &b`, for both array and scalar right-hand sides.
950+
//
951+
// These do **not** mutate in place. MLX has no in-place ops: the op builds a new
952+
// array and `a` is rebound to it, dropping the old handle. So `a += &b` is
953+
// exactly `a = &a + &b` with less typing, and it saves no memory. Two visible
954+
// consequences: `a` must be owned and `mut` (not a `&Array`), and the dtype can
955+
// change under it, since MLX promotes — `a += 1.0f64` leaves `a` float64.
956+
//
957+
// Same default-stream and panic-on-failure contract as the operators above.
958+
macro_rules! impl_op_assign {
959+
($($trait:ident :: $method:ident => $op:ident),* $(,)?) => {
960+
$(
961+
impl std::ops::$trait<&Array> for Array {
962+
fn $method(&mut self, rhs: &Array) {
963+
*self = self.$op(rhs, &Stream::default()).unwrap_or_else(|e| {
964+
panic!(concat!("Array::", stringify!($op), " failed: {}"), e)
965+
});
966+
}
967+
}
968+
969+
impl<T: ArrayElement> std::ops::$trait<T> for Array {
970+
fn $method(&mut self, rhs: T) {
971+
*self = self
972+
.$op(&Array::from_scalar(rhs), &Stream::default())
973+
.unwrap_or_else(|e| {
974+
panic!(concat!("Array::", stringify!($op), " failed: {}"), e)
975+
});
976+
}
977+
}
978+
)*
979+
};
980+
}
981+
982+
impl_op_assign! {
983+
AddAssign::add_assign => add,
984+
SubAssign::sub_assign => subtract,
985+
MulAssign::mul_assign => multiply,
986+
DivAssign::div_assign => divide,
987+
}
988+
949989
#[cfg(test)]
950990
mod tests {
951991
use super::*;
@@ -1317,12 +1357,6 @@ mod tests {
13171357
assert_eq!(ints.add(&floats, &s).unwrap().dtype(), Dtype::Float32);
13181358
}
13191359

1320-
#[test]
1321-
#[should_panic(expected = "array dtype float32 does not match")]
1322-
fn reading_the_wrong_element_type_panics() {
1323-
let _ = Array::from_slice(&[1.0f32, 2.0], &[2]).to_vec::<i32>();
1324-
}
1325-
13261360
#[test]
13271361
fn comparisons_produce_bool_masks() {
13281362
let s = Stream::cpu();
@@ -1728,7 +1762,7 @@ mod tests {
17281762
}
17291763

17301764
#[test]
1731-
#[should_panic(expected = "does not match requested element type")]
1765+
#[should_panic(expected = "array dtype int32 does not match requested element type")]
17321766
fn to_vec_wrong_dtype_panics() {
17331767
let ints = Array::from_slice(&[1i32, 2, 3], &[3]);
17341768
let _ = ints.to_vec::<f32>();
@@ -1759,6 +1793,61 @@ mod tests {
17591793
assert_eq!(a.to_vec::<f32>(), vec![10.0, 20.0, 30.0]);
17601794
}
17611795

1796+
#[test]
1797+
fn assigning_operators_match_methods() {
1798+
let b = Array::from_slice(&[1.0f32, 2.0, 3.0], &[3]);
1799+
1800+
let mut a = Array::from_slice(&[10.0f32, 20.0, 30.0], &[3]);
1801+
a += &b;
1802+
assert_eq!(a.to_vec::<f32>(), vec![11.0, 22.0, 33.0]);
1803+
a -= &b;
1804+
assert_eq!(a.to_vec::<f32>(), vec![10.0, 20.0, 30.0]);
1805+
a *= &b;
1806+
assert_eq!(a.to_vec::<f32>(), vec![10.0, 40.0, 90.0]);
1807+
a /= &b;
1808+
assert_eq!(a.to_vec::<f32>(), vec![10.0, 20.0, 30.0]);
1809+
1810+
// The right-hand side is borrowed, so `b` survives all four.
1811+
assert_eq!(b.to_vec::<f32>(), vec![1.0, 2.0, 3.0]);
1812+
}
1813+
1814+
#[test]
1815+
fn assigning_operators_take_scalars() {
1816+
let mut a = Array::from_slice(&[10.0f32, 20.0], &[2]);
1817+
a += 5.0f32;
1818+
assert_eq!(a.to_vec::<f32>(), vec![15.0, 25.0]);
1819+
a *= 2.0f32;
1820+
assert_eq!(a.to_vec::<f32>(), vec![30.0, 50.0]);
1821+
a -= 10.0f32;
1822+
assert_eq!(a.to_vec::<f32>(), vec![20.0, 40.0]);
1823+
a /= 4.0f32;
1824+
assert_eq!(a.to_vec::<f32>(), vec![5.0, 10.0]);
1825+
}
1826+
1827+
#[test]
1828+
fn assigning_operators_rebind_rather_than_mutate() {
1829+
// Broadcasting means the result need not even have the same shape as the
1830+
// original, which an in-place op could not do.
1831+
let mut a = Array::from_slice(&[1.0f32, 2.0], &[2]);
1832+
a += &Array::from_slice(&[10.0f32, 20.0, 30.0, 40.0], &[2, 2]);
1833+
assert_eq!(a.shape(), vec![2, 2]);
1834+
assert_eq!(a.to_vec::<f32>(), vec![11.0, 22.0, 31.0, 42.0]);
1835+
1836+
// And MLX's promotion applies, so the dtype can change under `a`.
1837+
let mut ints = Array::from_slice(&[1i32, 2], &[2]);
1838+
assert_eq!(ints.dtype(), Dtype::Int32);
1839+
ints *= 0.5f32;
1840+
assert_eq!(ints.dtype(), Dtype::Float32);
1841+
assert_eq!(ints.to_vec::<f32>(), vec![0.5, 1.0]);
1842+
}
1843+
1844+
#[test]
1845+
#[should_panic(expected = "Array::subtract failed: MLX error:")]
1846+
fn assigning_operator_panic_carries_mlx_message() {
1847+
let mut a = Array::from_slice(&[1.0f32, 2.0, 3.0], &[3]);
1848+
a -= &Array::from_slice(&[1.0f32, 2.0], &[2]);
1849+
}
1850+
17621851
#[test]
17631852
#[should_panic(expected = "Array::add failed: MLX error:")]
17641853
fn operator_panic_carries_mlx_message() {

0 commit comments

Comments
 (0)