---
title: "SGLang Plugin System"
metatags:
    description: "Allows hardware vendors and developers to extend SGLang without modifying the main repository code."
---

## Overview

Allows hardware vendors and developers to extend SGLang **without modifying the main repository code**.

The framework provides two plugin types, both discovered via Python's standard `setuptools` entry_points:

<table style={{width: "100%", borderCollapse: "collapse", tableLayout: "fixed"}}>
  <colgroup>
    <col style={{width: "33.33%"}} />
    <col style={{width: "33.33%"}} />
    <col style={{width: "33.33%"}} />
  </colgroup>
  <thead>
    <tr>
      <th>Plugin Type</th>
      <th>Entry Point Group</th>
      <th>Purpose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>Hardware Platform Plugin</strong></td>
      <td><code>sglang.srt.platforms</code></td>
      <td>Register a custom hardware platform (device operations, KV cache pools, attention backends, graph capture, compilation backends, etc.)</td>
    </tr>
    <tr>
      <td><strong>General Plugin</strong></td>
      <td><code>sglang.srt.plugins</code></td>
      <td>Inject hooks (before/after/around/replace) into any function/method, or replace entire classes</td>
    </tr>
  </tbody>
</table>

### Principles

- **Non-intrusive**: Existing CUDA/ROCm/NPU/XPU code remains unchanged. OOT code paths are added alongside existing hardware-specific logic.
- **Zero configuration**: Plugins are automatically discovered after `pip install`, no sglang code changes required.
- **Environment variable control**: `SGLANG_PLATFORM` selects or validates the active platform plugin; `SGLANG_PLUGINS` (comma-separated) controls which general plugins to load.

### Current Scope & Future Direction

The plugin system currently targets **out-of-tree (OOT) hardware platforms** — enabling new devices to integrate with SGLang without any changes to the main repository. The main-repo hardware paths (CUDA, ROCm, NPU, XPU, etc.) continue to use the existing `is_cuda()`/`is_npu()`/… utility functions.

As the plugin interfaces mature and stabilize, in-tree hardware backends can be gradually migrated to the same plugin architecture. This would replace the scattered `if device == "cuda" … elif device == "npu" …` branches throughout the codebase with a single polymorphic dispatch through the platform interface, making each hardware backend self-contained and the core engine hardware-agnostic.

## Architecture

### Platform Hierarchy

The platform hierarchy uses a DeviceMixin pattern to share device operations between SRT (LLM inference) and Multimodal subsystems:

```
DeviceMixin (shared device identity + operations)
├── SRTPlatform(DeviceMixin)           # + graph runner, KV pool, …
│   └── MySRTPlatform(SRTPlatform, MyDeviceMixin)   # OOT plugin
└── MMPlatform(DeviceMixin)            # + attention backend, VAE, … (future)
    └── MyMMPlatform(MMPlatform, MyDeviceMixin)      # OOT plugin
```

Key design points:
- **DeviceMixin** provides platform identity queries (`is_cuda()`, `is_npu()`, etc.) and device operations (`set_device()`, `get_device_name()`, etc.)
- **SRTPlatform** adds SRT-specific factory methods, capability flags, and lifecycle hooks
- OOT plugins implement a **device mixin** (vendor-specific operations) and compose it with **SRTPlatform** via multiple inheritance
- All methods are **instance methods** (not classmethods), called through the `current_platform` singleton
- Device operations and factory methods raise `NotImplementedError` by default (fail-fast)
- Capability flags use safe conservative defaults (`False`/`pass`)
- Methods are annotated `[Active]` (called by SGLang core) or `[Planned]` (reserved for future migration)

### Platform Discovery (`current_platform`)

`current_platform` is a **lazy singleton** in `sglang.srt.platforms`. On first access it resolves the active platform through the following priority chain:

