Skip to content

fix: keep Box-Muller randn finite by drawing u1 from (0, 1] - #1455

Merged
michalharakal merged 1 commit into
developfrom
fix/randn-box-muller-ln-zero
Oct 31, 2026
Merged

michalharakal merged 1 commit into
developfrom
fix/randn-box-muller-ln-zero

Conversation

@michalharakal

Copy link
Copy Markdown
Contributor

Fixes #1454.

What

All four Box-Muller randn blocks drew u1 = random.nextFloat() ∈ [0, 1). A zero draw (P ≈ 2⁻²⁴ per pair) makes ln(u1) = -Inf and injects Inf/NaN into the tensor — near-certain across the hundreds of millions of samples a large model init makes. Found in practice as all-NaN logits when initializing a Gemma 3 270M model from scratch on 0.62.4.

How

  • u1 = 1.0 - random.nextDouble(), which lies in (0, 1] — ln(1) = 0 is harmless.
  • The transform stays in double precision until the final toFloat() (the old code rounded sqrt/cos to float separately before multiplying).
  • The shared sqrt(-2 ln u1) term is hoisted in the pair variants (it was computed twice).
  • Distribution is unchanged: seeded mean/std sanity test included.

Touched sites:

  • DenseTensorDataFactory — single-sample overload + the TensorDataFactory.randn override (FP32 and FP16/BF16 branches)
  • MemorySegmentTensorDataFactory.randn (jvmMain)

No public API change (no apiDump needed).

Tests

  • DenseTensorDataFactoryRandnTest (commonTest, kotlin.test): a zero-bits Random forces the worst-case draw for FP32/FP16/BF16 and the single-sample overload; plus a seeded 100k-sample mean/std check.
  • MemorySegmentTensorDataFactoryRandnTest (jvmTest): same worst-case draw on the MemorySegment factory.

Red/green verified: with the production fix stashed, 5 of the 6 new tests fail (the seeded statistical test passes either way, as expected); with the fix, all pass.

🤖 Generated with Claude Code

All four Box-Muller blocks drew u1 = random.nextFloat() in [0, 1); a zero
draw (P ~ 2^-24 per pair) makes ln(u1) = -Inf and injects Inf/NaN into the
tensor. Across the hundreds of millions of samples a large model init
makes this is near-certain - observed as all-NaN logits when initializing
a Gemma 3 270M model from scratch.

Draw u1 = 1 - nextDouble(), which lies in (0, 1], keep the transform in
double precision until the final toFloat(), and hoist the shared sqrt
term. Regression tests force the zero draw with a zero-bits Random and
add a seeded mean/std sanity check.

Fixes #1454

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

📖 Documentation Preview

The documentation has been built successfully for this PR.

Generated Files:

  • Operator documentation: docs/modules/operators/_generated_/
  • JSON schema output: operators.json

Artifacts:

  • Download the documentation-preview-1455 artifact to view the complete documentation locally.

This comment will be updated automatically when the PR is updated.

@michalharakal
michalharakal merged commit f056f9b into develop Oct 31, 2026
20 checks passed
@michalharakal
michalharakal deleted the fix/randn-box-muller-ln-zero branch October 31, 2026 17:04
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.

randn() Box-Muller draws ln(u1) with u1 in [0,1) — injects Inf/NaN into large tensor inits

2 participants