Julia
DocsTandemRNG.jlusing Pkg; Pkg.add(url="https://github.com/tandem-rng/TandemRNG.jl")
using TandemRNG
rng = Tandem8x32(42)
x, rng = rand_next(rng, Float64) # value and next generatorOne generator, every language.
Tandem8x32 is a noncryptographic pseudorandom number generator with one specified stream that eighteen ports reproduce bit for bit, fast on CPUs and GPUs alike.
using Pkg; Pkg.add(url="https://github.com/tandem-rng/TandemRNG.jl")
using TandemRNG
rng = Tandem8x32(42)
x, rng = rand_next(rng, Float64) # value and next generator$ git clone https://github.com/tandem-rng/tandem-c && make -C tandem-c
#include "tandem.h"
tandem_rng rng = tandem_seed(42, 0, 0);
double x = tandem_next_f64(&rng);$ cargo add tandem-rng --git https://github.com/tandem-rng/tandem-rs
let mut rng = tandem_rng::Tandem::new(42);
let x = rng.next_f64();
let worker = rng.split(7); // by index, from the key$ pip install git+https://github.com/tandem-rng/tandem-numpy
from tandem_rng import TandemGenerator
g = TandemGenerator(42)
z = g.standard_normal(10**6)$ git clone https://github.com/tandem-rng/tandem-cuda
#include "tandem.cuh"
auto g = tandem::generator::seed(42);
g.fill_f64(x, n); // x in device memory$ pip install git+https://github.com/tandem-rng/tandem-jax
import jax, tandem_jax as tj
key = tj.key(42)
z = jax.random.normal(key, (1000,)) # any jax.random functionremotes::install_github("tandem-rng/tandem-r")
library(tandemrng)
rng <- tandem(42)
u <- tandem_runif(rng, 1e6)$ pip install --no-build-isolation git+https://github.com/tandem-rng/tandem-torch
from tandem_torch import Tandem
t = Tandem(42)
z = t.randn(1000) # also device="cuda"$ npm install github:tandem-rng/tandem-webgpu
import { seed, Tandem } from "tandem-webgpu";
const rng = new Tandem(seed(42n));
const xs = rng.fillF64(1 << 20);! fpm.toml: tandem_rng = { git = "https://github.com/tandem-rng/tandem-fortran" }
use tandem_rng
rng = tandem_new(42_int64)
x = rng%next_real64()$ git clone --recurse-submodules https://github.com/tandem-rng/tandem-kokkos
#include <tandem/kokkos.hpp>
tandem::Rng rng(42);
tandem::fill(x, rng); // any contiguous View$ git clone https://github.com/tandem-rng/tandem-java && mvn -f tandem-java install
Tandem rng = Tandem.seed(42);
double x = rng.nextDouble();
Tandem worker = rng.split(7);$ git clone https://github.com/tandem-rng/tandem-mojo && cd tandem-mojo && pixi install
from tandem import Tandem
var rng = Tandem(42)
var x = rng.next_f64()$ git clone --recurse-submodules https://github.com/tandem-rng/tandem-sycl
#include <tandem/sycl.hpp>
tandem::Rng rng(42);
tandem::fill(q, x, n, rng); // USM memory on queue q// Package.swift: .package(url: "https://github.com/tandem-rng/tandem-metal", branch: "main")
import Tandem
var rng = Tandem(seed: 42)
let x = rng.nextF64() // GPU fills take a Metal buffer$ pip install git+https://github.com/tandem-rng/tandem-mlx
import mlx.core as mx, tandem_mlx as tm
key = tm.key(42)
z = tm.normal(key, (1000,)) # Metal kernel on the GPU-- cabal.project: source-repository-package, location: https://github.com/tandem-rng/tandem-hs
import System.Random.Tandem qualified as T
let g0 = T.seed 42
(x, g1) = T.nextDouble g0 -- value and next generator$ git clone https://github.com/tandem-rng/tandem-ml && cd tandem-ml && opam install . && dune build
let g = Tandem.seed 42
let x, g = Tandem.float g
let worker = Tandem.split g 7 (* by index, from the key *)| port | hardware | Float64 uniform fill, GiB/s | Float64 normal fill, GiB/s | source |
|---|---|---|---|---|
| tandem-c | Apple M4 Pro, one thread | 16.5 std::mt19937_64 5.1 | 7.6 std::normal_distribution 0.90 | docs/speed.md at b031bed |
| TandemRNG.jl | Apple M4 Pro, one task | 17.2 Xoshiro 19.8 | 7.74 Xoshiro 7.19 | performance.md at 0410c6a |
| tandem-cuda | NVIDIA A100 40 GB | 1388 cuRAND 778 | 1064 cuRAND 567 | docs/speed.md at 59e7bcc |
GiB/s written to memory, with the baseline from the same page where it has one. The A100 fills run near the card's memory bandwidth. All three Float64 normal fills use the ziggurat.
The state is two halves of four words. The hidden half runs a bijective clock and takes feedback from the exposed half. The exposed half is a Philox-shaped Feistel step whose multipliers come from the hidden half. Each step emits the 128 exposed bits.
A keyed seeding function starts every chunk of 32 steps, and eight chunks interleave into one 1024-bit row, so eight GPU lanes write one contiguous row. split(i) derives a child from the key and an index, fork(n) a batch from the current block, and purpose(u) a named sub-stream. A child costs at most one seeding.
Element i of a fill equals draw i, so any split across threads, ranks or GPUs reproduces a serial run. Bounded integers, normals and exponentials follow one shared appendix, so the ports agree on those too.