One computation. CPU, Metal, Vulkan. The same bits.
Write the deterministic path once, choose the backend at runtime,
and keep authoritative results bit-for-bit identical across supported targets.
one typed Flow
│
┌─────────────┼─────────────┐
│ │ │
CPU Metal Vulkan
│ │ │
└─────────────┼─────────────┘
│
identical state bytes
Website · Quick start · Docs · API & errors · Download
The algorithm below is declared once. Only the explicit target changes.
#include <rund/compute.hpp>
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <span>
#include <string_view>
#include <utility>
#include <vector>
int main() {
const std::array<std::int32_t, 4> input{1, 2, 3, 4};
const auto execute = [&input](const rund::compute::Device &device) {
auto flow = rund::compute::on(device).map<std::int32_t>(
"step", input.size(), [](auto value) { return value * 2 + 1; });
auto program = std::move(flow).compile();
if (!program) {
return rund::compute::Result<std::vector<std::int32_t>>::fail(
program.reason());
}
return program->run(std::span<const std::int32_t>{input});
};
const auto report = [](const std::string_view target, const auto &result) {
const std::string_view message = result.error();
std::fprintf(stderr, "%.*s failed (code=%u): %.*s\n",
static_cast<int>(target.size()), target.data(),
static_cast<unsigned>(result.code()),
static_cast<int>(message.size()), message.data());
return result.exit_code();
};
auto cpu_device = rund::compute::open(rund::compute::Target::cpu());
if (!cpu_device) {
return report("cpu", cpu_device);
}
const auto cpu = execute(*cpu_device);
if (!cpu) {
return report("cpu", cpu);
}
const auto same_bits = [](const auto &left, const auto &right) {
return left.size() == right.size() &&
std::memcmp(left.data(), right.data(),
left.size() * sizeof(std::int32_t)) == 0;
};
const std::vector<std::int32_t> expected{3, 5, 7, 9};
if (*cpu != expected) {
std::fputs("cpu output mismatch\n", stderr);
return 2;
}
std::size_t available_backend_count = 1u;
#if defined(__APPLE__) && (defined(__arm64__) || defined(__aarch64__))
constexpr bool require_accelerators = true;
#else
constexpr bool require_accelerators = false;
#endif
const auto compare = [&](const std::string_view name,
const rund::compute::Target target) {
auto device = rund::compute::open(target);
if (!device) {
if (!require_accelerators &&
device.reason() == rund::compute::Reason::AdapterUnavailable) {
const std::string_view message = device.error();
std::fprintf(stderr, "%.*s unavailable (code=%u): %.*s\n",
static_cast<int>(name.size()), name.data(),
static_cast<unsigned>(device.code()),
static_cast<int>(message.size()), message.data());
return 0;
}
return report(name, device);
}
const auto result = execute(*device);
if (!result) {
return report(name, result);
}
if (!same_bits(*cpu, *result)) {
std::fprintf(stderr, "%.*s output mismatch\n",
static_cast<int>(name.size()), name.data());
return 2;
}
++available_backend_count;
return 0;
};
if (const int metal = compare("metal", rund::compute::Target::metal());
metal != 0) {
return metal;
}
if (const int vulkan = compare("vulkan", rund::compute::Target::vulkan());
vulkan != 0) {
return vulkan;
}
if (available_backend_count == 3u) {
std::printf("same bytes: cpu = metal = vulkan [%d, %d, %d, %d]\n",
(*cpu)[0], (*cpu)[1], (*cpu)[2], (*cpu)[3]);
} else {
std::printf("verified [%d, %d, %d, %d] on %zu native backend(s)\n",
(*cpu)[0], (*cpu)[1], (*cpu)[2], (*cpu)[3],
available_backend_count);
}
return 0;
}There is no backend branch inside the computation, no backend-specific graph,
and no silent CPU fallback. Device opening is the only capability boundary. A
non-Apple candidate may report the exact AdapterUnavailable reason there;
once a device opens, every compile, execution, and byte comparison must pass.
The supported Darwin ARM64 tuple requires CPU, Metal, and Vulkan and preserves
the strict three-way output above. A headless non-Apple host remains an
executable example without pretending that an unavailable accelerator ran.
runD treats determinism as an execution contract rather than a compiler flag.
| Contract | What runD fixes |
|---|---|
| One graph identity | The same typed Flow produces one canonical graph and fingerprint for every backend. |
| Explicit numeric law | Integer, fixed-point, rounding, overflow, and strict floating-point policy are part of admission—not ambient compiler behavior. |
| Stable execution order | Scheduling, reductions, conflicts, publication, and replay observations have defined ordering. |
| No hidden fallback | CPU, Metal, and Vulkan are selected explicitly; failure cannot silently change the execution path. |
| Proof, not hope | Installed contracts compare exact output bytes, state hashes, graph identity, and replay evidence across available backends. |
This is what makes runD useful for simulations, deterministic game state, lockstep systems, reproducible compute pipelines, and any workload where “close enough” is a bug.
same input
+ same numeric contract
+ same graph identity
+ same ordered execution
= same authoritative bits
Read the public Determinism, Numerics, and Compute guides behind that claim.
- Write once, run explicitly — one C++20 Flow for CPU, Metal, and Vulkan.
- Resident GPU execution — compile and prepare once, then reuse Programs, Jobs, Batches, and dependent Pipelines.
- Replay without a second code path — Live, Record, Replay, and Scenario share one canonical input boundary.
- Bounded by construction — memory, graph size, compilation, queues, iterations, retention, and failure behavior are admitted before execution.
- Typed failures — unsupported hardware, exhausted capacity, device loss, and invalid state stay machine-readable.
- Actionable evidence — hashes, counters, memory plans, profiles, and telemetry show what executed and why.
The versioned Quick Start is the public integration authority. The compact path below previews its verified Darwin ARM64 installation. Download these three adjacent files from Releases:
Before continuing, check the exact native dependency tuple in the Quick Start. The 1.0.4 release does not bundle MoltenVK or publish a versioned Homebrew tap, so current unversioned formulas are not a substitute when their versions have moved past the sealed artifact identity.
rund-sdk-1.0.4-darwin-arm64.tar.gz
rund-sdk-1.0.4-darwin-arm64.sha256
rund-verify
Verify the sealed producer tuple and install the SDK:
chmod +x ./rund-verify
sh ./rund-verify \
./rund-sdk-1.0.4-darwin-arm64.tar.gz \
./rund-sdk-1.0.4-darwin-arm64.sha256 \
"$PWD"Link the single public target:
cmake_minimum_required(VERSION 3.20)
project(example LANGUAGES CXX)
find_package(runD 1.0.4 EXACT CONFIG REQUIRED)
add_executable(example main.cpp)
target_link_libraries(example PRIVATE runD::sdk)cmake -S . -B build \
-DCMAKE_PREFIX_PATH="$PWD/rund-sdk-1.0.4-darwin-arm64"
cmake --build build
./build/exampleContinue with Troubleshooting if setup fails, or the public Compute guide after the first successful Flow.
runD does not claim that arbitrary host code, unconstrained floating-point expressions, wall-clock timing, or native event arrival order become deterministic automatically. Bit-identical authority requires an admitted numeric contract and the documented deterministic graph operations.
Host observations become deterministic after admission into the canonical input stream. Floating-point state must use the strict admitted law; diagnostic or presentation floating point cannot feed authoritative state.
1.0.4 is the current public alpha; 1.0.0 was the first. The SDK surface and
deterministic contracts are tested, but compatibility may change before a
stable release.
| Platform | Status | Backends |
|---|---|---|
| Darwin ARM64 | Supported binary SDK | CPU, Metal, Vulkan through MoltenVK |
| Linux x64 | Validated source candidate | No release-backend matrix claimed |
| Windows x64 | Not supported | — |
The Darwin verifier admits only a host whose compiler, standard library, Apple SDK, architecture, and native backend dependencies match the sealed producer tuple. Large accelerator workloads must also be tested against actual device and driver memory headroom; compilation or preparation alone is not a production-capacity guarantee.
See public Platform Support for the exact integration boundary.
| API | Purpose | Start here |
|---|---|---|
| Compute | Typed Flow, Program, Job, Batch, and Pipeline execution | Compute guide |
| Replay | Canonical input, checkpoints, scenarios, and evidence | Replay guide |
| Session | Reusable lifecycle, tasks, and bounded resources | Runtime guide |
| Fixed | Widened fixed-point expressions and explicit quantization | Numerics guide |
| Network | Move-only sockets and typed asynchronous I/O | Runtime guide |
| Telemetry | Stable counters and bounded actionable findings | Runtime guide |
Use the focused <rund/*.hpp> header for the API you need. Applications link
only runD::sdk; Kernel, Accel, and Node remain private implementation layers.
Source development requires CMake 3.20 or newer, Ninja, a C++20 compiler, and the dependencies for each enabled backend. Vulkan discovery can be disabled for a CPU-only development build.
cmake -S . -B .cache/build/local -G Ninja \
-DRUND_ENABLE_VULKAN=OFF
cmake --build .cache/build/local
ctest --test-dir .cache/build/local --output-on-failureRepository verification uses the checked-in operators:
tools/test/run --list
tools/test/run <case>
tools/check/run
tools/release/runRead CONTRIBUTING.md and the verification contract before changing behavior.
| Layer | Responsibility |
|---|---|
math32/, math64/ |
Fixed-width arithmetic and bit-level laws |
kernel/ |
Scheduling, workspaces, reductions, and execution order |
accel/ |
Backend-neutral accelerator values |
node/ |
Runtime, Compute bridge, networking, replay, and telemetry |
cluster/ |
Optional placement and retry identity |
package/ |
Installed SDK and artifact contract |
The repository-wide source of truth starts at
docs/README.md.
Contributions are welcome. Keep behavior, documentation, and executable evidence in the same change; start with CONTRIBUTING.md. Report security issues through the private process in SECURITY.md, not a public issue.
runD is released under the MIT License. The bundled xxHash header retains its own license notice.