Add parameter constraints - #665
Open
seanmor5 wants to merge 1 commit into
Open
Conversation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
polvalente
reviewed
Aug 24, 2026
Comment on lines
+61
to
+64
| opts = keyword!(opts, max: 2.0, axes: [0]) | ||
| norms = norm(x, opts[:axes]) | ||
| desired = Nx.clip(norms, 0, opts[:max]) | ||
| x * (desired / (norms + @epsilon)) |
polvalente
reviewed
Aug 24, 2026
Comment on lines
+60
to
+62
| defnp max_norm_impl(x, opts \\ []) do | ||
| opts = keyword!(opts, max: 2.0, axes: [0]) | ||
| norms = norm(x, opts[:axes]) |
Member
There was a problem hiding this comment.
norm should be an option as well. Otherwise the docs should make it clear we're talking euclidean norm
polvalente
reviewed
Aug 24, 2026
| ] | ||
| > | ||
| """ | ||
| def non_neg() do |
Member
There was a problem hiding this comment.
let's use full names
Suggested change
| def non_neg() do | |
| def non_negative() do |
polvalente
reviewed
Aug 24, 2026
Comment on lines
+109
to
+116
| def unit_norm(opts \\ []) do | ||
| fn x -> unit_norm_impl(x, opts) end | ||
| end | ||
|
|
||
| defnp unit_norm_impl(x, opts \\ []) do | ||
| opts = keyword!(opts, axes: [0]) | ||
| x / (norm(x, opts[:axes]) + @epsilon) | ||
| end |
Member
There was a problem hiding this comment.
Again, which norm is used should be an option
polvalente
reviewed
Aug 24, 2026
Comment on lines
+162
to
+164
| defnp norm(x, axes) do | ||
| Nx.sqrt(Nx.sum(x * x, axes: axes, keep_axes: true)) | ||
| end |
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 #111.
Axon had no way to keep a trainable parameter inside a feasible region during training. Keras offers this as constraints: projections such as
max_normornon_negthat run on a weight right after every optimizer update. This PR adds the same mechanism to Axon, following the Keras (post-update projection) semantics rather than PyTorch-style parametrizations that reparameterize the forward pass.Design
Constraints mirror how frozen parameters already work: they are metadata on
%Axon.ModelState{}that the compiler collects from the graph and that the training step consumes throughAxon.ModelStatetransforms.Axon.Constraintsis a new module shaped likeAxon.Initializers.max_norm/1,non_neg/0,unit_norm/1andmin_max_norm/1return arity-1 functions that take a parameter tensor and return the projected tensor. Norm-based constraints take:axes(default[0], the incoming weight vector of each output unit for a dense kernel). Their implementations aredefnps so they trace into the training step.Axon.param/3andAxon.parameter/3accept a:constraintoption: one of the atoms above (resolved to theAxon.Constraintsfunction with default options) or an arity-1 function. It is validated like:initializerand stored on%Axon.Parameter{}.%Axon.ModelState{}gains aconstraintsfield (akeepfield, nested%{layer => %{param => fun}}likedata). The compiler collects it next tofrozen_parameters, nests it for blocks, andmerge_model_state!carries constraints from a user-supplied initial model state so they compose withAxon.Loop.run/4.Axon.ModelState.constrain/3attaches a constraint to every parameter matched by a path mask (same mask style asfreeze/2), which is how you constrain built-in layers such asAxon.dense.Axon.ModelState.apply_constraints/1projects every constrained, trainable, non-frozen parameter.Axon.Loop.train_step/4callsapply_constraints/1right afterAxon.ModelState.update/3, so every optimizer update is followed by the projection.Usage
On a custom layer parameter:
On the parameters of built-in layers, by path:
Tradeoffs and limitations
Axon.Loop.serialize_state/2serializes them withterm_to_binary; anonymous constraints only deserialize when the module that defined them is available (theAxon.Constraintsfunctions are fine).Axon.constrain/3is added since the graph-levelAxon.freeze/2is deprecated in favor of theAxon.ModelStateAPI, and no per-layerkernel_constraint/bias_constraintoptions are added.merge_model_state!still drops thefrozen_parametersof a user-supplied initial model state at init time. This PR only carriesconstraintsacross; carryingfrozen_parametersthe same way is a candidate follow-up.Tests
test/axon/constraints_test.exs: doctests plus numeric checks for all four constraints (including custom:axes,:rate, type preservation forbf16, and use insidedefn).test/axon_test.exs::constraintvalidation and storage onAxon.param/3andAxon.parameter/3.test/axon/model_state_test.exs:constrain/3path masking and composition,apply_constraints/1projecting only constrained parameters, skipping frozen ones, and both working insidedefn.test/axon/compiler_test.exs: constraint metadata is collected for custom layers, nested for blocks, and preserved from an initial model state.test/axon/loop_test.exs:train_stepprojects a parameter after SGD (and leaves it alone when frozen),Axon.Loop.run/4honors constraints attached to the initial model state, and constraints surviveserialize_state/deserialize_state.🤖 Generated with Claude Code