Skip to content

Annotate activation functions with Nx blocks - #639

Merged
polvalente merged 9 commits into
elixir-nx:mainfrom
Chapaman:sm-nx-block
Aug 14, 2026
Merged

Annotate activation functions with Nx blocks#639
polvalente merged 9 commits into
elixir-nx:mainfrom
Chapaman:sm-nx-block

Conversation

@Chapaman

@Chapaman Chapaman commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

seanmor5 and others added 4 commits May 25, 2026 18:35
Nest block structs under the caller module (defblock selu -> Activations.Selu,
optional SeLU casing), keep opts on the struct so block args stay tensors, and
move stop_grad/custom_grad/cache_logits outside block bodies so BinaryBackend
evaluation does not leak Expr metadata. Update activation doctests for Nx 0.13
inspect formatting.
Comment thread lib/axon/activations.ex Outdated
Comment on lines +519 to +531
defn relu(x) do
# custom_grad must sit outside Nx.block/4 — BinaryBackend.block re-runs the
# callback and Expr.metadata would otherwise leak into the result.
custom_grad(
Nx.max(x, 0),
relu_block(x),
[x],
fn g -> [Nx.select(Nx.greater(x, 0), g, Nx.broadcast(0, g))] end
)
end

defblock Relu, relu_block(x) do
Nx.max(x, 0)
end

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that custom_grad should sit outside of Nx.block. The grad of block is the grad of the default callback IIRC, so I don't think this should matter

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure that custom_grad should sit outside of Nx.block. The grad of block is the grad of the default callback IIRC, so I don't think this should matter

You're right on the default callback, The thing I'm having trouble with is on forwarding. The callback gets re-run on real tensors and custom_grad/stop_grad turns results into a traced tensor

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

custom_grad and stop_grad should work though. Maybe defblock is not using proper defn scoping for the callback body?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should make sure that custom_gradand stop_grad are guarded on Nx?

Public API is a deftransform that builds Nx.block; the callback calls a
private defnp so custom_grad/stop_grad JIT normally instead of wrapping
concrete tensors. Pin nx/exla/torchx to elixir-nx/nx main for Nx.block.
@Chapaman
Chapaman marked this pull request as ready for review August 10, 2026 22:34
Comment thread lib/axon/activations.ex
Comment thread lib/axon/activations.ex Outdated
cache_logits(x, softmax_block(x, opts))
end

defblock SoftMax, softmax_block(x, opts \\ []) do

@polvalente polvalente Aug 10, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe this warrants defblockp where the inner deftransform is also defined with deftransformp so that this module just advertises softmax but not softmax_block

Comment thread lib/axon/block.ex Outdated
end

# Public transform first so a preceding @doc attaches here, not to defnp.
deftransform unquote(name)(unquote_splicing(args)) do

@polvalente polvalente Aug 10, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
deftransform unquote(name)(unquote_splicing(args)) do
unquote(kind)(unquote(name)(unquote_splicing(args))) do

where kind is either :deftransform or :deftransformp coming from defblock and defblockp respectively, I think is the correct solution to my other comment in activations

Polvalente wanted the SoftMax/Sigmoid helpers hidden from the module API;
defblockp generates deftransformp while leaving the block struct public.
The merge left deps/0 calling nx_opts/exla_opts/torchx_opts while the
helpers were still the old github-pin *_dep functions, which broke CI
at mix.exs compile. Hex already ships Nx.block in 0.13, so keep main's
path-override helpers.
@polvalente
polvalente merged commit c7c3f25 into elixir-nx:main Aug 14, 2026
7 checks passed
@Chapaman Chapaman mentioned this pull request Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants