Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
15 changes: 15 additions & 0 deletions exla/guides/backend_documentation/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Backend documentation

EXLA-specific documentation for Nx backend behaviour.

These guides document divergent behaviour, backend-specific options, and
limitations for Nx operations. They mirror the structure of the Nx API — see
the [backend documentation convention](https://hexdocs.pm/nx/backend_documentation-convention.html).

## Guides

* [Nx](backend_documentation-nx.html) — top-level `Nx` blocks, transfers, and `defn` integration
* [Nx.LinAlg](backend_documentation-nx_lin_alg.html) — linear algebra blocks

Implementation code lives in `EXLA.Defn`, `EXLA.CustomCall`, and related modules.

92 changes: 92 additions & 0 deletions exla/guides/backend_documentation/nx.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Nx (EXLA)

EXLA-specific notes for top-level `Nx` operations where behaviour diverges from
the portable Nx API or from other backends.

Linear algebra is documented separately in [Nx.LinAlg](backend_documentation-nx_lin_alg.html).

## runtime_call/4

Runtime Elixir callback from `defn` (`runtime_call` expression).

### Platforms

* **Host / CUDA** — supported via a device-side callback bridged through
`EXLA.MLIR.Value.runtime_call/3`
* **ROCm / TPU** — raises with a message to use `:host` or `:cuda`

### Outside `defn`

Executes the callback directly on the input backend without compilation.

### Warnings

Avoid `Nx.backend_transfer/2` on callback tensors inside the function when
using `Nx.Defn.Evaluator`. Avoid running other Nx computations on the same GPU
device from within the callback (deadlock risk).

## backend_copy/2

Copy tensor data to another backend (`EXLA.Backend.backend_copy/3`).

### Behaviour

Copying to `EXLA.Backend` on the same client and device returns the tensor
unchanged. Cross-device copies use `EXLA.DeviceBuffer.copy_to_device/3`.
Copying out reads device memory via `EXLA.DeviceBuffer.read/1` and delegates
to the target backend's `from_binary/3`.

### Options

* `:client`, `:device_id` — select the EXLA client and device

## backend_transfer/2

Transfer tensor data to another backend (`EXLA.Backend.backend_transfer/3`).

### Behaviour

Same as `backend_copy/2` followed by deallocation of the source device buffer
when leaving `EXLA.Backend`.

### Options

* `:client`, `:device_id` — select the EXLA client and device

## to_pointer/2

Export device memory as a pointer (`EXLA.Backend.to_pointer/2`).

### Modes

* `:local` — supported on **host** and **CUDA** clients; returns a local
address integer
* `:ipc` — supported on **host** (`shm_open` handle) and **CUDA** (IPC
handle plus device id)

### Options

* `:permissions` — octal file mode for host IPC shared memory (default
`0o400`)

### Limitations

Not supported for ROCm, TPU, or non-device buffers.

Comment on lines +72 to +75

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is exactly the kind of documentation that this PR let's shine through!

## from_pointer/5

Import device memory from a pointer (`EXLA.Backend.from_pointer/5`).

### Behaviour

Creates an `EXLA.DeviceBuffer` from a local address or IPC handle on **host**
or **CUDA** clients. Pointer `data_size` must match the tensor byte size.

### Options

* `:client`, `:device_id` — destination device
* `:names` — tensor names for the result template

### Limitations

Not supported for ROCm or TPU.
69 changes: 69 additions & 0 deletions exla/guides/backend_documentation/nx_lin_alg.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Nx.LinAlg (EXLA)

EXLA-specific notes for `Nx.LinAlg` where behaviour diverges from the portable
Nx API or from other backends.

On GPU and TPU clients, the `:precision` option passed to `Nx.Defn.jit/2`
affects accumulator precision for many composed linear algebra graphs compiled
from the default callbacks. For decomposition verification tests, prefer
`precision: :highest`.

## qr/2

QR decomposition (`%Nx.Block.LinAlg.QR{}`).

### Supported platforms

* **Host** — native CPU custom call for supported **real** dtypes (`f16`,
`bf16`, `f32`, `f64`, and integer types cast to `f32`); default callback for
complex inputs and unsupported types
* **CUDA / ROCm / TPU** — default callback only (no native QR custom call)

### Options

* `:mode` — honoured by whichever implementation is selected (`:reduced` or
`:complete`)
* `:eps` — used by the default callback only; ignored by the native custom call

### Numerical notes

Native and default implementations may differ slightly in orthogonality of `q`
and triangular structure of `r` within floating-point tolerance. Reconstruction

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
and triangular structure of `r` within floating-point tolerance. Reconstruction
and triangular structure of `r` within floating-point tolerance.
For example, for an eigenpair `(lambda, v)`, `(-lambda, -v)` is
an equally valid eigenpair that would lead to radically different
`q` and `r` matrices. Reconstruction of

`q · r` should match the input within reasonable tolerances for the dtype.

## eigh/2

Hermitian eigendecomposition (`%Nx.Block.LinAlg.Eigh{}`).

### Supported platforms

* **Host** — native CPU custom call for supported **real** dtypes (`f32`,
`f64`, and integer types cast to `f32`); default callback for complex inputs and
unsupported types
* **CUDA / ROCm / TPU** — default callback only

### Options

* `:max_iter` and `:eps` — honoured by the default callback; ignored by the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* `:max_iter` and `:eps`honoured by the default callback; ignored by the
* `:max_iter` and `:eps`honored by the default callback; ignored by the

native custom call

### Numerical notes

Eigenvectors are not unique (sign and degenerate subspaces). Compare results
using reconstruction `v · diag(λ) · vᵀ` rather than direct eigenvector equality
across backends.

## svd/2

Singular value decomposition (`%Nx.Block.LinAlg.SVD{}`).

### Options

* `:max_iter` — honoured (default iterative algorithm in Nx)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* `:max_iter`honoured (default iterative algorithm in Nx)
* `:max_iter`honored (default iterative algorithm in Nx)

* `:full_matrices?` — honoured

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* `:full_matrices?`honoured
* `:full_matrices?`honored


### Numerical notes

SVD is iterative; results may vary across devices and precision settings.
Singular values are the most stable quantity to compare across backends.
Use `precision: :highest` on GPU when verifying decompositions.
7 changes: 7 additions & 0 deletions exla/lib/exla.ex
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ defmodule EXLA do
your block tag struct; see `EXLA.CustomCall` for the `call/4` contract,
including returning `:skip` to fall back to the block's default Elixir callback.

Backend-specific behaviour, options, and limitations for Nx functions are

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Backend-specific behaviour, options, and limitations for Nx functions are
Backend-specific behavior, options, and limitations for Nx functions are

documented in the [Backend documentation](backend_documentation.html) guides
(for example [Nx](backend_documentation-nx.html) and
[Nx.LinAlg](backend_documentation-nx_lin_alg.html)). See also the
[backend documentation convention](https://hexdocs.pm/nx/backend_documentation-convention.html)
in the Nx package.

## Clients

The `EXLA` library uses a client for compiling and executing code.
Expand Down
17 changes: 15 additions & 2 deletions exla/mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ defmodule EXLA.MixProject do

defp deps do
[
{:nx, path: "../nx"},
{:nx, "~> 0.12.0"},
# {:nx, path: "../nx"},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a stray change

{:telemetry, "~> 0.4.0 or ~> 1.0"},
{:xla, "~> 0.10.0", runtime: false},
{:fine, "~> 0.1", runtime: false},
Expand All @@ -87,9 +88,21 @@ defmodule EXLA.MixProject do
source_url_pattern: "#{@source_url}/blob/v#{@version}/exla/%{path}#L%{line}",
extras: [
"guides/rotating-image.livemd",
"CHANGELOG.md"
"CHANGELOG.md",
"guides/backend_documentation/index.md": [
filename: "backend_documentation"
],
"guides/backend_documentation/nx.md": [
filename: "backend_documentation-nx"
],
"guides/backend_documentation/nx_lin_alg.md": [
filename: "backend_documentation-nx_lin_alg"
]
],
skip_undefined_reference_warnings_on: ["CHANGELOG.md"],
groups_for_extras: [
"Backend documentation": ~r"^guides/backend_documentation/"
],
groups_for_modules: [
# EXLA,
# EXLA.Backend,
Expand Down
7 changes: 7 additions & 0 deletions exla/test/exla/backend_documentation_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
defmodule EXLA.BackendDocumentationTest do
use ExUnit.Case, async: true

doctest_file("guides/backend_documentation/index.md")
doctest_file("guides/backend_documentation/nx.md")
doctest_file("guides/backend_documentation/nx_lin_alg.md")
end
55 changes: 55 additions & 0 deletions nx/guides/backend_documentation/convention.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Backend documentation convention

Nx exposes a stable public API (`Nx`, `Nx.LinAlg`, and so on) while individual
backends implement the underlying callbacks and optional `Nx.block/4` lowerings.
Users who need to know how a specific backend differs from the portable API should
not have to read backend source code.

## Documentation guides

Each backend may publish **backend documentation guides** that mirror the Nx
module structure. The naming convention is:

guides/backend_documentation/nx.md # mirrors top-level `Nx`
guides/backend_documentation/nx_lin_alg.md # mirrors `Nx.LinAlg`

For example, EXLA documents operations in its [Backend documentation](https://hexdocs.pm/exla/backend_documentation.html)
guides, and Torchx in its [Backend documentation](https://hexdocs.pm/torchx/backend_documentation.html)
guides.

## What to document

Document an operation only when a backend has **divergent behaviour**, **backend-specific
options**, or **trade-offs and limitations** worth calling out relative to the
portable Nx API or to other backends. Examples:

* an option that a backend honours, ignores, or overrides
* platform or device support gaps (for example an operation unavailable on MPS)
* numerical or performance trade-offs that affect how you use the API
* warnings about deadlock, unsupported pointer modes, and similar constraints

Do not document routine lowerings (for example which StableHLO op or LibTorch
kernel is used) when behaviour matches the reference Nx implementation. If you
are implementing or overriding custom blocks, the backend source remains the best
source of truth.

Cross-link from the Nx function doc when a backend guide has relevant notes for
that operation.

## Relation to `Nx.Backend`

Most tensor operations go through `Nx.Backend` callbacks. Operations that need
a portable default with optional native acceleration use `Nx.block/4` and the
`Nx.Block.*` tags — see `Nx.Backend.block/4`. Backend documentation guides
should cover divergent behaviour for both callback implementations and block
lowerings.

## Example

```elixir
iex> Nx.take(Nx.tensor([10, 20, 30]), Nx.tensor([0, 2]))
#Nx.Tensor<
s32[2]
[10, 30]
>
```
Loading
Loading