```
entry_points("sglang.srt.platforms")  → Enumerate ALL plugins by name (metadata only)
  │
  ├─ SGLANG_PLATFORM set (front-loading filter):
  │   ├─ Name not found in discovered → RuntimeError
  │   ├─ activate() returns non-None  → load that platform
  │   └─ activate() returns None      → RuntimeError (hardware unavailable)
  │
  └─ SGLANG_PLATFORM unset (auto-discover, activate all):
      ├─ 0 activated + SGLANG_USE_CPU_ENGINE=1 → fallback CpuSRTPlatform
      ├─ 0 activated + CUDA available          → fallback CudaSRTPlatform
      ├─ 0 activated + ROCm available          → fallback RocmSRTPlatform
      ├─ 0 activated + XPU available           → fallback XpuSRTPlatform
      ├─ 0 activated + none of the above       → fallback base SRTPlatform
      ├─ 1 activated → use it
      └─ N activated → RuntimeError (must set SGLANG_PLATFORM)
```

### Plugin Loading Flow

`load_plugins()` discovers and executes general plugins, then applies all registered hooks. It is called at four points:

<table style={{width: "100%", borderCollapse: "collapse", tableLayout: "fixed"}}>
  <colgroup>
    <col style={{width: "33.33%"}} />
    <col style={{width: "33.33%"}} />
    <col style={{width: "33.33%"}} />
  </colgroup>
  <thead>
    <tr>
      <th>Call Site</th>
      <th>Process</th>
      <th>Timing</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>cli/serve.py</code> serve()</td>
      <td>Main</td>
      <td>Before <code>prepare_server_args()</code></td>
    </tr>
    <tr>
      <td><code>launch_server.py</code> <code>__main__</code></td>
      <td>Main</td>
      <td>Before <code>prepare_server_args()</code></td>
    </tr>
    <tr>
      <td><code>engine.py</code> <code>_launch_subprocesses()</code></td>
      <td>Main</td>
      <td>Before <code>server_args.check_server_args()</code></td>
    </tr>
    <tr>
      <td><code>scheduler.py</code> <code>run_scheduler_process()</code></td>
      <td>Subprocess</td>
      <td>Before <code>Scheduler()</code> construction</td>
    </tr>
  </tbody>
</table>

> **Note**: `load_plugins()` is idempotent (guarded by `_plugins_loaded` flag). In spawn'd subprocesses the flag resets, so plugins are correctly re-loaded.

```
load_plugins()
  ├── _get_excluded_dists()                       → compute dists to skip (via SGLANG_PLATFORM)
  ├── load_plugins_by_group("sglang.srt.plugins",     → discover entry_points, filter by SGLANG_PLUGINS
  │     excluded_dists=...)                          skip plugins from unselected platform packages
  ├── for each plugin:                            → set _current_plugin_source context var
  │     func()                                      side effects (register hooks with source tracking)
  └── HookRegistry.apply_hooks()                  → monkey-patch targets
```

---

## Plugin Type 1: Hardware Platform Plugin

### Description

A hardware platform plugin registers an `SRTPlatform` subclass that tells SGLang how to interact with a specific hardware backend.

### Quick Start

**1. Create a minimal package:**

```
my_platform_plugin/
├── pyproject.toml
└── my_platform_plugin/
    ├── __init__.py    # activate() function
    ├── device.py      # MyDeviceMixin
    └── platform.py    # MySRTPlatform
```

**2. `pyproject.toml`:**

```toml
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "my-platform-plugin"
version = "0.1.0"

[project.entry-points."sglang.srt.platforms"]
my_device = "my_platform_plugin:activate"
```

**3. `__init__.py`** — activation function:

```python
def activate():
    """Return fully-qualified class name to activate, or None to skip."""
    if _my_device_is_available():
        return "my_platform_plugin.platform.MySRTPlatform"
    return None
```

**4. `device.py`** — device mixin:

```python
from sglang.srt.platforms.device_mixin import DeviceMixin, PlatformEnum

class MyDeviceMixin(DeviceMixin):
    _enum = PlatformEnum.OOT
    device_name = "my_device"
    device_type = "my_device"   # torch device type

    def set_device(self, device) -> None: ...
    def get_device_name(self, device_id=0) -> str: ...
    def get_device_total_memory(self, device_id=0) -> int: ...
    def get_current_memory_usage(self, device=None) -> float: ...
    def get_device_capability(self, device_id=0): ...
    def get_torch_distributed_backend_str(self) -> str: ...
```

**5. `platform.py`** — SRT platform:

```python
from sglang.srt.platforms.interface import SRTPlatform
from my_platform_plugin.device import MyDeviceMixin

class MySRTPlatform(SRTPlatform, MyDeviceMixin):
    def get_default_attention_backend(self) -> str: ...
    def support_cuda_graph(self) -> bool: ...
    # ... override other methods as needed
```

**6. Install and verify:**

```bash
pip install -e my_platform_plugin/
python -c "from sglang.srt.platforms import current_platform; print(current_platform)"
```

### Platform Interface Reference

#### Identity Queries (from DeviceMixin)

<table style={{width: "100%", borderCollapse: "collapse", tableLayout: "fixed"}}>
  <colgroup>
    <col style={{width: "33.33%"}} />
    <col style={{width: "33.33%"}} />
    <col style={{width: "33.33%"}} />
  </colgroup>
  <thead>
    <tr>
      <th>Method</th>
      <th>Default</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>is_cuda()</code></td>
      <td>Based on <code>_enum</code></td>
      <td>Whether this is an NVIDIA CUDA platform</td>
    </tr>
    <tr>
      <td><code>is_rocm()</code></td>
      <td>Based on <code>_enum</code></td>
      <td>Whether this is an AMD ROCm platform</td>
    </tr>
    <tr>
      <td><code>is_npu()</code></td>
      <td>Based on <code>_enum</code></td>
      <td>Whether this is a Huawei NPU platform</td>
    </tr>
    <tr>
      <td><code>is_cpu()</code></td>
      <td>Based on <code>_enum</code></td>
      <td>Whether this is a CPU-only platform</td>
    </tr>
    <tr>
      <td><code>is_xpu()</code></td>
      <td>Based on <code>_enum</code></td>
      <td>Whether this is an Intel XPU platform</td>
    </tr>
    <tr>
      <td><code>is_musa()</code></td>
      <td>Based on <code>_enum</code></td>
      <td>Whether this is a Moore Threads MUSA platform</td>
    </tr>
    <tr>
      <td><code>is_cuda_alike()</code></td>
      <td>CUDA+ROCM+MUSA</td>
      <td>True if the hardware supports CUDA-like APIs</td>
    </tr>
    <tr>
      <td><code>is_out_of_tree()</code></td>
      <td><code>True</code> for OOT</td>
      <td>Automatically detected based on <code>_enum = PlatformEnum.OOT</code></td>
    </tr>
  </tbody>
</table>

#### Device Operations (from DeviceMixin)

> Methods annotated **[Active]** are called by SGLang core through `current_platform` — OOT implementations take effect immediately.
> Methods annotated **[Planned]** are reserved interfaces — SGLang core still uses hardcoded calls (e.g. `torch.cuda.empty_cache()`). OOT implementations will NOT take effect until the core is migrated in a future PR.

<table style={{width: "100%", borderCollapse: "collapse", tableLayout: "fixed"}}>
  <colgroup>
    <col style={{width: "25%"}} />
    <col style={{width: "25%"}} />
    <col style={{width: "25%"}} />
    <col style={{width: "25%"}} />
  </colgroup>
  <thead>
    <tr>
      <th>Method</th>
      <th>Default</th>
      <th>Status</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>get_device(local_rank)</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td>Planned</td>
      <td>Return <code>torch.device</code> for a given local rank</td>
    </tr>
    <tr>
      <td><code>set_device(device)</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td>Planned</td>
      <td>Set the current device</td>
    </tr>
    <tr>
      <td><code>get_device_name(device_id)</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td>Planned</td>
      <td>Get human-readable device name</td>
    </tr>
    <tr>
      <td><code>get_device_uuid(device_id)</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td>Planned</td>
      <td>Get unique device identifier</td>
    </tr>
    <tr>
      <td><code>get_device_capability(device_id)</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td>Planned</td>
      <td>Get <code>DeviceCapability(major, minor)</code>. None if N/A</td>
    </tr>
    <tr>
      <td><code>empty_cache()</code></td>
      <td><code>pass</code></td>
      <td>Planned</td>
      <td>Release cached device memory</td>
    </tr>
    <tr>
      <td><code>synchronize()</code></td>
      <td><code>pass</code></td>
      <td>Planned</td>
      <td>Synchronize device operations</td>
    </tr>
    <tr>
      <td><code>get_device_total_memory(device_id)</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td><strong>Active</strong></td>
      <td>Get total device memory in bytes</td>
    </tr>
    <tr>
      <td><code>get_available_memory(device_id)</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td>Planned</td>
      <td>Return <code>(free_bytes, total_bytes)</code></td>
    </tr>
    <tr>
      <td><code>get_current_memory_usage(device)</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td><strong>Active</strong></td>
      <td>Get current peak memory usage in bytes</td>
    </tr>
    <tr>
      <td><code>is_pin_memory_available(device=None)</code></td>
      <td><code>False</code></td>
      <td><strong>Active</strong></td>
      <td>Whether pinned host memory is available for a target device</td>
    </tr>
    <tr>
      <td><code>get_torch_distributed_backend_str()</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td>Planned</td>
      <td>Distributed backend string (e.g. "nccl", "hccl")</td>
    </tr>
    <tr>
      <td><code>get_communicator_class()</code></td>
      <td><code>None</code></td>
      <td>Planned</td>
      <td>Platform-specific communicator class</td>
    </tr>
    <tr>
      <td><code>inference_mode()</code></td>
      <td><code>torch.inference_mode(True)</code></td>
      <td>Planned</td>
      <td>Return inference mode context manager</td>
    </tr>
    <tr>
      <td><code>seed_everything(seed)</code></td>
      <td>Set random/np/torch seeds</td>
      <td>Planned</td>
      <td>Set random seeds for reproducibility</td>
    </tr>
    <tr>
      <td><code>verify_quantization(quant)</code></td>
      <td><code>pass</code></td>
      <td>Planned</td>
      <td>Validate quantization method support</td>
    </tr>
    <tr>
      <td><code>get_cpu_architecture()</code></td>
      <td>Auto-detect x86/arm</td>
      <td>Planned</td>
      <td>Detect CPU architecture (<code>CpuArchEnum</code>)</td>
    </tr>
  </tbody>
</table>

#### Types (from DeviceMixin)

<table style={{width: "100%", borderCollapse: "collapse", tableLayout: "fixed"}}>
  <colgroup>
    <col style={{width: "50%"}} />
    <col style={{width: "50%"}} />
  </colgroup>
  <thead>
    <tr>
      <th>Type</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>PlatformEnum</code></td>
      <td>Enumeration of platform types: CUDA, ROCM, CPU, XPU, MUSA, NPU, TPU, MPS, OOT, UNSPECIFIED</td>
    </tr>
    <tr>
      <td><code>CpuArchEnum</code></td>
      <td>CPU architecture: X86, ARM, UNSPECIFIED</td>
    </tr>
    <tr>
      <td><code>DeviceCapability</code></td>
      <td><code>NamedTuple(major, minor)</code> with comparison support. Methods: <code>as_version_str()</code>, <code>to_int()</code></td>
    </tr>
  </tbody>
</table>

#### Capability Flags (from SRTPlatform)

<table style={{width: "100%", borderCollapse: "collapse", tableLayout: "fixed"}}>
  <colgroup>
    <col style={{width: "33.33%"}} />
    <col style={{width: "33.33%"}} />
    <col style={{width: "33.33%"}} />
  </colgroup>
  <thead>
    <tr>
      <th>Method</th>
      <th>Default</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>support_cuda_graph()</code></td>
      <td><code>False</code></td>
      <td>Whether device graph capture is supported (plain CUDA graph)</td>
    </tr>
    <tr>
      <td><code>support_piecewise_cuda_graph()</code></td>
      <td><code>False</code></td>
      <td>Whether piecewise CUDA graph (torch.compile backend) is supported</td>
    </tr>
    <tr>
      <td><code>supports_fp8()</code></td>
      <td><code>False</code></td>
      <td>Whether FP8 quantization is supported</td>
    </tr>
  </tbody>
</table>

#### Subsystem Factory Methods (from SRTPlatform)

<table style={{width: "100%", borderCollapse: "collapse", tableLayout: "fixed"}}>
  <colgroup>
    <col style={{width: "33.33%"}} />
    <col style={{width: "33.33%"}} />
    <col style={{width: "33.33%"}} />
  </colgroup>
  <thead>
    <tr>
      <th>Method</th>
      <th>Default</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>get_default_attention_backend()</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td>Default attention backend name</td>
    </tr>
    <tr>
      <td><code>get_graph_runner_cls()</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td>Graph Runner class</td>
    </tr>
    <tr>
      <td><code>get_mha_kv_pool_cls()</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td>MHA KV cache pool class</td>
    </tr>
    <tr>
      <td><code>get_mla_kv_pool_cls()</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td>MLA KV cache pool class</td>
    </tr>
    <tr>
      <td><code>get_dsa_kv_pool_cls()</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td>DSA KV cache pool class (DeepSeek V3.2)</td>
    </tr>
    <tr>
      <td><code>get_paged_allocator_cls()</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td>Paged allocator class</td>
    </tr>
    <tr>
      <td><code>get_quantization_config(quantization)</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td>Return hardware-specific quantization config for the specific quantization scheme, raise an error if not supported or return None to use the default config.</td>
    </tr>
    <tr>
      <td><code>get_piecewise_backend_cls()</code></td>
      <td><code>raise NotImplementedError</code></td>
      <td>Piecewise compilation backend class</td>
    </tr>
    <tr>
      <td><code>get_compile_backend(mode)</code></td>
      <td><code>"inductor"</code></td>
      <td>Compilation backend string</td>
    </tr>
    <tr>
      <td><code>get_dispatch_key_name()</code></td>
      <td><code>"native"</code></td>
      <td>BaseFusedOp (fused-op) dispatch key name</td>
    </tr>
  </tbody>
</table>

#### Lifecycle Hooks (from SRTPlatform)

<table style={{width: "100%", borderCollapse: "collapse", tableLayout: "fixed"}}>
  <colgroup>
    <col style={{width: "33.33%"}} />
    <col style={{width: "33.33%"}} />
    <col style={{width: "33.33%"}} />
  </colgroup>
  <thead>
    <tr>
      <th>Method</th>
      <th>Invocation Timing</th>
      <th>Purpose</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>apply_server_args_defaults(server_args)</code></td>
      <td>After ServerArgs parsing, in <code>__post_init__</code></td>
      <td>Set platform-specific defaults</td>
    </tr>
    <tr>
      <td><code>init_backend()</code></td>
      <td>In each worker, before model construction</td>
      <td>One-time backend initialization</td>
    </tr>
  </tbody>
</table>

### Environment Variables

<table style={{width: "100%", borderCollapse: "collapse", tableLayout: "fixed"}}>
  <colgroup>
    <col style={{width: "50%"}} />
    <col style={{width: "50%"}} />
  </colgroup>
  <thead>
    <tr>
      <th>Variable</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>SGLANG_PLATFORM</code></td>
      <td>Select the platform plugin by entry_point name (e.g. <code>kunlun</code>, <code>demo_cuda</code>). When set, <strong>only</strong> the named plugin's <code>activate()</code> is called (front-loading filter) — other plugins are not touched. Additionally, general plugins (<code>sglang.srt.plugins</code>) from unselected platform packages are automatically skipped to avoid importing their dependencies. Required when multiple plugins would activate. Errors if the name is not found or if the plugin's hardware is unavailable.</td>
    </tr>
    <tr>
      <td><code>SGLANG_PLUGINS</code></td>
      <td>Comma-separated whitelist of general plugin names to load (group: <code>sglang.srt.plugins</code>). If unset, all discovered general plugins are loaded.</td>
    </tr>
  </tbody>
