From 4e90c19ca71c7246f43c1075395279097f290bd9 Mon Sep 17 00:00:00 2001 From: Alberto Perdomo Date: Thu, 7 May 2026 08:59:21 +0100 Subject: [PATCH 1/4] ci: Wire fs_backend Python tests into CI Signed-off-by: Alberto Perdomo --- .github/workflows/ci-test.yaml | 10 +++- Makefile | 20 ++++++- .../llmd_fs_backend/tests/conftest.py | 28 ++++++++-- .../tests/requirements-cpu.txt | 3 ++ .../llmd_fs_backend/tests/test_file_mapper.py | 53 +++++++++++++++++++ 5 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 kv_connectors/llmd_fs_backend/tests/requirements-cpu.txt create mode 100644 kv_connectors/llmd_fs_backend/tests/test_file_mapper.py diff --git a/.github/workflows/ci-test.yaml b/.github/workflows/ci-test.yaml index 31dc22abe..b057d5fec 100644 --- a/.github/workflows/ci-test.yaml +++ b/.github/workflows/ci-test.yaml @@ -38,8 +38,16 @@ jobs: go-version: "${{ env.GO_VERSION }}" cache-dependency-path: ./go.sum + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + - name: Install dependencies - run: go mod download + run: | + go mod download + python -m pip install --upgrade pip + python -m pip install -r kv_connectors/llmd_fs_backend/tests/requirements-cpu.txt - name: Run unit tests run: make unit-test diff --git a/Makefile b/Makefile index 6ccf9b7b2..e7098f379 100644 --- a/Makefile +++ b/Makefile @@ -18,6 +18,7 @@ BUILDER := $(shell command -v buildah >/dev/null 2>&1 && echo buildah || echo $( UDS_TOKENIZER_IMAGE ?= llm-d-uds-tokenizer:e2e-test FS_BACKEND_NAME ?= llmd-fs-backend FS_BACKEND_DEV_IMG ?= $(IMAGE_TAG_BASE)/$(FS_BACKEND_NAME):$(DEV_VERSION) +FS_BACKEND_CPU_TESTS ?= kv_connectors/llmd_fs_backend/tests/test_file_mapper.py kv_connectors/llmd_fs_backend/tests/test_storage_events.py # go source files SRC = $(shell find . -type f -name '*.go') @@ -65,13 +66,30 @@ clang: test: unit-test e2e-test ## Run all tests (unit + e2e) .PHONY: unit-test -unit-test: unit-test-uds ## Run unit tests +unit-test: unit-test-uds unit-test-fs-backend-cpu ## Run unit tests .PHONY: unit-test-uds unit-test-uds: check-go download-zmq ## Run unit tests @printf "\033[33;1m==== Running unit tests ====\033[0m\n" @go test -v ./pkg/... +.PHONY: unit-test-fs-backend-cpu +unit-test-fs-backend-cpu: ## Run CPU-safe FS backend Python unit tests + @printf "\033[33;1m==== Running CPU-safe FS backend unit tests ====\033[0m\n" + @tests=(); \ + for test_path in $(FS_BACKEND_CPU_TESTS); do \ + if [ -e "$$test_path" ]; then \ + tests+=("$$test_path"); \ + else \ + echo "Skipping missing FS backend CPU test path: $$test_path"; \ + fi; \ + done; \ + if [ "$${#tests[@]}" -eq 0 ]; then \ + echo "No FS backend CPU test paths found."; \ + exit 0; \ + fi; \ + python3 -m pytest -q "$${tests[@]}" + .PHONY: unit-test-race unit-test-race: check-go download-zmq ## Run unit tests with Go race detector enabled @printf "\033[33;1m==== Running unit tests with race detector ====\033[0m\n" diff --git a/kv_connectors/llmd_fs_backend/tests/conftest.py b/kv_connectors/llmd_fs_backend/tests/conftest.py index 241aaa21d..f08353936 100644 --- a/kv_connectors/llmd_fs_backend/tests/conftest.py +++ b/kv_connectors/llmd_fs_backend/tests/conftest.py @@ -22,8 +22,6 @@ sys.path.insert(0, str(Path(__file__).parent)) import pytest -import torch -from vllm.config import VllmConfig, set_current_vllm_config def pytest_addoption(parser): @@ -35,20 +33,38 @@ def pytest_addoption(parser): parser.addoption("--obj-ca_bundle", default=None) -@pytest.fixture(scope="session", autouse=True) -def require_cuda(): +def pytest_configure(config): + config.addinivalue_line( + "markers", + "no_cuda_required: mark a test as not requiring CUDA setup/teardown", + ) + + +@pytest.fixture(autouse=True) +def require_cuda(request): """Skip all tests in this session if CUDA is not available.""" + if request.node.get_closest_marker("no_cuda_required"): + return + + import torch + if not torch.cuda.is_available(): pytest.skip("CUDA not available") @pytest.fixture(autouse=True) -def cuda_teardown(): +def cuda_teardown(request): """Ensure CUDA and C++ thread-pool resources from one test are fully released before the next test starts. Without this, async destructors can cause 'cudaErrorUnknown' or stale file-open errors in subsequent tests. """ + if request.node.get_closest_marker("no_cuda_required"): + yield + return + yield + import torch + gc.collect() # force Python GC to call C++ destructors immediately torch.cuda.synchronize() # surface any async CUDA errors in the right test torch.cuda.empty_cache() # free cached allocations so next test starts clean @@ -61,6 +77,8 @@ def default_vllm_config(): that use get_current_vllm_config() outside of a full engine context. This matches vLLM's internal test fixture pattern. """ + from vllm.config import VllmConfig, set_current_vllm_config + # Use empty VllmConfig() which provides sensible defaults with set_current_vllm_config(VllmConfig()): yield diff --git a/kv_connectors/llmd_fs_backend/tests/requirements-cpu.txt b/kv_connectors/llmd_fs_backend/tests/requirements-cpu.txt new file mode 100644 index 000000000..3a75086b9 --- /dev/null +++ b/kv_connectors/llmd_fs_backend/tests/requirements-cpu.txt @@ -0,0 +1,3 @@ +pytest +msgpack +pyzmq diff --git a/kv_connectors/llmd_fs_backend/tests/test_file_mapper.py b/kv_connectors/llmd_fs_backend/tests/test_file_mapper.py new file mode 100644 index 000000000..f63d1289f --- /dev/null +++ b/kv_connectors/llmd_fs_backend/tests/test_file_mapper.py @@ -0,0 +1,53 @@ +# Copyright 2026 The llm-d Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import importlib.util +import sys +from pathlib import Path + +import pytest + +pytestmark = pytest.mark.no_cuda_required + +CONNECTOR_ROOT = Path(__file__).resolve().parents[1] + + +def load_file_mapper_class(): + module_path = CONNECTOR_ROOT / "llmd_fs_backend" / "file_mapper.py" + spec = importlib.util.spec_from_file_location("file_mapper_under_test", module_path) + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + return module.FileMapper + + +def test_file_mapper_masks_hashes_to_lower_64_bits(): + file_mapper = load_file_mapper_class()( + root_dir="/tmp/kv-cache", + model_name="test-model", + gpu_block_size=16, + gpu_blocks_per_file=16, + tp_size=1, + pp_size=1, + pcp_size=1, + rank=0, + dtype="float16", + ) + + assert file_mapper.get_file_name((1 << 72) + 0x1234).endswith( + "/000/00/0000000000001234.bin" + ) + assert file_mapper.get_file_name(b"\x01\x02").endswith( + "/000/00/0000000000000102.bin" + ) From fcd6b074e2f01571b9d666daefd2bd53fd454f7b Mon Sep 17 00:00:00 2001 From: Alberto Perdomo Date: Wed, 20 May 2026 11:46:37 +0100 Subject: [PATCH 2/4] chore: Clean up branch Signed-off-by: Alberto Perdomo --- Makefile | 16 +----- .../tests/{ => cpu}/test_storage_events.py | 5 +- .../llmd_fs_backend/tests/test_file_mapper.py | 53 ------------------- 3 files changed, 6 insertions(+), 68 deletions(-) rename kv_connectors/llmd_fs_backend/tests/{ => cpu}/test_storage_events.py (99%) delete mode 100644 kv_connectors/llmd_fs_backend/tests/test_file_mapper.py diff --git a/Makefile b/Makefile index e7098f379..25a11b783 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ BUILDER := $(shell command -v buildah >/dev/null 2>&1 && echo buildah || echo $( UDS_TOKENIZER_IMAGE ?= llm-d-uds-tokenizer:e2e-test FS_BACKEND_NAME ?= llmd-fs-backend FS_BACKEND_DEV_IMG ?= $(IMAGE_TAG_BASE)/$(FS_BACKEND_NAME):$(DEV_VERSION) -FS_BACKEND_CPU_TESTS ?= kv_connectors/llmd_fs_backend/tests/test_file_mapper.py kv_connectors/llmd_fs_backend/tests/test_storage_events.py +FS_BACKEND_CPU_TESTS ?= kv_connectors/llmd_fs_backend/tests/cpu # go source files SRC = $(shell find . -type f -name '*.go') @@ -76,19 +76,7 @@ unit-test-uds: check-go download-zmq ## Run unit tests .PHONY: unit-test-fs-backend-cpu unit-test-fs-backend-cpu: ## Run CPU-safe FS backend Python unit tests @printf "\033[33;1m==== Running CPU-safe FS backend unit tests ====\033[0m\n" - @tests=(); \ - for test_path in $(FS_BACKEND_CPU_TESTS); do \ - if [ -e "$$test_path" ]; then \ - tests+=("$$test_path"); \ - else \ - echo "Skipping missing FS backend CPU test path: $$test_path"; \ - fi; \ - done; \ - if [ "$${#tests[@]}" -eq 0 ]; then \ - echo "No FS backend CPU test paths found."; \ - exit 0; \ - fi; \ - python3 -m pytest -q "$${tests[@]}" + @python3 -m pytest -q $(FS_BACKEND_CPU_TESTS) .PHONY: unit-test-race unit-test-race: check-go download-zmq ## Run unit tests with Go race detector enabled diff --git a/kv_connectors/llmd_fs_backend/tests/test_storage_events.py b/kv_connectors/llmd_fs_backend/tests/cpu/test_storage_events.py similarity index 99% rename from kv_connectors/llmd_fs_backend/tests/test_storage_events.py rename to kv_connectors/llmd_fs_backend/tests/cpu/test_storage_events.py index 3b9176515..b7b44ade9 100644 --- a/kv_connectors/llmd_fs_backend/tests/test_storage_events.py +++ b/kv_connectors/llmd_fs_backend/tests/cpu/test_storage_events.py @@ -20,8 +20,11 @@ from pathlib import Path import msgpack +import pytest -CONNECTOR_ROOT = Path(__file__).resolve().parents[1] +pytestmark = pytest.mark.no_cuda_required + +CONNECTOR_ROOT = Path(__file__).resolve().parents[2] class PrepareStoreOutput: diff --git a/kv_connectors/llmd_fs_backend/tests/test_file_mapper.py b/kv_connectors/llmd_fs_backend/tests/test_file_mapper.py deleted file mode 100644 index f63d1289f..000000000 --- a/kv_connectors/llmd_fs_backend/tests/test_file_mapper.py +++ /dev/null @@ -1,53 +0,0 @@ -# Copyright 2026 The llm-d Authors. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import importlib.util -import sys -from pathlib import Path - -import pytest - -pytestmark = pytest.mark.no_cuda_required - -CONNECTOR_ROOT = Path(__file__).resolve().parents[1] - - -def load_file_mapper_class(): - module_path = CONNECTOR_ROOT / "llmd_fs_backend" / "file_mapper.py" - spec = importlib.util.spec_from_file_location("file_mapper_under_test", module_path) - module = importlib.util.module_from_spec(spec) - sys.modules[spec.name] = module - spec.loader.exec_module(module) - return module.FileMapper - - -def test_file_mapper_masks_hashes_to_lower_64_bits(): - file_mapper = load_file_mapper_class()( - root_dir="/tmp/kv-cache", - model_name="test-model", - gpu_block_size=16, - gpu_blocks_per_file=16, - tp_size=1, - pp_size=1, - pcp_size=1, - rank=0, - dtype="float16", - ) - - assert file_mapper.get_file_name((1 << 72) + 0x1234).endswith( - "/000/00/0000000000001234.bin" - ) - assert file_mapper.get_file_name(b"\x01\x02").endswith( - "/000/00/0000000000000102.bin" - ) From 859f0d33e25c2f99aa4fa82eb749c2143690a002 Mon Sep 17 00:00:00 2001 From: Alberto Perdomo Date: Thu, 21 May 2026 07:39:26 +0100 Subject: [PATCH 3/4] fix: Use venv for llmd_fs_backend test Python deps Signed-off-by: Alberto Perdomo --- .github/workflows/ci-test.yaml | 10 +--------- .gitignore | 1 + Makefile | 22 +++++++++++++++++++--- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci-test.yaml b/.github/workflows/ci-test.yaml index b057d5fec..31dc22abe 100644 --- a/.github/workflows/ci-test.yaml +++ b/.github/workflows/ci-test.yaml @@ -38,16 +38,8 @@ jobs: go-version: "${{ env.GO_VERSION }}" cache-dependency-path: ./go.sum - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.12" - - name: Install dependencies - run: | - go mod download - python -m pip install --upgrade pip - python -m pip install -r kv_connectors/llmd_fs_backend/tests/requirements-cpu.txt + run: go mod download - name: Run unit tests run: make unit-test diff --git a/.gitignore b/.gitignore index 4ae4ae204..3f3584bd5 100644 --- a/.gitignore +++ b/.gitignore @@ -83,5 +83,6 @@ cufile.json # uds tokenizer default tokenizer cache path services/uds_tokenizer/tokenizers services/uds_tokenizer/.venv +kv_connectors/llmd_fs_backend/.venv **/vllm_source \ No newline at end of file diff --git a/Makefile b/Makefile index 25a11b783..4343ed654 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,10 @@ BUILDER := $(shell command -v buildah >/dev/null 2>&1 && echo buildah || echo $( UDS_TOKENIZER_IMAGE ?= llm-d-uds-tokenizer:e2e-test FS_BACKEND_NAME ?= llmd-fs-backend FS_BACKEND_DEV_IMG ?= $(IMAGE_TAG_BASE)/$(FS_BACKEND_NAME):$(DEV_VERSION) -FS_BACKEND_CPU_TESTS ?= kv_connectors/llmd_fs_backend/tests/cpu +FS_BACKEND_DIR := kv_connectors/llmd_fs_backend +FS_BACKEND_CPU_TESTS ?= $(FS_BACKEND_DIR)/tests/cpu +FS_BACKEND_VENV_DIR := $(FS_BACKEND_DIR)/.venv +FS_BACKEND_VENV_BIN := $(FS_BACKEND_VENV_DIR)/bin # go source files SRC = $(shell find . -type f -name '*.go') @@ -73,10 +76,23 @@ unit-test-uds: check-go download-zmq ## Run unit tests @printf "\033[33;1m==== Running unit tests ====\033[0m\n" @go test -v ./pkg/... +.PHONY: fs-backend-cpu-install-deps +fs-backend-cpu-install-deps: ## Set up venv and install FS backend CPU test dependencies + @printf "\033[33;1m==== Setting up FS backend CPU test venv ====\033[0m\n" + @if [ ! -f "$(FS_BACKEND_VENV_BIN)/python" ]; then \ + echo "Creating virtual environment in $(FS_BACKEND_VENV_DIR)..."; \ + $(PYTHON_EXE) -m venv $(FS_BACKEND_VENV_DIR); \ + echo "Upgrading pip..."; \ + $(FS_BACKEND_VENV_BIN)/pip install --upgrade pip > /dev/null; \ + else \ + echo "Virtual environment already exists"; \ + fi + @$(FS_BACKEND_VENV_BIN)/pip install -q -r $(FS_BACKEND_DIR)/tests/requirements-cpu.txt + .PHONY: unit-test-fs-backend-cpu -unit-test-fs-backend-cpu: ## Run CPU-safe FS backend Python unit tests +unit-test-fs-backend-cpu: fs-backend-cpu-install-deps ## Run CPU-safe FS backend Python unit tests @printf "\033[33;1m==== Running CPU-safe FS backend unit tests ====\033[0m\n" - @python3 -m pytest -q $(FS_BACKEND_CPU_TESTS) + @$(FS_BACKEND_VENV_BIN)/python -m pytest -q $(FS_BACKEND_CPU_TESTS) .PHONY: unit-test-race unit-test-race: check-go download-zmq ## Run unit tests with Go race detector enabled From 678c6fc64ba06fa29788e4c355fe8fad53ae470f Mon Sep 17 00:00:00 2001 From: Alberto Perdomo Date: Tue, 2 Jun 2026 11:02:46 +0100 Subject: [PATCH 4/4] chore: Support PVC evictor events Signed-off-by: Alberto Perdomo --- Makefile | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index 4343ed654..009f41968 100644 --- a/Makefile +++ b/Makefile @@ -19,9 +19,10 @@ UDS_TOKENIZER_IMAGE ?= llm-d-uds-tokenizer:e2e-test FS_BACKEND_NAME ?= llmd-fs-backend FS_BACKEND_DEV_IMG ?= $(IMAGE_TAG_BASE)/$(FS_BACKEND_NAME):$(DEV_VERSION) FS_BACKEND_DIR := kv_connectors/llmd_fs_backend -FS_BACKEND_CPU_TESTS ?= $(FS_BACKEND_DIR)/tests/cpu -FS_BACKEND_VENV_DIR := $(FS_BACKEND_DIR)/.venv -FS_BACKEND_VENV_BIN := $(FS_BACKEND_VENV_DIR)/bin +PVC_EVICTOR_DIR := kv_connectors/pvc_evictor +CPU_TEST_DIRS ?= $(FS_BACKEND_DIR)/tests/cpu $(PVC_EVICTOR_DIR)/tests +CPU_TEST_VENV_DIR := $(FS_BACKEND_DIR)/.venv +CPU_TEST_VENV_BIN := $(CPU_TEST_VENV_DIR)/bin # go source files SRC = $(shell find . -type f -name '*.go') @@ -69,30 +70,30 @@ clang: test: unit-test e2e-test ## Run all tests (unit + e2e) .PHONY: unit-test -unit-test: unit-test-uds unit-test-fs-backend-cpu ## Run unit tests +unit-test: unit-test-uds unit-test-cpu ## Run unit tests .PHONY: unit-test-uds unit-test-uds: check-go download-zmq ## Run unit tests @printf "\033[33;1m==== Running unit tests ====\033[0m\n" @go test -v ./pkg/... -.PHONY: fs-backend-cpu-install-deps -fs-backend-cpu-install-deps: ## Set up venv and install FS backend CPU test dependencies - @printf "\033[33;1m==== Setting up FS backend CPU test venv ====\033[0m\n" - @if [ ! -f "$(FS_BACKEND_VENV_BIN)/python" ]; then \ - echo "Creating virtual environment in $(FS_BACKEND_VENV_DIR)..."; \ - $(PYTHON_EXE) -m venv $(FS_BACKEND_VENV_DIR); \ +.PHONY: cpu-test-install-deps +cpu-test-install-deps: ## Set up venv and install CPU test dependencies + @printf "\033[33;1m==== Setting up CPU test venv ====\033[0m\n" + @if [ ! -f "$(CPU_TEST_VENV_BIN)/python" ]; then \ + echo "Creating virtual environment in $(CPU_TEST_VENV_DIR)..."; \ + $(PYTHON_EXE) -m venv $(CPU_TEST_VENV_DIR); \ echo "Upgrading pip..."; \ - $(FS_BACKEND_VENV_BIN)/pip install --upgrade pip > /dev/null; \ + $(CPU_TEST_VENV_BIN)/pip install --upgrade pip > /dev/null; \ else \ echo "Virtual environment already exists"; \ fi - @$(FS_BACKEND_VENV_BIN)/pip install -q -r $(FS_BACKEND_DIR)/tests/requirements-cpu.txt + @$(CPU_TEST_VENV_BIN)/pip install -q -r $(FS_BACKEND_DIR)/tests/requirements-cpu.txt -.PHONY: unit-test-fs-backend-cpu -unit-test-fs-backend-cpu: fs-backend-cpu-install-deps ## Run CPU-safe FS backend Python unit tests - @printf "\033[33;1m==== Running CPU-safe FS backend unit tests ====\033[0m\n" - @$(FS_BACKEND_VENV_BIN)/python -m pytest -q $(FS_BACKEND_CPU_TESTS) +.PHONY: unit-test-cpu +unit-test-cpu: cpu-test-install-deps ## Run CPU-safe Python unit tests + @printf "\033[33;1m==== Running CPU Python unit tests ====\033[0m\n" + @$(CPU_TEST_VENV_BIN)/python -m pytest -q $(CPU_TEST_DIRS) .PHONY: unit-test-race unit-test-race: check-go download-zmq ## Run unit tests with Go race detector enabled