Skip to content

Commit 4502664

Browse files
authored
fix: proper gpu support for linalg (#113)
* fix: add proper gpu support for linalg ops * fix: no-op for same device * chore: format
1 parent 52cbc5f commit 4502664

8 files changed

Lines changed: 236 additions & 32 deletions

File tree

emlx/c_src/emlx_nif.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,13 @@ NIF(eval) {
495495
return nx::nif::ok(env);
496496
}
497497

498+
NIF(to_device) {
499+
TENSOR_PARAM(0, t);
500+
DEVICE_PARAM(1, device);
501+
TENSOR(mlx::core::contiguous(*t, false, device));
502+
}
503+
ASYNC_NIF(to_device)
504+
498505
NIF(stack) {
499506
LIST_PARAM(0, std::vector<mlx::core::array>, arrays);
500507
PARAM(1, int, axis);
@@ -1797,6 +1804,7 @@ ASYNC_NIF(window_scatter_min)
17971804

17981805
static ErlNifFunc nif_funcs[] = {
17991806
{"eval", 2, eval_async},
1807+
{"to_device", 3, to_device_async},
18001808
{"to_blob", 2, to_blob_async},
18011809
{"to_blob", 3, to_blob_async},
18021810
{"tensor_to_shm", 3, tensor_to_shm_async},

emlx/lib/emlx.ex

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,31 @@ defmodule EMLX do
11951195
await_worker(job_ref)
11961196
end
11971197

1198+
@doc """
1199+
Moves a tensor to `target_device` (`:cpu` or `:gpu`) without deallocating
1200+
the source. This is a no-op if the tensor is already on the target device.
1201+
1202+
Unlike `Nx.backend_transfer/2`, this does not round-trip through a binary
1203+
and does not call `backend_deallocate` on the original tensor. Internally
1204+
it schedules `mlx::core::contiguous(arr, target_device)` on the target
1205+
device's worker thread, which on Apple Silicon (unified memory) avoids any
1206+
physical data copy.
1207+
"""
1208+
def to_device(tensor, target_device)
1209+
1210+
def to_device({dev, _} = tensor, dev), do: tensor
1211+
1212+
def to_device({old_device, ref} = tensor, target_device)
1213+
when is_tensor(old_device, ref) and target_device in [:cpu, :gpu] do
1214+
# Materialize the source on its own device's thread before constructing the
1215+
# contiguous op. Without this, evaluating contiguous(lazy_gpu_array, cpu)
1216+
# on the CPU worker would traverse the graph and hit a missing GPU stream.
1217+
eval(tensor)
1218+
{worker, effective_device} = resolve_worker(target_device)
1219+
job_ref = EMLX.NIF.to_device(worker, ref, effective_device) |> unwrap!()
1220+
await_worker(job_ref) |> wrap_tensor(effective_device)
1221+
end
1222+
11981223
# ── Worker resolution ──────────────────────────────────────────────────────
11991224
#
12001225
# `Process.get(:emlx_command_queue)` is set by EMLX.CommandQueue.with_queue/2.

emlx/lib/emlx/backend.ex

Lines changed: 61 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -175,14 +175,14 @@ defmodule EMLX.Backend do
175175
# Preserve quantization_config when copying a quantized tensor to EMLX.Backend.
176176
# The generic backend_copy goes through to_binary/from_binary which drops the config.
177177
target_device = device_option(opts)
178-
gpu_opts = {EMLX.Backend, device: target_device}
178+
device_opts = {EMLX.Backend, device: target_device}
179179

180180
packed_size = Enum.reduce(Tuple.to_list(packed_shape), 1, &*/2)
181181
packed_binary = EMLX.to_blob(ref, packed_size)
182182
new_ref = EMLX.from_blob(packed_binary, packed_shape, :uint32, target_device)
183183

184-
new_scales = Nx.backend_transfer(cfg.scales, gpu_opts)
185-
new_biases = Nx.backend_transfer(cfg.biases, gpu_opts)
184+
new_scales = Nx.backend_copy(cfg.scales, device_opts)
185+
new_biases = Nx.backend_copy(cfg.biases, device_opts)
186186

187187
%T{
188188
shape: logical_shape,
@@ -1984,8 +1984,10 @@ defmodule EMLX.Backend do
19841984
raise ArgumentError, "can't solve for singular matrix"
19851985
end
19861986

1987-
a_typed = to_typed_ref(from_nx(a), a.type, {:f, 32})
1988-
b_mx = to_typed_ref(from_nx(b), b.type, {:f, 32})
1987+
{device, _} = from_nx(a)
1988+
1989+
a_typed = to_typed_ref(from_nx(a), a.type, {:f, 32}) |> EMLX.to_device(:cpu)
1990+
b_mx = to_typed_ref(from_nx(b), b.type, {:f, 32}) |> EMLX.to_device(:cpu)
19891991

19901992
upper = !opts[:lower]
19911993

@@ -2018,6 +2020,7 @@ defmodule EMLX.Backend do
20182020

20192021
out_mx
20202022
|> EMLX.astype(to_mlx_type(out.type))
2023+
|> EMLX.to_device(device)
20212024
|> to_nx(out)
20222025
end
20232026

@@ -2103,11 +2106,18 @@ defmodule EMLX.Backend do
21032106
end
21042107

21052108
@impl true
2106-
def block(%Nx.Block.LinAlg.Cholesky{}, out, [tensor], _fun) do
2107-
from_nx(tensor)
2108-
|> to_typed_ref(tensor.type, {:f, 32})
2109-
|> EMLX.linalg_cholesky(false)
2110-
|> to_nx(out)
2109+
def block(%Nx.Block.LinAlg.Cholesky{} = struct, out, [tensor], fun) do
2110+
{device, _ref} = t_mx = from_nx(tensor)
2111+
2112+
case device do
2113+
:cpu ->
2114+
t = to_typed_ref(t_mx, tensor.type, {:f, 32})
2115+
cholesky = EMLX.linalg_cholesky(t, false)
2116+
to_nx(cholesky, out)
2117+
2118+
:gpu ->
2119+
fun.(struct, tensor)
2120+
end
21112121
end
21122122

21132123
@impl true
@@ -2120,10 +2130,18 @@ defmodule EMLX.Backend do
21202130
end
21212131

21222132
@impl true
2123-
def block(%Nx.Block.LinAlg.QR{mode: :reduced}, {out_q, out_r}, [tensor], _fun) do
2124-
t = to_typed_ref(from_nx(tensor), tensor.type, {:f, 32})
2125-
{q, r} = EMLX.linalg_qr(t)
2126-
{to_nx(q, out_q), to_nx(r, out_r)}
2133+
def block(%Nx.Block.LinAlg.QR{mode: :reduced} = struct, {out_q, out_r}, [tensor], fun) do
2134+
{device, _ref} = t_mx = from_nx(tensor)
2135+
2136+
case device do
2137+
:cpu ->
2138+
t = to_typed_ref(t_mx, tensor.type, {:f, 32})
2139+
{q, r} = EMLX.linalg_qr(t)
2140+
{to_nx(q, out_q), to_nx(r, out_r)}
2141+
2142+
:gpu ->
2143+
fun.(struct, tensor)
2144+
end
21272145
end
21282146

21292147
@impl true
@@ -2133,17 +2151,38 @@ defmodule EMLX.Backend do
21332151
end
21342152

21352153
@impl true
2136-
def block(%Nx.Block.LinAlg.Eigh{}, {out_eigenvals, out_eigenvecs}, [tensor], _fun) do
2137-
t = to_typed_ref(from_nx(tensor), tensor.type, {:f, 32})
2138-
{eigenvalues, eigenvectors} = EMLX.linalg_eigh(t, :L)
2139-
{to_nx(eigenvalues, out_eigenvals), to_nx(eigenvectors, out_eigenvecs)}
2154+
def block(%Nx.Block.LinAlg.Eigh{} = struct, {out_eigenvals, out_eigenvecs}, [tensor], fun) do
2155+
{device, _ref} = t_mx = from_nx(tensor)
2156+
2157+
case device do
2158+
:cpu ->
2159+
t = to_typed_ref(t_mx, tensor.type, {:f, 32})
2160+
{eigenvalues, eigenvectors} = EMLX.linalg_eigh(t, :L)
2161+
{to_nx(eigenvalues, out_eigenvals), to_nx(eigenvectors, out_eigenvecs)}
2162+
2163+
:gpu ->
2164+
fun.(struct, tensor)
2165+
end
21402166
end
21412167

21422168
@impl true
2143-
def block(%Nx.Block.LinAlg.SVD{full_matrices?: true}, {out_u, out_s, out_v}, [tensor], _fun) do
2144-
t = to_typed_ref(from_nx(tensor), tensor.type, {:f, 32})
2145-
[u, s, vt] = EMLX.linalg_svd(t, true)
2146-
{to_nx(u, out_u), to_nx(s, out_s), to_nx(vt, out_v)}
2169+
def block(
2170+
%Nx.Block.LinAlg.SVD{full_matrices?: true} = struct,
2171+
{out_u, out_s, out_v},
2172+
[tensor],
2173+
fun
2174+
) do
2175+
{device, _ref} = t_mx = from_nx(tensor)
2176+
2177+
case device do
2178+
:cpu ->
2179+
t = to_typed_ref(t_mx, tensor.type, {:f, 32})
2180+
[u, s, vt] = EMLX.linalg_svd(t, true)
2181+
{to_nx(u, out_u), to_nx(s, out_s), to_nx(vt, out_v)}
2182+
2183+
:gpu ->
2184+
fun.(struct, tensor)
2185+
end
21472186
end
21482187

21492188
@impl true

emlx/lib/emlx/nif.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ defmodule EMLX.NIF do
3333
:erlang.nif_error(:nif_not_loaded)
3434
end
3535

36+
def to_device(_worker, _tensor, _device) do
37+
:erlang.nif_error(:nif_not_loaded)
38+
end
39+
3640
def item(_worker, _tensor_ref) do
3741
:erlang.nif_error(:nif_not_loaded)
3842
end

emlx/test/emlx/to_device_test.exs

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
defmodule EMLX.ToDeviceTest do
2+
use EMLX.Case, async: false
3+
4+
describe "CPU → CPU" do
5+
test "preserves values" do
6+
t = Nx.iota({3, 4}, type: :f32, backend: {EMLX.Backend, device: :cpu})
7+
result_ref = EMLX.to_device(EMLX.Backend.from_nx(t), :cpu)
8+
9+
assert EMLX.to_blob(result_ref) ==
10+
Nx.to_binary(Nx.iota({3, 4}, type: :f32, backend: Nx.BinaryBackend))
11+
end
12+
13+
test "result is tagged :cpu" do
14+
t = Nx.iota({4}, type: :f32, backend: {EMLX.Backend, device: :cpu})
15+
result_ref = EMLX.to_device(EMLX.Backend.from_nx(t), :cpu)
16+
assert elem(result_ref, 0) == :cpu
17+
end
18+
19+
test "source tensor remains accessible after to_device" do
20+
t = Nx.iota({4}, type: :f32, backend: {EMLX.Backend, device: :cpu})
21+
ref = EMLX.Backend.from_nx(t)
22+
_result = EMLX.to_device(ref, :cpu)
23+
# Source must not be deallocated — to_blob would raise on deallocation
24+
assert is_binary(EMLX.to_blob(ref))
25+
end
26+
27+
test "works on a non-contiguous (transposed) tensor" do
28+
t = Nx.iota({3, 4}, type: :f32, backend: {EMLX.Backend, device: :cpu}) |> Nx.transpose()
29+
ref = EMLX.Backend.from_nx(t)
30+
result_ref = EMLX.to_device(ref, :cpu)
31+
assert elem(result_ref, 0) == :cpu
32+
assert is_binary(EMLX.to_blob(result_ref))
33+
end
34+
end
35+
36+
describe "CPU → GPU" do
37+
@tag :metal
38+
test "result is tagged :gpu" do
39+
t = Nx.iota({4}, type: :f32, backend: {EMLX.Backend, device: :cpu})
40+
result_ref = EMLX.to_device(EMLX.Backend.from_nx(t), :gpu)
41+
assert elem(result_ref, 0) == :gpu
42+
end
43+
44+
@tag :metal
45+
test "values are preserved" do
46+
t = Nx.iota({3, 4}, type: :f32, backend: {EMLX.Backend, device: :cpu})
47+
result_ref = EMLX.to_device(EMLX.Backend.from_nx(t), :gpu)
48+
binary = EMLX.to_blob(result_ref)
49+
expected = Nx.to_binary(Nx.iota({3, 4}, type: :f32, backend: Nx.BinaryBackend))
50+
assert binary == expected
51+
end
52+
53+
@tag :metal
54+
test "source tensor remains accessible after transfer" do
55+
t = Nx.iota({4}, type: :f32, backend: {EMLX.Backend, device: :cpu})
56+
ref = EMLX.Backend.from_nx(t)
57+
_result_ref = EMLX.to_device(ref, :gpu)
58+
assert is_binary(EMLX.to_blob(ref))
59+
end
60+
end
61+
62+
describe "GPU → CPU" do
63+
@tag :metal
64+
test "result is tagged :cpu" do
65+
t = Nx.iota({4}, type: :f32, backend: {EMLX.Backend, device: :gpu})
66+
result_ref = EMLX.to_device(EMLX.Backend.from_nx(t), :cpu)
67+
assert elem(result_ref, 0) == :cpu
68+
end
69+
70+
@tag :metal
71+
test "values are preserved" do
72+
t = Nx.iota({3, 4}, type: :f32, backend: {EMLX.Backend, device: :gpu})
73+
result_ref = EMLX.to_device(EMLX.Backend.from_nx(t), :cpu)
74+
binary = EMLX.to_blob(result_ref)
75+
expected = Nx.to_binary(Nx.iota({3, 4}, type: :f32, backend: Nx.BinaryBackend))
76+
assert binary == expected
77+
end
78+
79+
@tag :metal
80+
test "source tensor remains accessible after transfer" do
81+
t = Nx.iota({4}, type: :f32, backend: {EMLX.Backend, device: :gpu})
82+
ref = EMLX.Backend.from_nx(t)
83+
_result_ref = EMLX.to_device(ref, :cpu)
84+
assert is_binary(EMLX.to_blob(ref))
85+
end
86+
end
87+
88+
describe "GPU → GPU" do
89+
@tag :metal
90+
test "result is tagged :gpu" do
91+
t = Nx.iota({4}, type: :f32, backend: {EMLX.Backend, device: :gpu})
92+
result_ref = EMLX.to_device(EMLX.Backend.from_nx(t), :gpu)
93+
assert elem(result_ref, 0) == :gpu
94+
end
95+
96+
@tag :metal
97+
test "values are preserved" do
98+
t = Nx.iota({3, 4}, type: :f32, backend: {EMLX.Backend, device: :gpu})
99+
result_ref = EMLX.to_device(EMLX.Backend.from_nx(t), :gpu)
100+
binary = EMLX.to_blob(result_ref)
101+
expected = Nx.to_binary(Nx.iota({3, 4}, type: :f32, backend: Nx.BinaryBackend))
102+
assert binary == expected
103+
end
104+
end
105+
end

emlx_axon/mix.exs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
defmodule EMLXAxon.MixProject do
22
use Mix.Project
33

4-
@version "0.1.0"
4+
@version "0.3.0"
55
@source_url "https://github.com/elixir-nx/emlx"
66

77
def project do
@@ -24,11 +24,10 @@ defmodule EMLXAxon.MixProject do
2424

2525
defp deps do
2626
[
27-
{:emlx, path: "../emlx"},
27+
# {:emlx, path: "../emlx"},
28+
{:emlx, "~> 0.3"},
2829
{:axon, "~> 0.7"},
29-
# Inherit emlx's Nx git pin to avoid constraint conflicts.
30-
{:nx, github: "elixir-nx/nx", sparse: "nx", override: true},
31-
{:bumblebee, github: "elixir-nx/bumblebee", override: true, only: [:dev, :test]},
30+
{:bumblebee, "~> 0.7"},
3231
{:ex_doc, "~> 0.34", only: :docs}
3332
]
3433
end

emlx_axon/mix.lock

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
%{
2-
"axon": {:hex, :axon, "0.7.0", "2e2c6d93b4afcfa812566b8922204fa022b60081e86ebd411df4db7ea30f5457", [:mix], [{:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}, {:kino_vega_lite, "~> 0.1.7", [hex: :kino_vega_lite, repo: "hexpm", optional: true]}, {:nx, "~> 0.9", [hex: :nx, repo: "hexpm", optional: false]}, {:polaris, "~> 0.1", [hex: :polaris, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: true]}], "hexpm", "ee9857a143c9486597ceff434e6ca833dc1241be6158b01025b8217757ed1036"},
3-
"bumblebee": {:git, "https://github.com/elixir-nx/bumblebee.git", "0fd8114cf5429af9236f100f3350986e9d823c02", []},
2+
"axon": {:hex, :axon, "0.8.1", "c4a975e62a14ab6c374997b77367ec3a4c2740952ac474d3b0f202c91b7f75c4", [:mix], [{:kino, "~> 0.7", [hex: :kino, repo: "hexpm", optional: true]}, {:kino_vega_lite, "~> 0.1.7", [hex: :kino_vega_lite, repo: "hexpm", optional: true]}, {:nx, "~> 0.10", [hex: :nx, repo: "hexpm", optional: false]}, {:polaris, "~> 0.1", [hex: :polaris, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1 or ~> 4.1", [hex: :table_rex, repo: "hexpm", optional: true]}], "hexpm", "682a3517489300507ac9345f28341e7fa95bc5b4960d645816074ce551795d37"},
3+
"bumblebee": {:hex, :bumblebee, "0.7.0", "3532b1487e6c4f5f75189f7b820508f51fb7159238c68f97286427f87dc99654", [:mix], [{:axon, "~> 0.8.0", [hex: :axon, repo: "hexpm", optional: false]}, {:jason, "~> 1.4.0", [hex: :jason, repo: "hexpm", optional: false]}, {:nx, "~> 0.12.0", [hex: :nx, repo: "hexpm", optional: false]}, {:nx_image, "~> 0.1.0", [hex: :nx_image, repo: "hexpm", optional: false]}, {:nx_signal, "~> 0.2.0", [hex: :nx_signal, repo: "hexpm", optional: false]}, {:progress_bar, "~> 3.0", [hex: :progress_bar, repo: "hexpm", optional: false]}, {:safetensors, "~> 0.1.3", [hex: :safetensors, repo: "hexpm", optional: false]}, {:tokenizers, "~> 0.4", [hex: :tokenizers, repo: "hexpm", optional: false]}, {:unpickler, "~> 0.1.0", [hex: :unpickler, repo: "hexpm", optional: false]}, {:unzip, "~> 0.12.0 or ~> 0.13.0", [hex: :unzip, repo: "hexpm", optional: false]}], "hexpm", "40244e9e98227777084c44e7a2e3a7908bcddb02d5bb852bbb615163d5d076c9"},
44
"castore": {:hex, :castore, "1.0.18", "5e43ef0ec7d31195dfa5a65a86e6131db999d074179d2ba5a8de11fe14570f55", [:mix], [], "hexpm", "f393e4fe6317829b158fb74d86eb681f737d2fe326aa61ccf6293c4104957e34"},
5-
"complex": {:hex, :complex, "0.6.0", "b0130086a7a8c33574d293b2e0e250f4685580418eac52a5658a4bd148f3ccf1", [:mix], [], "hexpm", "0a5fa95580dcaf30fcd60fe1aaf24327c0fe401e98c24d892e172e79498269f9"},
5+
"complex": {:hex, :complex, "0.7.0", "695632ef9487517aa5d57edd1697801079d622414cb2e1a7cf538b1f9a50f205", [:mix], [], "hexpm", "0ee39c0803129f546e7f3f640da8f021c9e659402bf59da6f7f2c4848f068f8d"},
66
"decimal": {:hex, :decimal, "2.4.1", "6c0fbede12fb122ba685e9ab41c6a40c129e322b3aa192f9e072e61f3a6ffaf2", [:mix], [], "hexpm", "7e618897933a8455f19a727d7c5e50a2c071a544b700e5e724298ecb4340187f"},
77
"earmark_parser": {:hex, :earmark_parser, "1.4.44", "f20830dd6b5c77afe2b063777ddbbff09f9759396500cdbe7523efd58d7a339c", [:mix], [], "hexpm", "4778ac752b4701a5599215f7030989c989ffdc4f6df457c5f36938cc2d2a2750"},
88
"elixir_make": {:hex, :elixir_make, "0.9.0", "6484b3cd8c0cee58f09f05ecaf1a140a8c97670671a6a0e7ab4dc326c3109726", [:mix], [], "hexpm", "db23d4fd8b757462ad02f8aa73431a426fe6671c80b200d9710caf3d1dd0ffdb"},
9+
"emlx": {:hex, :emlx, "0.3.0", "71fa22717d4a768ed2353f052845b2cbd6840c9e50dc67b1c9ccd5731d54ecf8", [:make, :mix], [{:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nx, "~> 0.12", [hex: :nx, repo: "hexpm", optional: false]}], "hexpm", "2778038adb05c4a2813a24eb5a89112c41a0b5b00350f059b348af0e01891c58"},
910
"ex_doc": {:hex, :ex_doc, "0.40.2", "f50edec428c4b0a457a167de42414c461122a3585a99515a69d09fff19e5597e", [:mix], [{:earmark_parser, "~> 1.4.44", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "4fa426e2beb47854a162e2c488727fdec51cd4692e319b23810c2804cb1a40fe"},
1011
"jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"},
1112
"makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"},
1213
"makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"},
1314
"makeup_erlang": {:hex, :makeup_erlang, "1.1.0", "835f7e60792e08824cda445639555d7bf1bbbddb1b60b306e33cb6f6db24dc74", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "1cd6780fb1dd1a03979abaed0fe82712b0625118fd5257d3ebbf73f960c73c3c"},
1415
"nimble_parsec": {:hex, :nimble_parsec, "1.4.2", "8efba0122db06df95bfaa78f791344a89352ba04baedd3849593bfce4d0dc1c6", [:mix], [], "hexpm", "4b21398942dda052b403bbe1da991ccd03a053668d147d53fb8c4e0efe09c973"},
15-
"nx": {:git, "https://github.com/elixir-nx/nx.git", "1c3d86eb635e136eab7d5a1a12a57794c200d204", [sparse: "nx"]},
16+
"nx": {:hex, :nx, "0.12.0", "32bc205bab5486d73892132d17a11ea113e97427a29bb70606a544724b95e193", [:mix], [{:complex, "~> 0.7", [hex: :complex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d022a33ea3c900eb6e2e91b4e0793759459c886f482be61978004b5e4843b5e"},
1617
"nx_image": {:hex, :nx_image, "0.1.2", "0c6e3453c1dc30fc80c723a54861204304cebc8a89ed3b806b972c73ee5d119d", [:mix], [{:nx, "~> 0.4", [hex: :nx, repo: "hexpm", optional: false]}], "hexpm", "9161863c42405ddccb6dbbbeae078ad23e30201509cc804b3b3a7c9e98764b81"},
1718
"nx_signal": {:hex, :nx_signal, "0.2.0", "e1ca0318877b17c81ce8906329f5125f1e2361e4c4235a5baac8a95ee88ea98e", [:mix], [{:nx, "~> 0.6", [hex: :nx, repo: "hexpm", optional: false]}], "hexpm", "7247e5e18a177a59c4cb5355952900c62fdeadeb2bad02a9a34237b68744e2bb"},
1819
"polaris": {:hex, :polaris, "0.1.0", "dca61b18e3e801ecdae6ac9f0eca5f19792b44a5cb4b8d63db50fc40fc038d22", [:mix], [{:nx, "~> 0.5", [hex: :nx, repo: "hexpm", optional: false]}], "hexpm", "13ef2b166650e533cb24b10e2f3b8ab4f2f449ba4d63156e8c569527f206e2c2"},

emlx_axon/test/test_helper.exs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,24 @@
1-
ExUnit.start(exclude: [:bumblebee, :metal])
1+
use_gpu? =
2+
String.downcase(System.get_env("EMLX_TEST_DEFAULT_GPU", "false")) in [
3+
"1",
4+
"true",
5+
"yes",
6+
"t",
7+
"y"
8+
]
9+
10+
if use_gpu? do
11+
Application.put_env(:nx, :default_backend, {EMLX.Backend, device: :gpu})
12+
end
13+
14+
gpu_exclude =
15+
if use_gpu? do
16+
case EMLX.NIF.command_queue_new(:gpu) do
17+
{:ok, _} -> []
18+
{:error, _} -> [:metal]
19+
end
20+
else
21+
[:metal]
22+
end
23+
24+
ExUnit.start(exclude: [:bumblebee] ++ gpu_exclude)

0 commit comments

Comments
 (0)