大模型推理引擎vLLM(22):怎么添加一个CLI参数以及命令行参数的解析、传递链路代码梳理
目录
2 怎么增加添加一个命令行参数:以--all2all-backend为例
2.1 第 1 步:在 ParallelConfig 里定义字段
2.4 第 4 步:把 CLI 值写进 ParallelConfig
3.1 vllm018/vllm/entrypoints/cli/main.py
3.2 vllm018/vllm/entrypoints/cli/serve.py
3.3 vllm018/vllm/entrypoints/openai/api_server.py
abstract
CLI Command Line Interface
@dataclass 自动生成初始化和 __repr__函数,若定义了__post_init__,自动调用__post_init__函数,
repr(representation)就是用字符串表示对象的类名和成员
literal 字面量
CLI 值先进 EngineArgs,再写进 ParallelConfig,再装进 VllmConfig
EngineArgs, ParallelConfig, VllmConfig
all2all_backend: All2AllBackend = ParallelConfig.all2all_backend 变量名 类型 默认值
星号解析列表或元组,星星解析词典key=value
0 引言
工作中需要将之前用环境变量设置的变量,改为用命令行传输,借此机会,梳理下怎么添加一个参数,以及这个参数的从上到下传递链路。
1 先大体全局搜一下

然后先把搜索结果过一遍,基本上 就能有个大概了,
2 怎么增加添加一个命令行参数:以--all2all-backend为例
2.1 第 1 步:在 ParallelConfig 里定义字段
vllm021/vllm/config/parallel.py
先加类型别名(可选但推荐):
All2AllBackend = Literal[
"naive",
"pplx",
"deepep_high_throughput",
"deepep_low_latency",
"mori",
"nixl_ep",
"allgather_reducescatter",
"flashinfer_all2allv", # temporary alias for flashinfer_nvlink_two_sided
"flashinfer_nvlink_two_sided",
"flashinfer_nvlink_one_sided",
]
然后还是当前文件,class ParallelConfig:类中添加字段和字段说明

all2all_backend: All2AllBackend = "allgather_reducescatter"
"""All2All backend for MoE expert parallel communication. Available options:
- "allgather_reducescatter": All2all based on allgather and reducescatter
- "deepep_high_throughput": Use deepep high-throughput kernels
- "deepep_low_latency": Use deepep low-latency kernels
- "mori": Use mori kernels
- "nixl_ep": Use nixl-ep kernels
- "flashinfer_nvlink_two_sided": Use flashinfer two-sided kernels for mnnvl
- "flashinfer_nvlink_one_sided": Use flashinfer high-throughput a2a kernels"""
2.2 第 2 步:在 EngineArgs 里镜像字段
文件:vllm021/vllm/engine/arg_utils.py
位置:class EngineArgs 成员声明处(现约 480 行)
前面已经在parallelconfig类中添加这个参数了,这里为什么在EngineArgs类中又添加了一个同名字段,
为什么要“镜像”一份,而不是 CLI 直接改 ParallelConfig?因为链路是分层的:
命令行 args
→ 先填进扁平的 EngineArgs(第 2、3 步 + from_cli_args)
→ 再在 create_engine_config 里拆开,分别构造
ModelConfig / ParallelConfig / CacheConfig / ...
→ 最后装进 VllmConfig
engineargs其实就是暂存 解析出来的命令行参数的,解析出来之后再给到parallelconfig
2.3 第 3 步:注册命令行参数
文件:还是 arg_utils.py
位置:EngineArgs.add_cli_args 里(现约 1047–1049 行)
前面已有:
parallel_kwargs = get_kwargs(ParallelConfig)
再加:
parallel_group.add_argument(
"--all2all-backend", **parallel_kwargs["all2all_backend"]
)
这里 parallel_kwargs = get_kwargs(ParallelConfig)这里相当于把parallel里面的所有的变量全都解析了一遍,然后得到一个词典,里面的key就是变量名,value就是defular, choices, type, help信息
大致是
{
"all2all_backend": {
"default": "allgather_reducescatter",
"choices": [...],
"type": str,
"help": "...", # 只有 --help 时通常才有内容
},
"tensor_parallel_size": { ... },
"enable_expert_parallel": { ... },
...
}
然后下面
parallel_group.add_argument(
"--all2all-backend", **parallel_kwargs["all2all_backend"]
)
就是类似于
# 等价于手写一长串,只是帮你省事
parallel_group.add_argument(
"--all2all-backend",
default="allgather_reducescatter",
choices=[...],
type=str,
help="...",
)
2.4 第 4 步:把 CLI 值写进 ParallelConfig
文件:还是 arg_utils.py
位置:class EngineArgs:的create_engine_config函数里面,构造 ParallelConfig(...) 时(现约 1934 行)
parallel_config = ParallelConfig(
...
all2all_backend=self.all2all_backend,
...
)
3 怎么从main.py一层层下来的
前面总结的是如果开发代码,增加一个 命令行参数,需要修改哪些代码,接下来看一下,增加完之后,真正使用的时候,一个命令行参数是怎么一步步传下来的。
3.1 vllm018/vllm/entrypoints/cli/main.py
parser = FlexibleArgumentParser(
description="vLLM CLI",
epilog=VLLM_SUBCMD_PARSER_EPILOG.format(subcmd="[subcommand]"),
)
parser.add_argument(
"-v",
"--version",
action="version",
version=importlib.metadata.version("vllm"),
)
subparsers = parser.add_subparsers(required=False, dest="subparser")
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)
if hasattr(args, "dispatch_function"):
args.dispatch_function(args)
else:
parser.print_help()
if __name__ == "__main__":
main()

