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,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