-
-
Notifications
You must be signed in to change notification settings - Fork 307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
DOC: Using Zarr with GPU arrays #2574
Comments
@jhamman I can work on this section. I'd like to keep it brief for now and expand it once we integrate GPU compression and storage solutions since that's when users will see the full benefit of this pipeline. |
Sounds great @akshaysubr! The docs refactor landed this week so you could pick up and add something to the advanced topics user-guide section. |
I looked into this a bit today. I think there are ~3 things to implement / document:
I can put up a PR for the first one. But I wanted to confirm that using If that is, then does In [1]: import zarr
In [2]: store = zarr.storage.GpuMemoryStore()
In [3]: z = zarr.create_array(store=store, shape=(10000, 10000), chunks=(1000, 1000), dtype='int32')
/home/coder/zarr-python/src/zarr/core/buffer/gpu.py:99: UserWarning: Creating a zarr.buffer.gpu.Buffer with an array that does not support the __cuda_array_interface__ for zero-copy transfers, falling back to slow copy based path
return cls(buffer.as_array_like())
In [4]: z[:100, :100] = 1
/home/coder/zarr-python/src/zarr/core/buffer/gpu.py:99: UserWarning: Creating a zarr.buffer.gpu.Buffer with an array that does not support the __cuda_array_interface__ for zero-copy transfers, falling back to slow copy based path
return cls(buffer.as_array_like())
In [5]: type(z[:1000, :1000])
Out[5]: numpy.ndarray
In [6]: type(store._store_dict["c/0/0"])
Out[6]: zarr.core.buffer.gpu.Buffer I think that's not what we want. Using a Edit: Reading through #2473 (comment), this might not be ideal:
Since IIUC that will also put metadata in GPU memory. I'll do a bit more exploration. |
So does the current implementation only include |
Here's what currently works: import cupy as cp
import zarr.api.asynchronous
import zarr.core.buffer.cpu
import zarr.core.buffer.gpu
import zarr.storage
async def main() -> None:
src = cp.random.uniform(size=(100, 100)) # allocate on the device
store = zarr.storage.LocalStore("data.zarr")
z = await zarr.api.asynchronous.create_array(
store=store, name="a", shape=src.shape, chunks=(10, 10), dtype=src.dtype, overwrite=True
)
await z.setitem(
(slice(10), slice(10)), src[:10, :10], prototype=zarr.core.buffer.gpu.buffer_prototype
)
result = await z.getitem(
(slice(10), slice(10)), prototype=zarr.core.buffer.gpu.buffer_prototype
)
print(type(result)) # <class 'cupy.ndarray'>
if __name__ == "__main__":
import asyncio
asyncio.run(main()) There's also Notably, this uses the
I think kvikio will still be useful for users who want to want to read data directly into device memory (avoiding any intermediate allocations on the host before copying to the device). kvikio isn't currently compatible with zarr v3, but I'm hoping to look into that sometime. |
Yes, the prototype argument to from zarr.core.config import config
import zarr.core.buffer.gpu as gpu
# GPU buffers
gpu_buffer_mapping = {"buffer": gpu.Buffer, "ndbuffer": gpu.NDBuffer}
config.set(gpu_buffer_mapping) This will then set the |
Looks like zarr-python/src/zarr/core/array.py Line 2691 in e9772ac
|
The problem is that default_buffer_prototype also changes how the metadata is read, I think? |
Would it make sense to always use the numpy prototype for metadata? So essentially pass in |
That's what I'm leaning towards. The one thing I wanted to understand before suggesting that is the difference between |
I would actually like to see us implement a set of metadata specific methods on the store. I started down this path in #1851. |
I don't think this is an option. Some data buffers, those involving codecs before the @akshaysubr your suggestion works, with one other tweak to ensure that host memory is used when writing: diff --git a/src/zarr/core/array.py b/src/zarr/core/array.py
index 632e8221..75bb344b 100644
--- a/src/zarr/core/array.py
+++ b/src/zarr/core/array.py
@@ -38,6 +38,7 @@ from zarr.core.buffer import (
NDBuffer,
default_buffer_prototype,
)
+from zarr.core.buffer.cpu import buffer_prototype as cpu_buffer_prototype
from zarr.core.chunk_grids import RegularChunkGrid, _auto_partition, normalize_chunks
from zarr.core.chunk_key_encodings import (
ChunkKeyEncoding,
@@ -168,7 +169,7 @@ async def get_array_metadata(
if zarray_bytes is None:
raise FileNotFoundError(store_path)
elif zarr_format == 3:
- zarr_json_bytes = await (store_path / ZARR_JSON).get()
+ zarr_json_bytes = await (store_path / ZARR_JSON).get(prototype=cpu_buffer_prototype)
if zarr_json_bytes is None:
raise FileNotFoundError(store_path)
elif zarr_format is None:
@@ -1295,7 +1296,7 @@ class AsyncArray(Generic[T_ArrayMetadata]):
"""
Asynchronously save the array metadata.
"""
- to_save = metadata.to_buffer_dict(default_buffer_prototype())
+ to_save = metadata.to_buffer_dict(cpu_buffer_prototype)
awaitables = [set_or_delete(self.store_path / key, value) for key, value in to_save.items()]
if ensure_parents:
@@ -1307,7 +1308,7 @@ class AsyncArray(Generic[T_ArrayMetadata]):
[
(parent.store_path / key).set_if_not_exists(value)
for key, value in parent.metadata.to_buffer_dict(
- default_buffer_prototype()
+ cpu_buffer_prototype
).items()
]
) With that, we get import cupy as cp
import zarr.core.buffer.cpu
import zarr.core.buffer.gpu
import zarr.storage
from zarr.registry import (
register_buffer,
register_ndbuffer,
)
register_buffer(zarr.core.buffer.gpu.Buffer)
register_ndbuffer(zarr.core.buffer.gpu.NDBuffer)
zarr.config.set({"buffer": "zarr.core.buffer.gpu.Buffer"})
zarr.config.set({"ndbuffer": "zarr.core.buffer.gpu.NDBuffer"})
src = cp.random.uniform(size=(100, 100)) # allocate on the device
store = zarr.storage.MemoryStore()
z = zarr.create_array(
store=store, name="a", shape=src.shape, chunks=(10, 10), dtype=src.dtype, overwrite=True
)
z[:10, :10] = src[:10, :10] # errors
assert isinstance(store._store_dict["zarr.json"], zarr.core.buffer.cpu.Buffer), type(store._store_dict["zarr.json"])
assert type(z[:10, :10]) is cp.ndarray If we're OK with that (host memory for metadata, GPU memory for data if requested) then I can clean this up and make a PR.
I don't think we need GPU stuff to motivate that, but IMO it's probably worth doing. |
@TomAugspurger This looks good. Would it also be useful to add a helper method to set up the zarr configs for the GPU? Something like |
Yeah, I think so. It wasn't obvious to me why you might need to update both |
Describe the issue linked to the documentation
Zarr Python 3 has new support for working with CuPy arrays. The only problem is no one knows how to use it!
@akshaysubr / @madsbk / @jakirkham - would you be up for adding a section to the docs? See #2568 for a in-progress docs refactor that should make it easy to add a page on this.
Suggested fix for documentation
No response
The text was updated successfully, but these errors were encountered: