Skip to content

vllm.distributed.weight_transfer.clients

Built-in VLLMWeightSyncClient implementations.

These adapt the inference engine's weight-sync control plane to concrete transports. A TrainerWeightTransferEngine takes one of these (or any object with the same four methods — the protocol is structural) and drives the full handshake through it.

Imports of ray / requests are deferred to call time so this module is importable without those packages installed.

Classes:

HTTPVLLMWeightSyncClient

Talks to a vLLM server over the RLHF HTTP routes.

Mirrors vllm/entrypoints/serve/dev/rlhf/api_router.py: /init_weight_transfer_engine, /start_weight_update, /update_weights, /finish_weight_update.

Source code in vllm/distributed/weight_transfer/clients.py
class HTTPVLLMWeightSyncClient:
    """Talks to a vLLM server over the RLHF HTTP routes.

    Mirrors `vllm/entrypoints/serve/dev/rlhf/api_router.py`:
    `/init_weight_transfer_engine`, `/start_weight_update`, `/update_weights`,
    `/finish_weight_update`.
    """

    def __init__(self, base_url: str, timeout: float = 300) -> None:
        self.base_url = base_url.rstrip("/")
        self.timeout = timeout

    def _post(self, path: str, json: dict[str, Any] | None = None) -> None:
        import requests

        response = requests.post(
            f"{self.base_url}/{path}", json=json, timeout=self.timeout
        )
        response.raise_for_status()

    def init_weight_transfer_engine(self, init_info: dict[str, Any]) -> None:
        self._post("init_weight_transfer_engine", {"init_info": init_info})

    def start_weight_update(self) -> None:
        self._post("start_weight_update")

    def update_weights(self, update_info: dict[str, Any]) -> None:
        self._post(
            "update_weights", {"update_info": _json_safe_update_info(update_info)}
        )

    def finish_weight_update(self) -> None:
        self._post("finish_weight_update")

RayVLLMWeightSyncClient

Talks to one or more vLLM AsyncLLM/LLM Ray actors.

Each call fans out to every handle and blocks on all of them, so a multi-actor (e.g. multi-DP) deployment is driven as one unit.

Source code in vllm/distributed/weight_transfer/clients.py
class RayVLLMWeightSyncClient:
    """Talks to one or more vLLM `AsyncLLM`/`LLM` Ray actors.

    Each call fans out to every handle and blocks on all of them, so a
    multi-actor (e.g. multi-DP) deployment is driven as one unit.
    """

    def __init__(self, handle: "ActorHandle | list[ActorHandle]") -> None:
        self.handles = handle if isinstance(handle, list) else [handle]

    def init_weight_transfer_engine(self, init_info: dict[str, Any]) -> None:
        import ray

        request = WeightTransferInitRequest(init_info=init_info)
        ray.get([h.init_weight_transfer_engine.remote(request) for h in self.handles])

    def start_weight_update(self) -> None:
        import ray

        ray.get([h.start_weight_update.remote() for h in self.handles])

    def update_weights(self, update_info: dict[str, Any]) -> None:
        import ray

        request = WeightTransferUpdateRequest(update_info=update_info)
        ray.get([h.update_weights.remote(request) for h in self.handles])

    def finish_weight_update(self) -> None:
        import ray

        ray.get([h.finish_weight_update.remote() for h in self.handles])

_json_safe_update_info(update_info)

Make an update_info dict JSON-serializable for HTTP transport.

CUDA IPC handles (ipc_handles) are tuples of non-JSON-native objects, so over HTTP they are pickled+base64-encoded into ipc_handles_pickled (which the worker auto-deserializes when VLLM_ALLOW_INSECURE_SERIALIZATION=1). Other backends (NCCL) carry only JSON-native metadata and pass through unchanged. Mirrors the old IPC _do_send HTTP branch.

Source code in vllm/distributed/weight_transfer/clients.py
def _json_safe_update_info(update_info: dict[str, Any]) -> dict[str, Any]:
    """Make an update_info dict JSON-serializable for HTTP transport.

    CUDA IPC handles (`ipc_handles`) are tuples of non-JSON-native objects, so
    over HTTP they are pickled+base64-encoded into `ipc_handles_pickled` (which
    the worker auto-deserializes when `VLLM_ALLOW_INSECURE_SERIALIZATION=1`).
    Other backends (NCCL) carry only JSON-native metadata and pass through
    unchanged. Mirrors the old IPC `_do_send` HTTP branch.
    """
    ipc_handles = update_info.get("ipc_handles")
    if ipc_handles is None:
        return update_info

    import pickle

    import pybase64 as base64

    out = {k: v for k, v in update_info.items() if k != "ipc_handles"}
    out["ipc_handles_pickled"] = base64.b64encode(pickle.dumps(ipc_handles)).decode(
        "utf-8"
    )
    return out