Skip to content

Commit 2a554bc

Browse files
katlun-lgtmzcbenz
authored andcommitted
feat(array-api): add empty, empty_like, astype, matrix_transpose
empty/empty_like are pure aliases of zeros/zeros_like via m.attr("empty") = m.attr("zeros"), matching the pattern from ml-explore#3678. MLX does not expose uninitialized memory so zeros is the correct semantic match. astype exposes mx::astype as a free function (Array API §2.0). matrix_transpose transposes the last two dimensions and validates ndim >= 2. Docs and tests included. Part of the array API split from ml-explore#3684.
1 parent 6335c83 commit 2a554bc

3 files changed

Lines changed: 81 additions & 0 deletions

File tree

docs/src/python/ops.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ Operations
2929
array_equal
3030
asarray
3131
as_strided
32+
astype
3233
atleast_1d
3334
atleast_2d
3435
atleast_3d
@@ -73,6 +74,8 @@ Operations
7374
divmod
7475
einsum
7576
einsum_path
77+
empty
78+
empty_like
7679
equal
7780
erf
7881
erfinv
@@ -121,6 +124,7 @@ Operations
121124
logical_or
122125
logsumexp
123126
matmul
127+
matrix_transpose
124128
max
125129
maximum
126130
mean

python/src/ops.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5852,4 +5852,54 @@ void init_ops(nb::module_& m) {
58525852
m.attr("pow") = m.attr("power");
58535853
m.attr("bitwise_left_shift") = m.attr("left_shift");
58545854
m.attr("bitwise_right_shift") = m.attr("right_shift");
5855+
// Array API: empty / empty_like — pure aliases of zeros / zeros_like.
5856+
// MLX does not expose uninitialized memory, so zeros are a correct
5857+
// semantic match.
5858+
m.attr("empty") = m.attr("zeros");
5859+
m.attr("empty_like") = m.attr("zeros_like");
5860+
// Array API free-function wrappers.
5861+
m.def(
5862+
"astype",
5863+
[](const mx::array& a, mx::Dtype dtype, mx::StreamOrDevice s) {
5864+
return mx::astype(a, dtype, s);
5865+
},
5866+
nb::arg(),
5867+
"dtype"_a,
5868+
nb::kw_only(),
5869+
"stream"_a = nb::none(),
5870+
nb::sig(
5871+
"def astype(a: array, dtype: Dtype, /, *, stream: Union[None, Stream, Device] = None) -> array"),
5872+
R"pbdoc(
5873+
Cast the array to the given type. See also :meth:`array.astype`.
5874+
5875+
Args:
5876+
a (array): Input array.
5877+
dtype (Dtype): The type to cast to.
5878+
5879+
Returns:
5880+
array: The array cast to ``dtype``.
5881+
)pbdoc");
5882+
m.def(
5883+
"matrix_transpose",
5884+
[](const mx::array& a, mx::StreamOrDevice s) {
5885+
if (a.ndim() < 2) {
5886+
throw std::invalid_argument(
5887+
"[matrix_transpose] Input must have at least 2 dimensions.");
5888+
}
5889+
return mx::swapaxes(a, -2, -1, s);
5890+
},
5891+
nb::arg(),
5892+
nb::kw_only(),
5893+
"stream"_a = nb::none(),
5894+
nb::sig(
5895+
"def matrix_transpose(a: array, /, *, stream: Union[None, Stream, Device] = None) -> array"),
5896+
R"pbdoc(
5897+
Transpose the last two dimensions of an array.
5898+
5899+
Args:
5900+
a (array): Input array with at least two dimensions.
5901+
5902+
Returns:
5903+
array: The array with its last two dimensions transposed.
5904+
)pbdoc");
58555905
}

python/tests/test_ops.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3446,6 +3446,33 @@ def test_to_from_fp8(self):
34463446
self.assertTrue(mx.array_equal(mx.from_fp8(mx.to_fp8(vals)), vals))
34473447
self.assertTrue(mx.array_equal(mx.from_fp8(mx.to_fp8(-vals)), -vals))
34483448

3449+
def test_array_api_creation(self):
3450+
a = mx.arange(6, dtype=mx.int16).reshape(2, 3)
3451+
3452+
e = mx.empty((2, 3))
3453+
self.assertEqual(e.shape, (2, 3))
3454+
self.assertEqual(e.dtype, mx.float32)
3455+
self.assertEqual(mx.empty((4,), dtype=mx.int32).dtype, mx.int32)
3456+
3457+
el = mx.empty_like(a)
3458+
self.assertEqual(el.shape, (2, 3))
3459+
self.assertEqual(el.dtype, mx.int16)
3460+
self.assertEqual(mx.empty_like(a, dtype=mx.float32).dtype, mx.float32)
3461+
3462+
def test_astype_and_matrix_transpose(self):
3463+
a = mx.array([1, 2, 3], dtype=mx.int32)
3464+
self.assertEqual(mx.astype(a, mx.float32).dtype, mx.float32)
3465+
self.assertTrue(mx.array_equal(mx.astype(a, mx.float32), a.astype(mx.float32)))
3466+
3467+
m = mx.arange(6).reshape(2, 3)
3468+
self.assertEqual(mx.matrix_transpose(m).shape, (3, 2))
3469+
self.assertTrue(mx.array_equal(mx.matrix_transpose(m), mx.swapaxes(m, -2, -1)))
3470+
# Batched.
3471+
b = mx.arange(24).reshape(2, 3, 4)
3472+
self.assertEqual(mx.matrix_transpose(b).shape, (2, 4, 3))
3473+
with self.assertRaises(ValueError):
3474+
mx.matrix_transpose(mx.array([1, 2, 3]))
3475+
34493476

34503477
if __name__ == "__main__":
34513478
mlx_tests.MLXTestRunner()

0 commit comments

Comments
 (0)