Skip to content

Commit e7f27ea

Browse files
authored
Improve --help for enhanced user experience (vllm-project#24903)
Signed-off-by: Harry Mellor <19981378+hmellor@users.noreply.github.com>
1 parent 1f29141 commit e7f27ea

File tree

8 files changed

+114
-137
lines changed

8 files changed

+114
-137
lines changed

docs/mkdocs/hooks/generate_argparse.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,5 +168,5 @@ def on_startup(command: Literal["build", "gh-deploy", "serve"], dirty: bool):
168168
doc_path = ARGPARSE_DOC_DIR / f"{stem}.md"
169169
# Specify encoding for building on Windows
170170
with open(doc_path, "w", encoding="utf-8") as f:
171-
f.write(parser.format_help())
171+
f.write(super(type(parser), parser).format_help())
172172
logger.info("Argparse generated: %s", doc_path.relative_to(ROOT_DIR))

vllm/engine/arg_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,8 @@ def is_online_quantization(quantization: Any) -> bool:
156156

157157

158158
NEEDS_HELP = (
159-
"--help" in (argv := sys.argv) # vllm SUBCOMMAND --help
160-
or (argv0 := argv[0]).endswith("mkdocs") # mkdocs SUBCOMMAND
159+
any("--help" in arg for arg in sys.argv) # vllm SUBCOMMAND --help
160+
or (argv0 := sys.argv[0]).endswith("mkdocs") # mkdocs SUBCOMMAND
161161
or argv0.endswith("mkdocs/__main__.py") # python -m mkdocs SUBCOMMAND
162162
)
163163

vllm/entrypoints/cli/benchmark/main.py

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@
88

99
from vllm.entrypoints.cli.benchmark.base import BenchmarkSubcommandBase
1010
from vllm.entrypoints.cli.types import CLISubcommand
11-
from vllm.entrypoints.utils import (VLLM_SUBCMD_PARSER_EPILOG,
12-
show_filtered_argument_or_group_from_help)
11+
from vllm.entrypoints.utils import VLLM_SUBCMD_PARSER_EPILOG
1312

1413
if typing.TYPE_CHECKING:
1514
from vllm.utils import FlexibleArgumentParser
@@ -33,9 +32,8 @@ def subparser_init(
3332
subparsers: argparse._SubParsersAction) -> FlexibleArgumentParser:
3433
bench_parser = subparsers.add_parser(
3534
self.name,
36-
help=self.help,
3735
description=self.help,
38-
usage="vllm bench <bench_type> [options]")
36+
usage=f"vllm {self.name} <bench_type> [options]")
3937
bench_subparsers = bench_parser.add_subparsers(required=True,
4038
dest="bench_type")
4139

@@ -44,13 +42,12 @@ def subparser_init(
4442
cmd_cls.name,
4543
help=cmd_cls.help,
4644
description=cmd_cls.help,
47-
usage=f"vllm bench {cmd_cls.name} [options]",
45+
usage=f"vllm {self.name} {cmd_cls.name} [options]",
4846
)
4947
cmd_subparser.set_defaults(dispatch_function=cmd_cls.cmd)
5048
cmd_cls.add_cli_args(cmd_subparser)
51-
show_filtered_argument_or_group_from_help(cmd_subparser,
52-
["bench", cmd_cls.name])
53-
cmd_subparser.epilog = VLLM_SUBCMD_PARSER_EPILOG
49+
cmd_subparser.epilog = VLLM_SUBCMD_PARSER_EPILOG.format(
50+
subcmd=f"{self.name} {cmd_cls.name}")
5451
return bench_parser
5552

5653

vllm/entrypoints/cli/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def main():
3030

