文档编写指南¶
模型教程文档编写指南¶
docs/source/_templates/Model-Deployment-Tutorial-Template.md 是编写模型部署教程的模板。您可以复制并修改它以创建新文档。
可测试文档代码块生成(model-code)¶
- For documentation authors: how to insert testable command blocks into docs
- For developers: how to add a new converter
Built-in supported converter_tag values:
| converter_tag | Renders | YAML source |
|---|---|---|
single_node |
A single node's env exports + vllm serve script |
test_cases[case_index] |
multi_node |
One host's env exports + vllm serve script |
deployment[host_index] |
external_dp_template |
One external-DP node's env exports + vllm serve command |
templates[host_index] |
external_dp_launch |
One launch_online_dp.py line per node |
config[] |
external_dp_proxy |
The load-balance proxy launch command | config[] + routing |
For authors: add a block¶
Warning
By default, the generator scans only .md files under docs/source/tutorials/models/ and produces artifacts.
If you put model-code blocks in other directories, Sphinx builds will not automatically generate the corresponding scripts.
All model-code blocks need:
| Option | Required | Description |
|---|---|---|
block_name |
Yes | Block name; must be unique within the current document |
converter_tag |
Yes | Selects one of the built-in converters |
test_case_path |
Yes | Repository-relative YAML path that stays within the repo; the file must exist |
Use the body of the block to add shell wrapper lines such as set -eux. Always
place the {{ generated }} placeholder where the converter output should be
inserted.
converter_tag: single_node¶
single_node reads one item from test_cases. The optional case_index
metadata selects the item; when omitted, it defaults to 0.
Only the fields read by this converter are expanded below. Other test metadata can be left in the YAML and is ignored by this converter.
test_cases:
- name: qwen3-8b-single
model: Qwen/Qwen3-8B
envs:
HCCL_BUFFSIZE: "1024"
SERVER_PORT: DEFAULT_PORT
server_cmd:
- --tensor-parallel-size
- "1"
- --port
- $SERVER_PORT
- --trust-remote-code
server_cmd_extra:
- --enable-expert-parallel
benchmarks: ...
envs is rendered as export lines. SERVER_PORT: DEFAULT_PORT is resolved
to the default single-node port 8000. model becomes vllm serve <model>,
and server_cmd plus optional server_cmd_extra become command arguments.
Both command fields can be either a shell string or a flat token list.
按如下方式编写文档块:
```model-code
:block_name: qwen3_8b_single_node
:converter_tag: single_node
:test_case_path: tests/e2e/nightly/single_node/models/configs/your_model.yaml
:case_index: 0
set -eux
{{ generated }}
```
生成的 shell 脚本:
set -eux
export HCCL_BUFFSIZE=1024
export SERVER_PORT=8000
vllm serve Qwen/Qwen3-8B \
--tensor-parallel-size 1 \
--port $SERVER_PORT \
--trust-remote-code \
--enable-expert-parallel
converter_tag: multi_node¶
multi_node reads one item from deployment. The required host_index
metadata selects which host to render.
deployment:
- envs:
SERVER_PORT: "8000"
server_cmd: >
vllm serve Qwen/Qwen3-235B-A22B
--host 0.0.0.0
--port $SERVER_PORT
--data-parallel-size 2
--tensor-parallel-size 8
--data-parallel-address $LOCAL_IP
- envs:
SERVER_PORT: "8000"
server_cmd: >
vllm serve Qwen/Qwen3-235B-A22B
--headless
--port $SERVER_PORT
--data-parallel-size 2
--tensor-parallel-size 8
--data-parallel-start-rank 1
--data-parallel-address $MASTER_IP
benchmarks: ...
server_cmd must be a complete command starting with vllm serve <model>.
It can be written as a shell string or a flat token list.
按如下方式编写文档块:
```model-code
:block_name: qwen3_235b_worker_1
:converter_tag: multi_node
:test_case_path: tests/e2e/nightly/multi_node/internal_dp/config/your_model.yaml
:host_index: 1
set -eux
{{ generated }}
```
host_index: 1 生成的 shell 脚本:
set -eux
export MASTER_IP=192.168.1.10
export SERVER_PORT=8000
vllm serve Qwen/Qwen3-235B-A22B \
--headless \
--port $SERVER_PORT \
--data-parallel-size 2 \
--tensor-parallel-size 8 \
--data-parallel-start-rank 1 \
--data-parallel-address $MASTER_IP
converter_tag: external_dp_template¶
external_dp_template reads one item from templates. The required
host_index metadata selects which template to render. The top-level model
field is also required because the converter builds vllm serve <model>.
model: Eco-Tech/GLM-Test
templates:
- node_index: 0
envs:
HCCL_BUFFSIZE: "1024"
ASCEND_RT_VISIBLE_DEVICES: "${VISIBLE_DEVICES}"
server_cmd_template:
- --host
- 0.0.0.0
- --port
- ${PORT}
- --data-parallel-size
- ${DP_SIZE}
- --data-parallel-rank
- ${DP_RANK}
- --data-parallel-address
- ${DP_ADDRESS}
- --data-parallel-rpc-port
- ${DP_RPC_PORT}
- --tensor-parallel-size
- ${TP_SIZE}
- --trust-remote-code
config: ...
routing: ...
Known braced template variables are rewritten to the positional shell arguments
that run_dp_template.sh receives from launch_online_dp.py:
| 模板变量 | 渲染后的位置参数 |
|---|---|
${VISIBLE_DEVICES} |
$1 |
${PORT} |
$2 |
${DP_SIZE} |
$3 |
${DP_RANK} |
$4 |
${DP_ADDRESS} |
$5 |
${DP_RPC_PORT} |
$6 |
${TP_SIZE} |
$7 |
Unknown braced variables and unbraced shell references such as $SERVER_PORT
are left unchanged.
按如下方式编写文档块:
```model-code
:block_name: glm_external_dp_template_node0
:converter_tag: external_dp_template
:test_case_path: tests/e2e/nightly/multi_node/external_dp/config/your_model.yaml
:host_index: 0
set -eux
export HCCL_IF_IP=$local_ip
export GLOO_SOCKET_IFNAME=$nic_name
export TP_SOCKET_IFNAME=$nic_name
export HCCL_SOCKET_IFNAME=$nic_name
{{ generated }}
```
host_index: 0 生成的 shell 脚本:
set -eux
export HCCL_IF_IP=$local_ip
export GLOO_SOCKET_IFNAME=$nic_name
export TP_SOCKET_IFNAME=$nic_name
export HCCL_SOCKET_IFNAME=$nic_name
export HCCL_BUFFSIZE=1024
export ASCEND_RT_VISIBLE_DEVICES=$1
vllm serve Eco-Tech/GLM-Test \
--host 0.0.0.0 \
--port $2 \
--data-parallel-size $3 \
--data-parallel-rank $4 \
--data-parallel-address $5 \
--data-parallel-rpc-port $6 \
--tensor-parallel-size $7 \
--trust-remote-code
converter_tag: external_dp_launch¶
external_dp_launch reads the full config list and renders one
launch_online_dp.py command per node. It does not take an index option.
config:
- node_index: 0
port_start: 7100
dp_rpc_port: 12321
dp_size: 2
dp_size_local: 2
dp_rank_start: 0
tp_size: 8
dp_address: "${NODE_0_IP}"
- node_index: 1
port_start: 7200
dp_rpc_port: 12321
dp_size: 4
dp_size_local: 4
dp_rank_start: 0
tp_size: 4
dp_address: "${NODE_1_IP}"
templates: ...
routing: ...
按如下方式编写文档块:
```model-code
:block_name: glm_external_dp_launch
:converter_tag: external_dp_launch
:test_case_path: tests/e2e/nightly/multi_node/external_dp/config/your_model.yaml
set -eux
{{ generated }}
```
生成的 shell 脚本:
set -eux
python launch_online_dp.py --dp-size 2 --tp-size 8 --dp-size-local 2 --dp-rank-start 0 --dp-address ${NODE_0_IP} --dp-rpc-port 12321 --vllm-start-port 7100
python launch_online_dp.py --dp-size 4 --tp-size 4 --dp-size-local 4 --dp-rank-start 0 --dp-address ${NODE_1_IP} --dp-rpc-port 12321 --vllm-start-port 7200
converter_tag: external_dp_proxy¶
external_dp_proxy reads config and routing. It renders the
load_balance_proxy_server_example.py command for routing.type:
disaggregated_prefill. It does not take an index option.
routing:
type: disaggregated_prefill
groups:
prefiller: [0]
decoder: [1]
config:
- node_index: 0
port_start: 7100
dp_size_local: 2
dp_rpc_port: 12321
dp_size: 2
dp_rank_start: 0
tp_size: 8
dp_address: "${NODE_0_IP}"
- node_index: 1
port_start: 7200
dp_size_local: 4
dp_rpc_port: 12321
dp_size: 4
dp_rank_start: 0
tp_size: 4
dp_address: "${NODE_1_IP}"
templates: ...
routing.groups.prefiller and routing.groups.decoder contain indices into
config. Each referenced node expands to dp_size_local host and port entries.
The proxy itself is rendered on ${NODE_0_IP}:1999.
按如下方式编写文档块:
```model-code
:block_name: glm_external_dp_proxy
:converter_tag: external_dp_proxy
:test_case_path: tests/e2e/nightly/multi_node/external_dp/config/your_model.yaml
set -eux
{{ generated }}
```
生成的 shell 脚本:
set -eux
python load_balance_proxy_server_example.py \
--host ${NODE_0_IP} \
--port 1999 \
--prefiller-hosts \
${NODE_0_IP} \
${NODE_0_IP} \
--prefiller-ports \
7100 \
7101 \
--decoder-hosts \
${NODE_1_IP} \
${NODE_1_IP} \
${NODE_1_IP} \
${NODE_1_IP} \
--decoder-ports \
7200 \
7201 \
7202 \
7203
本地调试与生成¶
仅生成(不构建完整站点)¶
# Generate all model-code artifacts under docs/source/tutorials/models/
python3 tools/docs_codegen/cli.py
# Generate artifacts for a single document
python3 tools/docs_codegen/cli.py --doc docs/source/tutorials/models/Kimi-K2-Thinking.md
# Generate a single block and print it (no files written)
python3 tools/docs_codegen/cli.py \
--block docs/source/tutorials/models/Kimi-K2-Thinking.md::kimi_k2_thinking_single_node \
--dry-run --stdout
默认情况下,生成物写入:docs/_build/doc_codegen/<doc_stem>/<block_name>.sh。
Note
脚本生成后,请务必检查生成内容是否可运行,特别是环境变量和命令行参数等关键部分。
构建站点并在本地预览¶
# Install documentation build dependencies
python3 -m pip install -r docs/requirements-docs.txt
# (Optional) Clean previous builds
make -C docs clean
# Build the English site
make -C docs html
# (Optional) Build the Chinese site
make -C docs intl
# Preview locally
python3 -m http.server -d docs/_build/html 8000
# Then open in a browser:
# http://localhost:8000
面向开发者:添加新的转换器¶
A converter turns one loaded YAML file plus one parsed ModelCodeBlock into a
GeneratedScript. The current pipeline is:
BlockScannerparsesmodel-codefences and accepts only options listed inMODEL_CODE_OPTION_NAMES.YamlLoaderloadstest_case_path.get_converter()looks upblock.converter_tagfrombuild_default_converters().- The selected converter returns
GeneratedScript(content=..., language="shell"). GeneratorServicereplaces{{ generated }}in the block body, validates that the final script is non-empty, and writesdocs/_build/doc_codegen/<doc_stem>/<block_name>.sh.
To add a converter:
- In
tools/docs_codegen/converters.py, add aBaseConvertersubclass with a uniquename. That name is the value authors put in:converter_tag:. - Implement
convert(self, loaded_yaml, *, block) -> GeneratedScript. Usemake_docs_codegen_error(..., block=block)for user-facing validation errors so the CLI and Sphinx output include document context. - Reuse helpers from
tools/docs_codegen/utils.py, such asrequire_mapping,require_mapping_list,require_scalar_mapping,require_indexed_mapping,require_node_field,parse_command_tokens,substitute_template_positionals, andrender_cli_command. - Register the converter in
build_default_converters(). If it is not registered,get_converter()will reject the newconverter_tag. - If the converter needs new directive metadata, add the option name to
MODEL_CODE_OPTION_NAMESintools/docs_codegen/scanner.pyand toModelCodeDirective.option_specintools/docs_codegen/sphinx_extension.py. Read the option withblock.get_option("<option_name>"). - Add or update tests in
tests/ut/tools/test_docs_codegen.py. Cover the successful render path, required option validation, YAML shape validation, and any CLI/Sphinx scanner behavior affected by new metadata. - Add a real
model-codeexample in a model tutorial, preferably underdocs/source/tutorials/models/, and point it to an existing YAML file undertests/. - Validate with the CLI:
python3 tools/docs_codegen/cli.py --doc <your_doc> --dry-run
python3 tools/docs_codegen/cli.py --block <your_doc>::<block_name> --dry-run --stdout
If a converter should render something other than shell, set
GeneratedScript.language accordingly so Sphinx can highlight the generated
literal block correctly.