使用插件系统构建整洁、可维护的 vLLM 修改
说明
原文发布于这篇 Medium 文章。
来源:https://github.com/vllm-project/vllm-ascend
避免创建分支、避免猴子补丁,并保持头脑清醒
概述
大语言模型推理技术发展迅速,vLLM 已成为用于高吞吐量、低延迟模型服务的最强大引擎之一。它提供了连续批处理、高效调度、分页注意力以及生产就绪的 API 层——使其成为从小语言模型到大型前沿系统服务的理想选择。
但与任何快速发展的系统一样,团队或个人总有想要修改 vLLM 内部行为的时候。也许您想尝试自定义调度逻辑、更改 KV 缓存处理、注入专有优化,或者修补模型执行流程的一部分。
而这才是真正挑战的开始。
问题:“我需要修改 vLLM……现在该怎么办?”
如果更改很简单,或者对整个社区有益,答案就很直接
选项 A - 将您的贡献提交到 vLLM 上游
这始终是最整洁的方法。您的更改存在于开源社区,接受社区审查,并与 vLLM 的演进保持同步。
然而,现实并不总是那么尽如人意。许多修改是
- 专有的
- 特定领域的
- 过于实验性的
- 不够通用以至于无法被上游接受
- 或者受内部时间限制,与开源审查周期不符
当无法向上游提交时,您必须寻找另一条路。
选项 B - 维护您自己的 vLLM 分支 (fork)
这通常是第一反应
“让我们直接 fork vLLM,然后在那里添加我们的更改。”
虽然这对小型、进展缓慢的项目有效,但 vLLM 并非如此。
vLLM 是一个极其活跃的仓库,发布新版本的频率可达两周一次,并且每周合并数百个 PR。
维护一个长期运行的分支意味着
- ❌ 不断变基 (rebase) 或合并上游更改
- ❌ 在快速变化的领域解决冲突
- ❌ 手动重新应用您的补丁
- ❌ 进行繁重的兼容性测试
- ❌ 围绕一个自定义的 vLLM 工件管理内部开发工作流
不久之后,维护这个分支就成了一项全职工作。
对于许多团队来说,这种运营负担是根本不可持续的。
选项 C - 使用猴子补丁 (monkey patching)
另一条路是构建一个小的 Python 包,在构建时对原版 vLLM 应用猴子补丁。
乍一看,这似乎很吸引人
- ✅ 无需分支
- ✅ 与原版 vLLM 没有分歧
- ✅ 动态应用补丁
- ✅ 代码占用空间小
……但现实远非理想。
猴子补丁通常需要替换整个类或模块,即使您只想更改十行代码。这意味着
- ❌ 您复制了 vLLM 的大块源代码 - 即使是您没有修改的部分
- ❌ 每次 vLLM 升级都会破坏您的补丁 - 因为您替换了整个文件,而不仅仅是感兴趣的个别代码行
- ❌ 调试变得痛苦 - 错误是在您的补丁里?还是在未更改的原版代码里?还是因为猴子补丁意外地重新连接了行为?
- ❌ 运营复杂性随时间增长 - 每个 vLLM 版本都迫使您对比并重新同步您复制的文件——这与维护分支的问题完全相同,只是伪装在您的 Python 包中
- ❌ 对某些模块(如
Scheduler)进行猴子补丁通常不起作用,因为它们在EngineCore内部的另一个进程中运行。这可能导致进程同步问题,其中EngineCore继续调用您打算修改的模块的旧实现。
猴子补丁解决了表面问题,但引入了可能变得无法管理的长期维护挑战。
一个更整洁的替代方案:利用 vLLM 插件系统
为了克服分支和猴子补丁的局限性,我探索了 vLLM 不断发展的 通用插件架构,它允许开发人员在不更改上游代码的情况下,向引擎中注入有针对性的修改。
该架构能够实现
- ✅ 结构化、模块化的补丁
- ✅ 运行时激活
- ✅ 外科手术级别的代码覆盖
- ✅ 兼容性保障
- ✅ 无需复制整个文件
- ✅ 无需复杂的猴子补丁操作
- ✅ 无需维护分支
这提供了一个介于“一切都提交到上游”和“替换整个文件”之间的中间地带。
注意: vLLM 提供了四种插件组/机制——平台插件、引擎插件、模型插件和通用插件。本文特别关注通用插件系统,它会在所有 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-Instruct
Docker 集成
# 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 在进行任何其他 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. 不再需要创建分支、同步或变基
升级 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 部署和设计模式😊 您可以通过以下方式联系我
- 领英 (LinkedIn): https://www.linkedin.com/in/dhruvil-bhatt-uci/
- 个人网站 - https://www.dhruvilbhatt.com/
- 邮箱: dhruvilbhattlm10@gmail.com