main
py 130 lines 3.86 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """DMA copy backend for GPU<->CPU block transfers."""
4
5 from __future__ import annotations
6
7 import queue
8 import threading
9
10 import torch
11
12 from vllm.logger import init_logger
13 from vllm.platforms import current_platform
14 from vllm.v1.simple_kv_offload.cuda_mem_ops import (
15 CU_MEMCPY_SRC_ACCESS_ORDER_ANY,
16 CU_MEMCPY_SRC_ACCESS_ORDER_STREAM,
17 BatchMemcpyParams,
18 build_params,
19 copy_blocks,
20 )
21
22 logger = init_logger(__name__)
23
24
25 class DmaCopyBackend:
26 """cuMemcpyBatchAsync copy backend (background thread)."""
27
28 def __init__(self) -> None:
29 self._store_params: BatchMemcpyParams | None = None
30 self._load_params: BatchMemcpyParams | None = None
31 self._load_stream: torch.cuda.Stream | None = None
32 self._store_stream: torch.cuda.Stream | None = None
33 self._queue: queue.SimpleQueue | None = None
34 self._thread: threading.Thread | None = None
35 self._shutdown: bool = False
36
37 def init(
38 self,
39 gpu_caches: dict[str, torch.Tensor],
40 cpu_caches: dict[str, torch.Tensor],
41 device: torch.device,
42 load_stream: torch.cuda.Stream,
43 store_stream: torch.cuda.Stream,
44 ) -> None:
45 self._load_stream = load_stream
46 self._store_stream = store_stream
47
48 # Stores read the live KV cache -> STREAM (paired with the compute-done
49 # wait in get_finished); loads read stable pinned host memory -> ANY.
50 self._store_params = build_params(
51 gpu_caches,
52 cpu_caches,
53 store_stream,
54 src_access_order=CU_MEMCPY_SRC_ACCESS_ORDER_STREAM,
55 )
56 self._load_params = build_params(
57 cpu_caches,
58 gpu_caches,
59 load_stream,
60 src_access_order=CU_MEMCPY_SRC_ACCESS_ORDER_ANY,
61 )
62
63 self._queue = queue.SimpleQueue()
64 self._thread = threading.Thread(
65 target=self._copy_loop,
66 args=(self._queue, device, load_stream, store_stream),
67 daemon=True,
68 )
69 self._thread.start()
70
71 def launch_copy(
72 self,
73 src_blocks: list[int],
74 dst_blocks: list[int],
75 is_store: bool,
76 event_idx: int,
77 events_list: list[tuple[int, torch.Event]],
78 wait_event: torch.Event | None = None,
79 ) -> None:
80 params = self._store_params if is_store else self._load_params
81 assert params is not None and self._queue is not None
82 self._queue.put(
83 (
84 src_blocks,
85 dst_blocks,
86 params,
87 is_store,
88 event_idx,
89 events_list,
90 wait_event,
91 )
92 )
93
94 def shutdown(self) -> None:
95 if self._shutdown:
96 return
97 self._shutdown = True
98 if self._queue is not None:
99 self._queue.put(None)
100 if self._thread is not None:
101 self._thread.join(timeout=5.0)
102
103 @staticmethod
104 def _copy_loop(
105 q: queue.SimpleQueue,
106 device: torch.device,
107 load_stream: torch.cuda.Stream,
108 store_stream: torch.cuda.Stream,
109 ) -> None:
110 current_platform.set_device(device)
111 while True:
112 item = q.get()
113 if item is None:
114 return
115 (
116 src_blocks,
117 dst_blocks,
118 params,
119 is_store,
120 event_idx,
121 events_list,
122 wait_event,
123 ) = item
124 stream = store_stream if is_store else load_stream
125 if wait_event is not None:
126 stream.wait_event(wait_event)
127 copy_blocks(src_blocks, dst_blocks, params)
128 event = torch.Event()
129 event.record(stream)
130 events_list.append((event_idx, event))