这里有个args = parser.parse_args(),就相当于是解析了命令行参数。
然后有个 args.dispatch_function(args),这就是去了相应的function,这里
dispatch_function 是在注册子命令时绑定的。main.py 68 行:
cmd.subparser_init(subparsers).set_defaults(dispatch_function=cmd.cmd)
执行的是 vllm serve ... 时,args.subparser == "serve",
所以:args.dispatch_function == ServeSubcommand.cmd
也就是 serve.py 里的 cmd 函数。
那么就去了serve.py
3.2 vllm018/vllm/entrypoints/cli/serve.py
在这个文件
class ServeSubcommand(CLISubcommand):
"""The `serve` subcommand for the vLLM CLI."""
name = "serve"
@staticmethod
def cmd(args: argparse.Namespace) -> None:
这里往下
if args.api_server_count < 1:
run_headless(args)
elif args.api_server_count > 1:
run_multi_api_server(args)
else:
# Single API server (this process).
args.api_server_count = None
uvloop.run(run_server(args))
然后就去了vllm018/vllm/entrypoints/openai/api_server.py里面的run_server
3.3 vllm018/vllm/entrypoints/openai/api_server.py

这里去了670行的这个函数,那么

然后就到了前面分析的vllm018/vllm/engine/arg_utils.py里面的**from_cli_args**函数和**create_engine_config**函数了。也即是获取参数,以及构造了vllmconfig,


4 函数调用汇总
vllm serve(命令行)
│
├─ vllm018/vllm/entrypoints/cli/main.py:53
│ parser = FlexibleArgumentParser(...)
│
├─ vllm018/vllm/entrypoints/cli/main.py:68
│ cmd.subparser_init(subparsers).set_defaults(dispatch_function=cmd.cmd)
│ (vllm serve 时 → dispatch_function = ServeSubcommand.cmd)
│
├─ vllm018/vllm/entrypoints/cli/main.py:70
│ args = parser.parse_args()
│ └─ 得到 args(含 args.all2all_backend 等)
│ (内部:utils/argparse_utils.py:179 FlexibleArgumentParser.parse_args
│ → :360 super().parse_args())
│
├─ vllm018/vllm/entrypoints/cli/main.py:72
│ cmds[args.subparser].validate(args)
│ └─ serve 时 → entrypoints/cli/serve.py:120 validate_parsed_serve_args
│
└─ vllm018/vllm/entrypoints/cli/main.py:75
args.dispatch_function(args)
│
└─ vllm018/vllm/entrypoints/cli/serve.py:48
ServeSubcommand.cmd(args)
│
└─ vllm018/vllm/entrypoints/cli/serve.py:118 【单 API 常见分支】
uvloop.run(run_server(args))
│
└─ vllm018/vllm/entrypoints/openai/api_server.py:649
async def run_server(args, **uvicorn_kwargs)
│
└─ vllm018/vllm/entrypoints/openai/api_server.py:656
await run_server_worker(listen_address, sock, args, ...)
│
└─ vllm018/vllm/entrypoints/openai/api_server.py:659
async def run_server_worker(...)
│
└─ vllm018/vllm/entrypoints/openai/api_server.py:670
async with build_async_engine_client(args, ...)
│
└─ vllm018/vllm/entrypoints/openai/api_server.py:77
async def build_async_engine_client(args, ...)
│
├─ vllm018/vllm/entrypoints/openai/api_server.py:95
│ engine_args = AsyncEngineArgs.from_cli_args(args)
│ │
│ └─ vllm018/vllm/engine/arg_utils.py:1337
│ def from_cli_args(cls, args) # 1337–1344
│ └─ 得到 EngineArgs(含 all2all_backend)
│
└─ vllm018/vllm/entrypoints/openai/api_server.py:103
async with build_async_engine_client_from_engine_args(engine_args, ...)
│
└─ vllm018/vllm/entrypoints/openai/api_server.py:113
async def build_async_engine_client_from_engine_args(engine_args, ...)
│
└─ vllm018/vllm/entrypoints/openai/api_server.py:129
vllm_config = engine_args.create_engine_config(...)
│
└─ vllm018/vllm/engine/arg_utils.py:1476
def create_engine_config(self, ...) -> VllmConfig
│
├─ vllm018/vllm/engine/arg_utils.py:1716
│ parallel_config = ParallelConfig(
│ ...,
│ all2all_backend=self.all2all_backend, # 约 1736
│ ...
│ )
│ └─ vllm018/vllm/config/parallel.py:161 字段定义
│ └─ __post_init__ 校验(HCU patch 可能 env 覆盖)
│
└─ vllm018/vllm/engine/arg_utils.py:1923
config = VllmConfig(
...,
parallel_config=parallel_config,
...
)
└─ 总配置 VllmConfig 就绪
5 命令行可以用空格也可以用等号,可以用下划线_也可以用-
从下面的这段代码可以看出来,这里我们可以用下面的四种写法都是可以的
--all2all_backend deepep_high_throughput--all2all_backend=deepep_high_throughput--all2all-backend deepep_high_throughput--all2all-backend=deepep_high_throughput
其中,假如使我们写了_,那么会被替换成-,就是下图中的repl函数,
然后下面的if else分支可以看出来,他既支持空格,也支持等号,但是注意这里的if lese其实只是把_转成-,并没有处理参数,这里如果是有等号,那是把_改成-,如果是空格是把key的_改成-,修改完之后,本来空格的还是空格,本来等号的还是等号,至于根据等号和空格进行解析,不是这个函数的工作,是在其他地方解析的,
6 参考文献
VLLM开源代码:https://github.com/vllm-project/vllm
更多推荐





所有评论(0)