Packages

MLX bindings for Elixir via mlx-c. An Nx backend and Nx.Defn compiler for Apple's MLX machine learning framework on Apple Silicon.

Current section

Files

Jump to
elixir_mlx lib mlx random.ex
Raw

lib/mlx/random.ex

defmodule Mlx.Random do
@moduledoc """
Random number generation using MLX's PRNG.
MLX uses a splittable PRNG (like JAX). You create a key with `key/1`,
then split it for reproducible random sequences.
## Example
key = Mlx.Random.key(42)
{k1, k2} = Mlx.Random.split(key)
t = Mlx.Random.normal(k1, {3, 3})
"""
alias Mlx.NIF
defp s, do: elem(NIF.default_cpu_stream(), 1)
defp unwrap!({:ok, val}), do: val
defp unwrap!({:error, msg}), do: raise("MLX error: #{msg}")
defp to_mlx_scalar(val) do
t = Nx.tensor(val, type: :f32, backend: Mlx.Backend)
t.data.ref
end
@doc """
Creates a PRNG key from an integer seed.
Returns an opaque key tensor used for all random operations.
"""
def key(seed) when is_integer(seed) do
{:ok, ref} = NIF.mlx_random_key(seed)
ref
end
@doc """
Seeds the global random number generator.
"""
def seed(seed) when is_integer(seed) do
:ok = NIF.mlx_random_seed(seed)
:ok
end
@doc """
Splits a PRNG key into two new keys.
Returns `{key1, key2}`.
"""
def split(key) do
{k1, k2} = unwrap!(NIF.mlx_random_split(key, s()))
{k1, k2}
end
@doc """
Splits a PRNG key into `num` new keys.
Returns a single array of shape `{num, 2}` containing the keys.
"""
def split(key, num) when is_integer(num) do
unwrap!(NIF.mlx_random_split_num(key, num, s()))
end
@doc """
Generates uniform random values in `[low, high)`.
## Options
* `:shape` - output shape (default `{}` for scalar)
* `:dtype` - output type (default `:f32`)
* `:key` - PRNG key (default: global state)
"""
def uniform(opts \\ []) do
low = Keyword.get(opts, :low, 0.0)
high = Keyword.get(opts, :high, 1.0)
shape = Keyword.get(opts, :shape, {}) |> Tuple.to_list()
dtype = Keyword.get(opts, :dtype, :f32)
key = Keyword.get(opts, :key, nil)
low_ref = to_mlx_scalar(low)
high_ref = to_mlx_scalar(high)
key_ref = key || :nil
ref = unwrap!(NIF.mlx_random_uniform(low_ref, high_ref, shape, dtype, key_ref, s()))
%Nx.Tensor{
data: %Mlx.Backend{ref: ref},
type: Mlx.Dtype.to_nx(dtype),
shape: List.to_tuple(shape),
names: List.duplicate(nil, length(shape))
}
end
@doc """
Generates normal (Gaussian) random values.
## Options
* `:shape` - output shape (default `{}` for scalar)
* `:dtype` - output type (default `:f32`)
* `:loc` - mean (default `0.0`)
* `:scale` - standard deviation (default `1.0`)
* `:key` - PRNG key (default: global state)
"""
def normal(opts \\ []) do
shape = Keyword.get(opts, :shape, {}) |> Tuple.to_list()
dtype = Keyword.get(opts, :dtype, :f32)
loc = Keyword.get(opts, :loc, 0.0) / 1
scale = Keyword.get(opts, :scale, 1.0) / 1
key = Keyword.get(opts, :key, nil)
key_ref = key || :nil
ref = unwrap!(NIF.mlx_random_normal(shape, dtype, loc, scale, key_ref, s()))
%Nx.Tensor{
data: %Mlx.Backend{ref: ref},
type: Mlx.Dtype.to_nx(dtype),
shape: List.to_tuple(shape),
names: List.duplicate(nil, length(shape))
}
end
@doc """
Generates Bernoulli random values (0 or 1).
## Options
* `:p` - probability of 1 (default `0.5`, as a scalar Nx tensor)
* `:shape` - output shape (default `{}`)
* `:key` - PRNG key (default: global state)
"""
def bernoulli(opts \\ []) do
p = Keyword.get(opts, :p, 0.5)
shape = Keyword.get(opts, :shape, {}) |> Tuple.to_list()
key = Keyword.get(opts, :key, nil)
p_ref = to_mlx_scalar(p)
key_ref = key || :nil
ref = unwrap!(NIF.mlx_random_bernoulli(p_ref, shape, key_ref, s()))
%Nx.Tensor{
data: %Mlx.Backend{ref: ref},
type: {:u, 8},
shape: List.to_tuple(shape),
names: List.duplicate(nil, length(shape))
}
end
@doc """
Generates random integers in `[low, high)`.
## Options
* `:shape` - output shape (default `{}`)
* `:dtype` - output type (default `:s32`)
* `:key` - PRNG key (default: global state)
"""
def randint(low, high, opts \\ []) do
shape = Keyword.get(opts, :shape, {}) |> Tuple.to_list()
dtype = Keyword.get(opts, :dtype, :s32)
key = Keyword.get(opts, :key, nil)
low_ref = to_mlx_scalar(low)
high_ref = to_mlx_scalar(high)
key_ref = key || :nil
ref = unwrap!(NIF.mlx_random_randint(low_ref, high_ref, shape, dtype, key_ref, s()))
%Nx.Tensor{
data: %Mlx.Backend{ref: ref},
type: Mlx.Dtype.to_nx(dtype),
shape: List.to_tuple(shape),
names: List.duplicate(nil, length(shape))
}
end
@doc """
Generates truncated normal random values in `[lower, upper]`.
## Options
* `:shape` - output shape (default `{}`)
* `:dtype` - output type (default `:f32`)
* `:key` - PRNG key (default: global state)
"""
def truncated_normal(lower, upper, opts \\ []) do
shape = Keyword.get(opts, :shape, {}) |> Tuple.to_list()
dtype = Keyword.get(opts, :dtype, :f32)
key = Keyword.get(opts, :key, nil)
lower_ref = to_mlx_scalar(lower)
upper_ref = to_mlx_scalar(upper)
key_ref = key || :nil
ref =
unwrap!(NIF.mlx_random_truncated_normal(lower_ref, upper_ref, shape, dtype, key_ref, s()))
%Nx.Tensor{
data: %Mlx.Backend{ref: ref},
type: Mlx.Dtype.to_nx(dtype),
shape: List.to_tuple(shape),
names: List.duplicate(nil, length(shape))
}
end
@doc """
Samples from a categorical distribution defined by logits.
## Options
* `:axis` - axis along which to sample (default `-1`)
* `:key` - PRNG key (default: global state)
"""
def categorical(%Nx.Tensor{data: %Mlx.Backend{ref: logits_ref}} = logits, opts \\ []) do
axis = Keyword.get(opts, :axis, -1)
key = Keyword.get(opts, :key, nil)
key_ref = key || :nil
ref = unwrap!(NIF.mlx_random_categorical(logits_ref, axis, key_ref, s()))
# Output shape: logits shape with the sampled axis removed
out_shape =
logits.shape
|> Tuple.to_list()
|> List.delete_at(if(axis < 0, do: tuple_size(logits.shape) + axis, else: axis))
|> List.to_tuple()
%Nx.Tensor{
data: %Mlx.Backend{ref: ref},
type: {:u, 32},
shape: out_shape,
names: List.duplicate(nil, tuple_size(out_shape))
}
end
@doc """
Generates Gumbel random values.
## Options
* `:shape` - output shape (default `{}`)
* `:dtype` - output type (default `:f32`)
* `:key` - PRNG key (default: global state)
"""
def gumbel(opts \\ []) do
shape = Keyword.get(opts, :shape, {}) |> Tuple.to_list()
dtype = Keyword.get(opts, :dtype, :f32)
key = Keyword.get(opts, :key, nil)
key_ref = key || :nil
ref = unwrap!(NIF.mlx_random_gumbel(shape, dtype, key_ref, s()))
%Nx.Tensor{
data: %Mlx.Backend{ref: ref},
type: Mlx.Dtype.to_nx(dtype),
shape: List.to_tuple(shape),
names: List.duplicate(nil, length(shape))
}
end
@doc """
Generates Laplace-distributed random values.
## Options
* `:shape` - output shape (default `{}`)
* `:dtype` - output type (default `:f32`)
* `:loc` - location parameter (default `0.0`)
* `:scale` - scale parameter (default `1.0`)
* `:key` - PRNG key (default: global state)
"""
def laplace(opts \\ []) do
shape = Keyword.get(opts, :shape, {}) |> Tuple.to_list()
dtype = Keyword.get(opts, :dtype, :f32)
loc = Keyword.get(opts, :loc, 0.0) / 1
scale = Keyword.get(opts, :scale, 1.0) / 1
key = Keyword.get(opts, :key, nil)
key_ref = key || :nil
ref = unwrap!(NIF.mlx_random_laplace(shape, dtype, loc, scale, key_ref, s()))
%Nx.Tensor{
data: %Mlx.Backend{ref: ref},
type: Mlx.Dtype.to_nx(dtype),
shape: List.to_tuple(shape),
names: List.duplicate(nil, length(shape))
}
end
end