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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions