From 57fbaa3d49b4de8292e4f093ddbd5117a9affcd6 Mon Sep 17 00:00:00 2001 From: Christopher Milan Date: Fri, 24 Apr 2026 20:30:16 -0700 Subject: [PATCH] amd: fallback to llvm when comgr is not available (#15914) --- test/null/test_device.py | 2 +- test/null/test_viz.py | 2 +- tinygrad/device.py | 2 +- tinygrad/helpers.py | 8 ++++---- tinygrad/renderer/amd/elf.py | 4 ++-- tinygrad/renderer/cstyle.py | 13 +++++-------- tinygrad/renderer/llvmir.py | 4 +++- tinygrad/renderer/nir.py | 5 +++-- tinygrad/runtime/ops_null.py | 10 +++++++--- tinygrad/runtime/support/c.py | 5 ++--- tinygrad/runtime/support/compiler_amd.py | 1 + 11 files changed, 30 insertions(+), 26 deletions(-) diff --git a/test/null/test_device.py b/test/null/test_device.py index 90f8588fef..e798d653f1 100644 --- a/test/null/test_device.py +++ b/test/null/test_device.py @@ -153,7 +153,7 @@ class TestDevVar(unittest.TestCase): self.assertEqual(DEV.target("CPU"), Target("CPU")) def test_dev_arch_override(self): - with Context(DEV="NULL:HIP:gfx1100"): + with Context(DEV="NULL::gfx1100"): self.assertEqual(Device["NULL"].renderer.target.arch, "gfx1100") class MockCompiler(Compiler): diff --git a/test/null/test_viz.py b/test/null/test_viz.py index aa7ddfc36a..1bf7c2df61 100644 --- a/test/null/test_viz.py +++ b/test/null/test_viz.py @@ -722,7 +722,7 @@ class TestCfg(unittest.TestCase): gidx = UOp.special(1, "gidx0") sink = UOp.sink(out.base, lidx, gidx, arg=KernelInfo(name=name)) return UOp(Ops.PROGRAM, src=(sink, UOp(Ops.DEVICE, arg="NULL"), UOp(Ops.LINEAR, src=tuple([UOp(Ops.INS, arg=x) for x in insts])))) - with Context(DEV=f"NULL:HIP:{self.arch}"): + with Context(DEV=f"NULL::{self.arch}"): out = Tensor.custom_kernel(Tensor.empty(1), fxn=fxn)[0] prg = out.schedule()[-1].lower().prg.p return amdgpu_cfg(prg.lib, self.arch) diff --git a/tinygrad/device.py b/tinygrad/device.py index 83f1dec41a..b0b670a3d8 100644 --- a/tinygrad/device.py +++ b/tinygrad/device.py @@ -293,7 +293,7 @@ class Compiled: f"{self.device}_{rn}=1 is deprecated, use DEV={self.device}:{rn} or {self.device}_CC={rn} instead" t = DEV.target(self.device.split(':')[0], **({"arch":self.arch} if self.arch else {})) return select_first_inited(select_by_name(self.renderers, self._renderer_name, t.renderer, f"{self.device} has no renderer {t.renderer!r}"), - f"No renderer for {self.device} is available", self.cached_renderer, target=t) + f"No renderer for {self.device} is available", self.cached_renderer, t) def count(self) -> int: """ diff --git a/tinygrad/helpers.py b/tinygrad/helpers.py index 2cc58cc68d..5cf6827c1c 100644 --- a/tinygrad/helpers.py +++ b/tinygrad/helpers.py @@ -133,13 +133,13 @@ def select_by_name(candidates:Sequence[T], get_name:Callable[...,str], query:str raise RuntimeError(err_msg + (f", did you mean: {m[0]!r}?" if (m:=difflib.get_close_matches(query, map(get_name, candidates))) else "")) return ret -def select_first_inited(candidates:Sequence[Callable[...,T]], err_msg:str, cache:dict|None=None, **kwargs): +def select_first_inited(candidates:Sequence[Callable[...,T]], err_msg:str, cache:dict|None=None, *args): excs = [] for typ in candidates: - if cache is not None and typ in cache: return cache[typ] + if cache is not None and (typ,) + args in cache: return cache[(typ,) + args] try: - x = typ(**kwargs) - if cache is not None: cache[typ] = x + x = typ(*args) + if cache is not None: cache[(typ,) + args] = x return x except Exception as e: excs.append(e) raise excs[0] if len(excs) == 1 else ExceptionGroup(err_msg + " is available", excs) diff --git a/tinygrad/renderer/amd/elf.py b/tinygrad/renderer/amd/elf.py index 4383d2a811..a2a5aa081d 100644 --- a/tinygrad/renderer/amd/elf.py +++ b/tinygrad/renderer/amd/elf.py @@ -11,7 +11,7 @@ from tinygrad.runtime.autogen.amd.rdna3.ins import s_code_end # same encoding as from tinygrad.runtime.autogen.amd.cdna.ins import s_nop as s_nop_cdna _arch_map = {"gfx9": "cdna", "gfx10": "rdna3", "gfx11": "rdna3", "gfx12": "rdna4"} -def assemble_linear(ctx, prg:UOp, lin:UOp) -> bytes: +def assemble_linear(prg:UOp, lin:UOp, arch:str) -> bytes: insts = [u.arg for u in lin.src] # ** scan for max vgpr/sgpr/accvgpr @@ -41,7 +41,7 @@ def assemble_linear(ctx, prg:UOp, lin:UOp) -> bytes: elif u.op is Ops.DEFINE_LOCAL: lds_size += u.ptrdtype.size * u.ptrdtype.base.itemsize elif u.op is Ops.SPECIAL and u.arg.startswith("gidx"): gids.add(int(u.arg[-1])) code_bytes = b"".join(inst.to_bytes() for inst in insts) - arch = next(v for k, v in _arch_map.items() if ctx.target.arch.startswith(k)) + arch = next(v for k, v in _arch_map.items() if arch.startswith(k)) is_cdna, is_rdna4 = arch == "cdna", arch == "rdna4" # ** pad text to ISA alignment diff --git a/tinygrad/renderer/cstyle.py b/tinygrad/renderer/cstyle.py index 03bf27d465..50ba73d340 100644 --- a/tinygrad/renderer/cstyle.py +++ b/tinygrad/renderer/cstyle.py @@ -473,10 +473,10 @@ class HIPRenderer(CStyleLanguage): def is_cdna(arch): return arch.split(":")[0] in {"gfx942", "gfx950"} @staticmethod def is_cdna4(arch): return arch.split(":")[0] == "gfx950" - def __init__(self, target:Target): # gfx942 => MI300, gfx1100 => RX 7900, gfx1201 => RX 9700 + def __init__(self, target:Target, use_hipcc=False): # gfx942 => MI300, gfx1100 => RX 7900, gfx1201 => RX 9700 super().__init__(target) - from tinygrad.runtime.support.compiler_amd import HIPCompiler - self.compiler, self.tensor_cores = HIPCompiler(target.arch), tc.get_amd(target.arch) + from tinygrad.runtime.support.compiler_amd import HIPCompiler, HIPCCCompiler + self.compiler, self.tensor_cores = (HIPCCCompiler if use_hipcc else HIPCompiler)(target.arch), tc.get_amd(target.arch) if not self.is_cdna4(target.arch): self.extra_matcher += pm_manual_bf16_cast + extra_pm if self.is_cdna(target.arch): self.string_rewrite = PatternMatcher([ @@ -512,7 +512,7 @@ class HIPRenderer(CStyleLanguage): def asm(self, prg:UOp, lin:UOp) -> bytes: from tinygrad.renderer.amd.elf import assemble_linear - return assemble_linear(self, prg, lin) + return assemble_linear(prg, lin, self.target.arch) def render_vector_prefix(self, dtype:DType) -> str: vec, scal = self.render_dtype(dtype), self.render_dtype(dtype.scalar()) @@ -560,10 +560,7 @@ class HIPRenderer(CStyleLanguage): return super().render_kernel(function_name, kernel, bufs, uops, prefix) class HIPCCRenderer(HIPRenderer): - def __init__(self, target:Target): - super().__init__(target) - from tinygrad.runtime.support.compiler_amd import HIPCCCompiler - self.compiler = HIPCCCompiler(target.arch) + def __init__(self, target:Target): super().__init__(target, use_hipcc=True) class QCOMCLRenderer(OpenCLRenderer): def __init__(self, target:Target): diff --git a/tinygrad/renderer/llvmir.py b/tinygrad/renderer/llvmir.py index 20078efbf5..e763701b9e 100644 --- a/tinygrad/renderer/llvmir.py +++ b/tinygrad/renderer/llvmir.py @@ -238,7 +238,9 @@ class AMDLLVMRenderer(LLVMRenderer): (UPat(Ops.LOG2, dtype=dtypes.double, src=(UPat.var("d"),)), xlog2), (UPat(Ops.EXP2, dtype=dtypes.double, src=(UPat.var("d"),)), xexp2), ]) - def asm(self, prg: UOp, lin: UOp) -> bytes: return HIPRenderer(self.target).asm(prg, lin) + def asm(self, prg: UOp, lin: UOp) -> bytes: + from tinygrad.renderer.amd.elf import assemble_linear + return assemble_linear(prg, lin, self.target.arch) def render(self, uops: list[UOp]) -> str: prefix = ["""define i8 @f32_to_fp8(float %val, i1 %is_bf8) { entry: %ival = bitcast float %val to i32\n %exp = and i32 %ival, 2139095040\n %is_special = icmp eq i32 %exp, 2139095040 diff --git a/tinygrad/renderer/nir.py b/tinygrad/renderer/nir.py index a82a6a8ad3..5bc9894a23 100644 --- a/tinygrad/renderer/nir.py +++ b/tinygrad/renderer/nir.py @@ -6,7 +6,7 @@ from tinygrad.renderer.cstyle import CUDARenderer, OpenCLRenderer from tinygrad.uop.ops import GroupOp, Ops, UOp, PatternMatcher, UPat, range_str from tinygrad.runtime.autogen import mesa from tinygrad.runtime.support.c import POINTER -import base64, ctypes, ctypes.util, struct, functools, inspect, contextlib, itertools +import base64, ctypes, ctypes.util, struct, functools, inspect, itertools def g(s:str): return getattr(mesa, s) def nsrc(d:mesa.nir_def) -> mesa.nir_src: return mesa.nir_src(ssa=ctypes.pointer(d)) @@ -169,9 +169,10 @@ class NIRRenderer(Renderer): self.compiler = fromimport("tinygrad.runtime.support.compiler_mesa", self.__class__.__name__.replace("Renderer", "Compiler"))(target.arch) if hasattr(self.compiler, "nir_options"): self.nir_options = self.compiler.nir_options mesa.glsl_type_singleton_init_or_ref() + self._deinit_types = True def __del__(self): - with contextlib.suppress(AttributeError): mesa.glsl_type_singleton_decref() + if getattr(self, "_deinit_types", False): mesa.glsl_type_singleton_decref() def param(self, b:mesa.nir_builder, x, sz:int) -> mesa.nir_def: raise NotImplementedError("needs param") def prerender(self, uops:list[UOp]): diff --git a/tinygrad/runtime/ops_null.py b/tinygrad/runtime/ops_null.py index b068e3a405..baac2fafe4 100644 --- a/tinygrad/runtime/ops_null.py +++ b/tinygrad/runtime/ops_null.py @@ -3,14 +3,18 @@ from tinygrad.device import Compiled, Allocator from tinygrad.engine.jit import MultiGraphRunner from tinygrad.renderer import Renderer, cstyle, nir, ptx, llvmir, wgsl from tinygrad.renderer.cstyle import CStyleLanguage -from tinygrad.uop.ops import Ops -from tinygrad.helpers import cpu_profile, getenv, NULL_ALLOW_COPYOUT +from tinygrad.uop.ops import UOp, Ops +from tinygrad.helpers import cpu_profile, getenv, dedup, NULL_ALLOW_COPYOUT class NullRenderer(CStyleLanguage): has_local = False float4 = "float4" barrier = "// BARRIER" code_for_op = {**CStyleLanguage.code_for_op, Ops.THREEFRY: lambda a,b,dtype: f"threefry({a},{b})", Ops.MAX: lambda a,b,dtype: f"max({a},{b})"} + def asm(self, prg: UOp, lin: UOp) -> bytes: + assert self.target.arch.startswith("gfx"), "only amd supports assembly" + from tinygrad.renderer.amd.elf import assemble_linear + return assemble_linear(prg, lin, self.target.arch) class NullProgram: def __init__(self, device:str, name:str, lib:bytes, *args, **kwargs): self.device, self.name = device, name @@ -35,4 +39,4 @@ class NullDevice(Compiled): "EMULATE is deprecated, use DEV=NULL:HIP:"+{"AMD":"gfx1100", "AMD_RDNA4":"gfx1201", "AMD_CDNA4":"gfx950"}.get(emu, "") renderers = [NullRenderer] + [r for m in [cstyle, nir, ptx, llvmir, wgsl] for r in m.__dict__.values() if inspect.isclass(r) and issubclass(r, Renderer)] - super().__init__(device, NullAllocator(self), renderers, functools.partial(NullProgram, device), NullGraph) + super().__init__(device, NullAllocator(self), dedup(renderers), functools.partial(NullProgram, device), NullGraph) diff --git a/tinygrad/runtime/support/c.py b/tinygrad/runtime/support/c.py index e0a2558185..bc5c9d2657 100644 --- a/tinygrad/runtime/support/c.py +++ b/tinygrad/runtime/support/c.py @@ -112,7 +112,7 @@ class DLL(ctypes.CDLL): if f.read(4) == b'\x7FELF': return str(l) def __init__(self, nm:str, paths:str|list[str], extra_paths=[], emsg="", **kwargs): - self.nm, self.emsg = nm, emsg + self.nm, self.emsg = nm, emsg or f"try setting {nm.upper()+'_PATH'}?" if (path:= DLL.findlib(nm, paths if isinstance(paths, list) else [paths], extra_paths if isinstance(extra_paths, list) else [extra_paths])): if DEBUG >= 3: print(f"loading {nm} from {path}") try: @@ -135,6 +135,5 @@ class DLL(ctypes.CDLL): return wrap def __getattr__(self, nm): - if self.nm not in self._loaded_: - raise AttributeError(f"failed to load library {self.nm}: " + (self.emsg or f"try setting {self.nm.upper()+'_PATH'}?")) + if self.nm not in self._loaded_: raise AttributeError(f"failed to load library {self.nm}: {self.emsg}") return super().__getattr__(nm) diff --git a/tinygrad/runtime/support/compiler_amd.py b/tinygrad/runtime/support/compiler_amd.py index 0b9fb212b8..88cc2c6771 100644 --- a/tinygrad/runtime/support/compiler_amd.py +++ b/tinygrad/runtime/support/compiler_amd.py @@ -92,6 +92,7 @@ def compile_hip(prg:str, arch="gfx1100", asm=False) -> bytes: class HIPCompiler(Compiler): def __init__(self, arch:str): + assert comgr.dll.nm in c.DLL._loaded_, f"comgr not available: {comgr.dll.emsg}" self.arch = arch super().__init__(f"compile_hip_{self.arch}") def compile(self, src:str) -> bytes: