Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
f4c711a
x
hao-aaron May 22, 2026
89a06c6
x
hao-aaron May 22, 2026
2c6f684
x
hao-aaron May 28, 2026
d90fbc6
x
hao-aaron May 29, 2026
cddc347
experimental
hao-aaron Jun 3, 2026
651f6e0
baked first draft
hao-aaron Jun 3, 2026
19f4114
dry bake method
hao-aaron Jun 4, 2026
45e6eeb
remove deferred, simplify baked logic
hao-aaron Jun 4, 2026
1286553
remove rdt engine
hao-aaron Jun 4, 2026
bc06adb
bugfix
hao-aaron Jun 24, 2026
ecefa81
remove all or nothing bake path
hao-aaron Jun 25, 2026
7919818
x
hao-aaron Jun 25, 2026
d0fc27d
load balancing optimization
hao-aaron Jun 25, 2026
6b23a9d
vibecoded profiling stuff
hao-aaron Jun 26, 2026
3bfebb8
load from disk + memory registration
hao-aaron Jun 29, 2026
daf0df8
kimi multinode 8 train 8 inf
hao-aaron Jun 29, 2026
18c089c
better profiling and slice baking
hao-aaron Jul 1, 2026
18bd498
pipelined process and pull
hao-aaron Jul 1, 2026
8374293
revert custom all gather
hao-aaron Jul 7, 2026
710ebf1
coalecse rdt into one tensor send
hao-aaron Jul 8, 2026
03577fb
single buffer for multi dtype, fix race bug, record after scatter
hao-aaron Jul 8, 2026
da2a3fd
single update weights call, fully pipelined
hao-aaron Jul 8, 2026
2e14edc
restructure internal abstractions around chunks
hao-aaron Jul 9, 2026
a725416
assymetric consumer and producer
hao-aaron Jul 9, 2026
5f0b882
ipc
hao-aaron Jul 17, 2026
60c89d9
move fields into trainer init
hao-aaron Jul 17, 2026
d476172
remove backend str and attach to trainer init info
hao-aaron Jul 17, 2026
7181dd4
doc changes
hao-aaron Jul 20, 2026
bcc6692
nccl trainer engine
hao-aaron Jul 20, 2026
2db5418
add sparse
hao-aaron Jul 20, 2026
bc59d70
Merge origin/main into rdt-weight-sync
hao-aaron Jul 21, 2026
7478bb5
Merge origin/trainer-send-pr3-nccl into rdt-weight-sync
hao-aaron Jul 21, 2026
f28f199
update rdt to use trainer send engien
hao-aaron Jul 21, 2026
3d2222b
add unbind to rdt
hao-aaron Jul 22, 2026
3404110
rdt: route pulls per gather group so producers can gather within a PP…
hao-aaron Aug 5, 2026
ce39ae3
rdt: key the producer's packed-view cache by layout, not by spec names
hao-aaron Aug 5, 2026
d052c97
rdt: characterization tests for the consumer plan and producer protocol
hao-aaron Aug 5, 2026
5ff4d73
rdt: drop dead fields and correct stale references
hao-aaron Aug 5, 2026
88a4cf1
rdt: promote iter_groups and defers_processing onto the engine contracts
hao-aaron Aug 5, 2026
b14eff3
rdt: fix the op-allowlist drift and the residual pull's missing consu…
hao-aaron Aug 5, 2026
cb87173
rdt: collapse superseded generality in the consumer engine
hao-aaron Aug 5, 2026
d275d8e
rdt: extract the op-chain recording into sharded_rdt_lazy
hao-aaron Aug 5, 2026
624868b
rdt: add the doc page the backend never had
hao-aaron Aug 5, 2026
4e511df
examples: share the FSDP shard loader between the two RDT examples
hao-aaron Aug 5, 2026
4e57bcb
rdt: declare drain_pending alongside defers_processing
hao-aaron Aug 5, 2026
4012f81
rdt: normalize owned_groups, clear the pack cache on shutdown, 2-node…
hao-aaron Aug 6, 2026
50c7131
rdt: live-consumer free targets and a producer stall watchdog
hao-aaron Aug 7, 2026
a5c1cb9
rdt: mid-sync abort, an identity-keyed free ledger, and a rejoin-able…
hao-aaron Aug 10, 2026
5f28c99
Revert the mid-sync abort machinery; keep the rejoin enablers
hao-aaron Aug 11, 2026
c353338
Add EP-local expert serving to sharded RDT weight transfer
hao-aaron Aug 13, 2026
164b9c4
Credit the gather, not the publish, in sharded-RDT backpressure
hao-aaron Aug 13, 2026
b9f5f57
Document consumer memory sizing and the DP+EP configuration for shard…
hao-aaron Aug 13, 2026
bdd27a3
Merge branch 'main' into rdt-weight-sync
hao-aaron Aug 13, 2026
6a92bde
rdt: remove the profiling scaffolding
hao-aaron Aug 13, 2026
22694bd
rdt: remove the pack_check diagnostic
hao-aaron Aug 13, 2026
29da506
Bake models whose loaders feed never-meta params (GLM router bias)
hao-aaron Aug 13, 2026
0e4035e
examples: keep only the two sharded-RDT examples worth shipping
hao-aaron Aug 13, 2026
cbb33c1
tests: move the sharded-RDT trainer tests out of test_weight_transfer.py
hao-aaron Aug 13, 2026
e1562f4
rdt: make the comments concise
hao-aaron Aug 14, 2026
7e3923e
rdt: rename _BakedModule to _ModulePlan
hao-aaron Aug 14, 2026
777348d
docs: fix a _BakedModule reference the rename missed
hao-aaron Aug 14, 2026
19132db
rdt: merge _BakedCopy into _Scatter and record the produced dtype
hao-aaron Aug 14, 2026
5b5ab7e
rdt: give the engine a Data flow map, thin the per-type docstrings
hao-aaron Aug 14, 2026
0a2a6b0
rdt: trim the worker init-info field docs
hao-aaron Aug 14, 2026
de38822
rdt: delete the unbaked fallback, fail at init instead
hao-aaron Aug 14, 2026
bdeb04f
rdt: route by weight name, not by (group, ep_rank) coordinates
hao-aaron Aug 14, 2026
2e1d9cb
rdt: make the comments say what the code does, not what it replaced
hao-aaron Aug 14, 2026
3e108f0
rdt: fix the pre-commit failures
hao-aaron Aug 14, 2026
21713fb
doc changes
hao-aaron Aug 14, 2026
fe057c1
Key gather groups on the outermost index segment, not "model.layers."
hao-aaron Aug 14, 2026
f65cfd0
variance fix
hao-aaron Aug 16, 2026
65d7aec
sync fix
hao-aaron Aug 16, 2026
9f807c9
fix pp calculations and ray version check
hao-aaron Aug 17, 2026
21de6d9
memory leak fix
hao-aaron Aug 18, 2026
221386c
comments
hao-aaron Aug 20, 2026
bb25e20
replica duplication nixl buffers
hao-aaron Aug 21, 2026
b0b1543
Merge branch 'main' into rdt-weight-sync
hao-aaron Aug 21, 2026
fb33e27
comment fix
hao-aaron Aug 21, 2026
64c73bf
todo for fixing buffer memory
hao-aaron Aug 21, 2026
5ec2ef7
edit test
hao-aaron Aug 21, 2026
0e61f03
Merge branch 'main' into rdt-weight-sync
AndreasKaratzas Aug 21, 2026
24fb723
ray bump
hao-aaron Aug 22, 2026
b414dd1
Merge branch 'rdt-weight-sync' of https://github.com/hao-aaron/vllm i…
hao-aaron Aug 22, 2026
a3c5b97
test fixes
hao-aaron Aug 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
200 changes: 200 additions & 0 deletions examples/rl/rlhf_rdt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""
Demonstrates reinforcement learning using vLLM and Ray with the
RDT (Ray Direct Transport) weight transfer backend.

