Skip to content

support python mlx.array creation from list of mlx.array's - #325

Merged
awni merged 5 commits into
ml-explore:mainfrom
Alex-X-W:mutexuan_array_from_arrays
Jan 5, 2024
Merged

awni merged 5 commits into
ml-explore:mainfrom
Alex-X-W:mutexuan_array_from_arrays

Conversation

@Alex-X-W

Copy link
Copy Markdown
Contributor

Proposed changes

Adds support for python mlx.array create from list of mlx.array's. Trying to tackle issue 287.

Checklist

Put an x in the boxes that apply.

  • I have read the CONTRIBUTING document
  • I have run pre-commit run --all-files to format my code / installed pre-commit prior to committing changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have updated the necessary documentation (if needed)

Comment thread python/src/array.cpp
Comment on lines -19 to -25
enum PyScalarT {
pybool = 0,
pyint = 1,
pyfloat = 2,
pycomplex = 3,
};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace it with Dtype since we'd have to deal with all possible mlx dtypes when create from list of mlx.arrays. Not just python primitive types.

Comment thread mlx/dtype.h Outdated

Val val;
const uint8_t size;
uint8_t size;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have to drop the constness here since we'd need the Dtype to be copy-assignable, which we are going to need in validate_shape() so that we have a Dtype defined first and assigned later to get the final promoted type.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you keep this const? See comment below.

Comment thread python/src/array.cpp Outdated
fill_vector(l.template cast<py::tuple>(), vals);
} else if (py::isinstance<array>(l)) {
auto arr = py::cast<array>(l);
arr.eval();

@Alex-X-W Alex-X-W Dec 31, 2023 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to call eval() here since arr may come from transformations so that it does not hold the actual raw data.. without the eval() then the data<T>() pointer would be uninitialized (actually zero) so that we would end up with segmentation fault.

@awni

awni commented Dec 31, 2023

Copy link
Copy Markdown
Member

Thanks for adding this. I was thinking we should use concatenate and have this be part of the compute graph rather than force an eval.

Concretely when you do mx.array([a1, ...]), it's basically using concatenate under the hood. Does that make sense?

@Alex-X-W

Copy link
Copy Markdown
Contributor Author

@awni Thanks. I see, yeah that makes sense. I am gonna renovate the code a bit.

@Alex-X-W
Alex-X-W force-pushed the mutexuan_array_from_arrays branch from 352c625 to ebe6ddd Compare January 1, 2024 08:36
Comment thread python/src/array.cpp
Comment on lines +237 to +169
throw std::invalid_argument(
"Initialization encountered non-uniform length.");
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested in assertRaises here.

Comment thread python/src/array.cpp
Comment on lines -184 to -186
size_t size = 1;
for (auto s : shape) {
size *= s;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason the size here is never used, so I deleted this block.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, good catch.

@Alex-X-W

Alex-X-W commented Jan 1, 2024

Copy link
Copy Markdown
Contributor Author

Hi @awni ! May I bug you to review again? Thanks! And... Happy New Year 🥳

@awni
awni self-requested a review January 2, 2024 18:21
@awni

awni commented Jan 2, 2024

Copy link
Copy Markdown
Member

Hi @Alex-X-W ,

There's two things I think we need to fix with this:

  1. The main point to using stack is to avoid needing to eval the input arrays. I see that you eval them then get the raw pointer based on the type and copy it. That should change to be much simpler. Just put the arrays in a vector, the stack will handle type promotion.
  2. If you are creating an array from a big list (or list of lists) of python scalars it will be really inefficient to convert these to arrays and do a stack. I would change the code so that when there is no nested array it follows a different path and puts the values in vector of the appropriate type and then uses the array constructor.

@awni awni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Left a comment with requested changes.

@Alex-X-W
Alex-X-W force-pushed the mutexuan_array_from_arrays branch from ebe6ddd to 97ad937 Compare January 3, 2024 04:54
Comment thread python/src/array.cpp

if (all_python_primitive_elements) {
// `pl` does not contain mlx arrays
return array_from_list(pl, type, dtype, shape);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code path handles only the code path when all elements are python primitive types

mx.int64,
mx.float16,
mx.float32,
mx.bfloat16,

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added bfloat16 given #339 is merged.

@Alex-X-W
Alex-X-W requested a review from awni January 3, 2024 05:01

@awni awni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking really nice!!!

My main comment is I would prefer not to undo the constness of the dtype.val unless there is a strong need and I don't think there is in this case (see inline comments). Could you make the change, then I think we are good to go?

Comment thread mlx/dtype.h Outdated

Val val;
const uint8_t size;
uint8_t size;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you keep this const? See comment below.

Comment thread python/src/array.cpp Outdated
} else if (py::isinstance<array>(l)) {
all_python_primitive_elements = false;
auto arr = py::cast<array>(l);
if (arr.shape().size() + idx + 1 == shape.size() &&

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: arr.ndim() instead of arr.shape().size()

Comment thread python/src/array.cpp Outdated
Dtype type = bool_;
for (auto l : list) {
PyScalarT t;
Dtype t = bool_;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid needing to change the const of the dtype value, you could revert this to use the PyScalarT and if you get an array, just set it bool_ (lowest order type) and let the stack handle the type promotion if you actually get any arrays (which it already will by default).

Alternatively you could put it in a lambda and add the types to a vector and the reduce the vector with promotion

I think the first option is simpler though.

@Alex-X-W

Alex-X-W commented Jan 4, 2024

Copy link
Copy Markdown
Contributor Author

Thank you @awni for the pointers! They all make sense to me.
Please help take another look, thanks!

@Alex-X-W
Alex-X-W requested a review from awni January 4, 2024 06:02

@awni awni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!! I left one more nit. Could you check it then we can merge?

Comment thread python/src/array.cpp
return get_shape(l.template cast<py::tuple>(), shape);
} else if (py::isinstance<array>(l)) {
auto arr = py::cast<array>(l);
shape.insert(shape.end(), arr.shape().begin(), arr.shape().end());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think shape = arr.shape() is sufficient here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@awni no...
if a, b = mx.array([1]), mx.array([2])
then in evaluating mx.array([a, b]) on line 198 with shape = arr.shape() we'd end up with shape = [1], but actually the shape should be [2, 1]. shape = arr.shape() will drop a dimension in the process of list unrolling at line 189. You will see what I mean if you see the full block of the get_shape()

mlx/python/src/array.cpp

Lines 188 to 202 in 1bcee20

void get_shape(T list, std::vector<int>& shape) {
shape.push_back(py::len(list));
if (shape.back() > 0) {
auto& l = *list.begin();
if (py::isinstance<py::list>(l)) {
return get_shape(l.template cast<py::list>(), shape);
} else if (py::isinstance<py::tuple>(l)) {
return get_shape(l.template cast<py::tuple>(), shape);
} else if (py::isinstance<array>(l)) {
auto arr = py::cast<array>(l);
shape.insert(shape.end(), arr.shape().begin(), arr.shape().end());
return;
}
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I misunderstood this code, thanks for clarifying.

@awni

awni commented Jan 4, 2024

Copy link
Copy Markdown
Member

PS checked cpu/gpu tests pass + formatting passes

@awni awni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Thanks so much for adding this!

@awni
awni merged commit d8f41a5 into ml-explore:main Jan 5, 2024
zcbenz pushed a commit to Ritabanm/mlx that referenced this pull request Sep 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants