Skip to content

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?

  1. Python 原生:不需要学习新的 DSL,@ray.remote 一行装饰器即可
  2. 异构资源管理:可以精确指定 num_gpus=0.5,多个 Actor 共享一张 GPU
  3. Actor 模型天然适配 LLM 服务:每个模型副本就是一个 Actor,有状态、可通信
  4. 动态调度:不需要静态拓扑,可以运行时创建/销毁 Actor
  5. 生态成熟:Ray Serve(推理服务)、Ray Train(分布式训练)、Ray Data(数据处理)

2. Actor 编程模型

2.1 远程函数(Task)

最简单的 Ray 程序——将计算提交到远程 Worker:

python
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 资源

python
@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 类变成远程服务:

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 天然支持异步:发射任务后不等待,继续执行其他代码:

python
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 的方法:

python
# 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 发送数据时:

python
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 支持零拷贝共享:

python
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)  # 获取到的 tensor

GPU tensor 传输注意事项

ray.put() 会将 GPU tensor 拷贝到 CPU 共享内存中。ray.get() 获取时得到的是 CPU tensor。如果需要在 GPU 上使用,需要手动 .to(device)。对于大型模型参数同步,建议使用 NCCL 集合通信而非 Ray Object Store。

3.4 共享队列模式

生产者-消费者模式——用一个 Actor 充当共享队列:

python
@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 结合:

python
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 上有分布的数据,需要聚合计算(如求均值):

python
@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 个进程,跑完就结束。但推理服务需要:

  1. 动态伸缩:根据负载增减模型副本
  2. 细粒度资源管理:0.5 GPU 粒度的分配
  3. 异构调度:不同模型可以用不同数量的 GPU
  4. 故障恢复:单个 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:

python
@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:

python
@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 维护训练队列、执行梯度更新、提供最新参数:

python
@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 调度

python
# 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 使用特点

  1. ResourcePool:将 GPU 分组,rollout 和 training 可以用不同的 GPU 组,也可以共享
  2. Worker 权重同步:训练完成后,通过 broadcast_dict_tensor() 将参数从 TrainWorker 广播到 RolloutWorker
  3. 动态 batch:根据 GPU 显存动态调整 rollout 的 batch size

苏格拉底时刻

在继续之前,尝试回答这些问题:

  1. Task vs Actor:什么场景用 Task?什么场景用 Actor?(提示:有无状态?是否需要多次调用?)

  2. 顺序保证:两个不同的 Sender Actor 同时向一个 Receiver Actor 发送数据,数据到达顺序是怎样的?(提示:同一 Actor 内串行,不同 Actor 间无序)

  3. 参数同步策略:Generator 从 Trainer 拉取参数,如果训练在持续进行,Generator 拉到的参数可能是哪个版本?这会带来什么问题?(提示:on-policy vs off-policy)

  4. 资源声明num_gpus=0.5num_gpus=1 的实际区别是什么?如果两个 num_gpus=0.5 的 Actor 同时运行在一张 GPU 上,显存会冲突吗?

  5. 设计权衡:verl 支持 rollout 和 training 共享 GPU(colocate),这和分离部署相比有什么优劣?

参考思路
  1. Task 适合无状态、一次性的计算(如数据预处理、矩阵运算);Actor 适合有状态、长期存在的服务(如模型推理服务、训练器)。
  2. 同一 Sender 内部发送有序(因为 Actor 串行执行),不同 Sender 之间无序(并发执行)。总体结果是两个序列的交错。
  3. Generator 可能拉到"旧版本"参数。这导致生成的数据和当前策略不一致(off-policy),PPO 通过 importance sampling ratio 和 clipping 来缓解。
  4. num_gpus 是调度声明,不是物理隔离。两个 0.5 的 Actor 会共享显存空间,如果总显存超出 GPU 容量会 OOM。
  5. 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 Whitepaperarxiv.org/abs/1712.05889理解 Ray 的设计动机
vLLM 源码github.com/vllm-project/vllmexecutor/ray_gpu_executor.py
verl 源码github.com/volcengine/verlsingle_controller/ 目录
Ray Train 文档docs.ray.io/en/latest/train分布式训练框架
Actor Model 原论文Hewitt & Baker, 1973理解 Actor 模型的理论基础