Skip to content

[EC Connector] CPU Offloading EC Connector#47423

Merged
orozery merged 16 commits into
vllm-project:mainfrom
omerpaz95:cpu_offloading_ec_connector
Jul 13, 2026
Merged

[EC Connector] CPU Offloading EC Connector#47423
orozery merged 16 commits into
vllm-project:mainfrom
omerpaz95:cpu_offloading_ec_connector

Conversation

@omerpaz95

@omerpaz95 omerpaz95 commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Add ECCPUConnector — CPU encoder-cache offloading connector

Purpose

This PR adds ECCPUConnector, a self-contained encoder-cache (EC) connector that offloads encoder outputs to a shared CPU mmap region on /dev/shm so a single ec_role=both vLLM instance can reuse them on a later request instead of re-running the vision encoder.

The connector is intentionally minimal. Everything happens within one instance — encode once, save the encoder output to the CPU region, and on a subsequent cache hit reload mmap→GPU rather than recomputing.

This is a self-contained, minimal version of the bigger, p2p-enabled EC CPU NIXL Connector.

Logic

  • Scheduler delegate (ECCPUScheduler) — owns the mmap region and the
    shared offload bookkeeping (_local_encodings, _blocks), and routes
    scheduler-side calls to a producer and/or consumer sub-delegate. It exposes
    has_cache_item, ensure_cache_available, update_state_after_alloc, and
    build_connector_meta.
    • ECCPUProducer — the encode/offload pipeline. ensure_cache_available performs a speculative region-capacity check;
      update_state_after_alloc records a pending save;
      build_saves allocates region blocks (FIFO eviction of unpinned entries on exhaustion) and promotes the mm_hash into the shared _local_encodings cache.
    • ECCPUConsumer — the local-reload path. has_cache_item reports a hit when an mm_hash is present in _local_encodings;
      update_state_after_alloc pins the producer-allocated blocks;
      build_loads re-serves them for an mmap→GPU copy and unpins.
  • Worker delegate (ECCPUWorker) — a separate OS process, pure torch/mmap.
    save_caches copies GPU→mmap (synchronizing the save stream so the copy is CPU-complete before the block is served); start_load_caches copies mmap→GPU.

End-to-End Architecture

                 single vLLM instance (ec_role=both)
  ┌───────────────────────────────────────────────────────────────┐
  │  Scheduler process                                             │
  │  ┌──────────────────────── ECCPUScheduler ───────────────────┐ │
  │  │  ECCPUProducer            ECCPUConsumer                    │ │
  │  │    build_saves()            has_cache_item()               │ │
  │  │    ensure_cache_available   update_state_after_alloc (pin) │ │
  │  │                             build_loads() (re-serve)       │ │
  │  │  shared: _local_encodings, _blocks, _shared_lock           │ │
  │  └────────────────────────────────────────────────────────────┘│
  │                         │ saves / loads metadata (per step)      │
  │                         ▼                                        │
  │  Worker process                                                 │
  │  ┌──────────────────────── ECCPUWorker ──────────────────────┐ │
  │  │  save_caches:  GPU ──────► mmap block   (/dev/shm)         │ │
  │  │  start_load:   mmap block ─────► GPU                       │ │
  │  └────────────────────────────────────────────────────────────┘│
  └───────────────────────────────────────────────────────────────┘

