View Source TFLiteElixir.LiteRT.CompiledModel (tflite_elixir v1.0.0-rc4)

A model compiled through LiteRT, which is where the accelerators and the per-operator profile live.

This is a different path into the same runtime as TFLiteElixir.Interpreter, not a replacement for it. What it adds is a choice of accelerator that is asked for by name and answered honestly, and a profiler:

{:ok, env} = TFLiteElixir.LiteRT.CompiledModel.environment()
{:ok, model} = TFLiteElixir.LiteRT.CompiledModel.new(env, path,
                 accelerators: [:cpu, :gpu], profile: true)
{:ok, outputs} = TFLiteElixir.LiteRT.CompiledModel.run(model, inputs)
{:ok, slowest} = TFLiteElixir.LiteRT.CompiledModel.summarise_profile(model)

fully_accelerated?/1 says whether the accelerator took the whole graph or only part of it, which is the difference between a speedup and a slowdown and is not otherwise visible.

One caller at a time

LiteRT does not promise its compiled model API is safe to use from several threads, and the profile buffer under it says outright that it is not. So a second concurrent caller is refused here rather than allowed to corrupt anything, and {:error, "compiled model is in use by another caller"} is a normal answer rather than a fault. TFLiteElixir.LiteRT.CompiledModel.Server is the way to share one model between processes.

Availability

The LiteRT API is a build option and is off by default, so every function here can answer {:error, "tflite_beam was compiled without the LiteRT API"} on an ordinary build. platform_support/0 says what this build can reach.

Summary

Types

What to run on, in order of preference.

A profiling event. An integer type or source is one this build has no name for.

Compute precision. :default leaves it to the accelerator, which for Metal means fp32; :fp16 trades accuracy for speed.

Functions

Whether this build has the LiteRT API at all.

Which process the model belongs to, or :undefined if it is unclaimed.

Hand the model to a process, after which no other process may use it.

A LiteRT environment, which the accelerator plugins are loaded into.

An environment that looks in runtime_library_dir for accelerator plugins.

Raising version of environment/0.

As fully_accelerated?/1, but reports why it could not be answered.

Whether the accelerator took the whole graph.

The byte size of each input and output tensor, as {inputs, outputs}.

Raising version of io_sizes/1.

Compile a model with the default options: CPU, no profiling.

Compile a model.

Raising version of new/2.

Raising version of new/3.

How many profiling events are waiting, without reading them.

Which buffer kinds this platform can reach, e.g. %{metal: true, opencl: false}.

Every profiling event recorded so far, oldest first.

The most recent limit profiling events, or all of them when limit is zero.

Raising version of profile/1.

Raising version of profile/2.

Forget the events recorded so far and keep recording.

Run the model over a list of input binaries, one per input tensor.

Raising version of run/2.

Run the model and collect whatever counters the accelerator reports.

Run with metrics collection bracketing the inference.

The names of a model file's signatures, without compiling it.

Per-operator totals over every run since the last reset, slowest first.

Types

@type accelerator() :: :cpu | :gpu | :npu

What to run on, in order of preference.

@type event() :: %{
  tag: binary(),
  us: non_neg_integer(),
  type: atom() | integer(),
  source: atom() | integer()
}

A profiling event. An integer type or source is one this build has no name for.

@type metric_value() :: integer() | float() | boolean() | binary() | :unsupported
@type opts() :: [
  accelerators: [accelerator()],
  precision: precision(),
  profile: boolean(),
  signature: non_neg_integer() | String.t(),
  max_model_bytes: non_neg_integer()
]
@type precision() :: :default | :fp16 | :fp32 | :fp16_with_fp32_accum

Compute precision. :default leaves it to the accelerator, which for Metal means fp32; :fp16 trades accuracy for speed.

@type summary_entry() :: %{
  tag: binary(),
  kind: :operator | :delegate_operator | :delegate_profiled,
  count: pos_integer(),
  us: non_neg_integer()
}

Functions

@spec available?() :: boolean()

Whether this build has the LiteRT API at all.

It is a build option and it is off by default, so on an ordinary build every other function here answers {:error, "the LiteRT API was not compiled into this build..."}. Asking this first is cheaper than finding out from a call that was meant to do something.

Link to this function

controlling_process(model)

View Source
@spec controlling_process(reference()) ::
  {:ok, pid()} | :undefined | {:error, String.t()}

Which process the model belongs to, or :undefined if it is unclaimed.

An unclaimed model is open to every process. Claiming one is controlling_process/2.

Link to this function

controlling_process(model, pid)

View Source
@spec controlling_process(reference(), pid()) :: :ok | {:error, String.t()}

Hand the model to a process, after which no other process may use it.

The claim is dropped when that process dies, so a crash does not strand the model.

Link to this function

controlling_process!(model, pid)

View Source

Raising version of controlling_process/2.

@spec environment() :: {:ok, reference()} | {:error, String.t()}

A LiteRT environment, which the accelerator plugins are loaded into.

One is enough for any number of models and it has to outlive them.

Link to this function

environment(runtime_library_dir)

View Source
@spec environment(String.t()) :: {:ok, reference()} | {:error, String.t()}

An environment that looks in runtime_library_dir for accelerator plugins.

Without a directory the plugins are searched for relative to nothing, which is the usual reason a GPU accelerator silently does not load.

Raising version of environment/0.

