nemo_rl.data_plane.nixl.blobstore#
UnitSlabStore: blob bytes on the storage units’ slabs.
The client lays values out as raw bytes in registered local memory and hands the store the segments and the owner unit; on read it hands the store slab ranges and registered destination addresses. Pins, refcounts and the medium are the units’ business: pinned-DRAM slabs on CPU StorageUnit actors, one-sided RDMA over UCX, with an optional SSD tier on each unit.
Module Contents#
Classes#
Pinned-DRAM slabs on NixlStorageUnit actors, one-sided RDMA. |
Functions#
Data#
API#
- nemo_rl.data_plane.nixl.blobstore.__all__#
[‘LostBlobs’, ‘TransferFailed’, ‘UnitSlabStore’]
- nemo_rl.data_plane.nixl.blobstore.BUSY_RETRY_S#
0.2
- nemo_rl.data_plane.nixl.blobstore.BUSY_RETRY_MAX_S#
2.0
- nemo_rl.data_plane.nixl.blobstore.PENDING_POLL_S#
0.01
- exception nemo_rl.data_plane.nixl.blobstore.TransferFailed(
- blob_ids: list[str],
- reason: str,
- *,
- in_flight: bool = False,
Bases:
ExceptionA transfer or a control call failed, but the unit still has the blobs.
The caller may retry.
in_flight: a NIXL transfer may still be running.Initialization
Initialize self. See help(type(self)) for accurate signature.
- nemo_rl.data_plane.nixl.blobstore.remaining(deadline: float | None, cap: float) float#
min(cap, deadline - now); raise :class:DataPlaneTimeoutif spent.
- nemo_rl.data_plane.nixl.blobstore.busy_wait(deadline: float | None, backoff: float) float#
Sleep one Busy backoff (bounded by
deadline); return the next one.
- class nemo_rl.data_plane.nixl.blobstore.UnitSlabStore(
- ep: nemo_rl.data_plane.nixl.nixl_io.NixlEndpoint,
- directory: Any,
- *,
- namespace: str,
- timeout_s: float,
- ssd: bool = False,
- lock: Any = None,
Pinned-DRAM slabs on NixlStorageUnit actors, one-sided RDMA.
No Ray on the op path: control messages go to the units over ZMQ (nemo_rl/data_plane/nixl/control.py), bytes over NIXL. The caller names the unit of every op (rows live on unit
gidx % N).put: ZMQ
alloc+ NIXL WRITE + ZMQcommit(publishes the rows).get: one ZMQ
resolve_keysper unit (pins) + one NIXL READ per unit.clear: one awaited ZMQ
release_rowsper unit (row-wide).
Ray remains only off the op path: the unit list at start (and when a unit is unknown), and the liveness check after a failure.
Initialization
- _refresh_units() None#
- _unit(unit_id: int) dict[str, Any]#
- _addr(unit_id: int) str#
- put_segments(
- segments: list[tuple[int, int, int]],
- nbytes: int,
- n_entries: int,
- unit_id: int,
- blob_id: str | None = None,
- deadline: float | None = None,
Write one blob of
nbytesto unitunit_id; return its id.segmentsare(local_addr, blob_off, n): a value sent zero-copy from the caller’s own (registered) memory, or a run copied into a pool buffer. All go out in one NIXL transfer.Every wait is bounded by
deadline(monotonic). Raises- Class:
UnitFullwhen the unit refused for lack of space,- Class:
TransferFailedwhen it failed otherwise, and- Class:
DataPlaneTimeoutwhen the deadline passes (also while the unit keeps answering Busy, design §5.3).
- _commit(
- u: dict[str, Any],
- blob_id: str,
- deadline: float | None,
- errs: list,
Awaited commit: the blob’s rows are published once it returns True.
A blob the unit spilled to disk answers
"pending"while its file is written: ask again, inside the put deadline. Commit is idempotent on the unit, so a call whose reply was lost is asked again too. On failure the client aborts, which the unit ignores for a blob it did publish.
- put_many(
- blobs: list[tuple[list[tuple[int, int, int]], int, int, int, str]],
- deadline: float | None = None,
put_segmentsfor several(segments, nbytes, n_entries, unit_id, blob_id).The first blob of each unit goes out in one round over all units (alloc, one WRITE batch, commit), so a put waits for the slowest unit, not the sum. A blob that does not finish that round (busy, full, spill, no reply, lost) and any further blob of a unit take
put_segments, one by one.
- _put_round(
- by_unit: dict[int, tuple],
- deadline: float | None,
One alloc / WRITE / commit round, one blob per unit; the blob ids committed.
- _failed(unit_id: int, blobs: list[str], e: Exception) Exception#
Classify a failed read: lost if the unit is dead, else retryable.
- _unit_dead(unit_id: int) bool#
Ask Ray whether a unit’s actor is gone; mark it DEAD if so.
Only runs after a failure, so the op path stays Ray-free. A unit that is alive but slow is not dead: its blobs are still there.
- resolve_keys(
- by_unit: dict[int, list[str]],
- deadline: float | None,
One
resolve_keysper unit, all units at once; pins what it finds.Sent to every unit before any reply is read, so a get waits for the slowest unit rather than the sum of them. On a failure, the units that resolved are unpinned before the error is raised (
blob_idsof the error are the keys).
- read_entries(
- reads: dict[int, list[tuple[int, int, int]]],
- keys_of: dict[int, list[str]],
- deadline: float | None,
READ
(slab_off, nbytes, local_addr)ranges, all units at once.
- unpin_all(resolved: dict[int, dict[str, Any]]) None#
- release_rows(
- by_unit: dict[int, list[int]],
- deadline: float | None,
Awaited row-wide clear: one
release_rowsper unit, all units at once.Rows on a dead unit are already gone, so clearing them succeeds; a live unit that does not answer fails the clear (retryable).
- _call_units(
- op: str,
- args_by_unit: dict[int, dict[str, Any]],
- deadline: float | None,
opon every unit at once: results by unit, and(unit, error)failures.A unit unknown to the directory fails without being asked.
- close() None#