:telemetry events emitted by Emily.
All span-style events use :telemetry.span/3 semantics, so attaching
to *:start, *:stop, and *:exception is sufficient for
histograms and error tracking.
Events
Evaluation boundaries
[:emily, :eval, :start | :stop | :exception] — Emily.eval/1.
The :stop event carries :duration (monotonic native units).
[:emily, :to_binary, :start | :stop | :exception] — fires for both
Emily.to_binary/1 and the Nx.to_binary/1 path on Emily.Backend.
Metadata: :byte_size, :shape, :dtype.
Fallback entry
[:emily, :fallback, :start | :stop | :exception] — fires whenever
an op routes through Nx.BinaryBackend because the MLX path is not
wired. Metadata: :op, :input_shapes, :input_dtypes.
Per-fallback behaviour is configurable via :fallback:
config :emily, fallback: :silent | :warn | :raise:silent(default) — span events still fire, no log, no raise. Library consumers and CI logs stay quiet.:warn— one-shotLogger.warningper{op, input_shapes}pair. A Bumblebee user sees"indexed_put on shape [...] fell back to Nx.BinaryBackend"once per shape, not every forward pass. Typically set inconfig/dev.exswhile chasing performance regressions.:raise— raises aRuntimeErrorcarrying the op, input shapes, and input dtypes. Use in CI to fail builds when a hot path unexpectedly routes throughNx.BinaryBackend.
In :raise mode the :start/:stop span events do not fire
because the raise happens on entry; :silent and :warn preserve
the full span.
The legacy :warn_on_fallback boolean is still honoured when
:fallback is unset (true → :warn, false → :silent).
Prefer :fallback in new code; if both are set, :fallback wins.
Block-dispatch fallback
[:emily, :block, :fallback] — a discrete event (not a span) fired
each time the backend's block callback (Nx.Block.* dispatch,
Nx 0.12+) falls through to Nx's supplied default fun rather than a
wired native handler. Measurements: none (%{}). Metadata:
:struct (the Nx.Block.* struct module) and :args_count. Use it
in soak runs to flag a block op that used to lower natively but now
lands on the composed-defn slow path.
Native-compiler fallback
[:emily, :compiler, :fallback] — a discrete event (not a span) that
fires when a native: true defn can't be lowered by the Expr compiler
and routes through Nx.Defn.Evaluator instead (see
Emily.Compiler's :native_fallback option). Measurements:
:count (always 1). Metadata: :key (the JIT key) and :reason
(the lowering error message, which names the unsupported op or
construct). A one-shot Logger.warning per distinct :reason is also
emitted — set config :emily, :native_fallback, :raise to fail
instead of falling back.
Memory stats (poll-driven)
[:emily, :memory, :stats] — discrete event, not a span. Call
Emily.Memory.stats/0 to sample; measurements:
:active— bytes currently held by MLX:peak— high-water mark since lastEmily.Memory.reset_peak/0:cache— bytes cached for reuse
Wire this into a periodic task (e.g. Process.send_after/3 loop) to
graph memory drift in a long-running serving.
Attaching a handler
:telemetry.attach(
"emily-fallback-log",
[:emily, :fallback, :stop],
fn _event, measurements, metadata, _config ->
IO.inspect({metadata.op, measurements.duration})
end,
nil
)
Summary
Functions
Sample the MLX allocator and emit [:emily, :memory, :stats].
Functions
@spec memory_stats() :: %{ active: non_neg_integer(), peak: non_neg_integer(), cache: non_neg_integer() }
Sample the MLX allocator and emit [:emily, :memory, :stats].
Prefer Emily.Memory.stats/0 in new code. This function remains as
the telemetry-oriented entry point for existing callers.
Returns the measurements map so callers can also log or plot inline.
Examples
iex> stats = Emily.Telemetry.memory_stats()
iex> Map.keys(stats) |> Enum.sort()
[:active, :cache, :peak]