mixle.utils.parallel.model_decomposition module¶
Structural model-decomposition planner (component C2 of the model-parallel design).
Turns any mixle model into a model-parallel placement by walking the opt-in decomposition
contract (mixle.stats.compute.decomposition) instead of a blind reflective __dict__ walk:
size_model_tree()– a structural byte sizing of the model tree (own params per node, recursing into children via the declared axis), so shared subtrees are not double-counted and a node’s own parameter footprint (e.g. an HMM’s denseS*Stransition block) is visible rather than silently zeroed. Replaces the reflectiveestimate_model_nbytesfor sizing decisions.
decompose_model()– choose the cut (data vs model vs none) for the root shardable node and bin-pack the component / factor units across the device budget, carrying the per-cut reduction. This is the general form of, and a real consumer-shaped output for,planner.model_sharding_plan(which only ever understood mixture components and had no consumer).
It works for every family: nodes that do not opt into the contract report Decomposition.atomic() and
are simply replicated (data-parallel, already optimal). See ~/codex/notes/model-parallel-design.md.
- class NodeSize(path, axis, num_units, own_param_bytes, subtree_param_bytes, subtree_work=1.0, children=())[source]
Bases:
objectStructural size of one model-tree node.
- Parameters:
- path: str
- axis: DecompAxis
- num_units: int
- own_param_bytes: int
- subtree_param_bytes: int
- subtree_work: float = 1.0
- children: tuple[NodeSize, ...] = ()
- size_model_tree(model, _path='', _seen=None)[source]
Recursively size the model tree via the decomposition contract (shared subtrees counted once).
- shard_children(node, dc=None)[source]
Return the actual child distributions a node splits along its declared axis (else
()).
- cost_children(model)[source]
ALL child distributions of a node (for COST), not just the shardable ones.
shard_childrenreturns only the axis a node can be split along; for costing we need every nested distribution’s compute counted – e.g. an HMM is atomic (not shardable here) yet itsSemission distributions andlen_distare real work, and a heavy leaf buried under a non-shardable wrapper still costs FLOPs. Discovery is reflective (anySequenceEncodableProbabilityDistributionheld directly or inside a list/tuple/dict), so it works for any model without per-family wiring.
- subtree_work(model, _seen=None)[source]
Total compute weight of a model subtree – own emission cost plus ALL descendants (counted once).
Recurses over
cost_children()(every nested distribution), so a unit’s cost includes heavy subtrees the executor can’t split (a nested HMM, a GP leaf), making the balance honest about where the FLOPs actually are – not just where the model happens to be shardable.
- compute_cost(model, _seen=None)[source]
(flops_per_observation_proxy, bytes)for the whole model – compute load and memory footprint, the two resources the balancer trades off (compute is the load, memory is the constraint).
- class AxisCandidate(path, axis, reduction, num_units, unit_works)[source]
Bases:
objectOne shardable axis somewhere in the model tree, with the compute weight of each of its units.
- Parameters:
- path: str
- axis: DecompAxis
- reduction: ReductionOp
- num_units: int
- property total_work: float
- tree_axes(model)[source]
Enumerate every shardable axis anywhere in the tree (not just the root), each with per-unit work.
- best_parallel_axis(model, max_workers=None)[source]
Pick the axis whose parallelization removes the most serial wall-time (heaviest, parallelizable).
Benefit of cutting an axis with P available workers is
total_work * (1 - 1/min(P, num_units))– favouring the axis that carries the most work AND has enough units to keep the workers busy. This looks at the WHOLE tree, so a heavy mixture nested inside a thin composite is found (the root-only planner missed it). ReturnsNonewhen nothing in the tree is worth splitting.
- class ModelCut(device, start, stop, reduction)[source]
Bases:
objectOne model-parallel cut: a contiguous unit range placed on one device, with its reduction.
- device: DeviceSpec
- start: int
- stop: int
- reduction: ReductionOp
- class ModelDecomposition(axis, reduction, num_units, cuts, rationale, extra=<factory>)[source]
Bases:
objectThe chosen decomposition of a model onto a device budget.
- Parameters:
- axis: DecompAxis
- reduction: ReductionOp
- num_units: int
- cuts: tuple[ModelCut, ...]
- rationale: str
- property is_model_parallel: bool
- decompose_model(model, resources, *, n_data=None, min_components_per_shard=1, prefer_data_parallel=True)[source]
Decide how to place
modelacrossresourcesfor model parallelism.The WHOLE tree is searched for the heaviest worth-splitting axis (
best_parallel_axis()), so a big mixture nested inside a thin composite is found – not just the root. A model that does not opt into the contract, or whose subtree fits replicated and is best served by data parallelism, yields anaxis="none"plan (replicate the model, shard the data – already optimal for large N). Otherwise that axis’s units are bin-packed across devices BALANCED BY COMPUTE COST, carrying the per-cut reduction.