# Getting Started with ExBurn

## What is ExBurn?

ExBurn is a middle layer between [Nx](https://github.com/elixir-nx/nx) (Numerical Elixir) and [Burn](https://github.com/tracel-ai/burn) (a Rust deep learning framework). It lets you write ML code in Elixir that runs on the GPU — on NVIDIA cards (CUDA), Apple Silicon (Metal), or Android (Vulkan).

```
Elixir code → Nx.Defn → ExBurn → Burn/CubeCL → GPU
```

## Installation

Add to `mix.exs`:

```elixir
def deps do
  [
    {:ex_burn, "~> 0.5"},
    {:nx, ">= 0.12.0 and < 2.0.0"},
    {:axon, "~> 0.8"},
    {:ex_cubecl, ">= 0.5.0"}
  ]
end
```

```bash
mix deps.get
mix compile   # first build compiles the Rust NIF — expect a few minutes
```

### Prerequisites

| Requirement | Version | Notes |
|---|---|---|
| Elixir | ~> 1.18 | |
| OTP | 27+ | |
| Rust stable | any | Needed for NIF compilation (until precompiled binaries ship) |
| GPU drivers | — | CUDA / Metal / Vulkan depending on platform |

For iOS: `rustup target add aarch64-apple-ios`
For Android: `rustup target add aarch64-linux-android`

The NIF builds **CPU-only by default**. To build with GPU acceleration before
compiling, run `./build.sh` (auto-detects metal/cuda/vulkan) or set
`RUSTLER_NIF_CARGO_FEATURES=<backend>`.

### Step-by-Step Verification

Confirm each layer works before moving on:

```elixir
# 1. Is the NIF loaded and healthy?
ExBurn.nif_loaded?()
#=> true

# 2. Does the full Nx → Backend → NIF → Burn pipeline work?
ExBurn.smoke_test()
#=> :ok

# 3. What device are we running on?
ExBurn.summary()
#=>
# ExBurn v0.5.0
# ──────────────────────────────
# Device: Metal (Apple M2 Pro)     ← or "NdArray (CPU)"
# GPU: available                   ← CPU-only builds report "not available"
# Backends: metal
```

If any step fails, see the [Troubleshooting](#troubleshooting) section below.

## Basic Tensor Operations

```elixir
# Set ExBurn as the default backend — all Nx ops now go through Burn
Nx.default_backend(ExBurn.Backend)

# Or use the convenience function
ExBurn.configure!()

# Create tensors
a = Nx.tensor([1.0, 2.0, 3.0])
b = Nx.tensor([4.0, 5.0, 6.0])

# Element-wise operations
Nx.add(a, b)        # [5.0, 7.0, 9.0]
Nx.multiply(a, b)   # [4.0, 10.0, 18.0]

# Matrix operations
m = Nx.tensor([[1.0, 2.0], [3.0, 4.0]])
Nx.transpose(m)     # [[1.0, 3.0], [2.0, 4.0]]
```

## GPU-Accelerated Functions with `defn`

The `ExBurn.Defn.Compiler` implements the `Nx.Defn.Compiler` behaviour, letting you write GPU-accelerated numerical functions:

```elixir
# Set ExBurn as both backend and compiler
Nx.default_backend(ExBurn.Backend)
Nx.Defn.global_default_options(compiler: ExBurn.Defn.Compiler)

defmodule MyMath do
  import Nx.Defn

  defn add_and_scale(x, y, scale) do
    x |> Nx.add(y) |> Nx.multiply(scale)
  end

  defn dot_product(a, b) do
    a |> Nx.multiply(b) |> Nx.sum()
  end
end

# These execute on GPU via Burn
MyMath.add_and_scale(Nx.tensor([1.0, 2.0]), Nx.tensor([3.0, 4.0]), Nx.tensor(2.0))
#=> #Nx.Tensor<[8.0, 12.0]>
```

Per-function compiler override (no global setting):

```elixir
defmodule MyMath do
  import Nx.Defn

  defn my_fun(x), do: Nx.sin(x)
end

# Run just this call through the ExBurn compiler
Nx.Defn.jit_apply(&MyMath.my_fun/1, [x], compiler: ExBurn.Defn.Compiler)
```

## Checking GPU Availability

```elixir
# Quick check
ExBurn.default_device()    # :gpu or :cpu
ExBurn.device_name()       # e.g. "CUDA (NVIDIA GPU)" or "Metal (Apple GPU)"
ExBurn.device_info()       # full map with :device, :gpu_available, :backend, :available_backends
ExBurn.cuda_available?()   # true if NVIDIA GPU detected
```

## Using BurnBridge Directly

For performance-critical code, bypass the Nx layer and talk to Burn directly:

```elixir
# Create Burn tensors directly
t1 = ExBurn.BurnBridge.zeros([3, 3], :f32)
t2 = ExBurn.BurnBridge.ones([3, 3], :f32)

# Perform operations (single NIF call each)
t3 = ExBurn.BurnBridge.add(t1, t2)
t4 = ExBurn.BurnBridge.matmul(t1, t2)
t5 = ExBurn.BurnBridge.relu(t3)

# Convert back to Nx when needed
nx_tensor = ExBurn.BurnBridge.to_nx(t3)
```

## Using ExCubecl for GPU Buffers

ExCubecl provides low-level GPU buffer management:

```elixir
# Create GPU-resident buffers
{:ok, a} = ExCubecl.buffer([1.0, 2.0, 3.0], [3], :f32)
{:ok, b} = ExCubecl.buffer([4.0, 5.0, 6.0], [3], :f32)

# Inspect
{:ok, [3]} = ExCubecl.shape(a)
{:ok, 12} = ExCubecl.size(a)  # bytes

# Read data back
{:ok, data} = ExCubecl.read(a)

# Buffers are automatically freed when GC'd
```

## Project Structure

```
lib/ex_burn/
  ex_burn.ex            — Main API (version, configure!, device_info)
  defn_compiler.ex      — Nx.Defn.Compiler for GPU-accelerated defn
  backend.ex            — Nx.Backend implementation (delegates to Burn via NIF)
  nif.ex                — Rustler NIF stubs (40+ functions)
  nif_helper.ex         — Safe NIF wrappers ({:ok, result} tuples)
  tensor.ex             — Nx ↔ Burn tensor conversion utilities
  error.ex              — Structured error type (ExBurn.Error)
  burn_bridge.ex        — High-level Burn API (direct tensor ops)
  cubecl_bridge.ex      — GPU compute via ExCubecl (buffers, kernels, pipelines)
  model.ex              — Model definition, compilation, save/load
  training.ex           — Training loop (optimizers, LR schedules, callbacks)
  serving.ex            — Nx.Serving integration for batched inference
  serving/server.ex     — Serving server implementation

native/ex_burn_nif/
  src/lib.rs            — Rust NIF with real Burn Autodiff<CubeCL> operations
  Cargo.toml            — Burn + CubeCL + Autodiff dependencies
```

## Troubleshooting

| Symptom | Likely cause | Fix |
|---|---|---|
| `:erlang.nif_error(:nif_not_loaded)` | Native library wasn't compiled or linked | `mix clean && mix compile`; verify Rust is installed (`cargo --version`) |
| `dtype :f64 is not supported by the NIF` | A non-f32 dtype crossed the raw NIF boundary (direct `BurnBridge`/`Nif` call) | Use `Nx.as_type(t, {:f, 32})` first, or route through `Nx` ops with `ExBurn.Backend`, which value-converts automatically |
| `{:error, "Erlang error: :nif_panicked"}` | Rust-side assertion fired (rank/dtype mismatch on a direct NIF call) | Re-run with `RUST_BACKTRACE=1` for a Rust stack trace; check operation constraints in the README |
| `ExBurn.smoke_test()` returns `{:error, _}` | Pipeline broken at some layer | Re-run after `mix clean && mix compile`; if it persists, open an issue with the error message |
| GPU shows "not available" but you have one | NIF was built CPU-only | `./build.sh metal` (or cuda/vulkan), then recompile |

## Next Steps

- [Deep Learning Guide](06_deep_learning_guide.md) — Step-by-step lessons for learning deep learning with ExBurn
- [Training Models](02_training.md) — Define, compile, and train neural networks
- [Mobile Deployment](03_mobile_deployment.md) — iOS/Android compilation and optimization
- [Architecture Deep-Dive](04_architecture.md) — How the pipeline works internally
- [Training Optimization Guide](05_training_optimization.md) — Best practices for fast, stable training
- [Benchmarks](07_benchmarks.md) — Performance numbers and how to reproduce them
- [Contributing](../CONTRIBUTING.md) — Development workflow, testing, adding operations
