AFD Plugin Explained

v1/worker: the attention worker and runner

AdvancedCode mapabout 4 min read + 1 min deep dives

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

In one sentence

The attention role is a native vLLM worker with the AFD Model Runner V2 swapped in, plus a shared mixin that publishes the control plane.

Purpose#

These three files add the connector and control plane to the attention side without rewriting native vLLM. The concept behind them is on The attention runtime: a seam over Model Runner V2; this page maps the files.

ItemValue
Files (lines, origin)attention_worker.py (93), attention_model_runner_v2.py (622), attention_metadata.py (357); all AFD-owned
Main classesAFDAttentionWorker (afd_plugin/v1/worker/attention_worker.py:22), AFDAttentionModelRunnerV2 (afd_plugin/v1/worker/attention_model_runner_v2.py:358), AFDMetadataProviderMixin (afd_plugin/v1/worker/attention_metadata.py:21)
Runs ineach attention worker process (one per rank); transfers on the current CUDA stream
Called byvLLM, through the rewritten worker_cls (afd_plugin/validation.py:290-291)
Callsnative Worker and GPUModelRunner (vllm:vllm/v1/worker/gpu_worker.py:469-482), the connector factory, the model wrapper
Not in this filethe FFN runtime (v1/worker: the FFN worker and runner), the connector transport (connectors/gpu/p2p.py: the GPU P2P connector), the model split (model_executor/models/deepseek_v2.py: the DeepSeek wrapper)

Where it sits#

Who calls it#

The AFD config patch rewrites worker_cls to AFDAttentionWorker when the role is attention (afd_plugin/validation.py:280-291). vLLM then builds this worker like any other. The worker builds the runner inside init_device.

What it calls#

The worker delegates device setup to native Worker.init_device, which local-imports and builds GPUModelRunner (vllm:vllm/v1/worker/gpu_worker.py:469-482). The runner builds the connector through the factory (afd_plugin/connectors/factory.py:49-68) and reads the model wrapper.

Key classes and functions#

NameLineRole
AFDAttentionWorker.init_deviceafd_plugin/v1/worker/attention_worker.py:60-90swap in the AFD runner for the native construction window
AFDAttentionModelRunnerV2.__init__afd_plugin/v1/worker/attention_model_runner_v2.py:369-411build the connector; require a control plane
load_modelafd_plugin/v1/worker/attention_model_runner_v2.py:454-462native load, then connector rendezvous at the end
execute_modelafd_plugin/v1/worker/attention_model_runner_v2.py:569-595scope the AFD context, delegate to native V2
capture_modelafd_plugin/v1/worker/attention_model_runner_v2.py:509-553publish graph warm-up/capture control
AFDMetadataProviderMixin.send_dp_metadataafd_plugin/v1/worker/attention_metadata.py:75-116publish one control payload
install_afd_metadata_on_forward_contextafd_plugin/v1/worker/attention_metadata.py:211-257attach the sidecar, publish on the non-DBO path

Walk through the main methods#

AFDAttentionWorker.init_device: swap the runner class#

vLLM 0.30.0 builds its runner from a module symbol inside Worker.init_device, with no injection hook. The worker replaces that symbol for the native construction window, then restores it (afd_plugin/v1/worker/attention_worker.py:60-90).

        native_runner_cls = gpu_model_runner_v2.GPUModelRunner
        gpu_model_runner_v2.GPUModelRunner = AFDAttentionModelRunnerV2
        try:
            super().init_device()
        finally:
            gpu_model_runner_v2.GPUModelRunner = native_runner_cls
        # ### PATCH END: select and directly construct the AFD runner.

        torch.accelerator.empty_cache()
Replace the module class around super().init_device(), then restore it.
  1. Line 82. Save the native runner class.
  2. Line 83. Point the module symbol at AFDAttentionModelRunnerV2.
  3. Line 85. Native init_device local-imports and builds the runner, now the AFD class.
  4. Lines 86-87. Restore the native class in a finally, so the patch is scoped to the window.

Before the swap, the worker asserts the AFD stack and validates the V2 config (afd_plugin/v1/worker/attention_worker.py:64-73).

execute_model: scope the context, then delegate#

