tandem-sycl
Header-only SYCL implementation of Tandem8x32. It produces the stream of the specification bit for bit, on any SYCL device.
- API: the fills,
tandem::Rngand its draws. - Design: the fill kernels, normals and exponentials.
- Tests: what the suite checks and what CI runs.
- Performance: A100 figures against tandem-cuda and cuRAND.
Install
Tested with AdaptiveCpp 25.10.0 (OpenMP CPU backend, and CUDA on an NVIDIA A100) and Intel oneAPI DPC++ 2026.1.1 (OpenCL CPU runtime 2026.1.1).
Header-only. Clone with the submodule:
git clone --recurse-submodules https://github.com/tandem-rng/tandem-sycl
git submodule update --init # in a clone made without itAdd include/ and
external/tandem-cuda/include/ to the include path of a SYCL
build, or use CMake:
add_subdirectory(tandem-sycl)
target_link_libraries(app PRIVATE tandem::sycl)
add_sycl_to_target(TARGET app) # AdaptiveCpp, or -fsycl for DPC++The tandem::sycl target carries the include paths and
C++20, not the SYCL flags, because SYCL is a compiler mode. With DPC++,
build with -ffp-model=precise: icpx defaults
to fast math, whose float log and cos in
kernels miss the normals' tolerance by about 1e-4 relative. The host
normal draws use std::fma for bit identical results on
every compiler: on x86, build with -mfma, or each
multiply-add is a slow library call.
pixi.toml provides three environments:
pixi run test # AdaptiveCpp 25.10, OpenMP backend on the host CPU
pixi run -e dpcpp test-dpcpp # DPC++ 2026.1 with Intel's OpenCL CPU runtime, Linux
pixi run -e cuda test-cuda # AdaptiveCpp 25.10 for CUDA 12.9, Linux with an NVIDIA GPUAdaptiveCpp compiles kernels once to LLVM IR and translates them for the device at the first launch (its generic SSCP target), so one binary runs on the CPU and on NVIDIA GPUs. The CUDA build ran on driver 570, which supports CUDA 12.8, as the kernels reach the driver as PTX.
AI assistance
This port was written with the help of large language models under human direction. The design and the specification are human work, as is much of the Julia implementation. The code is tested bit for bit against every vector of the specification and against long stream dumps from the Julia implementation, and every value must match. The output does not depend on who or what wrote the code.