Device.count() (#15842)

This commit is contained in:
2026-04-21 16:46:38 -04:00
committed by GitHub
parent 1946ae8b51
commit 99a0debd62
14 changed files with 61 additions and 18 deletions
+1 -2
View File
@@ -1,7 +1,6 @@
from tinygrad import Tensor, Device, TinyJit, dtypes
from tinygrad.helpers import getenv
GPUS = getenv("GPUS", 4) # TODO: expose a way in tinygrad to access this
GPUS = Device[Device.DEFAULT].count()
N = 6144
@TinyJit
+9
View File
@@ -0,0 +1,9 @@
import unittest
from tinygrad import Device
class TestDeviceCount(unittest.TestCase):
def test_count(self):
self.assertGreaterEqual(Device[Device.DEFAULT].count(), 1)
if __name__ == "__main__":
unittest.main()
+4
View File
@@ -169,3 +169,7 @@ def cuGetErrorString(error: int, pStr) -> int:
# Set the pointer to point to our error string buffer
pStr._obj.value = ctypes.cast(buf, ctypes.POINTER(ctypes.c_char))
return orig_cuda.CUDA_SUCCESS
def cuDeviceGetCount(count) -> int:
count._obj.value = 1
return orig_cuda.CUDA_SUCCESS
+7
View File
@@ -38,6 +38,13 @@ class TestDevice(unittest.TestCase):
self.assertNotEqual(result.returncode, 0)
self.assertIn(b"did you mean: 'USB'", result.stderr)
@unittest.skipIf(Device.DEFAULT != "AMD", "only run on AMD")
def test_dev_id_out_of_range(self):
result = subprocess.run(['python3', '-c', 'from tinygrad import Device; Device[Device.DEFAULT]'],
env={**os.environ, "DEV":":99+AMD"}, capture_output=True)
self.assertNotEqual(result.returncode, 0)
self.assertIn(b"invalid visibility filter", result.stderr)
def test_lowercase_canonicalizes(self):
device = Device.DEFAULT
with Context(DEV=device.lower()):
+2 -2
View File
@@ -447,13 +447,13 @@ class TestDiskTensor(TempDirTestCase):
# get the DiskDevice and check internal state
disk_device = Device[f"DISK:{fn}"]
assert isinstance(disk_device, DiskDevice)
assert disk_device.count == 1
assert disk_device.refcount == 1
assert hasattr(disk_device, "mem")
first_fd = disk_device.fd
# create second tensor on same file - should reuse the device, not re-open
t2 = Tensor.empty(64, device=f"disk:{fn}", dtype=dtypes.uint8)
t2.to("CPU").realize()
assert disk_device.count == 2
assert disk_device.refcount == 2
assert disk_device.fd == first_fd, "file descriptor changed - file was unnecessarily re-opened"
# verify data is correct
np.testing.assert_equal(t1.numpy(), np.arange(128, dtype=np.uint8))
+6
View File
@@ -295,6 +295,12 @@ class Compiled:
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)
def count(self) -> int:
"""
Returns the number of physical accelerators available to the runtime.
"""
return 1
def synchronize(self):
"""
Synchronize all pending operations on the device.
+4
View File
@@ -695,6 +695,7 @@ class KFDIface:
kfd:FileIOInterface|None = None
event_page:HCQBuffer|None = None
gpus:list[FileIOInterface] = []
count:int = 0
def _is_usable_gpu(self, gpu_id):
with contextlib.suppress(OSError): return int(gpu_id.read()) != 0
@@ -710,6 +711,7 @@ class KFDIface:
KFDIface.kfd = FileIOInterface("/dev/kfd", os.O_RDWR)
gpus = [g for g in FileIOInterface(kfd_topo_path).listdir() if self._is_usable_gpu(FileIOInterface(f"{kfd_topo_path}/{g}/gpu_id"))]
KFDIface.gpus = hcq_filter_visible_devices(sorted(gpus, key=lambda x: int(x.split('/')[-1])), "AMD")
KFDIface.count = len(KFDIface.gpus)
if device_id >= len(KFDIface.gpus): raise RuntimeError(f"No device found for {device_id}. Requesting more devices than the system has?")
@@ -910,6 +912,8 @@ class PCIIface(PCIIfaceBase):
def device_fini(self): self.dev_impl.fini()
class USBIface(PCIIface):
count = 1 # TODO: support multiple usbgpus, see usb.py
def __init__(self, dev, dev_id): # pylint: disable=super-init-not-called
self.dev, self.pci_dev, self.vram_bar = dev, USBPCIDevice(dev.__class__.__name__[:2], f"usb:{dev_id}"), 0
self.dev_impl = AMDev(self.pci_dev)
+3 -1
View File
@@ -3,7 +3,7 @@ from typing import cast
import ctypes, functools, hashlib
from tinygrad.runtime.autogen import opencl as cl
from tinygrad.runtime.support import c
from tinygrad.helpers import to_char_p_p, from_mv, OSX, DEBUG, mv_address, suppress_finalizing
from tinygrad.helpers import to_char_p_p, from_mv, OSX, DEBUG, mv_address, suppress_finalizing, unwrap
from tinygrad.renderer.cstyle import OpenCLRenderer, IntelRenderer
from tinygrad.device import BufferSpec, LRUAllocator, Compiled, Compiler, CompileError
from tinygrad.dtype import ImageDType
@@ -121,6 +121,8 @@ class CLDevice(Compiled):
self.cl_compiler = CLCompiler(self, f"{hashlib.md5(self.device_name.encode() + self.driver_version.encode()).hexdigest()}")
super().__init__(device, CLAllocator(self), [renderer], functools.partial(CLProgram, self))
def count(self) -> int: return len(unwrap(self.device_ids))
def synchronize(self):
check(cl.clFinish(self.queue))
self.pending_copyin.clear()
+3 -1
View File
@@ -12,7 +12,7 @@ if (MOCKGPU:=DEV.target("CUDA").interface == "MOCK"): from test.mockgpu.cuda imp
def check(status):
if status != 0:
error = ctypes.string_at(init_c_var(ctypes.POINTER(ctypes.c_char), lambda x: cuda.cuGetErrorString(status, x))).decode()
error = ctypes.string_at(init_c_var(ctypes.POINTER(ctypes.c_char), lambda x: cuda.cuGetErrorString(status, ctypes.byref(x)))).decode()
raise RuntimeError(f"CUDA Error {status}, {error}")
def encode_args(args, vals) -> tuple[ctypes.Structure, ctypes.Array]:
@@ -120,6 +120,8 @@ class CUDADevice(Compiled):
super().__init__(device, CUDAAllocator(self), [CUDARenderer, PTXRenderer, NVCCRenderer], functools.partial(CUDAProgram, self),
None if MOCKGPU else CUDAGraph, arch=f"sm_{major.value}{minor.value}")
def count(self) -> int: return init_c_var(ctypes.c_int, lambda x: check(cuda.cuDeviceGetCount(ctypes.byref(x)))).value
def synchronize(self):
check(cuda.cuCtxSetCurrent(self.context))
check(cuda.cuCtxSynchronize())
+5 -5
View File
@@ -14,12 +14,12 @@ class DiskDevice(Compiled):
self.size: int|None = None
self.fd: int|None = None
self.count = 0
self.refcount = 0
super().__init__(device, DiskAllocator(self), [], None)
def _might_open(self, size:int):
assert self.size is None or size <= self.size, f"can't reopen Disk tensor with larger size, opened with {self.size}, tried to open with {size}"
if self.size is not None and hasattr(self, "mem"):
self.count += 1
self.refcount += 1
return
filename = self.device[len("disk:"):]
@@ -35,10 +35,10 @@ class DiskDevice(Compiled):
self.size = size
if hasattr(self.mem, 'madvise') and (hp := getattr(mmap, "MADV_HUGEPAGE", None)) is not None:
with contextlib.suppress(OSError): self.mem.madvise(hp) # some systems have transparent_hugepage disabled
self.count += 1
self.refcount += 1
def _might_close(self):
self.count -= 1
if self.count == 0:
self.refcount -= 1
if self.refcount == 0:
if self.fd is not None:
os.close(self.fd)
if hasattr(self, "mem"):
+3
View File
@@ -16,6 +16,9 @@ class HIPDevice(Compiled):
self.time_event_st, self.time_event_en = [init_c_var(hip.hipEvent_t, lambda x: hip.hipEventCreate(ctypes.byref(x), 0)) for _ in range(2)]
super().__init__(device, HIPAllocator(self), [HIPRenderer], functools.partial(HIPProgram, self), arch=self.arch)
def count(self) -> int: return init_c_var(ctypes.c_int, lambda x: check(hip.hipGetDeviceCount(x))).value
def synchronize(self):
check(hip.hipSetDevice(self.device_id))
check(hip.hipDeviceSynchronize())
+4 -2
View File
@@ -369,6 +369,7 @@ class NVKIface:
root = None
fd_ctl: FileIOInterface
fd_uvm: FileIOInterface
count: int
gpus_info: list|ctypes.Array = []
# TODO: Need a proper allocator for va addresses
@@ -396,7 +397,8 @@ class NVKIface:
with contextlib.suppress(RuntimeError): self.uvm(nv_gpu.UVM_MM_INITIALIZE, nv_gpu.UVM_MM_INITIALIZE_PARAMS(uvmFd=self.fd_uvm.fd), self.fd_uvm_2)
nv_iowr(NVKIface.fd_ctl, nv_gpu.NV_ESC_CARD_INFO, gpus_info:=(nv_gpu.nv_ioctl_card_info_t*64)())
NVKIface.gpus_info = hcq_filter_visible_devices(gpus_info, "NV")
NVKIface.gpus_info = hcq_filter_visible_devices([gi for gi in gpus_info if gi.valid], "NV")
NVKIface.count = len(NVKIface.gpus_info)
self.dev, self.device_id = dev, device_id
if self.device_id >= len(NVKIface.gpus_info) or not NVKIface.gpus_info[self.device_id].valid:
@@ -576,7 +578,7 @@ class PCIIface(PCIIfaceBase):
for _ in self.dev_impl.gsp.stat_q.read_resp(): pass
if self.dev_impl.is_err_state: raise RuntimeError("Device fault detected")
class MOCKNVKIface(NVKIface): pass
class MOCKNVKIface(NVKIface): count = 1
class NVDevice(HCQCompiled[NVSignal]):
def is_nvd(self) -> bool: return isinstance(self.iface, PCIIface)
+5 -1
View File
@@ -5,7 +5,7 @@ from dataclasses import replace
try: import fcntl # windows misses that
except ImportError: fcntl = None #type:ignore[assignment]
from tinygrad.helpers import DEV, PROFILE, getenv, to_mv, from_mv, cpu_profile, ProfileRangeEvent, select_first_inited, select_by_name, unwrap
from tinygrad.helpers import suppress_finalizing, TracingKey
from tinygrad.helpers import suppress_finalizing, pluralize, TracingKey
from tinygrad.device import Device, BufferSpec, Compiled, LRUAllocator, ProfileDeviceEvent, ProfileProgramEvent
from tinygrad.uop.ops import sym_infer, sint, UOp
from tinygrad.runtime.autogen import libc
@@ -62,6 +62,8 @@ if DEV.interface.startswith("MOCK"): from test.mockgpu.mockgpu import MockFileIO
def hcq_filter_visible_devices(devs, device):
assert (v:=getenv("HCQ_VISIBLE_DEVICES", "")) == "", f"HCQ_VISIBLE_DEVICES={v} is deprecated, use DEV={DEV.target(device, indices=v)} instead"
ids = [int(x) for x in DEV.target(device).indices.split(',') if x.strip()]
assert all(x < len(devs) for x in ids), f"invalid visibility filter: {ids} ({pluralize('device', len(devs))} available)"
return [devs[x] for x in ids] if (ids:=[int(x) for x in DEV.target(device).indices.split(',') if x.strip()]) else devs
SignalType = TypeVar('SignalType', bound='HCQSignal')
@@ -421,6 +423,8 @@ class HCQCompiled(Compiled, Generic[SignalType]):
if self._is_cpu(): HCQCompiled.cpu_devices.append(self)
def count(self) -> int: return self.iface.count if hasattr(self, 'iface') else 1
def synchronize(self, timeout:int|None=None):
if self.error_state is not None: raise self.error_state
if not hasattr(self, 'timeline_signal'): return
+5 -4
View File
@@ -1,6 +1,6 @@
from __future__ import annotations
import os, mmap, array, functools, ctypes, select, contextlib, dataclasses, sys, itertools, struct, socket, subprocess, time, enum, atexit
from tinygrad.helpers import round_up, getenv, OSX, temp, ceildiv, unwrap, fetch, system, _ensure_downloads_dir, DEBUG, flatten
from tinygrad.helpers import round_up, getenv, OSX, temp, ceildiv, unwrap, fetch, system, _ensure_downloads_dir, DEBUG, flatten, pluralize
from tinygrad.runtime.autogen import libc, pci, vfio, iokit, corefoundation
from tinygrad.runtime.support.hcq import FileIOInterface, MMIOInterface, HCQBuffer, hcq_filter_visible_devices
from tinygrad.runtime.support.memory import VirtMapping, AddrSpace, BumpAllocator
@@ -81,7 +81,8 @@ class _System:
return [(APLRemotePCIDevice if OSX else PCIDevice, x) for x in System.pci_scan_bus(vendor, devices, base_class)]
def pci_probe_device(self, device:str, dev_id:int, vendor:int, devices:tuple[tuple[int, tuple[int, ...]], ...], base_class:int|None=None):
cl, pcibus = hcq_filter_visible_devices(self.list_devices(vendor, devices, base_class), device)[dev_id]
try: cl, pcibus = (ds:=hcq_filter_visible_devices(self.list_devices(vendor, devices, base_class), device))[dev_id]
except IndexError: raise RuntimeError(f"{device}:{dev_id} does not exist ({pluralize('device', len(ds))} available)")
return cl(device[:2], pcibus)
def pci_setup_usb_bars(self, usb:CustomASM24Controller|ASM24Controller, gpu_bus:int, mem_base:int, pref_mem_base:int) -> dict[int, tuple[int, int]]:
@@ -244,11 +245,11 @@ class PCIIfaceBase:
def __init__(self, dev, dev_id, vendor, devices:tuple[tuple[int, tuple[int, ...]], ...], vram_bar, va_start, va_size,
dev_impl_t, base_class:int|None=None):
self.pci_dev = System.pci_probe_device(dev.__class__.__name__[:-6], dev_id, vendor, devices, base_class=base_class)
self.pci_dev = System.pci_probe_device(dn:=dev.__class__.__name__[:-6], dev_id, vendor, devices, base_class=base_class)
if self.is_local(): System.reserve_va(va_start, va_size)
with contextlib.suppress(Exception): self.pci_dev.resize_bar(vram_bar)
self.dev_impl = dev_impl_t(self.pci_dev)
self.dev, self.vram_bar = dev, vram_bar
self.dev, self.vram_bar, self.count = dev, vram_bar, len(hcq_filter_visible_devices(System.list_devices(vendor, devices, base_class), dn))
def alloc(self, size:int, host=False, uncached=False, cpu_access=False, contiguous=False, force_devmem=False, **kwargs) -> HCQBuffer:
should_use_sysmem = host or ((cpu_access if self.is_bar_small() else (uncached and cpu_access)) and not force_devmem)