Skip to content

引擎核心 — 练习

练习 1:EngineCore 主循环追踪

阅读 vllm/v1/engine/core.pyrun_loop 方法,回答:

  1. 主循环在什么条件下退出?
  2. 每轮迭代如何处理新到达的请求?
  3. 如果没有活跃请求,主循环如何等待?
参考答案
  1. 主循环在收到 shutdown 信号时退出。通过 ZMQ 的 poll 机制检测。
  2. 每轮迭代开始时,从 ZMQ socket 读取所有待处理消息,包括新请求、abort 信号和 utility calls。
  3. 使用 zmq_poller.poll(timeout) 等待新消息。如果没有活跃请求且没有待调度请求,主循环会阻塞等待直到新请求到达。

练习 2:AsyncLLM 输出流分析

分析 AsyncLLM.generate() 如何实现流式输出:

  1. generate() 方法返回什么类型?
  2. 如何实现从 EngineCore 的批量输出到单个请求流式输出的映射?
  3. abort 请求如何传播到 EngineCore?
参考答案
  1. 返回 AsyncIterator[RequestOutput],每次 yield 一个新的输出 chunk。
  2. EngineCore 每轮迭代返回所有请求的输出。OutputProcessor 按请求 ID 分发到对应的 stream。每个请求有自己的 AsyncQueue,generate() 从中消费。
  3. 调用 async_engine.abort(request_id),通过 EngineCoreClient 发送 abort 消息到 EngineCore。EngineCore 在下一轮迭代开始时处理 abort 信号,从调度队列中移除请求。

练习 3:IPC 性能分析

分析 ZMQ IPC 的性能特征:

  1. EngineCoreClient 使用哪种 ZMQ socket 类型?
  2. 序列化格式是什么?为什么选择这种格式?
  3. 大批量输出时如何避免内存拷贝?
参考答案
  1. 使用 ZMQ_DEALER(客户端)和 ZMQ_ROUTER(服务端)模式,支持多对一通信。
  2. 使用 msgspec 进行二进制序列化,比 JSON 更紧凑高效。msgspec 的 Message 编码支持零拷贝反序列化。
  3. ZMQ 支持零拷贝发送(send(copy=False)),大数组可以直接传递内存指针而不需要复制。对于 token IDs 数组,使用 numpy array 或 bytes buffer 直接传递。

拓展挑战

  • 在 vLLM 源码中添加自定义日志,追踪一个请求从入口到返回的完整时间线
  • 分析 InputProcessor 如何处理多模态输入(图像、音频)
  • 研究 ParallelSampling(并行采样)如何在 EngineCore 中实现多个 completion