mstar.engine.accelerator_graph_runner#
Functions
|
Reduce each of |
|
A forward's autocast scope; |
|
Return resource keys that prevent capture of the declared step. |
|
Capture |
|
Capture |
|
|
|
Stand-in request info for padding rows, which have no real request. |
|
|
|
The name a padding-row handle was minted from. |
|
MSTAR_REQUIRE_CUDA_GRAPHS=1 turns a dropped bucket into a startup failure, for deployments that would rather not come up than serve the eager path at 10-20x the latency. |
|
The cheapest buckets across every pool on a device, added while the largest eager step still fits in |
Classes
|
The captured slots for one (walk, cg_key_info, bs, num_tokens). |
|
|
|
One captured graph + the buffers its replay reads and writes. |
|
What one bucket costs on the device: its warm-up, and a capture thrown away, its own or its walk's largest. |
|
Which buckets each graph pool on a device captures, chosen before any is. |
|
The padding rows captured replays pad onto. |
The executable memory a walk's graphs take per node, outside any pool, measured on copies of its first throwaway graph: executables instantiated from it through libcuda. |
|
|
What a captured graph holds: in its pool, and outside the allocator. |
|
Captures one inner callable of a submodule's forward as a CUDA graph. |
|
One captured region, for one (bs, total_tokens) bucket. |
|
|
|
Dict-like view over a captured region's output buffers. |
|
A region's shape planned and run eagerly: what its capture reads. |
|
A spec prepared and run eagerly: what its capture reads. |
- class mstar.engine.accelerator_graph_runner.AcceleratorGraphBucket(config, config_idx, slots=<factory>)[source]#
Bases:
objectThe captured slots for one (walk, cg_key_info, bs, num_tokens).
- Parameters:
config (AcceleratorGraphConfig)
config_idx (int)
slots (list[AcceleratorGraphSlot])
- config: AcceleratorGraphConfig#
- slots: list[AcceleratorGraphSlot]#
- class mstar.engine.accelerator_graph_runner.AcceleratorGraphRunner(submodule_name, submodule, resources, step_runner, device, autocast_dtype, joint_comm_group, num_slots, enable_nvtx=False, device_capability=None)[source]#
Bases:
object- Parameters:
submodule_name (str)
submodule (NodeSubmodule)
step_runner (StepRunner)
device (device)
autocast_dtype (dtype | None)
joint_comm_group (JointGroups)
num_slots (int)
enable_nvtx (bool)
device_capability (DeviceCapability | None)
- CAPTURE_BATCH_SIZES = [1, 2, 4, 8, 16, 32, 64]#
- NUM_WARMUP = 2#
- property any_graphs#
- declare_inputs_for(lease)[source]#
Template rows for a declare-only call, cached per bucket.
A bucket’s shape is fixed, so these rows are constant; building them fresh cost ~130us of clones per pre-plan at bs=16, plus that many D2D copies. Only for callers that hand the rows to
declare_stepand nothing else — the list is shared, so a caller that mutates it or passes it topreprocesscorrupts every later step on this bucket.- Parameters:
lease (SlotLease)
- Return type:
- lease_slot(graph_walk, bs, slot, num_tokens=None, cg_key_info=None)[source]#
Pick the bucket and double-buffer slot an upcoming step replays on.
num_tokens=Nonemeans the batch’s inputs aren’t built yet, so the bucket comes from the batched-capture search instead.slot=Noneadvances the bucket’s counter so the next lease lands on the other slot; a caller that already reserved one (pre-plan) passes it back so both submissions target the same slot.
- max_batch_size_for(graph_walk)[source]#
Largest batch this walk was captured for, or None for no cap.
A config that opts out of capping captures only an acceleration subset of batch sizes — larger batches run eager rather than being split — so it does not constrain the batch. With every config for the walk opting out, nothing here caps it.
- pad_inputs(lease, inputs)[source]#
Real inputs plus the rows that bring the batch to capture batch size. Set the length to zero so no new pages are allocated for dummy requests.
- Parameters:
lease (SlotLease)
inputs (list[NodeInputs])
- Return type:
- plan_stream()[source]#
Dedicated stream for pre-planning.
Pre-plan must not submit onto the default stream: whether its memcpys land before or after the GPU thread records the previous batch’s completion event is timing-dependent, and landing after delays that event past pre-plan’s own kernels. Its own stream keeps the two independent; a plan-done event gates the replay that reads the buffers.
- Return type:
Any | None
- prepare_for_capture()[source]#
Claim the static buffers this runner’s captures will read.
Split from the capture so every runner claims first: nodes share resources, and a build driven by a later node would move buffers an earlier node’s graphs already baked in.
- Return type:
- release(lease, real_bs)[source]#
Return the padding rows to their at-rest state after a step.
Padding rows (is_padding_row) run against SINK_PAGE and the recurrent pool’s sink slot, so nothing stays resident: padding must never compete with real requests for storage.
- run_forward(lease, preprocessed, plan_done_event=None, launch_started_event=None)[source]#
Stage this step’s inputs into the slot and replay it.
plan_done_eventis the pre-plan’s event: its plan wrote this slot’s buffers on another stream, so the replay has to wait for those writes before it reads them.launch_started_eventreleases the submitting thread. It is set HERE rather than before the forward: staging copies each preprocessed tensor into its static buffer and holds the GIL while doing so, so releasing earlier hands the main thread a GIL the GPU thread still needs. Only graph.replay() below drops it in C++.
- select_batched_bucket(graph_walk, bs, cg_key_info=None)[source]#
A bucket for a batch whose inputs aren’t built yet.
Only a batched capture can answer this: its token count is a property of the config (
bsrows of a fixed per-request length), so the bucket follows from the batch size alone. A packed capture’s token count is a property of the requests, so it has to wait forprepare_inputsand go throughselect_bucket.This is what lets pre-plan run before inputs exist. Lifting the restriction means making
prepare_inputssafe to run ahead of the forward — seeEngine.pre_plan_for_batch.
- select_bucket(graph_walk, bs, num_tokens, cg_key_info=None)[source]#
Tightest captured bucket that fits this batch, or None for eager.
A walk may have several captures (e.g. one per image resolution, each a fixed shape with its own token count), so every matching bucket is considered rather than the first config declared.
num_tokenscounts the batch’s input tokens; a bucket is keyed by the tokens its plan carries, so the request is scaled by that config’s multiplier.
- size_captures()[source]#
Warm every spec up, and capture each walk’s largest buckets once, thrown away, before any runner captures for real.
Every bucket is warmed rather than the first in capture order: that order leads with batch size, so it can open on a decode bucket while a one-row prefill of thousands of tokens peaks far higher. And every slot: a slot’s first plan builds state of its own (FlashInfer’s graph wrappers and their workspace, 750 MiB for the Talker’s second slot), which has to be spent before free memory is read.
- Return type:
- size_lent_captures(wanted)[source]#
Capture every bucket a lender sized, thrown away, if
wantedhere or on another rank: a plan that leaves graphs out needs what each takes, not what its walk’s largest does.Every rank asks, so the ranks of a group size again together or not at all, and plan on the same kind of cost. With no forward and no barrier: sizing ran the forwards, and a capture runs no collective.
- Parameters:
wanted (bool)
- Return type:
- step_ids(lease, request_ids)[source]#
The padded addressing for one step: real ids first, then the slot’s padding ids. Plans, commits, and advances address real request state by its own id; only the padding rows run against the slot’s own state.
- warmup_and_capture(planned)[source]#
Capture the
plannedbuckets, of every config and batch size. With no plan, every bucket, each warmed up first as sizing would have.- Parameters:
planned (Collection[BucketKey] | None)
- class mstar.engine.accelerator_graph_runner.AcceleratorGraphSlot(graph, static_inputs, static_input_keys, static_outputs, dummy_rids, dummy_metadata, config_idx)[source]#
Bases:
objectOne captured graph + the buffers its replay reads and writes.
Double-buffer: a bucket holds one of these per slot. Replay alternates between slots so plan(N+1) on the inactive slot’s resources can run concurrently with replay(N) on the active slot — so a node with nothing to pre-plan keeps a single slot.
- Parameters:
graph (CapturedGraph)
static_outputs (BatchedModelOutput)
dummy_metadata (dict[int, CurrentForwardPassInfo])
config_idx (int)
- dummy_metadata: dict[int, CurrentForwardPassInfo]#
- graph: CapturedGraph#
- static_outputs: BatchedModelOutput#
- class mstar.engine.accelerator_graph_runner.CaptureCost(peak, graph, kept, slots, outside=0, executable=None)[source]#
Bases:
NamedTupleWhat one bucket costs on the device: its warm-up, and a capture thrown away, its own or its walk’s largest.
- Parameters:
- executable: ExecutableMemory | None#
Alias for field number 5
- class mstar.engine.accelerator_graph_runner.CapturePlan(buckets, predicted, outside, floor)[source]#
Bases:
NamedTupleWhich buckets each graph pool on a device captures, chosen before any is.
- Parameters:
- class mstar.engine.accelerator_graph_runner.DummyRowPool(prefix, step_runner, resources)[source]#
Bases:
objectThe padding rows captured replays pad onto.
Ingested once and kept for the runner’s lifetime: a re-ingest would hand the resources a fresh stream and orphan the pages the last capture left resident, and keeping them resident is what lets a step’s padding tail allocate nothing.
- Parameters:
prefix (str)
step_runner (StepRunner)
- handles(key, bs)[source]#
bspadding-row handles forkey. Negative and stable per (prefix, key, index), so they never collide with a real request’s handle nor with another pool’s rows.
- class mstar.engine.accelerator_graph_runner.ExecutableMemory[source]#
Bases:
objectThe executable memory a walk’s graphs take per node, outside any pool, measured on copies of its first throwaway graph: executables instantiated from it through libcuda. The cost per node changes with a graph’s size, even for the same kernels: a chain of adds takes 3.1 KiB a node at 300 nodes and 2.4 at 2222. A walk’s graphs are about the same size, so the first graph’s cost per node holds for all of them.
The copies are held until the runner captures. The driver keeps a destroyed graph’s executable memory to reuse, and maps new memory beside it, so after any destroy, free memory drops by less than a copy takes.
- measure(graph, nodes)[source]#
Set
per_nodeandheldfrom copies ofgraph, held until release.The count starts after the first copy that takes free memory, since copies until then fill pieces the driver mapped earlier. It adds one 2 MiB piece, since the first counted copy may start partway into a piece. Free memory is the device’s, so another process allocating or freeing moves it: a count is kept only if free memory never rose and no copy took more than two pieces above the mean. If no count is kept,
per_nodeis the largest rate a count gave. None if no copy could be counted.- Parameters:
nodes (int)
- Return type:
None
- class mstar.engine.accelerator_graph_runner.GraphMemory(pool, outside)[source]#
Bases:
NamedTupleWhat a captured graph holds: in its pool, and outside the allocator.
- class mstar.engine.accelerator_graph_runner.PiecewiseAcceleratorGraphRunner(label, config, resources, step_runner, device, autocast_dtype, num_slots, joint_comm_group=None, node_name=None, memory_pool=None, device_capability=None)[source]#
Bases:
objectCaptures one inner callable of a submodule’s forward as a CUDA graph.
Where
CudaGraphRunnerreplays a wholeforward_batchedunder engine control, this captures a SUB-REGION — a transformer block loop, say — while the surrounding preamble stays eager and the submodule invokesrunitself. The config supplies the callable, its static buffers, and the region’s own step declaration; the runner drives that step through the same admit → plan → commit cycle the engine uses, so the region’s attention plan lands in the resources’ per-(bucket, slot, label) buffers and the captured graph reads them at fixed addresses.- Parameters:
label (str)
config (PiecewiseAcceleratorGraphConfig)
step_runner (StepRunner)
device (device)
autocast_dtype (dtype | None)
num_slots (int)
joint_comm_group (JointGroups | None)
node_name (str | None)
memory_pool (AcceleratorGraphPool | None)
device_capability (DeviceCapability | None)
- CAPTURE_BATCH_SIZES = [1, 2, 4, 8, 16, 32, 64]#
- NUM_WARMUP = 2#
- can_lease(inputs)[source]#
The config’s
can_leasefor this batch; True when it sets none.- Parameters:
inputs (list[NodeInputs])
- Return type:
- lease_slot(batch_size, total_tokens=None)[source]#
The bucket this batch would replay on, asked before the forward.
Same question
runsettles per call, answered early so the outerdeclare_stepcan plan the region’s resources against it.
- prepare_for_capture()[source]#
Claim this region’s static buffers; see
CudaGraphRunner.- Return type:
- run(static_inputs, request_ids=None, seq_lens=None, real_bs=None)[source]#
Replay the captured region for these real inputs.
Copies each real input into the runner-owned buffer of the same name, declares and plans the region’s step over the padded batch, replays, then commits and returns the padded buffers behind a real-length view.
Only the static buffers carry data into a replay: the region’s Python ran once, at capture, so whatever it read off
PiecewiseCallInputsis baked into the graph.
- size_captures()[source]#
Warm every shape up and capture the largest once, thrown away; see AcceleratorGraphRunner.size_captures.
- Return type:
- size_lent_captures(wanted)[source]#
Capture every shape a lender sized, thrown away, if
wantedhere or on another rank; see AcceleratorGraphRunner.size_lent_captures.- Parameters:
wanted (bool)
- Return type:
- warmup_and_capture(planned)[source]#
- Parameters:
planned (Collection[BucketKey] | None)
- Return type:
None
- class mstar.engine.accelerator_graph_runner.PiecewiseGraphData(graph, static_inputs, static_outputs, dummy_rids, shape, bucket)[source]#
Bases:
objectOne captured region, for one (bs, total_tokens) bucket.
- Parameters:
- graph: CapturedGraph#
- shape: PiecewiseCaptureShape#
- class mstar.engine.accelerator_graph_runner.PiecewiseGraphKey(bs, seq_len, slot)[source]#
Bases:
NamedTuple
- class mstar.engine.accelerator_graph_runner.PiecewiseOutput(outputs, real_len)[source]#
Bases:
objectDict-like view over a captured region’s output buffers.
The runner replays into persistent buffers sized for the padded bucket; only the leading
real_lenrows are meaningful. Indexing andgetreturn an owned CLONE of that leading slice — safe to keep past the next replay.get_viewreturns the same slice WITHOUT copying.- get_view(key, default=None)[source]#
The leading
real_lenslice WITHOUT copying.The result aliases the runner-owned static output buffer and is OVERWRITTEN by the next
runof this runner. Nothing else writes there: the buffers live outside the graph memory pool, so replays of other regions sharing the node’s pool leave them alone. Read it within the same step; usegetwhen you need something that outlives it.- Parameters:
key (str)
- class mstar.engine.accelerator_graph_runner.WarmedRegion(run, static_inputs, dummy_rids, warm_outputs, peak)[source]#
Bases:
NamedTupleA region’s shape planned and run eagerly: what its capture reads.
- Parameters:
- class mstar.engine.accelerator_graph_runner.WarmedSpec(run, static_inputs, static_input_keys, dummy_rids, dummy_metadata, peak, returned)[source]#
Bases:
NamedTupleA spec prepared and run eagerly: what its capture reads.
- Parameters:
- dummy_metadata: dict[int, CurrentForwardPassInfo]#
Alias for field number 4
- mstar.engine.accelerator_graph_runner.agree_across_ranks(comm_group, values, device, reduce)[source]#
Reduce each of
valuesover every rank of the joint group withreduce. torch.amin ANDs flags and torch.amax takes the largest.Capture failure is per-rank: a rank that keeps a graph another rank dropped replays it while the other runs eager, which hangs as soon as the captured region holds a collective. Callers pass an order derived from the configs, which are identical on every rank.
- mstar.engine.accelerator_graph_runner.autocast_scope(dtype, device_type)[source]#
A forward’s autocast scope;
None(disable_autocast) runs the submodule in its own dtype and shuts out any ambient autocast.
- mstar.engine.accelerator_graph_runner.blocked_capture_resources(step, resources)[source]#
Return resource keys that prevent capture of the declared step.
- mstar.engine.accelerator_graph_runner.capture_into_graph(run, pool, device, autocast_dtype, keep_graph=False)[source]#
Capture
runinto a new CUDA graph allocated frompool.Returns the graph and what
runreturned. A capture that fails part way leaves two things behind that would sink every later capture on the same pool: the caching allocator keeps routing this thread’s allocations into the pool (the next capture_begin fails with “already recording to mempool_id”), and torch.cuda.graph never switches the thread back off its capture stream. Undo both before re-raising, so the buckets after a failed one still capture and the thread keeps running on the stream it came in on.A torch.compile’d forward that would recompile inside the capture is failed up front with the guard that broke: dynamo saves the CUDA RNG state before it compiles, which CUDA refuses during capture, so such a capture cannot succeed and would otherwise die with an unrelated error.
- Parameters:
pool (AcceleratorGraphPool)
keep_graph (bool)
- mstar.engine.accelerator_graph_runner.capture_with_static_outputs(run, warm_outputs, pool, device, autocast_dtype, keep_graph=False)[source]#
Capture
runso its outputs land in buffers allocated OUTSIDEpool.warm_outputsis whatrunreturned on a warm-up call ({name: Tensor}); it fixes the shapes and dtypes. The captured graph ends with a device-to-device copy of each output into a buffer allocated here, before the capture, from the ordinary allocator.Why not hand out the graph’s own output tensors: blocks a graph frees while it is being captured go back to the pool and are handed to the graphs captured after it, so an output that lived in the pool could sit exactly where an earlier-captured graph keeps its scratch and be overwritten by that graph’s next replay. That is harmless while a pool belongs to one region whose graphs replay one at a time and whose outputs are consumed before the next replay, but not once a node’s regions share one pool and replay in data-dependent order. Buffers outside the pool stay valid until this graph’s own next replay, whatever else the node runs in between.
- Parameters:
pool (AcceleratorGraphPool)
keep_graph (bool)
- mstar.engine.accelerator_graph_runner.compile_kwargs(config_mode)[source]#
torch.compile’s arguments for a capture config whosecompile_modeisconfig_mode, which the config checked when it was built.
- mstar.engine.accelerator_graph_runner.dummy_metadata(rids, graph_walk)[source]#
Stand-in request info for padding rows, which have no real request.
- mstar.engine.accelerator_graph_runner.dummy_rid_name(handle)[source]#
The name a padding-row handle was minted from. Debug/logging only.
- mstar.engine.accelerator_graph_runner.fail_if_graphs_required(missing)[source]#
MSTAR_REQUIRE_CUDA_GRAPHS=1 turns a dropped bucket into a startup failure, for deployments that would rather not come up than serve the eager path at 10-20x the latency.
- mstar.engine.accelerator_graph_runner.plan_captures(pools, free, reusable=None)[source]#
The cheapest buckets across every pool on a device, added while the largest eager step still fits in
freebeside them.A pool’s graphs, captured largest first, reuse its memory: it holds what every graph keeps, plus the most any one graph needs beyond that. So a bucket adds its slots’ kept bytes, and whatever it needs past the pool’s most so far. That is a model; the boot log compares it with what capture took. A bucket also adds its slots’ executable memory, less the memory its graphs reuse from the walk’s copies. A walk’s copies pay for its planned graphs’ executable memory once, across all its buckets.
reusablebounds the reuse summed over all buckets, None for no bound.- Parameters:
- Return type: