This crate provides the low-level raw device I/O layer for LMCache via Rust + PyO3. It is used by both:
- the legacy non-MP
RustRawBlockBackend - the MP
raw_blockL2 adapter (RawBlockL2Adapter) viaRawBlockCore
The Rust crate intentionally stays narrow: it owns the raw device handle and
exposes blocking pwrite_from_buffer / pread_into primitives. Slotting,
checkpointing, recovery, and MP task orchestration all live in Python.
RawBlockDevice accepts io_engine:
posix(default): synchronous Linuxpread/pwrite.io_uring: direct Rust io_uring syscall path using the existing worker, batch, andwait_iouringmachinery.
use_iouring=True remains accepted for backward compatibility. If io_engine
is explicitly set, it wins over the legacy flag.
In MP mode, the stack looks like this:
StoreController / PrefetchController
|
v
RawBlockL2Adapter
|
v
RawBlockCore
|
v
lmcache_rust_raw_block_io
|
v
raw device / file
This split lets LMCache reuse the same on-device metadata and recovery model in both non-MP and MP mode without duplicating the raw-block implementation.
LMCache LocalCPUBackend (aligned pinned CPU tensor)
|
| Python buffer / memoryview (no payload memcpy)
v
RustRawBlockBackend (PyO3 boundary)
|
| direct pointer path when O_DIRECT constraints are met
| fallback: bounce only for unaligned tail/block
v
RawBlockDevice::pwrite_from_buffer / pread_into
|
v
Block device or file
To compare local_disk vs rust_raw_block on a real NVMe device:
- Run
local_diskon an ext4 mount of the device. - Unmount it.
- Run
rust_raw_blockdirectly on the raw block device.
Use the benchmark commands in:
benchmarks/storage_backend_io/README.md
No fixed numbers are included here because results are host/device/workload dependent.
- Linux only (
pread/pwrite, O_DIRECT semantics). - O_DIRECT requires aligned offset, size, and user buffer address.
cd rust/raw_block
pip install maturin
maturin develop --releasefrom lmcache_rust_raw_block_io import RawBlockDevice
dev = RawBlockDevice("/dev/nvme0n1", True, use_odirect=True, alignment=4096)
dev.pwrite_from_buffer(offset=0, data=b"hello", total_len=4096)
buf = bytearray(4096)
dev.pread_into(offset=0, out=buf, payload_len=5, total_len=4096)io_uring:
dev = RawBlockDevice(
"/dev/nvme0n1",
True,
use_odirect=True,
alignment=4096,
io_engine="io_uring",
iouring_queue_depth=256,
)To use the MP adapter from lmcache server, pass a raw_block L2 adapter
config:
lmcache server \
--l1-size-gb 10 \
--eviction-policy LRU \
--l1-align-bytes 4096 \
--l2-adapter '{
"type": "raw_block",
"device_path": "/dev/nvme0n1",
"slot_bytes": 1048576,
"block_align": 4096,
"header_bytes": 4096,
"meta_total_bytes": 268435456,
"use_odirect": true,
"io_engine": "io_uring",
"num_store_workers": 2,
"num_lookup_workers": 1,
"num_load_workers": 4
}'Notes:
device_pathshould point to an unmounted raw block device or a dedicated file used only by LMCache.- With
use_odirect=true, LMCache MP L1 alignment must be at leastblock_align. - Restart recovery uses the metadata checkpoint region on the same device.
- Raw-block slot reclamation is driven by the shared/global L2 eviction
controller or explicit
delete()calls. raw_blockremains the adapter type for both supported engines.