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

ziheng 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 eae21b0  [PRNG] Add check to PRNG to make sure that unsigned integer 
arithmetic is wrapping (#7287)
eae21b0 is described below

commit eae21b087cbde53b99fe40b862be7c99dedc57d0
Author: Tristan Konolige <[email protected]>
AuthorDate: Wed Jan 27 10:05:27 2021 -0800

    [PRNG] Add check to PRNG to make sure that unsigned integer arithmetic is 
wrapping (#7287)
    
    * [PRNG] Add check to PRNG to make sure that unsigned integer arithmetic is 
wrapping
    
    * Add threefry_test_wrapping: a manual test for wrapping unsigned 
arithmetic.
    
    * fix test to actually run on the target
    
    * formatting
    
    * lint
---
 python/tvm/topi/random/kernel.py           | 62 +++++++++++++++++++++++++++++-
 tests/python/topi/python/test_topi_prng.py |  8 ++++
 2 files changed, 68 insertions(+), 2 deletions(-)

diff --git a/python/tvm/topi/random/kernel.py b/python/tvm/topi/random/kernel.py
index 576fd92..b21db37 100644
--- a/python/tvm/topi/random/kernel.py
+++ b/python/tvm/topi/random/kernel.py
@@ -17,6 +17,7 @@
 """Pseudorandom number kernels."""
 import tvm
 import tvm.topi
+import numpy as np
 from ... import tir
 from ...tir import ir_builder
 
@@ -135,7 +136,7 @@ def _threefry(
     assert key_buf.dtype == counter_buf.dtype, "threefry key and counter must 
be the same dtype"
 
     def mix(a, b, rotation):
-        x = a + b  # TODO should be wrapping
+        x = a + b  # wrapping
         y = x ^ ((b << rotation) | (b >> (iwidth - rotation)))
         return [x, y]
 
@@ -167,7 +168,7 @@ def _threefry(
     with irb.for_range(0, out_shape, name="l") as l:  # pylint: 
disable=invalid-name
         for i in range(nrounds // 4):
             for j in range(nwords):
-                out_buf[out_offset + l * nwords + j] += key_schedule(i, j)  # 
TODO wrapping
+                out_buf[out_offset + l * nwords + j] += key_schedule(i, j)  # 
wrapping
             for k in range(4):
                 for j in range(nwords // 2):
                     (
@@ -201,6 +202,13 @@ def threefry_generate(gen, out_shape):
     then a new generator is created by applying Threefry to the current key, 
path, and counter.
     This new generator will have a reset counter.
 
+    Warning
+    -------
+    Threeyfry requires that unsigned integer arithmetic wraps on overflow. 
Currently TVM has no
+    guarantee of this, so threefry contains an internal assert to check 
wrapping behavior. This
+    assert may or may not run depending on your platform, so it is recommended 
you run
+    :py:func:`threefry_test_wrapping` to verify wrapping behavior.
+
     Parameters
     ----------
     gen : Tensor[10, uint64]
@@ -234,6 +242,18 @@ def threefry_generate(gen, out_shape):
         out_gen = irb.buffer_ptr(out_gen_ptr)
         out_array = irb.buffer_ptr(out_array_ptr)
 
+        # Check that unsigned arithmetic wraps, as it is required to implement 
threefry correctly.
+        irb.emit(
+            tvm.tir.AssertStmt(
+                tvm.tir.const(0xFFFFFFFFFFFFFFFF, "uint64") + tvm.tir.const(1, 
"uint64")
+                == tvm.tir.const(0, "uint64"),
+                tvm.tir.StringImm(
+                    "Unsigned integer arithmetic is not wrapping, but threefry 
requires wrapping."
+                ),
+                tvm.tir.Evaluate(0),
+            )
+        )
+
         # Create a temporary array to hold the generator state we will use to 
create the random
         # numbers. We cannot use gen because we may need to update the key + 
path if there is not
         # enough room in the counter.
@@ -408,3 +428,41 @@ def threefry_split(gen):
         name="threefry_split",
         tag="threefry_split",
     )
+
+
+def threefry_test_wrapping(target, ctx):
+    """Test that unsigned arithmetic wraps on overflow.
+
+    Parameters
+    ----------
+    target : tvm.target.Target
+        Target to run against
+    ctx : tvm.runtime.TVMContext
+        Context to run the test on
+
+    Returns
+    -------
+    is_wrapping : bool
+        Whether or not unsigned integer arithmetic is wrapping for this 
target, context pair. True
+        indicates that threefry will work on this platform.
+    """
+    if isinstance(target, str):
+        target = tvm.target.Target(target)
+
+    def gen_ir(out_ptr):
+        irb = ir_builder.create()
+        out = irb.buffer_ptr(out_ptr)
+        if "gpu" in target.keys:
+            thread_x = tvm.te.thread_axis("threadIdx.x")
+            irb.scope_attr(thread_x, "thread_extent", 1)
+        out[0] = tvm.tir.const(0xFFFFFFFFFFFFFFFF, "uint64") + 
tvm.tir.const(1, "uint64")
+        return irb.get()
+
+    out = tvm.tir.decl_buffer((1,), dtype="uint64")
+    f = tvm.te.extern(
+        [out.shape], [], lambda ins, outs: gen_ir(outs[0]), dtype="uint64", 
out_buffers=[out]
+    )
+    s = tvm.te.create_schedule([f.op])
+    out_ary = tvm.nd.array(np.ones((1,), "uint64"), ctx)
+    tvm.build(s, [f], target=target)(out_ary)
+    return out_ary.asnumpy()[0] == 0
diff --git a/tests/python/topi/python/test_topi_prng.py 
b/tests/python/topi/python/test_topi_prng.py
index 43b0494..649e541 100644
--- a/tests/python/topi/python/test_topi_prng.py
+++ b/tests/python/topi/python/test_topi_prng.py
@@ -111,6 +111,14 @@ def test_threefry_generate(target, ctx):
     ).any(), "Overflowing counter with no space left in path should change 
state"
 
 
[email protected]_targets
+def test_threefry_wrapping(target, ctx):
+    assert tvm.topi.random.threefry_test_wrapping(
+        target, ctx
+    ), f"{target} does not suppport wrapping unsigned integer arithmetic"
+
+
 if __name__ == "__main__":
     test_threefry_split(tvm.target.Target("llvm"), tvm.context("cpu"))
     test_threefry_generate(tvm.target.Target("llvm"), tvm.context("cpu"))
+    test_threefry_wrapping(tvm.target.Target("llvm"), tvm.context("cpu"))

Reply via email to