Lifecycle (offload-reuse cycle)

  1. A request with mm_features arrives. has_cache_item(mm_hash) returns False (first sight), so the encoder input is scheduled for compute.
  2. The worker encodes the item; save_caches copies the encoder output GPU→mmap (with a blocking stream sync, so the bytes are durable before the step's model execution returns).
  3. In the same step, build_saves allocates the region blocks, records mm_hash → block_indices in _blocks, and promotes mm_hash into _local_encodings (the shared local cache) — so the item is reusable from the next step.
  4. A later request for the same mm_hash: has_cache_item now returns True, so the scheduler routes it to external_load_encoder_input instead of recomputing.
  5. update_state_after_alloc pins the cached blocks; build_loads re-serves the same block indices as a load and unpins them.
  6. start_load_caches copies mmap→GPU. The encoder is not re-invoked.

Configuration

Selected by name through the transfer config, with the ec_both role:

from vllm import LLM

llm = LLM(
    model="your-vision-model",
    kv_transfer_config={
        "ec_connector": "ECCPUConnector",
        "ec_role": "ec_both",
    },
)

ECTransferConfig is unchanged — connector selection is purely by name. All
three roles (ec_producer, ec_consumer, ec_both) are accepted, but only
ec_both delivers reuse in the CPU-only connector (a producer-only instance
saves but never reloads; a consumer-only instance finds nothing locally). The
full role plumbing is kept so the NIXL subclasses inherit role handling
untouched.

Files

All new files live under vllm/distributed/ec_transfer/ec_connector/:

File Purpose
ec_shared_region.py ECSharedRegionmmap lifecycle, alloc/free/pin/unpin, cudaHostRegister
cpu/__init__.py Package marker
cpu/common.py ECRegionContext, ECCPUConnectorMetadata, setup_ec_region
cpu/worker.py ECCPUWorker — GPU↔mmap copies
cpu/connector.py ECCPUConnector — role router + _make_* seams
cpu/scheduler/__init__.py ECCPUScheduler — scheduler delegate + _build_* seams
cpu/scheduler/common.py evict_and_alloc — FIFO eviction helper
cpu/scheduler/producer.py ECCPUProducer — encode/offload pipeline
cpu/scheduler/consumer.py ECCPUConsumer — local mmap reload

Plus one line in factory.py registering ECCPUConnector.

Test Plan

Unit tests live under tests/v1/ec_connector/unit/ and run in full with:

python -m pytest tests/v1/ec_connector/unit/ -v

Coverage:

  • test_ec_shared_region.py — region alloc/free/try_free/pin/unpin
    refcount semantics, FIFO eviction skipping pinned blocks, exhaustion raising AllocationError.
  • test_metadata.pyECCPUConnectorMetadata defaults and independence of saves/loads.
  • test_producer.pybuild_saves allocate-and-promote, _fifo_alloc eviction (oldest-unpinned / skip-pinned), ensure_cache_available deferral on a full region.
  • test_consumer.pyhas_cache_item, update_state_after_alloc pin, build_loads re-serve + unpin.
  • test_worker.py — GPU↔mmap round-trip reproduces the source tensor bit-for-bit (CUDA-gated tests skip on hosts without a GPU).
  • test_scheduler_cpu.py — delegate wiring and the full offload-reuse cycle (save → promote → hit → reload of the same blocks).
  • test_connector_cpu.py — role dispatch (scheduler vs worker) and factory registration.
  • test_no_nixl_imports.py — AST guard asserting no nixl/zmq/msgspec imports (and no NIXL-only submodules) are reachable from the cpu/ package.

omerpaz95 added 6 commits July 1, 2026 15:35
Signed-off-by: omerpaz95 <omerpaz95@gmail.com>
Signed-off-by: omerpaz95 <omerpaz95@gmail.com>
Signed-off-by: omerpaz95 <omerpaz95@gmail.com>
Signed-off-by: omerpaz95 <omerpaz95@gmail.com>
Signed-off-by: omerpaz95 <omerpaz95@gmail.com>
Signed-off-by: omerpaz95 <omerpaz95@gmail.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@mergify mergify Bot added cpu Related to CPU backends v1 labels Jul 2, 2026
Comment on lines +1 to +10
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""CPU-cache NIXL-transport EC connector.

See `ec-nixl-transfer-v4.md` for the full design.
"""

# from vllm.distributed.ec_transfer.ec_connector.cpu.connector import (
# ECCPUConnector,
# )

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove file?

Comment thread vllm/distributed/ec_transfer/ec_connector/cpu/ec_shared_region.py
Comment thread vllm/distributed/ec_transfer/ec_connector/cpu/scheduler/__init__.py
1. ECSharedRegion demoted to pure mmap lifecycle manager
Previously it was a full block allocator with alloc/free, ref-counting (pin/unpin), and locking.
Now it just handles open/mmap/CUDA-host-register/cleanup.
All allocation logic moved into the new EmbeddingCache class,
which is a proper named-entry cache with explicit states (not-ready -> ready -> pinned) and FIFO eviction.

2. New StepTracker for DMA completion safety
The old scheduler assumed saves were immediately ready.
The new design introduces StepTracker - it delays mark_ready and unpin operations by a configurable step count (max_concurrent_batches),
ensuring GPU<->host DMA transfers actually complete before entries are served or evicted.

3. Batched DMA instead of per-block copies
The worker previously iterated per-block with tensor.copy_() on a CUDA stream + synchronize.
Now it fills pooled descriptor buffers (DescriptorBufferPool)
and flushes everything in a single swap_blocks_batch call - no explicit CUDA stream management.

4. Save-rank filtering
Only tp_rank==0 && pcp_rank==0 performs the GPU-to-host write,
avoiding redundant bandwidth usage across ranks that hold identical encoder output.

5. Region sizing by byte budget
Changed from a raw num_ec_blocks count to an ec_cpu_bytes byte budget divided by per-block size.

6. Removed ECRegionContext
The intermediary dataclass bundling region + layout fields is gone;
the region is returned directly from create_ec_shared_region().

7. Add e2e test and CI integration

Signed-off-by: Or Ozeri <oro@il.ibm.com>
@orozery orozery requested review from Harry-Chen and khluu as code owners July 12, 2026 08:06
@mergify mergify Bot added the ci/build label Jul 12, 2026
@orozery orozery added the ready ONLY add when PR is ready to merge/full CI is needed label Jul 12, 2026
orozery and others added 4 commits July 12, 2026 11:23
Signed-off-by: Or Ozeri <oro@il.ibm.com>
Signed-off-by: Or Ozeri <oro@il.ibm.com>
Signed-off-by: omerpaz95 <omerpaz95@gmail.com>
@orozery orozery merged commit 43c8cbf into vllm-project:main Jul 13, 2026
85 checks passed
NickLucche pushed a commit to NickLucche/vllm that referenced this pull request Jul 15, 2026
Signed-off-by: omerpaz95 <omerpaz95@gmail.com>
Signed-off-by: Or Ozeri <oro@il.ibm.com>
Co-authored-by: Or Ozeri <oro@il.ibm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/build cpu Related to CPU backends ready ONLY add when PR is ready to merge/full CI is needed v1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants