From 9b1da2173999069536841636f6c222c87dc2a155 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Sat, 20 Jun 2026 17:28:38 +0200 Subject: [PATCH 1/2] Fix DeviceHostFile mapping views --- dask_cuda/device_host_file.py | 8 +++-- .../tests/test_device_host_file_mapping.py | 33 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 dask_cuda/tests/test_device_host_file_mapping.py diff --git a/dask_cuda/device_host_file.py b/dask_cuda/device_host_file.py index ef1fefbb..c53b5d68 100644 --- a/dask_cuda/device_host_file.py +++ b/dask_cuda/device_host_file.py @@ -284,10 +284,14 @@ def __getitem__(self, key): raise KeyError(key) def __len__(self): - return len(self.device_buffer) + len(self.others) + return sum(1 for _ in self) def __iter__(self): - return itertools.chain(self.device_buffer, self.others) + seen = set() + for key in itertools.chain(self.device_buffer, self.host_buffer, self.others): + if key not in seen: + seen.add(key) + yield key def __delitem__(self, key): self.device_keys.discard(key) diff --git a/dask_cuda/tests/test_device_host_file_mapping.py b/dask_cuda/tests/test_device_host_file_mapping.py new file mode 100644 index 00000000..dd17bf49 --- /dev/null +++ b/dask_cuda/tests/test_device_host_file_mapping.py @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026, NVIDIA CORPORATION. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +import numpy as np +import pandas as pd +import pytest + +from dask_cuda.device_host_file import DeviceHostFile + + +@pytest.mark.parametrize("device_memory_limit", [None, 1]) +def test_device_host_file_mapping_views_include_host_only_keys( + tmp_path, device_memory_limit +): + dhf = DeviceHostFile( + device_memory_limit=device_memory_limit, + memory_limit=None, + worker_local_directory=tmp_path, + ) + values = { + "numpy": np.arange(3), + "pandas": pd.DataFrame({"x": [1, 2, 3]}), + } + + for key, value in values.items(): + dhf[key] = value + + assert len(dhf) == len(values) + assert set(dhf) == set(values) + assert list(dhf).count("numpy") == 1 + assert list(dhf).count("pandas") == 1 + np.testing.assert_array_equal(dhf["numpy"], values["numpy"]) + pd.testing.assert_frame_equal(dhf["pandas"], values["pandas"]) From 41c434551cd54b6302cd7a7db9061d78ad0407a9 Mon Sep 17 00:00:00 2001 From: Minh Vu Date: Sat, 20 Jun 2026 17:47:51 +0200 Subject: [PATCH 2/2] Clear DeviceHostFile keys before overwrite --- dask_cuda/device_host_file.py | 31 +++--- .../tests/test_device_host_file_mapping.py | 94 +++++++++++++++++++ 2 files changed, 113 insertions(+), 12 deletions(-) diff --git a/dask_cuda/device_host_file.py b/dask_cuda/device_host_file.py index c53b5d68..a693e810 100644 --- a/dask_cuda/device_host_file.py +++ b/dask_cuda/device_host_file.py @@ -5,6 +5,7 @@ import logging import os import time +from contextlib import suppress import numpy from zict import Buffer, Func @@ -262,9 +263,7 @@ def __init__( self.others = {} def __setitem__(self, key, value): - if key in self.device_buffer: - # Make sure we register the removal of an existing key - del self[key] + self._discard_key(key) if is_spillable_object(value): self.others[key] = value @@ -293,17 +292,25 @@ def __iter__(self): seen.add(key) yield key - def __delitem__(self, key): + def _discard_key(self, key): + removed = key in self.others self.device_keys.discard(key) - if key in self.others: - del self.others[key] - else: - if isinstance(self.device_buffer, dict) and key not in self.device_buffer: - # If `self.device_buffer` is a dictionary, host `key`s are inserted - # directly into `self.host_buffer`. + self.others.pop(key, None) + + with suppress(KeyError): + del self.device_buffer[key] + removed = True + + if self.host_buffer is not self.device_buffer: + with suppress(KeyError): del self.host_buffer[key] - else: - del self.device_buffer[key] + removed = True + + return removed + + def __delitem__(self, key): + if not self._discard_key(key): + raise KeyError(key) def evict(self): """Evicts least recently used host buffer (aka, CPU or system memory) diff --git a/dask_cuda/tests/test_device_host_file_mapping.py b/dask_cuda/tests/test_device_host_file_mapping.py index dd17bf49..53c62077 100644 --- a/dask_cuda/tests/test_device_host_file_mapping.py +++ b/dask_cuda/tests/test_device_host_file_mapping.py @@ -6,6 +6,46 @@ import pytest from dask_cuda.device_host_file import DeviceHostFile +from dask_cuda.is_spillable_object import is_spillable_object + + +class HostValue: + pass + + +class SpillableValue: + pass + + +class DeviceValue: + __cuda_array_interface__ = { + "shape": (), + "typestr": "