Skip to content

Commit c48a483

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

2 files changed

Lines changed: 155 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: 154 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,89 @@ 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(
292+
"WPI: Re-initializing transfer engine. "
293+
"Shutting down old instance first."
294+
)
295+
self.shutdown()
296+
260297
self._buffer_id = init_info.buffer_id
261-
self._buffer_size = init_info.buffer_size_bytes
298+
# Round up size to an even number of bytes to avoid FP16
299+
# alignment issues in WPI driver
300+
self._buffer_size = (init_info.buffer_size_bytes + 1) // 2 * 2
262301
shard_index = init_info.shard_index
263302
total_shards = init_info.total_shards
264303

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,
304+
if total_shards > 0:
305+
raise NotImplementedError(
306+
"Sharded WPI is temporarily disabled until updates are re-sliced. "
307+
"Please run in broadcast mode (total_shards=0)."
272308
)
273309

310+
self._shard_index = shard_index
311+
self._total_shards = total_shards
312+
274313
self._client = WPIClient(
275314
socket_dir=init_info.socket_dir,
276315
driver_port=init_info.driver_port,
277316
)
278317

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",
318+
rank = getattr(self.parallel_config, "rank", 0)
319+
self._claim_id = f"{self._buffer_id}-claim"
320+
if total_shards > 0:
321+
self._claim_id = f"{self._claim_id}__shard_{shard_index}"
322+
else:
323+
self._claim_id = f"{self._claim_id}__rank_{rank}"
324+
325+
try:
326+
# Stage the empty receive buffer on the local WPI driver
327+
if not self._staged:
328+
self._client.stage_weight(
329+
buffer_id=self._buffer_id,
330+
size_bytes=self._buffer_size,
331+
claim_id=self._claim_id,
332+
shard_index=shard_index,
333+
total_shards=total_shards,
334+
)
335+
self._staged = True
336+
337+
# Receive FD and import CUDA memory
338+
device_index = torch.accelerator.current_device_index()
339+
try:
340+
absolute_gpu_id = (
341+
current_platform.visible_device_id_to_physical_device_id(
342+
device_index
343+
)
344+
)
345+
except Exception:
346+
absolute_gpu_id = device_index
347+
348+
fd = self._client.receive_fd(
349+
self._buffer_id,
350+
gpu_id=absolute_gpu_id,
285351
shard_index=shard_index,
286352
total_shards=total_shards,
287353
)
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-
)
354+
device_ptr = self._client.import_cuda_memory(
355+
fd,
356+
self._buffer_size,
357+
device_id=device_index,
358+
)
359+
self._vram_buffer = self._client.wrap_as_buffer(
360+
device_ptr,
361+
self._buffer_size,
362+
)
307363

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-
)
364+
# Connect to the notify socket for READY signals from NodePropagate
365+
self._client.connect_notify_socket(
366+
self._buffer_id,
367+
shard_index=shard_index,
368+
total_shards=total_shards,
369+
)
370+
except Exception as e:
371+
self.shutdown()
372+
raise e
314373

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

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

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

