1- // emily_nif.cpp — minimal M0 surface : tensor round-trip.
1+ // emily_nif.cpp — core NIFs : tensor resource, round-trip, eval .
22//
3- // The Tensor resource wraps an mlx::core::array. MLX arrays are
4- // reference-counted internally; our ResourcePtr<Tensor> just adds one
5- // BEAM-managed ref. No manual atomics, no custom destructor — fine and
6- // MLX together do the right thing.
3+ // Op NIFs live in c_src/ops/*.cpp; they share the Tensor resource
4+ // defined here via emily/tensor.hpp.
5+
6+ # include " emily/tensor.hpp "
77
88#include < fine.hpp>
99#include < mlx/mlx.h>
1212#include < cstring>
1313#include < stdexcept>
1414#include < string>
15+ #include < tuple>
1516#include < vector>
1617
1718namespace mx = mlx::core;
18-
19- // ---------- dtype mapping ----------
20-
21- namespace {
22-
23- mx::Dtype to_mlx_dtype (const std::string &kind, int64_t bits) {
24- if (kind == " f" && bits == 32 ) return mx::float32;
25- if (kind == " f" && bits == 16 ) return mx::float16;
26- if (kind == " bf" && bits == 16 ) return mx::bfloat16;
27- if (kind == " s" && bits == 8 ) return mx::int8;
28- if (kind == " s" && bits == 16 ) return mx::int16;
29- if (kind == " s" && bits == 32 ) return mx::int32;
30- if (kind == " s" && bits == 64 ) return mx::int64;
31- if (kind == " u" && bits == 8 ) return mx::uint8;
32- if (kind == " u" && bits == 16 ) return mx::uint16;
33- if (kind == " u" && bits == 32 ) return mx::uint32;
34- if (kind == " u" && bits == 64 ) return mx::uint64;
35- if (kind == " c" && bits == 64 ) return mx::complex64;
36- if (kind == " pred" ) return mx::bool_;
37-
38- throw std::invalid_argument (
39- " unsupported dtype: {" + kind + " , " + std::to_string (bits) + " }" );
40- }
41-
42- std::tuple<fine::Atom, int64_t > from_mlx_dtype (mx::Dtype dtype) {
43- if (dtype == mx::float32) return {fine::Atom (" f" ), 32 };
44- if (dtype == mx::float16) return {fine::Atom (" f" ), 16 };
45- if (dtype == mx::bfloat16) return {fine::Atom (" bf" ), 16 };
46- if (dtype == mx::int8) return {fine::Atom (" s" ), 8 };
47- if (dtype == mx::int16) return {fine::Atom (" s" ), 16 };
48- if (dtype == mx::int32) return {fine::Atom (" s" ), 32 };
49- if (dtype == mx::int64) return {fine::Atom (" s" ), 64 };
50- if (dtype == mx::uint8) return {fine::Atom (" u" ), 8 };
51- if (dtype == mx::uint16) return {fine::Atom (" u" ), 16 };
52- if (dtype == mx::uint32) return {fine::Atom (" u" ), 32 };
53- if (dtype == mx::uint64) return {fine::Atom (" u" ), 64 };
54- if (dtype == mx::complex64) return {fine::Atom (" c" ), 64 };
55- if (dtype == mx::bool_) return {fine::Atom (" pred" ), 1 };
56- throw std::runtime_error (" unmapped mlx dtype" );
57- }
58-
59- } // namespace
60-
61- // ---------- Tensor resource ----------
62-
63- class Tensor {
64- public:
65- Tensor (mx::array a) : array(std::move(a)) {}
66- mx::array array;
67- };
19+ using emily::Tensor;
20+ using emily::from_mlx_dtype;
21+ using emily::to_mlx_dtype;
22+ using emily::to_mlx_shape;
23+ using emily::wrap;
6824
6925FINE_RESOURCE (Tensor);
7026
71- // ---------- NIFs ----------
27+ // ---------- Core NIFs ----------
7228
7329// from_binary/3 — build a lazy MLX array from a BEAM binary.
7430// Regular scheduler: MLX copies the buffer into its own storage during
@@ -79,15 +35,11 @@ fine::ResourcePtr<Tensor> from_binary(
7935 std::vector<int64_t > shape,
8036 std::tuple<fine::Atom, int64_t > dtype_tuple) {
8137
82- auto kind = std::get<0 >(dtype_tuple).to_string ();
83- auto bits = std::get<1 >(dtype_tuple);
84- auto dtype = to_mlx_dtype (kind, bits);
85-
86- std::vector<int > shape_ints (shape.begin (), shape.end ());
38+ auto dtype = to_mlx_dtype (dtype_tuple);
39+ auto shape_ints = to_mlx_shape (shape);
8740
8841 int64_t nelem = 1 ;
8942 for (auto d : shape_ints) {
90- if (d < 0 ) throw std::invalid_argument (" negative dimension" );
9143 nelem *= d;
9244 }
9345
@@ -106,17 +58,23 @@ fine::ResourcePtr<Tensor> from_binary(
10658 auto deleter = [](mx::allocator::Buffer b) { mx::allocator::free (b); };
10759
10860 mx::array arr (buf, std::move (shape_ints), dtype, deleter);
109- return fine::make_resource<Tensor> (std::move (arr));
61+ return wrap (std::move (arr));
11062}
11163FINE_NIF (from_binary, 0 );
11264
11365// to_binary/1 — materialize the array and return its bytes as a binary.
11466// Dirty CPU: eval() triggers kernel launch and waits for completion.
67+ //
68+ // We route through mx::contiguous() first so views with non-standard
69+ // strides (transpose, slice, swapaxes, broadcast_to) produce the
70+ // correct in-memory layout. For already-contiguous arrays MLX elides
71+ // the copy.
11572std::string to_binary (ErlNifEnv *, fine::ResourcePtr<Tensor> tensor) {
116- mx::eval (tensor->array );
73+ auto materialized = mx::contiguous (tensor->array );
74+ mx::eval (materialized);
11775
118- const void *src = tensor-> array .data <void >();
119- size_t nbytes = tensor-> array .nbytes ();
76+ const void *src = materialized .data <void >();
77+ size_t nbytes = materialized .nbytes ();
12078
12179 std::string out;
12280 out.resize (nbytes);
0 commit comments