</table>

---

## Plugin Type 2: General Plugin

### Description

General function plugins inject behavior into sglang **without requiring a custom platform**. Use cases include:

- **Observability**: Add logging, metrics, and tracing to any function
- **Behavior modification**: Modify function arguments or return values
- **Performance profiling**: Add timing to critical functions
- **A/B testing**: Replace implementations at runtime

### Quick Start

**1. Create a minimal package:**

```
my_general_plugin/
├── pyproject.toml
└── my_general_plugin/
    └── __init__.py    # register() function
```

**2. `pyproject.toml`:**

```toml
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "my-general-plugin"
version = "0.1.0"

[project.entry-points."sglang.srt.plugins"]
my_plugin = "my_general_plugin:register"
```

**3. `__init__.py`** — register hooks:

```python
from sglang.srt.plugins.hook_registry import HookRegistry, HookType

def register():
    """Entry point called by load_plugins()."""
    HookRegistry.register(
        "sglang.srt.managers.scheduler.Scheduler.__init__",
        my_hook,
        HookType.AROUND,
    )

def my_hook(original_fn, self, *args, **kwargs):
    result = original_fn(self, *args, **kwargs)
    print(f"Scheduler initialized! gpu_id={self.gpu_id}")
    return result
```

**4. Install and run:**

```bash
pip install -e my_general_plugin/
sglang serve --model-path <model> [options]
# Look for "Scheduler initialized!" in logs
```

### Hook Types

`HookRegistry` supports four hook types:

<table style={{width: "100%", borderCollapse: "collapse", tableLayout: "fixed"}}>
  <colgroup>
    <col style={{width: "33.33%"}} />
    <col style={{width: "33.33%"}} />
    <col style={{width: "33.33%"}} />
  </colgroup>
  <thead>
    <tr>
      <th>Hook Type</th>
      <th>Signature</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>BEFORE</strong></td>
      <td><code>fn(*args, **kwargs) -&gt; (args, kwargs) \| None</code></td>
      <td>Runs before the original. Return <code>None</code> to keep args unchanged, or <code>(args, kwargs)</code> to modify.</td>
    </tr>
    <tr>
      <td><strong>AFTER</strong></td>
      <td><code>fn(result, *args, **kwargs) -&gt; new_result \| None</code></td>
      <td>Runs after the original. Return <code>None</code> to keep result, or a new value to replace.</td>
    </tr>
    <tr>
      <td><strong>AROUND</strong></td>
      <td><code>fn(original_fn, *args, **kwargs) -&gt; result</code></td>
      <td>Wraps the original. You must call <code>original_fn</code> yourself. Full control over execution.</td>
    </tr>
    <tr>
      <td><strong>REPLACE</strong></td>
      <td><code>fn(*args, **kwargs) -&gt; result</code> or <code>class</code></td>
      <td>Replace the original function or class entirely. For class targets, pass a replacement class directly — it is substituted via <code>setattr</code> preserving <code>isinstance()</code>/<code>issubclass()</code> semantics.</td>
    </tr>
  </tbody>
</table>

> **Note**: Only `REPLACE` accepts a class as the hook. Passing a class to `BEFORE`/`AFTER`/`AROUND` raises `TypeError` at registration time.

### Registration API

Hooks can be registered using the **imperative API** or the **decorator API**:

