Tracing and determinism¶
Determinism is a correctness requirement in llmsim, not a convenience:
the same (master seed, config, replication) produces identical results on any
backend, worker count, or build. A parallel result that differs from the
sequential reference is the worst bug class the project recognizes. The trace
is how that guarantee is made observable and testable.
The determinism contract¶
- No ordering ever depends on completion order or wall-clock time.
- Randomness derives from an explicit seed through a
SeedTree, so every process, replication, and shard draws from a stream fixed by its path, never by scheduling. - Every parallel capability ships a same-seed-same-result test against the sequential oracle in the same change.
See Randomness for the seed-derivation API.
Recording a trace¶
trace(sim) attaches a
Tracer that records a canonical
TraceRecord for every processed event:
from llmsim import trace
sim = llmsim.Sim(seed=0)
tracer = trace.trace(sim)
# ...build and run the model...
sim.run(until=100.0)
records = tracer.records # the deterministic event sequence
Because the record payload is canonical (order-independent, value-based),
two runs that are supposed to be equivalent produce byte-identical traces —
which is exactly the equality the PDES, replication, and offload test suites
assert. Call disable_trace(sim) to detach.
Why this matters for parallelism¶
Every parallel backend in llmsim is validated by comparing its trace to the single-thread reference:
- Replications — each replication is its own
Sim; the study's result set is keyed by(config, replication), never by which worker finished first. - PDES sharding — a sharded run's merged trace is asserted bitwise-equal to the sequential run of the same model, at every shard count, including a jittered concurrency soak.
- Compute offload — in strict mode a payload's result is delivered at a
simulated completion slot (
now + delay), so the trace is identical whether the payload took 1 ms or 1 s on the wall clock.
This is why llmsim rejects optimistic (Time Warp) synchronization outright and
why executor work is always submitted as (importable callable, seed, config)
rather than live objects: both choices exist to keep the trace reproducible.