vllm -- 源码学习2 (main.py) (小白级教程)
声明: 纯兴趣爱好,如有疏漏敬请谅解。
源码版本: v0.21.0
学习相关源码路径:
https://github.com/vllm-project/vllm/blob/v0.21.0/vllm/entrypoints/cli/main.py
学习总结:
源码的全局入口,即: 命令行启动入口(如何找到入口源码,请参见上一篇)。
main.py主要在做什么,个人理解总结:
根据调用vllm的命令行参数,去做函数分发。即: 根据你传入不同参数,执行不同启动函数。
命令总结:
(注: 执行命令别忘了激活你的环境)
vllm --不带任何参数、打印help信息
vllm -v --查询版本
vllm -version --查询版本
vllm serve --在线推理服务
vllm bench --性能基准测试 / 跑分
vllm chat --命令行直接聊天
vllm complete --命令行补全测试
vllm launch --启动vLLM 的独立底层组件
vllm collect-env --自动把你电脑的配置、版本、环境全部打包输出
vllm run-batch --批量跑提示词 + 把结果写入文件
流程梳理:
接下来一步一步梳理 vLLM 命令行源码启动的核心流程。看是如何支持这些命令的。
1. 模块列表变量: CMD_MODULES
声明模块类型的列表变量。
CMD_MODULES = [
vllm.entrypoints.cli.openai,
vllm.entrypoints.cli.serve,
vllm.entrypoints.cli.launch,
vllm.entrypoints.cli.benchmark.main,
vllm.entrypoints.cli.collect_env,
vllm.entrypoints.cli.run_batch,
]
这些模块和带参数的启动命令是一一对应的, 比如:serve对应vllm.entrypoints.cli.serve模块, 对应源码https://github.com/vllm-project/vllm/blob/v0.21.0/vllm/entrypoints/cli/serve.py。
重点: 这是下面分发逻辑要用的基础变量。
2. 调用函数cli_env_setup()
逻辑相对简单,在没有设置环境变量VLLM_WORKER_MULTIPROC_METHOD时,追加环境变量,值为: spawn。
非重点: 分发逻辑不用,可理解只是设置参数。
3. omni
如果在启动参数增加了omni参数,则启动流程就委托给omni。此命令是在v0.19.1版本后新增的,本次先不做深入展开。
4. bench预处理
只有启动参数是bench才会触发,例: vllm bench。
核心逻辑: 指定current_platform,未设置则默认指定为CpuPlatform()。
(注:current_platform在这里用了python的魔方方法https://github.com/vllm-project/vllm/blob/v0.21.0/vllm/platforms/__init__.py#L262)
非重点: 分发逻辑不用,可理解只是设置参数。
5.parser命令解释器
创建FlexibleArgumentParser命令行解释器,本质上是ArgumentParser。FlexibleArgumentParser继承了ArgumentParser,对部分函数做了功能增强。
parser = FlexibleArgumentParser(
description="vLLM CLI",
epilog=VLLM_SUBCMD_PARSER_EPILOG.format(subcmd="[subcommand]"),
)
增加-v --version参数
parser.add_argument(
"-v",
"--version",
action="version",
version=importlib.metadata.version("vllm"),
)
增加非必须子命令
subparsers = parser.add_subparsers(required=False, dest="subparser")
至此,可以看到代码中是如何接收-v --version参数与子命令。接下来继续学习,子命令与代码逻辑是如何关联。
重点: parser命令解释器与接下来的逻辑相关
6.关联逻辑
关联逻辑代码:
cmds = {}
for cmd_module in CMD_MODULES:
new_cmds = cmd_module.cmd_init()
for cmd in new_cmds:
cmd.subparser_init(subparsers).set_defaults(dispatch_function=cmd.cmd)
cmds[cmd.name] = cmd
args = parser.parse_args()
if args.subparser in cmds:
cmds[args.subparser].validate(args)
大体的思路是,循环模块变量,将子命令关联到各个函数。本来想在这里展开讲,当时发现东西不少。由于篇幅有限,在这里先不展开讲,准备放到下一篇详细讲解下关联代码逻辑。
在这里先了解核心机制,执行完这段代码,每个模块已经关联好子命令。
7. 分发
if hasattr(args, "dispatch_function"):
args.dispatch_function(args)
else:
parser.print_help()
代码的最后几行。
判断启动命令是否包含子命令,未包含则直接打印帮助信息:parser.print_help()。
若包含子命令,则进入相应模块的入口函数。
知识点总结
由于我算是python小白,可能总结的知识点会非常简单,还请见谅
1. 在函数内import模块
Python 允许在任何地方写 import,包括函数内部、if 内部、循环内部。
和顶部 import 有什么区别
顶部:
程序启动时就加载
全局都能用
启动慢一点
函数内:
调用函数时才加载
只在函数内能用
启动速度更快
避免循环依赖
2. sys.argv
用终端调用python时,输入的所有内容变成的列表。
举个例子, 假设你的python代码:
import sys
print(sys.argv)
调用命令: python test.py arg1 arg2 arg3 test
输出结果: ['test.py', 'arg1', 'arg2', 'arg3', '123']
3. __init__.py中__all__的作用
当别人 from 模块 import * 时,只能导入这里列出来的名字!没写的一律不让导!
4. Python魔法方法
def __getattr__(name: str):
当访问一个不存在的属性时触发。
比如在vllm源码中会使用: platforms.current_platform。
因为没定义current_platform,所以会进入__getattr__方法。
常用于懒加载,只有用到的时候才会进行初始化。
5. *args, **kwargs参数
def __init__(self, *args, **kwargs):
self:当前创建的解析器对象
*args:所有不带名字的参数
**kwargs:所有带名字的关键字参数(字典类型)
6. ArgumentParser
Python 官方自带的命令行参数解析工具
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)