tandem rng tandem-torch GitHub

API

Use

import torch
from tandem_torch import Tandem

t = Tandem(42)                                   # the spec's stream for seed 42
u = t.rand(1_000_000)                            # float64 in [0, 1), 53 random bits
f = t.rand(1 << 20, dtype=torch.float32, device="cuda")
w = t.bits(1 << 20, dtype=torch.uint32)          # stream words
z = t.randn(1000)                                # ziggurat, one 64-bit draw each
e = t.exponential(1000)                          # -log(1 - u), one float64 uniform each
b = t.randbool(100)
i = t.randint(-5, 5, (4, 4))                     # int64 on [-5, 5), Lemire, same values on CUDA
p = t.randperm(10)                               # Fisher-Yates, defined on the CPU
x = t.shuffle(torch.arange(12).view(3, 4), dim=1)
t.fill_(torch.empty(4096, dtype=torch.float16))  # fill in place, any supported dtype
x = t.at(torch.float32, 1000)                    # element 1000 of the next float32 fill, no advance
worker = t.split(7)                              # by index, from the key alone
kids = t.fork(4)                                 # from the current block, parent moves on
t.key, t.position, t.chunk_length                # transport form
s = t.get_state(); t.set_state(s)                # as on torch.Generator, also manual_seed

Reference

Bounded draws, normals, exponentials, randperm and bfloat16 are not in the specification. Design gives their contracts.

Every call aligns the stream position to the element width, reads, and advances, as the specification requires, so a uint8 draw followed by a float64 draw skips to the next 64-bit boundary. The functional forms tandem_torch.rand(key, position, *shape, ...) and tandem_torch.bits(...) return (tensor, next_position) for code that keeps the position itself.

from_key requires an initial position below 2**63. Draws and set_state accept successor positions throughout the unsigned 64-bit range. Each draw must stay within that range. An empty randint call leaves the position unchanged, including full-width integer ranges.

out retains its identity and view semantics. Strided, conjugate, and negative views receive the same logical values as a newly allocated tensor.

Supported dtypes: bool, uint8 to uint64, the signed integers as the unsigned draws reinterpreted, float16 ((raw >> 5) * 2^-11), float32 (24 random bits) and float64 (53 random bits), and complex64 and complex128, whose element is the real then the imaginary component as in the specification. bfloat16 is a tandem-torch extension that is not in the specification: (raw16 >> 8) * 2^-8 of a 16-bit word, the float16 rule with 8 fraction bits.

at(dtype, i) is random access: element i of the fill of dtype that would start at the current position, as a Python number, computed on the host without advancing. It supports int32, uint32, int64, uint64, float32 and float64, the types random access has in tandem-c and tandem-cuda.

Parallel use

Element i of a fill is draw i, so ranks, threads or devices that start at the position of their first element, or draw from split(task), reproduce a serial run for any decomposition, as Appendix B of the specification shows.