AFD Plugin Explained

The vLLM v1 map

BeginnerLearn the basicsabout 5 min read + 1 min deep dives

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

In one sentence

AFD hooks into one chain of vLLM v1 parts: API server, EngineCore, executor, worker, Model Runner V2 and the model.

The idea#

vLLM v1 runs a request through a fixed chain of parts. The API server process builds the config and owns HTTP. The EngineCore process holds the scheduler and the executor. The executor starts the workers. Each worker owns one device and builds a model runner, which runs the model.

afd-plugin does not replace this chain. It changes six small points along it: the config, the EngineCore start, the worker class, the model runner class and the model class. This page names each box once, gives its vLLM line, and points to the AFD hook.

An everyday comparison. Think of a factory line with six stations. AFD does not rebuild the line; it changes the tool at a few stations. The comparison stops here: one station (the FFN EngineCore) stops the line early and runs its own loop.

The picture#

Figure: the six vLLM v1 boxes from top to bottom, each with its vLLM line and the violet AFD hook on it.
Open full size ↗

Read the boxes top to bottom. Each box runs in a process or is built inside one. The violet row on each box is the one place AFD changes it. The afd-plugin loads once in each process, so the hooks take effect everywhere they are needed.

In the code#

The table names each box, its vLLM line at commit ced6857, and the AFD hook. The vLLM lines are in the pinned vLLM checkout; the hook lines are in afd-plugin.

BoxWhat it does (vLLM)AFD hook
API server process
AsyncLLM
The engine client in the API server process; it drives the EngineCore (vllm:vllm/v1/engine/async_llm.py:78). The plugin loads here, while the CLI parser is built, and installs the config patch (vllm:vllm/engine/arg_utils.py:3009). See The plugin entry point: register_afd.
EngineCore process Builds the model_executor and the scheduler (vllm:vllm/v1/engine/core.py:134, vllm:vllm/v1/engine/core.py:162). The FFN EngineCore returns after the executor, with no real scheduler (a no-op stand-in) or KV cache (afd_plugin/compat/patches/engine_core.py:59-68). See Attention and FFN roles: what each server owns.
Executor mp starts worker processes; uni runs one worker in-process (vllm:vllm/v1/executor/abstract.py:70-77). No AFD change. AFD works through the worker the executor builds.
Worker (per device) Imports parallel_config.worker_cls by its qualified name (vllm:vllm/v1/worker/worker_base.py:273-277). worker_cls was rewritten to the AFD role worker by the config patch (afd_plugin/compat/patches/config_validation.py:92-93). See Worker selection: choosing the role worker.
Model Runner V2 Built when the V2 model runner is on; vLLM reads VLLM_USE_V2_MODEL_RUNNER (vllm:vllm/config/vllm.py:692-694, vllm:vllm/v1/worker/gpu_worker.py:469-482). The AFD worker swaps the runner class in init_device (afd_plugin/v1/worker/attention_worker.py:82-87). See The attention runtime: a seam over Model Runner V2.
The model ModelRegistry resolves the architecture name to a class (vllm:vllm/model_executor/models/registry.py:1126-1141). AFD registers an AFD<arch> model wrapper and swaps the model identity (afd_plugin/__init__.py:211-214). See Model registration: the AFD<arch> wrappers.

The executor chooses between separate worker processes and one in-process worker.

        elif distributed_executor_backend == "mp":
            from vllm.v1.executor.multiproc_executor import MultiprocExecutor

            executor_class = MultiprocExecutor
        elif distributed_executor_backend == "uni":
            from vllm.v1.executor.uniproc_executor import UniProcExecutor

            executor_class = UniProcExecutor
mp builds worker processes; uni runs one worker inside the EngineCore process.

The worker then imports the class named in worker_cls. AFD has already set this field to its role worker, so vLLM builds the AFD worker here.

        parallel_config = vllm_config.parallel_config
        if isinstance(parallel_config.worker_cls, str):
            worker_class: type[WorkerBase] = resolve_obj_by_qualname(
                parallel_config.worker_cls
            )
The worker resolves worker_cls to a class by its qualified name.

Deep dive#

Deep dive

How many processes, and where each hook runs

The number of processes depends on the executor (vllm:vllm/v1/executor/abstract.py:70-77).

  • Multi-process executor (mp). Each worker is a separate process. The plugin loads again in each one, so the worker and runner hooks run in the worker process (vllm:vllm/v1/worker/worker_base.py:271).
  • Single-device executor (uni). The worker runs inside the EngineCore process (vllm:vllm/v1/executor/abstract.py:74-77). The worker's plugin load hits the guard, so it does not reload the plugins.

The config hook is different from the others. It runs in the API server process, while create_engine_config builds the config (afd_plugin/compat/patches/config_validation.py:51-113). vLLM then pickles the config and sends it to the EngineCore and worker processes, so those processes do not rebuild it. The worker rewrite must therefore happen in the API server process, before the config is sent.

Deep dive

The FFN EngineCore is the one box that breaks the chain

Five of the six boxes keep their normal vLLM job and only change a class or a field. The FFN EngineCore is different. After it builds the executor, the AFD patch returns before the scheduler and the KV cache are set up (afd_plugin/compat/patches/engine_core.py:56-68). The FFN side then runs a connector loop instead of vLLM's request loop (afd_plugin/compat/patches/engine_core.py:411-419). The attention EngineCore keeps the normal chain. The Attention and FFN roles: what each server owns page covers this split.

In afd-plugin#

Each hook has its own page. Start here:

Check yourself#

  1. 1
    Which vLLM box holds the scheduler and the executor?
    easy
    Show answer

    The EngineCore process. It builds both (vllm:vllm/v1/engine/core.py:134, vllm:vllm/v1/engine/core.py:162).

  2. 2
    When is the Model Runner V2 used?
    easy
    Show answer

    When the V2 model runner is on; vLLM reads it from VLLM_USE_V2_MODEL_RUNNER (vllm:vllm/config/vllm.py:692-694, vllm:vllm/v1/worker/gpu_worker.py:469-482). AFD needs it on both roles.

  3. 3
    Why must the worker-class rewrite happen in the API server process?
    medium
    Show answer

    vLLM builds the config there, then pickles it to the other processes. The rewrite runs inside create_engine_config before the config is sent (afd_plugin/compat/patches/config_validation.py:51-113).

  4. 4
    Which box does AFD not change?
    medium
    Show answer

    The executor. AFD works through the worker the executor builds; it adds no executor patch (vllm:vllm/v1/executor/abstract.py:70-77).

  5. 5
    The FFN EngineCore and the attention EngineCore are the same vLLM class. What makes them behave differently?
    hard
    Show answer

    The AFD EngineCore patch checks the role. For the FFN role it returns after building the executor and skips the scheduler and KV cache (afd_plugin/compat/patches/engine_core.py:56-68); the FFN side then runs a connector loop (afd_plugin/compat/patches/engine_core.py:411-419). The attention role keeps the normal chain.