"""Standalone quadrants reproducer for AMDGPU modulo bug.
Bug: `float_x % field_int` where the int is loaded from a scalar field
returns the unreduced value instead of applying the modulo, on AMDGPU only.
float x = 180.0
int m = 180 (literal) → floor(x) % m = 0 ✓ (OK on AMDGPU)
int m = 180 (scalar field) → floor(x) % m = 180 ✗ (BUG on AMDGPU)
int m = 180 (scalar field) → int(floor(x)) % m = 0 ✓ (workaround)
Run on both backends:
python qd_modulo_bug.py --backend cpu
python qd_modulo_bug.py --backend gpu
"""
import argparse
import quadrants as qd
ap = argparse.ArgumentParser()
ap.add_argument("--backend", choices=["cpu", "cuda", "amdgpu"], required=True)
args = ap.parse_args()
backend_map = {"cpu": qd.cpu, "cuda": qd.cuda, "amdgpu": qd.amdgpu}
qd.init(arch=backend_map[args.backend], default_fp=qd.f32, default_ip=qd.i32)
# Scalar field holding the divisor (180) — mirrors how Genesis stores support_res.
mod = qd.field(dtype=qd.i32, shape=())
mod[None] = 180
out = qd.field(dtype=qd.i32, shape=(6,))
@qd.kernel
def repro(mod: qd.template(), out: qd.template()):
x = qd.f32(180.0)
m_const = qd.i32(180)
m_field = mod[None]
# (A) float % int-literal — OK on both
out[0] = qd.i32(qd.math.floor(x) % m_const)
# (B) float % int-from-field — BUG on AMDGPU
out[1] = qd.i32(qd.math.floor(x) % m_field)
# (C) int-cast-first, then % field — workaround
out[2] = qd.i32(qd.math.floor(x)) % m_field
# (D) int-cast-first, then % literal — OK
out[3] = qd.i32(qd.math.floor(x)) % m_const
# (E) sanity: field value itself
out[4] = m_field
# (F) pure int % int-from-field
out[5] = qd.i32(180) % m_field
repro(mod, out)
r = out.to_numpy().tolist()
print(f"backend={args.backend}")
print(f" (A) i32(floor(180.0) % 180_literal) = {r[0]} expected 0")
print(f" (B) i32(floor(180.0) % 180_field) = {r[1]} expected 0 <-- BUG if 180")
print(f" (C) i32(floor(180.0)) % 180_field = {r[2]} expected 0")
print(f" (D) i32(floor(180.0)) % 180_literal = {r[3]} expected 0")
print(f" (E) 180_field = {r[4]} expected 180")
print(f" (F) 180_literal % 180_field = {r[5]} expected 0")
assert r[0] == 0
assert r[2] == 0 and r[3] == 0
assert r[4] == 180
assert r[5] == 0
if r[1] != 0:
print(f"\n!!! BUG CONFIRMED on {args.backend}: (B) returned {r[1]}, should be 0")
else:
print(f"\nOK on {args.backend}: no bug, (B) returned 0 as expected")
To reproduce: