@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 87aaadef73
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 15, 13:30 UTC

Serialize tensors using int8 views (#16866)

Signed-off-by: Staszek Pasko <staszek@gmail.com> Co-authored-by: Nick Hill <nhill@redhat.com>

Staszek Paśko committed Apr 19, 2025 at 19:28 UTC 87aaadef73543ab3e63eea933c39cee42c418e90
2 files changed +48 -13
tests/v1/test_serial_utils.py
+10 -6
@@ -47,6 +47,10 @@ def test_encode_decode():
47 torch.rand((1, 10), dtype=torch.float32),
48 torch.rand((3, 5, 4000), dtype=torch.float64),
49 torch.tensor(1984), # test scalar too
50 + # Make sure to test bf16 which numpy doesn't support.
51 + torch.rand((3, 5, 1000), dtype=torch.bfloat16),
52 + torch.tensor([float("-inf"), float("inf")] * 1024,
53 + dtype=torch.bfloat16),
54 ],
55 numpy_array=np.arange(512),
56 unrecognized=UnrecognizedType(33),
@@ -64,7 +68,7 @@ def test_encode_decode():
68 # There should be the main buffer + 4 large tensor buffers
69 # + 1 large numpy array. "large" is <= 512 bytes.
70 # The two small tensors are encoded inline.
67 - assert len(encoded) == 6
71 + assert len(encoded) == 8
72
73 decoded: MyType = decoder.decode(encoded)
74
@@ -76,7 +80,7 @@ def test_encode_decode():
80
81 encoded2 = encoder.encode_into(obj, preallocated)
82
79 - assert len(encoded2) == 6
83 + assert len(encoded2) == 8
84 assert encoded2[0] is preallocated
85
86 decoded2: MyType = decoder.decode(encoded2)
@@ -114,15 +118,15 @@ def test_multimodal_kwargs():
118
119 total_len = sum(memoryview(x).cast("B").nbytes for x in encoded)
120
117 - # expected total encoding length, should be 44536, +-20 for minor changes
118 - assert total_len >= 44516 and total_len <= 44556
121 + # expected total encoding length, should be 44559, +-20 for minor changes
122 + assert total_len >= 44539 and total_len <= 44579
123 decoded: MultiModalKwargs = decoder.decode(encoded).mm[0]
124 assert all(nested_equal(d[k], decoded[k]) for k in d)
125
126
127 def test_multimodal_items_by_modality():
124 - e1 = MultiModalFieldElem("audio", "a0", torch.zeros(1000,
125 - dtype=torch.int16),
128 + e1 = MultiModalFieldElem("audio", "a0",
129 + torch.zeros(1000, dtype=torch.bfloat16),
130 MultiModalBatchedField())
131 e2 = MultiModalFieldElem(
132 "video",
vllm/v1/serial_utils.py
+38 -7
@@ -80,7 +80,7 @@ class MsgpackEncoder:
80
81 def enc_hook(self, obj: Any) -> Any:
82 if isinstance(obj, torch.Tensor):
83 - return self._encode_ndarray(obj.numpy())
83 + return self._encode_tensor(obj)
84
85 # Fall back to pickle for object or void kind ndarrays.
86 if isinstance(obj, np.ndarray) and obj.dtype.kind not in ('O', 'V'):
@@ -133,9 +133,27 @@ class MsgpackEncoder:
133 # backing buffers that we've stashed in `aux_buffers`.
134 return obj.dtype.str, obj.shape, data
135
136 + def _encode_tensor(
137 + self, obj: torch.Tensor
138 + ) -> tuple[str, tuple[int, ...], Union[int, memoryview]]:
139 + assert self.aux_buffers is not None
140 + # this creates a copy of the tensor if it's not already contiguous
141 + obj = obj.contiguous()
142 + # view the tensor as a 1D array of bytes
143 + arr = obj.view((obj.numel(), )).view(torch.uint8).numpy()
144 + if obj.nbytes < self.size_threshold:
145 + # Smaller tensors are encoded inline, just like ndarrays.
146 + data = msgpack.Ext(CUSTOM_TYPE_RAW_VIEW, arr.data)
147 + else:
148 + # Otherwise encode index of backing buffer to avoid copy.
149 + data = len(self.aux_buffers)
150 + self.aux_buffers.append(arr.data)
151 + dtype = str(obj.dtype)[6:] # remove 'torch.' prefix
152 + return dtype, obj.shape, data
153 +
154 def _encode_nested_tensors(self, nt: NestedTensors) -> Any:
155 if isinstance(nt, torch.Tensor):
138 - return self._encode_ndarray(nt.numpy())
156 + return self._encode_tensor(nt)
157 if isinstance(nt, (int, float)):
158 # Although it violates NestedTensors type, MultiModalKwargs
159 # values are sometimes floats.
@@ -186,7 +204,7 @@ class MsgpackDecoder:
204 if issubclass(t, np.ndarray):
205 return self._decode_ndarray(obj)
206 if issubclass(t, torch.Tensor):
189 - return torch.from_numpy(self._decode_ndarray(obj))
207 + return self._decode_tensor(obj)
208 if issubclass(t, MultiModalKwargs):
209 if isinstance(obj, list):
210 return MultiModalKwargs.from_items(
@@ -199,11 +217,24 @@ class MsgpackDecoder:
217
218 def _decode_ndarray(self, arr: Any) -> np.ndarray:
219 dtype, shape, data = arr
202 - # Copy from inline representation, otherwise Torch is unhappy since
203 - # the returned memory is non-writeable.
220 + # zero-copy decode. We assume the ndarray will not be kept around,
221 + # as it now locks the whole received message buffer in memory.
222 + buffer = self.aux_buffers[data] if isinstance(data, int) else data
223 + return np.ndarray(buffer=buffer, dtype=np.dtype(dtype), shape=shape)
224 +
225 + def _decode_tensor(self, arr: Any) -> torch.Tensor:
226 + dtype, shape, data = arr
227 + # Copy from inline representation, to decouple the memory storage
228 + # of the message from the original buffer. And also make Torch
229 + # not complain about a readonly memoryview.
230 buffer = self.aux_buffers[data] if isinstance(data, int) \
231 else bytearray(data)
206 - return np.ndarray(buffer=buffer, dtype=np.dtype(dtype), shape=shape)
232 + # Create numpy wrapper around the bytes
233 + arr = np.ndarray(buffer=buffer, dtype=np.uint8, shape=(len(buffer), ))
234 + torch_dtype = getattr(torch, dtype)
235 + assert isinstance(torch_dtype, torch.dtype)
236 + # Convert back to proper shape & type
237 + return torch.from_numpy(arr).view(torch_dtype).view(shape)
238
239 def _decode_mm_items(self, obj: list) -> list[MultiModalKwargsItem]:
240 decoded_items = []
@@ -228,7 +259,7 @@ class MsgpackDecoder:
259 if not isinstance(obj, list):
260 raise TypeError(f"Unexpected NestedTensors contents: {type(obj)}")
261 if obj and isinstance(obj[0], str):
231 - return torch.from_numpy(self._decode_ndarray(obj))
262 + return self._decode_tensor(obj)
263 return [self._decode_nested_tensors(x) for x in obj]
264
265 def ext_hook(self, code: int, data: memoryview) -> Any: