mixle.utils.parallel.context_parallel_spine module¶
E8: context parallelism for SlidingWindowSpine.
See notes/designs/E8.md for the full design (why torch-native CP doesn’t suffice –
SlidingWindowSpine.step computes attention by hand, there is no single scaled_dot_product_attention
call for the native CP context manager to intercept – and why F1’s own CP
(tensor_pipeline_context_parallel.py) doesn’t drop in as-is – it shards a whole-block STATELESS
forward, not a carried-state streaming step).
This module reuses F1’s proven PATTERN (chunk the KV axis, all-gather once per attention op, reconstruct
with an explicit offset mask, verify by exact match against a dense reference) rather than its entry
points, purpose-built for ContextMechanism.step’s carried-state shape: each call shards the CURRENT
step’s KV axis (cache ++ chunk, not a whole logical stream) across cp_size simulated ranks.
Same scope cut as F1’s CP, for the same reason (this environment has <=1 real GPU): eager chunked-all-gather CP, exact and testable at small scale, NOT incremental ring attention (no rank-to-rank streaming overlap). See the design note’s Risks section.
RoPE is applied PER SHARD, using each shard’s own absolute key_positions_shard, BEFORE the gather
(not once on the full gathered K after). This is deliberately closer to what a real distributed rank
would do (a rank never needs another rank’s raw K to compute its own RoPE) at the cost of being only
mathematically – not bit-exactly – identical to the single-device code path (float non-associativity
of RoPE-per-slice-then-concat vs. RoPE-on-the-full-array). See notes/designs/E8.md’s Risks section
for the fallback (gather-raw-K-then-RoPE-once) if this tolerance had not held in practice; the test suite
(mixle/tests/context_parallel_spine_test.py) measures this directly and it DOES hold, at low-1e-6
absolute divergence even under real multi-chunk SGD training, comfortably inside the documented
rtol=1e-4 tolerance (F1’s own precedent).
Cache convention: SlidingWindowSpine.step’s cp_size==1 path caches the RoPE’d k_full (it
reassigns k_full = _apply_rope(...) before slicing into cache_k), not a raw pre-RoPE k_full.
The cp_size>1 branch must reproduce that exact convention – per-shard RoPE, then concat – when it
writes back to the cache, or cache_k silently diverges from the dense reference starting the second
streamed chunk even though the first chunk’s loss (and every single-chunk-only comparison) matches
bit-exactly. This was caught by this module’s own test suite during implementation, not anticipated by
the design note’s algorithm section (which describes cache ++ chunk as “raw k/v, pre-RoPE” as the
sharding INPUT, without stating what convention the cache is written back in) – see
SlidingWindowSpine.step’s cp_size>1 branch in context_spine.py for the fix.
Unlike F1’s TP (n_head % tp_size == 0) and CP (block % cp_size == 0), which both fail fast on
uneven division, this module’s KV axis length (cache_len + t) varies chunk to chunk and is not
required to divide evenly by cp_size – torch.chunk already degrades gracefully (size-balanced,
possibly-uneven chunks, or fewer than cp_size chunks if the axis is shorter than cp_size) rather
than erroring, and this module relies on exactly that behavior instead of adopting F1’s stricter
validate-and-reject posture. See the design note’s Risks section for why this is a deliberate difference,
not an oversight.
- class CPWindowShard(k_shard, v_shard, key_positions_shard)[source]
Bases:
objectOne (simulated) rank’s contiguous slice of the current step’s KV axis (
cache ++ chunk).k_shard/v_shardare this rank’s raw (pre-RoPE) K/V slice – RoPE is applied later, per shard, usingkey_positions_shard, so a real rank never needs another rank’s raw K.
- cp_shard_kv(k_full, v_full, key_positions, cp_size)[source]
Split the current step’s
(cache ++ chunk)K/V/positions intocp_sizecontiguous position chunks (torch.chunkalong the position axis) – the “exact window sharded across devices” the E8 card names.Deliberately does NOT require
cp_sizeto dividek_full’s length evenly: the KV axis length (cache_len + t) varies chunk to chunk near stream start/end, unlike F1’s TP/CP shapes which are fixed and validated up front.torch.chunkdegrades gracefully on its own (uneven trailing chunk, or fewer thancp_sizechunks if the axis is shorter thancp_size) – both cases are exercised bycontext_parallel_spine_test.py’s window-edge-case tests.
- cp_window_attention_forward(q, query_positions, shards, *, window, head_dim)[source]
Context-parallel sliding-window attention for one layer, one step.
q:(batch, t, n_head, head_dim), raw (pre-RoPE) queries for this chunk.query_positions:(t,)absolute positions (never sharded – only keys/values are, per the E8 card: “sharding the exact window,” i.e. the KV axis, not the query/chunk axis).Per shard (rank): apply RoPE to its own K slice using its own absolute
key_positions_shard(step 3 of the design’s algorithm – no cross-rank RoPE dependency, so this generalizes to a real distributed setting where a rank never materializes another rank’s raw K/V before the shard boundary). Then all-gather (concat in position order) the RoPE’d K and raw V across shards – the one collective per attention call, matching F1 CP’s “one collective per block” scope. Finally run the unmodified masked-matmul attention mathSlidingWindowSpine.stepalready does (delta = query_pos - key_pos; allowed = 0 <= delta < window) against the gathered full K/V.Returns
(batch, t, n_head * head_dim)– the same shape/contentSlidingWindowSpine.step’s single-device attention sub-step produces, reconstructed exactly (mod float non-associativity of the per-shard-RoPE-then-gather order – see the module docstring).
- validate_cp_window_plan(cp_size, window)[source]
Fail-fast on a malformed
cp_size; warn (never error) on a degeneratewindow/cp_size.Unlike F1’s
validate_tp_pp_cp_plan(which requires exact division of fixed model dimensions), E8’s KV axis length has no fixed size –cache_len + tvaries chunk to chunk – so there is no “cp_sizemust divide the window evenly” check to make: correctness holds for anycp_size >= 1(cp_shard_kv’storch.chunkdegrades gracefully on uneven/short axes). The only thing worth flagging is a plan that is valid but probably pointless: if the window is so small relative tocp_sizethat each shard would carry on the order of one key or fewer on average, the communication/bookkeeping overhead of sharding is very unlikely to pay for itself – that is a performance heads-up, not a correctness problem, hence a warning.