Skip to content

AMDGPU modulo bug #749

Description

@hughperkins

To reproduce:

"""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")

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions