59a0a3844c
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
PR Test AMD / finish (push) Has been cancelled
PR Test NVIDIA / finish (push) Has been cancelled
PR Test NVIDIA ARM / finish (push) Has been cancelled
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
567 lines
24 KiB
Python
567 lines
24 KiB
Python
# Copyright (c) 2026 LightSeek Foundation
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
|
|
import dataclasses
|
|
import threading
|
|
import time
|
|
from contextlib import contextmanager
|
|
from typing import Any
|
|
|
|
import numpy as np
|
|
import numpy.typing as npt
|
|
|
|
from tokenspeed.runtime.pd.base.status import TransferPoll
|
|
from tokenspeed.runtime.pd.mooncake.entities import (
|
|
KVArgs,
|
|
TransferInfo,
|
|
TransferKVChunk,
|
|
)
|
|
from tokenspeed.runtime.pd.mooncake.prefill import (
|
|
MooncakeKVManagerPrefill as MooncakeKVManager,
|
|
)
|
|
from tokenspeed.runtime.pd.utils import (
|
|
DisaggregationMode,
|
|
FastQueue,
|
|
StepCounter,
|
|
group_concurrent_contiguous,
|
|
)
|
|
from tokenspeed.runtime.utils import get_colorful_logger
|
|
from tokenspeed.runtime.utils.server_args import ServerArgs
|
|
|
|
logger = get_colorful_logger(__name__)
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class WriteRequest:
|
|
trans_info: TransferInfo
|
|
dst_ranks_info: tuple[str, int, int]
|
|
prefill_kv_blocks: npt.NDArray[np.int64]
|
|
dst_kv_blocks: npt.NDArray[np.int64]
|
|
submit_bids: list[int] = dataclasses.field(default_factory=list)
|
|
|
|
|
|
@dataclasses.dataclass
|
|
class LayerWiseTask:
|
|
kv_chunk: TransferKVChunk
|
|
begin_cache_step: int
|
|
aux_step: int
|
|
next_layer_id: int = 0
|
|
write_requests: list[WriteRequest] = dataclasses.field(default_factory=list)
|
|
polls: list[bool] = dataclasses.field(default_factory=list)
|
|
|
|
|
|
class MooncakeAsyncKVManager(MooncakeKVManager):
|
|
def __init__(
|
|
self,
|
|
args: KVArgs,
|
|
disaggregation_mode: DisaggregationMode,
|
|
server_args: ServerArgs,
|
|
is_mla_backend: bool | None = False,
|
|
draft_is_mla_backend: bool | None = False,
|
|
):
|
|
super().__init__(
|
|
args, disaggregation_mode, server_args, is_mla_backend, draft_is_mla_backend
|
|
)
|
|
self.target_layer_num = self.kv_args.target_layer_num
|
|
self.draft_layer_num = self.kv_args.draft_layer_num
|
|
self.layer_num = self.target_layer_num + self.draft_layer_num
|
|
self.is_mla_backend = is_mla_backend
|
|
self.draft_is_mla_backend = draft_is_mla_backend
|
|
self.kv_cache_quant_method = server_args.kv_cache_quant_method
|
|
self.submit_interval = server_args.disaggregation_layerwise_interval
|
|
if self.submit_interval <= 0:
|
|
raise ValueError("submit_interval must be positive")
|
|
self.current_transfer_batch: list[tuple[TransferKVChunk, int]] = []
|
|
self.offsets = args.offsets
|
|
|
|
def _init_offsets(self):
|
|
if self.is_mla_backend:
|
|
if self.kv_cache_quant_method == "per_token_head":
|
|
target_offsets = [
|
|
[i * self.target_layer_num + layer_id for i in range(3)]
|
|
for layer_id in range(self.target_layer_num)
|
|
]
|
|
draft_offsets = [
|
|
[3 * self.target_layer_num + layer_id]
|
|
for layer_id in range(self.draft_layer_num)
|
|
]
|
|
else:
|
|
target_offsets = [
|
|
[layer_id] for layer_id in range(self.target_layer_num)
|
|
]
|
|
draft_offsets = [
|
|
[self.target_layer_num + layer_id]
|
|
for layer_id in range(self.draft_layer_num)
|
|
]
|
|
else:
|
|
target_offsets = [
|
|
[i * self.layer_num + layer_id for i in range(2)]
|
|
for layer_id in range(self.target_layer_num)
|
|
]
|
|
draft_offsets = [
|
|
[
|
|
i * self.layer_num + self.target_layer_num + layer_id
|
|
for i in range(2)
|
|
]
|
|
for layer_id in range(self.draft_layer_num)
|
|
]
|
|
self.offsets = target_offsets + draft_offsets
|
|
|
|
def start_transfer_thread(
|
|
self, transfer_thread_pool_size: int, transfer_queue_size: int
|
|
):
|
|
self.transfer_queues: list[FastQueue] = [FastQueue()]
|
|
for queue in self.transfer_queues:
|
|
threading.Thread(
|
|
target=self.async_transfer_worker, args=(queue,), daemon=True
|
|
).start()
|
|
|
|
def register_step_counter(self, step_counter: StepCounter):
|
|
self.step_counter = step_counter
|
|
|
|
@contextmanager
|
|
def add_batch(self, is_idle: bool):
|
|
yield # add transfer request
|
|
|
|
if not is_idle:
|
|
begin_cache_step, begin_aux_step = self.step_counter.current_step()
|
|
for kv_chunk, shard_idx in self.current_transfer_batch:
|
|
self.transfer_queues[shard_idx].put(
|
|
LayerWiseTask(
|
|
kv_chunk=kv_chunk,
|
|
begin_cache_step=begin_cache_step,
|
|
aux_step=begin_aux_step if kv_chunk.is_last else None,
|
|
)
|
|
)
|
|
|
|
# advance to step of next batch no matter if batch is empty
|
|
self.step_counter.advance_step(
|
|
delta_cache_step=self.layer_num, delta_aux_step=1
|
|
)
|
|
|
|
self.current_transfer_batch.clear()
|
|
|
|
def add_transfer_request(
|
|
self,
|
|
bootstrap_room: int,
|
|
kv_indices: npt.NDArray[np.int64],
|
|
index_slice: slice,
|
|
is_last: bool,
|
|
aux_index: int | None = None,
|
|
mla_l1_5_args: tuple[Any, Any] | None = None,
|
|
):
|
|
logger.debug("async manager add_transfer_request")
|
|
if self.disaggregation_mode != DisaggregationMode.PREFILL:
|
|
raise RuntimeError("Transfer requests can only be added in prefill mode.")
|
|
if is_last and aux_index is None:
|
|
raise ValueError("aux_index must be set for the last transfer chunk.")
|
|
|
|
if (
|
|
bootstrap_room not in self.request_status
|
|
or self.check_status(bootstrap_room) == TransferPoll.Failed
|
|
):
|
|
logger.debug(
|
|
"Request with bootstrap_room=%s already failed", bootstrap_room
|
|
)
|
|
return
|
|
|
|
if bootstrap_room not in self.transfer_infos:
|
|
# This means that the current rank is a dummy rank for this request,
|
|
# and it has already been marked as success, so there is no need to
|
|
# add further chunks into the transfer queue.
|
|
return
|
|
|
|
# sharding according to the dst_infos to make sure
|
|
# requests with the same dst_sessions will be added into the same
|
|
# queue, which enables early abort with failed sessions.
|
|
dst_infos = self.transfer_infos[bootstrap_room].keys()
|
|
session_port_sum = sum(int(session.split(":")[1]) for session in dst_infos)
|
|
shard_idx = session_port_sum % len(self.transfer_queues)
|
|
|
|
kv_chunk = TransferKVChunk(
|
|
room=bootstrap_room,
|
|
prefill_kv_indices=kv_indices,
|
|
index_slice=index_slice,
|
|
is_last=is_last,
|
|
prefill_aux_index=aux_index,
|
|
mla_l1_5_args=mla_l1_5_args,
|
|
)
|
|
self.current_transfer_batch.append((kv_chunk, shard_idx))
|
|
|
|
def submit_aux(
|
|
self,
|
|
mooncake_session_id: str,
|
|
prefill_aux_index: int,
|
|
dst_aux_ptrs: list[int],
|
|
dst_aux_index: int,
|
|
):
|
|
# Submit transfer for all aux buffers (output_ids, logprobs, cached_tokens, etc.)
|
|
batch_ids = []
|
|
for aux_data_ptr, aux_item_len, dst_aux_ptr in zip(
|
|
self.kv_args.aux_data_ptrs, self.kv_args.aux_item_lens, dst_aux_ptrs
|
|
):
|
|
prefill_aux_addr = aux_data_ptr + prefill_aux_index * aux_item_len
|
|
decode_aux_addr = dst_aux_ptr + dst_aux_index * aux_item_len
|
|
bid = self.engine.transfer_submit_write(
|
|
mooncake_session_id, prefill_aux_addr, decode_aux_addr, aux_item_len
|
|
)
|
|
batch_ids.append(bid)
|
|
# The public interface returns a single batch id, so expose the last
|
|
# submitted id even though this helper may issue multiple writes.
|
|
return batch_ids[-1] if batch_ids else -1
|
|
|
|
def _transfer_data(self, mooncake_session_id, transfer_blocks):
|
|
if not transfer_blocks:
|
|
return 0
|
|
|
|
src_addrs, dst_addrs, lengths = zip(*transfer_blocks)
|
|
return self.engine.batch_transfer_sync(
|
|
mooncake_session_id, list(src_addrs), list(dst_addrs), list(lengths)
|
|
)
|
|
|
|
def submit_layer_cache(
|
|
self,
|
|
mooncake_session_id: str,
|
|
begin_layer_id: int, # [begin_layer_id, end_layer_id)
|
|
end_layer_id: int,
|
|
prefill_kv_blocks: npt.NDArray[np.int64],
|
|
dst_kv_blocks: npt.NDArray[np.int64],
|
|
) -> int:
|
|
dst_kv_ptrs = self.decode_kv_args_table[mooncake_session_id].dst_kv_ptrs
|
|
transfer_blocks = []
|
|
|
|
def submit_one_cache(ptr_offset):
|
|
src_ptr = self.kv_args.kv_data_ptrs[ptr_offset]
|
|
dst_ptr = dst_kv_ptrs[ptr_offset]
|
|
item_len = self.kv_args.kv_item_lens[ptr_offset]
|
|
for prefill_index, decode_index in zip(prefill_kv_blocks, dst_kv_blocks):
|
|
src_addr = src_ptr + int(prefill_index[0]) * item_len
|
|
dst_addr = dst_ptr + int(decode_index[0]) * item_len
|
|
length = item_len * len(prefill_index)
|
|
transfer_blocks.append((src_addr, dst_addr, length))
|
|
|
|
for layer_id in range(begin_layer_id, end_layer_id):
|
|
for offset in self.offsets[layer_id]:
|
|
submit_one_cache(offset)
|
|
return self._transfer_data(mooncake_session_id, transfer_blocks)
|
|
|
|
def async_transfer_worker(self, transfer_queue: FastQueue):
|
|
def discard_finished_bid_inplace(submit_bids: list[int]):
|
|
finished_cnt = 0
|
|
failed = False
|
|
for bid in submit_bids:
|
|
status = self.engine.transfer_check_status(bid)
|
|
if status == 1:
|
|
finished_cnt += 1
|
|
elif status == -1:
|
|
failed = True
|
|
if self.kv_transfer_metrics:
|
|
self.kv_transfer_metrics.record_kv_transfer_timeout()
|
|
logger.error("Transfer timeout detected!")
|
|
break
|
|
elif status == -2:
|
|
failed = True
|
|
if self.kv_transfer_metrics:
|
|
self.kv_transfer_metrics.record_kv_transfer_failure()
|
|
logger.error("Transfer failed detected")
|
|
break
|
|
else:
|
|
failed = status != 0
|
|
break
|
|
submit_bids[:] = submit_bids[finished_cnt:]
|
|
return not failed
|
|
|
|
def discard_tasks(
|
|
tasks: list[LayerWiseTask], dropped: list[LayerWiseTask]
|
|
) -> list[LayerWiseTask]:
|
|
dropped_rooms = set(id(task) for task in dropped)
|
|
return [task for task in tasks if id(task) not in dropped_rooms]
|
|
|
|
def query_ready_step(task: LayerWiseTask) -> tuple[int, int]:
|
|
if task.next_layer_id < self.layer_num:
|
|
ready_cache_step = self.step_counter.query_ready_cache_step()
|
|
if not StepCounter.is_step_ready(
|
|
ready_cache_step, task.begin_cache_step + task.next_layer_id
|
|
):
|
|
time.sleep(1e-3)
|
|
elif task.kv_chunk.is_last:
|
|
ready_aux_step = self.step_counter.query_ready_aux_step()
|
|
if not StepCounter.is_step_ready(ready_aux_step, task.aux_step):
|
|
time.sleep(1e-3)
|
|
|
|
ready_cache_step = self.step_counter.query_ready_cache_step()
|
|
ready_aux_step = self.step_counter.query_ready_aux_step()
|
|
return ready_cache_step, ready_aux_step
|
|
|
|
def get_new_tasks(blocking: bool) -> list[LayerWiseTask]:
|
|
new_tasks: list[LayerWiseTask] = []
|
|
if blocking:
|
|
new_tasks.append(transfer_queue.get())
|
|
while True:
|
|
try:
|
|
new_tasks.append(transfer_queue.get_nowait())
|
|
except FastQueue.Empty:
|
|
break
|
|
|
|
return new_tasks
|
|
|
|
def initialize(tasks: list[LayerWiseTask]) -> list[LayerWiseTask]:
|
|
abort_tasks: list[LayerWiseTask] = []
|
|
|
|
for task in tasks:
|
|
kv_chunk = task.kv_chunk
|
|
reqs_to_be_processed = (
|
|
self.transfer_infos[kv_chunk.room].values()
|
|
if kv_chunk.room in self.transfer_infos
|
|
else []
|
|
)
|
|
|
|
for req in reqs_to_be_processed:
|
|
if req.is_dummy:
|
|
task.polls.append(True)
|
|
abort_tasks.append(task)
|
|
break
|
|
|
|
with self.session_lock:
|
|
if self._is_session_failed(req.mooncake_session_id):
|
|
logger.info(
|
|
"Blocked transfer due to failed session (room=%s, session=%s).",
|
|
kv_chunk.room,
|
|
req.mooncake_session_id,
|
|
)
|
|
self.record_failure(
|
|
kv_chunk.room,
|
|
f"Decode instance could be dead, remote mooncake session {req.mooncake_session_id} is not alive",
|
|
)
|
|
task.polls.append(False)
|
|
abort_tasks.append(task)
|
|
break
|
|
|
|
resolved = self.resolve_transfer_indices(kv_chunk, req)
|
|
|
|
# Group by indices
|
|
prefill_kv_blocks, dst_kv_blocks = group_concurrent_contiguous(
|
|
resolved.src_indices, resolved.dst_indices
|
|
)
|
|
task.write_requests.append(
|
|
WriteRequest(
|
|
trans_info=req,
|
|
dst_ranks_info=(req.endpoint, req.dst_port, req.room),
|
|
prefill_kv_blocks=prefill_kv_blocks,
|
|
dst_kv_blocks=dst_kv_blocks,
|
|
)
|
|
)
|
|
|
|
return abort_tasks
|
|
|
|
def submit_transfer(
|
|
tasks: list[LayerWiseTask], ready_cache_step: int, ready_aux_step: int
|
|
) -> tuple[list[LayerWiseTask], list[LayerWiseTask]]:
|
|
abort_tasks: list[LayerWiseTask] = []
|
|
complete_tasks: list[LayerWiseTask] = []
|
|
|
|
for task in tasks:
|
|
kv_chunk = task.kv_chunk
|
|
# submit layer cache
|
|
if task.next_layer_id < self.layer_num and StepCounter.is_step_ready(
|
|
ready_cache_step, task.begin_cache_step + task.next_layer_id
|
|
):
|
|
for req in task.write_requests:
|
|
if (
|
|
(task.next_layer_id + 1) % self.submit_interval == 0
|
|
or task.next_layer_id == self.layer_num - 1
|
|
):
|
|
ret = self.submit_layer_cache(
|
|
req.trans_info.mooncake_session_id,
|
|
(task.next_layer_id // self.submit_interval)
|
|
* self.submit_interval,
|
|
task.next_layer_id + 1,
|
|
req.prefill_kv_blocks,
|
|
req.dst_kv_blocks,
|
|
)
|
|
|
|
if ret != 0:
|
|
if self.kv_transfer_metrics:
|
|
self.kv_transfer_metrics.record_kv_transfer_failure()
|
|
logger.error("Transfer failed detected!")
|
|
task.polls.append(False)
|
|
abort_tasks.append(task)
|
|
|
|
with self.session_lock:
|
|
self.session_failures[
|
|
req.trans_info.mooncake_session_id
|
|
] += 1
|
|
# Failures should never happen if the session is not dead, if the session fails once, mark it as failed
|
|
if (
|
|
self.session_failures[
|
|
req.trans_info.mooncake_session_id
|
|
]
|
|
>= 1
|
|
):
|
|
self._mark_session_failed(
|
|
req.trans_info.mooncake_session_id,
|
|
reason="submit_layer_cache",
|
|
)
|
|
logger.error(
|
|
"Session %s failed.",
|
|
req.trans_info.mooncake_session_id,
|
|
)
|
|
self.record_failure(
|
|
kv_chunk.room,
|
|
f"Failed to send kv chunk of {kv_chunk.room} to {req.trans_info.endpoint}:{req.trans_info.dst_port}",
|
|
)
|
|
break
|
|
|
|
task.next_layer_id += 1
|
|
|
|
# submit aux data
|
|
if (
|
|
kv_chunk.is_last
|
|
and task.aux_step is not None
|
|
and StepCounter.is_step_ready(ready_aux_step, task.aux_step)
|
|
):
|
|
task.aux_step = None # reset to None to mark aux has been submitted
|
|
if kv_chunk.is_last:
|
|
for req in task.write_requests:
|
|
aux_bid = self.submit_aux(
|
|
req.trans_info.mooncake_session_id,
|
|
kv_chunk.prefill_aux_index,
|
|
self.decode_kv_args_table[
|
|
req.trans_info.mooncake_session_id
|
|
].dst_aux_ptrs,
|
|
req.trans_info.dst_aux_index,
|
|
)
|
|
req.submit_bids.append(aux_bid)
|
|
|
|
if task.next_layer_id == self.layer_num and task.aux_step is None:
|
|
complete_tasks.append(task)
|
|
|
|
return complete_tasks, abort_tasks
|
|
|
|
def pop_transferred(tasks: list[LayerWiseTask]) -> list[LayerWiseTask]:
|
|
complete_tasks: list[LayerWiseTask] = []
|
|
for task in tasks:
|
|
kv_chunk = task.kv_chunk
|
|
for req in task.write_requests[
|
|
len(task.polls) :
|
|
]: # only check the uncompleted requests
|
|
success = discard_finished_bid_inplace(req.submit_bids)
|
|
if success:
|
|
if len(req.submit_bids) == 0:
|
|
task.polls.append(True)
|
|
else:
|
|
task.polls.append(False)
|
|
with self.session_lock:
|
|
self.session_failures[
|
|
req.trans_info.mooncake_session_id
|
|
] += 1
|
|
# Failures should never happen if the session is not dead, if the session fails once, mark it as failed
|
|
if (
|
|
self.session_failures[
|
|
req.trans_info.mooncake_session_id
|
|
]
|
|
>= 1
|
|
):
|
|
self._mark_session_failed(
|
|
req.trans_info.mooncake_session_id,
|
|
reason="submit_status",
|
|
)
|
|
logger.error(
|
|
"Session %s failed.",
|
|
req.trans_info.mooncake_session_id,
|
|
)
|
|
self.record_failure(
|
|
kv_chunk.room,
|
|
f"Failed to send kv chunk of {kv_chunk.room} to {req.trans_info.endpoint}:{req.trans_info.dst_port}",
|
|
)
|
|
break
|
|
|
|
# all finished or any failed
|
|
if len(task.polls) == len(task.write_requests) or (
|
|
len(task.polls) > 0 and not all(task.polls)
|
|
):
|
|
complete_tasks.append(task)
|
|
|
|
return complete_tasks
|
|
|
|
def finalize(tasks: list[LayerWiseTask]) -> None:
|
|
for task in tasks:
|
|
kv_chunk = task.kv_chunk
|
|
status = (
|
|
TransferPoll.Success if all(task.polls) else TransferPoll.Failed
|
|
)
|
|
if (
|
|
status == TransferPoll.Failed or kv_chunk.is_last
|
|
): # last chunk or any failed
|
|
self.update_status(kv_chunk.room, status)
|
|
for packed_req in task.write_requests:
|
|
endpoint, dst_port, room = packed_req.dst_ranks_info
|
|
self.sync_status_to_decode_endpoint(
|
|
endpoint, dst_port, room, status, self.attn_tp_rank
|
|
)
|
|
|
|
if (
|
|
kv_chunk.room not in self.request_status
|
|
or self.check_status(kv_chunk.room) == TransferPoll.Success
|
|
):
|
|
if kv_chunk.room in self.transfer_infos:
|
|
self.transfer_infos.pop(kv_chunk.room)
|
|
|
|
pending_tasks: list[LayerWiseTask] = []
|
|
inflight_tasks: list[LayerWiseTask] = []
|
|
while True:
|
|
try:
|
|
if (
|
|
len(
|
|
new_tasks := get_new_tasks(
|
|
blocking=(len(pending_tasks) + len(inflight_tasks) == 0)
|
|
)
|
|
)
|
|
> 0
|
|
):
|
|
if len(abort_tasks := initialize(new_tasks)) > 0:
|
|
finalize(abort_tasks)
|
|
new_tasks = discard_tasks(new_tasks, abort_tasks)
|
|
|
|
pending_tasks.extend(new_tasks)
|
|
|
|
if len(pending_tasks) > 0:
|
|
ready_cache_step, ready_aux_step = query_ready_step(
|
|
pending_tasks[0]
|
|
) # only wait the first task
|
|
submited_tasks, abort_tasks = submit_transfer(
|
|
pending_tasks, ready_cache_step, ready_aux_step
|
|
)
|
|
finalize(abort_tasks)
|
|
pending_tasks = discard_tasks(
|
|
pending_tasks, submited_tasks + abort_tasks
|
|
)
|
|
inflight_tasks.extend(submited_tasks)
|
|
|
|
if len(complete_tasks := pop_transferred(inflight_tasks)) > 0:
|
|
finalize(complete_tasks)
|
|
inflight_tasks = discard_tasks(inflight_tasks, complete_tasks)
|
|
|
|
except Exception as exc:
|
|
raise RuntimeError(
|
|
f"Transfer thread failed because of {exc}. Prefill instance "
|
|
f"with bootstrap_port={self.bootstrap_port} is dead."
|
|
) from exc
|