Files
modelscope--ms-swift/swift/ray/megatron/rollout/adapter.py
T
wehub-resource-sync a203934033
Lint test / lint (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:34:58 +08:00

124 lines
4.7 KiB
Python

# Copyright (c) ModelScope Contributors. All rights reserved.
from __future__ import annotations
import asyncio
import logging
import ray
import time
import torch
from typing import Any, Generator, List, Optional, Tuple
logger = logging.getLogger(__name__)
def _is_ipc_supported() -> bool:
"""Check if CUDA IPC is supported (GPU=True, NPU=fallback to SHM)."""
from transformers.utils import is_torch_npu_available
return not is_torch_npu_available()
class RolloutAdapter:
def __init__(
self,
*,
replica_rank: int = 0,
rollout_rank: int = 0,
bucket_size_mb: int = 2048,
):
self.replica_rank = replica_rank
self.rollout_rank = rollout_rank
self.bucket_size_mb = bucket_size_mb
self.is_primary = (rollout_rank == 0)
self.use_shm = not _is_ipc_supported()
self.zmq_handle = (f'ipc:///tmp/swift-rollout-zmq-replica-{replica_rank}-rank-{rollout_rank}.sock')
self._server_handle = None
# Persistent CUDA IPC buffer reused across all update_weights() syncs so the IPC
# handle stays stable (the vLLM worker's mapping cache hits) and no IPC mapping
# leaks per step.
self._ipc_buffer: Optional[torch.Tensor] = None
@property
def server_handle(self) -> Any:
if self._server_handle is None:
self._server_handle = ray.get_actor(f'swift_rollout_server_{self.replica_rank}_0')
return self._server_handle
def update_weights(
self,
weight_iter: Generator[Tuple[str, torch.Tensor], None, None],
*,
vllm_lora_param_names: Optional[set] = None,
peft_config: Optional[dict] = None,
base_sync_done: bool = False,
) -> None:
"""Push training weights to vLLM via ZMQ IPC.
Only the primary rank (rollout_rank == 0) sends weights;
other ranks are no-op.
Args:
weight_iter: Iterator of (name, tensor) from bridge.export_weights.
vllm_lora_param_names: When set, remap dense param names to
LoRA-wrapped names (*.base_layer.weight) for full-weight sync
with vllm_enable_lora.
peft_config: When provided with base_sync_done=True, vLLM loads
weights as a LoRA adapter via TensorLoRARequest.
base_sync_done: Indicates this is an adapter-only sync after
the initial full weight sync has completed.
"""
if vllm_lora_param_names:
from swift.rlhf_trainers.utils import add_base_layer_suffix_by_param_names
weight_iter = add_base_layer_suffix_by_param_names(weight_iter, vllm_lora_param_names)
if not self.is_primary:
for _ in weight_iter:
pass
return
from ..rollout.weight_transfer import BucketedWeightSender
start_time = time.time()
# Lazily (re)allocate the persistent IPC buffer; reused across syncs so the
# handle signature stays stable and the worker-side IPC cache hits.
external_buffer = None
if not self.use_shm:
from swift.utils import get_current_device
bucket_size = self.bucket_size_mb << 20
if self._ipc_buffer is None or self._ipc_buffer.numel() < bucket_size:
self._ipc_buffer = torch.empty(bucket_size, dtype=torch.uint8, device=get_current_device())
external_buffer = self._ipc_buffer
async def _do_ipc_sync():
sender = BucketedWeightSender(
zmq_handle=self.zmq_handle,
bucket_size_mb=self.bucket_size_mb,
use_shm=self.use_shm,
external_buffer=external_buffer,
)
try:
async with sender:
rpc_ref = self.server_handle.update_weights_ipc.remote(self.zmq_handle, self.use_shm, 600,
peft_config, base_sync_done)
await sender.handshake()
await sender.send_weights(weight_iter)
await asyncio.get_running_loop().run_in_executor(None, ray.get, rpc_ref)
finally:
sender.cleanup()
asyncio.run(_do_ipc_sync())
logger.debug('RolloutAdapter: update_weights done (replica=%d, adapter_only=%s, %.2fs)', self.replica_rank,
base_sync_done,
time.time() - start_time)
def reset_prefix_cache(self) -> None:
if not self.is_primary:
return
ray.get(self.server_handle.reset_prefix_cache.remote())
def get_model_param_names(self) -> List[str]:
if not self.is_primary:
return []
return ray.get(self.server_handle.get_model_param_names.remote()) or []