From c06013c11355aac2bddcbf7b5fce46f938fc0e4f Mon Sep 17 00:00:00 2001
From: dcherian
Date: Thu, 25 Aug 2022 14:15:24 -0600
Subject: [PATCH 01/21] Add kvikio blogpost
---
src/posts/xarray-kvikio/index.md | 138 +++++++++++++++++++++++++++++++
1 file changed, 138 insertions(+)
create mode 100644 src/posts/xarray-kvikio/index.md
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
new file mode 100644
index 00000000..ee304aeb
--- /dev/null
+++ b/src/posts/xarray-kvikio/index.md
@@ -0,0 +1,138 @@
+---
+title: "Enabling GPU-native analytics with Xarray and Kvikio"
+date: "2022-06-09"
+authors:
+ - name: Deepak Cherian
+ github: dcherian
+summary: "An experiment with direct-to-GPU reads from a Zarr store using Xarray."
+---
+
+## TLDR
+
+We [demonstrate](https://github.com/xarray-contrib/cupy-xarray/pull/10) registering an Xarray backend that reads data from a Zarr store directly to GPU memory as [CuPy arrays](https://cupy.dev) using the new [Kvikio library](https://docs.rapids.ai/api/kvikio/stable/) and [GPU Direct Storage](https://developer.nvidia.com/blog/gpudirect-storage/) technology.
+
+## Background
+
+### What is GPU Direct Storage
+
+Insert https://developer.nvidia.com/blog/wp-content/uploads/2019/08/GPUDirect-Fig-1-New.png somehow
+
+### What is Kvikio
+
+> kvikIO is a Python library providing bindings to cuFile, which enables GPUDirectStorage (GDS).
+
+For Xarray, the key bit is that kvikio exposes a zarr store [kvikio.zarr.GDSStore](https://docs.rapids.ai/api/kvikio/stable/api.html#zarr) that does all the hard work for us. Since Xarray knows how to read Zarr stores, we can adapt that in a new storage backend. And thanks to recent work funded by the Chan Zuckerberg Initiative, adding a [new backend](https://docs.xarray.dev/en/stable/internals/how-to-add-new-backend.html) is quite easy!
+
+## Integrating with Xarray
+
+Getting all this to work nicely requires using three in-progress pull requests that
+1. [Teach Zarr to handle alternative array classes](https://github.com/zarr-developers/zarr-python/pull/934)
+2. [Rewrite a small bit of Xarray to not cast all data to a numpy array after read from disk](https://github.com/pydata/xarray/pull/6874)
+3. [Make a backend that connects Xarray to Kvikio](https://github.com/xarray-contrib/cupy-xarray/pull/10)
+
+Writing the backend for Xarray was relatively easily. Most of the code was copied over from the existing Zarr backend. Most of the effort was in ensuring that dimension coordinates could be read in directly to host memory without raising an error. This is required because Xarrays creates `pandas.Index` objects for such variables. In the future, we could consider using `cudf.Index` instead to allow a fully GPU-backed Xarray object.
+
+## Usage
+
+Assuming you have all the pieces together (see [Appendix I]() and [Appendix II]() for step-by-step instructions), then using all this cool technology only requires adding `engine="kvikio"` to your `open_dataset` line (!)
+
+``` python
+import xarray as xr
+
+ds = xr.open_dataset("file.zarr", engine="kvikio", consolidated=False)
+```
+
+With this `ds.load()` will load directly to GPU memory and `ds` will now contain CuPy arrays.
+
+At present there are a few limitations:
+1. stores cannot be read with consolidated metadata, and
+2. compression is unsupported by the backend.
+
+## Quick demo
+
+First create an example uncompressed dataset to read from
+```
+import xarray as xr
+
+store = "./air-temperature.zarr"
+
+airt = xr.tutorial.open_dataset("air_temperature", engine="netcdf4")
+
+for var in airt.variables:
+ airt[var].encoding["compressor"] = None
+airt.to_zarr(store, mode="w", consolidated=True)
+```
+
+Now read
+```
+# consolidated must be False
+ds = xr.open_dataset(store, engine="kvikio", consolidated=False)
+ds
+```
+
+Now load a small subset
+``` python
+type(ds["air"].isel(time=0, lat=10).load().data)
+```
+```
+cupy._core.core.ndarray
+```
+
+Success!
+
+Xarray integrates [decently well](https://cupy-xarray.readthedocs.io/quickstart.html) with CuPy arrays so you should be able to test out analysis pipelines pretty seamlessly.
+
+## Cool demo
+
+We don't have a cool demo yet but are looking to develop one very soon!
+
+Reach out if you have ideas. We would love to hear from you.
+
+## Summary
+
+We demonstrate integrating the Kvikio library using Xarray's new backend entrypoints. With everything set up, simply adding `engine="kvikio"` enables direct-to-GPU reads from disk or over the network.
+
+## Appendix I : Step-by-step install instructions
+
+Wei Ji Leong (@weiji14) helpfully [provided steps](https://discourse.pangeo.io/t/favorite-way-to-go-from-netcdf-xarray-to-torch-tf-jax-et-al/2663/2) to get started on your machine:
+
+```
+# May need to install nvidia-gds first
+# https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#ubuntu-installation-common
+sudo apt install nvidia-gds
+
+git clone https://github.com/dcherian/cupy-xarray.git
+cd cupy-xarray
+
+mamba create --name cupy-xarray python=3.9 cupy=11.0 rapidsai-nightly::kvikio=22.10 jupyterlab=3.4.5 pooch=1.6.0 netcdf4=1.6.0 watermark=2.3.1
+mamba activate cupy-xarray
+python -m ipykernel install --user --name cupy-xarray
+
+# https://github.com/pydata/xarray/pull/6874
+pip install git+https://github.com/dcherian/xarray.git@kvikio
+# https://github.com/zarr-developers/zarr-python/pull/934
+pip install git+https://github.com/madsbk/zarr-python.git@cupy_support
+# https://github.com/xarray-contrib/cupy-xarray/pull/10
+git switch kvikio-entrypoint
+pip install --editable=.
+
+# Start jupyter lab
+jupyter lab --no-browser
+# Then open the docs/kvikio.ipynb notebook
+```
+
+## Appendix II : making sure GDS is working
+
+Scott Henderson (@scottyhq) pointed out that running `python kvikio/python/benchmarks/single-node-io.py` prints nice diagnostic information that lets you check whether GDS is set up. Note that on our system, we have "compatibility mode" enabled. So we don't see the benefits now but this was enough to wire everything up.
+```
+----------------------------------
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+ WARNING - KvikIO compat mode
+ libcufile.so not used
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+GPU | Quadro GP100 (dev #0)
+GPU Memory Total | 16.00 GiB
+BAR1 Memory Total | 256.00 MiB
+GDS driver | N/A (Compatibility Mode)
+GDS config.json | /etc/cufile.json
+```
From b8d357f3c4aaa88475dad598e9cdea3170c021fc Mon Sep 17 00:00:00 2001
From: dcherian
Date: Thu, 25 Aug 2022 14:34:15 -0600
Subject: [PATCH 02/21] edits
---
src/posts/xarray-kvikio/index.md | 59 ++++++++++++++++++++++++++------
1 file changed, 48 insertions(+), 11 deletions(-)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index ee304aeb..a30fd6f2 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -15,42 +15,52 @@ We [demonstrate](https://github.com/xarray-contrib/cupy-xarray/pull/10) register
### What is GPU Direct Storage
+Quoting [this nVIDIA blogpost](https://developer.nvidia.com/blog/gpudirect-storage/)
+
+> I/O, the process of loading data from storage to GPUs for processing, has historically been controlled by the CPU. As computation shifts from slower CPUs to faster GPUs, I/O becomes more of a bottleneck to overall application performance.
+> Just as GPUDirect RDMA (Remote Direct Memory Address) improved bandwidth and latency when moving data directly between a network interface card (NIC) and GPU memory, a new technology called GPUDirect Storage enables a direct data path between local or remote storage, like NVMe or NVMe over Fabric (NVMe-oF), and GPU memory.
+> Both GPUDirect RDMA and GPUDirect Storage avoid extra copies through a bounce buffer in the CPU’s memory and enable a direct memory access (DMA) engine near the NIC or storage to move data on a direct path into or out of GPU memory, all without burdening the CPU or GPU
+> For GPUDirect Storage, storage location doesn’t matter; it could be inside an enclosure, within the rack, or connected over the network.
+
Insert https://developer.nvidia.com/blog/wp-content/uploads/2019/08/GPUDirect-Fig-1-New.png somehow
### What is Kvikio
> kvikIO is a Python library providing bindings to cuFile, which enables GPUDirectStorage (GDS).
-For Xarray, the key bit is that kvikio exposes a zarr store [kvikio.zarr.GDSStore](https://docs.rapids.ai/api/kvikio/stable/api.html#zarr) that does all the hard work for us. Since Xarray knows how to read Zarr stores, we can adapt that in a new storage backend. And thanks to recent work funded by the Chan Zuckerberg Initiative, adding a [new backend](https://docs.xarray.dev/en/stable/internals/how-to-add-new-backend.html) is quite easy!
+For Xarray, the key bit is that kvikio exposes a zarr store [`kvikio.zarr.GDSStore`](https://docs.rapids.ai/api/kvikio/stable/api.html#zarr) that does all the hard work for us. Since Xarray knows how to read Zarr stores, we can adapt that to create a new storage backend that uses `kvikio`. And thanks to recent work funded by the Chan Zuckerberg Initiative, adding a [new backend](https://docs.xarray.dev/en/stable/internals/how-to-add-new-backend.html) is quite easy!
## Integrating with Xarray
Getting all this to work nicely requires using three in-progress pull requests that
+
1. [Teach Zarr to handle alternative array classes](https://github.com/zarr-developers/zarr-python/pull/934)
2. [Rewrite a small bit of Xarray to not cast all data to a numpy array after read from disk](https://github.com/pydata/xarray/pull/6874)
3. [Make a backend that connects Xarray to Kvikio](https://github.com/xarray-contrib/cupy-xarray/pull/10)
Writing the backend for Xarray was relatively easily. Most of the code was copied over from the existing Zarr backend. Most of the effort was in ensuring that dimension coordinates could be read in directly to host memory without raising an error. This is required because Xarrays creates `pandas.Index` objects for such variables. In the future, we could consider using `cudf.Index` instead to allow a fully GPU-backed Xarray object.
-## Usage
+## Usage
Assuming you have all the pieces together (see [Appendix I]() and [Appendix II]() for step-by-step instructions), then using all this cool technology only requires adding `engine="kvikio"` to your `open_dataset` line (!)
-``` python
+```python
import xarray as xr
ds = xr.open_dataset("file.zarr", engine="kvikio", consolidated=False)
```
-With this `ds.load()` will load directly to GPU memory and `ds` will now contain CuPy arrays.
+Notice that importing `cupy_xarray` was not needed. `cupy_xarray` uses entrypoints to register the Kvikio backend with Xarray.
+
+With this `ds.load()` will load directly to GPU memory and `ds` will now contain CuPy arrays. At present there are a few limitations:
-At present there are a few limitations:
-1. stores cannot be read with consolidated metadata, and
+1. stores cannot be read with consolidated metadata, and
2. compression is unsupported by the backend.
## Quick demo
First create an example uncompressed dataset to read from
+
```
import xarray as xr
@@ -64,23 +74,49 @@ airt.to_zarr(store, mode="w", consolidated=True)
```
Now read
+
```
# consolidated must be False
ds = xr.open_dataset(store, engine="kvikio", consolidated=False)
-ds
+ds.air
```
+```
+
+[3869000 values with dtype=float32]
+Coordinates:
+ * lat (lat) float32 75.0 72.5 70.0 67.5 65.0 ... 25.0 22.5 20.0 17.5 15.0
+ * lon (lon) float32 200.0 202.5 205.0 207.5 ... 322.5 325.0 327.5 330.0
+ * time (time) datetime64[ns] 2013-01-01 ... 2014-12-31T18:00:00
+Attributes:
+ GRIB_id: 11
+ GRIB_name: TMP
+ actual_range: [185.16000366210938, 322.1000061035156]
+ dataset: NMC Reanalysis
+ level_desc: Surface
+ long_name: 4xDaily Air temperature at sigma level 995
+ parent_stat: Other
+ precision: 2
+ statistic: Individual Obs
+ units: degK
+ var_desc: Air temperature
+```
+
+Note that we get Xarray's lazy backend arrays by default, and that dimension coordinate variables `lat`, `lon`, `time` were read. At this point this looks identical to what we get with a standard `xr.open_dataset(store, engine="zarr")` command.
+
Now load a small subset
-``` python
+
+```python
type(ds["air"].isel(time=0, lat=10).load().data)
```
+
```
cupy._core.core.ndarray
```
Success!
-Xarray integrates [decently well](https://cupy-xarray.readthedocs.io/quickstart.html) with CuPy arrays so you should be able to test out analysis pipelines pretty seamlessly.
+Xarray integrates [decently well](https://cupy-xarray.readthedocs.io/quickstart.html) with CuPy arrays so you should be able to test out analysis pipelines pretty easily.
## Cool demo
@@ -94,7 +130,7 @@ We demonstrate integrating the Kvikio library using Xarray's new backend entrypo
## Appendix I : Step-by-step install instructions
-Wei Ji Leong (@weiji14) helpfully [provided steps](https://discourse.pangeo.io/t/favorite-way-to-go-from-netcdf-xarray-to-torch-tf-jax-et-al/2663/2) to get started on your machine:
+[Wei Ji Leong](https://github.com/weiji14) helpfully [provided steps](https://discourse.pangeo.io/t/favorite-way-to-go-from-netcdf-xarray-to-torch-tf-jax-et-al/2663/2) to get started on your machine:
```
# May need to install nvidia-gds first
@@ -123,7 +159,8 @@ jupyter lab --no-browser
## Appendix II : making sure GDS is working
-Scott Henderson (@scottyhq) pointed out that running `python kvikio/python/benchmarks/single-node-io.py` prints nice diagnostic information that lets you check whether GDS is set up. Note that on our system, we have "compatibility mode" enabled. So we don't see the benefits now but this was enough to wire everything up.
+[Scott Henderson](https://github.com/scottyhq) pointed out that running `python kvikio/python/benchmarks/single-node-io.py` prints nice diagnostic information that lets you check whether GDS is set up. Note that on our system, we have "compatibility mode" enabled. So we don't see the benefits now but this was enough to wire everything up.
+
```
----------------------------------
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
From 089b3410c5f6dff04ce1219fb4cb0fd53688fd96 Mon Sep 17 00:00:00 2001
From: dcherian
Date: Thu, 25 Aug 2022 14:41:29 -0600
Subject: [PATCH 03/21] Proper date
---
src/posts/xarray-kvikio/index.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index a30fd6f2..d172da47 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -1,6 +1,6 @@
---
title: "Enabling GPU-native analytics with Xarray and Kvikio"
-date: "2022-06-09"
+date: "2022-08-25"
authors:
- name: Deepak Cherian
github: dcherian
From 05ba2be31d5056f971f8605c7aab72990f6f05fd Mon Sep 17 00:00:00 2001
From: dcherian
Date: Thu, 25 Aug 2022 14:42:56 -0600
Subject: [PATCH 04/21] Add image.
---
src/posts/xarray-kvikio/index.md | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index d172da47..a6eac030 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -22,7 +22,9 @@ Quoting [this nVIDIA blogpost](https://developer.nvidia.com/blog/gpudirect-stora
> Both GPUDirect RDMA and GPUDirect Storage avoid extra copies through a bounce buffer in the CPU’s memory and enable a direct memory access (DMA) engine near the NIC or storage to move data on a direct path into or out of GPU memory, all without burdening the CPU or GPU
> For GPUDirect Storage, storage location doesn’t matter; it could be inside an enclosure, within the rack, or connected over the network.
-Insert https://developer.nvidia.com/blog/wp-content/uploads/2019/08/GPUDirect-Fig-1-New.png somehow
+
+
+
### What is Kvikio
From 97d3d051ae7c68e071863c723932885d8ced4579 Mon Sep 17 00:00:00 2001
From: dcherian
Date: Thu, 25 Aug 2022 14:46:23 -0600
Subject: [PATCH 05/21] fix caps
---
src/posts/xarray-kvikio/index.md | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index a6eac030..0bcd2775 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -1,5 +1,5 @@
---
-title: "Enabling GPU-native analytics with Xarray and Kvikio"
+title: "Enabling GPU-native analytics with Xarray and kvikIO"
date: "2022-08-25"
authors:
- name: Deepak Cherian
@@ -9,7 +9,7 @@ summary: "An experiment with direct-to-GPU reads from a Zarr store using Xarray.
## TLDR
-We [demonstrate](https://github.com/xarray-contrib/cupy-xarray/pull/10) registering an Xarray backend that reads data from a Zarr store directly to GPU memory as [CuPy arrays](https://cupy.dev) using the new [Kvikio library](https://docs.rapids.ai/api/kvikio/stable/) and [GPU Direct Storage](https://developer.nvidia.com/blog/gpudirect-storage/) technology.
+We [demonstrate](https://github.com/xarray-contrib/cupy-xarray/pull/10) registering an Xarray backend that reads data from a Zarr store directly to GPU memory as [CuPy arrays](https://cupy.dev) using the new [kvikIO library](https://docs.rapids.ai/api/kvikio/stable/) and [GPU Direct Storage](https://developer.nvidia.com/blog/gpudirect-storage/) technology.
## Background
@@ -26,11 +26,11 @@ Quoting [this nVIDIA blogpost](https://developer.nvidia.com/blog/gpudirect-stora
-### What is Kvikio
+### What is kvikIO
> kvikIO is a Python library providing bindings to cuFile, which enables GPUDirectStorage (GDS).
-For Xarray, the key bit is that kvikio exposes a zarr store [`kvikio.zarr.GDSStore`](https://docs.rapids.ai/api/kvikio/stable/api.html#zarr) that does all the hard work for us. Since Xarray knows how to read Zarr stores, we can adapt that to create a new storage backend that uses `kvikio`. And thanks to recent work funded by the Chan Zuckerberg Initiative, adding a [new backend](https://docs.xarray.dev/en/stable/internals/how-to-add-new-backend.html) is quite easy!
+For Xarray, the key bit is that kvikIO exposes a zarr store [`kvikio.zarr.GDSStore`](https://docs.rapids.ai/api/kvikio/stable/api.html#zarr) that does all the hard work for us. Since Xarray knows how to read Zarr stores, we can adapt that to create a new storage backend that uses `kvikio`. And thanks to recent work funded by the Chan Zuckerberg Initiative, creating and registering a [new backend](https://docs.xarray.dev/en/stable/internals/how-to-add-new-backend.html) is quite easy!
## Integrating with Xarray
@@ -38,7 +38,7 @@ Getting all this to work nicely requires using three in-progress pull requests t
1. [Teach Zarr to handle alternative array classes](https://github.com/zarr-developers/zarr-python/pull/934)
2. [Rewrite a small bit of Xarray to not cast all data to a numpy array after read from disk](https://github.com/pydata/xarray/pull/6874)
-3. [Make a backend that connects Xarray to Kvikio](https://github.com/xarray-contrib/cupy-xarray/pull/10)
+3. [Make a backend that connects Xarray to kvikIO](https://github.com/xarray-contrib/cupy-xarray/pull/10)
Writing the backend for Xarray was relatively easily. Most of the code was copied over from the existing Zarr backend. Most of the effort was in ensuring that dimension coordinates could be read in directly to host memory without raising an error. This is required because Xarrays creates `pandas.Index` objects for such variables. In the future, we could consider using `cudf.Index` instead to allow a fully GPU-backed Xarray object.
@@ -52,7 +52,7 @@ import xarray as xr
ds = xr.open_dataset("file.zarr", engine="kvikio", consolidated=False)
```
-Notice that importing `cupy_xarray` was not needed. `cupy_xarray` uses entrypoints to register the Kvikio backend with Xarray.
+Notice that importing `cupy_xarray` was not needed. `cupy_xarray` uses [entrypoints](https://packaging.python.org/en/latest/specifications/entry-points/) to register the kvikIO backend with Xarray.
With this `ds.load()` will load directly to GPU memory and `ds` will now contain CuPy arrays. At present there are a few limitations:
@@ -128,7 +128,7 @@ Reach out if you have ideas. We would love to hear from you.
## Summary
-We demonstrate integrating the Kvikio library using Xarray's new backend entrypoints. With everything set up, simply adding `engine="kvikio"` enables direct-to-GPU reads from disk or over the network.
+We demonstrate integrating the kvikIO library using Xarray's new backend entrypoints. With everything set up, simply adding `engine="kvikio"` enables direct-to-GPU reads from disk or over the network.
## Appendix I : Step-by-step install instructions
From 44d46c7d4161411eee7dcb49cfceb068a2364fbc Mon Sep 17 00:00:00 2001
From: dcherian
Date: Thu, 25 Aug 2022 14:55:06 -0600
Subject: [PATCH 06/21] Add funding credit
---
src/posts/xarray-kvikio/index.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index 0bcd2775..e7d69381 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -130,6 +130,10 @@ Reach out if you have ideas. We would love to hear from you.
We demonstrate integrating the kvikIO library using Xarray's new backend entrypoints. With everything set up, simply adding `engine="kvikio"` enables direct-to-GPU reads from disk or over the network.
+## Acknowledgments
+
+My time on this project was funded by NASA-OSTFL 80NSSC22K0345 "Enhancing analysis of NASA data with the open-source Python Xarray Library"
+
## Appendix I : Step-by-step install instructions
[Wei Ji Leong](https://github.com/weiji14) helpfully [provided steps](https://discourse.pangeo.io/t/favorite-way-to-go-from-netcdf-xarray-to-torch-tf-jax-et-al/2663/2) to get started on your machine:
From c1b09c254b6b2a2ad9ab8b008476dbe88b515dcb Mon Sep 17 00:00:00 2001
From: Deepak Cherian
Date: Thu, 25 Aug 2022 15:15:02 -0600
Subject: [PATCH 07/21] Update src/posts/xarray-kvikio/index.md
Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com>
---
src/posts/xarray-kvikio/index.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index e7d69381..29a508aa 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -28,7 +28,7 @@ Quoting [this nVIDIA blogpost](https://developer.nvidia.com/blog/gpudirect-stora
### What is kvikIO
-> kvikIO is a Python library providing bindings to cuFile, which enables GPUDirectStorage (GDS).
+> [kvikIO](https://github.com/rapidsai/kvikio) is a Python library providing bindings to [cuFile](https://docs.nvidia.com/gpudirect-storage/api-reference-guide/index.html#introduction), which enables GPUDirectStorage (GDS).
For Xarray, the key bit is that kvikIO exposes a zarr store [`kvikio.zarr.GDSStore`](https://docs.rapids.ai/api/kvikio/stable/api.html#zarr) that does all the hard work for us. Since Xarray knows how to read Zarr stores, we can adapt that to create a new storage backend that uses `kvikio`. And thanks to recent work funded by the Chan Zuckerberg Initiative, creating and registering a [new backend](https://docs.xarray.dev/en/stable/internals/how-to-add-new-backend.html) is quite easy!
From 804c2621088069d87694694ab149462fac30c336 Mon Sep 17 00:00:00 2001
From: Deepak Cherian
Date: Thu, 25 Aug 2022 15:15:09 -0600
Subject: [PATCH 08/21] Update src/posts/xarray-kvikio/index.md
Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com>
---
src/posts/xarray-kvikio/index.md | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index 29a508aa..79c05d64 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -22,9 +22,7 @@ Quoting [this nVIDIA blogpost](https://developer.nvidia.com/blog/gpudirect-stora
> Both GPUDirect RDMA and GPUDirect Storage avoid extra copies through a bounce buffer in the CPU’s memory and enable a direct memory access (DMA) engine near the NIC or storage to move data on a direct path into or out of GPU memory, all without burdening the CPU or GPU
> For GPUDirect Storage, storage location doesn’t matter; it could be inside an enclosure, within the rack, or connected over the network.
-
-
-
+![Diagram showing standard path between GPU memory and CPU memory on the left, versus a direct data path between GPU memory and storage on the right](https://developer.nvidia.com/blog/wp-content/uploads/2019/08/GPUDirect-Fig-1-New.png)
### What is kvikIO
From 4a09f3488e3562fad779f68813a89dfe098f08f2 Mon Sep 17 00:00:00 2001
From: Deepak Cherian
Date: Thu, 25 Aug 2022 15:15:19 -0600
Subject: [PATCH 09/21] Update src/posts/xarray-kvikio/index.md
Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com>
---
src/posts/xarray-kvikio/index.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index 79c05d64..9d89242e 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -28,7 +28,7 @@ Quoting [this nVIDIA blogpost](https://developer.nvidia.com/blog/gpudirect-stora
> [kvikIO](https://github.com/rapidsai/kvikio) is a Python library providing bindings to [cuFile](https://docs.nvidia.com/gpudirect-storage/api-reference-guide/index.html#introduction), which enables GPUDirectStorage (GDS).
-For Xarray, the key bit is that kvikIO exposes a zarr store [`kvikio.zarr.GDSStore`](https://docs.rapids.ai/api/kvikio/stable/api.html#zarr) that does all the hard work for us. Since Xarray knows how to read Zarr stores, we can adapt that to create a new storage backend that uses `kvikio`. And thanks to recent work funded by the Chan Zuckerberg Initiative, creating and registering a [new backend](https://docs.xarray.dev/en/stable/internals/how-to-add-new-backend.html) is quite easy!
+For Xarray, the key bit is that kvikIO exposes a zarr store [`kvikio.zarr.GDSStore`](https://docs.rapids.ai/api/kvikio/stable/api.html#zarr) that does all the hard work for us. Since Xarray knows how to read Zarr stores, we can adapt it to create a new storage backend that uses `kvikio`. And thanks to recent work funded by the [Chan Zuckerberg Initiative](https://xarray.dev/blog/czi-eoss-grant-conclusion), creating and registering a [new backend](https://docs.xarray.dev/en/stable/internals/how-to-add-new-backend.html) is quite easy!
## Integrating with Xarray
From 0f579a6f629b99fe2ed6e78c7e71840431353591 Mon Sep 17 00:00:00 2001
From: Deepak Cherian
Date: Thu, 25 Aug 2022 15:16:24 -0600
Subject: [PATCH 10/21] Apply suggestions from code review
Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com>
---
src/posts/xarray-kvikio/index.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index 9d89242e..ab140828 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -54,8 +54,8 @@ Notice that importing `cupy_xarray` was not needed. `cupy_xarray` uses [entrypoi
With this `ds.load()` will load directly to GPU memory and `ds` will now contain CuPy arrays. At present there are a few limitations:
-1. stores cannot be read with consolidated metadata, and
-2. compression is unsupported by the backend.
+1. Zarr stores cannot be read with consolidated metadata, and
+2. compression is unsupported by the kvikIO backend.
## Quick demo
@@ -134,7 +134,7 @@ My time on this project was funded by NASA-OSTFL 80NSSC22K0345 "Enhancing analys
## Appendix I : Step-by-step install instructions
-[Wei Ji Leong](https://github.com/weiji14) helpfully [provided steps](https://discourse.pangeo.io/t/favorite-way-to-go-from-netcdf-xarray-to-torch-tf-jax-et-al/2663/2) to get started on your machine:
+[Wei Ji Leong](https://github.com/weiji14) helpfully [provided steps](https://github.com/xarray-contrib/cupy-xarray/pull/10#issuecomment-1218374773) to get started on your machine:
```
# May need to install nvidia-gds first
From f98f3b505fb878e37dcaeb6ce6e9a43a46893d38 Mon Sep 17 00:00:00 2001
From: Deepak Cherian
Date: Thu, 25 Aug 2022 15:18:46 -0600
Subject: [PATCH 11/21] Update src/posts/xarray-kvikio/index.md
---
src/posts/xarray-kvikio/index.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index ab140828..363c0ab3 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -38,7 +38,7 @@ Getting all this to work nicely requires using three in-progress pull requests t
2. [Rewrite a small bit of Xarray to not cast all data to a numpy array after read from disk](https://github.com/pydata/xarray/pull/6874)
3. [Make a backend that connects Xarray to kvikIO](https://github.com/xarray-contrib/cupy-xarray/pull/10)
-Writing the backend for Xarray was relatively easily. Most of the code was copied over from the existing Zarr backend. Most of the effort was in ensuring that dimension coordinates could be read in directly to host memory without raising an error. This is required because Xarrays creates `pandas.Index` objects for such variables. In the future, we could consider using `cudf.Index` instead to allow a fully GPU-backed Xarray object.
+Writing the backend for Xarray was relatively easy with most of the code copied over or inherited from the existing Zarr backend. We did have to ensure that dimension coordinates could be read in directly to host memory without raising an error (by default kvikIO loads all data to device memory). This is required because Xarrays creates `pandas.Index` objects for such variables. In the future, we could consider using `cudf.Index` instead to allow a fully GPU-backed Xarray object.
## Usage
From 6d46410cc4a804f6414b27865d789ba618a48b29 Mon Sep 17 00:00:00 2001
From: Deepak Cherian
Date: Thu, 25 Aug 2022 15:19:13 -0600
Subject: [PATCH 12/21] Update src/posts/xarray-kvikio/index.md
---
src/posts/xarray-kvikio/index.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index 363c0ab3..e158ec49 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -122,7 +122,7 @@ Xarray integrates [decently well](https://cupy-xarray.readthedocs.io/quickstart.
We don't have a cool demo yet but are looking to develop one very soon!
-Reach out if you have ideas. We would love to hear from you.
+[Reach out](https://discourse.pangeo.io/tag/machine-learning) if you have ideas. We would love to hear from you.
## Summary
From 56589f9ed5ae530db3e1b4e090a18f7390db537d Mon Sep 17 00:00:00 2001
From: Deepak Cherian
Date: Fri, 26 Aug 2022 10:12:14 -0600
Subject: [PATCH 13/21] Update src/posts/xarray-kvikio/index.md
Co-authored-by: Anderson Banihirwe
---
src/posts/xarray-kvikio/index.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index e158ec49..ad13ce3b 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -42,7 +42,7 @@ Writing the backend for Xarray was relatively easy with most of the code copied
## Usage
-Assuming you have all the pieces together (see [Appendix I]() and [Appendix II]() for step-by-step instructions), then using all this cool technology only requires adding `engine="kvikio"` to your `open_dataset` line (!)
+Assuming you have all the pieces together (see [Appendix I](#appendix-i--step-by-step-install-instructions) and [Appendix II](#appendix-ii--making-sure-gds-is-working) for step-by-step instructions), then using all this cool technology only requires adding `engine="kvikio"` to your `open_dataset` line (!)
```python
import xarray as xr
From 7eada7317791daf96713aca687a4d805d19c3c21 Mon Sep 17 00:00:00 2001
From: dcherian
Date: Thu, 25 Aug 2022 20:27:59 -0600
Subject: [PATCH 14/21] Small edits
---
src/posts/xarray-kvikio/index.md | 22 +++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index ad13ce3b..b068e08c 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -9,11 +9,11 @@ summary: "An experiment with direct-to-GPU reads from a Zarr store using Xarray.
## TLDR
-We [demonstrate](https://github.com/xarray-contrib/cupy-xarray/pull/10) registering an Xarray backend that reads data from a Zarr store directly to GPU memory as [CuPy arrays](https://cupy.dev) using the new [kvikIO library](https://docs.rapids.ai/api/kvikio/stable/) and [GPU Direct Storage](https://developer.nvidia.com/blog/gpudirect-storage/) technology.
+We [demonstrate](https://github.com/xarray-contrib/cupy-xarray/pull/10) registering an Xarray backend that reads data from a Zarr store directly to GPU memory as [CuPy arrays](https://cupy.dev) using the new [kvikIO library](https://docs.rapids.ai/api/kvikio/stable/) and [GPU Direct Storage](https://developer.nvidia.com/blog/gpudirect-storage/) technology. This allows direct-to-GPU reads and GPU-native analytics on existing pipelines 🎉 😱 🤯 🥳.
## Background
-### What is GPU Direct Storage
+### What is GPU Direct Storage?
Quoting [this nVIDIA blogpost](https://developer.nvidia.com/blog/gpudirect-storage/)
@@ -24,21 +24,21 @@ Quoting [this nVIDIA blogpost](https://developer.nvidia.com/blog/gpudirect-stora
![Diagram showing standard path between GPU memory and CPU memory on the left, versus a direct data path between GPU memory and storage on the right](https://developer.nvidia.com/blog/wp-content/uploads/2019/08/GPUDirect-Fig-1-New.png)
-### What is kvikIO
+### What is kvikIO?
> [kvikIO](https://github.com/rapidsai/kvikio) is a Python library providing bindings to [cuFile](https://docs.nvidia.com/gpudirect-storage/api-reference-guide/index.html#introduction), which enables GPUDirectStorage (GDS).
-For Xarray, the key bit is that kvikIO exposes a zarr store [`kvikio.zarr.GDSStore`](https://docs.rapids.ai/api/kvikio/stable/api.html#zarr) that does all the hard work for us. Since Xarray knows how to read Zarr stores, we can adapt it to create a new storage backend that uses `kvikio`. And thanks to recent work funded by the [Chan Zuckerberg Initiative](https://xarray.dev/blog/czi-eoss-grant-conclusion), creating and registering a [new backend](https://docs.xarray.dev/en/stable/internals/how-to-add-new-backend.html) is quite easy!
+For Xarray, the key bit is that kvikIO exposes a [a zarr store](https://docs.rapids.ai/api/kvikio/stable/api.html#zarr) called `GDSStore` that does all the hard work for us. Since Xarray knows how to read Zarr stores, we can adapt it to create a new storage backend that uses `kvikio`. And thanks to recent work funded by the [Chan Zuckerberg Initiative](https://xarray.dev/blog/czi-eoss-grant-conclusion), creating and registering a [new backend](https://docs.xarray.dev/en/stable/internals/how-to-add-new-backend.html) is quite easy!
## Integrating with Xarray
-Getting all this to work nicely requires using three in-progress pull requests that
+Getting all these pieces to work together requires using three in-progress pull requests that
1. [Teach Zarr to handle alternative array classes](https://github.com/zarr-developers/zarr-python/pull/934)
2. [Rewrite a small bit of Xarray to not cast all data to a numpy array after read from disk](https://github.com/pydata/xarray/pull/6874)
3. [Make a backend that connects Xarray to kvikIO](https://github.com/xarray-contrib/cupy-xarray/pull/10)
-Writing the backend for Xarray was relatively easy with most of the code copied over or inherited from the existing Zarr backend. We did have to ensure that dimension coordinates could be read in directly to host memory without raising an error (by default kvikIO loads all data to device memory). This is required because Xarrays creates `pandas.Index` objects for such variables. In the future, we could consider using `cudf.Index` instead to allow a fully GPU-backed Xarray object.
+Writing the backend for Xarray was relatively easy with most of the code copied over or inherited from the existing Zarr backend. We did have to ensure that dimension coordinates (for example, a `time` dimension with timestamps for a timeseries dataset) could be read in directly to host memory (RAM) without raising an error (by default kvikIO loads all data to device memory). This is required because Xarrays creates `pandas.Index` objects for such variables. In the future, we could consider using `cudf.Index` instead to allow a fully GPU-backed Xarray object.
## Usage
@@ -114,15 +114,15 @@ type(ds["air"].isel(time=0, lat=10).load().data)
cupy._core.core.ndarray
```
-Success!
+Success! 🎉 😱 🤯 🥳
-Xarray integrates [decently well](https://cupy-xarray.readthedocs.io/quickstart.html) with CuPy arrays so you should be able to test out analysis pipelines pretty easily.
+Xarray integrates [decently well](https://cupy-xarray.readthedocs.io/quickstart.html) with CuPy arrays so you should be able to test out existing analysis pipelines pretty easily.
## Cool demo
-We don't have a cool demo yet but are looking to develop one very soon!
+See above! 😆 We don't have a more extensive analysis demo yet but are looking to develop one very soon! The limiting step here is access to capable hardware.
-[Reach out](https://discourse.pangeo.io/tag/machine-learning) if you have ideas. We would love to hear from you.
+Reach out [on the Pangeo discourse forum](https://discourse.pangeo.io/tag/machine-learning) or over at [cupy-xarray](https://github.com/xarray-contrib/cupy-xarray) if you have ideas. We would love to hear from you.
## Summary
@@ -130,7 +130,7 @@ We demonstrate integrating the kvikIO library using Xarray's new backend entrypo
## Acknowledgments
-My time on this project was funded by NASA-OSTFL 80NSSC22K0345 "Enhancing analysis of NASA data with the open-source Python Xarray Library"
+This experiment was supported by funding from NASA-OSTFL 80NSSC22K0345 "Enhancing analysis of NASA data with the open-source Python Xarray Library".
## Appendix I : Step-by-step install instructions
From dbd88807beae14bcc861139f789e9e048ff057a8 Mon Sep 17 00:00:00 2001
From: Deepak Cherian
Date: Fri, 26 Aug 2022 10:19:04 -0600
Subject: [PATCH 15/21] Apply suggestions from code review
Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com>
---
src/posts/xarray-kvikio/index.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index b068e08c..7a48dc39 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -61,7 +61,7 @@ With this `ds.load()` will load directly to GPU memory and `ds` will now contain
First create an example uncompressed dataset to read from
-```
+```python
import xarray as xr
store = "./air-temperature.zarr"
@@ -75,13 +75,13 @@ airt.to_zarr(store, mode="w", consolidated=True)
Now read
-```
+```python
# consolidated must be False
ds = xr.open_dataset(store, engine="kvikio", consolidated=False)
ds.air
```
-```
+```python
[3869000 values with dtype=float32]
Coordinates:
@@ -136,7 +136,7 @@ This experiment was supported by funding from NASA-OSTFL 80NSSC22K0345 "Enhancin
[Wei Ji Leong](https://github.com/weiji14) helpfully [provided steps](https://github.com/xarray-contrib/cupy-xarray/pull/10#issuecomment-1218374773) to get started on your machine:
-```
+```bash
# May need to install nvidia-gds first
# https://docs.nvidia.com/cuda/cuda-installation-guide-linux/index.html#ubuntu-installation-common
sudo apt install nvidia-gds
From a709f412fbae1ff21eaabf63906eb65663ac9789 Mon Sep 17 00:00:00 2001
From: Deepak Cherian
Date: Fri, 26 Aug 2022 10:19:12 -0600
Subject: [PATCH 16/21] Apply suggestions from code review
Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com>
---
src/posts/xarray-kvikio/index.md | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index 7a48dc39..8b400466 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -18,8 +18,11 @@ We [demonstrate](https://github.com/xarray-contrib/cupy-xarray/pull/10) register
Quoting [this nVIDIA blogpost](https://developer.nvidia.com/blog/gpudirect-storage/)
> I/O, the process of loading data from storage to GPUs for processing, has historically been controlled by the CPU. As computation shifts from slower CPUs to faster GPUs, I/O becomes more of a bottleneck to overall application performance.
+>
> Just as GPUDirect RDMA (Remote Direct Memory Address) improved bandwidth and latency when moving data directly between a network interface card (NIC) and GPU memory, a new technology called GPUDirect Storage enables a direct data path between local or remote storage, like NVMe or NVMe over Fabric (NVMe-oF), and GPU memory.
+>
> Both GPUDirect RDMA and GPUDirect Storage avoid extra copies through a bounce buffer in the CPU’s memory and enable a direct memory access (DMA) engine near the NIC or storage to move data on a direct path into or out of GPU memory, all without burdening the CPU or GPU
+>
> For GPUDirect Storage, storage location doesn’t matter; it could be inside an enclosure, within the rack, or connected over the network.
![Diagram showing standard path between GPU memory and CPU memory on the left, versus a direct data path between GPU memory and storage on the right](https://developer.nvidia.com/blog/wp-content/uploads/2019/08/GPUDirect-Fig-1-New.png)
From ebda9cee7281d5a0b38339c07b50efb89b1e32da Mon Sep 17 00:00:00 2001
From: dcherian
Date: Fri, 26 Aug 2022 10:28:11 -0600
Subject: [PATCH 17/21] add weiji as coauthor
---
src/posts/xarray-kvikio/index.md | 12 +++---------
1 file changed, 3 insertions(+), 9 deletions(-)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index 8b400466..63bcebfd 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -2,8 +2,8 @@
title: "Enabling GPU-native analytics with Xarray and kvikIO"
date: "2022-08-25"
authors:
- - name: Deepak Cherian
- github: dcherian
+ - name: Deepak Cherian, Wei Ji Leong
+ github: dcherian, weiji14
summary: "An experiment with direct-to-GPU reads from a Zarr store using Xarray."
---
@@ -17,13 +17,7 @@ We [demonstrate](https://github.com/xarray-contrib/cupy-xarray/pull/10) register
Quoting [this nVIDIA blogpost](https://developer.nvidia.com/blog/gpudirect-storage/)
-> I/O, the process of loading data from storage to GPUs for processing, has historically been controlled by the CPU. As computation shifts from slower CPUs to faster GPUs, I/O becomes more of a bottleneck to overall application performance.
->
-> Just as GPUDirect RDMA (Remote Direct Memory Address) improved bandwidth and latency when moving data directly between a network interface card (NIC) and GPU memory, a new technology called GPUDirect Storage enables a direct data path between local or remote storage, like NVMe or NVMe over Fabric (NVMe-oF), and GPU memory.
->
-> Both GPUDirect RDMA and GPUDirect Storage avoid extra copies through a bounce buffer in the CPU’s memory and enable a direct memory access (DMA) engine near the NIC or storage to move data on a direct path into or out of GPU memory, all without burdening the CPU or GPU
->
-> For GPUDirect Storage, storage location doesn’t matter; it could be inside an enclosure, within the rack, or connected over the network.
+> I/O, the process of loading data from storage to GPUs for processing, has historically been controlled by the CPU. As computation shifts from slower CPUs to faster GPUs, I/O becomes more of a bottleneck to overall application performance. Just as GPUDirect RDMA (Remote Direct Memory Address) improved bandwidth and latency when moving data directly between a network interface card (NIC) and GPU memory, a new technology called GPUDirect Storage enables a direct data path between local or remote storage, like NVMe or NVMe over Fabric (NVMe-oF), and GPU memory. Both GPUDirect RDMA and GPUDirect Storage avoid extra copies through a bounce buffer in the CPU’s memory and enable a direct memory access (DMA) engine near the NIC or storage to move data on a direct path into or out of GPU memory, all without burdening the CPU or GPU. For GPUDirect Storage, storage location doesn’t matter; it could be inside an enclosure, within the rack, or connected over the network.
![Diagram showing standard path between GPU memory and CPU memory on the left, versus a direct data path between GPU memory and storage on the right](https://developer.nvidia.com/blog/wp-content/uploads/2019/08/GPUDirect-Fig-1-New.png)
From eb2c4d990f40e7d8c2896cd6cd2e64b99a6941b5 Mon Sep 17 00:00:00 2001
From: Deepak Cherian
Date: Fri, 26 Aug 2022 10:54:32 -0600
Subject: [PATCH 18/21] Update src/posts/xarray-kvikio/index.md
Co-authored-by: Anderson Banihirwe
---
src/posts/xarray-kvikio/index.md | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index 63bcebfd..b0297dbf 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -2,8 +2,10 @@
title: "Enabling GPU-native analytics with Xarray and kvikIO"
date: "2022-08-25"
authors:
- - name: Deepak Cherian, Wei Ji Leong
- github: dcherian, weiji14
+ - name: Deepak Cherian
+ github: dcherian
+ - name: Wei Ji Leong
+ github: weiji14
summary: "An experiment with direct-to-GPU reads from a Zarr store using Xarray."
---
From 27f284bcd6c335639f57636c78ba4b6d939660fd Mon Sep 17 00:00:00 2001
From: "pre-commit-ci[bot]"
<66853113+pre-commit-ci[bot]@users.noreply.github.com>
Date: Mon, 29 Aug 2022 20:01:16 +0000
Subject: [PATCH 19/21] [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---
src/posts/xarray-kvikio/index.md | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/posts/xarray-kvikio/index.md b/src/posts/xarray-kvikio/index.md
index b0297dbf..4717ef32 100644
--- a/src/posts/xarray-kvikio/index.md
+++ b/src/posts/xarray-kvikio/index.md
@@ -1,12 +1,12 @@
---
-title: "Enabling GPU-native analytics with Xarray and kvikIO"
-date: "2022-08-25"
+title: 'Enabling GPU-native analytics with Xarray and kvikIO'
+date: '2022-08-25'
authors:
- name: Deepak Cherian
github: dcherian
- name: Wei Ji Leong
github: weiji14
-summary: "An experiment with direct-to-GPU reads from a Zarr store using Xarray."
+summary: 'An experiment with direct-to-GPU reads from a Zarr store using Xarray.'
---
## TLDR
From 60d6289ac8f37c2d29903ee4fdd449ff056266a0 Mon Sep 17 00:00:00 2001
From: Anderson Banihirwe
Date: Mon, 29 Aug 2022 22:58:08 -0600
Subject: [PATCH 20/21] update date and generate social share card
---
public/cards/xarray-kvikio.png | Bin 0 -> 229986 bytes
src/posts/xarray-kvikio/index.md | 2 +-
2 files changed, 1 insertion(+), 1 deletion(-)
create mode 100644 public/cards/xarray-kvikio.png
diff --git a/public/cards/xarray-kvikio.png b/public/cards/xarray-kvikio.png
new file mode 100644
index 0000000000000000000000000000000000000000..a47ffc3333cdb36dcae0ca7e5880cf9383796f98
GIT binary patch
literal 229986
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⁡4Ss$NJXIoBu|7q+yD2cFCB6rev|
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