Skip to content
Draft
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
9 changes: 8 additions & 1 deletion .claude/settings.json
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
{}
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{"type": "command", "command": "python3 \"$CLAUDE_PROJECT_DIR/scripts/agent_check.py\"", "timeout": 45}]
}]
}
}
8 changes: 8 additions & 0 deletions .codex/hooks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"hooks": {
"PostToolUse": [{
"matcher": "apply_patch|Edit|Write",
"hooks": [{"type": "command", "command": "python3 \"$(git rev-parse --show-toplevel)/scripts/agent_check.py\"", "timeout": 45}]
}]
}
}
17 changes: 17 additions & 0 deletions .credo.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
%{
configs: [
%{
name: "default",
files: %{included: ["lib/", "test/"], excluded: [~r"/fixtures/", ~r"/package/"]},
checks: %{
enabled: [
{Credo.Check.Warning.IExPry, []},
{Credo.Check.Warning.IoInspect, []},
{Credo.Check.Warning.UnusedEnumOperation, []},
{Credo.Check.Consistency.TabsOrSpaces, []},
{Credo.Check.Readability.TrailingBlankLine, []}
]
}
}
]
}
5 changes: 4 additions & 1 deletion .formatter.exs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
# Used by "mix format"
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
inputs: [
"{mix,.formatter,.credo}.exs",
"{config,lib,test,scripts,bench,examples}/**/*.{ex,exs}"
]
]
19 changes: 18 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
.DEFAULT_GOAL := help
PYTHON ?= python3
export PATH := $(CURDIR)/.venv/bin:$(CURDIR)/.venv/Scripts:$(PATH)
export MIX_ENV := test
export ORT_DYLIB_PATH ?= $(shell $(PYTHON) scripts/install_runtime.py path 2>/dev/null)

.PHONY: help setup deps doctor test docs
.PHONY: help setup deps doctor test docs hooks fmt lint check

help: ## Show development commands
@awk 'BEGIN {FS = ":.*## "} /^[a-zA-Z0-9_.-]+:.*## / {printf " %-22s %s\n", $$1, $$2}' $(MAKEFILE_LIST)
Expand All @@ -13,6 +14,8 @@ setup: ## Fetch Mix dependencies and the development runtime
mix local.rebar --force
$(MAKE) deps
$(PYTHON) scripts/install_runtime.py install
$(PYTHON) -m venv .venv
.venv/bin/python -m pip install -r scripts/requirements.txt

deps: ## Fetch the locked test and documentation dependencies
mix deps.get --check-locked
Expand All @@ -26,3 +29,17 @@ test: ## Run library tests and doctests

docs: ## Build documentation with warnings treated as errors
MIX_ENV=docs mix docs --warnings-as-errors

hooks: ## Install the pre-commit launcher (refuses to overwrite an unrelated hook)
$(PYTHON) scripts/hooks.py

fmt: ## Apply the language formatters
mix format
cd native/ortex && cargo fmt
ruff format .
shfmt -w scripts/precommit

lint: ## Check formatting, static analysis, and compiler warnings
$(PYTHON) scripts/check.py --full

check: lint test ## Run the ordinary local checks
14 changes: 14 additions & 0 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,17 @@ normal ExUnit discovery. Additional suites are introduced by later stack PRs.

Documentation has its own Mix environment so building docs does not install
EXLA, Torchx, or example tokenizers. Large examples own their extra dependencies.

## Checks and hooks

Activate `.venv/bin` alongside the mise tools after setup. `make lint` runs
format checks, Credo, Clippy, and workflow validation. `make fmt` applies
formatters. `scripts/precommit` runs fast checks against an isolated snapshot of
the Git index, so partially staged files are checked as committed. It never
changes the index or working tree. Filenames are passed as arguments, not shell
code. Full checks require prepared dependencies; hooks never install them.

`make hooks` installs a launcher through `git rev-parse --git-path`; it refuses
to replace an unrelated hook. Git's hooks directory is shared by linked
worktrees. Agent hooks are adapters to the same checker and require the agent's
normal hook review/trust. No global permissions or model settings are changed.
2 changes: 1 addition & 1 deletion examples/distilbert/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ Run `python export.py` to create the ONNX model for distilbert/distilbert-base-u

### Labels

