Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
86a0fad
[Offloading] Per-job store completion tracking for KV cache offloading
EtelisIBM Apr 7, 2026
049e574
[Offloading] Fix assertion error: keep _reqs_being_stored entry for f…
EtelisIBM Apr 7, 2026
f7c0e5c
[Offloading] Add unit tests for OffloadingWorkerMetadata.aggregate
EtelisIBM Apr 7, 2026
5762df9
Merge branch 'main' into per-job-store-completion
Etelis Apr 7, 2026
2b2ead7
Add reverse mapping to avoid linear scan in store job cleanup
EtelisIBM Apr 8, 2026
d01da9e
Merge branch 'main' into per-job-store-completion
EtelisIBM Apr 9, 2026
2986007
Update vllm/distributed/kv_transfer/kv_connector/v1/offloading/common.py
Etelis Apr 12, 2026
0ad8015
[Offloading] Address CR: unify job tracking, key metadata by job ID
EtelisIBM Apr 12, 2026
4fb4d36
[Offloading] Address CR: remove internal job IDs, extract job trackers
EtelisIBM Apr 12, 2026
49f204c
[Offloading] Resolve merge conflict in common.py
EtelisIBM Apr 12, 2026
e12300c
Merge branch 'main' into per-job-store-completion
Etelis Apr 12, 2026
7fa79bb
Merge branch 'per-job-store-completion' of https://github.com/Etelis/…
EtelisIBM Apr 12, 2026
ae305ba
[Offloading] Fix shutdown() to use renamed tracker attributes
EtelisIBM Apr 12, 2026
1b5ceb9
Merge branch 'main' into per-job-store-completion
Etelis Apr 12, 2026
125d67a
Merge branch 'main' into per-job-store-completion
Etelis Apr 13, 2026
e00ed7f
[Offloading] Unify load/store transfer jobs
EtelisIBM Apr 14, 2026
c7dc237
[Offloading] Add completed load jobs
EtelisIBM Apr 14, 2026
049605a
[Offloading] Simplify request cleanup
EtelisIBM Apr 14, 2026
1f888d8
[Offloading] Rename metadata fields to job-centric naming
EtelisIBM Apr 18, 2026
c58f38e
[Offloading] Unify completed load/store jobs into completed_jobs
EtelisIBM Apr 18, 2026
ae46506
[Offloading] Rename _load_jobs to _current_batch_load_jobs
EtelisIBM Apr 18, 2026
d86ca2d
[Offloading] Hoist completion tracking into self._connector_worker_meta
EtelisIBM Apr 18, 2026
8f8a2de
[Offloading] Drop finished_sending fallback in update_connector_output
EtelisIBM Apr 18, 2026
1714fdd
[Offloading] Hoist offload keys to scheduler-level _job_keys
EtelisIBM Apr 18, 2026
23e2ce0
[Offloading] Rename aggregate() loop variable to job_id
EtelisIBM Apr 18, 2026
ff761e8
[Offloading] Flatten worker tracker state into _jobs / _req_state
EtelisIBM Apr 18, 2026
185dfcf
[Offloading] Remove unused variables from OffloadingConnectorWorker i…
EtelisIBM Apr 18, 2026
5cf6114
[Offloading] Drop "TP" from TransferJobStatus comment
EtelisIBM Apr 19, 2026
9cec233
[Offloading] Move offload keys into TransferJobStatus
EtelisIBM Apr 19, 2026
eab28b2
[Offloading] Assert load_job is None before assignment
EtelisIBM Apr 19, 2026
d8c0ffb
[Offloading] Carry job IDs in jobs_to_flush
EtelisIBM Apr 19, 2026
e0d4a7f
[Offloading] Assert pending_count == 0 after completion guard
EtelisIBM Apr 19, 2026
d5ff68c
[Offloading] Assert req_status is not None on completion
EtelisIBM Apr 19, 2026
1bbbde0
[Offloading] Gate per-job req_status pop on req.is_finished()
EtelisIBM Apr 19, 2026
9e262ac
[Offloading] Unify load_job/store_jobs into transfer_jobs + is_store …
EtelisIBM Apr 19, 2026
f85d2ae
Merge branch 'main' into per-job-store-completion
Etelis Apr 19, 2026
48a0ccb
[Offloading] Assert flushed jobs are stores in build_connector_meta
EtelisIBM Apr 23, 2026
4a55d2d
Update vllm/distributed/kv_transfer/kv_connector/v1/offloading/schedu…
Etelis Apr 23, 2026
838f36e
Update vllm/distributed/kv_transfer/kv_connector/v1/offloading/schedu…
Etelis Apr 23, 2026
90309d0
[Offloading] Fix req_status subscript and gate _req_status pop on req…
EtelisIBM Apr 23, 2026
91fc4a0
Merge remote-tracking branch 'upstream/main' into per-job-store-compl…
EtelisIBM Apr 23, 2026
11ecb54
[Offloading][Tests] Thread OffloadingWorkerMetadata through RequestRu…
EtelisIBM Apr 23, 2026
2b921d0
[Offloading] Tie transfer_jobs lifetime to worker acks, not per-job c…
EtelisIBM Apr 23, 2026
5020de2
[Offloading] Guard update_connector_output against None worker meta
EtelisIBM Apr 23, 2026
1421691
Merge remote-tracking branch 'upstream/main' into per-job-store-compl…
EtelisIBM Apr 23, 2026
f2e2d82
Merge remote-tracking branch 'upstream/main' into per-job-store-compl…
EtelisIBM Apr 26, 2026
fb28950
[Offloading] Drop defensive None-fallbacks where invariants guarantee…
EtelisIBM Apr 26, 2026
a6b66ca
[Offloading] Drop defensive None-fallbacks in worker get_finished
EtelisIBM Apr 26, 2026
8a139fd
Update vllm/distributed/kv_transfer/kv_connector/v1/offloading/schedu…
Etelis Apr 26, 2026
8ebde6b
Merge branch 'main' into per-job-store-completion
Etelis Apr 26, 2026
d9beba4
Update vllm/distributed/kv_transfer/kv_connector/v1/offloading/schedu…
EtelisIBM Apr 26, 2026
1a83c5d
[Offloading] Defer preemption-flush bookkeeping to update_connector_o…
EtelisIBM Apr 26, 2026
05b7cb3
[Offloading] request_finished returns False; fence block reuse via re…
EtelisIBM Apr 26, 2026
8dc87ae
Merge branch 'main' into per-job-store-completion
Etelis Apr 26, 2026
f459b3c
Merge branch 'main' into per-job-store-completion
Etelis Apr 26, 2026
df3c8ab
[Offloading][Tests] Drain deferred stores in RequestRunner._run
EtelisIBM Apr 26, 2026
9294929
[Offloading] Drop duplicate next_stored_block_idx assignment in updat…
EtelisIBM Apr 26, 2026
94e20b4
[Offloading][Tests] Drop unreachable post-EOS drain loop
EtelisIBM Apr 26, 2026
c84cdfe
[Offloading] Drop load fencing; consolidate fence to _build_store_jobs
EtelisIBM Apr 27, 2026
456ecf3
[Offloading] Lift jobs_to_flush to _current_batch_jobs_to_flush member
EtelisIBM Apr 27, 2026
b8e9cc8
[Offloading] Fast-path the fence: flat _unprotected_block_ids + isdis…
EtelisIBM Apr 27, 2026
805934e
[Offloading] Defer fence index population to request_finished
EtelisIBM Apr 27, 2026
043d0af
[Offloading] Guard the cleanup loop on dict-empty + request-finished
EtelisIBM Apr 27, 2026
c5879cd
Merge branch 'main' into per-job-store-completion
Etelis Apr 27, 2026
6712fe9
[Offloading] Drop _unprotected_block_ids; use dict.keys().isdisjoint
EtelisIBM Apr 27, 2026
b2d9c28
[Offloading] Replace _jobs with _load_jobs: dict[int, ReqId]
EtelisIBM Apr 27, 2026
1f751d6
[Offloading] Fence load dst blocks at update_state_after_alloc
EtelisIBM Apr 27, 2026
c0804a8
Merge branch 'main' into per-job-store-completion
Etelis Apr 27, 2026
3b88704
[Offloading][Tests] Unit tests for per-job store completion + fence
EtelisIBM Apr 27, 2026
ef6736a
Merge branch 'main' into per-job-store-completion
Etelis Apr 28, 2026
7f98be6
[Offloading] Add return type hint to build_connector_worker_meta
EtelisIBM Apr 28, 2026
8a82380
[Offloading] Fix complete_load called for keys never given to prepare…
EtelisIBM Apr 28, 2026
5e5f0d6
Merge remote-tracking branch 'upstream/main' into per-job-store-compl…
EtelisIBM Apr 28, 2026
eea29b7
Merge branch 'main' into per-job-store-completion
Etelis Apr 28, 2026
db7c221
Merge remote-tracking branch 'upstream/main' into pr-39186-merge-main
EtelisIBM Apr 28, 2026
7e79b85
Merge remote-tracking branch 'upstream/main' into pr-39186-merge-main-r3
EtelisIBM Apr 28, 2026
670fa66
Merge remote-tracking branch 'upstream/main' into pr-39186-merge-r6
EtelisIBM Apr 28, 2026
f059821
Merge remote-tracking branch 'upstream/main' into pr-39186-merge-r11
EtelisIBM Apr 28, 2026
39f381c
Merge remote-tracking branch 'upstream/main' into pr-39186-merge-r13
EtelisIBM Apr 28, 2026
ec4ae7d
Merge remote-tracking branch 'upstream/main' into pr-39186-merge-r15
EtelisIBM Apr 29, 2026
dbb078e
Merge remote-tracking branch 'upstream/main' into pr-39186-merge-r17
EtelisIBM Apr 29, 2026
8966c13
Merge remote-tracking branch 'upstream/main' into pr-39186-merge-r19
EtelisIBM Apr 29, 2026
c8862a1
Merge remote-tracking branch 'upstream/main' into pr-39186-merge-r21
EtelisIBM Apr 29, 2026
810bee9
Merge branch 'main' into per-job-store-completion
orozery Apr 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project

