3737"""
3838
3939import math
40+ import os
4041from collections .abc import Iterator
41- from dataclasses import dataclass , field
42+ from dataclasses import asdict , dataclass , field
4243from typing import TYPE_CHECKING , Any
4344
4445if TYPE_CHECKING :
4546 from vllm .config import VllmConfig
4647
48+ import ray
49+ import requests
4750import torch
4851
4952from vllm .config .weight_transfer import WeightTransferConfig
5356 WeightTransferUpdateInfo ,
5457)
5558from 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
5765logger = 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,88 @@ 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
342+ .visible_device_id_to_physical_device_id (device_index )
343+ )
344+ except Exception :
345+ absolute_gpu_id = device_index
346+
347+ fd = self ._client .receive_fd (
348+ self ._buffer_id ,
349+ gpu_id = absolute_gpu_id ,
285350 shard_index = shard_index ,
286351 total_shards = total_shards ,
287352 )
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- )
353+ device_ptr = self ._client .import_cuda_memory (
354+ fd ,
355+ self ._buffer_size ,
356+ device_id = device_index ,
357+ )
358+ self ._vram_buffer = self ._client .wrap_as_buffer (
359+ device_ptr ,
360+ self ._buffer_size ,
361+ )
307362
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- )
363+ # Connect to the notify socket for READY signals from NodePropagate
364+ self ._client .connect_notify_socket (
365+ self ._buffer_id ,
366+ shard_index = shard_index ,
367+ total_shards = total_shards ,
368+ )
369+ except Exception as e :
370+ self .shutdown ()
371+ raise e
314372
315373 logger .info (
316374 "WPI: Engine initialized — buffer=%s, size=%d bytes, "
@@ -324,18 +382,10 @@ def init_transfer_engine(self, init_info: WPIWeightTransferInitInfo) -> None:
324382
325383 def start_weight_update (self ) -> None :
326384 """Initialize layerwise reloading for the incoming checkpoint weights."""
327- from vllm .model_executor .model_loader .reload import (
328- initialize_layerwise_reload ,
329- )
330-
331385 initialize_layerwise_reload (self .model )
332386
333387 def finish_weight_update (self ) -> None :
334388 """Finalize layerwise reloading after all weights have been received."""
335- from vllm .model_executor .model_loader .reload import (
336- finalize_layerwise_reload ,
337- )
338-
339389 finalize_layerwise_reload (self .model , self .model_config )
340390
341391 def receive_weights (
@@ -359,9 +409,27 @@ def receive_weights(
359409 "WPI engine not initialized. Call init_transfer_engine() first."
360410 )
361411
412+ # Skip weight updates for other shards in tensor-parallel WPI
413+ if (
414+ update_info .total_shards > 0
415+ and update_info .shard_index != self ._shard_index
416+ ):
417+ logger .info (
418+ "WPI: Skipping weight update for shard %d (local shard index is %d)" ,
419+ update_info .shard_index ,
420+ self ._shard_index ,
421+ )
422+ return
423+
362424 # Wait for the WPI driver to signal that NCCL broadcast is complete
363425 self ._client .wait_for_ready (timeout = 300.0 )
364426
427+ logger .info (
428+ "WPI: Received weight update request. Params: %s, Shapes: %s" ,
429+ update_info .names ,
430+ update_info .shapes ,
431+ )
432+
365433 # Unpack tensors from the flat VRAM buffer and load incrementally
366434 for name , dtype_name , shape , offset in zip (
367435 update_info .names ,
@@ -391,7 +459,7 @@ def shutdown(self) -> None:
391459 if self ._client is not None :
392460 if self ._staged :
393461 try :
394- self ._client .unstage_weight (f" { self ._buffer_id } -claim" )
462+ self ._client .unstage_weight (self ._claim_id )
395463 except Exception as e :
396464 logger .warning ("WPI: Error during unstage: %s" , e )
397465 self ._staged = False
@@ -446,24 +514,46 @@ def trainer_init(
446514 shard_index = init_info .shard_index
447515 total_shards = init_info .total_shards
448516
517+ if total_shards > 0 :
518+ raise NotImplementedError (
519+ "Sharded WPI is temporarily disabled until updates are re-sliced. "
520+ "Please run in broadcast mode (total_shards=0)."
521+ )
522+
523+ # Round up size to an even number of bytes to avoid FP16
524+ # alignment issues in WPI driver
525+ buffer_size_bytes = (buffer_size_bytes + 1 ) // 2 * 2
526+
449527 client = WPIClient (socket_dir = socket_dir , driver_port = driver_port )
450528
529+ trainer_claim_id = f"{ buffer_id } -trainer-claim"
530+ if total_shards > 0 :
531+ trainer_claim_id = f"{ trainer_claim_id } __shard_{ shard_index } "
532+ else :
533+ trainer_claim_id = f"{ trainer_claim_id } __pid_{ os .getpid ()} "
534+
451535 # Stage the source buffer on the trainer's WPI driver
452536 client .stage_weight (
453537 buffer_id = buffer_id ,
454538 size_bytes = buffer_size_bytes ,
455- claim_id = f" { buffer_id } -trainer-claim" ,
539+ claim_id = trainer_claim_id ,
456540 shard_index = shard_index ,
457541 total_shards = total_shards ,
458542 )
459543
460544 # Receive FD and import CUDA memory on trainer GPU
461- import torch as _torch
545+ device_index = torch .accelerator .current_device_index ()
546+ try :
547+ absolute_gpu_id = (
548+ current_platform
549+ .visible_device_id_to_physical_device_id (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