We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Can't create tensor with empty buffer in metal, gpu 
```
# ====== Works ======

In [21]: Tensor([], device="TORCH").realize()
Out[21]: <Tensor <LB (0,) dtypes.float op=buffer<0, dtypes.float> st=ShapeTracker(shape=(0,), views=[View(shape=(0,), strides=(1,), offset=0, mas
k=None, contiguous=True, shape_strides=((0, 1),))])> on TORCH with grad None>

In [28]: Tensor([], device="LLVM").realize()
Out[28]: <Tensor <LB (0,) dtypes.float op=buffer<0, dtypes.float> st=ShapeTracker(shape=(0,), views=[View(shape=(0,), strides=(1,), offset=0, mask=None, contiguous=True, shape_strides=((0, 1),))])> on LLVM with grad None>


# ====== Doesn't Work ======

In [25]: Tensor([], device="GPU").realize()
LogicError: create_buffer failed: INVALID_BUFFER_SIZE

In [26]: Tensor([], device="METAL").realize()
AttributeError: 'NoneType' object has no attribute 'contents'
```

Not sure if this works on CUDA since I don't have nvidiagpu


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `tinygrad/runtime/lib.py`
Content:
```
1 import ctypes
2 import numpy as np
3 from collections import defaultdict, deque
4 from typing import TypeVar, Type, Any, Dict, Deque, Tuple
5 from tinygrad.helpers import DType, dtypes, prod, GlobalCounters, ImageDType
6 
7 _T = TypeVar("_T")
8 class RawBuffer:  # pylint: disable=abstract-method
9   def __init__(self, size:int, dtype:DType, buf:Any=None, allocator:Any=None, **kwargs):
10     self.size: int = size
11     self.dtype: DType = dtype
12     self._buf = buf if buf is not None else (allocator.alloc(size, dtype, **kwargs) if allocator else None) # If buf is provided, use it. Otherwise try to allocate from the allocator.
13     self._memsz: int = size*dtype.itemsize
14     self._allocator = allocator
15     GlobalCounters.mem_used += self._memsz
16   def __del__(self):  # NOTE: if it fails on init (bad dtype), it won't have a _memsz
17     if hasattr(self, '_memsz'): GlobalCounters.mem_used -= self._memsz
18     if hasattr(self, '_allocator') and self._allocator: self._allocator.free(self._buf)
19   def __repr__(self): return f"buffer<{self.size}, {self.dtype}>"
20   @property
21   def key(self): return (self.size, self.dtype)
22 
23   # NOTE: this interface allows for 0 copy
24   @classmethod
25   def fromCPU(cls:Type[_T], x:np.ndarray) -> _T: raise NotImplementedError("must be implemented")
26   def toCPU(self) -> np.ndarray: raise NotImplementedError("must be implemented")
27 
28 class RawConst(RawBuffer): # pylint: disable=abstract-method
29   def __repr__(self): return f"const<{self._buf}, {self.dtype}>"
30   @property
31   def key(self): return (str(self._buf), self.dtype)
32 
33 def buf_is_kernel_arg(x) -> bool:
34   return x.realized is not None and x.realized.__class__ is not RawConst
35 
36 # --teenygrad--
37 
38 class RawBufferCopyIn(RawBuffer):
39   def _copyin(self, x:np.ndarray) -> None: raise NotImplementedError("must be implemented")
40 
41   @classmethod
42   def fromCPU(cls, x:np.ndarray, **kwargs):
43     ret = cls(prod(x.shape), dtypes.from_np(x.dtype), **kwargs)
44     ret._copyin(x)
45     return ret
46 
47 class RawBufferMapped(RawBufferCopyIn):
48   def _buffer(self) -> memoryview: raise NotImplementedError("must be implemented")
49   # NOTE: this metadata prevents the backing buffer from being freed. hack can be removed with PEP688
50   def toCPU(self) -> np.ndarray: return np.frombuffer(self._buffer(), dtype=np.dtype(self.dtype.np, metadata={"backing": self}))  # type: ignore
51   def _copyin(self, x:np.ndarray) -> None: np.copyto(self.toCPU(), x.reshape(-1))
52 
53 # this one is simple enough that i moved it out of the runtimes
54 class RawMallocBuffer(RawBufferMapped):
55   def __init__(self, size, dtype: DType): super().__init__(size, dtype, ({dtypes.float64:ctypes.c_double, dtypes.float32: ctypes.c_float, dtypes.float16: ctypes.c_int16, dtypes.bfloat16: ctypes.c_int16, dtypes.int8: ctypes.c_int8, dtypes.uint8: ctypes.c_uint8, dtypes.bool: ctypes.c_uint8, dtypes.int32: ctypes.c_int32, dtypes.uint32: ctypes.c_uint32, dtypes.int64: ctypes.c_int64, dtypes.uint64: ctypes.c_uint64}[dtype] * size)())
56   def _buffer(self): return memoryview(self._buf)
57 
58 class RawBufferCopyInOut(RawBufferCopyIn):
59   def _copyout(self, x:np.ndarray) -> None: raise NotImplementedError("must be implemented")
60 
61   def toCPU(self) -> np.ndarray:
62     x: np.ndarray = np.empty(self.size, dtype=self.dtype.np)
63     self._copyout(x)
64     return x
65 
66 class RawBufferTransfer(RawBuffer):
67   def _transfer(self, x) -> None: raise NotImplementedError("must be implemented")
68 
69   @classmethod
70   def transfer(cls, x, shape, dtype, **kwargs):
71     ret = cls(prod(shape), dtype, **kwargs)
72     ret._transfer(x)
73     return ret
74 
75 class LRUAllocator:
76   def __init__(self, dev_memsz=(4<<30)):
77     self.epoch = 0
78     self.free_space: Dict[Any, int] = defaultdict(lambda: dev_memsz)
79     self.buffer_info: Dict[Any, Tuple[int, DType, str]] = dict()
80     self.cached_buffers: Dict[Tuple[int, ...], Deque[Tuple[Any, int]]] = defaultdict(deque) # Cached buffer storage, splitted by type and size, newest first.
81     self.aging_order: Dict[Any, Deque[Tuple[Tuple[int, ...], int]]] = defaultdict(deque) # Keys of cached_buffers, ordered from oldest to newest updates.
82   def __del__(self):
83     for v in self.cached_buffers.values():
84       for buf, _ in v: self._free_buffer(buf)
85   def _cache_reuse_buffer(self, rawbufs: Deque[Tuple[Any, int]]): # The newest cached buffer is reused.
86     GlobalCounters.mem_cached -= self._underlying_buf_memsz(rawbufs[0][0])
87     return rawbufs.popleft()[0]
88   def _alloc_buffer(self, size, dtype, device, **kwargs):
89     self.free_space[device] -= size*dtype.itemsize
90     while len(self.aging_order[device]) and self.free_space[device] < 0: # When OOM removing lru buffers.
91       bucket, epoch = self.aging_order[device].popleft()
92       if self.cached_buffers[bucket] and self.cached_buffers[bucket][-1][1] == epoch: self._free_buffer(self.cached_buffers[bucket].pop()[0]) # Free cached buffer if it is still in cache.
93     newbuf = self._do_alloc(size, dtype, device, **kwargs)
94     self.buffer_info[newbuf] = (size, dtype, device)
95     return newbuf
96   def _free_buffer(self, buf_to_free):
97     self.free_space[self.buffer_info[buf_to_free][2]] += self._underlying_buf_memsz(buf_to_free)
98     GlobalCounters.mem_cached -= self._underlying_buf_memsz(buf_to_free)
99     self.buffer_info.pop(buf_to_free)
100     self._do_free(buf_to_free)
101   def alloc(self, size, dtype, device='0', **kwargs):
102     rawbufs = self.cached_buffers.get(self._cached_bufkey(size, dtype, device), None)
103     return self._cache_reuse_buffer(rawbufs) if rawbufs else self._alloc_buffer(size, dtype, device, **kwargs)
104   def free(self, buf): # free() just caches buffer. It might be freed later when OOM during allocation.
105     self.epoch += 1
106     size, dtype, device = self.buffer_info[buf]
107     self.cached_buffers[self._cached_bufkey(size, dtype, device)].appendleft((buf, self.epoch))
108     self.aging_order[device].append((self._cached_bufkey(size, dtype, device), self.epoch))
109     GlobalCounters.mem_cached += self._underlying_buf_memsz(buf)
110   def _underlying_buf_memsz(self, buf): return self.buffer_info[buf][0] * self.buffer_info[buf][1].itemsize
111   def _cached_bufkey(self, size, dtype, device) -> Tuple[int, ...]: return (device, size, dtype, dtype.shape) if isinstance(dtype, ImageDType) else (device, size, dtype) # Provides a key for reusing device buffers with identical keys.
112   def _do_alloc(self, size, dtype, device, **kwargs): raise NotImplementedError("must be implemented")
113   def _do_free(self, buf): pass
114
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/tinygrad/runtime/lib.py b/tinygrad/runtime/lib.py
--- a/tinygrad/runtime/lib.py
+++ b/tinygrad/runtime/lib.py
@@ -41,13 +41,13 @@
   @classmethod
   def fromCPU(cls, x:np.ndarray, **kwargs):
     ret = cls(prod(x.shape), dtypes.from_np(x.dtype), **kwargs)
-    ret._copyin(x)
+    if x.size > 0: ret._copyin(x)
     return ret
 
 class RawBufferMapped(RawBufferCopyIn):
   def _buffer(self) -> memoryview: raise NotImplementedError("must be implemented")
   # NOTE: this metadata prevents the backing buffer from being freed. hack can be removed with PEP688
-  def toCPU(self) -> np.ndarray: return np.frombuffer(self._buffer(), dtype=np.dtype(self.dtype.np, metadata={"backing": self}))  # type: ignore
+  def toCPU(self) -> np.ndarray: return np.frombuffer(self._buffer(), dtype=np.dtype(self.dtype.np, metadata={"backing": self}), count=self.size)  # type: ignore
   def _copyin(self, x:np.ndarray) -> None: np.copyto(self.toCPU(), x.reshape(-1))
 
 # this one is simple enough that i moved it out of the runtimes
@@ -60,7 +60,7 @@
 
   def toCPU(self) -> np.ndarray:
     x: np.ndarray = np.empty(self.size, dtype=self.dtype.np)
-    self._copyout(x)
+    if x.size > 0: self._copyout(x)
     return x
 
 class RawBufferTransfer(RawBuffer):
@@ -90,7 +90,7 @@
     while len(self.aging_order[device]) and self.free_space[device] < 0: # When OOM removing lru buffers.
       bucket, epoch = self.aging_order[device].popleft()
       if self.cached_buffers[bucket] and self.cached_buffers[bucket][-1][1] == epoch: self._free_buffer(self.cached_buffers[bucket].pop()[0]) # Free cached buffer if it is still in cache.
-    newbuf = self._do_alloc(size, dtype, device, **kwargs)
+    newbuf = self._do_alloc(max(1, size), dtype, device, **kwargs)
     self.buffer_info[newbuf] = (size, dtype, device)
     return newbuf
   def _free_buffer(self, buf_to_free):

{"golden_diff": "diff --git a/tinygrad/runtime/lib.py b/tinygrad/runtime/lib.py\n--- a/tinygrad/runtime/lib.py\n+++ b/tinygrad/runtime/lib.py\n@@ -41,13 +41,13 @@\n   @classmethod\n   def fromCPU(cls, x:np.ndarray, **kwargs):\n     ret = cls(prod(x.shape), dtypes.from_np(x.dtype), **kwargs)\n-    ret._copyin(x)\n+    if x.size > 0: ret._copyin(x)\n     return ret\n \n class RawBufferMapped(RawBufferCopyIn):\n   def _buffer(self) -> memoryview: raise NotImplementedError(\"must be implemented\")\n   # NOTE: this metadata prevents the backing buffer from being freed. hack can be removed with PEP688\n-  def toCPU(self) -> np.ndarray: return np.frombuffer(self._buffer(), dtype=np.dtype(self.dtype.np, metadata={\"backing\": self}))  # type: ignore\n+  def toCPU(self) -> np.ndarray: return np.frombuffer(self._buffer(), dtype=np.dtype(self.dtype.np, metadata={\"backing\": self}), count=self.size)  # type: ignore\n   def _copyin(self, x:np.ndarray) -> None: np.copyto(self.toCPU(), x.reshape(-1))\n \n # this one is simple enough that i moved it out of the runtimes\n@@ -60,7 +60,7 @@\n \n   def toCPU(self) -> np.ndarray:\n     x: np.ndarray = np.empty(self.size, dtype=self.dtype.np)\n-    self._copyout(x)\n+    if x.size > 0: self._copyout(x)\n     return x\n \n class RawBufferTransfer(RawBuffer):\n@@ -90,7 +90,7 @@\n     while len(self.aging_order[device]) and self.free_space[device] < 0: # When OOM removing lru buffers.\n       bucket, epoch = self.aging_order[device].popleft()\n       if self.cached_buffers[bucket] and self.cached_buffers[bucket][-1][1] == epoch: self._free_buffer(self.cached_buffers[bucket].pop()[0]) # Free cached buffer if it is still in cache.\n-    newbuf = self._do_alloc(size, dtype, device, **kwargs)\n+    newbuf = self._do_alloc(max(1, size), dtype, device, **kwargs)\n     self.buffer_info[newbuf] = (size, dtype, device)\n     return newbuf\n   def _free_buffer(self, buf_to_free):\n", "issue": "Can't create tensor with empty buffer in metal, gpu \n```\r\n# ====== Works ======\r\n\r\nIn [21]: Tensor([], device=\"TORCH\").realize()\r\nOut[21]: <Tensor <LB (0,) dtypes.float op=buffer<0, dtypes.float> st=ShapeTracker(shape=(0,), views=[View(shape=(0,), strides=(1,), offset=0, mas\r\nk=None, contiguous=True, shape_strides=((0, 1),))])> on TORCH with grad None>\r\n\r\nIn [28]: Tensor([], device=\"LLVM\").realize()\r\nOut[28]: <Tensor <LB (0,) dtypes.float op=buffer<0, dtypes.float> st=ShapeTracker(shape=(0,), views=[View(shape=(0,), strides=(1,), offset=0, mask=None, contiguous=True, shape_strides=((0, 1),))])> on LLVM with grad None>\r\n\r\n\r\n# ====== Doesn't Work ======\r\n\r\nIn [25]: Tensor([], device=\"GPU\").realize()\r\nLogicError: create_buffer failed: INVALID_BUFFER_SIZE\r\n\r\nIn [26]: Tensor([], device=\"METAL\").realize()\r\nAttributeError: 'NoneType' object has no attribute 'contents'\r\n```\r\n\r\nNot sure if this works on CUDA since I don't have nvidiagpu\r\n\n", "before_files": [{"content": "import ctypes\nimport numpy as np\nfrom collections import defaultdict, deque\nfrom typing import TypeVar, Type, Any, Dict, Deque, Tuple\nfrom tinygrad.helpers import DType, dtypes, prod, GlobalCounters, ImageDType\n\n_T = TypeVar(\"_T\")\nclass RawBuffer:  # pylint: disable=abstract-method\n  def __init__(self, size:int, dtype:DType, buf:Any=None, allocator:Any=None, **kwargs):\n    self.size: int = size\n    self.dtype: DType = dtype\n    self._buf = buf if buf is not None else (allocator.alloc(size, dtype, **kwargs) if allocator else None) # If buf is provided, use it. Otherwise try to allocate from the allocator.\n    self._memsz: int = size*dtype.itemsize\n    self._allocator = allocator\n    GlobalCounters.mem_used += self._memsz\n  def __del__(self):  # NOTE: if it fails on init (bad dtype), it won't have a _memsz\n    if hasattr(self, '_memsz'): GlobalCounters.mem_used -= self._memsz\n    if hasattr(self, '_allocator') and self._allocator: self._allocator.free(self._buf)\n  def __repr__(self): return f\"buffer<{self.size}, {self.dtype}>\"\n  @property\n  def key(self): return (self.size, self.dtype)\n\n  # NOTE: this interface allows for 0 copy\n  @classmethod\n  def fromCPU(cls:Type[_T], x:np.ndarray) -> _T: raise NotImplementedError(\"must be implemented\")\n  def toCPU(self) -> np.ndarray: raise NotImplementedError(\"must be implemented\")\n\nclass RawConst(RawBuffer): # pylint: disable=abstract-method\n  def __repr__(self): return f\"const<{self._buf}, {self.dtype}>\"\n  @property\n  def key(self): return (str(self._buf), self.dtype)\n\ndef buf_is_kernel_arg(x) -> bool:\n  return x.realized is not None and x.realized.__class__ is not RawConst\n\n# --teenygrad--\n\nclass RawBufferCopyIn(RawBuffer):\n  def _copyin(self, x:np.ndarray) -> None: raise NotImplementedError(\"must be implemented\")\n\n  @classmethod\n  def fromCPU(cls, x:np.ndarray, **kwargs):\n    ret = cls(prod(x.shape), dtypes.from_np(x.dtype), **kwargs)\n    ret._copyin(x)\n    return ret\n\nclass RawBufferMapped(RawBufferCopyIn):\n  def _buffer(self) -> memoryview: raise NotImplementedError(\"must be implemented\")\n  # NOTE: this metadata prevents the backing buffer from being freed. hack can be removed with PEP688\n  def toCPU(self) -> np.ndarray: return np.frombuffer(self._buffer(), dtype=np.dtype(self.dtype.np, metadata={\"backing\": self}))  # type: ignore\n  def _copyin(self, x:np.ndarray) -> None: np.copyto(self.toCPU(), x.reshape(-1))\n\n# this one is simple enough that i moved it out of the runtimes\nclass RawMallocBuffer(RawBufferMapped):\n  def __init__(self, size, dtype: DType): super().__init__(size, dtype, ({dtypes.float64:ctypes.c_double, dtypes.float32: ctypes.c_float, dtypes.float16: ctypes.c_int16, dtypes.bfloat16: ctypes.c_int16, dtypes.int8: ctypes.c_int8, dtypes.uint8: ctypes.c_uint8, dtypes.bool: ctypes.c_uint8, dtypes.int32: ctypes.c_int32, dtypes.uint32: ctypes.c_uint32, dtypes.int64: ctypes.c_int64, dtypes.uint64: ctypes.c_uint64}[dtype] * size)())\n  def _buffer(self): return memoryview(self._buf)\n\nclass RawBufferCopyInOut(RawBufferCopyIn):\n  def _copyout(self, x:np.ndarray) -> None: raise NotImplementedError(\"must be implemented\")\n\n  def toCPU(self) -> np.ndarray:\n    x: np.ndarray = np.empty(self.size, dtype=self.dtype.np)\n    self._copyout(x)\n    return x\n\nclass RawBufferTransfer(RawBuffer):\n  def _transfer(self, x) -> None: raise NotImplementedError(\"must be implemented\")\n\n  @classmethod\n  def transfer(cls, x, shape, dtype, **kwargs):\n    ret = cls(prod(shape), dtype, **kwargs)\n    ret._transfer(x)\n    return ret\n\nclass LRUAllocator:\n  def __init__(self, dev_memsz=(4<<30)):\n    self.epoch = 0\n    self.free_space: Dict[Any, int] = defaultdict(lambda: dev_memsz)\n    self.buffer_info: Dict[Any, Tuple[int, DType, str]] = dict()\n    self.cached_buffers: Dict[Tuple[int, ...], Deque[Tuple[Any, int]]] = defaultdict(deque) # Cached buffer storage, splitted by type and size, newest first.\n    self.aging_order: Dict[Any, Deque[Tuple[Tuple[int, ...], int]]] = defaultdict(deque) # Keys of cached_buffers, ordered from oldest to newest updates.\n  def __del__(self):\n    for v in self.cached_buffers.values():\n      for buf, _ in v: self._free_buffer(buf)\n  def _cache_reuse_buffer(self, rawbufs: Deque[Tuple[Any, int]]): # The newest cached buffer is reused.\n    GlobalCounters.mem_cached -= self._underlying_buf_memsz(rawbufs[0][0])\n    return rawbufs.popleft()[0]\n  def _alloc_buffer(self, size, dtype, device, **kwargs):\n    self.free_space[device] -= size*dtype.itemsize\n    while len(self.aging_order[device]) and self.free_space[device] < 0: # When OOM removing lru buffers.\n      bucket, epoch = self.aging_order[device].popleft()\n      if self.cached_buffers[bucket] and self.cached_buffers[bucket][-1][1] == epoch: self._free_buffer(self.cached_buffers[bucket].pop()[0]) # Free cached buffer if it is still in cache.\n    newbuf = self._do_alloc(size, dtype, device, **kwargs)\n    self.buffer_info[newbuf] = (size, dtype, device)\n    return newbuf\n  def _free_buffer(self, buf_to_free):\n    self.free_space[self.buffer_info[buf_to_free][2]] += self._underlying_buf_memsz(buf_to_free)\n    GlobalCounters.mem_cached -= self._underlying_buf_memsz(buf_to_free)\n    self.buffer_info.pop(buf_to_free)\n    self._do_free(buf_to_free)\n  def alloc(self, size, dtype, device='0', **kwargs):\n    rawbufs = self.cached_buffers.get(self._cached_bufkey(size, dtype, device), None)\n    return self._cache_reuse_buffer(rawbufs) if rawbufs else self._alloc_buffer(size, dtype, device, **kwargs)\n  def free(self, buf): # free() just caches buffer. It might be freed later when OOM during allocation.\n    self.epoch += 1\n    size, dtype, device = self.buffer_info[buf]\n    self.cached_buffers[self._cached_bufkey(size, dtype, device)].appendleft((buf, self.epoch))\n    self.aging_order[device].append((self._cached_bufkey(size, dtype, device), self.epoch))\n    GlobalCounters.mem_cached += self._underlying_buf_memsz(buf)\n  def _underlying_buf_memsz(self, buf): return self.buffer_info[buf][0] * self.buffer_info[buf][1].itemsize\n  def _cached_bufkey(self, size, dtype, device) -> Tuple[int, ...]: return (device, size, dtype, dtype.shape) if isinstance(dtype, ImageDType) else (device, size, dtype) # Provides a key for reusing device buffers with identical keys.\n  def _do_alloc(self, size, dtype, device, **kwargs): raise NotImplementedError(\"must be implemented\")\n  def _do_free(self, buf): pass\n", "path": "tinygrad/runtime/lib.py"}], "after_files": [{"content": "import ctypes\nimport numpy as np\nfrom collections import defaultdict, deque\nfrom typing import TypeVar, Type, Any, Dict, Deque, Tuple\nfrom tinygrad.helpers import DType, dtypes, prod, GlobalCounters, ImageDType\n\n_T = TypeVar(\"_T\")\nclass RawBuffer:  # pylint: disable=abstract-method\n  def __init__(self, size:int, dtype:DType, buf:Any=None, allocator:Any=None, **kwargs):\n    self.size: int = size\n    self.dtype: DType = dtype\n    self._buf = buf if buf is not None else (allocator.alloc(size, dtype, **kwargs) if allocator else None) # If buf is provided, use it. Otherwise try to allocate from the allocator.\n    self._memsz: int = size*dtype.itemsize\n    self._allocator = allocator\n    GlobalCounters.mem_used += self._memsz\n  def __del__(self):  # NOTE: if it fails on init (bad dtype), it won't have a _memsz\n    if hasattr(self, '_memsz'): GlobalCounters.mem_used -= self._memsz\n    if hasattr(self, '_allocator') and self._allocator: self._allocator.free(self._buf)\n  def __repr__(self): return f\"buffer<{self.size}, {self.dtype}>\"\n  @property\n  def key(self): return (self.size, self.dtype)\n\n  # NOTE: this interface allows for 0 copy\n  @classmethod\n  def fromCPU(cls:Type[_T], x:np.ndarray) -> _T: raise NotImplementedError(\"must be implemented\")\n  def toCPU(self) -> np.ndarray: raise NotImplementedError(\"must be implemented\")\n\nclass RawConst(RawBuffer): # pylint: disable=abstract-method\n  def __repr__(self): return f\"const<{self._buf}, {self.dtype}>\"\n  @property\n  def key(self): return (str(self._buf), self.dtype)\n\ndef buf_is_kernel_arg(x) -> bool:\n  return x.realized is not None and x.realized.__class__ is not RawConst\n\n# --teenygrad--\n\nclass RawBufferCopyIn(RawBuffer):\n  def _copyin(self, x:np.ndarray) -> None: raise NotImplementedError(\"must be implemented\")\n\n  @classmethod\n  def fromCPU(cls, x:np.ndarray, **kwargs):\n    ret = cls(prod(x.shape), dtypes.from_np(x.dtype), **kwargs)\n    if x.size > 0: ret._copyin(x)\n    return ret\n\nclass RawBufferMapped(RawBufferCopyIn):\n  def _buffer(self) -> memoryview: raise NotImplementedError(\"must be implemented\")\n  # NOTE: this metadata prevents the backing buffer from being freed. hack can be removed with PEP688\n  def toCPU(self) -> np.ndarray: return np.frombuffer(self._buffer(), dtype=np.dtype(self.dtype.np, metadata={\"backing\": self}), count=self.size)  # type: ignore\n  def _copyin(self, x:np.ndarray) -> None: np.copyto(self.toCPU(), x.reshape(-1))\n\n# this one is simple enough that i moved it out of the runtimes\nclass RawMallocBuffer(RawBufferMapped):\n  def __init__(self, size, dtype: DType): super().__init__(size, dtype, ({dtypes.float64:ctypes.c_double, dtypes.float32: ctypes.c_float, dtypes.float16: ctypes.c_int16, dtypes.bfloat16: ctypes.c_int16, dtypes.int8: ctypes.c_int8, dtypes.uint8: ctypes.c_uint8, dtypes.bool: ctypes.c_uint8, dtypes.int32: ctypes.c_int32, dtypes.uint32: ctypes.c_uint32, dtypes.int64: ctypes.c_int64, dtypes.uint64: ctypes.c_uint64}[dtype] * size)())\n  def _buffer(self): return memoryview(self._buf)\n\nclass RawBufferCopyInOut(RawBufferCopyIn):\n  def _copyout(self, x:np.ndarray) -> None: raise NotImplementedError(\"must be implemented\")\n\n  def toCPU(self) -> np.ndarray:\n    x: np.ndarray = np.empty(self.size, dtype=self.dtype.np)\n    if x.size > 0: self._copyout(x)\n    return x\n\nclass RawBufferTransfer(RawBuffer):\n  def _transfer(self, x) -> None: raise NotImplementedError(\"must be implemented\")\n\n  @classmethod\n  def transfer(cls, x, shape, dtype, **kwargs):\n    ret = cls(prod(shape), dtype, **kwargs)\n    ret._transfer(x)\n    return ret\n\nclass LRUAllocator:\n  def __init__(self, dev_memsz=(4<<30)):\n    self.epoch = 0\n    self.free_space: Dict[Any, int] = defaultdict(lambda: dev_memsz)\n    self.buffer_info: Dict[Any, Tuple[int, DType, str]] = dict()\n    self.cached_buffers: Dict[Tuple[int, ...], Deque[Tuple[Any, int]]] = defaultdict(deque) # Cached buffer storage, splitted by type and size, newest first.\n    self.aging_order: Dict[Any, Deque[Tuple[Tuple[int, ...], int]]] = defaultdict(deque) # Keys of cached_buffers, ordered from oldest to newest updates.\n  def __del__(self):\n    for v in self.cached_buffers.values():\n      for buf, _ in v: self._free_buffer(buf)\n  def _cache_reuse_buffer(self, rawbufs: Deque[Tuple[Any, int]]): # The newest cached buffer is reused.\n    GlobalCounters.mem_cached -= self._underlying_buf_memsz(rawbufs[0][0])\n    return rawbufs.popleft()[0]\n  def _alloc_buffer(self, size, dtype, device, **kwargs):\n    self.free_space[device] -= size*dtype.itemsize\n    while len(self.aging_order[device]) and self.free_space[device] < 0: # When OOM removing lru buffers.\n      bucket, epoch = self.aging_order[device].popleft()\n      if self.cached_buffers[bucket] and self.cached_buffers[bucket][-1][1] == epoch: self._free_buffer(self.cached_buffers[bucket].pop()[0]) # Free cached buffer if it is still in cache.\n    newbuf = self._do_alloc(max(1, size), dtype, device, **kwargs)\n    self.buffer_info[newbuf] = (size, dtype, device)\n    return newbuf\n  def _free_buffer(self, buf_to_free):\n    self.free_space[self.buffer_info[buf_to_free][2]] += self._underlying_buf_memsz(buf_to_free)\n    GlobalCounters.mem_cached -= self._underlying_buf_memsz(buf_to_free)\n    self.buffer_info.pop(buf_to_free)\n    self._do_free(buf_to_free)\n  def alloc(self, size, dtype, device='0', **kwargs):\n    rawbufs = self.cached_buffers.get(self._cached_bufkey(size, dtype, device), None)\n    return self._cache_reuse_buffer(rawbufs) if rawbufs else self._alloc_buffer(size, dtype, device, **kwargs)\n  def free(self, buf): # free() just caches buffer. It might be freed later when OOM during allocation.\n    self.epoch += 1\n    size, dtype, device = self.buffer_info[buf]\n    self.cached_buffers[self._cached_bufkey(size, dtype, device)].appendleft((buf, self.epoch))\n    self.aging_order[device].append((self._cached_bufkey(size, dtype, device), self.epoch))\n    GlobalCounters.mem_cached += self._underlying_buf_memsz(buf)\n  def _underlying_buf_memsz(self, buf): return self.buffer_info[buf][0] * self.buffer_info[buf][1].itemsize\n  def _cached_bufkey(self, size, dtype, device) -> Tuple[int, ...]: return (device, size, dtype, dtype.shape) if isinstance(dtype, ImageDType) else (device, size, dtype) # Provides a key for reusing device buffers with identical keys.\n  def _do_alloc(self, size, dtype, device, **kwargs): raise NotImplementedError(\"must be implemented\")\n  def _do_free(self, buf): pass\n", "path": "tinygrad/runtime/lib.py"}]}