```python
# --- Imperative API ---
from sglang.srt.plugins.hook_registry import HookRegistry, HookType

def my_timer(original_fn, *args, **kwargs):
    start = time.perf_counter()
    result = original_fn(*args, **kwargs)
    print(f"Elapsed: {time.perf_counter() - start:.3f}s")
    return result

HookRegistry.register(
    "sglang.srt.managers.scheduler.Scheduler.get_next_batch_to_run",
    my_timer,
    HookType.AROUND,
)

# --- Decorator API ---
from sglang.srt.plugins.hook_registry import plugin_hook, HookType

@plugin_hook(
    "sglang.srt.managers.scheduler.Scheduler.get_next_batch_to_run",
    type=HookType.AROUND,
)
def my_timer(original_fn, *args, **kwargs):
    start = time.perf_counter()
    result = original_fn(*args, **kwargs)
    print(f"Elapsed: {time.perf_counter() - start:.3f}s")
    return result

# --- Class replacement (REPLACE) ---
from sglang.srt.plugins.hook_registry import plugin_hook, HookType
from sglang.srt.managers.scheduler import Scheduler

@plugin_hook(
    "sglang.srt.managers.scheduler.Scheduler",
    type=HookType.REPLACE,
)
class MyScheduler(Scheduler):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print("Enhanced scheduler initialized!")
```

### Hook Target Resolution

Target paths use fully-qualified dotted notation. Both formats are supported:

- **Dotted**: `sglang.srt.managers.scheduler.Scheduler.__init__`
- **Entry-points style**: `sglang.srt.managers.scheduler:Scheduler.__init__` (colon treated as dot)

### Common Hook Targets

<table style={{width: "100%", borderCollapse: "collapse", tableLayout: "fixed"}}>
  <colgroup>
    <col style={{width: "50%"}} />
    <col style={{width: "50%"}} />
  </colgroup>
  <thead>
    <tr>
      <th>Target</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>sglang.srt.server_args.ServerArgs.add_cli_args</code></td>
      <td>Add custom CLI arguments</td>
    </tr>
    <tr>
      <td><code>sglang.srt.server_args.ServerArgs.__post_init__</code></td>
      <td>Modify ServerArgs after parsing</td>
    </tr>
    <tr>
      <td><code>sglang.srt.server_args.ServerArgs.check_server_args</code></td>
      <td>Add/relax validation</td>
    </tr>
    <tr>
      <td><code>sglang.srt.managers.scheduler.Scheduler.__init__</code></td>
      <td>Custom scheduler state</td>
    </tr>
    <tr>
      <td><code>sglang.srt.managers.scheduler.Scheduler.get_next_batch_to_run</code></td>
      <td>Custom scheduling policy</td>
    </tr>
    <tr>
      <td><code>sglang.srt.managers.scheduler.Scheduler.run_batch</code></td>
      <td>Profiling / inspection</td>
    </tr>
    <tr>
      <td><code>sglang.srt.managers.scheduler.Scheduler.process_batch_result</code></td>
      <td>Custom metrics</td>
    </tr>
    <tr>
      <td><code>sglang.srt.managers.tp_worker.TpModelWorker.__init__</code></td>
      <td>Custom worker state</td>
    </tr>
    <tr>
      <td><code>sglang.srt.managers.tp_worker.TpModelWorker.forward_batch_generation</code></td>
      <td>Forward pass wrapping</td>
    </tr>
  </tbody>
</table>

---

## File Reference

<table style={{width: "100%", borderCollapse: "collapse", tableLayout: "fixed"}}>
  <colgroup>
    <col style={{width: "50%"}} />
    <col style={{width: "50%"}} />
  </colgroup>
  <thead>
    <tr>
      <th>File</th>
      <th>Description</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code>sglang/srt/platforms/device_mixin.py</code></td>
      <td><code>PlatformEnum</code> + <code>DeviceMixin</code> base class</td>
    </tr>
    <tr>
      <td><code>sglang/srt/platforms/interface.py</code></td>
      <td><code>SRTPlatform</code> base class (extends DeviceMixin)</td>
    </tr>
    <tr>
      <td><code>sglang/srt/platforms/__init__.py</code></td>
      <td><code>current_platform</code> lazy singleton + discovery logic</td>
    </tr>
    <tr>
      <td><code>sglang/srt/plugins/__init__.py</code></td>
      <td><code>load_plugins()</code> + <code>load_plugins_by_group()</code></td>
    </tr>
    <tr>
      <td><code>sglang/srt/plugins/hook_registry.py</code></td>
      <td><code>HookRegistry</code>, <code>HookType</code>, <code>plugin_hook</code> decorator</td>
    </tr>
  </tbody>
</table>
