Tour: Run a recipe
Code version: afd-plugin @ 7452d52 · 2026-10-27 · vLLM @ ced6857
In one sentence
This tour reads one GPU recipe line by line. It shows what each flag does inside the plugin and vLLM, and how to send a request.
The idea#
A recipe is a shell script that starts the two servers of an AFD run. This tour
reads 2a2f_graph_dbo_dp1tp2.sh, the 2A2F graph recipe for DeepSeek-V2-Lite
(recipe/gpu/P2pNcclAFDConnector/deepseek_v2_lite/prefill_decode_colocation/2a2f_graph_dbo_dp1tp2.sh:1-61).
It starts one attention server on two GPUs and one FFN server on
two GPUs, with CUDA graphs and DBO on.
Both servers use the same --additional-config block; only role and the GPUs differ.
The Recipes reference page lists every recipe; the Tour: Life of a request tour follows a
request through this same recipe.
The picture#
Read panel 1 as the two serve commands, side by side. Read panel 2 as the order: background both, wait for startup, then send requests to the attention port.
1Turn the V2 runner on#
The script exports one environment variable before it starts a server
(recipe/gpu/P2pNcclAFDConnector/deepseek_v2_lite/prefill_decode_colocation/2a2f_graph_dbo_dp1tp2.sh:5).
MODEL_PATH=${MODEL_PATH:-/path/model_weights/DeepSeek-V2-Lite}
export VLLM_USE_V2_MODEL_RUNNER=1AFD runs on Model Runner V2. Setting
VLLM_USE_V2_MODEL_RUNNER=1 turns the V2 runner on explicitly. vLLM also picks V2 by default when the
variable is unset (vllm:vllm/config/vllm.py:692-694); the recipe sets it so the choice is clear. The
validator raises only when V2 resolves to off (afd_plugin/validation.py:90-91).
2The attention serve command and the parallel sizes#
The attention server runs on GPUs 0 and 1. The parallel flags set the per-role rank count
(recipe/gpu/P2pNcclAFDConnector/deepseek_v2_lite/prefill_decode_colocation/2a2f_graph_dbo_dp1tp2.sh:7-10).
CUDA_VISIBLE_DEVICES=0,1 uv run vllm serve "$MODEL_PATH" \
--data-parallel-size 1 \
--tensor-parallel-size 2 \
--enable-expert-parallel \CUDA_VISIBLE_DEVICES=0,1picks the two GPUs for this role.--data-parallel-size 1 --tensor-parallel-size 2gives two attention ranks, because the rank count must equalDP × TP(afd_plugin/validation.py:107-117).--enable-expert-parallelturns on static expert parallelism, which AFD requires (afd_plugin/validation.py:118-125).
See Deployment topologies: how to place the roles for the rank rule.
3The afd block in --additional-config#
The --additional-config JSON carries the AFD settings under the "afd" key
(recipe/gpu/P2pNcclAFDConnector/deepseek_v2_lite/prefill_decode_colocation/2a2f_graph_dbo_dp1tp2.sh:11-20).
--additional-config '{
"afd": {
"role": "attention",
"connector": "P2pNcclAFDConnector",
"host": "127.0.0.1",
"port": 6269,
"num_attention_ranks": 2,
"num_ffn_ranks": 2
}
}' \The plugin reads this inner mapping and builds an AFDConfig
(afd_plugin/config.py:42). The role is attention here; the
connector is the GPU P2P connector; host and port
set the rendezvous where the two roles meet; num_attention_ranks and
num_ffn_ranks are both 2. See AFD config reference: the additional_config["afd"] keys.
4The batch, DBO and CUDA graph flags#
The next flags set the batch limits, DBO and the CUDA graph
(recipe/gpu/P2pNcclAFDConnector/deepseek_v2_lite/prefill_decode_colocation/2a2f_graph_dbo_dp1tp2.sh:21-29).
--max-num-seqs 64 \
--max-num-batched-tokens 64 \
--enable-dbo \
--dbo-decode-token-threshold 2 \
--dbo-prefill-token-threshold 12 \
--max-cudagraph-capture-size 64 \
--compilation-config '{
"cudagraph_mode": "FULL_DECODE_ONLY", "cudagraph_capture_sizes":[64]
}' \--enable-dbowith--dbo-decode-token-threshold 2and--dbo-prefill-token-threshold 12turns on DBO with low example thresholds. AFD needs exactly two microbatches when DBO is on (afd_plugin/validation.py:126-131). See Dual-batch overlap (DBO): two stages per step.--max-cudagraph-capture-size 64and the--compilation-configwithcudagraph_mode: FULL_DECODE_ONLYturn on CUDA graphs. GPU AFD allows onlyFULL_DECODE_ONLY(afd_plugin/v1/worker/cuda_graph.py:20-21). See CUDA graphs in AFD: FULL_DECODE_ONLY on both roles.
5The HTTP port and backgrounding#
The attention server binds an HTTP port and runs in the background
(recipe/gpu/P2pNcclAFDConnector/deepseek_v2_lite/prefill_decode_colocation/2a2f_graph_dbo_dp1tp2.sh:30-32).
--host 127.0.0.1 \
--port 18305 \
--trust-remote-code > attn.log 2>&1 &Clients connect to this HTTP port, 18305. It is a different port from the AFD
6269: the HTTP port carries requests, the AFD port carries the connector setup. The command ends
with &, so it runs in the background.
6The FFN server: the same block, role ffn#
The FFN server is the same command with two changes: GPUs 2 and 3, and role: ffn
(recipe/gpu/P2pNcclAFDConnector/deepseek_v2_lite/prefill_decode_colocation/2a2f_graph_dbo_dp1tp2.sh:34-59).
CUDA_VISIBLE_DEVICES=2,3 uv run vllm serve "$MODEL_PATH" \
--data-parallel-size 1 \
--tensor-parallel-size 2 \
--enable-expert-parallel \
--additional-config '{
"afd": {
"role": "ffn",
"connector": "P2pNcclAFDConnector",
"host": "127.0.0.1",
"port": 6269,
"num_attention_ranks": 2,
"num_ffn_ranks": 2
}
}' \The FFN server reads the same host, port and rank counts, so the two roles meet at
the same rendezvous. It runs no KV cache and no real scheduler, only a no-op stand-in; the connector drives it
(afd_plugin/compat/patches/engine_core.py:599-623). Send no client traffic here. See
Attention and FFN roles: what each server owns and The FFN runtime: a connector-driven server loop.
7Start order: background both, then wait#
The script backgrounds both servers and ends with wait
(recipe/gpu/P2pNcclAFDConnector/deepseek_v2_lite/prefill_decode_colocation/2a2f_graph_dbo_dp1tp2.sh:53-61).
--trust-remote-code > ffn.log 2>&1 &
waitBoth roles start together and meet at the AFD rendezvous. Wait for Application startup complete
in each log before you send traffic (recipe/gpu/P2pNcclAFDConnector/README.md:39-43).
Deep diveRun it from the repository root
Set the model path and the V2 runner, then run the script
(recipe/gpu/P2pNcclAFDConnector/deepseek_v2_lite/README.md:88-92).
export MODEL_PATH=/path/model_weights/DeepSeek-V2-Lite
export VLLM_USE_V2_MODEL_RUNNER=1
bash recipe/gpu/P2pNcclAFDConnector/deepseek_v2_lite/prefill_decode_colocation/2a2f_graph_dbo_dp1tp2.sh
The script writes attn.log and ffn.log in the current directory.
8Send a request, then benchmark it#
Send requests only to the attention HTTP server
(README.md:258-260). The FFN server has no request path; a request there fails fast
(afd_plugin/v1/worker/ffn_worker.py:122-131).
curl http://127.0.0.1:18305/v1/completions \
-H 'Content-Type: application/json' \
-d '{"model": "'"$MODEL_PATH"'", "prompt": "Hello", "max_tokens": 16}'
To benchmark, drive load against the same port with the repository's wrapper around
vllm bench serve (tools/benchmarks/request_generator.sh:44-57). The README shows the
benchmark command for this model (recipe/gpu/P2pNcclAFDConnector/deepseek_v2_lite/README.md:104-120).
See Tuning: the knobs the repo exposes.
What can go wrong#
- A request sent to the FFN server. The FFN worker is connector-driven; a
scheduler-driven
execute_modelraises at once (afd_plugin/v1/worker/ffn_worker.py:122-131). - Different host or port on the two roles. Both must use the same AFD
host:port, or the group setup hangs until the timeout (afd_plugin/connectors/gpu/p2p.py:271). - Traffic sent before startup. Wait for
Application startup completein both logs first (recipe/gpu/P2pNcclAFDConnector/README.md:39-43). - The rank count does not match DP x TP. The validator raises before the run starts
(
afd_plugin/validation.py:107-117).
Why it matters. The recipe is the full launch contract. One wrong flag, port, or target stops the run or hangs the rendezvous.
Check yourself#
- 1Which port do clients connect to, and which port is the AFD rendezvous?easy
Show answer
Clients connect to the HTTP port
18305; the roles meet at the AFD port6269(recipe/gpu/P2pNcclAFDConnector/deepseek_v2_lite/prefill_decode_colocation/2a2f_graph_dbo_dp1tp2.sh:16,recipe/gpu/P2pNcclAFDConnector/deepseek_v2_lite/prefill_decode_colocation/2a2f_graph_dbo_dp1tp2.sh:31). - 2What changes between the attention and FFN serve commands?easy
Show answer
The GPUs (0,1 vs 2,3) and the
role(attentionvsffn); the rest of the afd block is the same (recipe/gpu/P2pNcclAFDConnector/deepseek_v2_lite/prefill_decode_colocation/2a2f_graph_dbo_dp1tp2.sh:13,recipe/gpu/P2pNcclAFDConnector/deepseek_v2_lite/prefill_decode_colocation/2a2f_graph_dbo_dp1tp2.sh:40). - 3Why does the recipe setmedium
VLLM_USE_V2_MODEL_RUNNER=1if vLLM picks V2 by default?Show answer
To be explicit. vLLM picks V2 by default when the variable is unset (
vllm:vllm/config/vllm.py:692-694), and AFD requires the V2 runner (afd_plugin/validation.py:90-91). - 4The recipe turns on DBO with thresholds 2 and 12. What constraint must the run then meet?medium
Show answer
Exactly two microbatches. AFD raises when DBO or ubatching is on and
num_ubatches != 2(afd_plugin/validation.py:126-131). - 5Which two flags switch this recipe from eager to graph, and which graph mode do they set?hard
Show answer
--max-cudagraph-capture-size 64and--compilation-configwithcudagraph_mode: FULL_DECODE_ONLY(recipe/gpu/P2pNcclAFDConnector/deepseek_v2_lite/prefill_decode_colocation/2a2f_graph_dbo_dp1tp2.sh:26-28). GPU AFD allows only that mode (afd_plugin/v1/worker/cuda_graph.py:20-21).