Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 35 additions & 26 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,26 +1,35 @@
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where third-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
ortex-*.tar

# Temporary files, for example, from tests.
/tmp/
# The directory Mix will write compiled artifacts to.
/_build/

# If you run "mix test --cover", coverage assets end up here.
/cover/

# The directory Mix downloads your dependencies sources to.
/deps/

# Where third-party dependencies like ExDoc output generated docs.
/doc/

# Ignore .fetch files in case you like to edit your project deps locally.
/.fetch

# If the VM crashes, it generates a dump, let's ignore it too.
erl_crash.dump

# Also ignore archive artifacts (built via "mix archive.build").
*.ez

# Ignore package tarball (built via "mix hex.build").
ortex-*.tar

# Temporary files, for example, from tests.
/tmp/

# Priv output
/priv/native

/lib/framework
/lib/mix
compile.bat
compile.sh
test.bat
37 changes: 19 additions & 18 deletions config/config.exs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import Config
# Something is setting this to IEx.Pry so we're overriding it for now. Remove
# if you need to do real debugging
config :elixir, :dbg_callback, {Macro, :dbg, []}

config :ortex,
add_backend_on_inspect: config_env() != :test

# Set the cargo feature flags required to use the matching execution provider
# based on the OS we're running on
ortex_features =
case :os.type() do
{:win32, _} -> ["directml"]
{:unix, :darwin} -> ["coreml"]
{:unix, _} -> ["cuda", "tensorrt"]
end

config :ortex, Ortex.Native, features: ortex_features
import Config
# Something is setting this to IEx.Pry so we're overriding it for now. Remove
# if you need to do real debugging
config :elixir, :dbg_callback, {Macro, :dbg, []}

config :ortex,
add_backend_on_inspect: config_env() != :test

# Set the cargo feature flags required to use the matching execution provider
# based on the OS we're running on
ortex_features =
case :os.type() do
{:win32, _} -> ["directml"]
{:unix, :darwin} -> ["coreml"]
{:unix, _} -> ["cuda", "tensorrt"]
end

#config :nx, default_backend: {Torchx.Backend, device: :cuda}
config :ortex, Ortex.Native, features: ortex_features
5 changes: 5 additions & 0 deletions lib/ortex/image.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
defmodule Ortex.Image do
def prepare(image, width, height, size \\ 640) do
Ortex.Native.prepare_image(image, width, height, size)
end
end
197 changes: 107 additions & 90 deletions lib/ortex/model.ex
Original file line number Diff line number Diff line change
@@ -1,90 +1,107 @@
defmodule Ortex.Model do
@moduledoc """
A model for running Ortex inference with.

Implements a human-readable representation of a model including the name, dimension, and
type of each input and output

```
#Ortex.Model<
inputs: [{"x", "Int32", [nil, 100]}, {"y", "Float32", [nil, 100]}]
outputs: [
{"9", "Float32", [nil, 10]},
{"onnx::Add_7", "Float32", [nil, 10]},
{"onnx::Add_8", "Float32", [nil, 10]}
]>
```

`nil` values represent dynamic dimensions
"""

@enforce_keys [:reference]
defstruct [:reference]

@doc false
def load(path, eps \\ [:cpu], opt \\ 3) do
case Ortex.Native.init(path, eps, opt) do
{:error, msg} ->
raise msg

model ->
%Ortex.Model{reference: model}
end
end

@doc false
def run(%Ortex.Model{} = model, tensor) when not is_tuple(tensor) do
run(model, {tensor})
end

@doc false
def run(%Ortex.Model{reference: model}, tensors) do
# Move tensors into Ortex backend and pass the reference to the Ortex NIF
output =
case Ortex.Native.run(
model,
tensors
|> Tuple.to_list()
|> Enum.map(fn x -> x |> Nx.backend_transfer(Ortex.Backend) end)
|> Enum.map(fn %Nx.Tensor{data: %Ortex.Backend{ref: x}} -> x end)
) do
{:error, msg} -> raise msg
output -> output
end

