Hf3fs#
An L2 adapter backed by the native C++ 3FS connector. Uses the 3FS Usrbio API for high-performance distributed KV cache storage.
Prerequisites
3FS Usrbio 3FS Usrbio header (
hf3fs_usrbio.h) and lib (libhf3fs_api_shared.so) are required when compiling the LMCache hf3fs extension. We recommend installing them from source:git clone https://github.com/deepseek-ai/3fs cd 3fs git submodule update --init --recursive ./patches/apply.sh Install dependencies cmake --build build -j 32 cmake --install buildSee 3FS Build for the full build instructions.
If 3FS Usrbio header or lib are not installed in the system path (e.g.,
/usr/local/include,/usr/local/lib), you must setHF3FS_INCLUDE_DIRandHF3FS_LIB_DIRto point to them explicitly:HF3FS_INCLUDE_DIR=/path/to/usrbio/include \ HF3FS_LIB_DIR=/path/to/usrbio/lib \libabsl-dev
The connector uses Abseil containers (
sharded_flat_hash_map,flat_hash_map) from the system library. On Debian/Ubuntu:sudo apt-get update sudo apt-get install -y libabsl-dev
Build
The LMCache 3FS extension is not built by default. You must explicitly enable it:
BUILD_HF3FS=1 \
pip install -e . --verbose
The BUILD_HF3FS environment variable controls compilation:
BUILD_HF3FS=1: Enable build 3FS C++ extension.BUILD_HF3FS=0: Force disable (highest priority), even ifHF3FS_INCLUDE_DIRis set.Not set: Falls back to checking
HF3FS_INCLUDE_DIR. IfHF3FS_INCLUDE_DIRis also unset, the 3FS extension is skipped.
LMCache-Specific Fields
num_workers(int, default4): C++ worker threads for I/O.per_op_workers(dict[str, int], optional): A dict mapping lane keys to dedicated worker thread counts. Supported keys:"lookup"— threads forEXISTSoperations."retrieve"— threads forGET/load operations."store"— threads forSET/put operations."delete"— threads forDELETEoperations.
Operations whose lane key is not present in the dict use the shared
num_workerspool. There is no requirement to set all keys — you can configure only the lanes that need dedicated pools.
HF3FS Fields
mount_point(str, required): 3FS mount point directory.base_paths(str, required): Comma-separated subdirectories undermount_point.ior_entries(int, default256, range[128, 1024]): Max concurrent requests per Ior.io_depth(int, default0, range[-128, 128]): Batch control parameter.numa_id(int, default-1): NUMA node ID (-1= current node).iov_size(int, default209715200(200 MB), range[104857600, 2147483648]): Per-thread I/O buffer in bytes.enable_key_buffer(bool, defaultTrue): Enable the in-memory key buffer for accelerated exists lookups. When enabled, lookups of known keys respond from a hash set instead of issuing remote 3FS metadata calls. This dramatically reduces lookup latency.shard_dirs(bool, defaultFalse): Store each file under two chunk-hash prefix subdirectories instead of directly in its base path. See Directory Sharding below.
Configuration Example
--l2-adapter '{
"type": "hf3fs",
"mount_point": "/3fs/stage",
"base_paths": "/3fs/stage/path1,/3fs/stage/path2",
"ior_entries": 256,
"io_depth": 0,
"numa_id": -1,
"iov_size": 209715200,
"enable_key_buffer": true,
"num_workers": 4,
"per_op_workers": {
"lookup": 2,
"retrieve": 16,
"store": 4
}
}'
In-Memory Key Buffer
When enable_key_buffer is True (the default), the connector maintains an
in-memory cache of key filenames. Lookups of cached keys respond without issuing
remote 3FS metadata calls. This dramatically reduces lookup latency.
The cache is built by scanning base_paths at startup and grows with every
store. A key that is not in the cache costs one 3FS metadata call, and is added
to the cache if its file exists. This keeps chunks written by other LMCache
instances that share the same base_paths visible after startup.
A key deleted by another instance stays in the cache until this instance restarts, so a lookup can report it as present and the load of that key then fails.
- Trade-offs
Enable for workloads with many concurrent lookups of stored keys (prefetch, batch exists). Lookups of keys not in the cache cost the same 3FS metadata call as with the buffer disabled.
Disable when memory is constrained.
To disable the buffer cache:
--l2-adapter '{
"type": "hf3fs",
"mount_point": "/3fs/stage",
"base_paths": "/3fs/stage/path1",
"ior_entries": 256,
"io_depth": 0,
"numa_id": -1,
"iov_size": 209715200,
"enable_key_buffer": false,
}'
Directory Sharding
Each store creates a temporary file and renames it into place in the same
directory. On each client host, Linux holds the parent directory's lock
exclusively for the whole create and rename, including the round trips to the
3FS meta service, so all stores into one flat base path from one host run one
at a time, whatever the worker count. With
"shard_dirs": true, files are spread across a two-level subdirectory tree
named after the first four hex characters of the chunk hash, the same layout
the NIXL shard_dirs option uses:
shard_dirs: false <base_path>/<model>@0x<rank>@<group>@3fa2...e1.data
shard_dirs: true <base_path>/3f/a2/<model>@0x<rank>@<group>@3fa2...e1.data
Each subdirectory has its own lock, so stores into different subdirectories proceed in parallel. Subdirectories are created on first use. Choose the layout before populating a cache: a connector only finds files written in its own layout.
Unit Tests
The hf3fs tests are gated on 3FS extension availability and 3FS storage cluster accessibility: - 3FS C++ extension missing → config tests only - 3FS C++ extension present and 3FS storage cluster available → full test suite
To run the full test suite, set the HF3FS_MOUNT_POINT environment variable to the mount point directory of a real 3FS storage cluster:
HF3FS_MOUNT_POINT=/3fs/stage \
pytest tests/v1/distributed/test_hf3fs_l2_adapter.py -v