mirror of
https://github.com/tinygrad/tinygrad.git
synced 2026-08-29 18:56:07 +00:00
Device.count() (#15842)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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()
|
||||
@@ -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
|
||||
|
||||
@@ -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()):
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,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()
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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,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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user