Skip to content

Commit 7061e0b

Browse files
committed
address comments
Signed-off-by: Bill Du <yangspirit@google.com>
1 parent 320fb1d commit 7061e0b

2 files changed

Lines changed: 140 additions & 63 deletions

File tree

docs/training/weight_transfer/wpi.md

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,7 @@ llm.init_weight_transfer_engine(
4848
buffer_size_bytes=20 * 1024**3, # 20 GiB (must hold all weights)
4949
socket_dir="/run/wpi/sockets",
5050
driver_port=50051,
51-
# Optional sharding config for tensor parallel:
52-
# shard_index=-1 (defaults to tp_rank if total_shards > 0)
51+
# Note: sharded WPI is temporarily disabled; keep total_shards=0
5352
# total_shards=0
5453
)
5554
)

vllm/distributed/weight_transfer/wpi_engine.py

Lines changed: 139 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,16 @@
3737
"""
3838

3939
import math
40+
import os
4041
from collections.abc import Iterator
41-
from dataclasses import dataclass, field
42+
from dataclasses import asdict, dataclass, field
4243
from typing import TYPE_CHECKING, Any
4344

4445
if TYPE_CHECKING:
4546
from vllm.config import VllmConfig
4647

48+
import ray
49+
import requests
4750
import torch
4851

4952
from vllm.config.weight_transfer import WeightTransferConfig
@@ -53,6 +56,11 @@
5356
WeightTransferUpdateInfo,
5457
)
5558
from vllm.logger import init_logger
59+
from vllm.model_executor.model_loader.reload import (
60+
finalize_layerwise_reload,
61+
initialize_layerwise_reload,
62+
)
63+
from vllm.platforms import current_platform
5664

5765
logger = init_logger(__name__)
5866

@@ -70,6 +78,19 @@ class WPITrainerContext:
7078
buffer_id: str
7179
buffer_size_bytes: int
7280
target_node_ids: list[str]
81+
effective_buffer_id: str = ""
82+
claim_id: str = ""
83+
84+
def close(self) -> None:
85+
"""Release the staged trainer VRAM buffer and close the client."""
86+
if self.client is not None:
87+
if self.claim_id:
88+
try:
89+
self.client.unstage_weight(self.claim_id)
90+
except Exception as e:
91+
logger.warning("WPI: Error during trainer unstage: %s", e)
92+
self.client.close()
93+
self.client = None
7394

7495

7596
@dataclass
@@ -171,6 +192,12 @@ class WPIWeightTransferUpdateInfo(WeightTransferUpdateInfo):
171192
total_bytes: int = 0
172193
"""Total bytes packed into the buffer."""
173194

195+
shard_index: int = -1
196+
"""Shard index of the update."""
197+
198+
total_shards: int = 0
199+
"""Total number of shards of the update."""
200+
174201
def __post_init__(self):
175202
num_params = len(self.names)
176203
if len(self.dtype_names) != num_params:
@@ -241,6 +268,9 @@ def __init__(
241268
self._buffer_id: str = ""
242269
self._buffer_size: int = 0
243270
self._staged: bool = False
271+
self._shard_index: int = -1
272+
self._total_shards: int = 0
273+
self._claim_id: str = ""
244274

245275
def init_transfer_engine(self, init_info: WPIWeightTransferInitInfo) -> None:
246276
"""Initialize WPI driver connection and stage the persistent VRAM buffer.
@@ -257,60 +287,81 @@ def init_transfer_engine(self, init_info: WPIWeightTransferInitInfo) -> None:
257287
"""
258288
WPIClient = _import_wpi_client()
259289

290+
if self._client is not None:
291+
logger.info("WPI: Re-initializing transfer engine. Shutting down old instance first.")
292+
self.shutdown()
293+
260294
self._buffer_id = init_info.buffer_id
261-
self._buffer_size = init_info.buffer_size_bytes
295+
# Round up size to an even number of bytes to avoid FP16 alignment issues in WPI driver
296+
self._buffer_size = (init_info.buffer_size_bytes + 1) // 2 * 2
262297
shard_index = init_info.shard_index
263298
total_shards = init_info.total_shards
264299

265-
# Map tp_rank to shard_index if sharding is requested but index not set
266-
if total_shards > 0 and shard_index < 0:
267-
shard_index = self.parallel_config.rank
268-
logger.info(
269-
"WPI: Auto-mapping tp_rank=%d to shard_index=%d",
270-
self.parallel_config.rank,
271-
shard_index,
300+
if total_shards > 0:
301+
raise NotImplementedError(
302+
"Sharded WPI is temporarily disabled until updates are re-sliced. "
303+
"Please run in broadcast mode (total_shards=0)."
272304
)
273305

306+
self._shard_index = shard_index
307+
self._total_shards = total_shards
308+
274309
self._client = WPIClient(
275310
socket_dir=init_info.socket_dir,
276311
driver_port=init_info.driver_port,
277312
)
278313

279-
# Stage the empty receive buffer on the local WPI driver
280-
if not self._staged:
281-
self._client.stage_weight(
282-
buffer_id=self._buffer_id,
283-
size_bytes=self._buffer_size,
284-
claim_id=f"{self._buffer_id}-claim",
314+
rank = getattr(self.parallel_config, "rank", 0)
315+
self._claim_id = f"{self._buffer_id}-claim"
316+
if total_shards > 0:
317+
self._claim_id = f"{self._claim_id}__shard_{shard_index}"
318+
else:
319+
self._claim_id = f"{self._claim_id}__rank_{rank}"
320+
321+
try:
322+
# Stage the empty receive buffer on the local WPI driver
323+
if not self._staged:
324+
self._client.stage_weight(
325+
buffer_id=self._buffer_id,
326+
size_bytes=self._buffer_size,
327+
claim_id=self._claim_id,
328+
shard_index=shard_index,
329+
total_shards=total_shards,
330+
)
331+
self._staged = True
332+
333+
# Receive FD and import CUDA memory
334+
device_index = torch.accelerator.current_device_index()
335+
try:
336+
absolute_gpu_id = current_platform.visible_device_id_to_physical_device_id(device_index)
337+
except Exception:
338+
absolute_gpu_id = device_index
339+
340+
fd = self._client.receive_fd(
341+
self._buffer_id,
342+
gpu_id=absolute_gpu_id,
285343
shard_index=shard_index,
286344
total_shards=total_shards,
287345
)
288-
self._staged = True
289-
290-
# Receive FD and import CUDA memory
291-
device_index = torch.accelerator.current_device_index()
292-
fd = self._client.receive_fd(
293-
self._buffer_id,
294-
gpu_id=device_index,
295-
shard_index=shard_index,
296-
total_shards=total_shards,
297-
)
298-
device_ptr = self._client.import_cuda_memory(
299-
fd,
300-
self._buffer_size,
301-
device_id=device_index,
302-
)
303-
self._vram_buffer = self._client.wrap_as_buffer(
304-
device_ptr,
305-
self._buffer_size,
306-
)
346+
device_ptr = self._client.import_cuda_memory(
347+
fd,
348+
self._buffer_size,
349+
device_id=device_index,
350+
)
351+
self._vram_buffer = self._client.wrap_as_buffer(
352+
device_ptr,
353+
self._buffer_size,
354+
)
307355

308-
# Connect to the notify socket for READY signals from NodePropagate
309-
self._client.connect_notify_socket(
310-
self._buffer_id,
311-
shard_index=shard_index,
312-
total_shards=total_shards,
313-
)
356+
# Connect to the notify socket for READY signals from NodePropagate
357+
self._client.connect_notify_socket(
358+
self._buffer_id,
359+
shard_index=shard_index,
360+
total_shards=total_shards,
361+
)
362+
except Exception as e:
363+
self.shutdown()
364+
raise e
314365

315366
logger.info(
316367
"WPI: Engine initialized — buffer=%s, size=%d bytes, "
@@ -324,18 +375,10 @@ def init_transfer_engine(self, init_info: WPIWeightTransferInitInfo) -> None:
324375

325376
def start_weight_update(self) -> None:
326377
"""Initialize layerwise reloading for the incoming checkpoint weights."""
327-
from vllm.model_executor.model_loader.reload import (
328-
initialize_layerwise_reload,
329-
)
330-
331378
initialize_layerwise_reload(self.model)
332379

333380
def finish_weight_update(self) -> None:
334381
"""Finalize layerwise reloading after all weights have been received."""
335-
from vllm.model_executor.model_loader.reload import (
336-
finalize_layerwise_reload,
337-
)
338-
339382
finalize_layerwise_reload(self.model, self.model_config)
340383

341384
def receive_weights(
@@ -359,9 +402,23 @@ def receive_weights(
359402
"WPI engine not initialized. Call init_transfer_engine() first."
360403
)
361404

405+
# Skip weight updates for other shards in tensor-parallel WPI
406+
if (
407+
update_info.total_shards > 0
408+
and update_info.shard_index != self._shard_index
409+
):
410+
logger.info(
411+
"WPI: Skipping weight update for shard %d (local shard index is %d)",
412+
update_info.shard_index,
413+
self._shard_index,
414+
)
415+
return
416+
362417
# Wait for the WPI driver to signal that NCCL broadcast is complete
363418
self._client.wait_for_ready(timeout=300.0)
364419

420+
logger.info("WPI: Received weight update request. Params: %s, Shapes: %s", update_info.names, update_info.shapes)
421+
365422
# Unpack tensors from the flat VRAM buffer and load incrementally
366423
for name, dtype_name, shape, offset in zip(
367424
update_info.names,
@@ -391,7 +448,7 @@ def shutdown(self) -> None:
391448
if self._client is not None:
392449
if self._staged:
393450
try:
394-
self._client.unstage_weight(f"{self._buffer_id}-claim")
451+
self._client.unstage_weight(self._claim_id)
395452
except Exception as e:
396453
logger.warning("WPI: Error during unstage: %s", e)
397454
self._staged = False
@@ -446,24 +503,42 @@ def trainer_init(
446503
shard_index = init_info.shard_index
447504
total_shards = init_info.total_shards
448505

506+
if total_shards > 0:
507+
raise NotImplementedError(
508+
"Sharded WPI is temporarily disabled until updates are re-sliced. "
509+
"Please run in broadcast mode (total_shards=0)."
510+
)
511+
512+
# Round up size to an even number of bytes to avoid FP16 alignment issues in WPI driver
513+
buffer_size_bytes = (buffer_size_bytes + 1) // 2 * 2
514+
449515
client = WPIClient(socket_dir=socket_dir, driver_port=driver_port)
450516

517+
trainer_claim_id = f"{buffer_id}-trainer-claim"
518+
if total_shards > 0:
519+
trainer_claim_id = f"{trainer_claim_id}__shard_{shard_index}"
520+
else:
521+
trainer_claim_id = f"{trainer_claim_id}__pid_{os.getpid()}"
522+
451523
# Stage the source buffer on the trainer's WPI driver
452524
client.stage_weight(
453525
buffer_id=buffer_id,
454526
size_bytes=buffer_size_bytes,
455-
claim_id=f"{buffer_id}-trainer-claim",
527+
claim_id=trainer_claim_id,
456528
shard_index=shard_index,
457529
total_shards=total_shards,
458530
)
459531

460532
# Receive FD and import CUDA memory on trainer GPU
461-
import torch as _torch
533+
device_index = torch.accelerator.current_device_index()
534+
try:
535+
absolute_gpu_id = current_platform.visible_device_id_to_physical_device_id(device_index)
536+
except Exception:
537+
absolute_gpu_id = device_index
462538

463-
device_index = _torch.accelerator.current_device_index()
464539
fd = client.receive_fd(
465540
buffer_id,
466-
gpu_id=device_index,
541+
gpu_id=absolute_gpu_id,
467542
shard_index=shard_index,
468543
total_shards=total_shards,
469544
)
@@ -474,9 +549,14 @@ def trainer_init(
474549
)
475550
vram_buffer = client.wrap_as_buffer(device_ptr, buffer_size_bytes)
476551

552+
effective_buffer_id = client._effective_buffer_id(
553+
buffer_id, shard_index, total_shards
554+
)
555+
477556
logger.info(
478-
"WPI: Trainer initialized — buffer=%s, size=%d, targets=%s",
557+
"WPI: Trainer initialized — buffer=%s (effective=%s), size=%d, targets=%s",
479558
buffer_id,
559+
effective_buffer_id,
480560
buffer_size_bytes,
481561
target_node_ids,
482562
)
@@ -487,6 +567,8 @@ def trainer_init(
487567
buffer_id=buffer_id,
488568
buffer_size_bytes=buffer_size_bytes,
489569
target_node_ids=target_node_ids or [],
570+
effective_buffer_id=effective_buffer_id,
571+
claim_id=trainer_claim_id,
490572
)
491573

492574
@staticmethod
@@ -600,34 +682,30 @@ def trainer_send_weights(
600682

601683
# --- Step 2: Trigger NCCL broadcast via WPI driver ---
602684
ctx.client.propagate(
603-
buffer_id=ctx.buffer_id,
685+
buffer_id=ctx.effective_buffer_id,
604686
target_node_ids=ctx.target_node_ids,
605687
)
606688

607689
logger.info("WPI trainer: NodePropagate complete")
608690

609691
# --- Step 3: Send metadata to vLLM workers ---
610-
from dataclasses import asdict
611-
612692
update_info = asdict(
613693
WPIWeightTransferUpdateInfo(
614694
names=names,
615695
dtype_names=dtype_names,
616696
shapes=shapes,
617697
offsets=offsets,
618698
total_bytes=total_bytes,
699+
shard_index=args.shard_index,
700+
total_shards=args.total_shards,
619701
)
620702
)
621703

622704
if args.send_mode == "ray":
623-
import ray
624-
625705
ray.get(
626706
args.llm_handle.update_weights.remote(dict(update_info=update_info))
627707
)
628708
elif args.send_mode == "http":
629-
import requests
630-
631709
url = f"{args.url}/update_weights"
632710
payload = {"update_info": update_info}
633711
response = requests.post(url, json=payload, timeout=300)

0 commit comments

Comments
 (0)