3131
parser = FlexibleArgumentParser(
3232
description="vLLM CLI",
33-
epilog=VLLM_SUBCMD_PARSER_EPILOG,
33+
epilog=VLLM_SUBCMD_PARSER_EPILOG.format(subcmd="[subcommand]"),
3434
)
3535
parser.add_argument(
3636
'-v',

vllm/entrypoints/cli/run_batch.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
import typing
1010

1111
from vllm.entrypoints.cli.types import CLISubcommand
12-
from vllm.entrypoints.utils import (VLLM_SUBCMD_PARSER_EPILOG,
13-
show_filtered_argument_or_group_from_help)
12+
from vllm.entrypoints.utils import VLLM_SUBCMD_PARSER_EPILOG
1413
from vllm.logger import init_logger
1514

1615
if typing.TYPE_CHECKING:
@@ -50,7 +49,7 @@ def subparser_init(
5049
from vllm.entrypoints.openai.run_batch import make_arg_parser
5150

5251
run_batch_parser = subparsers.add_parser(
53-
"run-batch",
52+
self.name,
5453
help="Run batch prompts and write results to file.",
5554
description=(
5655
"Run batch prompts using vLLM's OpenAI-compatible API.\n"
@@ -59,9 +58,8 @@ def subparser_init(
5958
"vllm run-batch -i INPUT.jsonl -o OUTPUT.jsonl --model <model>",
6059
)
6160
run_batch_parser = make_arg_parser(run_batch_parser)
62-
show_filtered_argument_or_group_from_help(run_batch_parser,
63-
["run-batch"])
64-
run_batch_parser.epilog = VLLM_SUBCMD_PARSER_EPILOG
61+
run_batch_parser.epilog = VLLM_SUBCMD_PARSER_EPILOG.format(
62+
subcmd=self.name)
6563
return run_batch_parser
6664

6765

vllm/entrypoints/cli/serve.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@
1414
setup_server)
1515
from vllm.entrypoints.openai.cli_args import (make_arg_parser,
1616
validate_parsed_serve_args)
17-
from vllm.entrypoints.utils import (VLLM_SUBCMD_PARSER_EPILOG,
18-
show_filtered_argument_or_group_from_help)
17+
from vllm.entrypoints.utils import VLLM_SUBCMD_PARSER_EPILOG
1918
from vllm.logger import init_logger
2019
from vllm.usage.usage_lib import UsageContext
2120
from vllm.utils import (FlexibleArgumentParser, decorate_logs, get_tcp_uri,
@@ -29,6 +28,14 @@
2928

3029
logger = init_logger(__name__)
3130

31+
DESCRIPTION = """Launch a local OpenAI-compatible API server to serve LLM
32+
completions via HTTP. Defaults to Qwen/Qwen3-0.6B if no model is specified.
33+
34+
Search by using: `--help=<ConfigGroup>` to explore options by section (e.g.,
35+
--help=ModelConfig, --help=Frontend)
36+
Use `--help=all` to show all available flags at once.
37+
"""
38+
3239

3340
class ServeSubcommand(CLISubcommand):
3441
"""The `serve` subcommand for the vLLM CLI. """
@@ -56,14 +63,13 @@ def subparser_init(
5663
self,
5764
subparsers: argparse._SubParsersAction) -> FlexibleArgumentParser:
5865
serve_parser = subparsers.add_parser(
59-
"serve",
60-
help="Start the vLLM OpenAI Compatible API server.",
61-
description="Start the vLLM OpenAI Compatible API server.",
66+
self.name,
67+
description=DESCRIPTION,
6268
usage="vllm serve [model_tag] [options]")
6369

6470
serve_parser = make_arg_parser(serve_parser)
65-
show_filtered_argument_or_group_from_help(serve_parser, ["serve"])
66-
serve_parser.epilog = VLLM_SUBCMD_PARSER_EPILOG
71+
serve_parser.epilog = VLLM_SUBCMD_PARSER_EPILOG.format(
72+
subcmd=self.name)
6773
return serve_parser
6874

6975

vllm/entrypoints/utils.py

Lines changed: 9 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
# SPDX-License-Identifier: Apache-2.0
22
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
33

4-
import argparse
54
import asyncio
65
import dataclasses
76
import functools
87
import os
9-
import subprocess
10-
import sys
8+
from argparse import Namespace
119
from typing import Any, Optional, Union
1210

1311
from fastapi import Request
@@ -25,13 +23,10 @@
2523
logger = init_logger(__name__)
2624

2725
VLLM_SUBCMD_PARSER_EPILOG = (
28-
"Tip: Use `vllm [serve|run-batch|bench <bench_type>] "
29-
"--help=<keyword>` to explore arguments from help.\n"
30-
" - To view a argument group: --help=ModelConfig\n"
31-
" - To view a single argument: --help=max-num-seqs\n"
32-
" - To search by keyword: --help=max\n"
33-
" - To list all groups: --help=listgroup\n"
34-
" - To view help with pager: --help=page")
26+
"For full list: vllm {subcmd} --help=all\n"
27+
"For a section: vllm {subcmd} --help=ModelConfig (case-insensitive)\n" # noqa: E501
28+
"For a flag: vllm {subcmd} --help=max-model-len (_ or - accepted)\n" # noqa: E501
29+
"Documentation: https://docs.vllm.ai\n")
3530

3631

3732
async def listen_for_disconnect(request: Request) -> None:
@@ -196,96 +191,6 @@ def _validate_truncation_size(
196191
return truncate_prompt_tokens
197192

198193

199-
def _output_with_pager(text: str):
200-
"""Output text using scrolling view if available and appropriate."""
201-
202-
pagers = ['less -R', 'more']
203-
for pager_cmd in pagers:
204-
try:
205-
proc = subprocess.Popen(pager_cmd.split(),
206-
stdin=subprocess.PIPE,
207-
text=True)
208-
proc.communicate(input=text)
209-
return
210-
except (subprocess.SubprocessError, OSError, FileNotFoundError):
211-
continue
212-
213-
# No pager worked, fall back to normal print
214-
print(text)
215-
216-
217-
def show_filtered_argument_or_group_from_help(parser: argparse.ArgumentParser,
218-
subcommand_name: list[str]):
219-
220-
# Only handle --help=<keyword> for the current subcommand.
221-
# Since subparser_init() runs for all subcommands during CLI setup,
222-
# we skip processing if the subcommand name is not in sys.argv.
223-
# sys.argv[0] is the program name. The subcommand follows.
224-
# e.g., for `vllm bench latency`,
225-
# sys.argv is `['vllm', 'bench', 'latency', ...]`
226-
# and subcommand_name is "bench latency".
227-
if len(sys.argv) <= len(subcommand_name) or sys.argv[
228-
1:1 + len(subcommand_name)] != subcommand_name:
229-
return
230-
231-
for arg in sys.argv:
232-
if arg.startswith('--help='):
233-
search_keyword = arg.split('=', 1)[1]
234-
235-
# Enable paged view for full help
236-
if search_keyword == 'page':
237-
help_text = parser.format_help()
238-
_output_with_pager(help_text)
239-
sys.exit(0)
240-
241-
# List available groups
242-
if search_keyword == 'listgroup':
243-
output_lines = ["\nAvailable argument groups:"]
244-
for group in parser._action_groups:
245-
if group.title and not group.title.startswith(
246-
"positional arguments"):
247-
output_lines.append(f" - {group.title}")
248-
if group.description:
249-
output_lines.append(" " +
250-
group.description.strip())
251-
output_lines.append("")
252-
_output_with_pager("\n".join(output_lines))
253-
sys.exit(0)
254-
255-
# For group search
256-
formatter = parser._get_formatter()
257-
for group in parser._action_groups:
258-
if group.title and group.title.lower() == search_keyword.lower(
259-
):
260-
formatter.start_section(group.title)
261-
formatter.add_text(group.description)
262-
formatter.add_arguments(group._group_actions)
263-
formatter.end_section()
264-
_output_with_pager(formatter.format_help())
265-
sys.exit(0)
266-
267-
# For single arg
268-
matched_actions = []
269-
270-
for group in parser._action_groups:
271-
for action in group._group_actions:
272-
# search option name
273-
if any(search_keyword.lower() in opt.lower()
274-
for opt in action.option_strings):
275-
matched_actions.append(action)
276-
277-
if matched_actions:
278-
header = f"\nParameters matching '{search_keyword}':\n"
279-
formatter = parser._get_formatter()
280-
formatter.add_arguments(matched_actions)
281-
_output_with_pager(header + formatter.format_help())
282-
sys.exit(0)
283-
284-
print(f"\nNo group or parameter matching '{search_keyword}'")
285-
print("Tip: use `--help=listgroup` to view all groups.")
286-
sys.exit(1)
287-
288-
289194
def get_max_tokens(max_model_len: int, request: Union[ChatCompletionRequest,
290195
CompletionRequest],
291196
input_length: int, default_sampling_params: dict) -> int:
@@ -301,11 +206,11 @@ def get_max_tokens(max_model_len: int, request: Union[ChatCompletionRequest,
301206
if val is not None)
302207

303208

304-
def log_non_default_args(args: Union[argparse.Namespace, EngineArgs]):
209+
def log_non_default_args(args: Union[Namespace, EngineArgs]):
305210
non_default_args = {}
306211

307-
# Handle argparse.Namespace
308-
if isinstance(args, argparse.Namespace):
212+
# Handle Namespace
213+
if isinstance(args, Namespace):
309214
parser = make_arg_parser(FlexibleArgumentParser())
310215
for arg, default in vars(parser.parse_args([])).items():
311216
if default != getattr(args, arg):
@@ -323,6 +228,6 @@ def log_non_default_args(args: Union[argparse.Namespace, EngineArgs]):
323228
non_default_args["model"] = default_args.model
324229
else:
325230
raise TypeError("Unsupported argument type. " \
326-
"Must be argparse.Namespace or EngineArgs instance.")
231+
"Must be Namespace or EngineArgs instance.")
327232

328233
logger.info("non-default args: %s", non_default_args)

0 commit comments

Comments
 (0)