mixle.experimental.summary_tree module¶
E4: hierarchical summary tree + multi-scale objective – see notes/designs/E4.md for the full
derivation (persistent frontier tree over evicted tokens, tree-path positional encoding, the
predict-the-summary auxiliary loss, the stop-gradient horizon receipt). This module implements that
note section-by-section; see the note’s “Implementation notes vs. this design” section for the small,
honestly-documented places this module simplifies the note’s scheme for tractability.
What this is. E1’s SlidingWindowSpine keeps an exact
but bounded KV window; anything evicted from that window is gone. E4 keeps it: every evicted token is
folded, one at a time, into a bounded frontier of learned summaries via mixed-radix carry propagation
(the fast-multipole-method structure – near field exact, far field via a bounded number of
increasingly coarse representatives – applied to token history). Tree depth grows only as
log_fanout(evicted_count), so the far-field attention set stays bounded regardless of how much
history has streamed through.
Positional encoding. RoPE’s q . k dependence on i - j is well-conditioned only when
i - j is a small, well-scaled number (E1’s window); a far-field summary node represents a range
of possibly billions of original positions, and there is no single j to rotate by. E4 replaces
RoPE for the far field with (a) a content channel – a level embedding plus a sibling-slot embedding
summed into the node’s summary before it’s used as an attention key – and (b) a relative bias
channel – a learned scalar indexed by tree distance (lca_depth, see below), the ALiBi/T5-bias
shape of mechanism but indexed by tree distance instead of linear offset. Near-field window tokens
keep ordinary RoPE unchanged (E1’s _rope_angles/_apply_rope, reused verbatim).
Predict-the-summary auxiliary loss. Every node, the moment it’s finalized, is scored against the
exact additive token-id histogram of the leaves it covers via one shared linear head
(d_model -> vocab) and cross-entropy against the normalized histogram. This is direct supervision
independent of whether any future query ever attends to that node – for a node many levels up the
tree that a training run may never query again, this is its compressor’s only gradient.
Stop-gradient horizon (receipted). A frontier node moves from live to archived once H
further evicted tokens have arrived after it finalized; .summary is .detach()-ed at that moment
and never un-detaches. Archived frontier nodes stay forward-visible but stop receiving gradient. See
SummaryTreeSpine.detach() and mixle/tests/summary_tree_test.py for the exact-accounting
receipt.
- class TreeNode(summary, histogram, path, level, g, finalized_step, finalized_index_within_level, represented_leaves=0, detached=False, detached_at_finalized_count=None, detached_at_evicted_count=None)[source]
Bases:
objectOne finalized node of the persistent summary tree (E4.md’s
TreeNode).summarycarries one entry per layer (each(batch, d_model)) since every layer’s tree is built from that layer’s own(k, v)via that layer’s ownqkvprojection, but the bookkeeping fields below (histogram/path/level/g) are identical across layers – they describe which evicted tokens this node covers, not any layer-specific content – so they’re stored once.
- class SummaryTreeState(window, cached_ids, pending_leaf, pending, live, archived, level_finalized_count, evicted_count=0, batch_size=1, receipt=None)[source]
Bases:
objectContextMechanismcarried state: E1’s exact near field plus the persistent far-field tree.
- digits_of(n, base)[source]
Base-
basedigits ofn, least-significant first.digits_of(0, base) == (0,).Both arguments are validated because this is a public export and neither degenerate case has an answer to return (MXR-080-1862):
base=1never terminates –n // 1 == nforever, so the loop appends zeros until memory runs out –base=0divides by zero, and a negativenskips the loop entirely and silently reports the empty digit sequence, which is not a representation of anything.
- lca_depth(query_pos, node_level, node_g, fanout, *, max_climb=64)[source]
Tree distance between a query at absolute position
query_posand a node covering the contiguous leaf range[g * fanout**level, (g + 1) * fanout**level)(g= the node’s own 0-based sequential index among nodes finalized at its level – see E4.md’s carry-propagation section). Returns how many levels abovenode_levelthe query’s ancestor chain must climb before it lands in the same subtree as the node – seenotes/designs/E4.md’s “Implementation notes” section for why this integer recurrence is the exact algebraic equivalent of “matching leading digits of the two paths” without materializingquery_pos’s (potentially ~1e9-long) digit expansion. Pure function of(query_pos, node_level, node_g, fanout)– no dependence on how the stream was chunked, which is what makes it stable under re-chunking (Acceptance §3).Every argument is validated because this is a public export and each degenerate case returns a number that means nothing (MXR-080-1866).
fanout=1makesfanout**m == 1at every level, soq_ancnever moves and the loop runs tomax_climband reports that climb as a tree distance – a base-1 tree has no positional expansion to share a prefix in (the same reasondigits_of()refuses it). A negativequery_posfloor-divides toward minus infinity, and a negativenode_levelmakesfanout**ma float, so the “integer recurrence” returns a float distance. A negativenode_gnames no node. None of these are states the tree can reach; each previously returned a plausible-looking integer instead of refusing.