When exporting the model from huggingface transformers to ONNX, a `config.json` file is added to the chosen directory. This file has the id to label mappings and you can extract them directly to give a label to the input, as shwon in `distilbert_classification.exs`.
When exporting the model from huggingface transformers to ONNX, a `config.json` file is added to the chosen directory. This file has the id to label mappings and you can extract them directly to give a label to the input, as shwon in `distilbert_classification.exs`.
6 changes: 4 additions & 2 deletions examples/distilbert/export.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@

"""

from transformers import DistilBertTokenizer
from optimum.onnxruntime import ORTModelForSequenceClassification
from transformers import DistilBertTokenizer

save_directory = "./models/distilbert-onnx/"

tokenizer = DistilBertTokenizer.from_pretrained("distilbert-base-uncased-finetuned-sst-2-english")
model = ORTModelForSequenceClassification.from_pretrained("distilbert-base-uncased-finetuned-sst-2-english", export=True)
model = ORTModelForSequenceClassification.from_pretrained(
"distilbert-base-uncased-finetuned-sst-2-english", export=True
)
print(model)

model.save_pretrained(save_directory)
Expand Down
2 changes: 1 addition & 1 deletion examples/stablelm/export.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import torch
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
)
import torch

tokenizer = AutoTokenizer.from_pretrained("stabilityai/stablelm-tuned-alpha-3b")
model = AutoModelForCausalLM.from_pretrained("stabilityai/stablelm-tuned-alpha-3b")
Expand Down
4 changes: 2 additions & 2 deletions examples/stablelm/stablelm.exs
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ defmodule M do

x = output |> Nx.backend_transfer() |> Nx.argmax(axis: 2)
last = x[[.., -1]] |> Nx.new_axis(0)
IO.inspect(last[0][0] |> Nx.to_number)
IO.inspect(last[0][0] |> Nx.to_number())

case Enum.member?([50278, 50279, 50277, 1, 0], last[0][0] |> Nx.to_number) do
case Enum.member?([50278, 50279, 50277, 1, 0], last[0][0] |> Nx.to_number()) do
true ->
input

Expand Down
2 changes: 1 addition & 1 deletion lib/ortex/serving.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ defmodule Ortex.Serving do

## Examples

### Inline/serverless workflow
### Inline/serverless workflow

To quickly create an `Ortex.Serving` and run it

Expand Down
1 change: 1 addition & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ defmodule Ortex.MixProject do
[
{:rustler, "~> 0.29.1"},
{:nx, "~> 0.6"},
{:credo, "~> 1.7.19", only: :test, runtime: false},
{:ex_doc, "~> 0.40.4", only: :docs, runtime: false}
]
end
Expand Down
3 changes: 3 additions & 0 deletions mix.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
%{
"bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"},
"complex": {:hex, :complex, "0.7.0", "695632ef9487517aa5d57edd1697801079d622414cb2e1a7cf538b1f9a50f205", [:mix], [], "hexpm", "0ee39c0803129f546e7f3f640da8f021c9e659402bf59da6f7f2c4848f068f8d"},
"credo": {:hex, :credo, "1.7.19", "cc52129665fc7c15143d47838fda0f9cd6dac9ceced7bf4da6f85fcbfe64b12a", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "2d8bc95d5a7bb99dd2613621d4f08c6a3575c3fd4b62e6a2b48a100352a557b8"},
"earmark_parser": {:hex, :earmark_parser, "1.4.46", "67607a0532e810c6f630a515c548d0b24949643f168cc556303bee4cf96105c7", [:mix], [], "hexpm", "9c44636e8a1c68c62f526b2dcd85d941dbbcee7ab82cf64ba06ce28bef8e89f5"},
"ex_doc": {:hex, :ex_doc, "0.40.4", "66f2e42bf588594d5a8aab31cad87f2ddad09d0da1b1a2f379340ec2c2e497cb", [:mix], [{:earmark_parser, "~> 1.4.46", [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", "6222b9e423d76584ee34df2c82a5ed72c2d53dc153f7f483ad28b378694186cc"},
"file_system": {:hex, :file_system, "1.1.1", "31864f4685b0148f25bd3fbef2b1228457c0c89024ad67f7a81a3ffbc0bbad3a", [:mix], [], "hexpm", "7a15ff97dfe526aeefb090a7a9d3d03aa907e100e262a0f8f7746b78f8f87a5d"},
"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"},
"makeup": {:hex, :makeup, "1.2.3", "c3ef0ff83acf305384acba0f70bff87d8732502baa1eb3eb081d8c154fbe18f5", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "15970751b7021c2bcd7ed56a54b3def23affd3969fb7c214dffdf28e97ce6f76"},
"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"},
Expand Down
30 changes: 15 additions & 15 deletions native/ortex/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,15 @@ fn init(
}

#[rustler::nif]
fn show_session(
model: ResourceArc<model::OrtexModel>,
) -> NifResult<(
Vec<(String, String, Option<Vec<i64>>)>,
Vec<(String, String, Option<Vec<i64>>)>,
)> {
fn show_session(model: ResourceArc<model::OrtexModel>) -> NifResult<model::SessionDescription> {
Ok(model::show(model))
}

#[rustler::nif(schedule = "DirtyIo")]
fn run(
model: ResourceArc<model::OrtexModel>,
inputs: Vec<ResourceArc<OrtexTensor>>,
) -> NifResult<Vec<(ResourceArc<OrtexTensor>, Vec<usize>, Atom, usize)>> {
) -> NifResult<Vec<model::TensorOutput>> {
model::run(model, inputs).map_err(|e| rustler::Error::Term(Box::new(e.to_string())))
}

Expand Down Expand Up @@ -71,7 +66,7 @@ fn to_binary<'a>(
}

#[rustler::nif]
pub fn slice<'a>(
pub fn slice(
tensor: ResourceArc<OrtexTensor>,
start_indicies: Vec<isize>,
lengths: Vec<isize>,
Expand All @@ -85,15 +80,15 @@ pub fn slice<'a>(
}

#[rustler::nif]
pub fn reshape<'a>(
pub fn reshape(
tensor: ResourceArc<OrtexTensor>,
shape: Vec<usize>,
) -> NifResult<ResourceArc<OrtexTensor>> {
Ok(ResourceArc::new(tensor.reshape(shape)?))
}

#[rustler::nif]
pub fn concatenate<'a>(
pub fn concatenate(
tensors: Vec<ResourceArc<OrtexTensor>>,
dtype: Term,
axis: i32,
Expand All @@ -116,9 +111,14 @@ rustler::init!(
reshape,
concatenate
],
load = |env: Env, _| {
rustler::resource!(OrtexModel, env);
rustler::resource!(OrtexTensor, env);
true
}
load = load
);

// Rustler 0.29 expands resource registration into impls inside this callback.
// Remove this compatibility allowance with the separate Rustler migration.
#[allow(non_local_definitions)]
fn load(env: Env, _info: Term) -> bool {
rustler::resource!(OrtexModel, env);
rustler::resource!(OrtexTensor, env);
true
}
25 changes: 12 additions & 13 deletions native/ortex/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ use rustler::Atom;

/// Holds the model state which include onnxruntime session and environment. All
/// are threadsafe so this can be called concurrently from the beam.
pub type Descriptor = (String, String, Option<Vec<i64>>);
pub type SessionDescription = (Vec<Descriptor>, Vec<Descriptor>);
pub type TensorOutput = (ResourceArc<OrtexTensor>, Vec<usize>, Atom, usize);

pub struct OrtexModel {
pub session: ort::Session,
}
Expand Down Expand Up @@ -51,13 +55,8 @@ pub fn init(
/// Returns input/output information about a model. The result is a Tuple of
/// `inputs` and `outputs` with elements of `(Name, Type, Dimension)` where
/// `Dimension` elements of -1 are dynamic.
pub fn show(
model: ResourceArc<OrtexModel>,
) -> (
Vec<(String, String, Option<Vec<i64>>)>,
Vec<(String, String, Option<Vec<i64>>)>,
) {
let model: &OrtexModel = &*model;
pub fn show(model: ResourceArc<OrtexModel>) -> SessionDescription {
let model: &OrtexModel = &model;

let mut inputs = Vec::new();
for input in model.session.inputs.iter() {
Expand All @@ -83,7 +82,7 @@ pub fn show(
pub fn run(
model: ResourceArc<OrtexModel>,
inputs: Vec<ResourceArc<OrtexTensor>>,
) -> Result<Vec<(ResourceArc<OrtexTensor>, Vec<usize>, Atom, usize)>, Error> {
) -> Result<Vec<TensorOutput>, Error> {
// Grab the session and run a forward pass with it
let session: &ort::Session = &model.session;

Expand All @@ -94,7 +93,7 @@ pub fn run(
if is_bool_input(&onnx_input.input_type) {
// this assumes that the boolean input isn't huge -- we're cloning it twice;
// once below, once in the try_into()
let boolified_input: &OrtexTensor = &derefed_input.clone().to_bool();
let boolified_input: &OrtexTensor = &derefed_input.clone().into_bool();
let v: ort::SessionInputValue = boolified_input.try_into()?;
ortified_inputs.push(v);
} else {
Expand All @@ -109,12 +108,12 @@ pub fn run(

for output_descriptor in &session.outputs {
let output_name: &str = &output_descriptor.name;
let val = outputs.get(output_name).expect(
&format!(
let val = outputs.get(output_name).unwrap_or_else(|| {
panic!(
"Expected {} to be in the outputs, but didn't find it",
output_name
)[..],
);
)
});

// NOTE: try_into impl here will implicitly map bool outputs to u8 outputs
let ortextensor: OrtexTensor = val.try_into()?;
Expand Down
12 changes: 6 additions & 6 deletions native/ortex/src/tensor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,8 @@ impl OrtexTensor {
contents
}

pub fn slice<'a>(
&'a self,
pub fn slice(
&self,
start_indicies: Vec<isize>,
lengths: Vec<isize>,
strides: Vec<isize>,
Expand Down Expand Up @@ -184,7 +184,7 @@ impl OrtexTensor {
}
}

pub fn to_bool(self) -> OrtexTensor {
pub fn into_bool(self) -> OrtexTensor {
match self {
OrtexTensor::u8(y) => {
let bool_tensor = y.to_owned().mapv(|x| match x {
Expand All @@ -205,7 +205,7 @@ impl OrtexTensor {

fn slice_array<'a, T, D>(
array: &'a Array<T, D>,
slice_specs: &'a Vec<(isize, Option<isize>, isize)>,
slice_specs: &'a [(isize, Option<isize>, isize)],
) -> ArrayView<'a, T, D>
where
D: Dimension,
Expand All @@ -216,7 +216,7 @@ where
})
}

fn get_bytes<'a, T>(array: &'a ArrayBase<T, IxDyn>) -> &'a [u8]
fn get_bytes<T>(array: &ArrayBase<T, IxDyn>) -> &[u8]
where
T: Data,
{
Expand Down Expand Up @@ -343,7 +343,7 @@ macro_rules! concatenate {
// `typ` is the actual datatype, `ort_tensor_kind` is the OrtexTensor variant
($tensors:expr, $axis:expr, $typ:ty, $ort_tensor_kind:ident) => {{
type ArrayType<'a> = ArrayBase<ViewRepr<&'a $typ>, Dim<IxDynImpl>>;
fn filter(tensor: &OrtexTensor) -> Option<ArrayType> {
fn filter(tensor: &OrtexTensor) -> Option<ArrayType<'_>> {
match tensor {
OrtexTensor::$ort_tensor_kind(x) => Some(x.view()),
_ => None,
Expand Down
4 changes: 2 additions & 2 deletions native/ortex/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use rustler::{Atom, Env, NifResult};
use ort::{ExecutionProviderDispatch, GraphOptimizationLevel};

/// A faster (unsafe) way of creating an Array from an Erlang binary
fn initialize_from_raw_ptr<T>(ptr: *const T, shape: &[Ix]) -> ArrayViewMut<T, IxDyn> {
fn initialize_from_raw_ptr<T>(ptr: *const T, shape: &[Ix]) -> ArrayViewMut<'_, T, IxDyn> {
let array = unsafe { ArrayViewMut::from_shape_ptr(shape, ptr as *mut T) };
array
}
Expand Down Expand Up @@ -64,7 +64,7 @@ pub fn from_binary(
initialize_from_raw_ptr(bin.as_ptr() as *const i64, &shape).to_owned(),
))),
("u", 8) => Ok(ResourceArc::new(OrtexTensor::u8(
initialize_from_raw_ptr(bin.as_ptr() as *const u8, &shape).to_owned(),
initialize_from_raw_ptr(bin.as_ptr(), &shape).to_owned(),
))),
("u", 16) => Ok(ResourceArc::new(OrtexTensor::u16(
initialize_from_raw_ptr(bin.as_ptr() as *const u16, &shape).to_owned(),
Expand Down
7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[tool.ruff]
target-version = "py312"
line-length = 100
extend-exclude = ["deps", "_build", "runtime", "dist", ".venv"]

[tool.ruff.lint]
select = ["E4", "E7", "E9", "F", "I"]
Loading
Loading