Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The guide claimed Axon.bidirectional/4 composes with the custom layer without qualification, but the wrapped function runs inside an Axon.block/2 and cannot reference the mask node from the surrounding graph (and the backward pass would need a reversed mask anyway). Say so and show the working unmasked call. Also finish the truncated sentence in the Axon.param/3 docs describing what shape functions receive. Co-Authored-By: Claude Fable 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 #511.
Axon.Layers.dynamic_unroll/7andstatic_unroll/7are public, but their docs did not say whatcell_fnis called with, what it must return, whatcarryandmaskmean, or what the unroll returns. #511 asked for exactly that plus a guide that builds a recurrent layer from scratch instead of treatingAxon.lstmas a black box, and shows how to deal with padded, variable-length sequences. The built-in RNNs are generated by a macro, which made it hard to see that the underlying pieces are ordinary functions anyone can use.This is a documentation PR; no library behavior changes.
The guide
guides/model_creation/custom_recurrent_layers.livemdbuilds an Elman RNN in three steps and uses it to classify padded character sequences:input,carry,mask,input_kernel,recurrent_kernel,bias) and what it expects back ({output, new_carry}), and that the cell, not the unroll, is responsible for honoring the mask (1means padding).Axongraph is involved, the guide runsstatic_unroll,dynamic_unrolland anEnum.map_reduce/3over the time axis and shows all three agree, so readers see the unroll is just a scan. It explains when to prefer static (inlined, short sequences) vs dynamic (whileloop, graph size independent of length).SimpleRNN.simple_rnn/3mirrorsAxon.lstm/4: it declares params withAxon.param/3, callsAxon.layer/3with the sequence, anAxon.mask/3node and the params, and splits the{outputs, {hidden}}result withAxon.elem/2. A synthetic, seedable "names" dataset (two made-up languages, words of 3..10 letters padded with0) replaces the downloaded name files from the PyTorch tutorial the reporter was porting, so the notebook is self-contained. The guide then demonstrates that the final hidden state equals the state after the last real letter and does not change through the padding, that dropping the mask changes the result, and thatunroll: :staticgives the same numbers. It finishes withAxon.Looptraining (~94% test accuracy) and a "going further" section covering stacking,Axon.bidirectional/4, richer carries, and what a cell can and cannot call.The core of it:
Two things the guide calls out explicitly because they tripped up prototyping:
Axon.param/3shape functions receive one shape per Axon input of the layer (so a layer with a sequence and a mask input needs an arity-2 function, and the[{:axis, -1}, units]shape DSL cannot be used there), andAxon.mask/3has to be computed from the integer tokens beforeAxon.embedding/4.Doc changes
Axon.Layers.dynamic_unroll/7now documents the fullcell_fncontract, every argument, the mask semantics, the return value, and the while-loop trade-off.static_unroll/7refers to it and explains the inlining trade-off.Axon.param/3finishes its truncated sentence about shape functions: they receive one shape perAxoninput of the layer, in order, so a layer with twoAxoninputs needs an arity-2 function.Axon.mask/3says what it outputs (u8,1ateos_token), that it must come from the token input, and where to pass it.lstm_celldoc.mix.exsextrasandguides/guides.md; it lands under "Guides: Model Creation" via the existing wildcard.Limitations
Axon.elem/2, which is onmainbut not in the 0.8.1 release, so the notebook needs the next release (or a git dependency) to run. TheMix.installfollows the other guides.Axon.bidirectional/4works with the custom layer but only without a mask: the wrapped function runs inside anAxon.block/2, which cannot reference the mask node from the surrounding graph, and the backward pass would need a reversed mask anyway. The guide says so instead of claiming unqualified compatibility.:glorot_uniforminitializer for the recurrent kernel rather thanAxon.Initializers.orthogonal/1, because the orthogonal initializer's QR host callback breaksinit_fnwith anNx.templateunder EXLA. That is an existing issue and is not addressed here.Tests
test/axon/recurrent_guide_test.exsruns the guide's code, following the convention ofserialization_guide_test.exs, with theSimpleRNNmodule verbatim and a smallermax_len = 8dataset:static_unroll,dynamic_unrolland anEnum.map_reducescan agree on outputs and final carry, with the expected shapes.input_kernel,recurrent_kernelandbiaswith the expected shapes under the"rnn"name.Axon.Loop.traineron padded sequences returns aModelStatewith the RNN params, loss decreases across epochs and accuracy exceeds 80% (deterministic viaseed: 42).The file runs in ~2s under the default
Nx.Defn.Evaluatorand passes underUSE_EXLA=1as well.🤖 Generated with Claude Code