利用插件系统构建整洁、可维护的 vLLM 修改方案
注: 原文发布于此 Medium 文章。
概述
大语言模型推理技术正在飞速发展,而 vLLM 已成为实现高吞吐量、低延迟模型服务最强大的引擎之一。它提供连续批处理、高效调度、分页注意力(PagedAttention)以及生产就绪的 API 层,使其成为从小型语言模型到超大规模前沿系统部署的理想选择。
但是,对于任何快速发展的系统,团队或个人总会遇到想要修改 vLLM 内部行为的时候。也许您想尝试自定义调度逻辑、更改 KV 缓存处理方式、注入专有优化,或者对模型执行流程的某一部分打补丁。
这就是真正挑战开始的地方。
问题:“我需要修改 vLLM……现在该怎么办?”
如果更改很简单,或者对整个社区有益,那么解决方案非常直观:
方案 A - 将您的贡献合并到 vLLM 上游
这始终是最整洁的做法。您的更改将保留在开源项目中,接受社区审核,并随着 vLLM 的演进而保持同步。
然而,现实往往不那么理想。许多修改属于:
- 专有的
- 特定领域需求的
- 过于实验性的
- 通用性不足,无法被上游仓库接受
- 或者受限于内部时间表,无法与开源审核周期对齐
当无法向上游提交时,您必须寻找另一条路径。
方案 B - 维护自己的 vLLM 分支
这通常是第一直觉:
“让我们直接分叉(Fork)vLLM,然后把修改加进去。”
虽然这对于小型、迭代缓慢的项目可行,但 vLLM 并非此类项目。
vLLM 是一个极其活跃的仓库,发布新版本的频率高达 两周一次,并且每周合并 数百个 PR。
维护一个长期存在的独立分支意味着:
- ❌ 不断地执行变基或合并上游更改
- ❌ 解决在快速变化区域中的冲突
- ❌ 手动重新应用您的补丁
- ❌ 执行繁重的兼容性测试
- ❌ 管理围绕定制 vLLM 制品的内部开发者工作流
用不了多久,维护这个分支就会变成一项 全职责任。
对于许多团队而言,这种运营负担是不可持续的。
方案 C - 使用猴子补丁(Monkey Patching)
另一种路径是构建一个小型的 Python 包,在构建时将猴子补丁应用到原生 vLLM 之上。
乍看之下,这似乎很有吸引力:
- ✅ 无需分叉
- ✅ 不偏离原生 vLLM
- ✅ 动态应用补丁
- ✅ 代码体积小
……但现实远非理想。
猴子补丁通常需要替换整个类或模块,即使您只想更改十行代码。这意味着:
- ❌ 您复制了 vLLM 大量的源代码 —— 即使是那些您无需修改的部分
- ❌ 每次 vLLM 升级都会破坏您的补丁 —— 因为您替换的是整个文件,而不是关注特定行
- ❌ 调试变得痛苦 —— 问题出在您的补丁中吗?还是未更改的原始代码中?或者是因为猴子补丁意外重写了行为?
- ❌ 运营复杂度随时间增长 —— 每次 vLLM 发布都需要对比(diff)并重新同步您复制的文件 —— 这与维护分支的问题完全一样,只是伪装在 Python 包中
- ❌ 对某些模块(如
Scheduler)进行猴子补丁通常 无效,因为它们运行在独立的EngineCore进程中。这可能导致进程同步问题,即EngineCore继续调用您想要修改的模块的旧实现。
猴子补丁解决了表面的问题,却引入了长期维护挑战,最终可能变得难以管理。
更整洁的替代方案:利用 vLLM 插件系统
为了克服分支和猴子补丁的局限性,我研究了 vLLM 正在演进的 通用插件架构 (general_plugin architecture),它允许开发者在不更改上游代码的情况下,向引擎注入有针对性的修改。
这种架构实现了:
- ✅ 结构化、模块化的补丁
- ✅ 运行时激活
- ✅ 外科手术级别的代码覆盖
- ✅ 兼容性保护措施
- ✅ 无需复制整个文件
- ✅ 无需复杂的猴子补丁技巧
- ✅ 无需维护独立的分支
它在“完全合并到上游”和“替换整个文件”之间提供了一个折中方案。
注: vLLM 提供了四种插件组/机制 —— 平台插件、引擎插件、模型插件和 通用插件。本文专门关注 通用插件系统,它在所有 vLLM 进程中加载,因此非常适合本文所述的整洁修改方法。有关不同插件组的更多详细信息,请参阅 vLLM 文档:支持的插件类型。
使用 vLLM 插件构建整洁的扩展框架
利用插件系统,我创建了一个小型扩展包,作为所有自定义修改的容器。与其替换整个模块或分叉整个仓库,每个补丁:
- 仅包含 需要更改的确切代码片段或类
- 可以在 运行时启用或禁用
- 可以指定 最低支持的 vLLM 版本
- 除非特定的模型配置请求它,否则可以保持 休眠状态
由于插件是在运行时应用的,我们维护 单一、统一的容器镜像 来服务多个模型,同时针对不同模型选择性地启用不同的补丁。
这种方法受到基于插件的设计(如 ArcticInference)的启发,其中补丁在运行时被整洁、有选择性地注入。
实现:创建您的第一个 vLLM 插件包
让我们来构建一个基于 vLLM general_plugins 入口点的插件化扩展系统。
项目结构
vllm_custom_patches/
├── setup.py
├── vllm_custom_patches/
│ ├── __init__.py
│ ├── core.py # Base patching infrastructure
│ └── patches/
│ ├── __init__.py
│ └── priority_scheduler.py
└── README.md
核心补丁基础设施
基础是一个整洁的补丁机制,允许进行外科手术式的修改。
# vllm_custom_patches/core.py
import logging
from types import MethodType, ModuleType
from typing import Type, Union
from packaging import version
import vllm
logger = logging.getLogger(__name__)
PatchTarget = Union[Type, ModuleType]
class VLLMPatch:
"""
Base class for creating clean, surgical patches to vLLM classes.
Usage:
class MyPatch(VLLMPatch[TargetClass]):
def new_method(self):
return "patched behavior"
MyPatch.apply()
"""
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if not hasattr(cls, '_patch_target'):
raise TypeError(
f"{cls.__name__} must be defined as VLLMPatch[Target]"
)
@classmethod
def __class_getitem__(cls, target: PatchTarget) -> Type:
if not isinstance(target, (type, ModuleType)):
raise TypeError(f"Can only patch classes or modules, not {type(target)}")
return type(
f"{cls.__name__}[{target.__name__}]",
(cls,),
{'_patch_target': target}
)
@classmethod
def apply(cls):
"""Apply this patch to the target class/module."""
if cls is VLLMPatch:
raise TypeError("Cannot apply base VLLMPatch class directly")
target = cls._patch_target
# Track which patches have been applied
if not hasattr(target, '_applied_patches'):
target._applied_patches = {}
for name, attr in cls.__dict__.items():
if name.startswith('_') or name in ('apply',):
continue
if name in target._applied_patches:
existing = target._applied_patches[name]
raise ValueError(
f"{target.__name__}.{name} already patched by {existing}"
)
target._applied_patches[name] = cls.__name__
# Handle classmethods
if isinstance(attr, MethodType):
attr = MethodType(attr.__func__, target)
setattr(target, name, attr)
action = "replaced" if hasattr(target, name) else "added"
logger.info(f"✓ {cls.__name__} {action} {target.__name__}.{name}")
def min_vllm_version(version_str: str):
"""
Decorator to specify minimum vLLM version required for a patch.
Usage:
@min_vllm_version("0.9.1")
class MyPatch(VLLMPatch[SomeClass]):
pass
"""
def decorator(cls):
original_apply = cls.apply
@classmethod
def checked_apply(cls):
current = version.parse(vllm.__version__)
minimum = version.parse(version_str)
if current < minimum:
logger.warning(
f"Skipping {cls.__name__}: requires vLLM >= {version_str}, "
f"but found {vllm.__version__}"
)
return
original_apply()
cls.apply = checked_apply
cls._min_version = version_str
return cls
return decorator示例补丁:基于优先级的调度
现在,让我们创建一个具体补丁,为 vLLM 增加优先级调度。
# vllm_custom_patches/patches/priority_scheduler.py
import logging
from vllm.core.scheduler import Scheduler
from vllm_custom_patches.core import VLLMPatch, min_vllm_version
logger = logging.getLogger(__name__)
@min_vllm_version("0.9.1")
class PrioritySchedulerPatch(VLLMPatch[Scheduler]):
"""
Adds priority-based scheduling to vLLM's scheduler.
Requests can include a 'priority' field in their metadata.
Higher priority requests are scheduled first.
Compatible with vLLM 0.9.1+
"""
def schedule_with_priority(self):
"""
Enhanced scheduling that respects request priority.
This method can be called instead of the standard schedule()
to enable priority-aware scheduling.
"""
# Get the standard scheduler output
output = self._schedule()
# Sort by priority if metadata contains priority field
if hasattr(output, 'scheduled_seq_groups'):
output.scheduled_seq_groups.sort(
key=lambda seq: getattr(seq, 'priority', 0),
reverse=True
)
logger.debug(
f"Scheduled {len(output.scheduled_seq_groups)} sequences "
f"with priority ordering"
)
return output插件入口点与注册表
插件系统将一切连接在一起。
# vllm_custom_patches/__init__.py
import os
import logging
from typing import Dict, List
logger = logging.getLogger(__name__)
class PatchManager:
"""Manages registration and application of vLLM patches."""
def __init__(self):
self.available_patches: Dict[str, type] = {}
self.applied_patches: List[str] = []
def register(self, name: str, patch_class: type):
"""Register a patch for later application."""
self.available_patches[name] = patch_class
logger.info(f"Registered patch: {name}")
def apply_patch(self, name: str) -> bool:
"""Apply a single patch by name."""
if name not in self.available_patches:
logger.error(f"Unknown patch: {name}")
return False
try:
self.available_patches[name].apply()
self.applied_patches.append(name)
return True
except Exception as e:
logger.error(f"Failed to apply {name}: {e}")
return False
def apply_from_env(self):
"""
Apply patches specified in VLLM_CUSTOM_PATCHES environment variable.
Format: VLLM_CUSTOM_PATCHES="PatchOne,PatchTwo"
"""
env_patches = os.environ.get('VLLM_CUSTOM_PATCHES', '').strip()
if not env_patches:
logger.info("No custom patches specified (VLLM_CUSTOM_PATCHES not set)")
return
patch_names = [p.strip() for p in env_patches.split(',') if p.strip()]
logger.info(f"Applying patches: {patch_names}")
for name in patch_names:
self.apply_patch(name)
logger.info(f"Successfully applied: {self.applied_patches}")
# Global manager instance
manager = PatchManager()
def register_patches():
"""
Main entry point called by vLLM's plugin system.
This function is invoked automatically when vLLM starts.
"""
logger.info("=" * 60)
logger.info("Initializing vLLM Custom Patches Plugin")
logger.info("=" * 60)
# Import and register all available patches
from vllm_custom_patches.patches.priority_scheduler import PrioritySchedulerPatch
manager.register('PriorityScheduler', PrioritySchedulerPatch)
# Apply patches based on environment configuration
manager.apply_from_env()
logger.info("=" * 60)设置配置
setup.py 文件将插件注册到 vLLM 中。
# setup.py
from setuptools import setup, find_packages
setup(
name='vllm-custom-patches',
version='0.1.0',
description='Clean vLLM modifications via the plugin system',
packages=find_packages(),
install_requires=[
'vllm>=0.9.1',
'packaging>=20.0',
],
# Register with vLLM's plugin system
entry_points={
'vllm.general_plugins': [
'custom_patches = vllm_custom_patches:register_patches'
]
},
python_requires='>=3.11',
)使用示例
安装
# Install the plugin package
pip install -e .通过不同配置运行
# Vanilla vLLM (no patches)
VLLM_CUSTOM_PATCHES="" python -m vllm.entrypoints.openai.api_server \
--model mistralai/Mistral-7B-Instruct-v0.2
# With priority scheduling patch
VLLM_CUSTOM_PATCHES="PriorityScheduler" python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Meta-Llama-3-70B-InstructDocker 集成
# Dockerfile
FROM vllm/vllm-openai:latest
COPY . /workspace/vllm-custom-patches/
RUN pip install -e /workspace/vllm-custom-patches/
ENV VLLM_CUSTOM_PATCHES=""
CMD python -m vllm.entrypoints.openai.api_server \
--model ${MODEL_NAME} \
--host 0.0.0.0 \
--port 8000# Run with patches
docker run \
-e MODEL_NAME=meta-llama/Meta-Llama-3-70B-Instruct \
-e VLLM_CUSTOM_PATCHES="PriorityScheduler" \
-p 8000:8000 \
vllm-with-patches
# Run vanilla vLLM
docker run \
-e MODEL_NAME=mistralai/Mistral-7B-Instruct-v0.2 \
-e VLLM_CUSTOM_PATCHES="" \
-p 8000:8000 \
vllm-with-patches原理:vLLM 插件生命周期
了解补丁何时以及如何应用至关重要。以下是完整的生命周期:
vLLM 自动加载插件
关键洞察: vLLM 的架构涉及多个进程,特别是在使用张量并行、流水线并行或其他并行技术进行分布式推理时。为了确保一致性,vLLM 会在创建 每一个进程 开始任何实际工作之前,自动调用 load_general_plugins()。
这意味着:
- ✅ 您的补丁在 主进程 中加载
- ✅ 您的补丁在 所有工作进程 中加载
- ✅ 您的补丁在 GPU 工作进程、CPU 工作进程及任何辅助进程 中加载
- ✅ 加载发生在 模型初始化之前、调度器创建之前以及任何推理开始之前
完整的启动序列
当 vLLM 启动时,每个进程都会发生以下过程:
- 进程创建: vLLM 衍生出一个新进程(主进程、工作进程等)
- 插件系统激活: vLLM 在执行任何其他工作之前,在内部调用
load_general_plugins() - 入口点发现: Python 的入口点系统找到所有已注册的
vllm.general_plugins - 插件函数执行: 我们的
register_patches()函数被调用 - 补丁注册: 可用的补丁在管理器中注册
- 环境检查: 读取
VLLM_CUSTOM_PATCHES变量 - 选择性应用: 仅指定的补丁通过
VLLMPatch.apply()应用 - 版本验证: 每个补丁通过
@min_vllm_version检查 vLLM 版本兼容性 - 外科手术式修改: 在目标类上添加/替换特定方法
- 正常 vLLM 启动: 只有现在,vLLM 才会继续进行模型加载、调度器初始化等操作
这保证了您的补丁始终在 vLLM 执行任何操作之前 处于激活状态,确保所有进程的行为一致,并防止竞态条件。
基于插件扩展方式的优势
1. 极小且精准的补丁定义
没有重复的文件。没有冗余的代码。只有修改部分。 VLLMPatch 系统让您可以在不复制整个类的情况下添加单个方法。
2. 支持在同一个 vLLM 构建版本上运行多个模型
不同的模型可以通过 VLLM_CUSTOM_PATCHES 环境变量启用不同的补丁。
3. 版本感知安全检查
每个补丁都可以声明其所需的最低版本。
@min_vllm_version("0.9.1")
class MyPatch(VLLMPatch[TargetClass]):
pass这防止了升级过程中的意外行为。
4. 无需分叉、同步或变基(Rebase)
升级 vLLM 就像执行 pip install --upgrade vllm 并测试您的补丁一样简单。
5. 消除了猴子补丁带来的复杂性
整洁、可追踪的修改,没有传统猴子补丁那种静默破坏的风险。
6. 官方支持的 vLLM 功能
使用 vLLM 官方的 general_plugins 入口点系统,意味着这是一种受支持的扩展机制。
为什么这种模式很重要
随着推理引擎的飞速演进,团队经常被迫在以下两者之间做出选择:
- 修改内部行为
- 或者 与上游版本保持兼容
基于插件的扩展模型 消除了这种权衡。它让您能够快速创新,同时与快速增长的 vLLM 生态系统保持同步。
这种方法保持了最小的运营开销,同时维持了长期的灵活性 —— 无论是小团队还是大型平台团队都会非常欣赏这一点。
结语
如果您正在尝试或部署 vLLM,并且发现需要自定义行为,请考虑在承诺使用分支或猴子补丁策略之前,先利用通用插件系统。
它在 控制力、可维护性 和 合理性 之间取得了正确的平衡 —— 并且它能让您的代码库保持整洁、模块化和面向未来。
关键要点
- ✅ 使用
VLLMPatch[TargetClass]进行外科手术式的类级别修改 - ✅ 通过
setup.py中的vllm.general_plugins入口点注册 - ✅ 使用
VLLM_CUSTOM_PATCHES环境变量控制补丁。- 注:
VLLM_CUSTOM_PATCHES不是 官方的 vLLM 环境变量 —— 它只是本文中使用的示例。您可以在自己的插件包中选择任何环境变量名称。
- 注:
- ✅ 使用
@min_vllm_version装饰器对补丁进行版本保护 - ✅ 一个 Docker 镜像,多种配置
这种模式已被证明在生产环境中非常有效,并可从实验原型扩展到多模型生产部署。
联系我
如果您对推理系统的插件化架构感兴趣,或者想探索如何以整洁的方式组织运行时补丁,欢迎联系我。我很乐意交流关于可扩展 LLM 部署和设计模式的话题😊 您可以通过以下方式找到我:
