This is an automated email from the ASF dual-hosted git repository.

akaashrp pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git


The following commit(s) were added to refs/heads/main by this push:
     new 828d117ebd [Web] Expose RNG state for deterministic restore (#20034)
828d117ebd is described below

commit 828d117ebdb90e4474e5b7a9ead4e88b35865a58
Author: Akaash Parthasarathy <[email protected]>
AuthorDate: Tue Jul 21 17:31:05 2026 -0700

    [Web] Expose RNG state for deterministic restore (#20034)
    
    Expose tvmjs RNG state so callers can save and restore deterministic
    sampling progress. Adds the following:
    - `RNGState` as the exported state type for
    `LinearCongruentialGenerator`
    - `LinearCongruentialGenerator.getState()`
    - `LinearCongruentialGenerator.setState(state)`
    - `Instance.getRNGState()`
    - `Instance.setRNGState(state)`
---
 web/src/index.ts                        |  1 +
 web/src/runtime.ts                      | 21 ++++++++++++++++-
 web/src/support.ts                      | 32 +++++++++++++++++++++----
 web/tests/node/test_random_generator.js | 42 +++++++++++++++++++++++++++++++++
 4 files changed, 91 insertions(+), 5 deletions(-)

diff --git a/web/src/index.ts b/web/src/index.ts
index 4de7e09303..7cd05e7621 100644
--- a/web/src/index.ts
+++ b/web/src/index.ts
@@ -40,6 +40,7 @@ export {
 export { Disposable, LibraryProvider } from "./types";
 export { RPCServer } from "./rpc_server";
 export { assert, wasmPath, LinearCongruentialGenerator } from "./support";
+export type { RNGState } from "./support";
 export { detectGPUDevice, GPUDeviceDetectOutput } from "./webgpu";
 export { LRUCache, CacheState } from "./cache_state";
 export { createPolyfillWASI } from "./compact";
diff --git a/web/src/runtime.ts b/web/src/runtime.ts
index 078a0c7df2..9c83a4d9ac 100644
--- a/web/src/runtime.ts
+++ b/web/src/runtime.ts
@@ -23,7 +23,12 @@
 import { Pointer, PtrOffset, SizeOf, TypeIndex } from "./ctypes";
 import { Disposable } from "./types";
 import { Memory, CachedCallStack } from "./memory";
-import { assert, StringToUint8Array, LinearCongruentialGenerator } from 
"./support";
+import {
+  assert,
+  StringToUint8Array,
+  LinearCongruentialGenerator,
+  RNGState,
+} from "./support";
 import { Environment } from "./environment";
 import { AsyncifyHandler } from "./asyncify";
 import { FunctionInfo, WebGPUContext } from "./webgpu";
@@ -1542,6 +1547,20 @@ export class Instance implements Disposable {
     this.rng.setSeed(seed);
   }
 
+  /**
+   * Get the state of the internal LinearCongruentialGenerator.
+   */
+  getRNGState(): RNGState {
+    return this.rng.getState();
+  }
+
+  /**
+   * Restore the state of the internal LinearCongruentialGenerator.
+   */
+  setRNGState(state: RNGState): void {
+    this.rng.setState(state);
+  }
+
   /**
    * Sample index via top-p sampling.
    *
diff --git a/web/src/support.ts b/web/src/support.ts
index be85e85b7b..96cb1923b1 100644
--- a/web/src/support.ts
+++ b/web/src/support.ts
@@ -80,24 +80,26 @@ export function wasmPath(): string {
  * Linear congruential generator for random number generating that can be 
seeded.
  *
  * Follows the implementation of `include/tvm/support/random_engine.h`, which 
follows the
- * sepcification in 
https://en.cppreference.com/w/cpp/numeric/random/linear_congruential_engine.
+ * specification in 
https://en.cppreference.com/w/cpp/numeric/random/linear_congruential_engine.
  *
  * Note `Number.MAX_SAFE_INTEGER = 2^53 - 1`, and our intermediates are 
strictly less than 2^48.
  */
 
+export type RNGState = number;
+
 export class LinearCongruentialGenerator {
   readonly modulus: number;
   readonly multiplier: number;
   readonly increment: number;
-  // Always within the range (0, 2^32 - 1) non-inclusive; if 0, will forever 
generate 0.
+  // Always within the range (0, modulus) non-inclusive; if 0, will forever 
generate 0.
   private rand_state: number;
 
   /**
    * Set modulus, multiplier, and increment. Initialize `rand_state` according 
to `Date.now()`.
    */
   constructor() {
-    this.modulus = 2147483647;  // 2^32 - 1
-    this.multiplier = 48271;  // between 2^15 and 2^16
+    this.modulus = 2147483647; // 2^31 - 1
+    this.multiplier = 48271; // between 2^15 and 2^16
     this.increment = 0;
     this.setSeed(Date.now());
   }
@@ -119,6 +121,28 @@ export class LinearCongruentialGenerator {
     this.checkRandState();
   }
 
+  /**
+   * Get the current generator state for deterministic restoration.
+   */
+  getState(): RNGState {
+    return this.rand_state;
+  }
+
+  /**
+   * Restore a state returned by `getState()`.
+   */
+  setState(state: RNGState): void {
+    if (!Number.isInteger(state)) {
+      throw new Error("RNG state should be an integer.");
+    }
+    if (state <= 0 || state >= this.modulus) {
+      throw new Error(
+        `RNG state should be an integer in (0, ${this.modulus}).`,
+      );
+    }
+    this.rand_state = state;
+  }
+
   /**
    * Generate the next integer in the range (0, this.modulus) non-inclusive, 
updating `rand_state`.
    *
diff --git a/web/tests/node/test_random_generator.js 
b/web/tests/node/test_random_generator.js
index aefbf0f56f..0e8342202e 100644
--- a/web/tests/node/test_random_generator.js
+++ b/web/tests/node/test_random_generator.js
@@ -44,6 +44,28 @@ test("Test whether the same seed make two RNGs generate same 
results", () => {
   }
 });
 
+test("Restoring RNG state reproduces next random floats", () => {
+  const rng1 = new tvmjs.LinearCongruentialGenerator();
+  const rng2 = new tvmjs.LinearCongruentialGenerator();
+  rng1.setSeed(42);
+  for (let i = 0; i < 8; i++) {
+    rng1.randomFloat();
+  }
+
+  const state = rng1.getState();
+  const expected = [];
+  for (let i = 0; i < 16; i++) {
+    expected.push(rng1.randomFloat());
+  }
+
+  rng2.setState(state);
+  const restored = [];
+  for (let i = 0; i < expected.length; i++) {
+    restored.push(rng2.randomFloat());
+  }
+  expect(restored).toEqual(expected);
+});
+
 test("Test two RNGs with different seeds generate different results", () => {
   const rng1 = new tvmjs.LinearCongruentialGenerator();
   const rng2 = new tvmjs.LinearCongruentialGenerator();
@@ -67,3 +89,23 @@ test('Illegal argument to `setSeed()`', () => {
     rng1.setSeed(42.5);
   }).toThrow("Seed should be an integer.");
 });
+
+test("Illegal argument to `setState()`", () => {
+  const rng = new tvmjs.LinearCongruentialGenerator();
+
+  expect(() => {
+    rng.setState(undefined);
+  }).toThrow("RNG state should be an integer.");
+  expect(() => {
+    rng.setState({});
+  }).toThrow("RNG state should be an integer.");
+  expect(() => {
+    rng.setState(0);
+  }).toThrow("RNG state should be an integer in");
+  expect(() => {
+    rng.setState(rng.modulus);
+  }).toThrow("RNG state should be an integer in");
+  expect(() => {
+    rng.setState(1.5);
+  }).toThrow("RNG state should be an integer.");
+});

Reply via email to