Skip to content

Commit 434f8c4

Browse files
authored
[optim] Optimize the exception message for Yuanrong when storage strategy does not support a certain type of data (#120)
## Main changes - Modify the exception message to make it more understandable. - Add a solution for the "Cannot retrieve stored data" error to FAQ. - Add two environmental variables `DS_D2H_MEMCPY_POLICY` and `DS_H2D_MEMCPY_POLICY` to datasystem workers --------- Signed-off-by: dpj135 <958208521@qq.com>
1 parent 3623f39 commit 434f8c4

4 files changed

Lines changed: 108 additions & 11 deletions

File tree

docs/storage_backends/openyuanrong_datasystem.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ ps aux | grep datasystem_worker
420420
dscli stop --worker_address <IP>:<PORT>
421421
422422
# Force cleanup (use with caution)
423-
pkill -f datasystem_worker
423+
pkill -9 -f datasystem_worker
424424
```
425425

426426
### Multi-Process Initialization
@@ -463,6 +463,21 @@ RuntimeError: code: [Out of memory], msg: [Shared memory no space in arena: ...]
463463

464464
Solution: Increase `--shared_memory_size_mb` in `worker_args`, or reduce the data volume being cached.
465465

466+
### "Cannot retrieve stored data" Error on get/clear
467+
468+
If you encounter an error like:
469+
```
470+
ValueError: Cannot retrieve stored data because the backend that originally stored it is unavailable in the current process or node. Please check that the configuration and NPU resource availability are consistent across all processes and nodes.
471+
```
472+
473+
This occurs when `kv_batch_get` cannot find the storage backend that originally handled the data. The most common cause is a mismatch between the process that originally `put` the data and the process performing `get`, such as:
474+
475+
- Different `enable_yr_npu_transport` settings across processes or nodes (e.g., `true` vs `false`).
476+
- NPU hardware or CANN/torch-npu unavailable on the `get` process or node, even though the configuration is identical.
477+
- When running inside Ray actors, the actor may not be assigned NPU resources (e.g., missing `"NPU": 1` in `.options(resources=...)`), preventing the NPU transport backend from initializing.
478+
479+
Solution: Ensure that all processes and nodes use the same TransferQueue configuration and have consistent NPU resource availability. When using Ray actors, make sure NPU resources are properly allocated via `.options(resources={"NPU": 1})`.
480+
466481

467482
## Datasystem Logs
468483

tests/test_yuanrong_storage_client_e2e.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,3 +219,37 @@ def test_mixed_flow(self, config):
219219
assert_tensors_equal(o, r)
220220
else:
221221
assert o == r
222+
223+
def test_get_with_invalid_backend_meta_raises_error(self, config):
224+
"""Verify that get raises ValueError when backend_meta contains an unrecognized tag."""
225+
client = self.client_cls(config)
226+
keys = ["k1"]
227+
shapes = [[]]
228+
dtypes = [None]
229+
invalid_meta = ["99"]
230+
with pytest.raises(ValueError, match="Cannot retrieve stored data"):
231+
client.get(keys, shapes, dtypes, invalid_meta)
232+
233+
def test_get_with_empty_backend_meta_raises_error(self, config):
234+
"""Verify that get raises ValueError when backend_meta contains empty tags (not previously stored)."""
235+
client = self.client_cls(config)
236+
keys = ["k1"]
237+
shapes = [[]]
238+
dtypes = [None]
239+
empty_meta = [""]
240+
with pytest.raises(ValueError, match="no backend metadata"):
241+
client.get(keys, shapes, dtypes, empty_meta)
242+
243+
def test_put_with_no_strategies_raises_error(self, config):
244+
"""Verify that put raises ValueError when no strategy supports the value type."""
245+
client = self.client_cls(config)
246+
client._strategies = []
247+
with pytest.raises(ValueError, match=f"No storage backend can handle {self.client_cls.ROUTE_ITEM_AS_VALUE}"):
248+
client.put(["k1"], [1])
249+
250+
def test_clear_with_empty_backend_meta_silent(self, config):
251+
"""Verify that clear silently skips keys with empty backend_meta (not previously stored)."""
252+
client = self.client_cls(config)
253+
empty_meta = [""]
254+
# No exception, no warning — only debug log
255+
client.clear(["k1"], empty_meta)

transfer_queue/storage/bootstrap/yuanrong_bootstrap.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,10 @@ def start_datasystem_worker(
120120
if device_ids or enable_rdma or ucx_env_vars:
121121
env = os.environ.copy()
122122
if device_ids:
123+
# Ensure direct copy for specified devices
124+
env.setdefault("DS_D2H_MEMCPY_POLICY", "direct")
125+
env.setdefault("DS_H2D_MEMCPY_POLICY", "direct")
126+
123127
env["ASCEND_RT_VISIBLE_DEVICES"] = device_ids
124128
logger.info(
125129
f"Setting ASCEND_RT_VISIBLE_DEVICES={device_ids} for dscli subprocess ({node_type} at {worker_address})"

transfer_queue/storage/clients/yuanrong_client.py

Lines changed: 54 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,11 @@ def mget_zero_copy(self, keys: list[str]) -> list[Any]:
291291
"""
292292
buffers = self._ds_client.get_buffers(keys)
293293
valid_indexes = [i for i, buf in enumerate(buffers) if buf is not None]
294+
if valid_indexes and len(valid_indexes) < len(keys):
295+
logger.warning(
296+
f"{len(keys) - len(valid_indexes)} requested keys were not found in openYuanrong-datasystem storage. "
297+
f"Returned results will contain None for these keys."
298+
)
294299
valid_bufs = [buffers[i] for i in valid_indexes]
295300
decoded_objs = batch_decode_from(valid_bufs)
296301
results = [None] * len(keys)
@@ -310,6 +315,9 @@ class YuanrongStorageClient(StorageKVClient):
310315
- General objects (CPU tensors, str, bool, list, etc.) via GeneralKVClientAdapter with serialization.
311316
"""
312317

318+
ROUTE_ITEM_AS_VALUE = "value"
319+
ROUTE_ITEM_AS_BACKEND_META = "backend_meta"
320+
313321
def __init__(self, config: dict[str, Any]):
314322
if not YUANRONG_DATASYSTEM_IMPORTED:
315323
raise ImportError("YuanRong DataSystem not installed.")
@@ -331,7 +339,7 @@ def __init__(self, config: dict[str, Any]):
331339
self._strategies.append(strategy)
332340

333341
if not self._strategies:
334-
raise RuntimeError("No storage strategy available for YuanrongStorageClient")
342+
raise RuntimeError("No storage backend available for YuanrongStorageClient")
335343

336344
def put(self, keys: list[str], values: list[Any]) -> list[str]:
337345
"""Stores multiple key-value pairs to remote storage.
@@ -351,7 +359,9 @@ def put(self, keys: list[str], values: list[Any]) -> list[str]:
351359
if len(keys) != len(values):
352360
raise ValueError("Number of keys must match number of values")
353361

354-
routed_indexes = self._route_to_strategies(values, lambda strategy_, item_: strategy_.supports_put(item_))
362+
routed_indexes = self._route_to_strategies(
363+
values, lambda strategy_, item_: strategy_.supports_put(item_), item_label=self.ROUTE_ITEM_AS_VALUE
364+
)
355365

356366
# Define the 'put_task': Slicing the input list and calling the backend strategy.
357367
# The closure captures local 'keys' and 'values' for zero-overhead parameter passing.
@@ -392,9 +402,17 @@ def get(
392402
if not (len(keys) == len(shapes) == len(dtypes) == len(custom_backend_meta)):
393403
raise ValueError("Lengths of keys, shapes, dtypes, custom_backend_meta must match")
394404

405+
if any(not tag for tag in custom_backend_meta):
406+
raise ValueError(
407+
"Some keys have no backend metadata (empty string), indicating they "
408+
"were not previously stored. Ensure all keys have been put before calling get."
409+
)
410+
395411
strategy_tags = custom_backend_meta
396412
routed_indexes = self._route_to_strategies(
397-
strategy_tags, lambda strategy_, item_: strategy_.supports_get(item_)
413+
strategy_tags,
414+
lambda strategy_, item_: strategy_.supports_get(item_),
415+
item_label=self.ROUTE_ITEM_AS_BACKEND_META,
398416
)
399417

400418
# Define the 'get_task': handles slicing of keys, shapes, and dtypes simultaneously.
@@ -426,7 +444,10 @@ def clear(self, keys: list[str], custom_backend_meta: list[str] | None = None) -
426444

427445
strategy_tags = custom_backend_meta
428446
routed_indexes = self._route_to_strategies(
429-
strategy_tags, lambda strategy_, item_: strategy_.supports_clear(item_), ignore_unmatched=True
447+
strategy_tags,
448+
lambda strategy_, item_: strategy_.supports_clear(item_),
449+
ignore_unmatched=True,
450+
item_label=self.ROUTE_ITEM_AS_BACKEND_META,
430451
)
431452

432453
def clear_task(strategy, indexes):
@@ -439,7 +460,9 @@ def _route_to_strategies(
439460
self,
440461
items: list[Any],
441462
selector: Callable[[StorageStrategy, Any], bool],
463+
*,
442464
ignore_unmatched: bool = False,
465+
item_label: str,
443466
) -> dict[StorageStrategy, list[int]]:
444467
"""Groups item indices by the first strategy that supports them.
445468
@@ -454,12 +477,16 @@ def _route_to_strategies(
454477
Signature: `(strategy: StorageStrategy, item: Any) -> bool`.
455478
ignore_unmatched: If True, items that don't match any strategy will be ignored (not included in output).
456479
If False, a ValueError will be raised for any unmatched item.
480+
item_label: Description of what `items` represents, used in error messages.
481+
Use ROUTE_ITEM_AS_VALUE for put (user-provided data),
482+
or ROUTE_ITEM_AS_BACKEND_META for get/clear (backend metadata).
457483
458484
Returns:
459485
A dictionary mapping each active strategy to a list of indexes in `items`
460486
that it should handle. Every index appears exactly once.
461487
"""
462488
unmatched_count = 0
489+
warning_count = 0
463490
routed_indexes: dict[StorageStrategy, list[int]] = {s: [] for s in self._strategies}
464491
for i, item in enumerate(items):
465492
for strategy in self._strategies:
@@ -468,14 +495,31 @@ def _route_to_strategies(
468495
break
469496
else:
470497
if ignore_unmatched:
498+
if item: # non-empty item → real tag, backend likely unavailable
499+
warning_count += 1
471500
unmatched_count += 1
472501
else:
473-
raise ValueError(
474-
f"No strategy supports item of type {type(item).__name__}: {item}. "
475-
f"Available strategies: {[type(s).__name__ for s in self._strategies]}"
476-
)
477-
if unmatched_count > 0:
478-
logger.warning(f"{unmatched_count} items were not matched to any strategy and will be ignored.")
502+
if item_label == self.ROUTE_ITEM_AS_BACKEND_META:
503+
raise ValueError(
504+
"Cannot retrieve stored data because the backend that originally "
505+
"stored it is unavailable in the current process or node. Please "
506+
"check that the configuration and NPU resource availability are "
507+
"consistent across all processes and nodes."
508+
)
509+
else:
510+
raise ValueError(f"No storage backend can handle {item_label} of type {type(item).__name__}.")
511+
if warning_count > 0:
512+
logger.warning(
513+
f"{warning_count} stored items could not be processed because the backend "
514+
f"that originally handled them may be unavailable in the current process or "
515+
f"node. Please check that the configuration and NPU resource availability "
516+
f"are consistent across all processes and nodes."
517+
)
518+
if unmatched_count > warning_count:
519+
logger.debug(
520+
f"{unmatched_count - warning_count} items with empty {item_label} "
521+
f"will be silently skipped (likely not previously stored)."
522+
)
479523

480524
return routed_indexes
481525

0 commit comments

Comments
 (0)