说明

原文发布于这篇 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 启动时,每个进程中会发生以下情况

  1. 进程创建: vLLM 派生一个新进程(主进程、工作进程等)
  2. 插件系统激活: vLLM 在进行任何其他 vLLM 工作之前,内部调用 load_general_plugins()
  3. 入口点发现: Python 的入口点系统找到所有已注册的 vllm.general_plugins
  4. 插件函数执行: 我们的 register_patches() 函数被调用
  5. 补丁注册: 可用的补丁被注册到管理器中
  6. 环境检查: 读取 VLLM_CUSTOM_PATCHES 变量
  7. 选择性应用: 只有指定的补丁通过 VLLMPatch.apply() 应用
  8. 版本验证: 每个补丁通过 @min_vllm_version 检查 vLLM 版本兼容性
  9. 外科手术式修改: 在目标类上添加/替换特定的方法
  10. 正常的 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 部署和设计模式😊 您可以通过以下方式联系我