# Pack the output into new Ortex.Backend tensor(s)
output
|> Enum.map(fn {ref, shape, dtype_atom, dtype_bits} ->
%Nx.Tensor{
data: %Ortex.Backend{ref: ref},
shape: shape |> List.to_tuple(),
type: {dtype_atom, dtype_bits},
names: List.duplicate(nil, length(shape))
}
end)
|> List.to_tuple()
end
end

defimpl Inspect, for: Ortex.Model do
import Inspect.Algebra

def inspect(%Ortex.Model{reference: model}, inspect_opts) do
case Ortex.Native.show_session(model) do
{:error, msg} ->
raise msg

{inputs, outputs} ->
force_unfit(
concat([
color("#Ortex.Model<", :map, inspect_opts),
line(),
nest(concat([" inputs: ", Inspect.List.inspect(inputs, inspect_opts)]), 2),
line(),
nest(concat([" outputs: ", Inspect.List.inspect(outputs, inspect_opts)]), 2),
color(">", :map, inspect_opts)
])
)
end
end
end
defmodule Ortex.Model do
@moduledoc """
A model for running Ortex inference with.

Implements a human-readable representation of a model including the name, dimension, and
type of each input and output

```
#Ortex.Model<
inputs: [{"x", "Int32", [nil, 100]}, {"y", "Float32", [nil, 100]}]
outputs: [
{"9", "Float32", [nil, 10]},
{"onnx::Add_7", "Float32", [nil, 10]},
{"onnx::Add_8", "Float32", [nil, 10]}
]>
```

`nil` values represent dynamic dimensions
"""

@enforce_keys [:reference]
defstruct [:reference]

@doc false
def load(path, eps \\ [:cpu], opt \\ 3) do
case Ortex.Native.init(path, eps, opt) do
{:error, msg} ->
raise msg

model ->
%Ortex.Model{reference: model}
end
end

@doc false
def run(%Ortex.Model{} = model, tensor) when not is_tuple(tensor) do
run(model, {tensor})
end

@doc false
def run(%Ortex.Model{reference: model}, tensors) do
# Move tensors into Ortex backend and pass the reference to the Ortex NIF
output =
case Ortex.Native.run(
model,
tensors
|> Tuple.to_list()
|> Enum.map(fn x -> x |> Nx.backend_transfer(Ortex.Backend) end)
|> Enum.map(fn %Nx.Tensor{data: %Ortex.Backend{ref: x}} -> x end)
) do
{:error, msg} -> raise msg
output -> output
end

# Pack the output into new Ortex.Backend tensor(s)
output
|> Enum.map(fn {ref, shape, dtype_atom, dtype_bits} ->
%Nx.Tensor{
data: %Ortex.Backend{ref: ref},
shape: shape |> List.to_tuple(),
type: {dtype_atom, dtype_bits},
names: List.duplicate(nil, length(shape))
}
end)
|> List.to_tuple()
end

def create_mask(coefficients, mask_prototypes, threshold \\ 0.5) do
prototypes_bin = Nx.to_binary(mask_prototypes)
# Tuple like {1, 32, 240, 240}
{_, _, width, height} = proto_shape = Nx.shape(mask_prototypes)

Ortex.Native.create_mask(coefficients, prototypes_bin, proto_shape, threshold)
|> case do
binary_mask when is_binary(binary_mask) ->
binary_mask
|> Nx.from_binary(:u8)
|> Nx.reshape({width, height})

error ->
error
end
end
end

defimpl Inspect, for: Ortex.Model do
import Inspect.Algebra

def inspect(%Ortex.Model{reference: model}, inspect_opts) do
case Ortex.Native.show_session(model) do
{:error, msg} ->
raise msg

{inputs, outputs} ->
force_unfit(
concat([
color("#Ortex.Model<", :map, inspect_opts),
line(),
nest(concat([" inputs: ", Inspect.List.inspect(inputs, inspect_opts)]), 2),
line(),
nest(concat([" outputs: ", Inspect.List.inspect(outputs, inspect_opts)]), 2),
color(">", :map, inspect_opts)
])
)
end
end
end
87 changes: 45 additions & 42 deletions lib/ortex/native.ex
Original file line number Diff line number Diff line change
@@ -1,42 +1,45 @@
defmodule Ortex.Native do
@moduledoc false

