Parallel¶
The share-nothing parallelism surface: parallel replications of a whole model,
the execution-backend abstraction, single-run PDES sharding, and the compute
offload pool. Every symbol here is re-exported from the top-level llmsim
package.
Parallel replications¶
Experiment ¶
Experiment(factory, configs, *, master_seed, spool=False)
N replications x M configs of one model factory, reproducibly.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
factory
|
Factory
|
A module-level callable |
required |
configs
|
Sequence[Any]
|
The experiment's configurations; each must survive the chosen backend's transport (preflighted per run when the backend pickles). |
required |
master_seed
|
int
|
The explicit study seed (required, keyword-only; no ambient default). Every replication's stream derives from it via the seed tree, so the same seed reproduces the same study. |
required |
spool
|
bool
|
When |
False
|
Use as a context manager to cancel any in-flight work on exit::
with Experiment(my_model, configs, master_seed=42) as exp:
for result in exp.iter_results(replications=100):
if good_enough(result):
break # __exit__ cancels the rest
Validate the factory and root the seed tree at master_seed.
run ¶
run(replications, *, backend='auto', max_workers=None)
Run the full study and return results keyed by identity.
The returned dict maps (config_index, replication_index) to
:class:ReplicationResult and iterates in sorted key order --
completion order never leaks into the result set.
iter_results ¶
iter_results(
replications, *, backend="auto", max_workers=None
)
Yield results as they complete (each exactly once, identity-keyed).
Aggregation stays order-insensitive because every result carries its
(config_index, replication_index); only the arrival order is
completion-dependent. After :meth:cancel, replications that already
finished are still yielded, cancelled ones are not, and no new work is
dispatched.
cancel ¶
cancel()
Cancel the in-flight run (one API, two granularities).
Stops dispatching queued work and Future.cancel()-s pending
submissions on every backend. On the thread backend the shared token
additionally fires, so a cancellation-aware factory stops
mid-replication; a factory that owns its own sim.run() -- and any
interpreter/process worker -- finishes its current replication first
(replication granularity). Already-collected results stay valid.
ReplicationResult ¶
ReplicationResult(
config_index,
replication_index,
seed,
payload,
*,
spooled=False,
)
One replication's identity, seed, and returned value.
Results are keyed and compared by (config_index, replication_index)
plus the derived seed and the factory's value; completion order is nowhere
in the type, by construction.
Wrap one replication's payload (raw, or zstd-spooled bytes).
value
property
¶
value
The factory's returned value.
When the experiment spools (spool=True), the value is stored as
zstd-compressed pickle bytes to bound peak memory and decompressed on
each access; hold onto the returned object if you read it repeatedly.
run_replications ¶
run_replications(
factory,
configs,
*,
master_seed,
replications,
backend="auto",
max_workers=None,
)
Run one study functionally: construct, run, and return the result set.
Equivalent to Experiment(factory, configs,
master_seed=master_seed).run(replications, backend=backend,
max_workers=max_workers).
ReplicationError ¶
Bases: RuntimeError
One replication failed (fail-fast contract).
Names the offending (config_index, replication_index) and chains the
worker's original exception as __cause__. Raising instead of returning
a partial result set keeps a study's output all-or-nothing.
Execution backends¶
ExecutionBackend ¶
ExecutionBackend(kind)
One executor-shaped interface over threads, interpreters, and processes.
Instances are thin and stateless: they know their concrete kind, how to
create the matching concurrent.futures executor, and the two traits
the replication runner branches on (whether payloads cross a pickle
boundary, and whether workers can share a live cancellation token).
Wrap the concrete backend kind ("auto" is not accepted here).
requires_transport
property
¶
requires_transport
Whether payloads cross a pickle boundary on this backend.
supports_shared_cancellation
property
¶
supports_shared_cancellation
Whether workers can observe a live parent token mid-replication.
resolve
classmethod
¶
resolve(backend='auto')
Resolve a backend name, honoring an explicit choice verbatim.
"auto" picks "threads" on the free-threaded build (GIL
disabled) and "processes" on a GIL build.
cancel_token ¶
cancel_token()
Return the cancellation token this backend's workers should receive.
A live shared token on the thread backend (mid-replication cancel for
token-aware factories); the inert base token elsewhere, where isolated
workers cannot observe the parent. Callers fire either uniformly via
token._cancel().
executor ¶
executor(max_workers=None)
Create this backend's executor with max_workers workers.
max_workers=None defaults to os.process_cpu_count() on every
backend, so worker count is uniform and predictable rather than
executor-specific.
CancelToken ¶
The cancellation handle a factory may consult between step() calls.
This base token is inert -- :attr:cancelled is always False -- and
picklable; it is what factories receive on the interpreter and process
backends, where a parent's cancellation cannot be observed mid-run. On the
thread backend the factory receives a live shared token instead, so
checking :attr:cancelled between steps gives mid-replication
cancellation. Factories annotate the parameter as CancelToken and need
not know which they got.
FactoryValidationError ¶
Bases: TypeError
The factory cannot be transported to workers by reference.
Raised at :class:~llmsim.parallel.replicate.Experiment construction when
the factory is not an importable module-level callable (a lambda, a local
function or closure, or anything not reachable as
module.qualified_name). The fix is always the same: define the factory
with def at the top level of an importable module.
TransportError ¶
Bases: RuntimeError
A config or result cannot cross the backend's transport boundary.
Names the offending config (and replication, when known) so a failing study points at the exact object to fix -- never a silent partial run.
PDES sharding¶
ShardedSim ¶
ShardedSim(shards, *, master_seed, debug=False)
One large model partitioned into channel-connected shards.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
shards
|
int
|
Number of shards; each gets its own |
required |
master_seed
|
int
|
The explicit study seed (required, keyword-only). Shard
i's |
required |
debug
|
bool
|
Construct shard |
False
|
Declare a topology of shards empty shards.
run ¶
run(until, *, mode='threads')
Run the topology until every event below until has executed.
mode="threads" runs one thread per shard under the safe-window
synchronizer; mode="sequential" runs the identical window
algorithm on the calling thread — the reference oracle, bitwise
trace-equal to the threaded mode for the same master seed. Events at
exactly until (and later) are not executed, matching
Sim.run(until=...).
ShardPorts ¶
ShardPorts(shard_index, sim, registry)
A shard builder's window onto the topology's channels.
Handed to each builder as its second argument; every endpoint the shard uses must be declared through it, which is what makes construction-time wiring validation possible.
Bind the ports view to one shard's sim and the shared registry.
ShardError ¶
Bases: RuntimeError
One shard failed during a run (fail-fast contract).
Names the failing shard index and chains the original exception as
__cause__; all other shard threads are cancelled and joined before
this is raised, mirroring the Phase 2 replication failure contract.
TopologyError ¶
Bases: ValueError
The sharded topology is mis-wired.
Raised at construction/validation time — duplicate or missing shard builders, channel names without exactly one sender and one receiver, endpoints declared on the wrong shard, or a channel from a shard to itself. Never raised mid-run: a topology that validates runs.
LookaheadError ¶
Bases: ValueError
A channel's lookahead contract was violated.
Raised when a channel is declared with a non-positive lookahead (the
safe-window algorithm cannot make progress without positive lookahead on
every channel) or when send() is called with delay < lookahead
(the lookahead is a promise to the synchronizer; breaking it would let a
message land inside an already-executed window).
Channels and run modes¶
Channel ¶
Channel(*, name, lookahead, sim)
The sender-side endpoint of one directed inter-shard link.
Owned by exactly one shard; send() may only be called from that
shard's processes while it executes a window. The lookahead is the
channel's normative promise: every message carries
timestamp >= sender_now + lookahead.
Create the endpoint; the topology wires it via :meth:bind.
A ShardedSim assigns channel ids in sorted-name order after every
builder has declared its endpoints, so ids are deterministic
regardless of build interleaving.
bind ¶
bind(*, channel_id, mailbox)
Wire the endpoint to its id and destination (topology-internal).
send ¶
send(payload, *, delay)
Send payload to arrive delay after the sender's current time.
Raises:
| Type | Description |
|---|---|
LookaheadError
|
if |
Inbox ¶
Inbox(*, sim, name)
The receiving shard's Store-like endpoint for one channel.
yield inbox.get() in a shard process resolves to the next delivered
payload, in the globally deterministic delivery order. Deliveries are
performed by the synchronizer at window edges via :meth:deliver: each
message becomes a Timeout at its stamped time (an already-successful
event carrying the payload) whose processing deposits the payload into
this inbox's store, waking any waiting get().
Create the endpoint local to the receiving shard's sim.
deliver ¶
deliver(message)
Schedule message's payload to land at its timestamp.
Called by the synchronizer while the shard is quiescent, in the normative sorted order — the resulting event-id assignment makes same-timestamp delivery ordering deterministic.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
if the message's timestamp is in the shard's past — a causality violation that the safe-window algorithm can never legitimately produce. |
Message
dataclass
¶
Message(timestamp, channel_id, sequence, payload)
One cross-shard message with its deterministic ordering stamp.
sort_key
staticmethod
¶
sort_key(message)
Return the normative delivery order key: (timestamp, channel, seq).
Window analysis¶
analyze() estimates a partition's parallel-window economics from a sequential
trace before you shard — see PDES sharding.
analyze ¶
analyze(traces, *, lookahead)
Estimate achievable PDES speedup for a partition from its traces.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
traces
|
Mapping[int, Sequence[TraceRecord]]
|
Per-shard trace records (shard index -> records), e.g. from running the topology through the sequential reference runner with a tracer attached per shard. |
required |
lookahead
|
float
|
The window width — use the minimum lookahead the topology's channels would declare. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
if traces is empty or lookahead is not positive. |
PdesAnalysis
dataclass
¶
PdesAnalysis(
per_shard_events,
balance_speedup,
window_count,
predicted_speedup,
)
The window-model estimate for one proposed partition.
Compute offload¶
OffloadPool ¶
OffloadPool(sim, *, backend='auto', max_workers=None)
The worker pool behind :meth:~llmsim.core.sim.Sim.offload.
Construct one per Sim before calling run() (attaching a pool
mid-run is unsupported)::
sim = Sim(seed=7)
pool = OffloadPool(sim, backend="processes")
...
sim.run()
pool.close()
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sim
|
Sim
|
The simulation to attach to. A |
required |
backend
|
OffloadBackendName
|
|
'auto'
|
max_workers
|
int | None
|
Pool size; |
None
|
The executor starts lazily on the first pooled submission. Use as a
context manager, or call :meth:close, to release workers: outstanding
offloads are abandoned, pending futures cancelled, and running payloads
finish with their results discarded.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
if sim already has a pool, or if the requested
backend cannot host nested pools in this worker context
( |
Resolve the backend, apply the nested-pool rule, and attach.
submit ¶
submit(fn, args, kwargs, *, delay, strict)
Dispatch one payload (the OffloadHandler seam).
Called by :meth:~llmsim.core.sim.Sim.offload; see that method for
the user-facing contract.
drain ¶
drain()
Block for outstanding non-strict work once the schedule empties.
The run-end rule: a non-strict result is never silently dropped --
run() waits wall-clock for at least one outstanding payload,
delivers everything then complete, and keeps stepping.
close ¶
close()
Abandon outstanding offloads and shut the executor down.
Pending futures are cancelled; running payloads finish (they cannot be interrupted) and their results are discarded. Idempotent.
OffloadEvent ¶
OffloadEvent(sim, pool, future, qualname, *, strict)
Bases: Event[T]
The completion event of one offloaded payload.
In strict mode the event is born scheduled at its completion slot with a placeholder value; its first callback resolves the real outcome (blocking wall-clock on the worker future if needed) before any waiter runs, so the heap structure -- and therefore the trace -- is identical to the inline reference. In non-strict mode it is born untriggered and delivered by the pool once the owning thread observes the completed future.
:meth:cancel abandons the computation: the pending future is cancelled
if not started, and a finished result is discarded, never delivered.
Interrupting the last process waiting on this event cancels it
automatically (the requirements' cancellation contract).
Create the event for one submitted payload (see OffloadPool).
cancel ¶
cancel()
Abandon the offload: its result or exception is never delivered.
Idempotent; a no-op once the event has processed. Cancels the pending
future when the payload has not started; a running or finished
payload's outcome is discarded. The event still processes as a
discarded outcome (None) -- at its slot in strict mode, at
max(now, now + delay) otherwise -- so a process waiting on a
cancelled offload resumes with None rather than stranding.
NonStrictOffloadWarning ¶
Bases: RuntimeWarning
A strict=False offload was submitted while debug mode is on.
Non-strict delivery times depend on wall-clock completion, so event ordering is not reproducible across runs -- exactly the property debug mode exists to guard. Emitted per call, naming the payload and call site.