Link to this function

environment!(runtime_library_dir)

View Source

Raising version of environment/1.

Link to this function

fully_accelerated(model)

View Source
@spec fully_accelerated(reference()) :: {:ok, boolean()} | {:error, String.t()}

As fully_accelerated?/1, but reports why it could not be answered.

Link to this function

fully_accelerated!(model)

View Source

Raising version of fully_accelerated/1.

Link to this function

fully_accelerated?(model)

View Source
@spec fully_accelerated?(reference()) :: boolean()

Whether the accelerator took the whole graph.

A partly accelerated model pays for every crossing between the accelerator and the CPU, and is often slower than the CPU alone, so a false here is worth acting on rather than ignoring.

@spec io_sizes(reference()) ::
  {:ok, {[non_neg_integer()], [non_neg_integer()]}} | {:error, String.t()}

The byte size of each input and output tensor, as {inputs, outputs}.

Raising version of io_sizes/1.

@spec new(reference(), String.t()) :: {:ok, reference()} | {:error, String.t()}

Compile a model with the default options: CPU, no profiling.

Link to this function

new(env, model_path, opts)

View Source
@spec new(reference(), String.t(), opts()) ::
  {:ok, reference()} | {:error, String.t()}

Compile a model.

Options
  • :accelerators. What to run on, in order of preference, e.g. [:cpu, :gpu]. Naming an accelerator asks for it; whether it was used is fully_accelerated?/1. Defaults to [:cpu].
  • :precision. :default, :fp16, :fp32 or :fp16_with_fp32_accum.
  • :profile. Record per-operator timings, readable with profile/1 and summarise_profile/1. Off by default because it is not free.
  • :signature. Which signature to compile for, by index or by name.
  • :max_model_bytes. Refuse a model file larger than this.

Raising version of new/2.

Link to this function

new!(env, model_path, opts)

View Source

Raising version of new/3.

@spec pending_events(reference()) :: {:ok, non_neg_integer()} | {:error, String.t()}

How many profiling events are waiting, without reading them.

Zero for a model compiled without profile: true. This is what sizes the copy profile/2 has to make, so it is the number to look at before calling it on a memory-constrained target.

Raising version of pending_events/1.

@spec platform_support() ::
  {:ok, %{required(atom()) => boolean()}} | {:error, String.t()}

Which buffer kinds this platform can reach, e.g. %{metal: true, opencl: false}.

Answers {:error, reason} on a build without the LiteRT API; available?/0 is the question to ask first.

@spec profile(reference()) :: {:ok, [event()]} | {:error, String.t()}

Every profiling event recorded so far, oldest first.

@spec profile(reference(), non_neg_integer()) ::
  {:ok, [event()]} | {:error, String.t()}

The most recent limit profiling events, or all of them when limit is zero.

limit bounds the events returned, not the reading: LiteRT will not hand over part of a backlog, so every call copies whatever pending_events/1 reports. That is about 109 MiB for a full buffer, which is nothing on a workstation and fatal on a board with 256 MB.

Raising version of profile/1.

Raising version of profile/2.

@spec reset_profile(reference()) :: :ok | {:error, String.t()}

Forget the events recorded so far and keep recording.

Raising version of reset_profile/1.

@spec run(reference(), [binary()]) :: {:ok, [binary()]} | {:error, String.t()}

Run the model over a list of input binaries, one per input tensor.

The sizes each input has to be are io_sizes/1, and a wrong size is refused rather than read past.

Raising version of run/2.

Link to this function

run_with_metrics(model, inputs)

View Source
@spec run_with_metrics(reference(), [binary()]) ::
  {:ok, {[binary()], [{binary(), metric_value()}]}} | {:error, String.t()}

Run the model and collect whatever counters the accelerator reports.

Link to this function

run_with_metrics(model, inputs, detail_level)

View Source
@spec run_with_metrics(reference(), [binary()], non_neg_integer()) ::
  {:ok, {[binary()], [{binary(), metric_value()}]}} | {:error, String.t()}

Run with metrics collection bracketing the inference.

Usually the counters come back empty. Filling them in is the accelerator's job, through two entries of its definition that are allowed to be null, so an empty list means nobody offered anything rather than that something went wrong. Use profile/1 for timings; this is for counters a backend chooses to expose.

Link to this function

run_with_metrics!(model, inputs)

View Source

Raising version of run_with_metrics/2.

Link to this function

run_with_metrics!(model, inputs, detail_level)

View Source

Raising version of run_with_metrics/3.

Link to this function

signatures(env, model_path)

View Source
@spec signatures(reference(), String.t()) :: {:ok, [binary()]} | {:error, String.t()}

The names of a model file's signatures, without compiling it.

A model with no named signature reports the one default signature LiteRT gives it, so the list is never empty.

Link to this function

signatures!(env, model_path)

View Source

Raising version of signatures/2.

Link to this function

summarise_profile(model)

View Source
@spec summarise_profile(reference()) ::
  {:ok, [summary_entry()]} | {:error, String.t()}

Per-operator totals over every run since the last reset, slowest first.

Only operator events are folded in. The enclosing Invoke, tensor allocation and LiteRT's own buffer handling are events too, and adding them together would count the operators twice, so they stay in profile/1 alone.

Link to this function

summarise_profile!(model)

View Source

Raising version of summarise_profile/1.