Repository navigation
0.8.1 — a read cache can cross a vmap boundary - #31
Merged
Merged
Conversation
GatedDeltaNetReadCache and the kernel's ReadCache arrived after #12 registered the state containers, so torch saw a cache as one opaque leaf. A cache made and consumed inside one vmapped call never noticed. Writing G caches in one vmap call and re-reading them in another did: the writers could not return them, and the re-readers could not take them at in_dims=0. ReadCache.seq_len is the UndertowState.seen case again. It is an int that drives control flow, so register_dataclass would make it a leaf and vmap would refuse it. The Undertow registration's hand-rolled flatten is now a helper that takes the context field by name and reads the children off the dataclass, so a field added later becomes a child instead of being dropped. ReadCache and UndertowState both go through it. The outer GatedDeltaNetReadCache uses register_dataclass, since its conv is None-or-tuple exactly like the state's. The caches are appended to REGISTERED, so the five states keep their positions. - A round trip keeps seq_len, and the rebuilt cache re-reads the original output exactly, for a first pass (conv None) and a continued one. - Paths reach every leaf through both registrations. - G vmapped writes, then G vmapped re-reads in a separate call, match the layers run one at a time, at the tolerance the existing set-by-set test uses. The new tests fail on main with the errors the issue reports. Weights, a vmapped two-chunk stream through an Undertow-plus-GDN trunk, and a re-read hash identically on main and here. docs/PYTREE.md gains a read-cache section, and design record §3.9 no longer says the cache is unregistered. 591 passing with the CUDA tests deselected. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The GDN read caches become pytree nodes beside the five states (#20). Additive and numerically inert, so a patch release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #20.
GatedDeltaNetReadCacheand the kernel'sReadCachecame after #12 registered the state containers, sotorchstill treated a cache as one opaque leaf. That never showed while a cache was made and used inside one vmapped call, or shared unbatched across sets. It did show once the write and the re-read happened in separate batched calls, which is whatstack_module_state+vmap(functional_call(...))needs.Gwriters could not return their caches:and so
Gre-readers had nothing to take back in atin_dims=0. Both now work.The change
ReadCache.seq_lenis theUndertowState.seencase again. It is a permanentintthat drives control flow: where a re-read trims its padded output, and what a query's length is checked against.register_dataclasswould make it a leaf, andvmaprefuses it. So it goes in the treespec context, and it gets no default, for the reasonlumen/pytree.pyalready gives forseen.One helper rather than a second copy. The Undertow registration's hand-rolled flatten is now
_register_with_context(cls, context). It takes the context field by name and reads the children off the dataclass, so the keyed and unkeyed flattens come from one field list and cannot disagree. A field added to either class later becomes a child automatically. If the children were listed by hand, a new field would be dropped by the flatten and silently rebuilt from its default. Now the worst a new non-tensor field can do is be refused byvmap, which is loud.UndertowStategoes through the same helper, and its flatten is unchanged: children[keys, values], contextseen.The outer
GatedDeltaNetReadCacheneeds nothing special. Itstrajectoryflattens through its own registration, and itsconvisNoneon a pass that began a sequence, the sameNone-or-tuple caseregister_dataclassalready handles forGatedDeltaNetState.conv.REGISTEREDgrows from five names to seven. The caches are appended, so the five states keep their positions.Numerically inert, checked rather than assumed
Weights, a vmapped two-chunk stream through an Undertow-plus-GDN trunk (which goes through the rewritten Undertow flatten), and a GDN re-read all hash identically on
mainand on this branch. Registration changes a registry insidetorch. No tensor changes value, and no checkpoint key moves.Tests
6 new, CPU-only, each run for a first pass (
convisNone) and a continued one (convis a tuple):seq_lensurvives flatten/unflatten, every leaf is a tensor, and the rebuilt cache re-reads the original output exactly (atol=0).tree_flatten_with_pathreaches every leaf through both registrations (.trajectory.unfolded….conv[2]), and a first pass has no.convleaf.Gvmapped writes, thenGvmapped re-reads in a separate call, match the layers run one at a time. Every cache leaf comes back with theGaxis, conv included.Against
main'spytree.py, with the skip guard disabled, all 7 new or changed tests fail with the errors the issue reports. The 22 existing tests pass both ways.On exactness, measured on one machine (CPU,
torch 2.9.1): re-reading undervmapover caches written one at a time is exact, max abs difference0.0, which matches the issue's probe. With the writes batched too, the difference is about1.8e-7. That is fp32 rounding from batching the chunkwise scan, the same thing the existing set-by-set test tolerates. The new test uses that test's tolerance (1e-5) rather than asserting zero.Degradation, checked by stubbing
torch's entry points before import:register_pytree_noderaising,REGISTEREDdrops to five and the layer writes and re-reads exactly as before. As with aBlockStateholding an unregisteredUndertowState, the outer cache then flattens to one opaque leaf, andREGISTEREDshowsReadCacheas missing.591 passing with the CUDA tests deselected. The GPU-marked tests were not run for this change: registration is pure Python over tensor references and never looks at a device.
Docs
docs/PYTREE.mdgains a read-cache section with a working write-then-re-read example, and theREGISTEREDlisting is updated.docs/design/GATED_DELTANET.md§3.9 said the cache was "not registered … and is its own change". It now describes the registration.Release
0.8.1, in the last commit. Additive and numerically inert, so a patch release.🤖 Generated with Claude Code