Skip to main content

lib/ex_burn.ex

defmodule ExBurn do
  @moduledoc """
  ExBurn — Elixir bridge to the [Burn](https://burn.dev) deep learning framework.

  ExBurn provides a high-level API for tensor computation, neural network
  training, and GPU-accelerated machine learning by delegating to Burn
  via Rust NIFs (Native Implemented Functions).

  ## Architecture

  ```
  Elixir/Axon → Nx.Defn → ExBurn.Defn.Compiler → ExBurn.Backend → ExBurn.Nif (Rustler) → Burn/CubeCL → GPU
                                                                                   ↕
                                                                             ExCubecl (GPU buffers, kernels, pipelines)
  ```

  ## Quick Start

      # Set ExBurn as the default Nx backend
      Nx.default_backend(ExBurn.Backend)

      # Create and manipulate tensors
      t = Nx.tensor([1.0, 2.0, 3.0])
      Nx.add(t, t) |> Nx.to_list()

  ## Modules

  - `ExBurn.Defn.Compiler` — `Nx.Defn.Compiler` implementation for GPU-accelerated defn
  - `ExBurn.Backend` — Nx backend that delegates to Burn via NIF
  - `ExBurn.Nif`     — Rustler NIF stubs for Burn interop
  - `ExBurn.Tensor`  — Tensor conversion utilities between Nx and Burn formats
  - `ExBurn.BurnBridge` — High-level bridge for Burn operations and ExCubecl buffers
  - `ExBurn.CubeclBridge` — GPU compute via ExCubecl (buffers, kernels, pipelines)
  - `ExBurn.Model`   — Model definition and training orchestration
  - `ExBurn.Training` — Training loop implementation
  - `ExBurn.Serving` — Nx.Serving integration for batched concurrent inference
  """

  @doc "Returns the current version of ExBurn."
  @spec version() :: String.t()
  def version, do: Application.spec(:ex_burn, :vsn) |> to_string()

  @doc """
  Returns the default device for tensor operations.

  Currently returns `:gpu` when a compatible GPU backend is available,
  otherwise falls back to `:cpu`.
  """
  @spec default_device() :: :cpu | :gpu
  def default_device do
    if ExBurn.NifHelper.gpu_available(), do: :gpu, else: :cpu
  end

  @doc """
  Returns the name of the active compute device (e.g., "CUDA (NVIDIA GPU)").
  """
  @spec device_name() :: String.t()
  def device_name do
    ExBurn.BurnBridge.device_name()
  end

  @doc """
  Returns a map with device information including GPU availability,
  backend name, and available backends.
  """
  @spec device_info() :: map()
  def device_info do
    ExBurn.BurnBridge.device_info()
  end

  @doc """
  Checks whether an NVIDIA CUDA GPU is available.
  """
  @spec cuda_available?() :: boolean()
  def cuda_available? do
    ExBurn.CubeclBridge.cuda_available?()
  end

  @doc """
  Sets the default Nx backend to `ExBurn.Backend`.

  After calling this, all Nx operations will be executed via Burn.
  """
  @spec configure!() :: :ok
  def configure! do
    Nx.default_backend(ExBurn.Backend)
    :ok
  end

  @doc """
  Returns a summary of the ExBurn environment.

  Includes version, device info, GPU availability, and available backends.

  ## Example

      IO.puts(ExBurn.summary())
  """
  @spec summary() :: String.t()
  def summary do
    device = device_name()
    gpu? = cuda_available?()
    backends = ExBurn.CubeclBridge.available_backends()

    """
    ExBurn v#{version()}
    ──────────────────────────────
    Device: #{device}
    GPU: #{if gpu?, do: "available", else: "not available"}
    Backends: #{Enum.join(Enum.map(backends, &Atom.to_string/1), ", ")}
    """
    |> String.trim()
  end

  @doc """
  Checks whether the NIF library is loaded and functional.

  Returns `true` if the NIF responds to a basic health check,
  `false` otherwise.
  """
  @spec nif_loaded?() :: boolean()
  def nif_loaded? do
    try do
      apply(ExBurn.Nif, :gpu_available, [])
      true
    rescue
      _ -> false
    end
  end

  @doc """
  Returns the number of NIF functions registered by the Rust library.

  Useful for debugging NIF loading issues.
  """
  @spec nif_function_count() :: non_neg_integer()
  def nif_function_count do
    if Code.ensure_loaded?(ExBurn.Nif) do
      apply(ExBurn.Nif, :debug_nif_count, [])
    else
      0
    end
  rescue
    _ -> 0
  end

  @doc """
  Performs a quick smoke test of the ExBurn pipeline.

  Creates a small tensor, runs it through the backend, and verifies
  the result. Returns `:ok` on success or `{:error, reason}` on failure.

  ## Example

      case ExBurn.smoke_test() do
        :ok -> IO.puts("ExBurn is working!")
        {:error, message} -> IO.puts("ExBurn error: " <> message)
      end
  """
  @spec smoke_test() :: :ok | {:error, String.t()}
  def smoke_test do
    try do
      Nx.default_backend(ExBurn.Backend)

      a = Nx.tensor([1.0, 2.0, 3.0])
      b = Nx.tensor([4.0, 5.0, 6.0])
      result = Nx.add(a, b)
      [5.0, 7.0, 9.0] = Nx.to_list(result)

      :ok
    rescue
      e -> {:error, "Smoke test failed: #{Exception.message(e)}"}
    after
      Nx.default_backend(Nx.BinaryBackend)
    end
  end
end