Ray 分布式框架
一句话总结
Ray 是一个通用的分布式计算框架,通过 Task(无状态远程函数)和 Actor(有状态远程对象)两个核心原语,让 Python 程序员用最少的代码改动就能将单机程序扩展到集群。vLLM 用它做多卡推理调度,verl 用它编排 PPO 的 rollout 与 training 流程。
在大模型体系中的位置
┌─────────────────────────────────────────────────────────────┐
│ LLM Engineering Stack │
│ │
│ Model Design → Distributed Training → Inference → Serving │
│ ↑ ↑ │
│ Ray Train Ray Serve │
│ ↑ ↑ │
│ ┌──────────────────────────────┐ │
│ │ Ray Core (Actor / Task) │ ← 你在这里 │
│ └──────────────────────────────┘ │
│ │
│ Ray 是底层调度引擎,vLLM、verl、DeepSpeed-Chat 等 │
│ 上层框架都依赖它进行分布式资源管理与任务编排 │
└─────────────────────────────────────────────────────────────┘Ray 解决的核心问题:如何把多台机器、多张 GPU 当做一个统一的计算资源池来使用?
- 对训练来说:编排 generator(推理采样)+ trainer(梯度更新)的异步协作
- 对推理来说:调度多 GPU tensor parallel,管理请求队列和模型副本
- 对 RLHF/PPO 来说:协调 rollout、reward、training 三个阶段的数据流转
1. Ray 核心概念
1.1 四大组件
| 组件 | 作用 | 类比 |
|---|---|---|
| Task | 无状态远程函数,@ray.remote 装饰后 .remote() 调用 | 分布式的函数调用 |
| Actor | 有状态远程对象,@ray.remote 装饰类 | 分布式的 Python 对象 |
| Object Store | 共享内存,存储 Task/Actor 的输入输出 | 分布式的 Redis |
| GCS (Global Control Store) | 全局元数据服务,管理节点、Actor、资源信息 | 集群的"大脑" |
1.2 执行模型
Driver (你的主程序)
│
├── ray.init() # 连接/启动集群
│
├── func.remote(args) # 提交 Task → Worker 执行
│ └── 返回 ObjectRef # 未来值的引用(Future)
│
├── Actor.remote() # 创建 Actor → 常驻 Worker
│ └── actor.method.remote() # 调用 Actor 方法
│
└── ray.get(ref) # 阻塞获取结果关键设计
Ray 的 .remote() 调用是非阻塞的,返回一个 ObjectRef(Future)。只有当你调用 ray.get() 时才会阻塞等待结果。这使得自然地表达异步并行成为可能。
1.3 为什么 vLLM 和 verl 都选择 Ray?
- Python 原生:不需要学习新的 DSL,
@ray.remote一行装饰器即可 - 异构资源管理:可以精确指定
num_gpus=0.5,多个 Actor 共享一张 GPU - Actor 模型天然适配 LLM 服务:每个模型副本就是一个 Actor,有状态、可通信
- 动态调度:不需要静态拓扑,可以运行时创建/销毁 Actor
- 生态成熟:Ray Serve(推理服务)、Ray Train(分布式训练)、Ray Data(数据处理)
2. Actor 编程模型
2.1 远程函数(Task)
最简单的 Ray 程序——将计算提交到远程 Worker:
import ray
import torch
ray.init()
@ray.remote
def run_task(tensor, device):
"""远程函数:在 Worker 上执行矩阵运算"""
tensor = tensor.to(device)
norm_val = tensor.norm() # L2 范数
mean_val = tensor.mean() # 均值
return norm_val.cpu(), mean_val.cpu()
# 创建数据:5 个 4x4 矩阵
batch = torch.randn(5, 4, 4, device='cpu')
# 并行提交 5 个任务
futures = [run_task.remote(batch[i], 'cpu') for i in range(5)]
# 阻塞获取所有结果
results = ray.get(futures)
for i, (nv, mv) in enumerate(results):
print(f"矩阵{i+1}: 范数={nv:.4f}, 均值={mv:.4f}")指定 GPU 资源:
@ray.remote(num_gpus=0.5) # 每个 Task 占用半张 GPU
def compute_on_gpu(tensor):
print('remote device:', tensor.device)
return tensor.norm(), tensor.mean()num_gpus 的含义
num_gpus=0.5 不是说只用半张 GPU 的算力——它是资源声明,告诉 Ray 调度器"我需要 0.5 个 GPU 资源单位"。这意味着一张 GPU 上最多调度 2 个这样的 Task/Actor。实际的显存隔离需要用户自己管理。
2.2 Actor(有状态远程对象)
Actor 是 Ray 最强大的抽象——将一个 Python 类变成远程服务:
@ray.remote
class Producer:
def __init__(self):
self.payload = [7, 21, 55, 3, 42, 16]
def push(self, consumer, value):
# 调用另一个 Actor 的方法——Actor 间通信
return consumer.pull.remote(value)
def get_payload(self):
return self.payload
@ray.remote
class Consumer:
def __init__(self):
self.buffer = []
def pull(self, value):
self.buffer.append(value)
return len(self.buffer)
def get_buffer(self):
return self.buffer
# 创建 Actor 实例(运行在远程 Worker 上)
producer = Producer.remote()
consumer = Consumer.remote()
# Actor 间通信:Producer → Consumer
payload = ray.get(producer.get_payload.remote())
for val in payload:
future = ray.get(producer.push.remote(consumer, val))
count = ray.get(future)
print(f'Consumer 已接收数据量: {count}')2.3 异步执行模式
Ray 天然支持异步:发射任务后不等待,继续执行其他代码:
import ray
import torch
import time
import random
ray.init()
@ray.remote
def fun_mul(x, y):
for i in range(10):
b = x @ y
time.sleep(random.random())
print(f'[MUL] step:{i}')
return b
@ray.remote
def fun_add(x, y):
for i in range(10):
b = x + y
time.sleep(random.random())
print(f'\t\t[ADD] step:{i}')
return b
A = torch.randn(2048, 2048)
B = torch.randn(2048, 2048)
# 异步执行:mul 和 add 并行运行
future_mul = fun_mul.remote(A, B) # 非阻塞,立即返回
result_add = ray.get(fun_add.remote(A, B)) # add 运行时,mul 也在运行
result_mul = ray.get(future_mul) # 此时 mul 可能已经完成同步 vs 异步的关键区别
ray.get(func.remote(x))→ 同步:提交后立即等待结果ref = func.remote(x)→ 异步:提交后继续执行,稍后ray.get(ref)获取结果- 善用异步是 Ray 性能优化的关键
3. 分布式通信模式
在大模型系统中,组件之间需要频繁通信。Ray 提供了多种通信模式。
3.1 Actor 间直接通信
最直观的方式——一个 Actor 直接调用另一个 Actor 的方法:
# Producer 批量异步发送,Consumer 同步接收
@ray.remote
class Producer:
def __init__(self, items):
self.items = items
def push_all(self, consumer):
refs = []
for val in self.items:
ref = consumer.pull.remote(val)
refs.append(ref)
return refs
@ray.remote
class Consumer:
def __init__(self):
self.buffer = []
def pull(self, value):
self.buffer.append(value)
return len(self.buffer)
def get_buffer(self):
return self.buffer为什么异步发送但接收顺序不乱?
Ray 的 Actor 模型保证:同一个 Actor 的方法调用是顺序执行的(串行化)。即使发送方异步发出多个请求,接收方 Actor 内部依然按到达顺序逐个处理。这是 Actor Model 的核心保证——无需加锁,无需考虑竞态条件。
3.2 多 Actor 并发通信
当多个 Sender 同时向一个 Receiver 发送数据时:
producer_a = Producer.remote([100, 200, 300, 400])
producer_b = Producer.remote([51, 62, 73, 84, 95])
consumer = Consumer.remote()
# 两个 Producer 并行发送
refs_a = ray.get(producer_a.push_all.remote(consumer))
refs_b = ray.get(producer_b.push_all.remote(consumer))
# 合并等待
ray.get(refs_a + refs_b)
received = ray.get(consumer.get_buffer.remote())
print('接收到的数据:', received)
# 可能输出: [100, 51, 200, 62, 300, 73, 400, 84, 95]
# 两个 Producer 的数据交错到达,但每个 Producer 内部有序3.3 Tensor 传输与共享对象
大模型场景中经常需要传输 GPU tensor。Ray 的 Object Store 支持零拷贝共享:
import ray
import torch
ray.init()
# 将 tensor 放入共享内存
tensor_gpu = torch.zeros(4, 4, device='cuda:0')
tensor_ref = ray.put(tensor_gpu) # 返回 ObjectRef
# 任何 Worker 都可以通过 ref 获取
result = ray.get(tensor_ref)
print(result.device) # 获取到的 tensorGPU tensor 传输注意事项
ray.put() 会将 GPU tensor 拷贝到 CPU 共享内存中。ray.get() 获取时得到的是 CPU tensor。如果需要在 GPU 上使用,需要手动 .to(device)。对于大型模型参数同步,建议使用 NCCL 集合通信而非 Ray Object Store。
3.4 共享队列模式
生产者-消费者模式——用一个 Actor 充当共享队列:
@ray.remote
class SharedQueue:
def __init__(self, max_size=100):
from collections import deque
self.queue = deque(maxlen=max_size)
def push(self, item):
if len(self.queue) < self.queue.maxlen:
self.queue.append(item)
return True
return False
def pop(self):
if self.queue:
return self.queue.popleft()
return None
def get_stats(self):
return {"queue_size": len(self.queue)}
# 使用方式
queue = SharedQueue.remote(max_size=5)
# Producer Actor 向队列写入
# Consumer Actor 从队列读取
# 两者通过 SharedQueue Actor 解耦这种模式在 RLHF 中非常常见:Generator 产生 rollout 数据 → 放入队列 → Trainer 从队列取数据训练。
3.5 Ray + PyTorch Distributed 集合通信
对于需要高带宽通信的场景(如 all-reduce),Ray 可以与 PyTorch Distributed 结合:
import ray
import torch
import torch.distributed as dist
from ray.util.placement_group import placement_group
from ray.util.scheduling_strategies import PlacementGroupSchedulingStrategy
def init_distributed_with_ray():
ray.init()
# 创建 Placement Group 确保 Worker 被调度到正确位置
pg = placement_group([{"CPU": 1}] * 4)
ray.get(pg.ready())
@ray.remote(
num_cpus=1,
scheduling_strategy=PlacementGroupSchedulingStrategy(
placement_group=pg,
placement_group_capture_child_tasks=True
)
)
class Worker:
def __init__(self, rank, world_size):
self.rank = rank
self.world_size = world_size
def init_process_group(self, master_addr, master_port):
import os
os.environ['MASTER_ADDR'] = master_addr
os.environ['MASTER_PORT'] = master_port
os.environ['WORLD_SIZE'] = str(self.world_size)
os.environ['RANK'] = str(self.rank)
dist.init_process_group(
backend="gloo",
init_method="env://",
world_size=self.world_size,
rank=self.rank,
)
# 现在可以使用 dist.all_reduce() 等集合通信
A = torch.randn(100)
dist.all_reduce(A)
return True
# 创建 Workers 并初始化
world_size = 4
master_addr = ray.util.get_node_ip_address()
workers = [Worker.remote(i, world_size) for i in range(world_size)]
ray.get([
w.init_process_group.remote(master_addr, "29500")
for w in workers
])Placement Group 的作用
Placement Group 确保相关的 Worker 被调度到同一组物理节点上,这对通信延迟至关重要。PACK 策略尽量把 Worker 放在同一台机器,SPREAD 策略则分散到不同机器(适合需要更多 GPU 的场景)。
4. Ray 分布式推理
4.1 基本思路
用 Ray 调度多 GPU 推理的核心设计:
┌─────────────┐
请求 ──────────→ │ Ray Driver │
└──────┬──────┘
│ 调度
┌────────────┼────────────┐
↓ ↓ ↓
┌──────────┐ ┌──────────┐ ┌──────────┐
│ GPU 0 │ │ GPU 1 │ │ GPU 2 │
│ Model │ │ Model │ │ Model │
│ Replica │ │ Replica │ │ Replica │
└──────────┘ └──────────┘ └──────────┘4.2 用 Ray 实现 All-Reduce
当多个 GPU 上有分布的数据,需要聚合计算(如求均值):
@ray.remote(num_gpus=0.5)
def all_reduce_mean(refs):
"""在目标设备上执行 reduce 操作"""
tensors = ray.get(refs)
tensors = [tensor.to('cuda:0') for tensor in tensors]
tensors_cat = torch.cat(tensors, dim=0)
result = tensors_cat.mean(dim=0)
return result
# 模拟 8 个 GPU 上的数据
data_list = [torch.randn(1, 3, 4, device='cpu') for _ in range(8)]
refs = [ray.put(data) for data in data_list]
# 在一个 GPU 上执行 reduce
result = ray.get(all_reduce_mean.remote(refs)).to('cpu')4.3 vLLM 中的 Ray 使用
vLLM 使用 Ray 来实现多 GPU tensor parallel 推理:
vLLM Engine
│
├── RayGPUExecutor
│ ├── 创建 N 个 Worker Actor(每个占 1 GPU)
│ ├── 初始化 NCCL 通信组
│ └── 调度 execute_model() 到所有 Worker
│
└── Worker Actor
├── 持有模型的一个 TP shard
├── 通过 NCCL 执行 tensor parallel 通信
└── 执行前向推理关键代码路径(vLLM 源码参考):
vllm/executor/ray_gpu_executor.py— Ray Worker 的创建和管理vllm/worker/worker.py— Worker Actor 的实现- 每个 Worker 持有模型的 1/N 参数(tensor parallel),通过 NCCL all-reduce 同步
为什么 vLLM 不直接用 torchrun?
torchrun 适合静态的训练场景——启动 N 个进程,跑完就结束。但推理服务需要:
- 动态伸缩:根据负载增减模型副本
- 细粒度资源管理:0.5 GPU 粒度的分配
- 异构调度:不同模型可以用不同数量的 GPU
- 故障恢复:单个 Worker 挂了不影响整个服务
Ray 的 Actor 模型天然满足这些需求。
5. Ray 分布式训练:Coordinator 架构
在 RLHF/PPO 场景中,训练系统需要**生成(rollout)和训练(update)**两个阶段交替进行。Ray 的 Actor 模型非常适合这种架构。
5.1 整体架构
┌──────────────────────────────────────────────────────────────┐
│ SystemCoordinator (Actor) │
│ │
│ ┌─────────────┐ 管理 ┌─────────────────────────────┐ │
│ │ 启动训练循环 │ ─────────→ │ TrainerActor (1 个) │ │
│ │ 启动生成循环 │ │ • 接收生成数据 │ │
│ │ 监控系统状态 │ │ • 训练队列管理 │ │
│ └─────────────┘ │ • 模型参数更新 │ │
│ │ • 参数版本管理 │ │
│ 管理 └──────────┬──────────────────┘ │
│ │ │ │
│ ↓ │ 参数同步 │
│ ┌──────────────────┐ ↓ │
│ │ GeneratorActor ×N │ ←──── 拉取最新参数 │
│ │ • vLLM 推理生成 │ ────→ 发送训练数据 │
│ │ • 数据预处理 │ │
│ │ • 定期参数更新 │ │
│ └──────────────────┘ │
└──────────────────────────────────────────────────────────────┘5.2 Coordinator(协调器)
Coordinator 是整个系统的中枢,负责创建和编排所有 Actor:
@ray.remote
class SystemCoordinator:
"""系统协调器:创建和管理整个分布式系统"""
def __init__(self, num_generators=2):
# 创建训练节点(1 个,占主 GPU)
self.trainer = TrainerActor.remote()
# 创建生成节点(N 个,分布在不同 GPU)
self.generators = []
for i in range(num_generators):
generator = GeneratorActor.remote(
generator_id=f"Generator-{i+1}",
trainer_actor=self.trainer, # 传入 trainer 引用
device_id=i % 2 # 轮流分配 GPU
)
self.generators.append(generator)
def start_training_loop(self, interval=0.5):
"""启动训练循环"""
@ray.remote
def training_worker(trainer, interval):
while True:
result = ray.get(trainer.train_step.remote())
if result["status"] == "success":
step = result["step"]
loss = result["loss"]
if step % 10 == 0:
print(f"训练步骤 {step}: loss={loss:.4f}")
time.sleep(interval)
self.training_future = training_worker.remote(
self.trainer, interval
)
def start_generation_loop(self, prompts, interval=1.0):
"""启动生成循环"""
for i, generator in enumerate(self.generators):
@ray.remote
def gen_worker(gen, prompts, interval):
idx = 0
while True:
batch = prompts[idx:idx+2]
ray.get(gen.generate_and_send.remote(batch))
idx = (idx + 2) % len(prompts)
time.sleep(interval)
gen_worker.remote(generator, prompts, interval)5.3 GeneratorActor(生成节点)
Generator 负责使用模型生成文本,并将结果发送给 Trainer:
@ray.remote(num_gpus=0.5)
class GeneratorActor:
def __init__(self, generator_id, trainer_actor, device_id=1):
self.generator_id = generator_id
self.trainer_actor = trainer_actor # Trainer 的远程引用
self.device = torch.device(f"cuda:{device_id}")
# 初始化本地模型副本
self.local_model = SharedLanguageModel().to(self.device)
# 从 Trainer 拉取初始参数
self._update_from_trainer()
def _update_from_trainer(self):
"""从 Trainer 拉取最新参数"""
params_info = ray.get(
self.trainer_actor.get_current_params.remote()
)
self.local_model.load_state_dict(params_info["params"])
self.params_version = params_info["version"]
def generate_and_send(self, prompts):
"""生成 → 处理 → 发送给 Trainer"""
# 1. 用本地模型生成
generated_texts = self._generate(prompts)
# 2. 构造训练样本
samples = self._prepare_training_data(prompts, generated_texts)
# 3. 发送给 Trainer
ray.get(
self.trainer_actor.receive_generated_data.remote(samples)
)
# 4. 定期更新参数
self._update_from_trainer()
return {"samples_sent": len(samples)}5.4 TrainerActor(训练节点)
Trainer 维护训练队列、执行梯度更新、提供最新参数:
@ray.remote(num_gpus=0.5)
class TrainerActor:
def __init__(self):
self.device = torch.device("cuda:0")
self.model = SharedLanguageModel().to(self.device)
self.optimizer = torch.optim.AdamW(self.model.parameters())
self.criterion = nn.CrossEntropyLoss()
# 训练队列——存储 Generator 发来的数据
self.training_queue = deque(maxlen=1000)
self.params_version = 0
def receive_generated_data(self, batch_data):
"""接收 Generator 发送的数据"""
for data in batch_data:
self.training_queue.append(data)
return True
def train_step(self, batch_size=8):
"""执行一步训练"""
if len(self.training_queue) < batch_size:
return {"status": "waiting"}
# 从队列采样
batch = random.sample(list(self.training_queue), batch_size)
# 标准训练流程
self.model.train()
self.optimizer.zero_grad()
loss = self._compute_loss(batch)
loss.backward()
torch.nn.utils.clip_grad_norm_(self.model.parameters(), 1.0)
self.optimizer.step()
self.params_version += 1
return {"status": "success", "loss": loss.item()}
def get_current_params(self):
"""提供最新参数(CPU 版本,便于传输)"""
params_cpu = {
k: v.cpu().clone()
for k, v in self.model.state_dict().items()
}
return {"params": params_cpu, "version": self.params_version}5.5 数据流全景
时间 ─────────────────────────────────────────────────→
Generator-1: [生成] ──→ [发送数据] ──→ [拉取参数] ──→ [生成] ...
Generator-2: [生成] ──→ [发送数据] ──→ [拉取参数] ──→ [生成] ...
↘ ↓ ↑ ↗
Trainer: [等待数据] → [训练] → [更新版本] → [训练] ...
↑ ↑
训练队列 训练队列
(来自多个 Generator) (持续填充)与 verl 的对应关系
verl(Volcano Engine RL)的架构与此非常相似:
- RolloutWorker ≈ GeneratorActor:使用 vLLM 进行推理采样
- TrainWorker ≈ TrainerActor:使用 FSDP/Megatron 进行梯度更新
- ResourcePool ≈ Coordinator:管理 GPU 资源分配
- verl 的创新在于支持 rollout 和 training 共享 GPU(colocate 模式),进一步提升资源利用率
6. 在 vLLM 和 verl 中的应用
6.1 vLLM:多卡 Tensor Parallel 调度
# vLLM 简化版工作流程
import ray
from vllm import LLM
# vLLM 内部会自动使用 Ray
llm = LLM(
model="meta-llama/Llama-3-8B",
tensor_parallel_size=4, # 4 卡 tensor parallel
# vLLM 自动创建 4 个 Ray Worker Actor
)
# 每次推理请求:
# 1. Engine 将 prompt 放入调度队列
# 2. Scheduler 选择一批请求
# 3. 通过 Ray 调用所有 Worker 的 execute_model()
# 4. 各 Worker 通过 NCCL 做 tensor parallel 通信
# 5. 收集结果返回vLLM + Ray 的关键设计:
- Worker 之间通过 NCCL 通信(不是 Ray Object Store),保证高带宽
- Ray 负责 生命周期管理(创建、销毁、故障检测)
- 支持 多模型共存:不同模型用不同的 Actor Group
6.2 verl:PPO 训练的 Rollout-Training 分离
verl PPO 训练循环:
for each iteration:
┌─────────────────────────────┐
│ Phase 1: Rollout (推理) │
│ • RolloutWorker (vLLM) │
│ • 生成 response │
│ • 计算 old_log_probs │
└─────────────┬───────────────┘
│ 数据传输(Ray Object Store)
↓
┌─────────────────────────────┐
│ Phase 2: Reward │
│ • RewardWorker │
│ • 计算 reward scores │
└─────────────┬───────────────┘
│
↓
┌─────────────────────────────┐
│ Phase 3: Training │
│ • TrainWorker (FSDP) │
│ • PPO loss + 梯度更新 │
│ • 参数广播回 RolloutWorker │
└─────────────────────────────┘verl 的 Ray 使用特点:
- ResourcePool:将 GPU 分组,rollout 和 training 可以用不同的 GPU 组,也可以共享
- Worker 权重同步:训练完成后,通过
broadcast_dict_tensor()将参数从 TrainWorker 广播到 RolloutWorker - 动态 batch:根据 GPU 显存动态调整 rollout 的 batch size
苏格拉底时刻
在继续之前,尝试回答这些问题:
Task vs Actor:什么场景用 Task?什么场景用 Actor?(提示:有无状态?是否需要多次调用?)
顺序保证:两个不同的 Sender Actor 同时向一个 Receiver Actor 发送数据,数据到达顺序是怎样的?(提示:同一 Actor 内串行,不同 Actor 间无序)
参数同步策略:Generator 从 Trainer 拉取参数,如果训练在持续进行,Generator 拉到的参数可能是哪个版本?这会带来什么问题?(提示:on-policy vs off-policy)
资源声明:
num_gpus=0.5和num_gpus=1的实际区别是什么?如果两个num_gpus=0.5的 Actor 同时运行在一张 GPU 上,显存会冲突吗?设计权衡:verl 支持 rollout 和 training 共享 GPU(colocate),这和分离部署相比有什么优劣?
参考思路
- Task 适合无状态、一次性的计算(如数据预处理、矩阵运算);Actor 适合有状态、长期存在的服务(如模型推理服务、训练器)。
- 同一 Sender 内部发送有序(因为 Actor 串行执行),不同 Sender 之间无序(并发执行)。总体结果是两个序列的交错。
- Generator 可能拉到"旧版本"参数。这导致生成的数据和当前策略不一致(off-policy),PPO 通过 importance sampling ratio 和 clipping 来缓解。
num_gpus是调度声明,不是物理隔离。两个 0.5 的 Actor 会共享显存空间,如果总显存超出 GPU 容量会 OOM。- Colocate 节省 GPU 数量(不需要单独的推理卡),但需要精细的显存管理(推理时释放训练显存,反之亦然)。分离部署更简单但需要更多 GPU。
面试考点
Q1: Ray 的 Actor 和 Erlang/Akka 的 Actor 有什么区别?
参考答案:Ray Actor 是 Python 原生的,支持直接传递 Python 对象和 NumPy/Torch tensor,有共享对象存储。与 Erlang 不同,Ray 不保证 Actor 间消息的有序性(只保证同一 Actor 内的方法调用有序),也不内置 supervision tree 的容错机制。
Q2: 为什么 vLLM 用 NCCL 而不用 Ray Object Store 做 tensor parallel 通信?
参考答案:Ray Object Store 走的是 CPU 共享内存,GPU tensor 需要先 D2H 拷贝到 CPU,再 H2D 拷贝到目标 GPU。NCCL 支持 GPU 直接通信(通过 NVLink/NVSwitch/IB),带宽可达 300-900 GB/s,而经过 CPU 中转的延迟和带宽都不可接受。Ray 负责 Worker 的生命周期管理,NCCL 负责高性能数据传输,各司其职。
Q3: verl 中 rollout 和 training 共享 GPU 的技术挑战?
参考答案:核心挑战是显存管理。训练时 model + optimizer + gradient + activation 占大量显存,推理时需要 KV cache。verl 的做法是在两个阶段之间动态释放和重新分配显存:训练结束后释放 optimizer state 和 gradient,为推理的 KV cache 腾空间;推理结束后释放 KV cache,重建训练所需的 tensor。
推荐资源
| 资源 | 链接 | 说明 |
|---|---|---|
| Ray 官方文档 | docs.ray.io | 最权威的参考 |
| Ray Architecture Whitepaper | arxiv.org/abs/1712.05889 | 理解 Ray 的设计动机 |
| vLLM 源码 | github.com/vllm-project/vllm | 看 executor/ray_gpu_executor.py |
| verl 源码 | github.com/volcengine/verl | 看 single_controller/ 目录 |
| Ray Train 文档 | docs.ray.io/en/latest/train | 分布式训练框架 |
| Actor Model 原论文 | Hewitt & Baker, 1973 | 理解 Actor 模型的理论基础 |