mixle.utils.parallel.model_parallel module¶
Model-parallel estimation: distribute a model’s shardable axis across workers (component C3).
The inversion of the data-parallel backends: there the model is replicated and the data sharded. Here the model’s shardable axis is distributed. Two entry points share one recursive fold:
optimize(..., backend="model_parallel")– theModelParallelEncodedDatahandle: the data is replicated in-process and the model axis is threaded. Simplest, single-machine.optimize(ModelParallelEstimator(est), backend="spark"|"mpi"|"mp"|"local")– the estimator wrapper: composes with any data backend, so the data is sharded by that backend (Spark partitions, MPI ranks, mp pool) while the model axis is distributed inside each partition’s accumulator. This is the data x model composition – both axes at once.
The fold (model_parallel_fold()) is recursive: it walks the whole model tree and threads the
axes that a per-node compute-cost model says save the most wall-time (_parallel_ids, consistent
with the C2 planner – a narrow batch of heavy MVGaussians beats a wider batch of cheap leaves),
recursing serially below any threaded node so no two pools ever nest. Each recursive case reproduces the
corresponding accumulator’s seq_update exactly:
FACTOR (Composite/Record) – the per-factor accumulators are independent, so the per-factor
seq_updatecalls are distributed (bit-identical).COMPONENT (mixtures) – the responsibility
logsumexpcouples the components, so the cheap normalization runs centrally on the gathered score matrix while the expensive per-component scoring and accumulation are distributed – a bit-identical mirror ofMixtureAccumulator.seq_update.atomic / unknown – the replicated base case
acc.seq_update(enc, weights, model).
So the whole fold is bit-identical to the single-node path (the data-axis reduce across partitions is the
usual additive combine, exact up to float reassociation like every data-parallel backend). Correct
for every family; never worse than backend="local". See ~/codex/notes/model-parallel-design.md.
- class ModelParallelEncodedData(data, *, estimator=None, model=None, encoder=None, num_workers=None, **_)[source]
Bases:
EncodedDataHandleReplicate the data, distribute the model’s shardable axis across threads (single machine).
- Parameters:
data (Any)
estimator (Any | None)
model (Any | None)
encoder (Any | None)
num_workers (int | None)
_ (Any)
- pysp_seq_log_density_sum(estimate)[source]
Return
(num_observations, summed_log_density)forestimate.
- pysp_seq_estimate(estimator, prev_estimate)[source]
Run one distributed/local sufficient-statistic fold and M-step.
- pysp_seq_initialize(estimator, rng, p)[source]
Initialize a model through the handle’s resident encoded data.
- Parameters:
estimator (Any)
rng (RandomState)
p (float)
- Return type:
- class ModelParallelEstimator(inner, num_workers=None)[source]
Bases:
ParameterEstimatorWrap an estimator so EM distributes the model axis – composing with any
backend=for the data.optimize(ModelParallelEstimator(est), backend="spark"|"mpi"|"mp"|"local")shards the data through that backend while each partition’s E-step distributes the model axis. The M-step (estimate) and the accumulator’s value/combine contract are the wrapped estimator’s, unchanged.- Parameters:
inner (ParameterEstimator)
num_workers (int | None)
- accumulator_factory()[source]
- Return type:
ModelParallelAccumulatorFactory
- class ModelParallelAccumulator(inner, num_workers=None)[source]
Bases:
SequenceEncodableStatisticAccumulatorWrap an accumulator so its E-step
seq_updateruns the recursive model-parallel fold.All sufficient-statistic methods delegate to the wrapped (
inner) accumulator unchanged, so the value/combine/from_value/key-merge contract – and thus every data backend’s reduce – is preserved; onlyseq_updateis replaced with the distributed fold. Holdinginnerinvars()keeps the accumulator key-validator’s recursion transparent.- Parameters:
inner (SequenceEncodableStatisticAccumulator)
num_workers (int | None)
- update(x, weight, estimate)[source]
- seq_update(x, weights, estimate)[source]
- seq_initialize(x, weights, rng)[source]
- key_merge(stats_dict)[source]
Pool this accumulator’s statistics into
stats_dictunder its merge key.The structural default implements the common single-key pattern: store the accumulator under
self.keysthe first time the key is seen, elsecombineinto the one already there. Accumulators with several named keys (e.g. an HMM’s init/trans/state keys) or a non-accumulator stats payload override this. AkeysofNone(the default) is a no-op.
- key_replace(stats_dict)[source]
Replace this accumulator’s statistics from the pooled
stats_dictentry (see key_merge).
- class ModelParallelAccumulatorFactory(inner_factory, num_workers=None)[source]
Bases:
StatisticAccumulatorFactory- Parameters:
inner_factory (Any)
num_workers (int | None)
- make()[source]
- Return type:
ModelParallelAccumulator
- model_parallel_fold(acc, model, enc, weights, num_workers=None)[source]
Run a model-parallel E-step of
modelover encodedencinto accumulatoracc(in place).
- auto_parallel_estimator(estimator, model, resources=None, *, n_data=None, min_components_per_shard=1)[source]
Consult the C2 planner (
decompose_model()) and return(estimator, decomposition).When the planner picks model-parallelism for
modelonresources, the estimator is wrapped inModelParallelEstimatorsized to the planner’s cuts; otherwise the plain estimator is returned (replicate the model, shard the data – already optimal when N dominates). Either way, run the returned estimator throughoptimize(data, est, backend=<data backend>): the data axis is handled bybackendand the model axis, if any, by the wrapper – composing into the data x model split. Thedecomposition’srationaleexplains the choice.resourcesdefaults to the local CPU slots (useResources.from_spark(sc)/Resources.from_mpi()to size the split to a real cluster).