afd_plugin/validation.py: runtime wiring checks
Code version: afd-plugin @ 7452d52 · 2026-10-27 · vLLM @ ced6857
In one sentence
This module holds the CPU-safe checks that wire the AFD runtime: the worker names, the model-runner validators, the worker mapper and the stack check.
Purpose#
The module validates that an AFD deployment is wired correctly, before the model runs. It uses duck typing, so unit tests and CPU runs do not need a real vLLM config.
| File | afd_plugin/validation.py (388 lines, shared). |
|---|---|
| Main functions | validate_gpu_model_runner_v2_config
(afd_plugin/validation.py:76), assert_compatible_afd_stack
(afd_plugin/validation.py:312),
afd_worker_qualname_for_platform_default (afd_plugin/validation.py:280). |
| Runs in | The API server process (worker mapping) and each worker process (the
stack and runner checks, in init_device). |
| Called by | The config patch (afd_plugin/compat/patches/config_validation.py:24-26)
and the AFD workers (afd_plugin/v1/worker/attention_worker.py:16-19). |
| Calls | The config parser (afd_plugin/validation.py:12-17), the CUDA-graph
helpers (afd_plugin/validation.py:18-21), and has_afd_model_registration. |
| Not in this file | The NPU validator internals beyond naming them, the worker bodies,
and the config schema (in config.py). |
Where it sits#
Who calls it#
The config patch calls afd_worker_qualname_for_platform_default to rewrite the
worker (afd_plugin/compat/patches/config_validation.py:192-197). Each AFD worker calls
assert_compatible_afd_stack and the runner validator in init_device
(afd_plugin/v1/worker/attention_worker.py:64-73).
What it calls#
It parses the AFD config with an expected role (afd_plugin/validation.py:93), and imports
has_afd_model_registration lazily so the module stays independent of the model package
(afd_plugin/validation.py:136-138).
Key classes and functions#
| Name | Line | Role |
|---|---|---|
| Worker FQCN constants | afd_plugin/validation.py:26-41 | The qualified names of the AFD and native workers. |
BATCH_SIZE_FACTOR / cam_receive_capacity | afd_plugin/validation.py:45-54 | Compute the CAM receive capacity from an env factor. |
validate_gpu_model_runner_v2_config | afd_plugin/validation.py:76 | The GPU deployment rules. |
validate_npu_model_runner_v2_config | afd_plugin/validation.py:146 | The Ascend NPU deployment rules. |
afd_worker_qualname_for_platform_default | afd_plugin/validation.py:280 | Map the platform default worker to the AFD worker. |
assert_compatible_afd_stack | afd_plugin/validation.py:312 | Check the config and the worker class match the role. |
Walk through the main methods#
The FQCN constants#
The module names each worker and runner as a string constant. The GPU workers, the NPU workers
and the native platform workers all have their own names
(afd_plugin/validation.py:26-41).
ATTENTION_WORKER_FQCN: Final[str] = "afd_plugin.v1.worker.AFDAttentionWorker"
FFN_WORKER_FQCN: Final[str] = "afd_plugin.v1.worker.AFDFFNWorker"
ATTENTION_MODEL_RUNNER_FQCN: Final[str] = (
"afd_plugin.v1.worker.AFDAttentionModelRunnerV2"
)
FFN_MODEL_RUNNER_FQCN: Final[str] = "afd_plugin.v1.worker.GPUFFNModelRunner"
NPU_ATTENTION_WORKER_FQCN: Final[str] = "afd_plugin.v1.worker.npu.AFDNPUAttentionWorker"
NPU_FFN_WORKER_FQCN: Final[str] = "afd_plugin.v1.worker.npu.AFDNPUFFNWorker"
NPU_ATTENTION_MODEL_RUNNER_FQCN: Final[str] = (
"afd_plugin.v1.worker.npu.AFDNPUAttentionModelRunnerV2"
)
NPU_FFN_MODEL_RUNNER_FQCN: Final[str] = "afd_plugin.v1.worker.npu.AFDNPUFFNModelRunner"
VLLM_GPU_WORKER_FQCN: Final[str] = "vllm.v1.worker.gpu_worker.Worker"
VLLM_ASCEND_NPU_WORKER_FQCN: Final[str] = "vllm_ascend.worker.worker.NPUWorker"
VLLM_ASCEND_310P_WORKER_FQCN: Final[str] = "vllm_ascend._310p.worker_310p.NPUWorker310"
VLLM_ASCEND_XLITE_WORKER_FQCN: Final[str] = "vllm_ascend.xlite.xlite_worker.XliteWorker"
GPU_MRV2_NUM_UBATCHES: Final[int] = 2validate_gpu_model_runner_v2_config, rule by rule#
This function raises a RuntimeError for each broken rule
(afd_plugin/validation.py:76-143).
- Line 90-91. It needs the V2 model runner
(
afd_plugin/validation.py:90-91). - Line 93-97. The device must be CUDA and the connector the synchronous
P2pNcclAFDConnector(afd_plugin/validation.py:93-97). - Line 99-105. It rejects pipeline and context parallelism
(
afd_plugin/validation.py:99-105). - Line 107-117. The role rank count must equal data-parallel size times
tensor-parallel size (
afd_plugin/validation.py:107-117). - Line 118-125. It needs static expert parallelism, and rejects elastic EP,
EPLB and sequence-parallel MoE (
afd_plugin/validation.py:118-125). - Line 126-131. With DBO or ubatching, the number of microbatches must be exactly
two (
afd_plugin/validation.py:126-131). - Line 136-141. The model must resolve to a registered AFD model
(
afd_plugin/validation.py:136-141). - Line 143. It checks the CUDA-graph mode for the role
(
afd_plugin/validation.py:143).
assert_compatible_afd_stack#
This check compares the resolved worker with the expected AFD worker for the role. It raises
when the field is still "auto", or when the worker does not match
(afd_plugin/validation.py:337-367).
if worker_cls_raw.strip() == "auto":
raise ValueError(
"parallel_config.worker_cls remained 'auto' after AFD config "
"normalization; ensure the AFD general plugin is loaded before "
f"VllmConfig is created{_ctx()}",
)
expected_qualname = (
async_expected_worker
or expected_worker_qualname_override
or expected_worker_qualname(config.role)
)
worker_fqcn = normalize_qualname(worker_cls_raw.strip())
expected_fqcn = normalize_qualname(expected_qualname)
if worker_fqcn != expected_fqcn:
prefix = (
"CAMAsyncAFDConnector requires Ascend NPU worker class: "
if async_expected_worker is not None
else "invalid worker class for AFD runtime stack: "
)
raise ValueError(
prefix + f"got={worker_fqcn!r} expected={expected_qualname!r}; "
"remove --worker-cls to let AFD select it automatically, or pass "
f"--worker-cls {expected_qualname}{_ctx()}",
)afd_worker_qualname_for_platform_default#
This function maps the normalized platform default worker to the AFD worker for the role. CUDA
maps to the GPU workers; the standard Ascend worker maps to the NPU workers; the 310P and Xlite
Ascend workers are rejected (afd_plugin/validation.py:280-309). The
Worker selection: choosing the role worker page gives the table.
The NPU validator (NPU scope)#
validate_npu_model_runner_v2_config holds the Ascend rules
(afd_plugin/validation.py:146-239). It accepts the sync and async CAM connectors, and
checks eager execution, the DP rule and the ACL graph modes. This is NPU code; the
Ascend NPU overview: the same contract, different transports page covers it. Other pages cite it only to name a rule.
Deep dive#
Deep diveThe GPU rules as an error-path table
Every rule in validate_gpu_model_runner_v2_config raises a
RuntimeError with a clear message.
| Rule | Lines | Message hint |
|---|---|---|
| No V2 runner | afd_plugin/validation.py:90-91 | "AFD GPU requires ModelRunnerV2". |
| Wrong device or connector | afd_plugin/validation.py:93-97 | "requires CUDA and synchronous P2pNcclAFDConnector". |
| PP or CP set | afd_plugin/validation.py:99-105 | "does not support PP or CP". |
| Rank mismatch | afd_plugin/validation.py:107-117 | "ranks must match DP * TP". |
| Not static EP | afd_plugin/validation.py:118-125 | "requires static expert parallelism". |
| Wrong ubatch count | afd_plugin/validation.py:126-131 | "requires exactly two microbatches". |
| Not an AFD model | afd_plugin/validation.py:136-141 | "requires a registered AFD model". |
Deep diveBATCH_SIZE_FACTOR and the CAM receive capacity
cam_receive_capacity scales a fixed token cap by an environment factor. It reads
BATCH_SIZE_FACTOR (default "1"), packs it as a 32-bit float to match the
native code, and multiplies CAM_MAX_RECEIVE_TOKENS (262144)
(afd_plugin/validation.py:45-54).
CAM_MAX_RECEIVE_TOKENS = 262144
CAM_MAX_SOURCE_ROWS = (1 << 16) - 1
def cam_receive_capacity() -> int:
# Match the native std::stof + float multiplication, including rounding.
factor = struct.unpack(
"f", struct.pack("f", float(os.environ.get("BATCH_SIZE_FACTOR", "1")))
)[0]
return int(CAM_MAX_RECEIVE_TOKENS * factor)This value bounds the async CAM connector on NPU. The 32-bit pack and unpack reproduce the
rounding of the native std::stof and float multiply, so Python and the kernel agree on
the capacity.
Related concepts#
- Worker selection: choosing the role worker
- AFD configuration: additional_config["afd"]
- afd_plugin/config.py: the AFD config schema and parser
- afd_plugin/__init__.py: the plugin entry module
Check yourself#
- 1What doeseasy
validate_gpu_model_runner_v2_configcheck first?Show answer
That the V2 model runner is on. It raises "AFD GPU requires ModelRunnerV2" otherwise (
afd_plugin/validation.py:90-91). - 2Why does the module importmedium
has_afd_model_registrationinside the function, not at the top?Show answer
To keep the CPU-safe validation module independent of the model-wrapper package; the validator runs only after the vLLM config is built (
afd_plugin/validation.py:133-138). - 3Which two connectors does the NPU validator accept?medium
Show answer
The synchronous
CAMP2pAFDConnectorand the asyncCAMAsyncAFDConnector(afd_plugin/validation.py:159-178). It is NPU code; see Ascend NPU overview: the same contract, different transports. - 4Why doeshard
cam_receive_capacitypack the factor as a 32-bit float before multiplying?Show answer
To reproduce the native
std::stofand float multiply exactly, including the rounding, so Python and the kernel compute the same capacity (afd_plugin/validation.py:49-54).