AFD Plugin Explained

afd_plugin/config.py: the AFD config schema and parser

IntermediateCode mapabout 4 min read + 1 min deep dives

Code version: afd-plugin @ 7452d52 · 2026-10-27 · vLLM @ ced6857

In one sentence

This module defines AFDConfig and the functions that parse and validate additional_config["afd"], with no vLLM or CUDA import.

Purpose#

The module turns a raw mapping into a validated AFDConfig. It is CPU-safe, so local tests parse a config without vLLM.

Filesafd_plugin/config.py (370 lines), config_utils.py (75 lines), afd_plugin/envs.py (27 lines).
Main classAFDConfig, a frozen dataclass (afd_plugin/config.py:41-73).
Runs inAny process (CPU), during config build and worker init. No vLLM import at module load.
Called byThe config patch (afd_plugin/compat/patches/config_validation.py:23), the validation module (afd_plugin/validation.py:12-17), and the connector factory.
CallsThe coercers in config_utils.py (afd_plugin/config.py:13-15).
Not in this filePer-connector option parsing (in each connector), worker selection (in validation.py), and the vLLM config field itself.

Where it sits#

Who calls it#

Every part that needs the AFD config parses it here. The config patch parses it to decide the worker and the NPU path (afd_plugin/compat/patches/config_validation.py:160-172). The runtime validators parse it with an expected role (afd_plugin/validation.py:93).

What it calls#

It calls the three coercers in config_utils.py for the typed fields (afd_plugin/config.py:147-174). It imports validate_p2p_topology lazily, only for the P2P connector (afd_plugin/config.py:335-338).

Key classes and functions#

NameLineRole
AFDConfigafd_plugin/config.py:41The frozen config dataclass with the aliases as properties.
_ALIASESafd_plugin/config.py:32-38Old afd_* names mapped to the short field names.
_normalize_mappingafd_plugin/config.py:120Rename aliases, split extra config, coerce types.
afd_config_from_mappingafd_plugin/config.py:179Build and optionally validate an AFDConfig.
parse_optional_afd_configafd_plugin/config.py:244Parse when the key is present, else None.
is_afd_activeafd_plugin/config.py:289True only when a present config validates.
is_afd_async_dpafd_plugin/config.py:295Select the async-DP mode from the flag and the connector.
validate_afd_configafd_plugin/config.py:311Check roles, connector, host, port and rank counts.
coerce_extra_intafd_plugin/config_utils.py:35Parse an int; reject a bool or a float.
force_balanced_topk_ids_enabledafd_plugin/envs.py:14Read a diagnostic env flag for routing.

Walk through the main methods#

AFDConfig and its aliases#

The dataclass is frozen, so a config cannot change after it is built. Each old afd_* name is a read-only property that returns the short field (afd_plugin/config.py:75-89).

    @property
    def afd_connector(self) -> str:
        return self.connector

    @property
    def afd_role(self) -> AFDRole:
        return self.role

    @property
    def afd_port(self) -> int:
        return self.port

    @property
    def afd_host(self) -> str:
        return self.host
The alias properties: afd_connector, afd_role, afd_port, afd_host.

_normalize_mapping#

This function does the rename, the split and the coercion in one pass.

    for key, value in raw.items():
        normalized_key = _ALIASES.get(key, key)
        if normalized_key == "connector_extra_config":
            if not isinstance(value, Mapping):
                raise TypeError(f"{key} must be a mapping")
            connector_extra_config = dict(value)
            continue
        if normalized_key not in valid_fields:
            raise ValueError(
                "unknown AFD config field "
                f"{key!r}; put connector-specific values under "
                "'connector_extra_config'",
            )
        if normalized_key in normalized:
            raise ValueError(
                f"duplicate AFD config field for {normalized_key!r}: "
                f"both alias and canonical key were provided",
            )
        normalized[normalized_key] = value
Rename the alias, pull out connector_extra_config, reject an unknown field, reject a duplicate.
  1. Line 128. It maps the key through _ALIASES, so an alias becomes its short name.
  2. Lines 129-133. It pulls connector_extra_config aside and checks it is a mapping.
  3. Lines 134-139. An unknown field raises, with a message that points to connector_extra_config.
  4. Lines 140-144. An alias and its canonical key together raise a duplicate error.

After the loop, it coerces the boolean and integer fields (afd_plugin/config.py:147-174).

validate_afd_config#

The validator runs on the built config. It raises a clear error for each broken rule (afd_plugin/config.py:311-350). For the P2P connector it also checks the topology (afd_plugin/config.py:335-338).

config_utils.coerce_extra_int#

The integer coercer rejects a bool or a float before it calls int(), so a wrong type cannot be truncated silently (afd_plugin/config_utils.py:35-45).

def coerce_extra_int(value: object, *, field_name: str) -> int:
    """Parse an integer option without truncating floats."""

    if isinstance(value, bool | float):
        raise TypeError(f"{field_name} must be an integer, got {value!r}")
    try:
        return int(value)
    except (TypeError, ValueError, OverflowError) as exc:
        raise TypeError(
            f"{field_name} must be an integer, got {value!r}",
        ) from exc
coerce_extra_int: reject bool and float, then parse.

Deep dive#

Deep dive

The parser entry points, and when each is used

Several functions wrap _normalize_mapping for different needs.

FunctionReturnsUsed for
parse_afd_configAn AFDConfig; raises if the key is missing (afd_plugin/config.py:262-279).Code that needs AFD to be on.
parse_optional_afd_configAFDConfig or None (afd_plugin/config.py:244-259).Code that checks whether AFD is on.
is_afd_activebool (afd_plugin/config.py:289-292).A quick on/off test with validation.
is_afd_async_dpbool (afd_plugin/config.py:295-308).Import-time async-DP patch selection (parses without validation).
connector_extra_config_from_sourcedict (afd_plugin/config.py:282-286).Get only the connector options.

compute_hash builds a stable hash of the graph-affecting settings, for cache keys (afd_plugin/config.py:99-114).

Deep dive

Environment flags in envs.py

envs.py holds two diagnostic flags. Each reads one variable and returns a bool.

VariableRead atEffect
AFD_FORCE_BALANCED_TOPK_IDSafd_plugin/envs.py:14-15 Force balanced top-k ids for the routing simulator.
AFD_ASYNC_CAM_LAYERED_GMMafd_plugin/envs.py:18-19 Select a layered GMM path in the async CAM connector (NPU).

A value is true when it is 1, true, yes or on, in any case (afd_plugin/envs.py:11).

Check yourself#

  1. 1
    Why is AFDConfig a frozen dataclass?
    easy
    Show answer

    So a config cannot change after it is built (afd_plugin/config.py:41). The old names are read-only properties (afd_plugin/config.py:75-89).

  2. 2
    Which function parses the config without validating it, and why?
    medium
    Show answer

    is_afd_async_dp. It is a lightweight selector for import-time async-DP patches, so it parses with validate=False (afd_plugin/config.py:295-308).

  3. 3
    What does _normalize_mapping do with a key it does not know?
    medium
    Show answer

    It raises ValueError and points to connector_extra_config for connector-specific values (afd_plugin/config.py:134-139).

  4. 4
    A caller passes "num_ffn_ranks": True. Where and why does this fail?
    hard
    Show answer

    In coerce_extra_int: it rejects a bool before calling int(), so the value does not become 1 (afd_plugin/config_utils.py:38-39). The integer fields use this coercer (afd_plugin/config.py:153-162).