import pytest

from vllm.distributed.kv_transfer.kv_connector.v1.offloading.common import (
OffloadingWorkerMetadata,
)

pytestmark = pytest.mark.cpu_test


def test_aggregate_sums_counts():
meta1 = OffloadingWorkerMetadata(completed_store_jobs={42: 1})
meta2 = OffloadingWorkerMetadata(completed_store_jobs={42: 1})
result = meta1.aggregate(meta2)
assert result.completed_store_jobs == {42: 2}


def test_aggregate_disjoint_jobs():
meta1 = OffloadingWorkerMetadata(completed_store_jobs={42: 1})
meta2 = OffloadingWorkerMetadata(completed_store_jobs={43: 1})
result = meta1.aggregate(meta2)
assert result.completed_store_jobs == {42: 1, 43: 1}


def test_aggregate_multiple_workers():
meta1 = OffloadingWorkerMetadata(completed_store_jobs={42: 1, 43: 1})
meta2 = OffloadingWorkerMetadata(completed_store_jobs={42: 1})
meta3 = OffloadingWorkerMetadata(completed_store_jobs={42: 1, 43: 1})
result = meta1.aggregate(meta2).aggregate(meta3)
assert result.completed_store_jobs == {42: 3, 43: 2}
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,44 @@
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
from dataclasses import dataclass

