---
# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
sidebar-title: Random Number Generation & Reproducibility
---
# Random Number Generation & Reproducibility
**Quick Links:**
[Overview](#overview) • [What Is Reproducible](#what-is-reproducible-what-is-not) • [User Guide](#user-guide) • [Developer Guide](#developer-guide) • [Reference](#reference)
---
## Overview
**TL;DR:** Use `--random-seed 42` to get **identical dataset content** across runs. Performance metrics and worker assignment vary due to distributed system architecture.
AIPerf provides **deterministic reproducibility** for all seed-controlled randomness using hash-based RNG derivation. This enables reproducible dataset generation while maintaining realistic load testing performance.
> [!WARNING]
> **Default behavior:** Without `--random-seed`, AIPerf produces **non-deterministic** results. Set `--random-seed ` for reproducibility.
> [!WARNING]
> **Distributed System Constraints:** Even with `--random-seed`, **performance metrics and worker assignment are NOT reproducible** due to system non-determinism (network timing, async I/O, ZMQ load balancing).
**Reproducible (with `--random-seed`):**
- ✅ Dataset content (prompts, images, audio)
- ✅ Dataset sampling order (random/shuffle strategies)
- ✅ Request timing intervals (Poisson values)
- ✅ Model selection (random strategy)
- ✅ Session IDs (`session_000000`, `session_000001`, ...)
**NOT Reproducible (system-dependent):**
- ❌ Worker assignment / request execution order
- ❌ Performance metrics (TTFT, ITL, throughput)
- ❌ Server responses / absolute timestamps
**Testing:** Reproducibility is enforced by integration canary tests and CI/CD validation on every commit. See [Testing & Validation](#testing--validation).
## What Is Reproducible, What Is Not
**Key Principle:** Seeds control **WHAT you ask**, not **WHEN it completes** or **WHAT the server answers**.
### ✅ Reproducible with --random-seed
**Dataset:** Prompt text/tokens, image dimensions/formats, audio duration/formats, session IDs
**Sampling:** Random selection, shuffle order, conversation selection
**Timing Decisions:** Poisson interval values, cancellation decisions
### ❌ NOT Reproducible
**Worker/Execution:** Which worker handles which request, request start/completion order, async I/O timing
**Performance:** TTFT, ITL, latency, throughput
**System:** Timestamps, process IDs, request IDs (ZMQ routing)
**Server:** LLM output text, output token counts, errors/failures
### Why This Architecture?
AIPerf achieves its high throughput through parallel workers, ZMQ load balancing, and async I/O. Full determinism would require single-worker synchronous execution, destroying performance.
### How It Works
**Phase 1 (Startup - PROFILE_CONFIGURE):**
- DatasetManager pre-generates complete dataset using derived RNGs and stores in memory
- TimingManager creates credit issuing strategy with RNG-based interval generator
- Workers set global seed (defensive measure) but don't derive/use RNGs
**Phase 2 (Runtime - PROFILE_START):**
- TimingManager generates intervals on-the-fly using RNG, sleeps, then drops credits
- Workers receive credits via ZMQ load balancing
- Workers request conversations from DatasetManager's pre-generated pool
- DatasetManager returns conversations (using sampler RNG or specific ID)
- Workers send API requests with pre-generated content
- **Result:** Same dataset and interval values, but actual timing/worker assignment vary per run
**Analogy:** Like a deterministic deck of cards (same 52 cards, same shuffle) dealt to players who play at different speeds. The deck is reproducible; card distribution to players varies based on who finishes hands first.
### Testing & Validation
Reproducibility is enforced by automated tests on every commit:
- **[test_random_generator_canary.py](https://github.com/ai-dynamo/aiperf/blob/main/tests/integration/test_random_generator_canary.py)**: Compares payloads against reference snapshots to detect regressions
- **[test_deterministic_behavior.py](https://github.com/ai-dynamo/aiperf/blob/main/tests/integration/test_deterministic_behavior.py)**: Verifies byte-for-byte identical outputs with same seed, different outputs with different seeds, tested with 5+ parallel workers
---
## User Guide
### Basic Usage
```bash
# Reproducible dataset
aiperf profile --random-seed 42 [options...]
# Non-reproducible (default)
aiperf profile [options...]
```
Same seed + same config = identical dataset content. Performance metrics always vary.
### Use Cases
**Debugging:** Reproduce exact prompts across runs to isolate prompt-related vs. network/timing issues
```bash
aiperf profile --random-seed 42 [...] --profile-export-file run1.json
aiperf profile --random-seed 42 [...] --profile-export-file run2.json
# Prompts identical; metrics may vary
```
**Performance Testing:** Compare metrics with same dataset
```bash
aiperf profile --random-seed 42 [...] --profile-export-file baseline.json
# After optimization...
aiperf profile --random-seed 42 [...] --profile-export-file optimized.json
# Use statistical analysis (median, p95, p99)
```
**Stress Testing:** Vary patterns by omitting seed
```bash
for i in {1..10}; do
aiperf profile [...] --profile-export-file run_$i.json
done
```
## Developer Guide
### System Architecture
**Where RNGs Are Used:**
- **DatasetManager:** Pre-generates all dataset content at startup using derived RNGs
- **TimingManager:** Generates Poisson timing intervals and cancellation decisions
- **Workers:** Set global seed (defensive) but do NOT derive RNGs—they only execute API requests with pre-generated content
**Process Flow:**
1. `bootstrap.py` initializes RNG with `rng.init(seed)` in each process
- Sets Python's `random.seed()` and NumPy's `np.random.seed()` globally (defensive measure)
- Protects against third-party code inadvertently using global random state
2. DatasetManager creates generators (PromptGenerator, ImageGenerator, etc.) that derive RNGs in `__init__`
3. TimingManager creates interval generator that derives RNG in `__init__`
4. Workers initialize global seed but don't derive any RNGs (they only execute API requests)
5. All dataset content is generated before any requests are sent
6. Workers pull from pre-generated pool at runtime
### How to Use RNGs in Your Code
> [!WARNING]
> Workers do NOT use RNGs. Only use RNGs in **DatasetManager** (content generation) or **TimingManager** (request timing) components.
```python
from aiperf.common import random_generator as rng
class MyGenerator:
def __init__(self, config):
# Derive once in __init__ with unique identifier
self._rng = rng.derive("dataset.mycomponent.feature")
def generate(self):
# Use stored RNG instance
return self._rng.choice([1, 2, 3, 4, 5])
```
**Rules:**
1. Derive in `__init__`, not in methods (or you'll get the same first value every call)
2. Store as instance variable
3. Use unique dotted identifier: `..`
4. Never use Python's `random` module (technically seeded, but fragile—any code using it affects your sequence)
### Hash-Based Seed Derivation
Uses SHA-256 to derive independent seeds: `SHA-256(root_seed:identifier)` → child seed
**Benefits:**
- Deterministic: Same identifier always gets same seed
- Independent: Changing one RNG doesn't affect others
- Fast: ~1-2 microseconds per derivation (happens once at init)
### Common Mistakes
**❌ Deriving in methods** → Returns same first value every call.
**✅ Derive in `__init__`.**
**❌ Using Python's `random`** → Fragile (global state affected by any code).
**✅ Use `rng.derive()`.**
**❌ Adding operations to existing RNG** → Shifts all subsequent values.
**✅ Derive new RNG for new feature.**
## FAQ
**Q: Performance metrics still vary with same seed. Why?**
A: Expected. Seeds control dataset content, not network timing or worker scheduling. See [What Is Reproducible](#what-is-reproducible-what-is-not).
**Q: Same seed across different configs?**
A: Yes. Same seed + different config = different but reproducible results.
**Q: Multiple workers—how does this work?**
A: Workers set global seed (defensive) but don't derive RNGs. DatasetManager pre-generates content, workers pull from this fixed pool. Validated with 5+ workers.
**Q: Are RNGs thread-safe?**
A: No, but not an issue—each process uses RNGs in its own space. If adding multi-threaded RNG usage, derive per-thread.
**Q: Session IDs reproducible?**
A: Yes. With seed: sequential (`session_000000`, `session_000001`). Without: UUIDs.
**Q: Performance impact?**
A: None measurable. Network I/O dominates by 1000×.
## Reference
### All Component-Specific RNG Identifiers
**Dataset**
```python
# Prompts (3)
"dataset.prompt.length" # Token count distribution
"dataset.prompt.corpus" # Content position selection
"dataset.prompt.prefix" # Prefix selection
# Images (4)
"dataset.image.dimensions" # Width + height (coupled for aspect ratio)
"dataset.image.format" # PNG/JPEG/etc. selection
"dataset.image.source" # Source image selection (assets and directory modes only)
"dataset.image.noise" # Random-noise pixel generation (noise mode, default)
# Audio (3)
"dataset.audio.duration" # Length distribution
"dataset.audio.format" # Sample rate + bit depth
"dataset.audio.data" # Audio sample generation
# Samplers (2)
"dataset.sampler.random" # Random sampling strategy
"dataset.sampler.shuffle" # Shuffle sampling strategy
# Loaders (2)
"dataset.loader.random_pool" # Random pool loader
"dataset.loader.sharegpt" # ShareGPT loader
```
**Timing**
```python
"timing.request.cancellation" # Cancellation decisions (probabilistic)
"timing.request.poisson_interval" # Exponential inter-arrival times (Poisson process)
```
**Composer**
```python
"composer.turn.model_selection" # Model selection per turn
"composer.turn.max_tokens" # max_tokens sampling
"composer.conversation.turn_count" # Number of turns per conversation
"composer.conversation.turn_delay" # Delay between turns
```
**Models**
```python
"models.sequence.distribution" # ISL/OSL distribution sampling
```
### Module API
```python
from aiperf.common import random_generator as rng
# Initialize (called automatically in bootstrap.py)
rng.init(seed: int | None)
# seed: Any integer for deterministic, None for random
# Also sets global random.seed() and np.random.seed() defensively
# Derive component RNGs (call in __init__)
my_rng = rng.derive(identifier: str) -> RandomGenerator
# Returns: Independent RNG with SHA-256 derived seed
# Reset (for testing only)
rng.reset()
```
See [random_generator.py](https://github.com/ai-dynamo/aiperf/blob/main/src/aiperf/common/random_generator.py) for the RandomGenerator class and full API details.