The runner adds nothing to the forward itself. It opens the AFD context and calls super (afd_plugin/v1/worker/attention_model_runner_v2.py:580-595).

        # ### PATCH START: scope AFD metadata provider/replay and profiler step.
        step_afd_gpu_profiler(self.prof)
        with _use_afd_execution_context(
            self,
            int(scheduler_output.total_num_scheduled_tokens),
        ):
            return super().execute_model(
                scheduler_output,
                intermediate_tensors,
                dummy_run=dummy_run,
                skip_attn_for_dummy_run=skip_attn_for_dummy_run,
                is_profile=is_profile,
                context_len=context_len,
                valid_dummy_state_slots=valid_dummy_state_slots,
            )
        # ### PATCH END: scope AFD metadata provider/replay and profiler step.
The AFD context scopes the metadata provider and replay hook; native V2 runs the forward. Sampling is a separate native step.

send_dp_metadata: publish one control payload#

The mixin builds one payload with the per-stage DP metadata and the graph flags, updates the connector state, then sends (afd_plugin/v1/worker/attention_metadata.py:75-116).

        payload = AFDControlPayload(
            dp_metadata_list=dp_metadata_list,
            is_graph_capturing=is_graph_capturing,
            is_warmup=is_warmup,
            is_graph_replaying=is_graph_replaying,
            is_profile=self._afd_is_profile,
        )
        self.connector.control_plane.update_state_from_dp_metadata(payload)
        self.connector.control_plane.send_dp_metadata_list(payload)
Build the payload, update state, then send the DP-metadata list.
  1. Lines 108-114. Build the AFDControlPayload with the DP metadata and the warm-up/capture/replay flags.
  2. Line 115. update_state_from_dp_metadata pre-allocates the wire buffers.
  3. Line 116. send_dp_metadata_list sends on the designated sender ranks.

Deep dive#

Deep dive

The overrides and what each adds

OverrideAddsLine
__init__connector + profiler; control-plane checkafd_plugin/v1/worker/attention_model_runner_v2.py:369-411
load_modeldeferred connector rendezvousafd_plugin/v1/worker/attention_model_runner_v2.py:454-462
initialize_kv_cacheDP1 ubatch factory for DBOafd_plugin/v1/worker/attention_model_runner_v2.py:473-486
capture_modelgraph warm-up/capture controlafd_plugin/v1/worker/attention_model_runner_v2.py:509-553
execute_modelAFD context scopeafd_plugin/v1/worker/attention_model_runner_v2.py:569-595
shutdownprofiler stop + connector closeafd_plugin/v1/worker/attention_model_runner_v2.py:609-619

Every override calls super() for the real work. The runner's docstring states the delegation boundary (afd_plugin/v1/worker/attention_model_runner_v2.py:358-365).

Deep dive

The shared mixin, used by GPU and NPU

AFDMetadataProviderMixin holds the connector-facing plumbing: build the sidecar, publish control, and attach the sidecar to a context (afd_plugin/v1/worker/attention_metadata.py:21-28). The GPU V2 runner and the Ascend runners both reuse it, so the control-plane logic lives in one place. The module also has the DP-metadata builders for DBO stages (afd_plugin/v1/worker/attention_metadata.py:308-351).

Check yourself#

  1. 1
    How does the worker install the AFD runner?
    easy
    Show answer

    It replaces the module GPUModelRunner symbol around super().init_device(), then restores it (afd_plugin/v1/worker/attention_worker.py:82-87).

  2. 2
    Which class holds the control-plane publish logic, and why is it a mixin?
    medium
    Show answer

    AFDMetadataProviderMixin (afd_plugin/v1/worker/attention_metadata.py:21). It is a mixin so the GPU V2 runner and the NPU runners share one implementation (afd_plugin/v1/worker/attention_metadata.py:21-28).

  3. 3
    Why does the module-symbol swap in init_device work at all?
    hard
    Show answer

    Because native Worker.init_device local-imports GPUModelRunner from the module just before it constructs the runner, so the attribute swap is seen at construction (vllm:vllm/v1/worker/gpu_worker.py:469-482, afd_plugin/v1/worker/attention_worker.py:82-87).