from vllm.distributed.kv_transfer.kv_connector.v1.base import KVConnectorMetadata
from vllm.distributed.kv_transfer.kv_connector.v1.base import (
KVConnectorMetadata,
KVConnectorWorkerMetadata,
)
from vllm.v1.kv_offload.worker.worker import TransferSpec

ReqId = str

# A store entry bundles a scheduler-assigned job ID with the transfer spec.
# The job ID allows per-job completion tracking: the worker reports it back
# when the DMA finishes, and the scheduler calls complete_store immediately.
Comment thread
Etelis marked this conversation as resolved.
Outdated
StoreJobEntry = tuple[int, TransferSpec]


@dataclass
class OffloadingConnectorMetadata(KVConnectorMetadata):
reqs_to_load: dict[ReqId, TransferSpec]
reqs_to_store: dict[ReqId, TransferSpec]
reqs_to_store: dict[ReqId, StoreJobEntry]

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.

Can we switch to dict[int, StoreJobEntry where StoreJobEntry will contain ReqId?
Down the line we want to get rid of ReqId for stores.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

reqs_to_flush: set[str] | None = None

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.

Let's change this also to jobs_to_flush



@dataclass
class OffloadingWorkerMetadata(KVConnectorWorkerMetadata):
"""Worker -> Scheduler metadata for completed store jobs.

Each worker reports {job_id: 1} for newly completed stores.
aggregate() sums counts across workers within a step.
The scheduler accumulates across steps and processes
a store completion only when count reaches world_size.
"""

completed_store_jobs: dict[int, int]

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.

Can we also add completed_load_jobs?
We can use them to kick-off manager.complete_load(..) based on it, instead of relying on finished_recving.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sounds good, but in this case we will still not remove them from the queue self._reqs_being_loaded being loaded until we get finished_recving right?

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.

Don't think we'll need self._reqs_being_loaded.


def aggregate(
self, other: "KVConnectorWorkerMetadata"
) -> "KVConnectorWorkerMetadata":
assert isinstance(other, OffloadingWorkerMetadata)
merged = dict(self.completed_store_jobs)
for k, v in other.completed_store_jobs.items():
merged[k] = merged.get(k, 0) + v
return OffloadingWorkerMetadata(completed_store_jobs=merged)
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
from vllm.distributed.kv_transfer.kv_connector.v1.base import KVConnectorMetadata
from vllm.distributed.kv_transfer.kv_connector.v1.offloading.common import (
OffloadingConnectorMetadata,
OffloadingWorkerMetadata,
ReqId,
StoreJobEntry,
)
from vllm.logger import init_logger
from vllm.v1.core.kv_cache_manager import KVCacheBlocks
Expand Down Expand Up @@ -129,6 +131,16 @@ def __init__(self, spec: OffloadingSpec):
self._reqs_being_stored = defaultdict[ReqId, set[OffloadKey]](set)
self._reqs_being_loaded = defaultdict[ReqId, set[OffloadKey]](set)

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.

Can we remove this?
We should track everything with RequestOffloadState.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed.


# Per-job store completion tracking.
# Scheduler assigns job IDs so it can call complete_store per-job.
self._store_job_counter: int = 0

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.

can we name to _job_counter?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

self._store_job_hashes: dict[int, set[OffloadKey]] = {}
self._store_job_to_req: dict[int, ReqId] = {}

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.

Let's unify _store_job_hashes, _store_job_to_req, _store_job_pending_counts to a single dictionary dict[int, JobStatus

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done with a class.

self._req_to_jobs: dict[ReqId, set[int]] = defaultdict(set)

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.

Can we use RequestOffloadState.store_jobs instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

# Accumulate partial TP completions across steps.
self._expected_worker_count: int = spec.vllm_config.parallel_config.world_size
self._store_job_pending_counts: dict[int, int] = {}

def get_num_new_matched_tokens(
self, request: Request, num_computed_tokens: int
) -> tuple[int | None, bool]:
Expand Down Expand Up @@ -264,12 +276,14 @@ def update_state_after_alloc(
if self._blocks_being_loaded is not None:
self._blocks_being_loaded.update(req_blocks_being_loaded)

def _get_reqs_to_store(self, scheduler_output: SchedulerOutput):
def _get_reqs_to_store(
self, scheduler_output: SchedulerOutput
) -> dict[ReqId, StoreJobEntry]:
# Below assertion will be removed once this function supports HMA
assert len(self.config.kv_group_configs) == 1
group_config = self.config.kv_group_configs[0]

reqs_to_store: dict[ReqId, TransferSpec] = {}
reqs_to_store: dict[ReqId, StoreJobEntry] = {}
# iterate over both new and cached requests
for req_id, new_block_id_groups, preempted in yield_req_data(scheduler_output):
req_status = self._req_status[req_id]
Expand Down Expand Up @@ -332,18 +346,33 @@ def _get_reqs_to_store(self, scheduler_output: SchedulerOutput):
src_block_ids, group_sizes=(len(src_block_ids),)
)

reqs_to_store[req_id] = (src_spec, dst_spec)
# Assign a scheduler job ID for per-job completion tracking.
job_id = self._store_job_counter
self._store_job_counter += 1
self._store_job_hashes[job_id] = set(keys_to_store)
self._store_job_to_req[job_id] = req_id
self._req_to_jobs[req_id].add(job_id)

reqs_to_store[req_id] = (job_id, (src_spec, dst_spec))
self._reqs_being_stored[req_id] |= keys_to_store

logger.debug(
"Request %s offloading %s blocks starting from block #%d",
"Request %s offloading %s blocks starting from block #%d (job %d)",
req_id,
len(keys_to_store),
start_block_idx,
job_id,
)

return reqs_to_store

def _cleanup_store_jobs_for_req(self, req_id: ReqId) -> None:

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.

Don't think we need this.
jobs should only be cleaned up via OffloadingWorkerMetadata.completed_jobs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

"""Remove per-job tracking state for a given request."""
for jid in self._req_to_jobs.pop(req_id, ()):
self._store_job_hashes.pop(jid, None)
self._store_job_to_req.pop(jid, None)
self._store_job_pending_counts.pop(jid, None)

def build_connector_meta(
self, scheduler_output: SchedulerOutput
) -> KVConnectorMetadata:
Expand All @@ -361,6 +390,7 @@ def build_connector_meta(
if keys:
self.manager.complete_store(keys)
keys.clear()
self._cleanup_store_jobs_for_req(req_id)

return meta

Expand All @@ -372,10 +402,39 @@ def update_connector_output(self, connector_output: KVConnectorOutput):
connector_output (KVConnectorOutput): the worker-side
connectors output.
"""
# Process per-job store completions via worker metadata.
# Each worker reports {job_id: 1} when its DMA finishes.
# We accumulate across steps; once count >= world_size,
# the job is fully complete and we call complete_store.
meta = connector_output.kv_connector_worker_meta
if isinstance(meta, OffloadingWorkerMetadata):

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.

We should assert and remove this if.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

for job_id, count in meta.completed_store_jobs.items():
total = self._store_job_pending_counts.get(job_id, 0) + count

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.

Let's initialize at _expected_worker_count and decrease, instead of start from 0 and increase.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll remove it completely and use it in the class of the StoreJobStatus

if total >= self._expected_worker_count:

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.

Trying to decrease indentation level: negate the if and continue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

self._store_job_pending_counts.pop(job_id, None)
block_hashes = self._store_job_hashes.pop(job_id, None)
if block_hashes:
self.manager.complete_store(block_hashes)
req_id = self._store_job_to_req.pop(job_id, None)
if req_id is not None:
self._req_to_jobs.get(req_id, set()).discard(job_id)
remaining = self._reqs_being_stored.get(req_id)
if remaining is not None:
remaining -= block_hashes
# Keep the empty set so request_finished()
# still returns True, ensuring _free_blocks
# waits for finished_sending.
else:
self._store_job_pending_counts[job_id] = total

# Handle request-level completion (for _free_blocks in scheduler).
# If per-job tracking already cleared _reqs_being_stored, the pop
# returns None and complete_store is a no-op.
for req_id in connector_output.finished_sending or []:

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.

We should only inspect OffloadingWorkerMetadata.
finished_sending / finished_recving should be a don't care from our perspective here.

keys = self._reqs_being_stored.pop(req_id, None)
if keys:
self.manager.complete_store(keys)

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.

I don't think we need this either.
Also the same logic can be removed from build_connector_meta (when handling preemptions).

self._cleanup_store_jobs_for_req(req_id)

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.

Why do we need this?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So when we Preempt or finish a store request we don't actually keep the stale entry.


for req_id in connector_output.finished_recving or []:
keys = self._reqs_being_loaded.pop(req_id, None)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
)
from vllm.distributed.kv_transfer.kv_connector.v1.offloading.common import (
OffloadingConnectorMetadata,
OffloadingWorkerMetadata,
ReqId,
)
from vllm.distributed.kv_transfer.kv_connector.v1.offloading.metrics import (
Expand Down Expand Up @@ -59,6 +60,12 @@ def __init__(self, spec: OffloadingSpec):

self._finished_reqs_waiting_for_store: set[ReqId] = set()

# Maps internal worker job IDs to scheduler-assigned job IDs
# for per-job store completion reporting.
self._internal_to_sched_job: dict[int, int] = {}

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.

I think we can remove the internal ID and use the external ID from the scheduler instead.
Then we don't need this dictionary.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

They could then have collided ids, I'll make sure they both generate IDs from the same space.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll move the load jobs to the scheduler as well, it'll also simplify the worker further.

# Completed store jobs to report via build_connector_worker_meta.
self._completed_store_jobs: dict[int, int] = {}

def _generate_job_id(self) -> int:
job_id = self._job_counter
self._job_counter = job_id + 1
Expand Down Expand Up @@ -321,10 +328,12 @@ def start_kv_transfers(self, metadata: OffloadingConnectorMetadata):
assert success

def prepare_store_kv(self, metadata: OffloadingConnectorMetadata):
for req_id, transfer_spec in metadata.reqs_to_store.items():
for req_id, (sched_job_id, transfer_spec) in metadata.reqs_to_store.items():
job_id = self._generate_job_id()
self._jobs[job_id] = (req_id, True)
self._store_jobs[req_id].add(job_id)
# Map internal job ID to scheduler job ID for per-job reporting.
self._internal_to_sched_job[job_id] = sched_job_id
# NOTE(orozery): defer the store to the beginning of the next engine step,
# so that offloading starts AFTER transfers related to token sampling,
# thereby avoiding delays to token generation due to offloading.
Expand Down Expand Up @@ -358,6 +367,11 @@ def get_finished(self, finished_req_ids: set[str]) -> tuple[set[str], set[str]]:
transfer_type=transfer_result.transfer_type,
)
if store:
# Report per-job completion via worker metadata.
sched_job_id = self._internal_to_sched_job.pop(job_id, None)
if sched_job_id is not None:
self._completed_store_jobs[sched_job_id] = 1

req_jobs = self._store_jobs[req_id]
req_jobs.remove(job_id)
if req_jobs:
Expand All @@ -383,6 +397,16 @@ def get_finished(self, finished_req_ids: set[str]) -> tuple[set[str], set[str]]:

return finished_sending, finished_recving

def build_connector_worker_meta(self) -> OffloadingWorkerMetadata | None:
Comment thread
orozery marked this conversation as resolved.
"""Return completed store job IDs since the last call."""
if not self._completed_store_jobs:
return None
meta = OffloadingWorkerMetadata(
completed_store_jobs=self._completed_store_jobs,
)
self._completed_store_jobs = {}
return meta

def get_kv_connector_stats(self) -> KVConnectorStats | None:
"""
Get the KV transfer stats for the connector.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ def get_finished(self, finished_req_ids: set[str]) -> tuple[set[str], set[str]]:
assert self.connector_worker is not None
return self.connector_worker.get_finished(finished_req_ids)

def build_connector_worker_meta(self):

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.

nit: add return type hint

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done.

if self.connector_worker is not None:
return self.connector_worker.build_connector_worker_meta()
return None

def get_num_new_matched_tokens(
self, request: "Request", num_computed_tokens: int
) -> tuple[int | None, bool]:
Expand Down
Loading