341392
def receive_weights(
@@ -359,9 +410,27 @@ def receive_weights(
359410
"WPI engine not initialized. Call init_transfer_engine() first."
360411
)
361412

413+
# Skip weight updates for other shards in tensor-parallel WPI
414+
if (
415+
update_info.total_shards > 0
416+
and update_info.shard_index != self._shard_index
417+
):
418+
logger.info(
419+
"WPI: Skipping weight update for shard %d (local shard index is %d)",
420+
update_info.shard_index,
421+
self._shard_index,
422+
)
423+
return
424+
362425
# Wait for the WPI driver to signal that NCCL broadcast is complete
363426
self._client.wait_for_ready(timeout=300.0)
364427

428+
logger.info(
429+
"WPI: Received weight update request. Params: %s, Shapes: %s",
430+
update_info.names,
431+
update_info.shapes,
432+
)
433+
365434
# Unpack tensors from the flat VRAM buffer and load incrementally
366435
for name, dtype_name, shape, offset in zip(
367436
update_info.names,
@@ -391,7 +460,7 @@ def shutdown(self) -> None:
391460
if self._client is not None:
392461
if self._staged:
393462
try:
394-
self._client.unstage_weight(f"{self._buffer_id}-claim")
463+
self._client.unstage_weight(self._claim_id)
395464
except Exception as e:
396465
logger.warning("WPI: Error during unstage: %s", e)
397466
self._staged = False
@@ -446,24 +515,45 @@ def trainer_init(
446515
shard_index = init_info.shard_index
447516
total_shards = init_info.total_shards
448517

518+
if total_shards > 0:
519+
raise NotImplementedError(
520+
"Sharded WPI is temporarily disabled until updates are re-sliced. "
521+
"Please run in broadcast mode (total_shards=0)."
522+
)
523+
524+
# Round up size to an even number of bytes to avoid FP16
525+
# alignment issues in WPI driver
526+
buffer_size_bytes = (buffer_size_bytes + 1) // 2 * 2
527+
449528
client = WPIClient(socket_dir=socket_dir, driver_port=driver_port)
450529

530+
trainer_claim_id = f"{buffer_id}-trainer-claim"
531+
if total_shards > 0:
532+
trainer_claim_id = f"{trainer_claim_id}__shard_{shard_index}"
533+
else:
534+
trainer_claim_id = f"{trainer_claim_id}__pid_{os.getpid()}"
535+
451536
# Stage the source buffer on the trainer's WPI driver
452537
client.stage_weight(
453538
buffer_id=buffer_id,
454539
size_bytes=buffer_size_bytes,
455-
claim_id=f"{buffer_id}-trainer-claim",
540+
claim_id=trainer_claim_id,
456541
shard_index=shard_index,
457542
total_shards=total_shards,
458543
)
459544

460545
# Receive FD and import CUDA memory on trainer GPU
461-
import torch as _torch
546+
device_index = torch.accelerator.current_device_index()
547+
try:
548+
absolute_gpu_id = current_platform.visible_device_id_to_physical_device_id(
549+
device_index
550+
)
551+
except Exception:
552+
absolute_gpu_id = device_index
462553

463-
device_index = _torch.accelerator.current_device_index()
464554
fd = client.receive_fd(
465555
buffer_id,
466-
gpu_id=device_index,
556+
gpu_id=absolute_gpu_id,
467557
shard_index=shard_index,
468558
total_shards=total_shards,
469559
)
@@ -474,9 +564,14 @@ def trainer_init(
474564
)
475565
vram_buffer = client.wrap_as_buffer(device_ptr, buffer_size_bytes)
476566

567+
effective_buffer_id = client._effective_buffer_id(
568+
buffer_id, shard_index, total_shards
569+
)
570+
477571
logger.info(
478-
"WPI: Trainer initialized — buffer=%s, size=%d, targets=%s",
572+
"WPI: Trainer initialized — buffer=%s (effective=%s), size=%d, targets=%s",
479573
buffer_id,
574+
effective_buffer_id,
480575
buffer_size_bytes,
481576
target_node_ids,
482577
)
@@ -487,6 +582,8 @@ def trainer_init(
487582
buffer_id=buffer_id,
488583
buffer_size_bytes=buffer_size_bytes,
489584
target_node_ids=target_node_ids or [],
585+
effective_buffer_id=effective_buffer_id,
586+
claim_id=trainer_claim_id,
490587
)
491588

492589
@staticmethod
@@ -600,34 +697,30 @@ def trainer_send_weights(
600697

601698
# --- Step 2: Trigger NCCL broadcast via WPI driver ---
602699
ctx.client.propagate(
603-
buffer_id=ctx.buffer_id,
700+
buffer_id=ctx.effective_buffer_id,
604701
target_node_ids=ctx.target_node_ids,
605702
)
606703

607704
logger.info("WPI trainer: NodePropagate complete")
608705

609706
# --- Step 3: Send metadata to vLLM workers ---
610-
from dataclasses import asdict
611-
612707
update_info = asdict(
613708
WPIWeightTransferUpdateInfo(
614709
names=names,
615710
dtype_names=dtype_names,
616711
shapes=shapes,
617712
offsets=offsets,
618713
total_bytes=total_bytes,
714+
shard_index=args.shard_index,
715+
total_shards=args.total_shards,
619716
)
620717
)
621718

622719
if args.send_mode == "ray":
623-
import ray
624-
625720
ray.get(
626721
args.llm_handle.update_weights.remote(dict(update_info=update_info))
627722
)
628723
elif args.send_mode == "http":
629-
import requests
630-
631724
url = f"{args.url}/update_weights"
632725
payload = {"update_info": update_info}
633726
response = requests.post(url, json=payload, timeout=300)

0 commit comments

Comments
 (0)