utils.py 6.26 KB
Newer Older
1
2
# SPDX-License-Identifier: Apache-2.0

Robert Shaw's avatar
Robert Shaw committed
3
import multiprocessing
4
5
import os
import weakref
6
from collections import defaultdict
7
from collections.abc import Sequence
8
9
from typing import (TYPE_CHECKING, Any, Callable, Generic, Optional, TypeVar,
                    Union, overload)
10
11

import torch
12
13

from vllm.logger import init_logger
14
from vllm.model_executor.models.utils import extract_layer_index
15
from vllm.utils import get_mp_context, kill_process_tree
16

17
18
19
if TYPE_CHECKING:
    from vllm.attention.layer import Attention

20
logger = init_logger(__name__)
21
22
23
24

T = TypeVar("T")


25
class ConstantList(Generic[T], Sequence):
26

27
    def __init__(self, x: list[T]) -> None:
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
        self._x = x

    def append(self, item):
        raise Exception("Cannot append to a constant list")

    def extend(self, item):
        raise Exception("Cannot extend a constant list")

    def insert(self, item):
        raise Exception("Cannot insert into a constant list")

    def pop(self, item):
        raise Exception("Cannot pop from a constant list")

    def remove(self, item):
        raise Exception("Cannot remove from a constant list")

    def clear(self):
        raise Exception("Cannot clear a constant list")

48
49
50
51
52
53
    def index(self,
              item: T,
              start: int = 0,
              stop: Optional[int] = None) -> int:
        return self._x.index(item, start,
                             stop if stop is not None else len(self._x))
54
55

    @overload
56
    def __getitem__(self, item: int) -> T:
57
58
59
        ...

    @overload
60
    def __getitem__(self, s: slice, /) -> list[T]:
61
62
        ...

63
    def __getitem__(self, item: Union[int, slice]) -> Union[T, list[T]]:
64
65
66
        return self._x[item]

    @overload
67
    def __setitem__(self, item: int, value: T):
68
69
70
        ...

    @overload
71
    def __setitem__(self, s: slice, value: T, /):
72
73
        ...

74
    def __setitem__(self, item: Union[int, slice], value: Union[T, list[T]]):
75
76
77
78
79
80
81
82
83
84
85
86
87
        raise Exception("Cannot set item in a constant list")

    def __delitem__(self, item):
        raise Exception("Cannot delete item from a constant list")

    def __iter__(self):
        return iter(self._x)

    def __contains__(self, item):
        return item in self._x

    def __len__(self):
        return len(self._x)
88

89
90
91
    def __repr__(self):
        return f"ConstantList({self._x})"

92

93
94
95
96
97
98
99
100
101
102
103
104
class BackgroundProcHandle:
    """
    Utility class to handle creation, readiness, and shutdown
    of background processes used by the AsyncLLM and LLMEngine.
    """

    def __init__(
        self,
        input_path: str,
        output_path: str,
        process_name: str,
        target_fn: Callable,
105
        process_kwargs: dict[Any, Any],
106
107
108
109
110
111
112
113
114
115
116
    ):
        context = get_mp_context()
        reader, writer = context.Pipe(duplex=False)

        assert ("ready_pipe" not in process_kwargs
                and "input_path" not in process_kwargs
                and "output_path" not in process_kwargs)
        process_kwargs["ready_pipe"] = writer
        process_kwargs["input_path"] = input_path
        process_kwargs["output_path"] = output_path

Robert Shaw's avatar
Robert Shaw committed
117
        # Run busy loop in background process.
118
        self.proc = context.Process(target=target_fn, kwargs=process_kwargs)
Robert Shaw's avatar
Robert Shaw committed
119
120
        self._finalizer = weakref.finalize(self, shutdown, self.proc,
                                           input_path, output_path)
121
122
123
124
125
126
127
128
        self.proc.start()

        # Wait for startup.
        if reader.recv()["status"] != "READY":
            raise RuntimeError(f"{process_name} initialization failed. "
                               "See root cause above.")

    def shutdown(self):
Robert Shaw's avatar
Robert Shaw committed
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
        self._finalizer()


# Note(rob): shutdown function cannot be a bound method,
# else the gc cannot collect the object.
def shutdown(proc: multiprocessing.Process, input_path: str, output_path: str):
    # Shutdown the process.
    if proc.is_alive():
        proc.terminate()
        proc.join(5)

        if proc.is_alive():
            kill_process_tree(proc.pid)

    # Remove zmq ipc socket files.
    ipc_sockets = [output_path, input_path]
    for ipc_socket in ipc_sockets:
        socket_file = ipc_socket.replace("ipc://", "")
        if os and os.path.exists(socket_file):
            os.remove(socket_file)
149
150
151


def bind_kv_cache(
152
153
154
    kv_caches: dict[str, torch.Tensor],
    forward_context: dict[str, "Attention"],
    runner_kv_caches: list[torch.Tensor],
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
) -> None:
    """
    Bind the allocated KV cache to both ModelRunner and forward context so
    that the KV cache can be used in the forward pass.

    This function:
      1) Fills the ModelRunner's kv cache list (`runner_kv_caches`) with
         kv_caches.
      2) Associates each attention layer in the `forward_context` with its 
         corresponding KV cache in kv_caches.

    Args:
        kv_caches: The allocated kv_caches with layer names as keys.
        forward_context: The global forward context containing all Attention 
        layers with layer names as keys.
        runner_kv_caches: The kv_cache declared by ModelRunner.
    """
    # Bind kv_caches to ModelRunner
    assert len(runner_kv_caches) == 0

    # Convert kv_caches dict to a list of tensors in the order of layer_index.
    index2name = defaultdict(list)
    for layer_name in kv_caches:
        index2name[extract_layer_index(layer_name)].append(layer_name)

    for layer_index in sorted(index2name.keys()):
        layer_names = index2name[layer_index]
        if len(layer_names) > 1:
            # One typical case is encoder-decoder model, e.g., bart.
            # The cross attention and self attention in the same decoder layer
            # has different layer_name but the same layer_index.
            raise NotImplementedError
        layer_name = layer_names[0]
        runner_kv_caches.append(kv_caches[layer_name])

    # Bind kv_caches to forward context
    for layer_name, kv_cache in kv_caches.items():
        # NOTE: Use list because of v0 PP virtual engine.
        forward_context[layer_name].kv_cache = [kv_cache]
194
195
196


def copy_slice(from_tensor: torch.Tensor, to_tensor: torch.Tensor,
197
               length: int) -> torch.Tensor:
198
199
200
201
202
    """
    Copy the first length elements of a tensor into another tensor in a
    non-blocking manner.

    Used to copy pinned CPU tensor data to pre-allocated GPU tensors.
203
204

    Returns the sliced target tensor.
205
    """
206
    return to_tensor[:length].copy_(from_tensor[:length], non_blocking=True)