Skip to content

perf: Unroll the threefry2x32 rounds in Nx.Random - #1891

Merged
polvalente merged 3 commits into
elixir-nx:mainfrom
georgeguimaraes:perf/random-unroll-threefry
Oct 22, 2026
Merged

polvalente merged 3 commits into
elixir-nx:mainfrom
georgeguimaraes:perf/random-unroll-threefry

Conversation

@georgeguimaraes

Copy link
Copy Markdown
Member

Nx.Random ran the 20 rounds of threefry2x32 in two while loops. The round count is static, so the loops now use generators with unroll: true, and the rounds become part of the traced expression. The values do not change.

EMLX evaluates each iteration of a while loop separately, so each draw did many small device calls. On the GPU of an M2 Pro, with EMLX 0.5.0:

Jitted call Before After
uniform, shape {2, 49152} 9.47 ms 0.88 ms
fold_in 4.88 ms 0.47 ms

On EXLA (CPU, same Mac), compile takes longer and a large draw runs faster:

Jitted call Compile before Compile after Run before Run after
uniform, shape {2, 49152} 49 ms 110 ms 0.55 ms 0.26 ms
uniform, shape {16} 36 ms 57 ms 0.11 ms 0.11 ms

The sums of the drawn values are equal before and after on both backends. nx/test/nx/random_test.exs and exla/test/exla/random_test.exs pass.

I found this in a text-to-speech model whose decoder draws dropout masks at each step. The draws took about 21 ms of a 25 ms step on EMLX.

The 20 rounds of threefry2x32 ran in two while loops. A backend that evaluates each iteration of a while loop separately, such as EMLX, then does many small device calls for each draw. The rounds have a static count, so the loops now use generators with unroll: true, and the rounds become part of the traced expression. The values do not change.
@polvalente
polvalente merged commit aaeaa18 into elixir-nx:main Oct 22, 2026
9 checks passed
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