@rustler_version Application.spec(:rustler, :vsn) |> to_string() |> Version.parse!()

# We have to compile the crate before `use Rustler` compiles the crate since
# cargo downloads the onnxruntime shared libraries and they are not available
# to load or copy into Elixir's during the on_load or Elixir compile steps.
# In the future, this may be configurable in Rustler.
if Version.compare(@rustler_version, "0.30.0") in [:gt, :eq] do
Rustler.Compiler.compile_crate(:ortex, Application.compile_env(:ortex, __MODULE__, []),
otp_app: :ortex,
crate: :ortex
)
else
Rustler.Compiler.compile_crate(__MODULE__, otp_app: :ortex, crate: :ortex)
end

Ortex.Util.copy_ort_libs()

use Rustler,
otp_app: :ortex,
crate: :ortex,
skip_compilation?: true

# When loading a NIF module, dummy clauses for all NIF function are required.
# NIF dummies usually just error out when called when the NIF is not loaded, as that should never normally happen.
def init(_model_path, _execution_providers, _optimization_level),
do: :erlang.nif_error(:nif_not_loaded)

def run(_model, _inputs), do: :erlang.nif_error(:nif_not_loaded)
def from_binary(_bin, _shape, _type), do: :erlang.nif_error(:nif_not_loaded)
def to_binary(_reference, _bits, _limit), do: :erlang.nif_error(:nif_not_loaded)
def show_session(_model), do: :erlang.nif_error(:nif_not_loaded)

def slice(_tensor, _start_indicies, _lengths, _strides),
do: :erlang.nif_error(:nif_not_loaded)

def reshape(_tensor, _shape), do: :erlang.nif_error(:nif_not_loaded)

def concatenate(_tensors_refs, _type, _axis), do: :erlang.nif_error(:nif_not_loaded)
end
defmodule Ortex.Native do
@moduledoc false

@rustler_version Application.spec(:rustler, :vsn) |> to_string() |> Version.parse!()

# We have to compile the crate before `use Rustler` compiles the crate since
# cargo downloads the onnxruntime shared libraries and they are not available
# to load or copy into Elixir's during the on_load or Elixir compile steps.
# In the future, this may be configurable in Rustler.
if Version.compare(@rustler_version, "0.30.0") in [:gt, :eq] do
Rustler.Compiler.compile_crate(:ortex, Application.compile_env(:ortex, __MODULE__, []),
otp_app: :ortex,
crate: :ortex
)
else
Rustler.Compiler.compile_crate(__MODULE__, otp_app: :ortex, crate: :ortex)
end

Ortex.Util.copy_ort_libs()

use Rustler,
otp_app: :ortex,
crate: :ortex,
skip_compilation?: true

# When loading a NIF module, dummy clauses for all NIF function are required.
# NIF dummies usually just error out when called when the NIF is not loaded, as that should never normally happen.
def init(_model_path, _execution_providers, _optimization_level),
do: :erlang.nif_error(:nif_not_loaded)

def run(_model, _inputs), do: :erlang.nif_error(:nif_not_loaded)
def from_binary(_bin, _shape, _type), do: :erlang.nif_error(:nif_not_loaded)
def to_binary(_reference, _bits, _limit), do: :erlang.nif_error(:nif_not_loaded)
def show_session(_model), do: :erlang.nif_error(:nif_not_loaded)

def slice(_tensor, _start_indicies, _lengths, _strides),
do: :erlang.nif_error(:nif_not_loaded)

def reshape(_tensor, _shape), do: :erlang.nif_error(:nif_not_loaded)

def concatenate(_tensors_refs, _type, _axis), do: :erlang.nif_error(:nif_not_loaded)

def create_mask(_coefficients, _prototypes_bin, _proto_shape_term, _threshold), do: :erlang.nif_error(:nif_not_loaded)
def prepare_image(_binary, _width, _height, _size), do: :erlang.nif_error(:nif_not_loaded)
end
Loading