59a0a3844c
PR Test AMD / cancel-on-close (push) Has been skipped
PR Test NVIDIA ARM / scan (push) Has been skipped
PR Test NVIDIA / cancel-on-close (push) Has been skipped
PR Test AMD / scan (push) Has been skipped
PR Test NVIDIA ARM / cancel-on-close (push) Has been skipped
PR Test NVIDIA / scan (push) Has been skipped
Release Docker Images / build (cu129-torch-2.11.0) (push) Has been skipped
Release Docker Images / build (cu130-torch-2.11.0) (push) Has been skipped
Release PyPI / publish (push) Has been skipped
Scheduler Python Test / test (push) Successful in 27m19s
Docs / build (push) Successful in 28m8s
Scheduler C++ Test / test (push) Successful in 28m19s
Scheduler C++ Test / test-flat (push) Successful in 28m18s
Docs / deploy (push) Has been cancelled
PR Test AMD / finish (push) Has been cancelled
PR Test NVIDIA / finish (push) Has been cancelled
PR Test NVIDIA ARM / finish (push) Has been cancelled
PR Test NVIDIA ARM / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test AMD / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
PR Test NVIDIA / ${{ matrix.name }} (${{ matrix.runner }}) (push) Has been cancelled
190 lines
5.8 KiB
Markdown
190 lines
5.8 KiB
Markdown
# Tokenspeed-kernel Plugin System
|
||
|
||
> **Status: experimental.** The plugin contract — entry-point group name,
|
||
> `register()` signature, `KernelSpec` fields, selection priority semantics,
|
||
> and the `tokenspeed_kernel.plugins` Python API — may change without
|
||
> backwards-compatibility guarantees while we shake the design out. Pin to
|
||
> an exact `tokenspeed-kernel` version in your plugin's dependencies.
|
||
|
||
This subpackage lets third-party packages register kernel implementations
|
||
into the global `KernelRegistry` without modifying `tokenspeed-kernel`
|
||
itself.
|
||
|
||
## How it works
|
||
|
||
1. Your plugin package exposes a `register()` function that calls
|
||
`tokenspeed_kernel.registry.register_kernel(...)` (or
|
||
`KernelRegistry.get().register(...)`) for each kernel it provides.
|
||
2. Your `pyproject.toml` advertises that function under the
|
||
`tokenspeed_kernel.plugins` entry-point group.
|
||
3. The host application (engine, benchmark, notebook, etc.) calls
|
||
`tokenspeed_kernel.plugins.discover_plugins()` once at startup, after
|
||
built-in kernels have been imported. Discovery walks the entry-point
|
||
group and invokes each `register()`.
|
||
|
||
Loading is **fully explicit** — importing `tokenspeed_kernel` or
|
||
`tokenspeed_kernel.plugins` does **not** trigger discovery on its own.
|
||
|
||
## Example plugin
|
||
|
||
A minimal out-of-tree package that contributes a custom decode-attention
|
||
kernel for NVIDIA Hopper.
|
||
|
||
```
|
||
my-kernels-plugin/
|
||
├── pyproject.toml
|
||
└── my_kernels_plugin/
|
||
└── __init__.py
|
||
```
|
||
|
||
`my_kernels_plugin/__init__.py`:
|
||
|
||
```python
|
||
import torch
|
||
|
||
from tokenspeed_kernel.platform import ArchVersion, CapabilityRequirement
|
||
from tokenspeed_kernel.signature import format_signatures
|
||
from tokenspeed_kernel.registry import register_kernel
|
||
|
||
|
||
def register() -> None:
|
||
"""Entry point invoked by tokenspeed_kernel.plugins.discover_plugins()."""
|
||
|
||
@register_kernel(
|
||
"attention",
|
||
"decode",
|
||
solution="my_custom",
|
||
signatures=format_signatures(
|
||
("q", "k_cache", "v_cache"), "dense", {torch.bfloat16}
|
||
),
|
||
capability=CapabilityRequirement(
|
||
vendors=frozenset({"nvidia"}),
|
||
min_arch_version=ArchVersion(9, 0),
|
||
),
|
||
# Built-in FlashInfer decode is priority 18; pick 19 to win selection.
|
||
priority=19,
|
||
)
|
||
def my_custom_attn_decode(q, kv_cache, page_table, seq_lens, **kwargs):
|
||
...
|
||
```
|
||
|
||
`pyproject.toml`:
|
||
|
||
```toml
|
||
[project]
|
||
name = "my-kernels-plugin"
|
||
version = "0.1.0"
|
||
# Pin tightly while the plugin contract is experimental.
|
||
dependencies = ["tokenspeed-kernel==<exact-version>"]
|
||
|
||
[project.entry-points."tokenspeed_kernel.plugins"]
|
||
my_plugin = "my_kernels_plugin:register"
|
||
```
|
||
|
||
Install and load:
|
||
|
||
```bash
|
||
pip install -e ./my-kernels-plugin
|
||
```
|
||
|
||
```python
|
||
import tokenspeed_kernel # registers built-in kernels
|
||
from tokenspeed_kernel.plugins import discover_plugins, list_plugins
|
||
|
||
discover_plugins()
|
||
print(list_plugins()) # -> [PluginInfo(name='my_plugin', ...)]
|
||
```
|
||
|
||
## Host-application integration
|
||
|
||
Engines and other long-running hosts should call `discover_plugins()`
|
||
exactly once at startup, after built-in kernel modules have been imported
|
||
(so plugins can override built-ins by registering at a higher priority).
|
||
|
||
```python
|
||
import tokenspeed_kernel # noqa: F401 -- registers built-ins
|
||
from tokenspeed_kernel.plugins import discover_plugins
|
||
|
||
discover_plugins()
|
||
```
|
||
|
||
For ad-hoc use (notebooks, scripts, tests), there is no need to use entry
|
||
points at all — call `register_kernel(...)` directly:
|
||
|
||
```python
|
||
import torch
|
||
from tokenspeed_kernel.signature import format_signatures
|
||
from tokenspeed_kernel.registry import register_kernel
|
||
|
||
|
||
@register_kernel(
|
||
"gemm",
|
||
"mm",
|
||
solution="experiment",
|
||
signatures=format_signatures(("a", "b"), "dense", {torch.bfloat16}),
|
||
priority=15,
|
||
)
|
||
def my_experimental_gemm(a, b, **kwargs):
|
||
...
|
||
```
|
||
|
||
## Disabling plugins
|
||
|
||
Plugins can be skipped without uninstalling them:
|
||
|
||
```bash
|
||
TOKENSPEED_KERNEL_DISABLE_PLUGINS="my_plugin,other_plugin" python ...
|
||
```
|
||
|
||
```python
|
||
from tokenspeed_kernel.plugins import disable_plugin, discover_plugins
|
||
|
||
disable_plugin("my_plugin")
|
||
discover_plugins()
|
||
```
|
||
|
||
The names refer to entry-point names (the left-hand side of the
|
||
`[project.entry-points."tokenspeed_kernel.plugins"]` table), not
|
||
distribution names.
|
||
|
||
## Inspection
|
||
|
||
```bash
|
||
python -m tokenspeed_kernel.plugins list
|
||
python -m tokenspeed_kernel.plugins info my_plugin
|
||
```
|
||
|
||
```python
|
||
from tokenspeed_kernel.plugins import list_plugins
|
||
|
||
for info in list_plugins():
|
||
print(info.name, info.version, info.kernel_names)
|
||
```
|
||
|
||
## Selection contract
|
||
|
||
- Priority is an integer in `[0, 20)`. Higher wins. The reference
|
||
implementation lives at `0`. Built-in optimized kernels typically sit at
|
||
`10`–`18`. Plugin authors who want to override a built-in should choose
|
||
a value strictly higher than the built-in they replace.
|
||
- `discover_plugins()` walks entry points in alphabetical order by
|
||
entry-point name. When two registrations land at the same priority for
|
||
the same `(family, mode)`, the warning is emitted and selection becomes
|
||
load-order-dependent — set explicit, distinct priorities to avoid this.
|
||
- A plugin whose `register()` raises does not crash discovery; a
|
||
`UserWarning` is emitted and other plugins continue loading.
|
||
|
||
## Failure modes worth knowing
|
||
|
||
- **No discovery call → no plugin kernels.** Forgetting to call
|
||
`discover_plugins()` is silent.
|
||
- **Plugin loaded before built-ins.** If you call `discover_plugins()`
|
||
before `import tokenspeed_kernel`, plugins that intend to override
|
||
built-ins will appear to win, but the built-in modules will be imported
|
||
later and may overwrite the plugin's slot. Always import
|
||
`tokenspeed_kernel` first.
|
||
- **Stale registry.** Calling `KernelRegistry.reset()` clears registered
|
||
kernels but leaves `_loaded_plugins` populated; re-discovery will skip
|
||
already-loaded plugins. Use `discover_plugins(force=True)` after a
|
||
reset.
|