UNPKG

@huggingface/transformers

Version:

State-of-the-art Machine Learning for the web. Run 🤗 Transformers directly in your browser, with no need for a server!

226 lines (214 loc) • 8.12 kB
/** * Let there be order amidst the chaos. * * This file implements Mersenne Twister 19937, matching Python's `random` module exactly for reproducibility. * * ```javascript * import { random } from '@huggingface/transformers'; * * random.seed(42); * random.random(); // 0.6394267984578837 (matches Python) * random.gauss(0, 1); // normal-distributed value * random.choices(['a','b'], [3, 1]); // weighted pick * * const arr = [1, 2, 3, 4, 5]; * random.shuffle(arr); // in-place Fisher-Yates shuffle * * // Use a separate instance to avoid affecting the global state: * const rng = new random.Random(42); * rng.random(); // 0.6394267984578837 (same seed, independent state) * ``` * * **Note on Reproducibility:** * Similarly to the [Python random](https://docs.python.org/3/library/random.html#notes-on-reproducibility) * module, it is useful to be able to reproduce the sequences given by a pseudo-random number generator. * By reusing a seed value, the same sequence should be reproducible from run to run as long as multiple * threads or asynchronous operations are not running concurrently. * * @module utils/random */ import { apis } from '../env.js'; /** * Mersenne Twister 19937 PRNG, matching Python's `random.Random` class exactly. * * Each instance has its own independent state, so seeding one instance does not * affect any other instance or the global helper functions. * * @example * const rng1 = new Random(42); * const rng2 = new Random(42); * rng1.random() === rng2.random(); // true (same seed, independent state) */ export class Random { constructor(seed) { this._mt = new Uint32Array(624); this._idx = 625; this._gauss_next = null; this._random_fn = this.random.bind(this); this.seed(seed); } /** * Seeds this instance's PRNG. * * When called with a number, initializes the state deterministically from that value. * When called with no arguments (or `undefined`/`null`), seeds from OS entropy * via `crypto.getRandomValues`, matching Python's `random.seed()` behaviour. * * @param {number} [n] The seed value. Omit to seed from OS entropy. */ seed(n) { if (n === undefined || n === null) { if (apis.IS_CRYPTO_AVAILABLE) { const buf = new Uint32Array(1); crypto.getRandomValues(buf); n = buf[0]; } else { n = Date.now() >>> 0; } } const mt = this._mt; const u = (a, b) => Math.imul(a, b) >>> 0, key = []; for (let v = n || 0; v > 0; v = Math.floor(v / 0x100000000)) key.push(v & 0xffffffff); if (!key.length) key.push(0); mt[0] = 19650218; for (let k = 1; k < 624; ++k) mt[k] = (u(1812433253, mt[k - 1] ^ (mt[k - 1] >>> 30)) + k) >>> 0; let i = 1, j = 0; for (let k = Math.max(624, key.length); k > 0; --k, ++i, ++j) { if (i >= 624) { mt[0] = mt[623]; i = 1; } if (j >= key.length) j = 0; mt[i] = ((mt[i] ^ u(mt[i - 1] ^ (mt[i - 1] >>> 30), 1664525)) + key[j] + j) >>> 0; } for (let k = 623; k > 0; --k, ++i) { if (i >= 624) { mt[0] = mt[623]; i = 1; } mt[i] = ((mt[i] ^ u(mt[i - 1] ^ (mt[i - 1] >>> 30), 1566083941)) - i) >>> 0; } mt[0] = 0x80000000; this._idx = 624; this._gauss_next = null; } /** * Generates a random unsigned 32-bit integer. * * Performs the "twist" step when the state buffer is exhausted, * then applies the standard MT19937 tempering transform. * * @returns {number} A random integer in the range [0, 2^32 - 1]. */ _int32() { const mt = this._mt; if (this._idx >= 624) { for (let k = 0; k < 624; ++k) { // twist const y = (mt[k] & 0x80000000) | (mt[(k + 1) % 624] & 0x7fffffff); mt[k] = (mt[(k + 397) % 624] ^ (y >>> 1) ^ (y & 1 ? 0x9908b0df : 0)) >>> 0; } this._idx = 0; } let y = mt[this._idx++]; y ^= y >>> 11; y ^= (y << 7) & 0x9d2c5680; y ^= (y << 15) & 0xefc60000; y ^= y >>> 18; return y >>> 0; } /** * Generates a random floating-point number in the half-open interval [0, 1). * * Combines two 32-bit integers (using 53 bits of precision) to produce * a uniformly distributed double, matching Python's `random.random()`. * * @returns {number} A random float in [0, 1). */ random() { return ((this._int32() >>> 5) * 67108864.0 + (this._int32() >>> 6)) / 9007199254740992.0; } /** * Generates a random number from a Gaussian (normal) distribution. * * Uses the Box-Muller transform with a cached spare value, * matching Python's `random.gauss()` output for the same seed. * * @param {number} [mu=0] The mean of the distribution. * @param {number} [sigma=1] The standard deviation of the distribution. * @returns {number} A normally distributed random value. */ gauss(mu = 0, sigma = 1) { let z = this._gauss_next; this._gauss_next = null; if (z === null) { const x2pi = this.random() * 2 * Math.PI, g2rad = Math.sqrt(-2 * Math.log(1 - this.random())); z = Math.cos(x2pi) * g2rad; this._gauss_next = Math.sin(x2pi) * g2rad; } return mu + z * sigma; } /** * Shuffles an array in-place using the Fisher-Yates algorithm. * * Uses rejection sampling via `getrandbits`-style bit masking to ensure * a uniform distribution, matching Python's `random.shuffle()`. * * @param {any[]} arr The array to shuffle in-place. */ shuffle(arr) { for (let i = arr.length - 1; i > 0; --i) { const k = 32 - Math.clz32(i + 1); let r = this._int32() >>> (32 - k); while (r > i) r = this._int32() >>> (32 - k); const t = arr[i]; arr[i] = arr[r]; arr[r] = t; } } /** * Selects a single element from a weighted population. * * Matches Python's `random.choices(population, weights=weights, k=1)[0]` * * @param {any[]} population The array of items to choose from. * @param {number[]} weights An array of non-negative weights, one per population element. * @returns {*} A single randomly selected element from the population. */ choices(population, weights) { return population[_weightedIndexWith(this._random_fn, weights)]; } } /** * Returns a random index into `weights`, where each index's probability * is proportional to its weight. Uses a linear scan: O(n) time, O(1) memory. * * @param {() => number} randomFn A function returning a uniform random float in [0, 1). * @param {ArrayLike<number>} weights Non-negative weights. * @returns {number} A randomly selected index in `[0, weights.length)`. */ function _weightedIndexWith(randomFn, weights) { let sum = 0; for (let i = 0; i < weights.length; ++i) sum += weights[i]; let x = randomFn() * sum; for (let i = 0; i < weights.length; ++i) { x -= weights[i]; if (x < 0) return i; } return weights.length - 1; // floating-point guard } // Global default instance: mirrors the module-level functions in Python's `random` module. const _default = new Random(); export const random = Object.freeze({ Random, seed: _default.seed.bind(_default), random: _default.random.bind(_default), gauss: _default.gauss.bind(_default), shuffle: _default.shuffle.bind(_default), choices: _default.choices.bind(_default), }); // Private helper function, used by LogitsSampler, but not exported as part of the public API. export const _weightedIndex = (weights) => _weightedIndexWith(random.random, weights);