Compared to ``rlhf_nccl.py``, this example uses RDT's NIXL transport for the
trainer -> inference weight sync. Rather than setting up an out-of-band NCCL
group with master_address / master_port / rank_offset, the inference workers
look up the trainer Ray actor by name and pull each weight via an
``@ray.method(tensor_transport="nixl")`` accessor on the trainer.

Prerequisites:
pip install nixl

The script:
* Loads the training model on GPU 0 inside a Ray actor.
* Initializes vLLM across GPUs 1-2 (TP=2) with ``distributed_executor_backend="ray"``
(mandatory for the RDT backend -- workers must be Ray actors).
* Generates from a list of prompts with dummy weights (expected: gibberish).
* Syncs the trainer's weights into vLLM via the RDT engine.
* Generates again -- output should now be coherent.

This example assumes a single-node cluster with three GPUs.
"""

import os
import sys

import ray
from ray.util.placement_group import placement_group
from ray.util.scheduling_strategies import PlacementGroupSchedulingStrategy
from transformers import AutoModelForCausalLM

from vllm import LLM, SamplingParams
from vllm.config import WeightTransferConfig

MODEL_NAME = "facebook/opt-125m"
TRAINER_ACTOR_NAME = "rdt_trainer"
# Explicit namespace so the vLLM workers -- which run inside an EngineCore
# subprocess that does its own ray.init() -- can resolve the named trainer
# actor. With ray.init() in an anonymous namespace, the worker-side init
# would land in a *different* anonymous namespace and ray.get_actor would
# fail.
RAY_NAMESPACE = "rdt_example"


class MyLLM(LLM):
"""Configure the vLLM worker for Ray placement group execution."""

def __init__(self, *args, **kwargs):
os.environ["VLLM_RAY_BUNDLE_INDICES"] = "0,1"
super().__init__(*args, **kwargs)


@ray.remote(num_gpus=1, enable_tensor_transport=True)
class TrainModel:
"""Ray actor that wraps the training model and serves weights over RDT/NIXL."""

def __init__(self, model_name: str):
self.model = AutoModelForCausalLM.from_pretrained(model_name).to("cuda:0")
# Cache name -> Parameter mapping for O(1) lookups. PyTorch parameters
# mutate in place during training, so cached references stay valid.
self._param_lookup = dict(self.model.named_parameters())

@ray.method(tensor_transport="nixl")
def rdt_produce_weight(self, name: str):
"""Return the weight tensor for ``name``. NIXL handles the transport
when called from another Ray actor (i.e. a vLLM inference worker)."""
return self._param_lookup[name]

def get_weight_metadata(self):
"""Return weight names, dtypes, and shapes for the RDT update_info."""
names = []
dtype_names = []
shapes = []
for name, p in self.model.named_parameters():
names.append(name)
dtype_names.append(str(p.dtype).split(".")[-1])
shapes.append(list(p.shape))
return names, dtype_names, shapes


# Pin Ray-actor processes to the same Python interpreter as the driver. This
# matters on managed clusters where the default worker Python may differ from
# the venv that has vLLM + nixl installed. Also propagate selected env vars
# (NCCL config, LD_PRELOAD) so vLLM's TP=2 workers see the same setup as the
# driver -- needed on hosts where the default NCCL libs SEGV under cu13.
_RUNTIME_ENV: dict[str, object] = {"py_executable": sys.executable}
_FORWARDED_ENV_VARS = {
k: os.environ[k]
for k in ("NCCL_CUMEM_ENABLE", "VLLM_NCCL_SO_PATH", "LD_PRELOAD")
if k in os.environ
}
if _FORWARDED_ENV_VARS:
_RUNTIME_ENV["env_vars"] = _FORWARDED_ENV_VARS
ray.init(runtime_env=_RUNTIME_ENV, namespace=RAY_NAMESPACE)

# Trainer actor on GPU 0; named so inference workers can resolve it via
# ray.get_actor() during init_weight_transfer_engine.
train_model = TrainModel.options(name=TRAINER_ACTOR_NAME).remote(MODEL_NAME)

# Reserve GPUs 1-2 for the vLLM inference engine via a placement group.
pg_inference = placement_group([{"GPU": 1, "CPU": 0}] * 2)
ray.get(pg_inference.ready())
scheduling_inference = PlacementGroupSchedulingStrategy(
placement_group=pg_inference,
placement_group_capture_child_tasks=True,
placement_group_bundle_index=0,
)

# distributed_executor_backend="ray" is REQUIRED for the RDT backend: each
# vLLM worker must be a Ray actor so it can call ray.get_actor() and submit
# .remote() tasks against the trainer.
llm = ray.remote(
num_cpus=0,
num_gpus=0,
scheduling_strategy=scheduling_inference,
)(MyLLM).remote(
model=MODEL_NAME,
enforce_eager=True,
tensor_parallel_size=2,
data_parallel_size=1,
distributed_executor_backend="ray",
weight_transfer_config=WeightTransferConfig(backend="rdt"),
load_format="dummy",
quantization="fp8",
)

prompts = [
"Hello, my name is",
"The president of the United States is",
"The capital of France is",
"The future of AI is",
]

sampling_params = SamplingParams(temperature=0)

# First generation: dummy weights, output is expected to be nonsense.
outputs = ray.get(llm.generate.remote(prompts, sampling_params))
print("-" * 50)
print("Before weight sync (dummy weights):")
for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"Prompt: {prompt!r}\nGenerated text: {generated_text!r}")
print("-" * 50)

ray.get(llm.sleep.remote(level=0))

# Initialize the RDT engine on each worker. The only init_info needed is the
# trainer's named-actor handle -- no master_address/port, no world_size,
# no rank_offset.
ray.get(
llm.init_weight_transfer_engine.remote(
dict(
init_info=dict(
trainer_actor_name=TRAINER_ACTOR_NAME,
trainer_actor_namespace=RAY_NAMESPACE,
)
)
)
)

# Gather per-weight metadata from the trainer to feed update_info. The actual
# tensors are NOT sent here -- they're pulled by each worker during
# update_weights below.
names, dtype_names, shapes = ray.get(train_model.get_weight_metadata.remote())

ray.get(llm.start_weight_update.remote(is_checkpoint_format=True))

# update_weights triggers the per-worker pull. Each worker iterates through
# `names`, calls `train_model.rdt_produce_weight.remote(name)`, ray.gets the
# resulting NIXL-transferred tensor, hands it to load_weights, and drops the
# reference before pulling the next one. There is no separate trainer-side
# broadcast call -- the trainer's @ray.method serves on demand.
ray.get(
llm.update_weights.remote(
dict(
update_info=dict(
names=names,
dtype_names=dtype_names,
shapes=shapes,
)
)
)
)

ray.get(llm.finish_weight_update.remote())
ray.get(llm.wake_up.remote(tags=["scheduling"]))

# Second generation: output should now be coherent.
outputs_updated = ray.get(llm.generate.remote(prompts, sampling_params))
print("-" * 50)
print("After weight sync (trainer weights pulled via RDT/NIXL):")
for output in outputs_updated:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"Prompt: {prompt!r}\nGenerated text: {generated_text!r}")
print("-" * 50)
Loading
Loading