fix: 64-bit randint could return high; correct the integer fills - #172
fix: 64-bit randint could return high; correct the integer fills#172vchamarthi wants to merge 3 commits into
Conversation
|
@vlad-perevezentsev |
Looks separate |
| } | ||
| } | ||
|
|
||
| void irk_rand_uint64_vec(irk_state *state, |
There was a problem hiding this comment.
There is near-twin irk_discrete_uniform_long_vec (used by randint_untyped) keeps the old single-path rejection loop and the pre-fix VSL_RNG_METHOD_UNIFORM_STD constant.
It'd be nice to research on performance improvement there also in the follow-up PR.
Can you @vchamarthi work with @vlad-perevezentsev to fix the full-range scalar draw also for the other BRNGs? |
What
Four fixes in the integer fills, all in
mkl_distributions.cpp.1.
randintcould returnhigh. In the masked branch of the 64-bit fill,rngwas incremented before the mask was derived from it:Fixed by leaving
rngas the inclusive maximum and usingrng + 1only whereviRngUniformwants an exclusive bound.The same surplus mask bit also forced roughly half of all draws to be rejected
and redrawn whenever
rng + 1was a power of two. With the mask correct,mask == rngmeans nothing can be rejected, so those requests now fill theresult buffer directly.
2. The integer fills handled at most two
MKL_INT_MAXchunks. They usedifwhere the other 43 chunking sites in the same file usewhile, so a largerrequest recursed once, fell through, and passed a count that overflows MKL's
32-bit
int.NDEBUGcompiles out theassert, so the error was swallowed andthe tail was never written.
3.
multinomialdecremented its chunk counter by elements, not draws(
len -= k * MKL_INT_MAX), skipping work and leaving the tail of large outputsunwritten.
4. The fills narrower than
intstaged the whole request.viRngUniformonly emits 32-bit integers, so a
uint8fill allocated four bytes per elementto produce one, then read that buffer back to narrow it: 40 MB allocated to
write 10 MB of output. Now staged one cache-resident tile at a time.
Numbers
Xeon Gold 6338, single thread, ns/element, min of 2 runs. Intel-channel
mkl/mkl-devel2026.1.0-intel_236, meson build. Baseline and patchedinstalled in separate conda envs.
uint64, power-of-two range 2**32uint64, power-of-two range 2**48uint64,iinfo.maxupper boundbool, 10Muint8, 10Muint16, 10Muint32, 10M (control)Streams change
Only power-of-two ranges at or above
INT_MAXchange, and only because the oldmask there was wrong. Of 12 seeded outputs compared, exactly one hash moved:
randint(0, 2**32, dtype='uint64'). The other 11 are bit-identical, includingevery narrow dtype,
uint32,standard_normalandmultinomial. Thenarrow-int tiling is bit-identical by construction: a VSL stream is sequential,
so tiling draws the same values in the same order.
Testing
master, so they are invariant guards rather than fitted to this change.
randint(0, 2**31), 8e9 draws perdtype: 3 hits on default
int, 3 onint64, 2 onuint64. After the fix, 0on all three.
6b7962f.Notes for reviewers
test_randint_in_bounds_fuzzonly useshighin{4, 8, 16}, all of whichtake the 32-bit path, so the masked 64-bit branch had no coverage. The new
test covers ranges at and above
INT_MAX.asserts the bound invariant rather than trying to reproduce it.
above two
MKL_INT_MAXchunks (~4.4 GB forbool, ~26 GB formultinomial). Worth a second opinion on whether thewhileconversion iscorrect in all eight fills.
irk_discrete_uniform_long_vecalready derived its mask from the inclusivemaximum, which is the pattern this restores in the 64-bit fill.