Add pinned runtime, native kernels and production launcher
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +10 -0
- INSTALL.md +75 -0
- assets/ciru-halo-agent.png +3 -0
- bundle/native/libornith_attention_iu4.so +3 -0
- bundle/native/libornith_dense_g256.so +0 -0
- bundle/native/libornith_dense_g256_n32.so +0 -0
- bundle/native/libornith_head_i8_tile.so +3 -0
- bundle/native/libornith_persistent_iu4.so +3 -0
- bundle/native/libornith_routed_direct.so +3 -0
- bundle/native/libornith_routed_n32.so +3 -0
- bundle/native/libornith_routed_storage_n32.so +3 -0
- bundle/packaging/serve.sh +31 -0
- bundle/paths.env +4 -0
- bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/INSTALLER +1 -0
- bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/METADATA +8 -0
- bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/RECORD +32 -0
- bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/REQUESTED +0 -0
- bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/WHEEL +5 -0
- bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/direct_url.json +1 -0
- bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/entry_points.txt +5 -0
- bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/licenses/LICENSE-APACHE-2.0 +202 -0
- bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/top_level.txt +1 -0
- bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/uv_build.json +1 -0
- bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/uv_cache.json +1 -0
- bundle/plugin-site/ornith_g256/__init__.py +26 -0
- bundle/plugin-site/ornith_g256/adaptive_c1.py +340 -0
- bundle/plugin-site/ornith_g256/attention.py +19 -0
- bundle/plugin-site/ornith_g256/attention_compact.py +204 -0
- bundle/plugin-site/ornith_g256/attention_fast.py +216 -0
- bundle/plugin-site/ornith_g256/attention_folded.py +170 -0
- bundle/plugin-site/ornith_g256/attention_iu4.py +41 -0
- bundle/plugin-site/ornith_g256/attention_iu4_persistent.py +254 -0
- bundle/plugin-site/ornith_g256/attention_mixed.py +75 -0
- bundle/plugin-site/ornith_g256/attention_partition.py +162 -0
- bundle/plugin-site/ornith_g256/attention_storage.py +93 -0
- bundle/plugin-site/ornith_g256/attention_tile.py +160 -0
- bundle/plugin-site/ornith_g256/attention_verify.py +64 -0
- bundle/plugin-site/ornith_g256/attention_window.py +74 -0
- bundle/plugin-site/ornith_g256/cache_full1120.py +72 -0
- bundle/plugin-site/ornith_g256/column_backend.py +93 -0
- bundle/plugin-site/ornith_g256/column_kernel.py +263 -0
- bundle/plugin-site/ornith_g256/config.py +58 -0
- bundle/plugin-site/ornith_g256/dense_n32.py +53 -0
- bundle/plugin-site/ornith_g256/dense_source.py +37 -0
- bundle/plugin-site/ornith_g256/dflash_conv_boundary.py +123 -0
- bundle/plugin-site/ornith_g256/dflash_spec.py +29 -0
- bundle/plugin-site/ornith_g256/dynamic_graphs.py +266 -0
- bundle/plugin-site/ornith_g256/gdn_compact.py +99 -0
- bundle/plugin-site/ornith_g256/gdn_compact_kernel.py +302 -0
- bundle/plugin-site/ornith_g256/gdn_spec.py +151 -0
.gitattributes
CHANGED
|
@@ -34,3 +34,13 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
bundle/models/target/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
bundle/models/target/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
assets/ciru-halo-agent.png filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
bundle/native/libornith_attention_iu4.so filter=lfs diff=lfs merge=lfs -text
|
| 39 |
+
bundle/native/libornith_head_i8_tile.so filter=lfs diff=lfs merge=lfs -text
|
| 40 |
+
bundle/native/libornith_persistent_iu4.so filter=lfs diff=lfs merge=lfs -text
|
| 41 |
+
bundle/native/libornith_routed_direct.so filter=lfs diff=lfs merge=lfs -text
|
| 42 |
+
bundle/native/libornith_routed_n32.so filter=lfs diff=lfs merge=lfs -text
|
| 43 |
+
bundle/native/libornith_routed_storage_n32.so filter=lfs diff=lfs merge=lfs -text
|
| 44 |
+
runtime/aiter-jit-gfx1151/module_aiter_core.so filter=lfs diff=lfs merge=lfs -text
|
| 45 |
+
runtime/wheels/amd_aiter-0.1.0rc1-cp314-cp314-linux_x86_64.whl filter=lfs diff=lfs merge=lfs -text
|
| 46 |
+
runtime/wheels/vllm-0.1.0rc2.dev9+g9255fd9fb9.rocm100-cp314-cp314-linux_x86_64.whl filter=lfs diff=lfs merge=lfs -text
|
INSTALL.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Install and run Ornith1.5 Ciru Halo Agent
|
| 2 |
+
|
| 3 |
+
This release includes the target model, trained DFlash2 drafter, native kernels, custom vLLM plugin, and exact vLLM/AITER runtime wheels and source archives. **Use this runtime; stock `pip install vllm` does not provide the custom quantization or serving path.**
|
| 4 |
+
|
| 5 |
+
## Hardware and platform
|
| 6 |
+
|
| 7 |
+
- AMD Ryzen AI Max+ Strix Halo, gfx1151, with 128 GB unified memory.
|
| 8 |
+
- Linux x86-64 with a working AMD GPU driver, readable/writable `/dev/kfd` and render nodes.
|
| 9 |
+
- Budget roughly 100 GB available system memory for the loaded production profile. The recorded whole-host peak was 95.35 GB; other applications also use that memory.
|
| 10 |
+
- Allow at least 60 GB free disk for the 24.3 GB model assets, runtime installation and caches; source rebuilds need additional space.
|
| 11 |
+
- Validated platform: NixOS, Linux 7.2.2, glibc 2.42. A clean isolated runtime installation was tested on Strix Halo. The Ubuntu recipe below is provided for deployment and has not been independently validated on Ubuntu. Do not replace your system glibc to run this model.
|
| 12 |
+
|
| 13 |
+
## Download
|
| 14 |
+
|
| 15 |
+
Install [uv](https://docs.astral.sh/uv/getting-started/installation/) and Git, then:
|
| 16 |
+
|
| 17 |
+
```bash
|
| 18 |
+
uvx --from huggingface_hub hf download \
|
| 19 |
+
jcbtc/Ornith1.5-Ciru-Halo-Agent-vllm-strix-halo \
|
| 20 |
+
--local-dir ./ciru-halo-agent
|
| 21 |
+
cd ciru-halo-agent
|
| 22 |
+
```
|
| 23 |
+
|
| 24 |
+
## Ubuntu 26.04 LTS prerequisites
|
| 25 |
+
|
| 26 |
+
Ubuntu 26.04 supplies [glibc 2.43](https://packages.ubuntu.com/resolute/libc6). This is the mainstream distro recipe; the validated host remains NixOS.
|
| 27 |
+
|
| 28 |
+
```bash
|
| 29 |
+
sudo apt-get update
|
| 30 |
+
sudo apt-get install -y build-essential git cmake ninja-build pkg-config xxd \
|
| 31 |
+
curl ca-certificates tar libnuma-dev libdrm-dev libelf-dev libssl-dev \
|
| 32 |
+
zlib1g-dev libvulkan-dev
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
Ensure your user can access `/dev/kfd` and `/dev/dri/renderD*`; GPU access must work before launching. The installer obtains the pinned ROCm SDK and Python packages; a stock distro vLLM package is not needed.
|
| 36 |
+
|
| 37 |
+
```bash
|
| 38 |
+
bash runtime/INSTALL-ORNITH-RUNTIME.sh "$PWD/installed-runtime"
|
| 39 |
+
bash bundle/serve.sh --dry-run
|
| 40 |
+
bash bundle/serve.sh --host 127.0.0.1 --port 8000
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
The runtime installer refuses to overwrite an existing installation. `uv` must be on PATH. Installation downloads pinned dependencies from the AMD wheel index and Python package index.
|
| 44 |
+
|
| 45 |
+
## NixOS
|
| 46 |
+
|
| 47 |
+
Enable the standard dynamic loader with `programs.nix-ld.enable = true;` and working AMD GPU device access. The provided shell supplies the compiler and host libraries:
|
| 48 |
+
|
| 49 |
+
```bash
|
| 50 |
+
nix-shell runtime/shell.nix --run \
|
| 51 |
+
'bash runtime/INSTALL-ORNITH-RUNTIME.sh "$PWD/installed-runtime"'
|
| 52 |
+
nix-shell runtime/shell.nix --run \
|
| 53 |
+
'bash bundle/serve.sh --host 127.0.0.1 --port 8000'
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
## API and agent clients
|
| 57 |
+
|
| 58 |
+
The server exposes an OpenAI-compatible API at `http://127.0.0.1:8000/v1`, model ID **`ciru-halo-agent`**. Point Hermes or another compatible agent client to that URL. Use `--host 0.0.0.0` only when you intend to expose it on your network; the launcher provides no authentication by default.
|
| 59 |
+
|
| 60 |
+
```bash
|
| 61 |
+
curl http://127.0.0.1:8000/health
|
| 62 |
+
curl http://127.0.0.1:8000/v1/chat/completions \
|
| 63 |
+
-H 'Content-Type: application/json' \
|
| 64 |
+
-d '{"model":"ciru-halo-agent","messages":[{"role":"user","content":"Write a Python function that merges overlapping intervals."}],"temperature":0.6,"top_p":0.95,"max_tokens":4096,"chat_template_kwargs":{"enable_thinking":false}}'
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
The default profile supplies **262,144 tokens of per-request context capacity**, **eight active sequences**, **44 GiB shared KV/state pool**, prefix caching and adaptive speculation. Input and output share the context window; your agent client must reserve output space and compact history before filling it. Eight independent, fully populated 256K histories are not promised. The server is text-only in this release; older optional vision experiments are not presented as current-profile validation.
|
| 68 |
+
|
| 69 |
+
First startup compiles/loads GPU kernels and creates caches. Wait for `/health` before sending work. Keep `bundle/cache` writable. Change runtime/model locations with `ORNITH_RUNTIME_ROOT`, `ORNITH_MODEL`, and `ORNITH_DRAFT`. Ordinary users do not need to change quantization or draft-policy settings.
|
| 70 |
+
|
| 71 |
+
## Build from source
|
| 72 |
+
|
| 73 |
+
The [Ciru source repository](https://github.com/ciru-ai/ornith-ciru-halo-agent) contains the model plugin, all eight native kernel sources, and the corresponding build script. See its `BUILD.md` for the native rebuild command. Runtime source archives are provided in this Hugging Face repository under `runtime/`; they include the matching vLLM/AITER source, licenses and release overlay notes. The binary installation above is the tested way to assemble the pinned engine; native source rebuilding is separate from retraining or requantizing the model.
|
| 74 |
+
|
| 75 |
+
Pinned runtime: vLLM `0.1.0rc2.dev9+g9255fd9fb9.rocm100` (base `9255fd9fb9fedf4b29d574a8d8bb21d93892cc98` plus supplied cache overlay), AITER `0.1.0rc1`, Python 3.14.3, PyTorch `2.13.0+rocm10.0.0`, ROCm SDK 10.0.0 and Transformers 5.16.1. Preserve included third-party licenses when redistributing.
|
assets/ciru-halo-agent.png
ADDED
|
Git LFS Details
|
bundle/native/libornith_attention_iu4.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2905806824bece62ce9d9140608859df869f3e3d3663c67bf79ad4ea495cd61f
|
| 3 |
+
size 186640
|
bundle/native/libornith_dense_g256.so
ADDED
|
Binary file (69.8 kB). View file
|
|
|
bundle/native/libornith_dense_g256_n32.so
ADDED
|
Binary file (76.8 kB). View file
|
|
|
bundle/native/libornith_head_i8_tile.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2c03de5ed015a052b407436c7e42b50fa33ef76cb46dccab9b226546ca1d1e09
|
| 3 |
+
size 128952
|
bundle/native/libornith_persistent_iu4.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:40e5cf35157c3e716de8067f7eef1312a3b9e787dd52a6f2e232ccd2d07fa68c
|
| 3 |
+
size 103792
|
bundle/native/libornith_routed_direct.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5b3ca6826547e0ff1c417e733229e104721cec403ccd8f88f7fcdb1726d6f4ed
|
| 3 |
+
size 156392
|
bundle/native/libornith_routed_n32.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:48415e71f6c85e3aefcecf99d72607bb05900244b4aaa9a7ba2c58378d92f5fb
|
| 3 |
+
size 151928
|
bundle/native/libornith_routed_storage_n32.so
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:3ea288cd0e37a4c373038744c3cc4cd4eac09675ac137e953d5ae7a6cd43dd3d
|
| 3 |
+
size 158536
|
bundle/packaging/serve.sh
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Copyright 2026 Ciru. Source only the explicitly selected installed runtime.
|
| 3 |
+
set -euo pipefail
|
| 4 |
+
repo_root=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
|
| 5 |
+
runtime_root=${ORNITH_RUNTIME_ROOT:-}
|
| 6 |
+
plugin_site=${ORNITH_PLUGIN_SITE:-$repo_root/.runtime/plugin-site}
|
| 7 |
+
cache_directory=${XDG_CACHE_HOME:-$HOME/.cache}/ornith-g256
|
| 8 |
+
launch_args=()
|
| 9 |
+
while (($#)); do
|
| 10 |
+
case "$1" in
|
| 11 |
+
--runtime-root) runtime_root=${2:?--runtime-root requires a directory}; shift 2 ;;
|
| 12 |
+
--plugin-site) plugin_site=${2:?--plugin-site requires a directory}; shift 2 ;;
|
| 13 |
+
--cache-directory) cache_directory=${2:?--cache-directory requires a directory}; launch_args+=("$1" "$2"); shift 2 ;;
|
| 14 |
+
*) launch_args+=("$1"); shift ;;
|
| 15 |
+
esac
|
| 16 |
+
done
|
| 17 |
+
if [[ -z "$runtime_root" ]]; then
|
| 18 |
+
echo 'Set --runtime-root DIR (installed Ciru vLLM runtime) or ORNITH_RUNTIME_ROOT.' >&2
|
| 19 |
+
exit 2
|
| 20 |
+
fi
|
| 21 |
+
test -f "$runtime_root/runtime-env.sh"
|
| 22 |
+
test -x "$runtime_root/venv/bin/python"
|
| 23 |
+
test -d "$plugin_site/ornith_g256"
|
| 24 |
+
export VLLM_SOURCE="$runtime_root/vllm" VLLM_VENV="$runtime_root/venv"
|
| 25 |
+
export AITER_SOURCE="$runtime_root/aiter"
|
| 26 |
+
export XDG_CACHE_HOME="$cache_directory" AITER_JIT_DIR="$cache_directory/aiter"
|
| 27 |
+
# shellcheck source=/dev/null
|
| 28 |
+
source "$runtime_root/runtime-env.sh"
|
| 29 |
+
unset VLLM_SOURCE VLLM_VENV
|
| 30 |
+
export PYTHONPATH="$plugin_site${PYTHONPATH:+:$PYTHONPATH}"
|
| 31 |
+
exec "$runtime_root/venv/bin/python" -m ornith_g256.launch "${launch_args[@]}" --cache-directory "$cache_directory"
|
bundle/paths.env
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Environment overrides for the packaged release.
|
| 2 |
+
export ORNITH_MODEL="${ORNITH_MODEL:-$bundle_root/models/target}"
|
| 3 |
+
export ORNITH_DRAFT="${ORNITH_DRAFT:-$bundle_root/models/draft}"
|
| 4 |
+
export ORNITH_RUNTIME_ROOT="${ORNITH_RUNTIME_ROOT:-$bundle_root/../installed-runtime}"
|
bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/INSTALLER
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
uv
|
bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/METADATA
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Metadata-Version: 2.4
|
| 2 |
+
Name: ciru-ornith-g256
|
| 3 |
+
Version: 0.0.2a0
|
| 4 |
+
Summary: Self-contained project adapter for Ornith G256 and DFlash2 on Ciru vLLM
|
| 5 |
+
Author-email: Ciru <ciru@ciru.ai>
|
| 6 |
+
Requires-Python: >=3.10
|
| 7 |
+
License-File: LICENSE-APACHE-2.0
|
| 8 |
+
Dynamic: license-file
|
bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/RECORD
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
bin/ornith-g256-serve,sha256=gw9CG161mmR0ebQFSvD64ZhMtGPuBb9gDv9lhy6oHHw,319
|
| 2 |
+
ciru_ornith_g256-0.0.2a0.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
|
| 3 |
+
ciru_ornith_g256-0.0.2a0.dist-info/METADATA,sha256=rTkRa0xu1BdLKZsDFr6laFc4K679lMWJ8BlBNhUBdnc,256
|
| 4 |
+
ciru_ornith_g256-0.0.2a0.dist-info/RECORD,,
|
| 5 |
+
ciru_ornith_g256-0.0.2a0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
| 6 |
+
ciru_ornith_g256-0.0.2a0.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
|
| 7 |
+
ciru_ornith_g256-0.0.2a0.dist-info/direct_url.json,sha256=MwFMTtCGcu6fn-TruM1lo2XsI7q0gT0JZbfW7sb3OEA,105
|
| 8 |
+
ciru_ornith_g256-0.0.2a0.dist-info/entry_points.txt,sha256=ESe1wopdkiGfr5DNWZcSemlgPr3uvRre1OyHkl88QqU,121
|
| 9 |
+
ciru_ornith_g256-0.0.2a0.dist-info/licenses/LICENSE-APACHE-2.0,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
|
| 10 |
+
ciru_ornith_g256-0.0.2a0.dist-info/top_level.txt,sha256=EdCMLXnn8tDNBUR3JhkMvOi8hEw6IuhYFYEcfdhekxU,12
|
| 11 |
+
ciru_ornith_g256-0.0.2a0.dist-info/uv_build.json,sha256=RBNvo1WzZ4oRRq0W9-hknpT7T8If536DEMBg9hyq_4o,2
|
| 12 |
+
ciru_ornith_g256-0.0.2a0.dist-info/uv_cache.json,sha256=L73WKGonFia8yJ8vK90qJmbQxtUhlvCNsq7D4umUJX4,137
|
| 13 |
+
ornith_g256/__init__.py,sha256=vk_ZSh7Mt3Drbg5zyLpEGPvSGuG2MMiKV7_hwsp4-TM,1004
|
| 14 |
+
ornith_g256/attention.py,sha256=L908DthNywUqno-d9AaN34CpaZMb6gopXgItNrdTzaE,772
|
| 15 |
+
ornith_g256/attention_fast.py,sha256=J7GhM8rbh_86Y8AnBJ4dqB5BEv1dUmCkRA_ehUkfg4o,12274
|
| 16 |
+
ornith_g256/attention_tile.py,sha256=_OaqYdO7YhAobtgDAdQmoFy-g5s7DF8PGO25VuY9E3g,2157
|
| 17 |
+
ornith_g256/attention_verify.py,sha256=OjKVmWlCZfS63swvm3CbKA2ZYLln1xkvOVmtng2MYHc,3755
|
| 18 |
+
ornith_g256/column_backend.py,sha256=oDW4OTaeczsF9ozoPfDQIstiRVAW_M8wwpeBJ6s8VAM,5752
|
| 19 |
+
ornith_g256/column_kernel.py,sha256=J_sFPZcBhHMqRiEgJq_TFS7Aeg38PcTFzFzA1TnxfIE,9422
|
| 20 |
+
ornith_g256/config.py,sha256=IyENi2Je83Vjji1gQ6kqJhgaW18cU32dSzTJG3EJ828,2869
|
| 21 |
+
ornith_g256/dflash_spec.py,sha256=yUi5nqsi0q9cX8DKn3ZKCFWj2GrKBgk2K_VHSpvv8bM,1393
|
| 22 |
+
ornith_g256/gdn_spec.py,sha256=CTGxs2Adk1u2rN_LZdMH2uQQVkXNOHvZslbdelJTdqM,5696
|
| 23 |
+
ornith_g256/launch.py,sha256=o_aMA9-DiJBB6FH2aOhEgynFUrl5GXU9YY9gTRQm_0w,9265
|
| 24 |
+
ornith_g256/lifecycle.py,sha256=MoVVIXfLjOigFJoB-BaNK8RKc5qTlcqE4VrMy7IKC3M,8269
|
| 25 |
+
ornith_g256/loader.py,sha256=qFPG0BQhSnrfKYVI-RdbbhjdBTOJF67Bbry0_Dw-yxM,2933
|
| 26 |
+
ornith_g256/method.py,sha256=0cvBQvPLd1vgTOh8zE6AOWmNaLJYhmkFZ0ZWvdHAcaM,7340
|
| 27 |
+
ornith_g256/moe_base.py,sha256=JvtRdBXySXhZ8uP_Gu2PMefFakqszPs0xmOOZdffB8Q,3683
|
| 28 |
+
ornith_g256/native.py,sha256=U14h8Uy3BlRBBfRNWXK05mVtwRmUgbvWLQ3QUjJvPYU,6901
|
| 29 |
+
ornith_g256/prefix_cache.py,sha256=tdbO44VRim0oVvy-ELoJOYqSgcC_MGFKkUzncIrovZs,4817
|
| 30 |
+
ornith_g256/runtime.py,sha256=xCdlAa-_jiOLw0l8JK4A0XK--_HQdjAvskFlXKHWG2E,4997
|
| 31 |
+
ornith_g256/worker.py,sha256=UpYQcXpzv4awhAKYiPsBSuk-z9-5rV2DUb_q-qUPw0A,9492
|
| 32 |
+
ornith_g256/worker_base.py,sha256=oA7xKVFy5lXIs8fXkM_QMWEUqUkuSD3t_eoDbTt7-Gg,4601
|
bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/REQUESTED
ADDED
|
File without changes
|
bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/WHEEL
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Wheel-Version: 1.0
|
| 2 |
+
Generator: setuptools (79.0.1)
|
| 3 |
+
Root-Is-Purelib: true
|
| 4 |
+
Tag: py3-none-any
|
| 5 |
+
|
bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/direct_url.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"url":"file:///srv/ssd/sn850x/scratch/crown/ornith-prefix-64k-v1/src/runtime/ornith_g256","dir_info":{}}
|
bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/entry_points.txt
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[console_scripts]
|
| 2 |
+
ornith-g256-serve = ornith_g256.launch:main
|
| 3 |
+
|
| 4 |
+
[vllm.general_plugins]
|
| 5 |
+
ornith_g256 = ornith_g256:register
|
bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/licenses/LICENSE-APACHE-2.0
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Apache License
|
| 3 |
+
Version 2.0, January 2004
|
| 4 |
+
http://www.apache.org/licenses/
|
| 5 |
+
|
| 6 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 7 |
+
|
| 8 |
+
1. Definitions.
|
| 9 |
+
|
| 10 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 11 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 12 |
+
|
| 13 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 14 |
+
the copyright owner that is granting the License.
|
| 15 |
+
|
| 16 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 17 |
+
other entities that control, are controlled by, or are under common
|
| 18 |
+
control with that entity. For the purposes of this definition,
|
| 19 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 20 |
+
direction or management of such entity, whether by contract or
|
| 21 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 22 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 23 |
+
|
| 24 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 25 |
+
exercising permissions granted by this License.
|
| 26 |
+
|
| 27 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 28 |
+
including but not limited to software source code, documentation
|
| 29 |
+
source, and configuration files.
|
| 30 |
+
|
| 31 |
+
"Object" form shall mean any form resulting from mechanical
|
| 32 |
+
transformation or translation of a Source form, including but
|
| 33 |
+
not limited to compiled object code, generated documentation,
|
| 34 |
+
and conversions to other media types.
|
| 35 |
+
|
| 36 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 37 |
+
Object form, made available under the License, as indicated by a
|
| 38 |
+
copyright notice that is included in or attached to the work
|
| 39 |
+
(an example is provided in the Appendix below).
|
| 40 |
+
|
| 41 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 42 |
+
form, that is based on (or derived from) the Work and for which the
|
| 43 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 44 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 45 |
+
of this License, Derivative Works shall not include works that remain
|
| 46 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 47 |
+
the Work and Derivative Works thereof.
|
| 48 |
+
|
| 49 |
+
"Contribution" shall mean any work of authorship, including
|
| 50 |
+
the original version of the Work and any modifications or additions
|
| 51 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 52 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 53 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 54 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 55 |
+
means any form of electronic, verbal, or written communication sent
|
| 56 |
+
to the Licensor or its representatives, including but not limited to
|
| 57 |
+
communication on electronic mailing lists, source code control systems,
|
| 58 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 59 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 60 |
+
excluding communication that is conspicuously marked or otherwise
|
| 61 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 62 |
+
|
| 63 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 64 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 65 |
+
subsequently incorporated within the Work.
|
| 66 |
+
|
| 67 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 68 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 69 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 70 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 71 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 72 |
+
Work and such Derivative Works in Source or Object form.
|
| 73 |
+
|
| 74 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 75 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 76 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 77 |
+
(except as stated in this section) patent license to make, have made,
|
| 78 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 79 |
+
where such license applies only to those patent claims licensable
|
| 80 |
+
by such Contributor that are necessarily infringed by their
|
| 81 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 82 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 83 |
+
institute patent litigation against any entity (including a
|
| 84 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 85 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 86 |
+
or contributory patent infringement, then any patent licenses
|
| 87 |
+
granted to You under this License for that Work shall terminate
|
| 88 |
+
as of the date such litigation is filed.
|
| 89 |
+
|
| 90 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 91 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 92 |
+
modifications, and in Source or Object form, provided that You
|
| 93 |
+
meet the following conditions:
|
| 94 |
+
|
| 95 |
+
(a) You must give any other recipients of the Work or
|
| 96 |
+
Derivative Works a copy of this License; and
|
| 97 |
+
|
| 98 |
+
(b) You must cause any modified files to carry prominent notices
|
| 99 |
+
stating that You changed the files; and
|
| 100 |
+
|
| 101 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 102 |
+
that You distribute, all copyright, patent, trademark, and
|
| 103 |
+
attribution notices from the Source form of the Work,
|
| 104 |
+
excluding those notices that do not pertain to any part of
|
| 105 |
+
the Derivative Works; and
|
| 106 |
+
|
| 107 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 108 |
+
distribution, then any Derivative Works that You distribute must
|
| 109 |
+
include a readable copy of the attribution notices contained
|
| 110 |
+
within such NOTICE file, excluding those notices that do not
|
| 111 |
+
pertain to any part of the Derivative Works, in at least one
|
| 112 |
+
of the following places: within a NOTICE text file distributed
|
| 113 |
+
as part of the Derivative Works; within the Source form or
|
| 114 |
+
documentation, if provided along with the Derivative Works; or,
|
| 115 |
+
within a display generated by the Derivative Works, if and
|
| 116 |
+
wherever such third-party notices normally appear. The contents
|
| 117 |
+
of the NOTICE file are for informational purposes only and
|
| 118 |
+
do not modify the License. You may add Your own attribution
|
| 119 |
+
notices within Derivative Works that You distribute, alongside
|
| 120 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 121 |
+
that such additional attribution notices cannot be construed
|
| 122 |
+
as modifying the License.
|
| 123 |
+
|
| 124 |
+
You may add Your own copyright statement to Your modifications and
|
| 125 |
+
may provide additional or different license terms and conditions
|
| 126 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 127 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 128 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 129 |
+
the conditions stated in this License.
|
| 130 |
+
|
| 131 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 132 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 133 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 134 |
+
this License, without any additional terms or conditions.
|
| 135 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 136 |
+
the terms of any separate license agreement you may have executed
|
| 137 |
+
with Licensor regarding such Contributions.
|
| 138 |
+
|
| 139 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 140 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 141 |
+
except as required for reasonable and customary use in describing the
|
| 142 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 143 |
+
|
| 144 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 145 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 146 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 147 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 148 |
+
implied, including, without limitation, any warranties or conditions
|
| 149 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 150 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 151 |
+
appropriateness of using or redistributing the Work and assume any
|
| 152 |
+
risks associated with Your exercise of permissions under this License.
|
| 153 |
+
|
| 154 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 155 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 156 |
+
unless required by applicable law (such as deliberate and grossly
|
| 157 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 158 |
+
liable to You for damages, including any direct, indirect, special,
|
| 159 |
+
incidental, or consequential damages of any character arising as a
|
| 160 |
+
result of this License or out of the use or inability to use the
|
| 161 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 162 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 163 |
+
other commercial damages or losses), even if such Contributor
|
| 164 |
+
has been advised of the possibility of such damages.
|
| 165 |
+
|
| 166 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 167 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 168 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 169 |
+
or other liability obligations and/or rights consistent with this
|
| 170 |
+
License. However, in accepting such obligations, You may act only
|
| 171 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 172 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 173 |
+
defend, and hold each Contributor harmless for any liability
|
| 174 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 175 |
+
of your accepting any such warranty or additional liability.
|
| 176 |
+
|
| 177 |
+
END OF TERMS AND CONDITIONS
|
| 178 |
+
|
| 179 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 180 |
+
|
| 181 |
+
To apply the Apache License to your work, attach the following
|
| 182 |
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
| 183 |
+
replaced with your own identifying information. (Don't include
|
| 184 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 185 |
+
comment syntax for the file format. We also recommend that a
|
| 186 |
+
file or class name and description of purpose be included on the
|
| 187 |
+
same "printed page" as the copyright notice for easier
|
| 188 |
+
identification within third-party archives.
|
| 189 |
+
|
| 190 |
+
Copyright [yyyy] [name of copyright owner]
|
| 191 |
+
|
| 192 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 193 |
+
you may not use this file except in compliance with the License.
|
| 194 |
+
You may obtain a copy of the License at
|
| 195 |
+
|
| 196 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 197 |
+
|
| 198 |
+
Unless required by applicable law or agreed to in writing, software
|
| 199 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 200 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 201 |
+
See the License for the specific language governing permissions and
|
| 202 |
+
limitations under the License.
|
bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
ornith_g256
|
bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/uv_build.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{}
|
bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/uv_cache.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"timestamp":{"secs_since_epoch":1788727205,"nanos_since_epoch":456565118},"commit":null,"tags":null,"env":{},"directories":{"src":null}}
|
bundle/plugin-site/ornith_g256/__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Ciru G256 prototype; no installed vLLM files are modified."""
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def register():
|
| 5 |
+
from .cache_full1120 import install as install_full1120
|
| 6 |
+
install_full1120()
|
| 7 |
+
from vllm.model_executor.layers.quantization import (
|
| 8 |
+
_CUSTOMIZED_METHOD_TO_QUANT_CONFIG, register_quantization_config,
|
| 9 |
+
)
|
| 10 |
+
from vllm.v1.attention.backends.registry import AttentionBackendEnum, register_backend
|
| 11 |
+
from .config import OrnithG256Config
|
| 12 |
+
|
| 13 |
+
existing = _CUSTOMIZED_METHOD_TO_QUANT_CONFIG.get("ornith_g256")
|
| 14 |
+
if existing is None:
|
| 15 |
+
register_quantization_config("ornith_g256")(OrnithG256Config)
|
| 16 |
+
elif existing is not OrnithG256Config:
|
| 17 |
+
raise RuntimeError("Another plugin owns ornith_g256")
|
| 18 |
+
register_backend(AttentionBackendEnum.CUSTOM, "ornith_g256.attention_fast.OrnithG256SelectableAttentionBackend")
|
| 19 |
+
# Install in every vLLM process: cache groups are also built in the engine
|
| 20 |
+
# core, which does not instantiate our worker. The wrapper is a no-op for
|
| 21 |
+
# prefix-off runs and models outside this project.
|
| 22 |
+
from .prefix_cache import install
|
| 23 |
+
install()
|
| 24 |
+
|
| 25 |
+
from .adaptive_c1 import install as install_adaptive_c1
|
| 26 |
+
install_adaptive_c1()
|
bundle/plugin-site/ornith_g256/adaptive_c1.py
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Private reversible C1 policy pilot; fixed controls and C2–8 DF7 retained."""
|
| 2 |
+
from collections import deque
|
| 3 |
+
from functools import wraps
|
| 4 |
+
from pathlib import Path
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
import time
|
| 8 |
+
|
| 9 |
+
CONTROL = Path(__file__).resolve().parents[3] / 'policy-mode'
|
| 10 |
+
_INSTALLED = False
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class RequestCost:
|
| 14 |
+
"""Request-local, reversible C1 probe policy. Thresholds are pilot settings."""
|
| 15 |
+
MAX_CONTEXT = 32768
|
| 16 |
+
INITIAL_FLOOR_MS = 19.0
|
| 17 |
+
WINDOW = 8
|
| 18 |
+
GAIN = .90
|
| 19 |
+
MAX_PROBE_CYCLES = 4
|
| 20 |
+
|
| 21 |
+
def __init__(self):
|
| 22 |
+
self.mode = os.environ.get('ORNITH_C1_POLICY') or CONTROL.read_text().strip()
|
| 23 |
+
if self.mode not in ('k0', 'k7', 'k15', 'auto'):
|
| 24 |
+
raise ValueError(f'Unknown C1 mode {self.mode!r}')
|
| 25 |
+
self.depth = 15 if self.mode == 'auto' else int(self.mode[1:])
|
| 26 |
+
self.cycles = 0
|
| 27 |
+
self.samples = deque(maxlen=self.WINDOW)
|
| 28 |
+
self.floor_ms = self.INITIAL_FLOOR_MS
|
| 29 |
+
self.cycle_ms = {}
|
| 30 |
+
self.tokens = 0
|
| 31 |
+
self.floor_tokens = 0
|
| 32 |
+
self.warm = 2
|
| 33 |
+
self.bad_windows = 0
|
| 34 |
+
self.probe = None
|
| 35 |
+
self.retry_after = {0: 0, 7: 0, 15: 0}
|
| 36 |
+
self.last_15_probe = 0
|
| 37 |
+
self.last_7_probe = 0
|
| 38 |
+
self.paused = False
|
| 39 |
+
self.long_context = False
|
| 40 |
+
self.rid = None
|
| 41 |
+
|
| 42 |
+
def event(self, kind, **values):
|
| 43 |
+
print('ORNITH_C1_' + kind + ' ' + json.dumps(dict(
|
| 44 |
+
request_id=self.rid, mode=self.mode, depth=self.depth,
|
| 45 |
+
output_progress=self.tokens, **values)), flush=True)
|
| 46 |
+
|
| 47 |
+
def reset_to_15(self, reason):
|
| 48 |
+
old = self.depth
|
| 49 |
+
self.depth = 15
|
| 50 |
+
self.probe = None
|
| 51 |
+
self.samples.clear()
|
| 52 |
+
self.warm = 2
|
| 53 |
+
self.bad_windows = 0
|
| 54 |
+
self.floor_tokens = 0
|
| 55 |
+
if old != 15:
|
| 56 |
+
self.event('RECOVERY', previous=old, reason=reason)
|
| 57 |
+
|
| 58 |
+
def budget(self, context):
|
| 59 |
+
if self.mode != 'auto':
|
| 60 |
+
return self.depth
|
| 61 |
+
if context > self.MAX_CONTEXT:
|
| 62 |
+
if not self.long_context:
|
| 63 |
+
self.reset_to_15('context_above_32768')
|
| 64 |
+
self.long_context = True
|
| 65 |
+
return 15
|
| 66 |
+
if self.paused:
|
| 67 |
+
self.paused = False
|
| 68 |
+
self.reset_to_15('return_from_concurrency')
|
| 69 |
+
return self.depth
|
| 70 |
+
|
| 71 |
+
def start_probe(self, candidate, base_cost, reason):
|
| 72 |
+
incumbent = self.depth
|
| 73 |
+
self.probe = dict(incumbent=incumbent, candidate=candidate,
|
| 74 |
+
baseline_ms=base_cost, samples=[], excess_ms=0.0)
|
| 75 |
+
self.depth = candidate
|
| 76 |
+
self.samples.clear()
|
| 77 |
+
self.warm = 1
|
| 78 |
+
self.bad_windows = 0
|
| 79 |
+
if candidate == 15:
|
| 80 |
+
self.last_15_probe = self.tokens
|
| 81 |
+
if candidate == 7:
|
| 82 |
+
self.last_7_probe = self.tokens
|
| 83 |
+
self.event('PROBE', incumbent=incumbent, candidate=candidate,
|
| 84 |
+
baseline_ms_per_token=base_cost, reason=reason)
|
| 85 |
+
|
| 86 |
+
def finish_probe(self, accept, cost, reason):
|
| 87 |
+
probe = self.probe
|
| 88 |
+
assert probe is not None
|
| 89 |
+
candidate, incumbent = probe['candidate'], probe['incumbent']
|
| 90 |
+
self.depth = candidate if accept else incumbent
|
| 91 |
+
self.probe = None
|
| 92 |
+
self.samples.clear()
|
| 93 |
+
self.warm = 1
|
| 94 |
+
self.floor_tokens = 0
|
| 95 |
+
if not accept:
|
| 96 |
+
self.retry_after[candidate] = self.tokens + 64
|
| 97 |
+
if accept and incumbent == 15:
|
| 98 |
+
self.last_15_probe = self.tokens
|
| 99 |
+
self.retry_after[15] = self.tokens + 32
|
| 100 |
+
self.event('PROBE_RESULT', incumbent=incumbent, candidate=candidate,
|
| 101 |
+
accepted=accept, cost_ms_per_token=cost,
|
| 102 |
+
reference_ms_per_token=probe['baseline_ms'],
|
| 103 |
+
observed_cycles=len(probe['samples']), excess_ms=probe['excess_ms'],
|
| 104 |
+
reason=reason)
|
| 105 |
+
# Failure of DF7 must not block an independently promising floor.
|
| 106 |
+
# The first paired run exposed repeated 15->7 rejections with K0 never
|
| 107 |
+
# reachable, despite measured DF15 costs far above ordinary decode.
|
| 108 |
+
if (not accept and candidate == 7 and incumbent == 15
|
| 109 |
+
and probe['baseline_ms'] >= self.floor_ms / self.GAIN
|
| 110 |
+
and self.tokens >= self.retry_after[0]):
|
| 111 |
+
self.start_probe(0, probe['baseline_ms'], 'rejected_df7_try_floor_directly')
|
| 112 |
+
return
|
| 113 |
+
# Full seven-token proposals are direct evidence of a saturated DF7
|
| 114 |
+
# block. Probe DF15 promptly; this still requires a measured gain.
|
| 115 |
+
if accept and candidate == 7 and incumbent == 0:
|
| 116 |
+
filled = sum(progress == 8 for _, progress in probe['samples'])
|
| 117 |
+
if filled >= 2 and self.tokens >= self.retry_after[15]:
|
| 118 |
+
self.start_probe(15, cost, 'recovered_full_df7_proposals')
|
| 119 |
+
|
| 120 |
+
def observe(self, elapsed_ms, progressed, rid, actual, future, context):
|
| 121 |
+
self.rid = rid
|
| 122 |
+
self.tokens += progressed
|
| 123 |
+
if actual == 0:
|
| 124 |
+
self.floor_tokens += progressed
|
| 125 |
+
self.cycles += 1
|
| 126 |
+
if self.mode == 'auto':
|
| 127 |
+
self.budget(context)
|
| 128 |
+
steady = actual == future == self.depth
|
| 129 |
+
if self.probe is not None:
|
| 130 |
+
# Include transition/warm work as probe overhead, but never use it
|
| 131 |
+
# as a stationary mode-cost estimate.
|
| 132 |
+
self.probe['excess_ms'] += max(
|
| 133 |
+
0.0, elapsed_ms - progressed * self.probe['baseline_ms'])
|
| 134 |
+
if not steady:
|
| 135 |
+
self.event('TRANSITION', actual_depth=actual, future_depth=future,
|
| 136 |
+
elapsed_ms=elapsed_ms, progressed=progressed)
|
| 137 |
+
return
|
| 138 |
+
if self.mode == 'auto' and self.long_context:
|
| 139 |
+
return
|
| 140 |
+
if self.warm:
|
| 141 |
+
self.warm -= 1
|
| 142 |
+
return
|
| 143 |
+
self.samples.append((elapsed_ms, progressed))
|
| 144 |
+
if self.mode != 'auto':
|
| 145 |
+
if self.cycles in (2, 8, 16) or self.cycles % 32 == 0:
|
| 146 |
+
ms = sum(x[0] for x in self.samples)
|
| 147 |
+
progress = sum(x[1] for x in self.samples)
|
| 148 |
+
self.event('WINDOW', actual_depth=actual, cycle=self.cycles,
|
| 149 |
+
elapsed_ms=ms, progressed=progress,
|
| 150 |
+
tg=1000*progress/ms if ms else None)
|
| 151 |
+
return
|
| 152 |
+
if self.probe is not None:
|
| 153 |
+
probe = self.probe
|
| 154 |
+
probe['samples'].append((elapsed_ms, progressed))
|
| 155 |
+
n = len(probe['samples'])
|
| 156 |
+
ms = sum(x[0] for x in probe['samples'])
|
| 157 |
+
progress = sum(x[1] for x in probe['samples'])
|
| 158 |
+
cost = ms / progress
|
| 159 |
+
if n < 2:
|
| 160 |
+
return
|
| 161 |
+
if n == 2 and cost <= self.GAIN*probe['baseline_ms'] and actual == 7 and all(p == 8 for _, p in probe['samples']):
|
| 162 |
+
self.cycle_ms[actual] = ms/n
|
| 163 |
+
self.finish_probe(True, cost, 'two_full_df7_blocks')
|
| 164 |
+
elif cost >= 1.25*probe['baseline_ms'] or (probe['excess_ms'] >= 100.0 and cost >= probe['baseline_ms']):
|
| 165 |
+
self.finish_probe(False, cost, 'quick_loss_or_probe_cost_cap')
|
| 166 |
+
elif n >= self.MAX_PROBE_CYCLES:
|
| 167 |
+
self.cycle_ms[actual] = ms/n
|
| 168 |
+
if actual == 0:
|
| 169 |
+
self.floor_ms = cost
|
| 170 |
+
self.finish_probe(cost <= self.GAIN*probe['baseline_ms'], cost, 'four_steady_cycles')
|
| 171 |
+
return
|
| 172 |
+
# Bound recovery latency by output progress, including warm K0 work.
|
| 173 |
+
if actual == 0 and self.floor_tokens >= 63:
|
| 174 |
+
ms = sum(x[0] for x in self.samples)
|
| 175 |
+
progress = sum(x[1] for x in self.samples)
|
| 176 |
+
self.floor_ms = ms/progress
|
| 177 |
+
self.floor_tokens = 0
|
| 178 |
+
self.start_probe(7, self.floor_ms, 'floor_recovery_after_63_tokens')
|
| 179 |
+
return
|
| 180 |
+
if len(self.samples) < self.WINDOW:
|
| 181 |
+
return
|
| 182 |
+
samples = list(self.samples)
|
| 183 |
+
self.samples.clear()
|
| 184 |
+
ms = sum(x[0] for x in samples)
|
| 185 |
+
progress = sum(x[1] for x in samples)
|
| 186 |
+
cost = ms/progress
|
| 187 |
+
self.cycle_ms[actual] = ms/len(samples)
|
| 188 |
+
self.event('WINDOW', actual_depth=actual, cycle=self.cycles,
|
| 189 |
+
elapsed_ms=ms, progressed=progress, tg=1000/cost,
|
| 190 |
+
floor_ms_per_token=self.floor_ms)
|
| 191 |
+
if actual == 0:
|
| 192 |
+
self.floor_ms = cost
|
| 193 |
+
return
|
| 194 |
+
if actual == 15:
|
| 195 |
+
# Prefix truncation predicts opportunity only, never a measured
|
| 196 |
+
# speed claim: Q8 and Q16 noncausal proposals need not be identical.
|
| 197 |
+
cycle7 = self.cycle_ms.get(7, self.cycle_ms[15]*.76)
|
| 198 |
+
predicted7 = cycle7*len(samples)/sum(min(p, 8) for _, p in samples)
|
| 199 |
+
promising = predicted7 <= self.GAIN*cost and cost >= .85*self.floor_ms
|
| 200 |
+
self.bad_windows = self.bad_windows + 1 if promising else 0
|
| 201 |
+
if self.bad_windows >= 2 and self.tokens >= self.retry_after[7]:
|
| 202 |
+
self.start_probe(7, cost, 'two_windows_predict_at_least_10pct_gain')
|
| 203 |
+
return
|
| 204 |
+
if actual == 7:
|
| 205 |
+
filled = sum(p == 8 for _, p in samples)
|
| 206 |
+
if (self.tokens >= self.retry_after[15] and
|
| 207 |
+
(filled >= 2 or self.tokens-self.last_15_probe >= 64)):
|
| 208 |
+
self.start_probe(15, cost, 'df7_full_blocks_or_periodic_df15_recovery')
|
| 209 |
+
elif cost >= 1.05*self.floor_ms and self.tokens >= self.retry_after[0]:
|
| 210 |
+
self.start_probe(0, cost, 'df7_cost_above_measured_floor')
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
class Policy:
|
| 214 |
+
def __init__(self):
|
| 215 |
+
self.requests = {}
|
| 216 |
+
self.pending = None
|
| 217 |
+
self.observing = None
|
| 218 |
+
self.previous_mode = None
|
| 219 |
+
|
| 220 |
+
def budget(self, ids, scheduler=None):
|
| 221 |
+
if len(ids) != 1:
|
| 222 |
+
if len(ids) > 1:
|
| 223 |
+
for rid in ids:
|
| 224 |
+
state = self.requests.get(rid)
|
| 225 |
+
if state is not None and state.mode == 'auto':
|
| 226 |
+
state.paused = True
|
| 227 |
+
return 7
|
| 228 |
+
rid = ids[0]
|
| 229 |
+
if rid not in self.requests:
|
| 230 |
+
self.requests[rid] = RequestCost()
|
| 231 |
+
state = self.requests[rid]
|
| 232 |
+
state.rid = rid
|
| 233 |
+
context = 0 if scheduler is None else scheduler.requests[rid].num_computed_tokens
|
| 234 |
+
return state.budget(context)
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
def _policy(scheduler):
|
| 238 |
+
if not scheduler.vllm_config.additional_config.get('ornith_g256', {}).get('adaptive_c1_fallback'):
|
| 239 |
+
return None
|
| 240 |
+
if not hasattr(scheduler, '_ornith_c1_cost_policy'):
|
| 241 |
+
scheduler._ornith_c1_cost_policy = Policy()
|
| 242 |
+
return scheduler._ornith_c1_cost_policy
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
def install():
|
| 246 |
+
global _INSTALLED
|
| 247 |
+
if _INSTALLED:
|
| 248 |
+
return
|
| 249 |
+
from vllm.v1.core.sched.scheduler import Scheduler
|
| 250 |
+
schedule_original = Scheduler.schedule
|
| 251 |
+
update_original = Scheduler.update_from_output
|
| 252 |
+
stats_original = Scheduler.make_spec_decoding_stats
|
| 253 |
+
draft_original = Scheduler.update_draft_token_ids
|
| 254 |
+
|
| 255 |
+
@wraps(schedule_original)
|
| 256 |
+
def schedule(self, *args, **kwargs):
|
| 257 |
+
policy = _policy(self)
|
| 258 |
+
if policy is None:
|
| 259 |
+
return schedule_original(self, *args, **kwargs)
|
| 260 |
+
policy.requests = {rid: state for rid, state in policy.requests.items()
|
| 261 |
+
if rid in self.requests and not self.requests[rid].is_finished()}
|
| 262 |
+
started = time.monotonic()
|
| 263 |
+
output = schedule_original(self, *args, **kwargs)
|
| 264 |
+
ids = list(output.num_scheduled_tokens)
|
| 265 |
+
future = policy.budget(ids, self)
|
| 266 |
+
output.num_spec_tokens_to_schedule = future
|
| 267 |
+
policy.pending = dict(started=started, ids=ids, future=future)
|
| 268 |
+
mode = (len(ids), future)
|
| 269 |
+
if ids and mode != policy.previous_mode:
|
| 270 |
+
print('ORNITH_C1_MODE ' + json.dumps(dict(requests=len(ids), draft=future)), flush=True)
|
| 271 |
+
policy.previous_mode = mode
|
| 272 |
+
return output
|
| 273 |
+
|
| 274 |
+
@wraps(update_original)
|
| 275 |
+
def update(self, scheduler_output, model_runner_output):
|
| 276 |
+
policy = _policy(self)
|
| 277 |
+
if policy is None:
|
| 278 |
+
return update_original(self, scheduler_output, model_runner_output)
|
| 279 |
+
ids = list(scheduler_output.num_scheduled_tokens)
|
| 280 |
+
rid = ids[0] if len(ids) == 1 else None
|
| 281 |
+
cached = scheduler_output.scheduled_cached_reqs
|
| 282 |
+
depth = len(scheduler_output.scheduled_spec_decode_tokens.get(rid, ()))
|
| 283 |
+
eligible = (rid is not None and not scheduler_output.scheduled_new_reqs
|
| 284 |
+
and rid in cached.req_ids and not cached.is_context_phase(rid)
|
| 285 |
+
and depth in (0, 7, 15)
|
| 286 |
+
and scheduler_output.num_scheduled_tokens[rid] == depth + 1)
|
| 287 |
+
policy.observing = dict(rid=rid, accepted=0 if depth == 0 else None) if eligible else None
|
| 288 |
+
try:
|
| 289 |
+
result = update_original(self, scheduler_output, model_runner_output)
|
| 290 |
+
seen = policy.observing
|
| 291 |
+
if seen is not None and seen['accepted'] is not None and policy.pending is not None:
|
| 292 |
+
policy.pending['observation'] = (rid, seen['accepted'] + 1, depth)
|
| 293 |
+
return result
|
| 294 |
+
finally:
|
| 295 |
+
policy.observing = None
|
| 296 |
+
|
| 297 |
+
@wraps(stats_original)
|
| 298 |
+
def stats(self, spec_decoding_stats, num_draft_tokens, num_accepted_tokens,
|
| 299 |
+
num_invalid_spec_tokens, request_id):
|
| 300 |
+
result = stats_original(self, spec_decoding_stats, num_draft_tokens,
|
| 301 |
+
num_accepted_tokens, num_invalid_spec_tokens, request_id)
|
| 302 |
+
policy = getattr(self, '_ornith_c1_cost_policy', None)
|
| 303 |
+
seen = None if policy is None else policy.observing
|
| 304 |
+
if (seen is not None and request_id == seen['rid']
|
| 305 |
+
and 0 <= num_accepted_tokens <= num_draft_tokens
|
| 306 |
+
and not (num_invalid_spec_tokens or {}).get(request_id, 0)):
|
| 307 |
+
seen['accepted'] = num_accepted_tokens
|
| 308 |
+
return result
|
| 309 |
+
|
| 310 |
+
@wraps(draft_original)
|
| 311 |
+
def draft(self, draft_token_ids):
|
| 312 |
+
result = draft_original(self, draft_token_ids)
|
| 313 |
+
policy = _policy(self)
|
| 314 |
+
if policy is None:
|
| 315 |
+
return result
|
| 316 |
+
pending = policy.pending
|
| 317 |
+
policy.pending = None
|
| 318 |
+
if pending is None:
|
| 319 |
+
return result
|
| 320 |
+
observation = pending.get('observation')
|
| 321 |
+
if observation is not None:
|
| 322 |
+
rid, progressed, depth = observation
|
| 323 |
+
state = policy.requests.get(rid)
|
| 324 |
+
request = self.requests.get(rid)
|
| 325 |
+
if state is not None and request is not None and not request.is_finished():
|
| 326 |
+
state.observe((time.monotonic()-pending['started'])*1000, progressed, rid, depth, pending['future'], request.num_computed_tokens)
|
| 327 |
+
# Truncate only before reservation, retaining the already tested K0
|
| 328 |
+
# context updates and homogeneous Q8 batch path on later arrivals.
|
| 329 |
+
live = [rid for rid in pending['ids'] if rid in self.requests
|
| 330 |
+
and not self.requests[rid].is_finished()]
|
| 331 |
+
budget = policy.budget(live, self)
|
| 332 |
+
if len(live) == 1:
|
| 333 |
+
del self.requests[live[0]].spec_token_ids[budget:]
|
| 334 |
+
return result
|
| 335 |
+
|
| 336 |
+
Scheduler.schedule = schedule
|
| 337 |
+
Scheduler.update_from_output = update
|
| 338 |
+
Scheduler.make_spec_decoding_stats = stats
|
| 339 |
+
Scheduler.update_draft_token_ids = draft
|
| 340 |
+
_INSTALLED = True
|
bundle/plugin-site/ornith_g256/attention.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Reuse the retained exact-parent column kernel without its old admission campaign."""
|
| 2 |
+
# Copyright 2026 Ciru.
|
| 3 |
+
from collections import Counter
|
| 4 |
+
from vllm.v1.attention.backends.rocm_attn import RocmAttentionBackend, RocmAttentionImpl
|
| 5 |
+
from .column_backend import OrnithColumnAttentionImpl
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class OrnithG256AttentionBackend(RocmAttentionBackend):
|
| 9 |
+
@staticmethod
|
| 10 |
+
def get_name(): return 'CUSTOM'
|
| 11 |
+
@staticmethod
|
| 12 |
+
def get_impl_cls(): return OrnithG256AttentionImpl
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class OrnithG256AttentionImpl(OrnithColumnAttentionImpl):
|
| 16 |
+
def __init__(self, *args, **kwargs):
|
| 17 |
+
RocmAttentionImpl.__init__(self, *args, **kwargs)
|
| 18 |
+
self.dispatch_counts, self.capture_by_C, self.eager_by_C = Counter(), Counter(), Counter()
|
| 19 |
+
self.context_bounds = [None, None]
|
bundle/plugin-site/ornith_g256/attention_compact.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
# Copyright 2026 Ciru.
|
| 3 |
+
"""Shared dense KV scratch for target prefill with installed AMD Triton flash.
|
| 4 |
+
|
| 5 |
+
Configure once before profiling/capture. Calls must be serialized across target
|
| 6 |
+
layers, as for the existing native arena. The backend retains ownership of KV
|
| 7 |
+
writes. This module neither installs a hook nor changes decode dispatch.
|
| 8 |
+
"""
|
| 9 |
+
import ast
|
| 10 |
+
import inspect
|
| 11 |
+
import os
|
| 12 |
+
|
| 13 |
+
os.environ['FLASH_ATTENTION_TRITON_AMD_AUTOTUNE'] = '0'
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
from vllm.logger import init_logger
|
| 17 |
+
from vllm.triton_utils import tl, triton
|
| 18 |
+
|
| 19 |
+
logger = init_logger('vllm.ornith_g256.attention_compact')
|
| 20 |
+
_arena = None
|
| 21 |
+
_prefill = None
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
@triton.jit
|
| 25 |
+
def _kv_boundaries(Starts, Seq, CuK, REQUESTS: tl.constexpr, BLOCK: tl.constexpr):
|
| 26 |
+
req = tl.arange(0, BLOCK)
|
| 27 |
+
start = tl.load(Starts + req, req < REQUESTS, other=0)
|
| 28 |
+
end = tl.load(Starts + req + 1, req < REQUESTS, other=0)
|
| 29 |
+
length = tl.load(Seq + req, req < REQUESTS, other=0)
|
| 30 |
+
length = tl.where(end > start, length, 0)
|
| 31 |
+
boundaries = tl.cumsum(length, 0)
|
| 32 |
+
tl.store(CuK, 0)
|
| 33 |
+
tl.store(CuK + req + 1, boundaries, req < REQUESTS)
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
@triton.jit
|
| 37 |
+
def _gather(K, V, Table, CuK, DenseK, DenseV,
|
| 38 |
+
KBS: tl.constexpr, KH: tl.constexpr, KD: tl.constexpr,
|
| 39 |
+
KT: tl.constexpr, KX: tl.constexpr,
|
| 40 |
+
VBS: tl.constexpr, VH: tl.constexpr, VD: tl.constexpr, VT: tl.constexpr,
|
| 41 |
+
TABLE_STRIDE: tl.constexpr, TOKENS: tl.constexpr, PAGE_SIZE: tl.constexpr):
|
| 42 |
+
token = tl.program_id(0) * TOKENS + tl.arange(0, TOKENS)
|
| 43 |
+
head = tl.program_id(1)
|
| 44 |
+
req = tl.program_id(2)
|
| 45 |
+
first = tl.load(CuK + req)
|
| 46 |
+
length = tl.load(CuK + req + 1) - first
|
| 47 |
+
if tl.program_id(0) * TOKENS >= length:
|
| 48 |
+
return
|
| 49 |
+
dim = tl.arange(0, 256)
|
| 50 |
+
valid = token < length
|
| 51 |
+
block = tl.load(Table + req * TABLE_STRIDE + token // PAGE_SIZE,
|
| 52 |
+
valid, other=0).to(tl.int64)
|
| 53 |
+
within = token % PAGE_SIZE
|
| 54 |
+
k_offset = (block[:, None] * KBS + head * KH + (dim[None, :] // 8) * KD
|
| 55 |
+
+ within[:, None] * KT + (dim[None, :] % 8) * KX)
|
| 56 |
+
v_offset = (block[:, None] * VBS + head * VH + dim[None, :] * VD
|
| 57 |
+
+ within[:, None] * VT)
|
| 58 |
+
key = tl.load(K + k_offset, valid[:, None], other=0)
|
| 59 |
+
value = tl.load(V + v_offset, valid[:, None], other=0)
|
| 60 |
+
offset = ((first + token[:, None]) * 2 + head) * 256 + dim[None, :]
|
| 61 |
+
tl.store(DenseK + offset, key, valid[:, None])
|
| 62 |
+
tl.store(DenseV + offset, value, valid[:, None])
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def _graph_safe_prefill():
|
| 66 |
+
from aiter.ops.triton._triton_kernels.flash_attn_triton_amd import fwd_prefill as module
|
| 67 |
+
if module.AUTOTUNE != 'off':
|
| 68 |
+
raise RuntimeError('Set FLASH_ATTENTION_TRITON_AMD_AUTOTUNE=0 before importing AMD flash attention')
|
| 69 |
+
tree = ast.parse(inspect.getsource(module.attention_forward_prefill_triton_impl))
|
| 70 |
+
|
| 71 |
+
class RemoveDeviceBoundaryAssertions(ast.NodeTransformer):
|
| 72 |
+
removed = 0
|
| 73 |
+
|
| 74 |
+
def visit_Assert(self, node):
|
| 75 |
+
# vLLM owns/validates the GPU metadata. The four wrapper checks
|
| 76 |
+
# read cu[0]/cu[-1] on the host, preventing graph capture. Capacity
|
| 77 |
+
# tails are also legal here: cu[-1] need not equal tensor capacity.
|
| 78 |
+
if any(isinstance(child, ast.Subscript)
|
| 79 |
+
and isinstance(child.value, ast.Name)
|
| 80 |
+
and child.value.id in ('cu_seqlens_q', 'cu_seqlens_k')
|
| 81 |
+
for child in ast.walk(node.test)):
|
| 82 |
+
self.removed += 1
|
| 83 |
+
return None
|
| 84 |
+
return node
|
| 85 |
+
|
| 86 |
+
transform = RemoveDeviceBoundaryAssertions()
|
| 87 |
+
tree = transform.visit(tree)
|
| 88 |
+
if transform.removed != 4:
|
| 89 |
+
raise RuntimeError('Unsupported AMD flash varlen boundary assertions')
|
| 90 |
+
namespace = dict(vars(module))
|
| 91 |
+
exec(compile(ast.fix_missing_locations(tree), __file__ + ':varlen', 'exec'), namespace)
|
| 92 |
+
return namespace['attention_forward_prefill_triton_impl']
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def configure(*, max_num_seqs, max_model_len, max_num_batched_tokens, device,
|
| 96 |
+
iu4_prefill_library=None):
|
| 97 |
+
"""Allocate one reusable arena, returning its accounted tensor bytes."""
|
| 98 |
+
global _arena, _prefill
|
| 99 |
+
device = torch.device(device)
|
| 100 |
+
if device.index is None:
|
| 101 |
+
device = torch.device('cuda', torch.cuda.current_device())
|
| 102 |
+
library = (os.path.realpath(os.path.expanduser(os.fspath(iu4_prefill_library)))
|
| 103 |
+
if iu4_prefill_library is not None else None)
|
| 104 |
+
identity = (max_num_seqs, max_model_len, max_num_batched_tokens, device)
|
| 105 |
+
if _arena is not None:
|
| 106 |
+
if _arena['identity'] != identity or _arena['iu4_library'] != library:
|
| 107 |
+
raise RuntimeError('Compact attention arena was configured differently')
|
| 108 |
+
return _arena['bytes']
|
| 109 |
+
if (not 1 <= max_num_seqs <= 8 or not 1 <= max_model_len <= 262144
|
| 110 |
+
or not 1 <= max_num_batched_tokens <= 2048
|
| 111 |
+
or torch.cuda.is_current_stream_capturing()):
|
| 112 |
+
raise ValueError('Configure target compact prefill before capture, within C8/256K/2048 tokens')
|
| 113 |
+
_prefill = _graph_safe_prefill()
|
| 114 |
+
key = torch.empty((max_num_seqs * max_model_len, 2, 256),
|
| 115 |
+
dtype=torch.bfloat16, device=device)
|
| 116 |
+
value = torch.empty_like(key)
|
| 117 |
+
cu_k = torch.empty(max_num_seqs + 1, dtype=torch.int32, device=device)
|
| 118 |
+
lse = torch.empty((16, max_num_batched_tokens), dtype=torch.float32, device=device)
|
| 119 |
+
tensors = (key, value, cu_k, lse)
|
| 120 |
+
iu4 = None
|
| 121 |
+
if library is not None:
|
| 122 |
+
from .attention_iu4 import NativeAttention
|
| 123 |
+
iu4 = NativeAttention(device, library, max_model_len=max_model_len)
|
| 124 |
+
size = sum(t.numel() * t.element_size() for t in tensors)
|
| 125 |
+
if iu4 is not None:
|
| 126 |
+
size += iu4.bytes
|
| 127 |
+
_arena = dict(identity=identity, key=key, value=value, cu_k=cu_k, lse=lse,
|
| 128 |
+
bytes=size, iu4=iu4, iu4_library=library)
|
| 129 |
+
return size
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def try_forward(query, key_cache, value_cache, output, block_table,
|
| 133 |
+
query_start_loc, seq_lens, *, max_query_len, max_seq_len, sm_scale):
|
| 134 |
+
"""Return False for an unsupported call; otherwise write output and return True.
|
| 135 |
+
|
| 136 |
+
Supports variable-length C1..C8 prefill, mixed small queries, empty request
|
| 137 |
+
slots and trailing graph token padding. CUDA metadata stays on the GPU.
|
| 138 |
+
Target eligibility (causal, no window/sinks/bias/output scaling) is the
|
| 139 |
+
caller's responsibility. The native cache writer must have run first.
|
| 140 |
+
"""
|
| 141 |
+
page_size = key_cache.shape[3] if key_cache.ndim == 5 else 0
|
| 142 |
+
if (max_query_len <= 8 or query.ndim != 3 or query.shape[1:] != (16, 256)
|
| 143 |
+
or page_size not in (1120, 2240) or key_cache.shape[1:] != (2, 32, page_size, 8)
|
| 144 |
+
or value_cache.ndim != 4 or value_cache.shape[1:] != (2, 256, page_size)
|
| 145 |
+
or output.shape != query.shape or query.stride(2) != 1
|
| 146 |
+
or output.stride(2) != 1 or query_start_loc.ndim != 1
|
| 147 |
+
or seq_lens.ndim != 1 or block_table.ndim != 2
|
| 148 |
+
or block_table.stride(1) != 1
|
| 149 |
+
or not query_start_loc.is_contiguous() or not seq_lens.is_contiguous()
|
| 150 |
+
or query_start_loc.numel() != seq_lens.numel() + 1
|
| 151 |
+
or block_table.shape[0] < seq_lens.numel()
|
| 152 |
+
or block_table.shape[1] * page_size < max_seq_len
|
| 153 |
+
or any(t.dtype != torch.bfloat16 for t in (query, key_cache, value_cache, output))
|
| 154 |
+
or any(t.dtype != torch.int32 for t in (block_table, query_start_loc, seq_lens))
|
| 155 |
+
or any(t.device != query.device for t in (key_cache, value_cache, output,
|
| 156 |
+
block_table, query_start_loc, seq_lens))):
|
| 157 |
+
return False
|
| 158 |
+
if _arena is None:
|
| 159 |
+
raise RuntimeError('Configure compact attention before memory profiling')
|
| 160 |
+
max_reqs, max_length, max_tokens, device = _arena['identity']
|
| 161 |
+
requests, rows = seq_lens.numel(), query.shape[0]
|
| 162 |
+
if (not 1 <= requests <= max_reqs or not 1 <= max_seq_len <= max_length
|
| 163 |
+
or not 1 <= rows <= max_tokens or query.device != device):
|
| 164 |
+
return False
|
| 165 |
+
# Capacity-sized views avoid reading the final cumulative GPU length.
|
| 166 |
+
# The original flash kernel bounds every request by cu_q/cu_k instead.
|
| 167 |
+
key = _arena['key'][:requests * max_seq_len]
|
| 168 |
+
value = _arena['value'][:requests * max_seq_len]
|
| 169 |
+
cu_k = _arena['cu_k'][:requests + 1]
|
| 170 |
+
lse = _arena['lse'][:, :rows]
|
| 171 |
+
_kv_boundaries[(1,)](query_start_loc, seq_lens, cu_k, REQUESTS=requests,
|
| 172 |
+
BLOCK=triton.next_power_of_2(requests), num_warps=1)
|
| 173 |
+
_gather[(triton.cdiv(max_seq_len, 32), 2, requests)](
|
| 174 |
+
key_cache, value_cache, block_table, cu_k, key, value,
|
| 175 |
+
*key_cache.stride(), *value_cache.stride(),
|
| 176 |
+
TABLE_STRIDE=block_table.stride(0), TOKENS=32, PAGE_SIZE=page_size,
|
| 177 |
+
num_warps=8, num_stages=1)
|
| 178 |
+
# Graph-padded rows are not real requests and receive defined zero output.
|
| 179 |
+
output.zero_()
|
| 180 |
+
if (_arena['iu4'] is not None and requests == 1 and max_query_len == 1120
|
| 181 |
+
and max_seq_len >= 4096 and max_seq_len % 32 == 0
|
| 182 |
+
and max_query_len <= rows and sm_scale == 256**-.5):
|
| 183 |
+
_arena['iu4'].forward(query, key, value, output, query_start_loc,
|
| 184 |
+
seq_lens, max_seq_len)
|
| 185 |
+
logger.info_once('Ornith C1 IU4 prefill active: BF16 cache gather + '
|
| 186 |
+
'normalized Q/K H256 and P/V H32, signed IU4 QK/PV; '
|
| 187 |
+
'Q1120 and aligned K>=4096, existing BF16 fallback elsewhere')
|
| 188 |
+
return True
|
| 189 |
+
_prefill(q=query, k=key, v=value, o=output, softmax_lse=lse,
|
| 190 |
+
sd_mask=None, sm_scale=sm_scale, alibi_slopes=None, causal=True,
|
| 191 |
+
window_size_left=-1, window_size_right=-1, bias=None, layout='thd',
|
| 192 |
+
cu_seqlens_q=query_start_loc, cu_seqlens_k=cu_k,
|
| 193 |
+
# Varlen reads actual Q/K lengths from cu_q/cu_k. These are only
|
| 194 |
+
# launch bounds (Q also sets the rectangular grid), yet upstream
|
| 195 |
+
# specializes both as constexpr. Stable arena bounds avoid compiling
|
| 196 |
+
# a new flash kernel at every growing-context prefill chunk. Empty Q
|
| 197 |
+
# tiles return before the attention loop in the installed kernel.
|
| 198 |
+
max_seqlens_q=max_tokens, max_seqlens_k=max_length,
|
| 199 |
+
dropout_p=0.0, philox_seed=0, philox_offset=0,
|
| 200 |
+
return_scores=False, use_exp2=True,
|
| 201 |
+
q_descale=None, k_descale=None, v_descale=None)
|
| 202 |
+
logger.info_once('Ornith compact target prefill active: shared KV gather + AMD '
|
| 203 |
+
'Triton varlen flash; <=8-query decode unchanged')
|
| 204 |
+
return True
|
bundle/plugin-site/ornith_g256/attention_fast.py
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Optional retained FP32 segmented decode; stock ROCm handles multiple queries."""
|
| 2 |
+
# Copyright 2026 Ciru.
|
| 3 |
+
import ctypes
|
| 4 |
+
from collections import Counter
|
| 5 |
+
from types import SimpleNamespace
|
| 6 |
+
|
| 7 |
+
import torch
|
| 8 |
+
from vllm.config import get_current_vllm_config
|
| 9 |
+
from vllm.v1.attention.backend import AttentionType
|
| 10 |
+
from vllm.v1.attention.backends.rocm_attn import RocmAttentionBackend, RocmAttentionImpl
|
| 11 |
+
from vllm.v1.attention.ops.paged_attn import PagedAttention
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class PagedLayout(ctypes.Structure):
|
| 15 |
+
_fields_ = [('bytes', ctypes.c_size_t), ('valid_lengths', ctypes.c_size_t),
|
| 16 |
+
('partial', ctypes.c_size_t), ('segments', ctypes.c_int)]
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class PagedStrides(ctypes.Structure):
|
| 20 |
+
_fields_ = [(name, ctypes.c_int64) for name in
|
| 21 |
+
('q0', 'q1', 'k0', 'k1', 'k2', 'k3', 'k4', 'v0', 'v1', 'v2', 'v3', 'table0')]
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
class NativeFastFP32:
|
| 25 |
+
"""ABI1 binding only: same library, strides and scratch layout as paged_torch."""
|
| 26 |
+
def __init__(self, library, *, Ccap, Lcap, device):
|
| 27 |
+
if not 1 <= Ccap <= 8 or not 1 <= Lcap <= 8192:
|
| 28 |
+
raise ValueError('FP32 paged decode supports <=8 sequences and <=8192 context')
|
| 29 |
+
self.Ccap, self.Lcap, self.device = Ccap, Lcap, torch.device(device)
|
| 30 |
+
self.library = str(library)
|
| 31 |
+
self.lib = ctypes.CDLL(self.library)
|
| 32 |
+
self.lib.ornith_paged_abi_version.argtypes = []
|
| 33 |
+
self.lib.ornith_paged_abi_version.restype = ctypes.c_uint32
|
| 34 |
+
if self.lib.ornith_paged_abi_version() != 1:
|
| 35 |
+
raise ValueError('Expected retained paged attention ABI1')
|
| 36 |
+
self.lib.ornith_paged_get_layout.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.POINTER(PagedLayout)]
|
| 37 |
+
self.lib.ornith_paged_get_layout.restype = ctypes.c_int
|
| 38 |
+
self.lib.ornith_paged_launch.argtypes = ([ctypes.c_void_p] * 6 + [ctypes.c_size_t]
|
| 39 |
+
+ [ctypes.c_void_p] * 3 + [ctypes.c_int] * 4 + [PagedStrides, ctypes.c_void_p])
|
| 40 |
+
self.lib.ornith_paged_launch.restype = ctypes.c_int
|
| 41 |
+
layout = PagedLayout()
|
| 42 |
+
if self.lib.ornith_paged_get_layout(Ccap, Lcap, ctypes.byref(layout)):
|
| 43 |
+
raise ValueError('Paged attention workspace query failed')
|
| 44 |
+
self.workspace_bytes = layout.bytes
|
| 45 |
+
self.output_f32_offset = (layout.bytes + 255) // 256 * 256
|
| 46 |
+
self.arena_bytes = self.output_f32_offset + Ccap * 16 * 256 * 4
|
| 47 |
+
|
| 48 |
+
def bind_arena(self, arena):
|
| 49 |
+
if (arena.dtype != torch.uint8 or arena.ndim != 1 or not arena.is_contiguous()
|
| 50 |
+
or arena.numel() < self.arena_bytes or arena.data_ptr() % 256
|
| 51 |
+
or arena.device != self.device):
|
| 52 |
+
raise ValueError('Invalid shared attention arena')
|
| 53 |
+
output = arena[self.output_f32_offset:self.arena_bytes].view(torch.float32).view(self.Ccap, 16, 256)
|
| 54 |
+
return SimpleNamespace(workspace=arena[:self.workspace_bytes],
|
| 55 |
+
outputs=tuple(output[:c] for c in range(self.Ccap + 1)))
|
| 56 |
+
|
| 57 |
+
def launch_out(self, query, key, value, table, lengths, buffers, flag, output):
|
| 58 |
+
strides = PagedStrides(*query.stride()[:2], *key.stride(), *value.stride(), table.stride(0))
|
| 59 |
+
stream = torch.cuda.current_stream(query.device)
|
| 60 |
+
status = self.lib.ornith_paged_launch(
|
| 61 |
+
*[tensor.data_ptr() for tensor in (query, key, value, table, lengths, buffers.workspace)],
|
| 62 |
+
buffers.workspace.numel(), buffers.outputs[query.shape[0]].data_ptr(), output.data_ptr(), flag.data_ptr(),
|
| 63 |
+
query.shape[0], self.Ccap, self.Lcap, key.shape[0], strides, stream.cuda_stream)
|
| 64 |
+
if status:
|
| 65 |
+
raise RuntimeError(f'FP32 paged attention launch failed: {status}')
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class OrnithG256SelectableAttentionBackend(RocmAttentionBackend):
|
| 69 |
+
@staticmethod
|
| 70 |
+
def get_name(): return 'CUSTOM'
|
| 71 |
+
|
| 72 |
+
@staticmethod
|
| 73 |
+
def get_impl_cls():
|
| 74 |
+
settings = get_current_vllm_config().additional_config.get('ornith_g256', {})
|
| 75 |
+
mode = settings.get('attention_mode', 'column')
|
| 76 |
+
if mode == 'stock_rocm':
|
| 77 |
+
return RocmAttentionImpl
|
| 78 |
+
if mode == 'column':
|
| 79 |
+
from .attention import OrnithG256AttentionImpl
|
| 80 |
+
return OrnithG256AttentionImpl
|
| 81 |
+
if mode == 'fast_fp32':
|
| 82 |
+
if not settings.get('attention_library'):
|
| 83 |
+
raise ValueError('fast_fp32 requires additional_config.ornith_g256.attention_library')
|
| 84 |
+
return OrnithG256FastAttentionImpl
|
| 85 |
+
raise ValueError(f'Unknown ornith_g256 attention_mode: {mode}')
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class OrnithG256FastAttentionImpl(RocmAttentionImpl):
|
| 89 |
+
implementation = 'ciru.ornith.g256.fast_fp32.abi1'
|
| 90 |
+
|
| 91 |
+
def __init__(self, *args, **kwargs):
|
| 92 |
+
super().__init__(*args, **kwargs)
|
| 93 |
+
self._ornith_fast = None
|
| 94 |
+
self._ornith_verify = None
|
| 95 |
+
self.dispatch_counts = Counter()
|
| 96 |
+
self.capture_by_C, self.eager_by_C = Counter(), Counter()
|
| 97 |
+
self.context_bounds = [None, None]
|
| 98 |
+
|
| 99 |
+
def static_native_support(self):
|
| 100 |
+
return (self.attn_type == AttentionType.DECODER
|
| 101 |
+
and (self.num_heads, self.num_kv_heads, self.head_size) == (16, 2, 256)
|
| 102 |
+
and self.scale == 0.0625 and self.kv_cache_dtype in ('auto', 'bfloat16')
|
| 103 |
+
and self.alibi_slopes is None and self.sliding_window == (-1, -1)
|
| 104 |
+
and self.logits_soft_cap == 0 and self.sinks is None
|
| 105 |
+
and self.kv_sharing_target_layer_name is None)
|
| 106 |
+
|
| 107 |
+
def bind_native(self, backend, buffers, flags, slot, prefix):
|
| 108 |
+
if self._ornith_fast is not None:
|
| 109 |
+
raise RuntimeError('Attention storage already bound')
|
| 110 |
+
if not self.static_native_support():
|
| 111 |
+
raise ValueError('Unsupported Ornith FP32 attention geometry')
|
| 112 |
+
if (flags.dtype != torch.int32 or flags.ndim != 1 or flags.device != backend.device
|
| 113 |
+
or not 0 <= slot < flags.numel()):
|
| 114 |
+
raise ValueError('Invalid attention flag slot')
|
| 115 |
+
self._ornith_fast = (backend, buffers, flags[slot:slot + 1], slot, str(prefix))
|
| 116 |
+
|
| 117 |
+
def bind_verify(self, backend, buffers):
|
| 118 |
+
if self._ornith_fast is None or self._ornith_verify is not None:
|
| 119 |
+
raise RuntimeError('Bind verification once after normal attention storage')
|
| 120 |
+
self._ornith_verify = (backend, buffers)
|
| 121 |
+
|
| 122 |
+
def _try_verify(self, query, kv_cache, m, output, output_scale, output_block_scale):
|
| 123 |
+
if (self._ornith_verify is None or m is None or not self.static_native_support()
|
| 124 |
+
or m.use_cascade or m.causal is not True or output_scale is not None
|
| 125 |
+
or output_block_scale is not None or not 1 <= m.max_query_len <= 8):
|
| 126 |
+
return False
|
| 127 |
+
backend, buffers = self._ornith_verify
|
| 128 |
+
count, sequences = query.shape[0], m.seq_lens.shape[0]
|
| 129 |
+
if (not 1 <= count <= backend.Ccap or not 1 <= sequences <= 8
|
| 130 |
+
or m.max_seq_len > backend.Lcap or query.shape != (count, 16, 256)
|
| 131 |
+
or output.shape != query.shape or not 0 <= m.num_actual_tokens <= count
|
| 132 |
+
or m.block_table.shape[0] != sequences
|
| 133 |
+
or m.query_start_loc.shape != (sequences + 1,)
|
| 134 |
+
or m.block_table.shape[1] < backend.table_cols
|
| 135 |
+
or query.dtype != torch.bfloat16 or output.dtype != torch.bfloat16
|
| 136 |
+
or kv_cache.dtype != torch.bfloat16 or m.block_table.dtype != torch.int32
|
| 137 |
+
or m.seq_lens.dtype != torch.int32 or m.query_start_loc.dtype != torch.int32
|
| 138 |
+
or query.stride(2) != 1 or not output.is_contiguous()
|
| 139 |
+
or m.block_table.stride(1) != 1 or not m.seq_lens.is_contiguous()
|
| 140 |
+
or not m.query_start_loc.is_contiguous()):
|
| 141 |
+
return False
|
| 142 |
+
kcache, vcache = PagedAttention.split_kv_cache(kv_cache.transpose(0, 1), 2, 256)
|
| 143 |
+
if kcache.shape[1:] != (2, 32, 1104, 8) or vcache.shape[1:] != (2, 256, 1104):
|
| 144 |
+
return False
|
| 145 |
+
# The inherited stock do_kv_cache_update executes before this attention
|
| 146 |
+
# operation. GPU query lengths mask the newly written future positions.
|
| 147 |
+
backend.launch_out(query, kcache, vcache, m.block_table, m.seq_lens,
|
| 148 |
+
m.query_start_loc, m.num_actual_tokens, buffers,
|
| 149 |
+
self._ornith_fast[2], output)
|
| 150 |
+
capturing = torch.cuda.is_current_stream_capturing()
|
| 151 |
+
self.dispatch_counts['capture_verify_calls' if capturing else 'eager_verify_calls'] += 1
|
| 152 |
+
self.dispatch_counts['verify_calls'] += 1
|
| 153 |
+
(self.capture_by_C if capturing else self.eager_by_C)[str(count)] += 1
|
| 154 |
+
return True
|
| 155 |
+
|
| 156 |
+
def forward(self, layer, query, key, value, kv_cache, attn_metadata, output,
|
| 157 |
+
output_scale=None, output_block_scale=None):
|
| 158 |
+
m = attn_metadata
|
| 159 |
+
if self._try_verify(query, kv_cache, m, output, output_scale, output_block_scale):
|
| 160 |
+
return output
|
| 161 |
+
reason = None
|
| 162 |
+
if m is None:
|
| 163 |
+
reason = 'profile'
|
| 164 |
+
elif not self.static_native_support():
|
| 165 |
+
reason = 'static_feature'
|
| 166 |
+
elif (m.use_cascade or m.causal is not True or output_scale is not None
|
| 167 |
+
or output_block_scale is not None):
|
| 168 |
+
reason = 'metadata_feature'
|
| 169 |
+
elif m.max_query_len != 1:
|
| 170 |
+
reason = 'prefill_or_multiquery'
|
| 171 |
+
elif self._ornith_fast is None:
|
| 172 |
+
raise RuntimeError('FP32 decode reached an unbound target attention layer')
|
| 173 |
+
else:
|
| 174 |
+
backend, buffers, flag, _, _ = self._ornith_fast
|
| 175 |
+
count = m.seq_lens.shape[0]
|
| 176 |
+
if (not 1 <= count <= backend.Ccap or m.max_seq_len > backend.Lcap
|
| 177 |
+
or query.shape != (count, 16, 256) or output.shape != query.shape
|
| 178 |
+
or not 0 < m.num_actual_tokens <= count
|
| 179 |
+
or m.block_table.shape[0] != count or m.query_start_loc.shape != (count + 1,)
|
| 180 |
+
or m.block_table.shape[1] < (backend.Lcap + 1055) // 1056):
|
| 181 |
+
reason = 'capacity_or_query_mapping'
|
| 182 |
+
elif (query.dtype != torch.bfloat16 or output.dtype != torch.bfloat16
|
| 183 |
+
or kv_cache.dtype != torch.bfloat16 or m.block_table.dtype != torch.int32
|
| 184 |
+
or m.seq_lens.dtype != torch.int32 or m.query_start_loc.dtype != torch.int32):
|
| 185 |
+
reason = 'dtype'
|
| 186 |
+
elif (query.stride(2) != 1 or not output.is_contiguous()
|
| 187 |
+
or m.block_table.stride(1) != 1 or not m.seq_lens.is_contiguous()
|
| 188 |
+
or not m.query_start_loc.is_contiguous()):
|
| 189 |
+
reason = 'stride'
|
| 190 |
+
else:
|
| 191 |
+
kcache, vcache = PagedAttention.split_kv_cache(kv_cache.transpose(0, 1), 2, 256)
|
| 192 |
+
if kcache.shape[1:] != (2, 32, 1056, 8) or vcache.shape[1:] != (2, 256, 1056):
|
| 193 |
+
reason = 'cache_page_shape'
|
| 194 |
+
else:
|
| 195 |
+
# The existing opaque Attention op owns this current-stream
|
| 196 |
+
# launch and the preceding stock cache update. No host KV read.
|
| 197 |
+
backend.launch_out(query, kcache, vcache, m.block_table, m.seq_lens,
|
| 198 |
+
buffers, flag, output)
|
| 199 |
+
capturing = torch.cuda.is_current_stream_capturing()
|
| 200 |
+
self.dispatch_counts['capture_native_calls' if capturing else 'eager_native_calls'] += 1
|
| 201 |
+
(self.capture_by_C if capturing else self.eager_by_C)[str(count)] += 1
|
| 202 |
+
self.dispatch_counts['native_calls'] += 1
|
| 203 |
+
lo, hi = self.context_bounds
|
| 204 |
+
self.context_bounds = [m.max_seq_len if lo is None else min(lo, m.max_seq_len),
|
| 205 |
+
m.max_seq_len if hi is None else max(hi, m.max_seq_len)]
|
| 206 |
+
return output
|
| 207 |
+
self.dispatch_counts['fallback_' + reason] += 1
|
| 208 |
+
return super().forward(layer, query, key, value, kv_cache, m, output,
|
| 209 |
+
output_scale, output_block_scale)
|
| 210 |
+
|
| 211 |
+
def inspect_dispatch(self, prefix=None):
|
| 212 |
+
bound = self._ornith_fast
|
| 213 |
+
return dict(implementation=self.implementation, bound=bound is not None,
|
| 214 |
+
prefix=prefix or (bound[4] if bound else None), flag_slot=bound[3] if bound else None,
|
| 215 |
+
counts=dict(self.dispatch_counts), capture_by_C=dict(self.capture_by_C),
|
| 216 |
+
eager_by_C=dict(self.eager_by_C), native_max_seq_len_host_bounds=list(self.context_bounds))
|
bundle/plugin-site/ornith_g256/attention_folded.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
# Copyright 2026 Ciru. Optional native query/GQA folding.
|
| 3 |
+
# Generated arithmetic retains vLLM/SGLang Apache-2.0 code: Copyright
|
| 4 |
+
# contributors to the vLLM project; Copyright2025 vLLM Team;
|
| 5 |
+
# Copyright2023-2024 SGLang Team. See installed triton_decode_attention.py.
|
| 6 |
+
"""Optional native K/V sharing in eight-query tiles times eight GQA heads.
|
| 7 |
+
|
| 8 |
+
No installation hook: callers explicitly select this operation.
|
| 9 |
+
"""
|
| 10 |
+
from functools import lru_cache
|
| 11 |
+
import inspect
|
| 12 |
+
import linecache
|
| 13 |
+
|
| 14 |
+
import torch
|
| 15 |
+
from vllm.triton_utils import tl, triton
|
| 16 |
+
from . import attention_partition as current
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@triton.jit
|
| 20 |
+
def _reduction_lengths(Starts, Seq, RowSeq,
|
| 21 |
+
REQUESTS: tl.constexpr, REQUEST_BLOCK: tl.constexpr):
|
| 22 |
+
row = tl.program_id(0)
|
| 23 |
+
req = tl.arange(0, REQUEST_BLOCK)
|
| 24 |
+
first = tl.load(Starts + req, req < REQUESTS, other=0)
|
| 25 |
+
end = tl.load(Starts + req + 1, req < REQUESTS, other=0)
|
| 26 |
+
owns = (req < REQUESTS) & (row >= first) & (row < end)
|
| 27 |
+
owner = tl.max(tl.where(owns, req + 1, 0), 0) - 1
|
| 28 |
+
seq = tl.load(Seq + owner, owner >= 0, other=0)
|
| 29 |
+
# All queries share the request's split boundaries. Causality is applied
|
| 30 |
+
# per row in stage1, rather than changing which partials stage2 visits.
|
| 31 |
+
tl.store(RowSeq + row, seq)
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
@lru_cache(maxsize=2)
|
| 35 |
+
def _kernel(query_tiles=1):
|
| 36 |
+
if query_tiles not in (1, 2):
|
| 37 |
+
raise ValueError('Folded attention supports one or two eight-query tiles')
|
| 38 |
+
native, reduce = current._kernels()
|
| 39 |
+
source = inspect.getsource(native.fn)
|
| 40 |
+
replacements = {
|
| 41 |
+
'def _fwd_grouped_kernel_stage1(': 'def _folded_query_stage1(',
|
| 42 |
+
' B_Seqlen,\n': ' B_Seqlen,\n Query_Start,\n',
|
| 43 |
+
''' cur_head_id = tl.program_id(1)
|
| 44 |
+
cur_kv_head = cur_head_id // tl.cdiv(kv_group_num, BLOCK_H)
|
| 45 |
+
split_kv_id = tl.program_id(2)
|
| 46 |
+
|
| 47 |
+
VALID_BLOCK_H: tl.constexpr = BLOCK_H if kv_group_num > BLOCK_H else kv_group_num
|
| 48 |
+
cur_head = cur_head_id * VALID_BLOCK_H + tl.arange(0, BLOCK_H)
|
| 49 |
+
mask_h = cur_head < (cur_head_id + 1) * VALID_BLOCK_H
|
| 50 |
+
mask_h = mask_h & (cur_head < q_head_num)''':
|
| 51 |
+
''' cur_kv_head = tl.program_id(1)
|
| 52 |
+
split_kv_id = tl.program_id(2)
|
| 53 |
+
query_first = tl.load(Query_Start + cur_batch)
|
| 54 |
+
query_end = tl.load(Query_Start + cur_batch + 1)
|
| 55 |
+
query_count = query_end - query_first
|
| 56 |
+
if query_count <= 0:
|
| 57 |
+
return
|
| 58 |
+
offs_m = tl.arange(0, BLOCK_H)
|
| 59 |
+
cur_query = query_first + offs_m // 8
|
| 60 |
+
cur_head = cur_kv_head * 8 + offs_m % 8
|
| 61 |
+
mask_h = offs_m // 8 < query_count''',
|
| 62 |
+
' cur_batch_seq_len = tl.load(B_Seqlen + cur_batch)\n':
|
| 63 |
+
' cur_batch_seq_len = tl.load(B_Seqlen + cur_batch)\n'
|
| 64 |
+
' causal_len = cur_batch_seq_len - query_count + offs_m // 8 + 1\n',
|
| 65 |
+
'cur_batch * stride_qbs + cur_head[:, None] * stride_qh + offs_d[None, :]':
|
| 66 |
+
'cur_query[:, None] * stride_qbs + cur_head[:, None] * stride_qh + offs_d[None, :]',
|
| 67 |
+
'mask_h[:, None] & (offs_n[None, :] < split_kv_end), qk, float("-inf")':
|
| 68 |
+
'mask_h[:, None] & (offs_n[None, :] < split_kv_end) & (offs_n[None, :] < causal_len[:, None]), qk, float("-inf")',
|
| 69 |
+
'cur_batch * stride_mid_ob\n + cur_head[:, None] * stride_mid_oh':
|
| 70 |
+
'cur_query[:, None] * stride_mid_ob\n + cur_head[:, None] * stride_mid_oh',
|
| 71 |
+
'cur_batch * stride_mid_ob\n + cur_head * stride_mid_oh':
|
| 72 |
+
'cur_query * stride_mid_ob\n + cur_head * stride_mid_oh',
|
| 73 |
+
' acc / e_sum[:, None],':
|
| 74 |
+
' tl.where(causal_len[:, None] > split_kv_start, acc / e_sum[:, None], 0.),',
|
| 75 |
+
' e_max + tl.log(e_sum),':
|
| 76 |
+
' tl.where(causal_len > split_kv_start, e_max + tl.log(e_sum), -float("inf")),',
|
| 77 |
+
}
|
| 78 |
+
for old, new in replacements.items():
|
| 79 |
+
if source.count(old) != 1:
|
| 80 |
+
raise RuntimeError('Unsupported folded-query source anchor: ' + old[:80])
|
| 81 |
+
source = source.replace(old, new)
|
| 82 |
+
kernel_name = '_folded_query_stage1'
|
| 83 |
+
if query_tiles == 2:
|
| 84 |
+
# Keep the original <=8-query specialization byte-for-byte. For up to
|
| 85 |
+
# sixteen queries, each request has two otherwise identical 64-row
|
| 86 |
+
# tiles. query_count remains the TOTAL request query count: shrinking
|
| 87 |
+
# it to the tile size would shift the causal positions of tile zero.
|
| 88 |
+
tiled_replacements = {
|
| 89 |
+
' cur_batch = tl.program_id(0)\n':
|
| 90 |
+
' cur_batch = tl.program_id(0) // 2\n'
|
| 91 |
+
' query_tile = tl.program_id(0) % 2\n',
|
| 92 |
+
' if query_count <= 0:\n':
|
| 93 |
+
' if query_count <= query_tile * 8:\n',
|
| 94 |
+
' cur_query = query_first + offs_m // 8\n':
|
| 95 |
+
' cur_query = query_first + query_tile * 8 + offs_m // 8\n',
|
| 96 |
+
' mask_h = offs_m // 8 < query_count\n':
|
| 97 |
+
' mask_h = query_tile * 8 + offs_m // 8 < query_count\n',
|
| 98 |
+
' causal_len = cur_batch_seq_len - query_count + offs_m // 8 + 1\n':
|
| 99 |
+
' causal_len = cur_batch_seq_len - query_count + query_tile * 8 + offs_m // 8 + 1\n',
|
| 100 |
+
'def _folded_query_stage1(': 'def _folded_query_tiles_stage1(',
|
| 101 |
+
}
|
| 102 |
+
for old, new in tiled_replacements.items():
|
| 103 |
+
if source.count(old) != 1:
|
| 104 |
+
raise RuntimeError('Unsupported folded query-tile source anchor: ' + old[:80])
|
| 105 |
+
source = source.replace(old, new)
|
| 106 |
+
kernel_name = '_folded_query_tiles_stage1'
|
| 107 |
+
filename = __file__ + ('.generated' if query_tiles == 1 else '.query_tiles.generated')
|
| 108 |
+
linecache.cache[filename] = (len(source), None, source.splitlines(True), filename)
|
| 109 |
+
namespace = dict(native.fn.__globals__)
|
| 110 |
+
exec(compile(source, filename, 'exec'), namespace)
|
| 111 |
+
return namespace[kernel_name], reduce
|
| 112 |
+
|
| 113 |
+
|
| 114 |
+
def forward(query, key_cache, value_cache, output, block_table, query_start_loc,
|
| 115 |
+
seq_lens, sm_scale, k_scale, v_scale, *, max_query_len=8):
|
| 116 |
+
"""Match attention_partition.forward, with native current KV prewritten.
|
| 117 |
+
|
| 118 |
+
Caller eligibility: causal target attention, no sinks/alibi/window/output
|
| 119 |
+
scaling, at most8 requests and128 total rows, max_query_len<=16. Query
|
| 120 |
+
starts include empty slots; trailing padded rows receive zero outputs.
|
| 121 |
+
Tensor metadata is trusted as in the ROCm backend; no host reads.
|
| 122 |
+
Scratch remains [query,16,32,257] FP32 plus LSE and per-query reducer extent.
|
| 123 |
+
This is R*526404 bytes and does not copy the block table or KV pool.
|
| 124 |
+
"""
|
| 125 |
+
page_size = key_cache.shape[3] if key_cache.ndim == 5 else 0
|
| 126 |
+
if (query.ndim != 3 or query.shape[1:] != (16, 256)
|
| 127 |
+
or page_size not in (1120, 2240) or key_cache.shape[1:] != (2, 32, page_size, 8)
|
| 128 |
+
or value_cache.ndim != 4 or value_cache.shape[1:] != (2, 256, page_size)
|
| 129 |
+
or key_cache.shape[0] != value_cache.shape[0]
|
| 130 |
+
or key_cache.shape[0] == 0 or output.shape != query.shape):
|
| 131 |
+
raise ValueError("Folded attention requires target H16/KV2/D256/page1120 or2240")
|
| 132 |
+
if (not 1 <= max_query_len <= 16 or query.shape[0] > 128
|
| 133 |
+
or not 1 <= seq_lens.numel() <= 8
|
| 134 |
+
or block_table.ndim != 2 or block_table.shape[0] < seq_lens.numel()
|
| 135 |
+
or block_table.shape[1] < 1 or query_start_loc.numel() != seq_lens.numel() + 1
|
| 136 |
+
or not seq_lens.is_contiguous() or not query_start_loc.is_contiguous()
|
| 137 |
+
or block_table.stride(1) != 1 or query.stride(2) != 1 or output.stride(2) != 1):
|
| 138 |
+
raise ValueError("Unsupported query metadata or noncontiguous inner dimensions")
|
| 139 |
+
if (not query.is_cuda or any(t.device != query.device for t in
|
| 140 |
+
(key_cache, value_cache, output, block_table, query_start_loc, seq_lens, k_scale, v_scale))
|
| 141 |
+
or any(t.dtype != torch.bfloat16 for t in (query, key_cache, value_cache, output))
|
| 142 |
+
or any(t.dtype not in (torch.int32, torch.int64) for t in
|
| 143 |
+
(block_table, query_start_loc, seq_lens))):
|
| 144 |
+
raise ValueError("Partitioned attention requires BF16 and integer metadata on one GPU")
|
| 145 |
+
rows = query.shape[0]
|
| 146 |
+
if not rows:
|
| 147 |
+
return output
|
| 148 |
+
query_tiles = 1 if max_query_len <= 8 else 2
|
| 149 |
+
stage1, reduce = _kernel(query_tiles)
|
| 150 |
+
row_seq = torch.empty((rows,), dtype=torch.int32, device=query.device)
|
| 151 |
+
_reduction_lengths[(rows,)](query_start_loc, seq_lens, row_seq,
|
| 152 |
+
REQUESTS=seq_lens.numel(), REQUEST_BLOCK=triton.next_power_of_2(seq_lens.numel()),
|
| 153 |
+
num_warps=4)
|
| 154 |
+
splits = 32
|
| 155 |
+
logits = torch.empty((rows, 16, splits, 257), dtype=torch.float32, device=query.device)
|
| 156 |
+
lse = torch.empty((rows, 16), dtype=torch.float32, device=query.device)
|
| 157 |
+
stage1[(seq_lens.numel() * query_tiles, 2, splits)](
|
| 158 |
+
query, key_cache, value_cache, sm_scale, block_table, seq_lens, query_start_loc, logits,
|
| 159 |
+
block_table.stride(0), query.stride(0), query.stride(1),
|
| 160 |
+
key_cache.stride(0), key_cache.stride(3), key_cache.stride(1),
|
| 161 |
+
value_cache.stride(0), value_cache.stride(3), value_cache.stride(1),
|
| 162 |
+
logits.stride(0), logits.stride(1), logits.stride(2), k_scale, v_scale,
|
| 163 |
+
kv_group_num=8, q_head_num=16, BLOCK_DMODEL=256, BLOCK_DPE=0,
|
| 164 |
+
BLOCK_DV=256, BLOCK_N=16, BLOCK_H=64, NUM_KV_SPLITS=splits,
|
| 165 |
+
PAGE_SIZE=page_size, logit_cap=0., Lk=256, Lv=256, IS_MLA=False,
|
| 166 |
+
stride_buf_kds=key_cache.stride(2), stride_buf_kxs=key_cache.stride(4),
|
| 167 |
+
stride_buf_vds=value_cache.stride(2), num_warps=4, num_stages=1,
|
| 168 |
+
waves_per_eu=1, matrix_instr_nonkdim=16, kpack=2)
|
| 169 |
+
reduce(logits, query, output, lse, value_cache.transpose(2, 3), row_seq, splits)
|
| 170 |
+
return output
|
bundle/plugin-site/ornith_g256/attention_iu4.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright 2026 Ciru. Isolated C1 attention binding; no installed runtime edits.
|
| 2 |
+
import ctypes
|
| 3 |
+
import torch
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class NativeAttention:
|
| 7 |
+
"""Preallocated signed IU4 scratch and stream-only native enqueue calls."""
|
| 8 |
+
|
| 9 |
+
def __init__(self, device, library_path, max_model_len=65536):
|
| 10 |
+
if not 1120 <= max_model_len <= 262144:
|
| 11 |
+
raise ValueError("IU4 prefill capacity must be1120..262144")
|
| 12 |
+
self.max_keys = 65536 if max_model_len <= 65536 else 262144
|
| 13 |
+
groups = self.max_keys // 32
|
| 14 |
+
self.library = ctypes.CDLL(str(library_path))
|
| 15 |
+
self.library.iu4_prepare.argtypes = [ctypes.c_void_p] * 9 + [ctypes.c_int] * 3 + [ctypes.c_void_p]
|
| 16 |
+
self.library.iu4_prepare.restype = ctypes.c_int
|
| 17 |
+
self.library.iu4_attention.argtypes = [ctypes.c_void_p] * 7 + [ctypes.c_int] + [ctypes.c_void_p]
|
| 18 |
+
self.library.iu4_attention.restype = ctypes.c_int
|
| 19 |
+
specs = [((16, 1120, 32), torch.int32), ((16, 1120), torch.float16),
|
| 20 |
+
((2, groups, 32, 32), torch.int32), ((2, self.max_keys), torch.float16),
|
| 21 |
+
((2, groups, 4, 256), torch.int32), ((2, groups, 256), torch.float16)]
|
| 22 |
+
self.packed = [torch.empty(shape, dtype=dtype, device=device) for shape, dtype in specs]
|
| 23 |
+
self.pointers = [tensor.data_ptr() for tensor in self.packed]
|
| 24 |
+
self.output = torch.empty((1120, 16, 256), dtype=torch.bfloat16, device=device)
|
| 25 |
+
self.bytes = self.output.numel() * self.output.element_size() + sum(tensor.numel() * tensor.element_size() for tensor in self.packed)
|
| 26 |
+
|
| 27 |
+
def forward(self, query, key, value, output, starts, lengths, max_keys):
|
| 28 |
+
# C1 max_query_len1120 and exact CPU metadata max_seq_len are required.
|
| 29 |
+
# The unchanged consumer uses its original contiguous fixed1120 output.
|
| 30 |
+
# No allocation, tensor copy to CPU, synchronization or cache mutation here.
|
| 31 |
+
if not 1120 <= max_keys <= self.max_keys or max_keys % 32:
|
| 32 |
+
raise ValueError("IU4 prefill requires aligned keys within reserved capacity")
|
| 33 |
+
stream = torch.cuda.current_stream().cuda_stream
|
| 34 |
+
rc = self.library.iu4_prepare(query.data_ptr(), key.data_ptr(), value.data_ptr(),
|
| 35 |
+
*self.pointers, max_keys, query.stride(0), query.stride(1), stream)
|
| 36 |
+
if rc:
|
| 37 |
+
raise RuntimeError(f'IU4 attention preparation launch failed: HIP {rc}')
|
| 38 |
+
rc = self.library.iu4_attention(*self.pointers, self.output.data_ptr(), max_keys, stream)
|
| 39 |
+
if rc:
|
| 40 |
+
raise RuntimeError(f'IU4 attention launch failed: HIP {rc}')
|
| 41 |
+
output[:1120].copy_(self.output)
|
bundle/plugin-site/ornith_g256/attention_iu4_persistent.py
ADDED
|
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
# Copyright 2026 Ciru. Isolated optional persistent long-context attention.
|
| 3 |
+
"""Page1120 only; canonical BF16 cache owns page identity and prefix lifecycle."""
|
| 4 |
+
import ctypes
|
| 5 |
+
from functools import lru_cache
|
| 6 |
+
import inspect
|
| 7 |
+
import linecache
|
| 8 |
+
import os
|
| 9 |
+
import types
|
| 10 |
+
|
| 11 |
+
import numpy as np
|
| 12 |
+
import torch
|
| 13 |
+
|
| 14 |
+
_state = None
|
| 15 |
+
_installed = False
|
| 16 |
+
_library = None
|
| 17 |
+
_threshold = 32768
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _compile(source, namespace, suffix):
|
| 21 |
+
name = __file__ + suffix
|
| 22 |
+
linecache.cache[name] = (len(source), None, source.splitlines(True), name)
|
| 23 |
+
exec(compile(source, name, 'exec'), namespace)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
@lru_cache(maxsize=2)
|
| 27 |
+
def _bf16_kernels(query_tiles):
|
| 28 |
+
"""Original BF16 arithmetic; short requests only, GPU-gated at replay."""
|
| 29 |
+
from . import attention_folded as folded
|
| 30 |
+
native, reduce = folded._kernel(query_tiles)
|
| 31 |
+
source = inspect.getsource(native.fn)
|
| 32 |
+
anchor = ' cur_batch_seq_len = tl.load(B_Seqlen + cur_batch)\n'
|
| 33 |
+
assert source.count(anchor) == 1
|
| 34 |
+
source = source.replace(anchor, anchor +
|
| 35 |
+
f' if cur_batch_seq_len >= {_threshold}:\n return\n')
|
| 36 |
+
namespace = dict(native.fn.__globals__)
|
| 37 |
+
_compile(source, namespace, f'.bf16_{query_tiles}.generated')
|
| 38 |
+
stage1 = namespace[native.fn.__name__]
|
| 39 |
+
namespace = dict(reduce.__globals__)
|
| 40 |
+
stage2 = namespace['_fwd_kernel_stage2']
|
| 41 |
+
source = inspect.getsource(stage2.fn)
|
| 42 |
+
assert source.count(anchor) == 1
|
| 43 |
+
# Padded outputs belong exclusively to the native guarded reducer.
|
| 44 |
+
source = source.replace(anchor, anchor +
|
| 45 |
+
f' if cur_batch_seq_len >= {_threshold} or cur_batch_seq_len <= 0:\n return\n')
|
| 46 |
+
_compile(source, namespace, '.bf16_reduce.generated')
|
| 47 |
+
reduce = types.FunctionType(reduce.__code__, namespace, reduce.__name__,
|
| 48 |
+
reduce.__defaults__, reduce.__closure__)
|
| 49 |
+
return stage1, reduce
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
class Bank:
|
| 53 |
+
def __init__(self, cache):
|
| 54 |
+
self.cache = cache
|
| 55 |
+
n = cache.shape[0]
|
| 56 |
+
groups = n * 35
|
| 57 |
+
self.packed = [torch.empty(shape, dtype=dtype, device=cache.device)
|
| 58 |
+
for shape, dtype in [
|
| 59 |
+
((n, 35, 2, 32, 32), torch.int32),
|
| 60 |
+
((n, 35, 2, 32), torch.float16),
|
| 61 |
+
((n, 35, 2, 4, 256), torch.int32),
|
| 62 |
+
((n, 35, 2, 256), torch.int32) # FP16 nonDC scale + FP16 normalized V_DC,
|
| 63 |
+
]]
|
| 64 |
+
self.valid = torch.zeros(groups, dtype=torch.int32, device=cache.device)
|
| 65 |
+
self.busy = torch.zeros_like(self.valid)
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
class State:
|
| 69 |
+
def __init__(self, runner):
|
| 70 |
+
self.lib = ctypes.CDLL(_library)
|
| 71 |
+
P, I = ctypes.c_void_p, ctypes.c_int
|
| 72 |
+
for name, args in [
|
| 73 |
+
('update_cache', [P] * 9 + [I, P]),
|
| 74 |
+
('prepare_decode', [P] * 9 + [I] * 5 + [P]),
|
| 75 |
+
('cached_attention', [P] * 12 + [I] * 4 + [P]),
|
| 76 |
+
('tail_reduce', [P] * 9 + [I] * 7 + [P]),
|
| 77 |
+
('cache_reset', [P] * 3 + [I, P]),
|
| 78 |
+
('cache_copy', [P] * 7 + [I, P]),
|
| 79 |
+
]:
|
| 80 |
+
fn = getattr(self.lib, name)
|
| 81 |
+
fn.argtypes, fn.restype = args, I
|
| 82 |
+
self.banks = {}
|
| 83 |
+
targets = 0
|
| 84 |
+
for layer in runner.get_model().modules():
|
| 85 |
+
impl = getattr(layer, 'impl', None)
|
| 86 |
+
if (getattr(impl, 'num_heads', None), getattr(impl, 'num_kv_heads', None),
|
| 87 |
+
getattr(impl, 'head_size', None)) != (16, 2, 256):
|
| 88 |
+
continue
|
| 89 |
+
cache = layer.kv_cache
|
| 90 |
+
if (not isinstance(cache, torch.Tensor) or cache.ndim != 4
|
| 91 |
+
or cache.shape[1:] != (2, 1120, 512)
|
| 92 |
+
or cache.dtype != torch.bfloat16
|
| 93 |
+
or cache.stride() != (1196032, 573440, 512, 1)):
|
| 94 |
+
raise RuntimeError('Persistent IU4 requires bound layer-major target page1120 BF16')
|
| 95 |
+
targets += 1
|
| 96 |
+
if cache.data_ptr() not in self.banks:
|
| 97 |
+
self.banks[cache.data_ptr()] = Bank(cache)
|
| 98 |
+
if targets != 10 or len(self.banks) != 5:
|
| 99 |
+
raise RuntimeError(f'Expected ten target layers aliasing five banks, got {targets}/{len(self.banks)}')
|
| 100 |
+
self.device = next(iter(self.banks.values())).cache.device
|
| 101 |
+
device = self.device
|
| 102 |
+
self.groups = torch.empty(2048, dtype=torch.int32, device=device)
|
| 103 |
+
self.contexts = torch.empty(8, dtype=torch.int32, device=device)
|
| 104 |
+
self.counts = torch.empty(8, dtype=torch.int32, device=device)
|
| 105 |
+
self.owners = torch.empty(64, dtype=torch.int32, device=device)
|
| 106 |
+
self.stats = torch.zeros(3, dtype=torch.int64, device=device)
|
| 107 |
+
self.qp = torch.empty((16, 64, 32), dtype=torch.int32, device=device)
|
| 108 |
+
self.qs = torch.empty((16, 64), dtype=torch.float16, device=device)
|
| 109 |
+
# Both arms use disjoint rows of the same preallocated partial storage.
|
| 110 |
+
self.parts = torch.empty((64, 16, 33, 257), dtype=torch.float32, device=device)
|
| 111 |
+
self.row_seq = torch.empty(64, dtype=torch.int32, device=device)
|
| 112 |
+
self.lse = torch.empty((64, 16), dtype=torch.float32, device=device)
|
| 113 |
+
self.reset_events = self.copy_events = 0
|
| 114 |
+
|
| 115 |
+
def call(self, name, tensors, *ints):
|
| 116 |
+
rc = getattr(self.lib, name)(*[t.data_ptr() for t in tensors], *ints,
|
| 117 |
+
torch.cuda.current_stream(self.device).cuda_stream)
|
| 118 |
+
if rc:
|
| 119 |
+
raise RuntimeError(f'Persistent IU4 {name} launch failed: {rc}')
|
| 120 |
+
|
| 121 |
+
def update(self, cache, slots):
|
| 122 |
+
bank = self.banks.get(cache.data_ptr())
|
| 123 |
+
if bank is None:
|
| 124 |
+
return
|
| 125 |
+
if slots.numel() > 2048 or slots.dtype != torch.int64:
|
| 126 |
+
raise RuntimeError('Unexpected persistent IU4 slot capacity/type')
|
| 127 |
+
self.call('update_cache', [cache, slots, self.groups, bank.busy,
|
| 128 |
+
bank.valid, *bank.packed], slots.numel())
|
| 129 |
+
|
| 130 |
+
def events(self, scheduler_output):
|
| 131 |
+
from vllm.utils.torch_utils import async_tensor_h2d
|
| 132 |
+
zeros = scheduler_output.new_block_ids_to_zero
|
| 133 |
+
copies = scheduler_output.kv_cache_block_copies
|
| 134 |
+
if zeros:
|
| 135 |
+
ids = async_tensor_h2d(np.asarray(zeros, dtype=np.int64), device=self.device)
|
| 136 |
+
for bank in self.banks.values():
|
| 137 |
+
self.call('cache_reset', [bank.valid, bank.busy, ids], len(zeros))
|
| 138 |
+
self.reset_events += len(zeros)
|
| 139 |
+
if copies:
|
| 140 |
+
pairs_cpu = np.asarray(copies, dtype=np.int64).reshape(-1, 2)
|
| 141 |
+
# Usual CoW destinations are fresh. For dependency chains, retain
|
| 142 |
+
# upstream snapshot-copy semantics using its existing helper.
|
| 143 |
+
if set(pairs_cpu[:, 0]) & set(pairs_cpu[:, 1]):
|
| 144 |
+
from vllm.v1.worker.utils import copy_kv_cache_blocks_inplace
|
| 145 |
+
for bank in self.banks.values():
|
| 146 |
+
copy_kv_cache_blocks_inplace(
|
| 147 |
+
[*bank.packed, bank.valid.view(-1, 35), bank.busy.view(-1, 35)],
|
| 148 |
+
bank.cache.shape[0], copies)
|
| 149 |
+
else:
|
| 150 |
+
pairs = async_tensor_h2d(pairs_cpu, device=self.device)
|
| 151 |
+
for bank in self.banks.values():
|
| 152 |
+
self.call('cache_copy', [*bank.packed, bank.valid, bank.busy, pairs], len(copies))
|
| 153 |
+
self.copy_events += len(copies)
|
| 154 |
+
|
| 155 |
+
def forward(self, query, key_cache, value_cache, output, block_table,
|
| 156 |
+
starts, seq, sm_scale, k_scale, v_scale, max_query_len):
|
| 157 |
+
from vllm.triton_utils import triton
|
| 158 |
+
from .attention_folded import _reduction_lengths
|
| 159 |
+
rows, requests = query.shape[0], seq.numel()
|
| 160 |
+
bank = self.banks[key_cache.data_ptr()]
|
| 161 |
+
tiles = 1 if max_query_len <= 8 else 2
|
| 162 |
+
self.call('prepare_decode', [query, starts, seq, self.contexts,
|
| 163 |
+
self.counts, self.owners, self.stats, self.qp, self.qs],
|
| 164 |
+
requests, rows, query.stride(0), query.stride(1), _threshold)
|
| 165 |
+
_reduction_lengths[(rows,)](starts, seq, self.row_seq,
|
| 166 |
+
REQUESTS=requests, REQUEST_BLOCK=triton.next_power_of_2(requests), num_warps=4)
|
| 167 |
+
logits = self.parts[:rows, :, :32, :]
|
| 168 |
+
stage1, reduce = _bf16_kernels(tiles)
|
| 169 |
+
stage1[(requests * tiles, 2, 32)](
|
| 170 |
+
query, key_cache, value_cache, sm_scale, block_table, seq, starts, logits,
|
| 171 |
+
block_table.stride(0), query.stride(0), query.stride(1),
|
| 172 |
+
key_cache.stride(0), key_cache.stride(3), key_cache.stride(1),
|
| 173 |
+
value_cache.stride(0), value_cache.stride(3), value_cache.stride(1),
|
| 174 |
+
logits.stride(0), logits.stride(1), logits.stride(2), k_scale, v_scale,
|
| 175 |
+
kv_group_num=8, q_head_num=16, BLOCK_DMODEL=256, BLOCK_DPE=0,
|
| 176 |
+
BLOCK_DV=256, BLOCK_N=16, BLOCK_H=64, NUM_KV_SPLITS=32,
|
| 177 |
+
PAGE_SIZE=1120, logit_cap=0., Lk=256, Lv=256, IS_MLA=False,
|
| 178 |
+
stride_buf_kds=key_cache.stride(2), stride_buf_kxs=key_cache.stride(4),
|
| 179 |
+
stride_buf_vds=value_cache.stride(2), num_warps=4, num_stages=1,
|
| 180 |
+
waves_per_eu=1, matrix_instr_nonkdim=16, kpack=2)
|
| 181 |
+
reduce(logits, query, output, self.lse[:rows], value_cache.transpose(2, 3), self.row_seq[:rows], 32)
|
| 182 |
+
self.call('cached_attention', [self.qp, self.qs, *bank.packed, self.parts,
|
| 183 |
+
block_table, self.contexts, self.counts, starts, seq], requests,
|
| 184 |
+
block_table.stride(0), tiles, _threshold)
|
| 185 |
+
self.call('tail_reduce', [query, bank.cache, block_table, self.contexts,
|
| 186 |
+
starts, self.owners, seq, self.parts, output], rows,
|
| 187 |
+
block_table.stride(0), query.stride(0), query.stride(1),
|
| 188 |
+
output.stride(0), output.stride(1), _threshold)
|
| 189 |
+
return output
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def bind(runner):
|
| 193 |
+
global _state
|
| 194 |
+
if _state is not None:
|
| 195 |
+
return 0
|
| 196 |
+
before = torch.cuda.memory_allocated(runner.device)
|
| 197 |
+
_state = State(runner)
|
| 198 |
+
added = torch.cuda.memory_allocated(runner.device) - before
|
| 199 |
+
print('ORNITH_PERSISTENT_IU4_BOUND', len(_state.banks),
|
| 200 |
+
next(iter(_state.banks.values())).cache.shape[0], added, _threshold, flush=True)
|
| 201 |
+
return added
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
def snapshot():
|
| 205 |
+
if _state is None:
|
| 206 |
+
return None
|
| 207 |
+
return {'bank_count': len(_state.banks), 'gpu_long_short_requests_long_queries': _state.stats.cpu().tolist(),
|
| 208 |
+
'reset_page_events': _state.reset_events, 'copy_page_events': _state.copy_events,
|
| 209 |
+
'threshold': _threshold}
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def install():
|
| 213 |
+
global _installed, _library, _threshold
|
| 214 |
+
if _installed:
|
| 215 |
+
return
|
| 216 |
+
_library = os.environ['ORNITH_PERSISTENT_IU4_LIBRARY']
|
| 217 |
+
_threshold = int(os.environ.get('ORNITH_PERSISTENT_IU4_MIN_SEQ', '32768'))
|
| 218 |
+
if not 32768 <= _threshold <= 63000:
|
| 219 |
+
raise ValueError('Persistent IU4 experiment requires a long-only threshold32768..63000')
|
| 220 |
+
from vllm.v1.attention.backends.rocm_attn import RocmAttentionImpl
|
| 221 |
+
from vllm.v1.worker.gpu_model_runner import GPUModelRunner
|
| 222 |
+
from . import attention_folded
|
| 223 |
+
original_update = RocmAttentionImpl.do_kv_cache_update
|
| 224 |
+
original_states = GPUModelRunner._update_states
|
| 225 |
+
original_forward = attention_folded.forward
|
| 226 |
+
|
| 227 |
+
def update(self, layer, key, value, kv_cache, slot_mapping):
|
| 228 |
+
result = original_update(self, layer, key, value, kv_cache, slot_mapping)
|
| 229 |
+
if _state is not None:
|
| 230 |
+
_state.update(kv_cache, slot_mapping)
|
| 231 |
+
return result
|
| 232 |
+
|
| 233 |
+
def states(self, scheduler_output):
|
| 234 |
+
if _state is not None:
|
| 235 |
+
_state.events(scheduler_output)
|
| 236 |
+
return original_states(self, scheduler_output)
|
| 237 |
+
|
| 238 |
+
def forward(query, key_cache, value_cache, output, block_table, query_start_loc,
|
| 239 |
+
seq_lens, sm_scale, k_scale, v_scale, *, max_query_len=8):
|
| 240 |
+
if (_state is None or key_cache.data_ptr() not in _state.banks
|
| 241 |
+
or query.shape[0] > 64 or seq_lens.numel() > 8
|
| 242 |
+
or sm_scale != .0625 or query.dtype != torch.bfloat16
|
| 243 |
+
or output.dtype != torch.bfloat16 or query.stride(2) != 1
|
| 244 |
+
or output.stride(2) != 1 or not 1 <= max_query_len <= 16):
|
| 245 |
+
return original_forward(query, key_cache, value_cache, output,
|
| 246 |
+
block_table, query_start_loc, seq_lens, sm_scale, k_scale, v_scale,
|
| 247 |
+
max_query_len=max_query_len)
|
| 248 |
+
return _state.forward(query, key_cache, value_cache, output, block_table,
|
| 249 |
+
query_start_loc, seq_lens, sm_scale, k_scale, v_scale, max_query_len)
|
| 250 |
+
|
| 251 |
+
RocmAttentionImpl.do_kv_cache_update = update
|
| 252 |
+
GPUModelRunner._update_states = states
|
| 253 |
+
attention_folded.forward = forward
|
| 254 |
+
_installed = True
|
bundle/plugin-site/ornith_g256/attention_mixed.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Isolated mixed-target dispatch using existing kernels and CPU phase bounds.
|
| 2 |
+
|
| 3 |
+
The backend writes current K/V once before this helper. This helper only creates
|
| 4 |
+
views and rebases query starts on the device; it neither changes cache ownership
|
| 5 |
+
nor reads GPU metadata on the host. Not installed in any measured bundle.
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
_diagnostic_signatures = set()
|
| 10 |
+
|
| 11 |
+
def try_forward(dispatch, query, key, value, output, kv_cache_dtype,
|
| 12 |
+
key_cache, value_cache, block_table, query_start_loc, seq_lens,
|
| 13 |
+
max_seq_len, max_query_len, k_scale, v_scale,
|
| 14 |
+
alibi_slopes, sliding_window, sm_scale, output_scale, sinks,
|
| 15 |
+
is_block_table_ptr, causal):
|
| 16 |
+
from . import native
|
| 17 |
+
requests = getattr(native, '_ATTENTION_PHASE_REQUESTS', ())
|
| 18 |
+
# Subcalls have fewer requests, preventing recursion without global state.
|
| 19 |
+
# Dummy capture, draft attention, and unmatched metadata use the old route.
|
| 20 |
+
n = len(requests)
|
| 21 |
+
if (n < 2 or n != seq_lens.numel() or query_start_loc.numel() != n + 1
|
| 22 |
+
or block_table.shape[0] < n or output.shape != query.shape
|
| 23 |
+
or requests[0][0] != 0 or requests[-1][1] > query.shape[0]
|
| 24 |
+
or max(end-start for start, end, _, _ in requests) != max_query_len
|
| 25 |
+
or not all(end > start for start, end, _, _ in requests)
|
| 26 |
+
or not any(r[2] for r in requests) or all(r[2] for r in requests)):
|
| 27 |
+
return False
|
| 28 |
+
groups = []
|
| 29 |
+
for req, (_, _, prefill, _) in enumerate(requests):
|
| 30 |
+
if not prefill and groups and not groups[-1][2]:
|
| 31 |
+
first, _, phase = groups[-1]
|
| 32 |
+
groups[-1] = (first, req + 1, phase)
|
| 33 |
+
else:
|
| 34 |
+
# Keep each prefill C1, enabling its existing Q1120 IU4 path.
|
| 35 |
+
groups.append((req, req + 1, prefill))
|
| 36 |
+
# Folded decode keeps its current <=128-row contract. Do not partially
|
| 37 |
+
# dispatch before deciding whether the entire mixed call is supported.
|
| 38 |
+
if any(not phase and requests[last-1][1]-requests[first][0] > 128
|
| 39 |
+
for first, last, phase in groups):
|
| 40 |
+
return False
|
| 41 |
+
logical_end = requests[-1][1]
|
| 42 |
+
if logical_end < output.shape[0]:
|
| 43 |
+
output[logical_end:].zero_()
|
| 44 |
+
diagnostic = []
|
| 45 |
+
for first, last, prefill in groups:
|
| 46 |
+
start, end = requests[first][0], requests[last-1][1]
|
| 47 |
+
# GPU subtraction rebases cu_q to the sliced query, without .item(),
|
| 48 |
+
# .cpu(), or tensor reconstruction from GPU values.
|
| 49 |
+
starts = query_start_loc[first:last+1] - start
|
| 50 |
+
max_q = max(r[1]-r[0] for r in requests[first:last])
|
| 51 |
+
max_k = max(r[3] for r in requests[first:last])
|
| 52 |
+
dispatch(query[start:end], None if key is None else key[start:end],
|
| 53 |
+
None if value is None else value[start:end], output[start:end],
|
| 54 |
+
kv_cache_dtype, key_cache, value_cache,
|
| 55 |
+
block_table[first:last], starts, seq_lens[first:last],
|
| 56 |
+
max_k, max_q, k_scale, v_scale, alibi_slopes, sliding_window,
|
| 57 |
+
sm_scale, output_scale, sinks, is_block_table_ptr, causal)
|
| 58 |
+
if len(_diagnostic_signatures) < 4:
|
| 59 |
+
from . import attention_compact, attention_iu4_persistent
|
| 60 |
+
iu4_prefill = (prefill and last-first == 1 and max_q == 1120
|
| 61 |
+
and max_k >= 4096 and max_k % 32 == 0
|
| 62 |
+
and attention_compact._arena['iu4'] is not None)
|
| 63 |
+
diagnostic.append({'rows': (start, end), 'prefill': prefill,
|
| 64 |
+
'max_q': max_q, 'max_k': max_k,
|
| 65 |
+
'iu4_prefill_guard': iu4_prefill,
|
| 66 |
+
'persistent_decode_installed': attention_iu4_persistent._installed})
|
| 67 |
+
# Successful subdispatch receipts only; CPU metadata, no GPU read or sync.
|
| 68 |
+
# At most four distinct query/path signatures, so repeated layers stay quiet.
|
| 69 |
+
if diagnostic:
|
| 70 |
+
signature = tuple((d['max_q'], d['prefill'], d['iu4_prefill_guard'])
|
| 71 |
+
for d in diagnostic)
|
| 72 |
+
if signature not in _diagnostic_signatures:
|
| 73 |
+
_diagnostic_signatures.add(signature)
|
| 74 |
+
print('ORNITH_MIXED_ATTENTION_SUCCESS ' + str(diagnostic), flush=True)
|
| 75 |
+
return True
|
bundle/plugin-site/ornith_g256/attention_partition.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
# Copyright 2026 Ciru.
|
| 3 |
+
# The generated kernels retain the installed vLLM/SGLang Apache-2.0 code:
|
| 4 |
+
# Copyright contributors to the vLLM project; Copyright 2025 vLLM Team;
|
| 5 |
+
# Copyright 2023-2024 SGLang Team. See vLLM triton_decode_attention.py.
|
| 6 |
+
"""Native-page partitioned attention for target H16/KV2/D256/page1120 or2240.
|
| 7 |
+
|
| 8 |
+
The backend must write current K/V before calling ``forward``. Each query row
|
| 9 |
+
becomes a causal decode request; existing grouped split/reduction arithmetic is
|
| 10 |
+
retained. This module has no installation hook and never converts the KV pool.
|
| 11 |
+
"""
|
| 12 |
+
|
| 13 |
+
from functools import lru_cache
|
| 14 |
+
import inspect
|
| 15 |
+
import linecache
|
| 16 |
+
|
| 17 |
+
import torch
|
| 18 |
+
from vllm.triton_utils import tl, triton
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
@triton.jit
|
| 22 |
+
def _query_metadata(
|
| 23 |
+
Starts, Seq, Table, RowSeq, RowTable,
|
| 24 |
+
TABLE_STRIDE: tl.constexpr, COLS: tl.constexpr,
|
| 25 |
+
REQUESTS: tl.constexpr, REQUEST_BLOCK: tl.constexpr, COL_BLOCK: tl.constexpr,
|
| 26 |
+
PAGE_SIZE: tl.constexpr,
|
| 27 |
+
):
|
| 28 |
+
row = tl.program_id(0)
|
| 29 |
+
req = tl.arange(0, REQUEST_BLOCK)
|
| 30 |
+
start = tl.load(Starts + req, req < REQUESTS, other=0)
|
| 31 |
+
end = tl.load(Starts + req + 1, req < REQUESTS, other=0)
|
| 32 |
+
owns = (req < REQUESTS) & (start <= row) & (row < end)
|
| 33 |
+
owner = tl.max(tl.where(owns, req + 1, 0), 0) - 1
|
| 34 |
+
active = owner >= 0
|
| 35 |
+
first = tl.sum(tl.where(owns, start, 0), 0)
|
| 36 |
+
count = tl.sum(tl.where(owns, end - start, 0), 0)
|
| 37 |
+
seq = tl.load(Seq + owner, active, other=0)
|
| 38 |
+
causal_len = tl.where(active, seq - count + row - first + 1, 0)
|
| 39 |
+
tl.store(RowSeq + row, causal_len)
|
| 40 |
+
cols = tl.arange(0, COL_BLOCK)
|
| 41 |
+
page = tl.load(Table + owner * TABLE_STRIDE + cols,
|
| 42 |
+
active & (cols < COLS) & (cols * PAGE_SIZE < causal_len), other=0)
|
| 43 |
+
tl.store(RowTable + row * COLS + cols, page, cols < COLS)
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def _compile(source, namespace, suffix):
|
| 47 |
+
filename = __file__ + suffix
|
| 48 |
+
linecache.cache[filename] = (len(source), None, source.splitlines(True), filename)
|
| 49 |
+
exec(compile(source, filename, "exec"), namespace)
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
@lru_cache(maxsize=1)
|
| 53 |
+
def _kernels():
|
| 54 |
+
from vllm.v1.attention.ops import triton_decode_attention as module
|
| 55 |
+
|
| 56 |
+
namespace = dict(vars(module))
|
| 57 |
+
source = inspect.getsource(module._fwd_grouped_kernel_stage1.fn)
|
| 58 |
+
replacements = {
|
| 59 |
+
" IS_MLA: tl.constexpr = False,":
|
| 60 |
+
" stride_buf_kds: tl.constexpr,\n stride_buf_kxs: tl.constexpr,\n"
|
| 61 |
+
" stride_buf_vds: tl.constexpr,\n IS_MLA: tl.constexpr = False,",
|
| 62 |
+
"base_offs_k = cur_kv_head * stride_buf_kh + offs_d[:, None]":
|
| 63 |
+
"base_offs_k = cur_kv_head * stride_buf_kh + (offs_d[:, None] // 8) * stride_buf_kds + (offs_d[:, None] % 8) * stride_buf_kxs",
|
| 64 |
+
"base_offs_v = cur_kv_head * stride_buf_vh + offs_dv[None, :]":
|
| 65 |
+
"base_offs_v = cur_kv_head * stride_buf_vh + offs_dv[None, :] * stride_buf_vds",
|
| 66 |
+
}
|
| 67 |
+
for old, new in replacements.items():
|
| 68 |
+
if source.count(old) != 1:
|
| 69 |
+
raise RuntimeError("Unsupported installed partitioned decoder addressing")
|
| 70 |
+
source = source.replace(old, new)
|
| 71 |
+
_compile(source, namespace, ".stage1.generated")
|
| 72 |
+
|
| 73 |
+
# A graph-padded row owns no request. Stage1 then reads no KV or scratch;
|
| 74 |
+
# bypass the reducer's undefined 0/0 result without changing active math.
|
| 75 |
+
source = inspect.getsource(module._fwd_kernel_stage2.fn)
|
| 76 |
+
old = " cur_batch_seq_len = tl.load(B_Seqlen + cur_batch)\n"
|
| 77 |
+
if source.count(old) != 1:
|
| 78 |
+
raise RuntimeError("Unsupported installed partitioned decoder reduction")
|
| 79 |
+
source = source.replace(old, old +
|
| 80 |
+
" if cur_batch_seq_len <= 0:\n"
|
| 81 |
+
" pad_d = tl.arange(0, BLOCK_DV)\n"
|
| 82 |
+
" tl.store(o + cur_batch * stride_obs + cur_head * stride_oh + pad_d, 0., pad_d < Lv)\n"
|
| 83 |
+
" tl.store(lse + cur_batch * stride_lse_bs + cur_head, -float('inf'))\n"
|
| 84 |
+
" return\n")
|
| 85 |
+
_compile(source, namespace, ".stage2.generated")
|
| 86 |
+
_compile(inspect.getsource(module._decode_softmax_reducev_fwd), namespace,
|
| 87 |
+
".reduce.generated")
|
| 88 |
+
return namespace["_fwd_grouped_kernel_stage1"], namespace["_decode_softmax_reducev_fwd"]
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def _metadata(block_table, query_start_loc, seq_lens, rows, page_size=1120):
|
| 92 |
+
"""Allocate and fill per-query metadata without a device-to-host read."""
|
| 93 |
+
row_table = torch.empty((rows, block_table.shape[1]), dtype=torch.int32,
|
| 94 |
+
device=block_table.device)
|
| 95 |
+
row_seq = torch.empty((rows,), dtype=torch.int32, device=block_table.device)
|
| 96 |
+
if rows:
|
| 97 |
+
_query_metadata[(rows,)](
|
| 98 |
+
query_start_loc, seq_lens, block_table, row_seq, row_table,
|
| 99 |
+
TABLE_STRIDE=block_table.stride(0), COLS=block_table.shape[1],
|
| 100 |
+
REQUESTS=seq_lens.numel(), REQUEST_BLOCK=triton.next_power_of_2(seq_lens.numel()),
|
| 101 |
+
COL_BLOCK=triton.next_power_of_2(block_table.shape[1]), PAGE_SIZE=page_size,
|
| 102 |
+
num_warps=4)
|
| 103 |
+
return row_table, row_seq
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def forward(query, key_cache, value_cache, output, block_table, query_start_loc,
|
| 107 |
+
seq_lens, sm_scale, k_scale, v_scale, *, max_query_len=8):
|
| 108 |
+
"""Write and return ``output``; allocate scratch only for this call.
|
| 109 |
+
|
| 110 |
+
Caller eligibility: BF16 causal target attention, no sinks/alibi/window/output
|
| 111 |
+
scaling, at most 8 requests and max_query_len <= 8, at most 64 query rows
|
| 112 |
+
including padding. ``query_start_loc`` has
|
| 113 |
+
len(seq_lens)+1 entries and includes zero-query slots; trailing query padding
|
| 114 |
+
beyond its final entry is allowed and gets zero output. Metadata and cached
|
| 115 |
+
page IDs must be valid, as in the ROCm backend. No host synchronization occurs.
|
| 116 |
+
|
| 117 |
+
Scratch bytes for R query rows and B table columns: R*(526404 + 4*B),
|
| 118 |
+
including float32 split outputs/LSE and int32 causal lengths/block tables.
|
| 119 |
+
"""
|
| 120 |
+
page_size = key_cache.shape[3] if key_cache.ndim == 5 else 0
|
| 121 |
+
if (query.ndim != 3 or query.shape[1:] != (16, 256)
|
| 122 |
+
or page_size not in (1120, 2240) or key_cache.shape[1:] != (2, 32, page_size, 8)
|
| 123 |
+
or value_cache.ndim != 4 or value_cache.shape[1:] != (2, 256, page_size)
|
| 124 |
+
or key_cache.shape[0] != value_cache.shape[0]
|
| 125 |
+
or key_cache.shape[0] == 0 or output.shape != query.shape):
|
| 126 |
+
raise ValueError("Partitioned attention requires target H16/KV2/D256/page1120 or2240")
|
| 127 |
+
if (not 1 <= max_query_len <= 8 or query.shape[0] > 64
|
| 128 |
+
or not 1 <= seq_lens.numel() <= 8
|
| 129 |
+
or block_table.ndim != 2 or block_table.shape[0] < seq_lens.numel()
|
| 130 |
+
or block_table.shape[1] < 1 or query_start_loc.numel() != seq_lens.numel() + 1
|
| 131 |
+
or not seq_lens.is_contiguous() or not query_start_loc.is_contiguous()
|
| 132 |
+
or block_table.stride(1) != 1 or query.stride(2) != 1 or output.stride(2) != 1):
|
| 133 |
+
raise ValueError("Unsupported query metadata or noncontiguous inner dimensions")
|
| 134 |
+
if (not query.is_cuda or any(t.device != query.device for t in
|
| 135 |
+
(key_cache, value_cache, output, block_table, query_start_loc, seq_lens, k_scale, v_scale))
|
| 136 |
+
or any(t.dtype != torch.bfloat16 for t in (query, key_cache, value_cache, output))
|
| 137 |
+
or any(t.dtype not in (torch.int32, torch.int64) for t in
|
| 138 |
+
(block_table, query_start_loc, seq_lens))):
|
| 139 |
+
raise ValueError("Partitioned attention requires BF16 and integer metadata on one GPU")
|
| 140 |
+
rows = query.shape[0]
|
| 141 |
+
if not rows:
|
| 142 |
+
return output
|
| 143 |
+
stage1, reduce = _kernels()
|
| 144 |
+
table, seq = _metadata(block_table, query_start_loc, seq_lens, rows, page_size)
|
| 145 |
+
splits = 32
|
| 146 |
+
logits = torch.empty((rows, 16, splits, 257), dtype=torch.float32, device=query.device)
|
| 147 |
+
lse = torch.empty((rows, 16), dtype=torch.float32, device=query.device)
|
| 148 |
+
stage1[(rows, 2, splits)](
|
| 149 |
+
query, key_cache, value_cache, sm_scale, table, seq, logits,
|
| 150 |
+
table.stride(0), query.stride(0), query.stride(1),
|
| 151 |
+
key_cache.stride(0), key_cache.stride(3), key_cache.stride(1),
|
| 152 |
+
value_cache.stride(0), value_cache.stride(3), value_cache.stride(1),
|
| 153 |
+
logits.stride(0), logits.stride(1), logits.stride(2), k_scale, v_scale,
|
| 154 |
+
kv_group_num=8, q_head_num=16, BLOCK_DMODEL=256, BLOCK_DPE=0,
|
| 155 |
+
BLOCK_DV=256, BLOCK_N=16, BLOCK_H=16, NUM_KV_SPLITS=splits,
|
| 156 |
+
PAGE_SIZE=page_size, logit_cap=0., Lk=256, Lv=256, IS_MLA=False,
|
| 157 |
+
stride_buf_kds=key_cache.stride(2), stride_buf_kxs=key_cache.stride(4),
|
| 158 |
+
stride_buf_vds=value_cache.stride(2), num_warps=4, num_stages=1,
|
| 159 |
+
waves_per_eu=1, matrix_instr_nonkdim=16, kpack=2)
|
| 160 |
+
# The reduction helper only reads v_buffer.shape[-1], never cache data.
|
| 161 |
+
reduce(logits, query, output, lse, value_cache.transpose(2, 3), seq, splits)
|
| 162 |
+
return output
|
bundle/plugin-site/ornith_g256/attention_storage.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
# Copyright 2026 Ciru.
|
| 3 |
+
"""Store target KV token-major inside the existing vLLM cache pages.
|
| 4 |
+
|
| 5 |
+
Allocation, block IDs, prefix ownership and cache size remain backend-owned.
|
| 6 |
+
The writer receives true BSHD views; stride-aware attention receives the
|
| 7 |
+
equivalent historical 5D/4D views. No second cache or history copy is created.
|
| 8 |
+
"""
|
| 9 |
+
import types
|
| 10 |
+
|
| 11 |
+
import torch
|
| 12 |
+
from vllm.logger import init_logger
|
| 13 |
+
|
| 14 |
+
logger = init_logger('vllm.ornith_g256.attention_storage')
|
| 15 |
+
_installed = False
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def _flash_views(cache):
|
| 19 |
+
# Bound backend cache is [blocks, K/V, tokens, heads * dim].
|
| 20 |
+
page_size = cache.shape[2] if cache.ndim == 4 else 0
|
| 21 |
+
if (page_size not in (1120, 2240) or cache.shape[1:] != (2, page_size, 512)
|
| 22 |
+
or cache.dtype != torch.bfloat16
|
| 23 |
+
or cache.stride(2) != 512 or cache.stride(3) != 1):
|
| 24 |
+
raise ValueError('Token-major target KV requires BF16 [B,2,page,512], page1120/2240')
|
| 25 |
+
blocks = cache.shape[0]
|
| 26 |
+
return (cache[:, 0].view(blocks, page_size, 2, 256),
|
| 27 |
+
cache[:, 1].view(blocks, page_size, 2, 256))
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def _split_for_attention(cache, num_heads, head_size):
|
| 31 |
+
# RocmAttentionImpl passes the K/V-first transpose to this seam.
|
| 32 |
+
if (num_heads, head_size) != (2, 256):
|
| 33 |
+
raise ValueError('Token-major cache views are target-specific')
|
| 34 |
+
key, value = _flash_views(cache.transpose(0, 1))
|
| 35 |
+
blocks, page_size = key.shape[:2]
|
| 36 |
+
key_view = key.as_strided((blocks, 2, 32, page_size, 8),
|
| 37 |
+
(key.stride(0), 256, 8, 512, 1))
|
| 38 |
+
value_view = value.as_strided((blocks, 2, 256, page_size),
|
| 39 |
+
(value.stride(0), 256, 1, 512))
|
| 40 |
+
return key_view, value_view
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def install():
|
| 44 |
+
"""Install only in an isolated worker before loading/capturing the model."""
|
| 45 |
+
global _installed
|
| 46 |
+
if _installed:
|
| 47 |
+
return
|
| 48 |
+
from vllm._aiter_ops import rocm_aiter_ops
|
| 49 |
+
from vllm.v1.attention.backend import AttentionType
|
| 50 |
+
from vllm.v1.attention.backends import rocm_attn
|
| 51 |
+
from vllm.v1.attention.ops.triton_reshape_and_cache_flash import (
|
| 52 |
+
triton_reshape_and_cache_flash,
|
| 53 |
+
)
|
| 54 |
+
|
| 55 |
+
if rocm_aiter_ops.is_enabled():
|
| 56 |
+
raise ValueError('Token-major KV currently uses the unfused ROCm cache writer')
|
| 57 |
+
impl = rocm_attn.RocmAttentionImpl
|
| 58 |
+
original_forward = impl.forward
|
| 59 |
+
original_update = impl.do_kv_cache_update
|
| 60 |
+
namespace = dict(original_forward.__globals__)
|
| 61 |
+
namespace['PagedAttention'] = types.SimpleNamespace(
|
| 62 |
+
split_kv_cache=_split_for_attention)
|
| 63 |
+
forward_with_views = types.FunctionType(
|
| 64 |
+
original_forward.__code__, namespace, 'forward_token_major',
|
| 65 |
+
original_forward.__defaults__, original_forward.__closure__)
|
| 66 |
+
forward_with_views.__kwdefaults__ = original_forward.__kwdefaults__
|
| 67 |
+
|
| 68 |
+
def target(self):
|
| 69 |
+
return (self.attn_type == AttentionType.DECODER
|
| 70 |
+
and (self.num_heads, self.num_kv_heads, self.head_size) == (16, 2, 256)
|
| 71 |
+
and self.kv_cache_dtype == 'auto'
|
| 72 |
+
and self.alibi_slopes is None and self.sinks is None
|
| 73 |
+
and self.sliding_window == (-1, -1)
|
| 74 |
+
and self.logits_soft_cap == 0)
|
| 75 |
+
|
| 76 |
+
def forward(self, layer, query, key, value, kv_cache, attn_metadata,
|
| 77 |
+
output, output_scale=None, output_block_scale=None):
|
| 78 |
+
selected = forward_with_views if target(self) else original_forward
|
| 79 |
+
return selected(self, layer, query, key, value, kv_cache, attn_metadata,
|
| 80 |
+
output, output_scale, output_block_scale)
|
| 81 |
+
|
| 82 |
+
def update(self, layer, key, value, kv_cache, slot_mapping):
|
| 83 |
+
if not target(self):
|
| 84 |
+
return original_update(self, layer, key, value, kv_cache, slot_mapping)
|
| 85 |
+
key_cache, value_cache = _flash_views(kv_cache)
|
| 86 |
+
triton_reshape_and_cache_flash(
|
| 87 |
+
key, value, key_cache, value_cache, slot_mapping,
|
| 88 |
+
self.kv_cache_dtype, layer._k_scale, layer._v_scale)
|
| 89 |
+
logger.info_once('Ornith target KV uses token-major pages in the existing cache allocation')
|
| 90 |
+
|
| 91 |
+
impl.forward = forward
|
| 92 |
+
impl.do_kv_cache_update = update
|
| 93 |
+
_installed = True
|
bundle/plugin-site/ornith_g256/attention_tile.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
# Copyright 2026 Ciru. Wrappers around the installed vLLM attention implementation.
|
| 3 |
+
"""Small query tiles plus bounded sliding-window context loads for the drafter."""
|
| 4 |
+
import hashlib
|
| 5 |
+
import inspect
|
| 6 |
+
import types
|
| 7 |
+
import torch
|
| 8 |
+
|
| 9 |
+
from vllm.v1.attention.ops import prefix_prefill
|
| 10 |
+
from vllm.v1.attention.ops.chunked_prefill_paged_decode import chunked_prefill_paged_decode
|
| 11 |
+
from .attention_window import build_query_kernel, build_window_kernel
|
| 12 |
+
from .attention_partition import forward as partitioned_attention
|
| 13 |
+
|
| 14 |
+
_compact_prefill = False
|
| 15 |
+
_folded_decode = False
|
| 16 |
+
_folded_decode_max_queries = 8
|
| 17 |
+
|
| 18 |
+
original_source = inspect.getsource(prefix_prefill.context_attention_fwd)
|
| 19 |
+
replacement = ' BLOCK_M = 16 if 1 <= max_input_len <= 8 else 32'
|
| 20 |
+
assert original_source.count(' BLOCK_M = 32') == 1
|
| 21 |
+
modified_source = original_source.replace(' BLOCK_M = 32', replacement)
|
| 22 |
+
assert modified_source.count('_fwd_kernel[') == 1
|
| 23 |
+
modified_source = modified_source.replace(
|
| 24 |
+
'_fwd_kernel[',
|
| 25 |
+
'(_window_kernel if sliding_window > 0 else _query_kernel)[')
|
| 26 |
+
helper_namespace = dict(vars(prefix_prefill))
|
| 27 |
+
helper_namespace['_window_kernel'] = build_window_kernel(prefix_prefill)
|
| 28 |
+
helper_namespace['_query_kernel'] = build_query_kernel(prefix_prefill)
|
| 29 |
+
exec(compile(modified_source, __file__ + ':context_attention_fwd', 'exec'), helper_namespace)
|
| 30 |
+
context_attention_fwd_tile16 = helper_namespace['context_attention_fwd']
|
| 31 |
+
caller_namespace = dict(chunked_prefill_paged_decode.__globals__)
|
| 32 |
+
caller_namespace['context_attention_fwd'] = context_attention_fwd_tile16
|
| 33 |
+
_tiled_attention = types.FunctionType(
|
| 34 |
+
chunked_prefill_paged_decode.__code__, caller_namespace,
|
| 35 |
+
'chunked_prefill_paged_decode_tile16', chunked_prefill_paged_decode.__defaults__,
|
| 36 |
+
chunked_prefill_paged_decode.__closure__)
|
| 37 |
+
_tiled_attention.__kwdefaults__ = chunked_prefill_paged_decode.__kwdefaults__
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def chunked_prefill_paged_decode_tile16(
|
| 41 |
+
query, key, value, output, kv_cache_dtype, key_cache, value_cache,
|
| 42 |
+
block_table, query_start_loc, seq_lens, max_seq_len, max_query_len,
|
| 43 |
+
k_scale, v_scale, alibi_slopes=None, sliding_window=None, sm_scale=None,
|
| 44 |
+
output_scale=None, sinks=None, is_block_table_ptr=False, causal=True,
|
| 45 |
+
):
|
| 46 |
+
page_size = key_cache.shape[3] if key_cache.ndim == 5 else 0
|
| 47 |
+
target_cache = (page_size in (1120, 2240)
|
| 48 |
+
and key_cache.shape[1:] == (2, 32, page_size, 8)
|
| 49 |
+
and value_cache.shape[1:] == (2, 256, page_size))
|
| 50 |
+
# Current K/V has already been written by the ROCm backend. Split only
|
| 51 |
+
# mixed target calls, preserving the existing per-shape attention choices.
|
| 52 |
+
if (_compact_prefill and _folded_decode and target_cache
|
| 53 |
+
and query.shape[1:] == (16, 256)
|
| 54 |
+
and query.dtype == key_cache.dtype == value_cache.dtype == output.dtype == torch.bfloat16
|
| 55 |
+
and kv_cache_dtype == 'auto' and causal and not is_block_table_ptr
|
| 56 |
+
and alibi_slopes is None and sinks is None and output_scale is None
|
| 57 |
+
and (sliding_window is None or sliding_window <= 0)):
|
| 58 |
+
from .attention_mixed import try_forward as mixed_forward
|
| 59 |
+
if mixed_forward(chunked_prefill_paged_decode_tile16,
|
| 60 |
+
query, key, value, output, kv_cache_dtype, key_cache, value_cache,
|
| 61 |
+
block_table, query_start_loc, seq_lens, max_seq_len, max_query_len,
|
| 62 |
+
k_scale, v_scale, alibi_slopes, sliding_window, sm_scale,
|
| 63 |
+
output_scale, sinks, is_block_table_ptr, causal):
|
| 64 |
+
return output
|
| 65 |
+
# Q1/C1 uses the existing partitioned decoder; shape-only eligibility
|
| 66 |
+
# is identical during graph capture and replay as sequence length grows.
|
| 67 |
+
if (_folded_decode and max_query_len == 1
|
| 68 |
+
and query.shape == (1,16,256) and seq_lens.numel() == 1
|
| 69 |
+
and target_cache
|
| 70 |
+
and query.dtype == key_cache.dtype == value_cache.dtype == output.dtype == torch.bfloat16
|
| 71 |
+
and kv_cache_dtype == 'auto' and causal and not is_block_table_ptr
|
| 72 |
+
and alibi_slopes is None and sinks is None and output_scale is None
|
| 73 |
+
and (sliding_window is None or sliding_window <= 0)):
|
| 74 |
+
return partitioned_attention(query,key_cache,value_cache,output,
|
| 75 |
+
block_table,query_start_loc,seq_lens,
|
| 76 |
+
sm_scale if sm_scale is not None else 256**-.5,
|
| 77 |
+
k_scale,v_scale,max_query_len=1)
|
| 78 |
+
# A 16-token verification block is two efficient eight-query/GQA tiles.
|
| 79 |
+
# Route it before generic prefill; the latter wastes the intended decode
|
| 80 |
+
# reuse and should not determine this block size's performance potential.
|
| 81 |
+
if (_folded_decode and 8 < max_query_len <= _folded_decode_max_queries
|
| 82 |
+
and query.shape[0] <= 128 and seq_lens.numel() <= 8
|
| 83 |
+
and query.shape[1:] == (16, 256)
|
| 84 |
+
and target_cache
|
| 85 |
+
and query.dtype == key_cache.dtype == value_cache.dtype == output.dtype == torch.bfloat16
|
| 86 |
+
and kv_cache_dtype == 'auto' and causal and not is_block_table_ptr
|
| 87 |
+
and alibi_slopes is None and sinks is None and output_scale is None
|
| 88 |
+
and (sliding_window is None or sliding_window <= 0)):
|
| 89 |
+
from .attention_folded import forward
|
| 90 |
+
return forward(query, key_cache, value_cache, output, block_table,
|
| 91 |
+
query_start_loc, seq_lens,
|
| 92 |
+
sm_scale if sm_scale is not None else 256**-.5,
|
| 93 |
+
k_scale, v_scale, max_query_len=max_query_len)
|
| 94 |
+
if (_compact_prefill and max_query_len > 8
|
| 95 |
+
and query.shape[1:] == (16, 256)
|
| 96 |
+
and target_cache
|
| 97 |
+
and query.dtype == key_cache.dtype == value_cache.dtype == output.dtype == torch.bfloat16
|
| 98 |
+
and kv_cache_dtype == 'auto' and causal and not is_block_table_ptr
|
| 99 |
+
and alibi_slopes is None and sinks is None and output_scale is None
|
| 100 |
+
and (sliding_window is None or sliding_window <= 0)):
|
| 101 |
+
from .attention_compact import try_forward
|
| 102 |
+
if try_forward(query, key_cache, value_cache, output, block_table,
|
| 103 |
+
query_start_loc, seq_lens, max_query_len=max_query_len,
|
| 104 |
+
max_seq_len=max_seq_len,
|
| 105 |
+
sm_scale=sm_scale if sm_scale is not None else 256**-.5):
|
| 106 |
+
return output
|
| 107 |
+
raise RuntimeError('Enabled compact prefill rejected target metadata: ' + str({
|
| 108 |
+
name: (tuple(t.shape), tuple(t.stride()), str(t.dtype), str(t.device))
|
| 109 |
+
for name, t in [('query', query), ('output', output), ('table', block_table),
|
| 110 |
+
('starts', query_start_loc), ('seq_lens', seq_lens)]
|
| 111 |
+
}) + f'; max_query_len={max_query_len}, max_seq_len={max_seq_len}')
|
| 112 |
+
# Parallel KV partitions hide the long-latency accesses of dispersed hybrid
|
| 113 |
+
# cache pages. Larger query batches reuse K/V better in the tiled prefill
|
| 114 |
+
# kernel; Q106 was slower with virtual decode, so keep the measured cutoff.
|
| 115 |
+
if (1 < max_query_len <= 8 and max_seq_len >= 8192
|
| 116 |
+
and query.shape[0] <= 64 and seq_lens.numel() <= 8
|
| 117 |
+
and query.shape[1:] == (16, 256)
|
| 118 |
+
and target_cache
|
| 119 |
+
and query.dtype == key_cache.dtype == value_cache.dtype == output.dtype == torch.bfloat16
|
| 120 |
+
and kv_cache_dtype == 'auto' and causal and not is_block_table_ptr
|
| 121 |
+
and alibi_slopes is None and sinks is None and output_scale is None
|
| 122 |
+
and (sliding_window is None or sliding_window <= 0)):
|
| 123 |
+
if _folded_decode:
|
| 124 |
+
from .attention_folded import forward as decode
|
| 125 |
+
else:
|
| 126 |
+
decode = partitioned_attention
|
| 127 |
+
return decode(
|
| 128 |
+
query, key_cache, value_cache, output, block_table, query_start_loc,
|
| 129 |
+
seq_lens, sm_scale if sm_scale is not None else 256**-.5,
|
| 130 |
+
k_scale, v_scale, max_query_len=max_query_len)
|
| 131 |
+
return _tiled_attention(
|
| 132 |
+
query, key, value, output, kv_cache_dtype, key_cache, value_cache,
|
| 133 |
+
block_table, query_start_loc, seq_lens, max_seq_len, max_query_len,
|
| 134 |
+
k_scale, v_scale, alibi_slopes, sliding_window, sm_scale, output_scale,
|
| 135 |
+
sinks, is_block_table_ptr, causal)
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
provenance = dict(source_file=prefix_prefill.__file__,
|
| 139 |
+
original_helper_sha256=hashlib.sha256(original_source.encode()).hexdigest(),
|
| 140 |
+
modified_helper_sha256=hashlib.sha256(modified_source.encode()).hexdigest(),
|
| 141 |
+
exact_change=replacement.strip(),
|
| 142 |
+
window_change='Skip and mask context K/V outside the earliest query-row window',
|
| 143 |
+
query_change='Return before context work for query tiles with no output rows',
|
| 144 |
+
decode_change='Target page1120/2240, context>=8192, maxQ2..8: native-layout32-way KV partitioning; optional maxQ16 folding',
|
| 145 |
+
unchanged='Full-attention arithmetic, BLOCK_N32, cache tile32, four warps, one stage')
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def install(*, compact_prefill=False, folded_decode=False, folded_decode_max_queries=8):
|
| 149 |
+
"""Select this helper for target and draft ROCm attention in this worker."""
|
| 150 |
+
global _compact_prefill, _folded_decode, _folded_decode_max_queries
|
| 151 |
+
if folded_decode_max_queries not in (8, 16):
|
| 152 |
+
raise ValueError('Folded verification supports eight or sixteen query positions')
|
| 153 |
+
_compact_prefill = compact_prefill
|
| 154 |
+
_folded_decode = folded_decode
|
| 155 |
+
_folded_decode_max_queries = folded_decode_max_queries
|
| 156 |
+
from vllm.v1.attention.backends import rocm_attn
|
| 157 |
+
current = rocm_attn.chunked_prefill_paged_decode
|
| 158 |
+
if current not in (chunked_prefill_paged_decode, chunked_prefill_paged_decode_tile16):
|
| 159 |
+
raise RuntimeError('Another extension replaced the ROCm prefill helper')
|
| 160 |
+
rocm_attn.chunked_prefill_paged_decode = chunked_prefill_paged_decode_tile16
|
bundle/plugin-site/ornith_g256/attention_verify.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Optional page1104 multi-query verification, with prebound GPU metadata."""
|
| 2 |
+
# Copyright 2026 Ciru.
|
| 3 |
+
import ctypes
|
| 4 |
+
from types import SimpleNamespace
|
| 5 |
+
|
| 6 |
+
import torch
|
| 7 |
+
from .attention_fast import PagedLayout, PagedStrides
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
class NativeVerifyFP32:
|
| 11 |
+
page_size = 1104
|
| 12 |
+
|
| 13 |
+
def __init__(self, library, *, Ccap, Lcap, device):
|
| 14 |
+
if not 1 <= Ccap <= 64 or not 1 <= Lcap <= 8192:
|
| 15 |
+
raise ValueError('Verify attention supports <=64 queries, context <=8192')
|
| 16 |
+
self.Ccap, self.Lcap, self.device = Ccap, Lcap, torch.device(device)
|
| 17 |
+
self.library = str(library)
|
| 18 |
+
self.lib = ctypes.CDLL(self.library)
|
| 19 |
+
self.lib.ornith_verify_paged_abi_version.restype = ctypes.c_uint32
|
| 20 |
+
if self.lib.ornith_verify_paged_abi_version() != 1:
|
| 21 |
+
raise ValueError('Expected verification ABI1')
|
| 22 |
+
self.lib.ornith_verify_paged_get_layout.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.POINTER(PagedLayout)]
|
| 23 |
+
self.lib.ornith_verify_paged_get_layout.restype = ctypes.c_int
|
| 24 |
+
self.lib.ornith_verify_paged_launch.argtypes = ([ctypes.c_void_p] * 6 + [ctypes.c_size_t]
|
| 25 |
+
+ [ctypes.c_void_p] * 3 + [ctypes.c_int] * 4 + [PagedStrides, ctypes.c_void_p])
|
| 26 |
+
self.lib.ornith_verify_paged_launch.restype = ctypes.c_int
|
| 27 |
+
self.lib.ornith_verify_metadata_launch.argtypes = ([ctypes.c_void_p] * 6
|
| 28 |
+
+ [ctypes.c_int] * 4 + [ctypes.c_int64, ctypes.c_void_p])
|
| 29 |
+
self.lib.ornith_verify_metadata_launch.restype = ctypes.c_int
|
| 30 |
+
layout = PagedLayout()
|
| 31 |
+
if self.lib.ornith_verify_paged_get_layout(Ccap, Lcap, ctypes.byref(layout)):
|
| 32 |
+
raise ValueError('Verify workspace query failed')
|
| 33 |
+
self.workspace_bytes = layout.bytes
|
| 34 |
+
self.output_offset = (layout.bytes + 255) // 256 * 256
|
| 35 |
+
self.table_offset = self.output_offset + Ccap * 16 * 256 * 4
|
| 36 |
+
self.table_cols = (Lcap + self.page_size - 1) // self.page_size
|
| 37 |
+
self.lengths_offset = self.table_offset + Ccap * self.table_cols * 4
|
| 38 |
+
self.arena_bytes = self.lengths_offset + Ccap * 4
|
| 39 |
+
|
| 40 |
+
def bind_arena(self, arena):
|
| 41 |
+
if (arena.dtype != torch.uint8 or arena.ndim != 1 or not arena.is_contiguous()
|
| 42 |
+
or arena.numel() < self.arena_bytes or arena.data_ptr() % 256
|
| 43 |
+
or arena.device != self.device):
|
| 44 |
+
raise ValueError('Invalid verification attention arena')
|
| 45 |
+
return SimpleNamespace(
|
| 46 |
+
workspace=arena[:self.workspace_bytes],
|
| 47 |
+
output=arena[self.output_offset:self.table_offset].view(torch.float32),
|
| 48 |
+
table=arena[self.table_offset:self.lengths_offset].view(torch.int32),
|
| 49 |
+
lengths=arena[self.lengths_offset:self.arena_bytes].view(torch.int32))
|
| 50 |
+
|
| 51 |
+
def launch_out(self, query, key, value, table, lengths, starts, actual, buffers, flag, output):
|
| 52 |
+
stream = torch.cuda.current_stream(query.device).cuda_stream
|
| 53 |
+
status = self.lib.ornith_verify_metadata_launch(
|
| 54 |
+
*[t.data_ptr() for t in (table, lengths, starts, buffers.table, buffers.lengths, flag)],
|
| 55 |
+
query.shape[0], lengths.shape[0], actual, self.table_cols, table.stride(0), stream)
|
| 56 |
+
if status:
|
| 57 |
+
raise RuntimeError(f'Verify metadata launch failed: {status}')
|
| 58 |
+
strides = PagedStrides(*query.stride()[:2], *key.stride(), *value.stride(), self.table_cols)
|
| 59 |
+
status = self.lib.ornith_verify_paged_launch(
|
| 60 |
+
*[t.data_ptr() for t in (query, key, value, buffers.table, buffers.lengths, buffers.workspace)],
|
| 61 |
+
buffers.workspace.numel(), buffers.output.data_ptr(), output.data_ptr(), flag.data_ptr(),
|
| 62 |
+
query.shape[0], self.Ccap, self.Lcap, key.shape[0], strides, stream)
|
| 63 |
+
if status:
|
| 64 |
+
raise RuntimeError(f'Verify attention launch failed: {status}')
|
bundle/plugin-site/ornith_g256/attention_window.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
# Copyright 2026 Ciru. Derived at runtime from the installed vLLM kernel.
|
| 3 |
+
"""Skip empty query tiles and context outside the drafter's sliding window.
|
| 4 |
+
|
| 5 |
+
Masking probabilities alone is insufficient when an evicted cache entry points
|
| 6 |
+
to the null page: zero times a nonfinite V still contaminates the accumulator.
|
| 7 |
+
The remaining per-row attention mask preserves the installed window semantics.
|
| 8 |
+
"""
|
| 9 |
+
import inspect
|
| 10 |
+
import linecache
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def _skip_empty_queries(source):
|
| 14 |
+
marker = ' block_start_loc = BLOCK_M * start_m\n'
|
| 15 |
+
if source.count(marker) != 1:
|
| 16 |
+
raise RuntimeError('Unsupported installed prefix kernel: query tile structure')
|
| 17 |
+
return source.replace(marker, marker +
|
| 18 |
+
' if block_start_loc >= cur_batch_query_len:\n'
|
| 19 |
+
' return\n')
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def _compile_kernel(prefix_prefill, source, variant):
|
| 23 |
+
# Triton obtains source through inspect; keep generated sources available
|
| 24 |
+
# under distinct project-owned filenames without modifying installed vLLM.
|
| 25 |
+
filename = __file__ + '.' + variant + '.generated'
|
| 26 |
+
linecache.cache[filename] = (len(source), None, source.splitlines(True), filename)
|
| 27 |
+
namespace = dict(vars(prefix_prefill))
|
| 28 |
+
exec(compile(source, filename, 'exec'), namespace)
|
| 29 |
+
return namespace['_fwd_kernel']
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def build_query_kernel(prefix_prefill):
|
| 33 |
+
"""Preserve full attention arithmetic, omitting tiles with no output rows."""
|
| 34 |
+
source = _skip_empty_queries(inspect.getsource(prefix_prefill._fwd_kernel.fn))
|
| 35 |
+
return _compile_kernel(prefix_prefill, source, 'query')
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def build_window_kernel(prefix_prefill):
|
| 39 |
+
source = _skip_empty_queries(inspect.getsource(prefix_prefill._fwd_kernel.fn))
|
| 40 |
+
old_loop = ''' # compute query against context (no causal mask here)
|
| 41 |
+
for start_n in tl.range(
|
| 42 |
+
0, cur_batch_ctx_len, BLOCK_SIZE, loop_unroll_factor=num_unroll_cache
|
| 43 |
+
):'''
|
| 44 |
+
new_loop = ''' # No row in this query tile can attend earlier context. Align the loop
|
| 45 |
+
# to the cache tile and mask its boundary loads as well as probabilities.
|
| 46 |
+
first_context_token = 0
|
| 47 |
+
if SLIDING_WINDOW > 0:
|
| 48 |
+
first_context_token = tl.maximum(
|
| 49 |
+
0, cur_batch_ctx_len + block_start_loc - SLIDING_WINDOW + 1
|
| 50 |
+
)
|
| 51 |
+
first_context_tile = (first_context_token // BLOCK_SIZE) * BLOCK_SIZE
|
| 52 |
+
for start_n in tl.range(
|
| 53 |
+
first_context_tile, cur_batch_ctx_len, BLOCK_SIZE,
|
| 54 |
+
loop_unroll_factor=num_unroll_cache
|
| 55 |
+
):'''
|
| 56 |
+
replacements = [(old_loop, new_loop)]
|
| 57 |
+
# These clauses occur once each for K and V in the context loop only.
|
| 58 |
+
old_condition = ''' start_n + BLOCK_SIZE > cur_batch_ctx_len
|
| 59 |
+
or BLOCK_DMODEL != BLOCK_DMODEL_PADDED'''
|
| 60 |
+
if source.count(old_condition) != 2:
|
| 61 |
+
raise RuntimeError('Unsupported installed prefix kernel: context load conditions')
|
| 62 |
+
source = source.replace(old_condition, ''' start_n < first_context_token
|
| 63 |
+
or start_n + BLOCK_SIZE > cur_batch_ctx_len
|
| 64 |
+
or BLOCK_DMODEL != BLOCK_DMODEL_PADDED''')
|
| 65 |
+
for indices in ('offs_bs_n[None, :]', 'offs_bs_n[:, None]'):
|
| 66 |
+
old = f'& ((start_n + {indices}) < cur_batch_ctx_len),'
|
| 67 |
+
new = (f'& ((start_n + {indices}) < cur_batch_ctx_len)\n'
|
| 68 |
+
f' & ((start_n + {indices}) >= first_context_token),')
|
| 69 |
+
replacements.append((old, new))
|
| 70 |
+
for old, new in replacements:
|
| 71 |
+
if source.count(old) != 1:
|
| 72 |
+
raise RuntimeError('Unsupported installed prefix kernel: context window structure')
|
| 73 |
+
source = source.replace(old, new)
|
| 74 |
+
return _compile_kernel(prefix_prefill, source, 'window')
|
bundle/plugin-site/ornith_g256/cache_full1120.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Isolated full-state1120 cache geometry; original tensors and arithmetic."""
|
| 2 |
+
import logging
|
| 3 |
+
from dataclasses import replace
|
| 4 |
+
|
| 5 |
+
PAGE_BYTES = 2_392_064
|
| 6 |
+
TARGET_BLOCK = 1120
|
| 7 |
+
DRAFT_BLOCK = 560
|
| 8 |
+
_upstream_align = None
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def enabled(config):
|
| 13 |
+
return (getattr(config.model_config, 'quantization', None) == 'ornith_g256'
|
| 14 |
+
and config.cache_config.enable_prefix_caching
|
| 15 |
+
and config.additional_config.get('ornith_g256', {}).get('dynamic_spec_profile', False))
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def _align(cls, vllm_config, backend_cls):
|
| 19 |
+
_upstream_align(cls, vllm_config, backend_cls)
|
| 20 |
+
if not enabled(vllm_config):
|
| 21 |
+
return
|
| 22 |
+
cache = vllm_config.cache_config
|
| 23 |
+
spec = vllm_config.speculative_config
|
| 24 |
+
assert cache.mamba_cache_mode == 'align' and cache.prefix_match_unit is None
|
| 25 |
+
assert spec.method == 'dflash' and spec.num_speculative_tokens == 15
|
| 26 |
+
cache.block_size = TARGET_BLOCK
|
| 27 |
+
cache.mamba_block_size = TARGET_BLOCK
|
| 28 |
+
cache.mamba_page_size_padded = PAGE_BYTES
|
| 29 |
+
logger.info('Ornith full1120 platform: target/Mamba=%s draft=%s physical_page=%s',
|
| 30 |
+
TARGET_BLOCK, DRAFT_BLOCK, PAGE_BYTES)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def padded_specs(config, specs):
|
| 34 |
+
if not enabled(config):
|
| 35 |
+
return specs
|
| 36 |
+
from vllm.v1.kv_cache_interface import FullAttentionSpec, MambaSpec, SlidingWindowSpec
|
| 37 |
+
from .prefix_cache import EXPECTED_DRAFT_NAMES
|
| 38 |
+
drafts = {name for name in specs if name.startswith('model.layers.')}
|
| 39 |
+
targets = set(specs) - drafts
|
| 40 |
+
assert drafts == EXPECTED_DRAFT_NAMES
|
| 41 |
+
assert len(targets) == 40 and all(name.startswith('language_model.model.layers.') for name in targets)
|
| 42 |
+
assert sum(isinstance(specs[name], MambaSpec) for name in targets) == 30
|
| 43 |
+
assert sum(isinstance(specs[name], FullAttentionSpec) for name in targets) == 10
|
| 44 |
+
result = {}
|
| 45 |
+
for name, spec in specs.items():
|
| 46 |
+
if name in drafts:
|
| 47 |
+
assert isinstance(spec, SlidingWindowSpec) and spec.sliding_window == 4096
|
| 48 |
+
block = DRAFT_BLOCK
|
| 49 |
+
else:
|
| 50 |
+
block = TARGET_BLOCK
|
| 51 |
+
if isinstance(spec, MambaSpec):
|
| 52 |
+
assert spec.real_page_size_bytes == PAGE_BYTES
|
| 53 |
+
updated = replace(spec, block_size=block, page_size_padded=PAGE_BYTES)
|
| 54 |
+
assert updated.page_size_bytes == PAGE_BYTES
|
| 55 |
+
result[name] = updated
|
| 56 |
+
logger.info('Ornith full1120 specs: target_attention=10x1120 Mamba=30x1120 '
|
| 57 |
+
'draft=6x560 all_page_bytes=%s recurrent_dtype=%s', PAGE_BYTES,
|
| 58 |
+
next(spec.dtypes for spec in result.values() if isinstance(spec, MambaSpec)))
|
| 59 |
+
return result
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def install():
|
| 63 |
+
from vllm.platforms.interface import Platform
|
| 64 |
+
global _upstream_align
|
| 65 |
+
current = Platform._align_hybrid_block_size.__func__
|
| 66 |
+
if current is _align:
|
| 67 |
+
return
|
| 68 |
+
if _upstream_align is None:
|
| 69 |
+
_upstream_align = current
|
| 70 |
+
elif current is not _upstream_align:
|
| 71 |
+
raise RuntimeError('Another extension replaced hybrid cache alignment')
|
| 72 |
+
Platform._align_hybrid_block_size = classmethod(_align)
|
bundle/plugin-site/ornith_g256/column_backend.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Parent32tile semantics with four output-column workgroups per KV head."""
|
| 2 |
+
# Copyright2026 Ciru.
|
| 3 |
+
from collections import Counter
|
| 4 |
+
import torch
|
| 5 |
+
from vllm.config import get_current_vllm_config
|
| 6 |
+
from vllm.v1.attention.backend import AttentionType
|
| 7 |
+
from vllm.v1.attention.ops.paged_attn import PagedAttention
|
| 8 |
+
from vllm.v1.attention.backends.rocm_attn import RocmAttentionBackend,RocmAttentionImpl
|
| 9 |
+
KERNEL_SHA256='27fb053d970184732a46212026afd3152ec07a0dfc3dc4c5cc5cc0d539f17c81'
|
| 10 |
+
from .column_kernel import ornith_column_paged_attention
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class OrnithColumnAttentionBackend(RocmAttentionBackend):
|
| 14 |
+
@staticmethod
|
| 15 |
+
def get_name(): return 'CUSTOM'
|
| 16 |
+
@staticmethod
|
| 17 |
+
def get_impl_cls(): return OrnithColumnAttentionImpl
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class OrnithColumnAttentionImpl(RocmAttentionImpl):
|
| 21 |
+
implementation='ciru.ornith.column.backend.v1'
|
| 22 |
+
def __init__(self,*args,**kwargs):
|
| 23 |
+
super().__init__(*args,**kwargs)
|
| 24 |
+
self.dispatch_counts=Counter()
|
| 25 |
+
self.capture_by_C=Counter()
|
| 26 |
+
self.eager_by_C=Counter()
|
| 27 |
+
self.context_bounds=[None,None]
|
| 28 |
+
|
| 29 |
+
def static_native_support(self):
|
| 30 |
+
return (self.attn_type==AttentionType.DECODER
|
| 31 |
+
and (self.num_heads,self.num_kv_heads,self.head_size)==(16,2,256)
|
| 32 |
+
and self.scale==.0625 and self.kv_cache_dtype in ('auto','bfloat16')
|
| 33 |
+
and self.alibi_slopes is None and self.sliding_window==(-1,-1)
|
| 34 |
+
and self.logits_soft_cap==0 and self.sinks is None
|
| 35 |
+
and self.kv_sharing_target_layer_name is None)
|
| 36 |
+
|
| 37 |
+
def forward(self,layer,query,key,value,kv_cache,attn_metadata,output,
|
| 38 |
+
output_scale=None,output_block_scale=None):
|
| 39 |
+
m=attn_metadata;reason=None
|
| 40 |
+
if m is None: reason='profile'
|
| 41 |
+
elif not self.static_native_support(): reason='static_feature'
|
| 42 |
+
elif (m.use_cascade or m.causal is not True or output_scale is not None
|
| 43 |
+
or output_block_scale is not None): reason='metadata_feature'
|
| 44 |
+
elif m.max_query_len!=1: reason='prefill_or_mixed'
|
| 45 |
+
else:
|
| 46 |
+
C=m.seq_lens.shape[0]
|
| 47 |
+
if (C<1 or query.shape!=(C,16,256) or output.shape!=query.shape
|
| 48 |
+
or not 0<m.num_actual_tokens<=C or m.block_table.shape[0]!=C
|
| 49 |
+
or m.query_start_loc.shape!=(C+1,)):
|
| 50 |
+
reason='query_mapping'
|
| 51 |
+
elif (query.dtype!=torch.bfloat16 or output.dtype!=torch.bfloat16
|
| 52 |
+
or kv_cache.dtype!=torch.bfloat16 or m.block_table.dtype!=torch.int32
|
| 53 |
+
or m.seq_lens.dtype!=torch.int32 or m.query_start_loc.dtype!=torch.int32):
|
| 54 |
+
reason='dtype'
|
| 55 |
+
elif (query.stride(2)!=1 or output.stride(2)!=1 or m.block_table.stride(1)!=1
|
| 56 |
+
or not m.seq_lens.is_contiguous() or not m.query_start_loc.is_contiguous()):
|
| 57 |
+
reason='stride'
|
| 58 |
+
else:
|
| 59 |
+
kc,vc=PagedAttention.split_kv_cache(kv_cache.transpose(0,1),2,256)
|
| 60 |
+
if vc.shape[3]!=1056: reason='cache_page_size'
|
| 61 |
+
else:
|
| 62 |
+
# P4's ordinary opaque Attention op owns current-stream
|
| 63 |
+
# execution and its preceding inherited cache update.
|
| 64 |
+
ornith_column_paged_attention[(C,2,4)](output,query,kc,vc,None,m.block_table,
|
| 65 |
+
m.seq_lens,None,self.scale,layer._k_scale,layer._v_scale,1.,
|
| 66 |
+
num_query_heads=16,num_queries_per_kv=8,num_queries_per_kv_padded=16,
|
| 67 |
+
block_table_stride=m.block_table.stride(0),query_stride_0=query.stride(0),
|
| 68 |
+
query_stride_1=query.stride(1),output_stride_0=output.stride(0),
|
| 69 |
+
output_stride_1=output.stride(1),BLOCK_SIZE=32,PHYSICAL_BLOCK_SIZE=1056,
|
| 70 |
+
HEAD_SIZE=256,HEAD_SIZE_PADDED=256,USE_ALIBI_SLOPES=False,SLIDING_WINDOW=0,x=8,
|
| 71 |
+
stride_k_cache_0=kc.stride(0),stride_k_cache_1=kc.stride(1),
|
| 72 |
+
stride_k_cache_2=kc.stride(2),stride_k_cache_3=kc.stride(3),stride_k_cache_4=kc.stride(4),
|
| 73 |
+
stride_v_cache_0=vc.stride(0),stride_v_cache_1=vc.stride(1),
|
| 74 |
+
stride_v_cache_2=vc.stride(2),stride_v_cache_3=vc.stride(3),
|
| 75 |
+
filter_by_query_len=True,query_start_len_ptr=m.query_start_loc,USE_SINKS=False,USE_FP8=False)
|
| 76 |
+
capturing=torch.cuda.is_current_stream_capturing()
|
| 77 |
+
self.dispatch_counts['capture_native_calls' if capturing else 'eager_native_calls']+=1
|
| 78 |
+
(self.capture_by_C if capturing else self.eager_by_C)[str(C)]+=1
|
| 79 |
+
self.dispatch_counts['native_calls']+=1
|
| 80 |
+
if not capturing and m.max_seq_len>1:self.dispatch_counts['eager_cached_native_calls']+=1
|
| 81 |
+
lo,hi=self.context_bounds
|
| 82 |
+
self.context_bounds=[m.max_seq_len if lo is None else min(lo,m.max_seq_len),
|
| 83 |
+
m.max_seq_len if hi is None else max(hi,m.max_seq_len)]
|
| 84 |
+
return output
|
| 85 |
+
self.dispatch_counts['fallback_'+reason]+=1
|
| 86 |
+
return super().forward(layer,query,key,value,kv_cache,m,output,output_scale,output_block_scale)
|
| 87 |
+
|
| 88 |
+
def inspect_dispatch(self,prefix=None):
|
| 89 |
+
return {'implementation':self.implementation,'kernel_sha256':KERNEL_SHA256,'prefix':prefix,
|
| 90 |
+
'counts':dict(self.dispatch_counts),'capture_by_C':dict(self.capture_by_C),
|
| 91 |
+
'eager_by_C':dict(self.eager_by_C),'native_max_seq_len_host_bounds':list(self.context_bounds),
|
| 92 |
+
'counter_semantics':'Host dispatch counts; capture keyed by actual C/prefix, graph replay counted separately',
|
| 93 |
+
'capacity_policy':'inherits parent context/batch capacity; tested coverage recorded externally'}
|
bundle/plugin-site/ornith_g256/column_kernel.py
ADDED
|
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
| 3 |
+
|
| 4 |
+
# Authors:
|
| 5 |
+
# - Burkhard Ringlein <ngl@zurich.ibm.com>
|
| 6 |
+
# - Jan van Lunteren <jvl@zurich.ibm.com>
|
| 7 |
+
# - Chih-Chieh Yang <chih.chieh.yang@ibm.com>
|
| 8 |
+
# - Thomas Parnell <tpa@zurich.ibm.com>
|
| 9 |
+
|
| 10 |
+
# Ciru2026 modification: four contiguous64-column output partitions.
|
| 11 |
+
# Full D256 QK/all8 query rows (padded16) and32tile arithmetic are retained.
|
| 12 |
+
import torch
|
| 13 |
+
from vllm.platforms import current_platform
|
| 14 |
+
from vllm.triton_utils import tl, triton
|
| 15 |
+
float8_info = torch.finfo(current_platform.fp8_dtype())
|
| 16 |
+
|
| 17 |
+
@triton.jit
|
| 18 |
+
def cdiv_fn(x, y):
|
| 19 |
+
return (x + y - 1) // y
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
@triton.jit
|
| 23 |
+
def ornith_column_paged_attention(
|
| 24 |
+
output_ptr, # [num_tokens, num_query_heads, head_size]
|
| 25 |
+
query_ptr, # [num_tokens, num_query_heads, head_size]
|
| 26 |
+
key_cache_ptr, # [num_blks, num_kv_heads, head_size // x, blk_size, x]
|
| 27 |
+
value_cache_ptr, # [num_blks, num_kv_heads, head_size, blk_size]
|
| 28 |
+
sink_ptr, # [num_query_heads]
|
| 29 |
+
block_tables_ptr, # [num_seqs, max_num_blocks_per_seq]
|
| 30 |
+
seq_lens_ptr, # [num_seqs]
|
| 31 |
+
alibi_slopes_ptr, # [num_query_heads]
|
| 32 |
+
scale, # float32
|
| 33 |
+
k_scale, # float32
|
| 34 |
+
v_scale, # float32
|
| 35 |
+
out_scale_inv,
|
| 36 |
+
num_query_heads: tl.constexpr, # int
|
| 37 |
+
num_queries_per_kv: tl.constexpr, # int
|
| 38 |
+
num_queries_per_kv_padded: tl.constexpr, # int
|
| 39 |
+
block_table_stride: tl.int64, # int
|
| 40 |
+
query_stride_0: tl.int64, # int
|
| 41 |
+
query_stride_1: tl.int64, # int, should be equal to head_size
|
| 42 |
+
output_stride_0: tl.int64, # int
|
| 43 |
+
output_stride_1: tl.int64, # int, should be equal to head_size
|
| 44 |
+
BLOCK_SIZE: tl.constexpr, # int
|
| 45 |
+
PHYSICAL_BLOCK_SIZE: tl.constexpr, # int
|
| 46 |
+
HEAD_SIZE: tl.constexpr, # int
|
| 47 |
+
HEAD_SIZE_PADDED: tl.constexpr, # int, must be power of 2
|
| 48 |
+
USE_ALIBI_SLOPES: tl.constexpr, # bool
|
| 49 |
+
SLIDING_WINDOW: tl.constexpr, # int
|
| 50 |
+
x: tl.constexpr, # int
|
| 51 |
+
stride_k_cache_0: tl.int64, # int
|
| 52 |
+
stride_k_cache_1: tl.int64, # int
|
| 53 |
+
stride_k_cache_2: tl.int64, # int
|
| 54 |
+
stride_k_cache_3: tl.int64, # int
|
| 55 |
+
stride_k_cache_4: tl.int64, # int
|
| 56 |
+
stride_v_cache_0: tl.int64, # int
|
| 57 |
+
stride_v_cache_1: tl.int64, # int
|
| 58 |
+
stride_v_cache_2: tl.int64, # int
|
| 59 |
+
stride_v_cache_3: tl.int64, # int
|
| 60 |
+
filter_by_query_len: tl.constexpr, # bool
|
| 61 |
+
query_start_len_ptr, # [num_seqs+1]
|
| 62 |
+
USE_SINKS: tl.constexpr, # bool
|
| 63 |
+
USE_FP8: tl.constexpr,
|
| 64 |
+
FP8_MIN: tl.constexpr = float8_info.min,
|
| 65 |
+
FP8_MAX: tl.constexpr = float8_info.max,
|
| 66 |
+
):
|
| 67 |
+
seq_idx = tl.program_id(0)
|
| 68 |
+
kv_head_idx = tl.program_id(1)
|
| 69 |
+
output_partition = tl.program_id(2)
|
| 70 |
+
offs_v = output_partition * 64 + tl.arange(0, 64)
|
| 71 |
+
v_dim_mask = offs_v < HEAD_SIZE
|
| 72 |
+
|
| 73 |
+
if filter_by_query_len:
|
| 74 |
+
cur_batch_in_all_start_index = tl.load(query_start_len_ptr + seq_idx)
|
| 75 |
+
cur_batch_in_all_stop_index = tl.load(query_start_len_ptr + seq_idx + 1)
|
| 76 |
+
cur_batch_query_len = cur_batch_in_all_stop_index - cur_batch_in_all_start_index
|
| 77 |
+
if cur_batch_query_len > 1:
|
| 78 |
+
return
|
| 79 |
+
else:
|
| 80 |
+
cur_batch_in_all_start_index = seq_idx
|
| 81 |
+
|
| 82 |
+
query_head_idx = kv_head_idx * num_queries_per_kv + tl.arange(
|
| 83 |
+
0, num_queries_per_kv_padded
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
query_offset = (
|
| 87 |
+
cur_batch_in_all_start_index * query_stride_0
|
| 88 |
+
+ query_head_idx[:, None] * query_stride_1
|
| 89 |
+
)
|
| 90 |
+
|
| 91 |
+
head_mask = query_head_idx < (kv_head_idx + 1) * num_queries_per_kv
|
| 92 |
+
head_mask = head_mask & (query_head_idx < num_query_heads)
|
| 93 |
+
|
| 94 |
+
dim_mask = tl.where(tl.arange(0, HEAD_SIZE_PADDED) < HEAD_SIZE, 1, 0).to(tl.int1)
|
| 95 |
+
|
| 96 |
+
# Q : (num_queries_per_kv, HEAD_SIZE,)
|
| 97 |
+
Q = tl.load(
|
| 98 |
+
query_ptr + query_offset + tl.arange(0, HEAD_SIZE_PADDED)[None, :],
|
| 99 |
+
mask=dim_mask[None, :] & head_mask[:, None],
|
| 100 |
+
other=0.0,
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
block_table_offset = seq_idx * block_table_stride
|
| 104 |
+
|
| 105 |
+
if not USE_SINKS:
|
| 106 |
+
M = tl.full([num_queries_per_kv_padded], float("-inf"), dtype=tl.float32)
|
| 107 |
+
L = tl.zeros([num_queries_per_kv_padded], dtype=tl.float32)
|
| 108 |
+
else:
|
| 109 |
+
M = tl.load(
|
| 110 |
+
sink_ptr + query_head_idx,
|
| 111 |
+
mask=head_mask,
|
| 112 |
+
other=float("-inf"),
|
| 113 |
+
).to(dtype=tl.float32)
|
| 114 |
+
L = tl.where(float("-inf") < M, 1.0, 0.0)
|
| 115 |
+
|
| 116 |
+
acc = tl.zeros([num_queries_per_kv_padded, 64], dtype=tl.float32)
|
| 117 |
+
|
| 118 |
+
# sequence len for this particular sequence
|
| 119 |
+
seq_len = tl.load(seq_lens_ptr + seq_idx)
|
| 120 |
+
|
| 121 |
+
# alibi slope for this head
|
| 122 |
+
if USE_ALIBI_SLOPES:
|
| 123 |
+
alibi_slope = tl.load(
|
| 124 |
+
alibi_slopes_ptr + query_head_idx, mask=head_mask, other=0.0
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
num_blocks = cdiv_fn(seq_len, BLOCK_SIZE)
|
| 128 |
+
|
| 129 |
+
offs_n = tl.arange(0, BLOCK_SIZE)
|
| 130 |
+
offs_d = tl.arange(0, HEAD_SIZE_PADDED)
|
| 131 |
+
# iterate through tiles
|
| 132 |
+
for j in range(0, num_blocks):
|
| 133 |
+
start_n = j * BLOCK_SIZE
|
| 134 |
+
# Calculate the logical location within a non-standard physical block,
|
| 135 |
+
# such as 544 in Qwen/Qwen3-Next-80B-A3B-Thinking.
|
| 136 |
+
# Supports non-contiguous mapping
|
| 137 |
+
# from logical blocks to physical blocks
|
| 138 |
+
abs_token_idx = start_n + offs_n
|
| 139 |
+
l_block_idx = abs_token_idx // PHYSICAL_BLOCK_SIZE
|
| 140 |
+
# Vectorized loading of physical block IDs
|
| 141 |
+
p_block_idx = tl.load(block_tables_ptr + block_table_offset + l_block_idx)
|
| 142 |
+
internal_offsets = abs_token_idx % PHYSICAL_BLOCK_SIZE
|
| 143 |
+
|
| 144 |
+
# 5D addressing logic of K
|
| 145 |
+
k_offset = (
|
| 146 |
+
p_block_idx[None, :] * stride_k_cache_0
|
| 147 |
+
+ kv_head_idx * stride_k_cache_1
|
| 148 |
+
+ (offs_d[:, None] // x) * stride_k_cache_2
|
| 149 |
+
+ internal_offsets[None, :] * stride_k_cache_3
|
| 150 |
+
+ (offs_d[:, None] % x) * stride_k_cache_4
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
# 4D addressing logic of V (Slot is innermost)
|
| 154 |
+
v_offset = (
|
| 155 |
+
p_block_idx[:, None] * stride_v_cache_0
|
| 156 |
+
+ kv_head_idx * stride_v_cache_1
|
| 157 |
+
+ offs_v[None, :] * stride_v_cache_2
|
| 158 |
+
+ internal_offsets[:, None] * stride_v_cache_3
|
| 159 |
+
)
|
| 160 |
+
|
| 161 |
+
# Only the final tile can straddle seq_len. Slots >= seq_len are
|
| 162 |
+
# unwritten KV cache that may hold NaN/garbage; they are score-masked
|
| 163 |
+
# below, but 0 * NaN = NaN would still poison the output, so mask them
|
| 164 |
+
# out of the K/V loads too. Earlier tiles are fully written, so they
|
| 165 |
+
# use the cheaper token-uniform dim_mask (matching the pre-0.25.0 fast
|
| 166 |
+
# path) and skip the per-token predicate entirely.
|
| 167 |
+
# K : (HEAD_SIZE, BLOCK_SIZE), V : (BLOCK_SIZE, HEAD_SIZE)
|
| 168 |
+
if j == num_blocks - 1:
|
| 169 |
+
kv_load_mask = abs_token_idx < seq_len
|
| 170 |
+
K_load = tl.load(
|
| 171 |
+
key_cache_ptr + k_offset,
|
| 172 |
+
mask=dim_mask[:, None] & kv_load_mask[None, :],
|
| 173 |
+
other=0.0,
|
| 174 |
+
eviction_policy="evict_last",
|
| 175 |
+
)
|
| 176 |
+
V_load = tl.load(
|
| 177 |
+
value_cache_ptr + v_offset,
|
| 178 |
+
mask=v_dim_mask[None, :] & kv_load_mask[:, None],
|
| 179 |
+
other=0.0,
|
| 180 |
+
eviction_policy="evict_last",
|
| 181 |
+
)
|
| 182 |
+
else:
|
| 183 |
+
K_load = tl.load(
|
| 184 |
+
key_cache_ptr + k_offset,
|
| 185 |
+
mask=dim_mask[:, None],
|
| 186 |
+
other=0.0,
|
| 187 |
+
eviction_policy="evict_last",
|
| 188 |
+
)
|
| 189 |
+
V_load = tl.load(
|
| 190 |
+
value_cache_ptr + v_offset,
|
| 191 |
+
mask=v_dim_mask[None, :],
|
| 192 |
+
other=0.0,
|
| 193 |
+
eviction_policy="evict_last",
|
| 194 |
+
)
|
| 195 |
+
|
| 196 |
+
if K_load.dtype.is_fp8():
|
| 197 |
+
K = (K_load.to(tl.float32) * tl.load(k_scale)).to(Q.dtype)
|
| 198 |
+
else:
|
| 199 |
+
K = K_load
|
| 200 |
+
|
| 201 |
+
if V_load.dtype.is_fp8():
|
| 202 |
+
V = (V_load.to(tl.float32) * tl.load(v_scale)).to(Q.dtype)
|
| 203 |
+
else:
|
| 204 |
+
V = V_load
|
| 205 |
+
|
| 206 |
+
seq_offset = j * BLOCK_SIZE + tl.arange(0, BLOCK_SIZE)
|
| 207 |
+
boundary = tl.full([BLOCK_SIZE], seq_len, dtype=tl.int32)
|
| 208 |
+
seq_mask = seq_offset[None, :] < boundary
|
| 209 |
+
|
| 210 |
+
# First calculate the dot, then apply the mask.
|
| 211 |
+
qk = scale * tl.dot(Q, K)
|
| 212 |
+
S = tl.where(head_mask[:, None] & seq_mask, qk, float("-inf"))
|
| 213 |
+
|
| 214 |
+
context_len = seq_len - 1
|
| 215 |
+
|
| 216 |
+
if SLIDING_WINDOW > 0:
|
| 217 |
+
S = tl.where((context_len - seq_offset) < SLIDING_WINDOW, S, -10000)
|
| 218 |
+
|
| 219 |
+
if USE_ALIBI_SLOPES:
|
| 220 |
+
S += alibi_slope[:, None] * (seq_offset - context_len)
|
| 221 |
+
|
| 222 |
+
# compute running maximum
|
| 223 |
+
# m_j : (num_queries_per_kv,)
|
| 224 |
+
m_j = tl.maximum(M, tl.max(S, axis=1))
|
| 225 |
+
|
| 226 |
+
# P : (num_queries_per_kv, BLOCK_SIZE,)
|
| 227 |
+
p = tl.exp(S - m_j[:, None])
|
| 228 |
+
p = tl.where(m_j[:, None] == float("-inf"), 0.0, p)
|
| 229 |
+
|
| 230 |
+
# l_j : (num_queries_per_kv,)
|
| 231 |
+
l_j = tl.sum(p, axis=1)
|
| 232 |
+
|
| 233 |
+
# alpha : (num_queries_per_kv, )
|
| 234 |
+
alpha = tl.exp(M - m_j)
|
| 235 |
+
alpha = tl.where(float("-inf") == M, 0.0, alpha)
|
| 236 |
+
|
| 237 |
+
# acc : (num_queries_per_kv, BLOCK_SIZE,)
|
| 238 |
+
acc = acc * alpha[:, None]
|
| 239 |
+
|
| 240 |
+
# update constants
|
| 241 |
+
L = L * alpha + l_j
|
| 242 |
+
M = m_j
|
| 243 |
+
|
| 244 |
+
# acc : (num_queries_per_kv, BLOCK_SIZE,)
|
| 245 |
+
acc += tl.dot(p.to(V.dtype), V)
|
| 246 |
+
|
| 247 |
+
# epilogue
|
| 248 |
+
acc = acc / (L[:, None] + 1e-10)
|
| 249 |
+
if USE_FP8:
|
| 250 |
+
acc = acc * tl.load(out_scale_inv)
|
| 251 |
+
acc = tl.clamp(acc, FP8_MIN, FP8_MAX)
|
| 252 |
+
|
| 253 |
+
output_offset = (
|
| 254 |
+
cur_batch_in_all_start_index * output_stride_0
|
| 255 |
+
+ query_head_idx * output_stride_1
|
| 256 |
+
)
|
| 257 |
+
|
| 258 |
+
tl.store(
|
| 259 |
+
output_ptr + output_offset[:, None] + offs_v[None, :],
|
| 260 |
+
acc,
|
| 261 |
+
mask=v_dim_mask[None, :] & head_mask[:, None],
|
| 262 |
+
)
|
| 263 |
+
|
bundle/plugin-site/ornith_g256/config.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Explicit large-projection policy; routing and small GDN controls stay BF16."""
|
| 2 |
+
# Copyright 2026 Ciru.
|
| 3 |
+
import re
|
| 4 |
+
import torch
|
| 5 |
+
from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
|
| 6 |
+
|
| 7 |
+
QUANT_CONFIG = dict(quant_method="ornith_g256", group_size=256,
|
| 8 |
+
transform_block=128, activation_bits=8)
|
| 9 |
+
DENSE_SUFFIXES = (".linear_attn.in_proj_qkvz", ".linear_attn.out_proj",
|
| 10 |
+
".self_attn.qkv_proj", ".self_attn.o_proj",
|
| 11 |
+
".mlp.shared_expert.gate_up_proj", ".mlp.shared_expert.down_proj")
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class OrnithG256Config(QuantizationConfig):
|
| 15 |
+
def __init__(self, config):
|
| 16 |
+
super().__init__()
|
| 17 |
+
if any(config.get(k) != v for k, v in QUANT_CONFIG.items()):
|
| 18 |
+
raise ValueError(f"Expected {QUANT_CONFIG}")
|
| 19 |
+
self.checkpoint_config = dict(config)
|
| 20 |
+
self.activation_bits, self.transform_block = 8, 128
|
| 21 |
+
|
| 22 |
+
@classmethod
|
| 23 |
+
def get_name(cls): return "ornith_g256"
|
| 24 |
+
def get_supported_act_dtypes(self): return [torch.bfloat16]
|
| 25 |
+
@classmethod
|
| 26 |
+
def get_min_capability(cls): return 0
|
| 27 |
+
@staticmethod
|
| 28 |
+
def get_config_filenames(): return ["quantize_config.json"]
|
| 29 |
+
@classmethod
|
| 30 |
+
def from_config(cls, config): return cls(config)
|
| 31 |
+
|
| 32 |
+
def get_quant_method(self, layer, prefix):
|
| 33 |
+
from vllm.model_executor.layers.fused_moe.routed_experts import RoutedExperts
|
| 34 |
+
from vllm.model_executor.layers.linear import LinearBase, UnquantizedLinearMethod
|
| 35 |
+
from vllm.model_executor.layers.vocab_parallel_embedding import (
|
| 36 |
+
ParallelLMHead, VocabParallelEmbedding, UnquantizedEmbeddingMethod,
|
| 37 |
+
)
|
| 38 |
+
from .method import G256MoEMethod, G256LinearMethod, W8HeadMethod
|
| 39 |
+
# MTP weights retain BF16; its temporary head is replaced with the
|
| 40 |
+
# target W8 head by the installed proposer after loading.
|
| 41 |
+
if prefix.startswith('mtp.'):
|
| 42 |
+
if isinstance(layer, RoutedExperts):
|
| 43 |
+
from vllm.model_executor.layers.fused_moe.unquantized_fused_moe_method import UnquantizedFusedMoEMethod
|
| 44 |
+
return UnquantizedFusedMoEMethod(layer.moe_config)
|
| 45 |
+
if isinstance(layer, LinearBase):
|
| 46 |
+
return UnquantizedLinearMethod()
|
| 47 |
+
if isinstance(layer, RoutedExperts):
|
| 48 |
+
if not re.search(r"layers\.[0-9]+\.mlp\.experts$", prefix):
|
| 49 |
+
raise ValueError(f"Unsupported routed module {prefix}")
|
| 50 |
+
G256MoEMethod.validate_layer_scope(layer)
|
| 51 |
+
return G256MoEMethod(layer.moe_config, self, prefix)
|
| 52 |
+
if isinstance(layer, ParallelLMHead):
|
| 53 |
+
return W8HeadMethod(prefix)
|
| 54 |
+
if isinstance(layer, LinearBase):
|
| 55 |
+
return G256LinearMethod(prefix) if prefix.endswith(DENSE_SUFFIXES) else UnquantizedLinearMethod()
|
| 56 |
+
if isinstance(layer, VocabParallelEmbedding):
|
| 57 |
+
return UnquantizedEmbeddingMethod()
|
| 58 |
+
return None
|
bundle/plugin-site/ornith_g256/dense_n32.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Isolated M1 dense N32 shadows; retain N16 verification and prefill."""
|
| 2 |
+
# Copyright 2026 Ciru.
|
| 3 |
+
import ctypes as C
|
| 4 |
+
import json
|
| 5 |
+
from pathlib import Path
|
| 6 |
+
import torch
|
| 7 |
+
_INSTALLED=False
|
| 8 |
+
_BANKS={}
|
| 9 |
+
_TOTAL=0
|
| 10 |
+
_LIB=None
|
| 11 |
+
_LAUNCH=None
|
| 12 |
+
|
| 13 |
+
def install():
|
| 14 |
+
global _INSTALLED,_LIB,_LAUNCH
|
| 15 |
+
if _INSTALLED:return
|
| 16 |
+
from . import native
|
| 17 |
+
from .method import G256LinearMethod
|
| 18 |
+
library=Path(__file__).resolve().parents[2]/'native/libornith_dense_g256_n32.so'
|
| 19 |
+
_LIB=C.CDLL(str(library.resolve(strict=True)))
|
| 20 |
+
_LAUNCH=_LIB.ornith_dense_g256_launch_n32
|
| 21 |
+
_LAUNCH.argtypes=[C.c_void_p]*4+[C.c_size_t]+[C.c_void_p]*2+[C.c_int]*7+[C.c_void_p]
|
| 22 |
+
_LAUNCH.restype=C.c_int
|
| 23 |
+
original_load=G256LinearMethod.process_weights_after_loading
|
| 24 |
+
def load(self,layer):
|
| 25 |
+
global _TOTAL
|
| 26 |
+
original_load(self,layer)
|
| 27 |
+
if self.fields != ('g256_codes','g256_metadata'):return
|
| 28 |
+
n,k=self.n,self.k
|
| 29 |
+
if n%32 or not 0<n<=12288 or k%256 or not 0<k<=8192:
|
| 30 |
+
raise ValueError(f'Dense N32 unsupported N{n}/K{k}')
|
| 31 |
+
codes=layer.g256_codes;meta=layer.g256_metadata
|
| 32 |
+
c32=codes.reshape(n//32,2,k//256,32,16).permute(0,2,3,1,4).contiguous().reshape(n//32,k//256,32,32)
|
| 33 |
+
m32=meta.reshape(n//32,2,k//256,16).permute(0,2,1,3).contiguous().reshape(n//32,k//256,32)
|
| 34 |
+
layer.register_buffer('_dense_n32_codes',c32,persistent=False)
|
| 35 |
+
layer.register_buffer('_dense_n32_metadata',m32,persistent=False)
|
| 36 |
+
key=codes.data_ptr()
|
| 37 |
+
if key in _BANKS:raise RuntimeError('Dense N32 bank prepared twice')
|
| 38 |
+
_BANKS[key]=(c32,m32)
|
| 39 |
+
_TOTAL+=c32.numel()*c32.element_size()+m32.numel()*m32.element_size()
|
| 40 |
+
if len(_BANKS) in (1,160):
|
| 41 |
+
print('ORNITH_DENSE_N32 '+json.dumps({'matrices':len(_BANKS),'extra_payload_bytes':_TOTAL,'last_shape':[n,k],'scope':'M1 only; N16 retained'}),flush=True)
|
| 42 |
+
G256LinearMethod.process_weights_after_loading=load
|
| 43 |
+
original_dense=native._dense_impl
|
| 44 |
+
def dense(x,codes,metadata,workspace,out,flags,capacity,n,k,geometry,a8_max_rows):
|
| 45 |
+
if x.shape[0]==1 and a8_max_rows>=1 and geometry==2:
|
| 46 |
+
bank=_BANKS.get(codes.data_ptr())
|
| 47 |
+
if bank is None:raise RuntimeError('Missing dense N32 shadow for M1')
|
| 48 |
+
native.validate(x,out,capacity,k)
|
| 49 |
+
native.check(_LAUNCH(native.ptr(x),native.ptr(bank[0]),native.ptr(bank[1]),native.ptr(workspace),workspace.numel(),native.ptr(out),native.ptr(flags),1,capacity,n,k,8,128,2,native.stream(x)),'G256 dense N32 M1')
|
| 50 |
+
return
|
| 51 |
+
return original_dense(x,codes,metadata,workspace,out,flags,capacity,n,k,geometry,a8_max_rows)
|
| 52 |
+
native._dense_impl=dense
|
| 53 |
+
_INSTALLED=True
|
bundle/plugin-site/ornith_g256/dense_source.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Original BF16 selected dense weights for prefill; verification stays packed."""
|
| 2 |
+
from pathlib import Path
|
| 3 |
+
import torch
|
| 4 |
+
from safetensors import safe_open
|
| 5 |
+
_INSTALLED=False
|
| 6 |
+
_WEIGHTS={}
|
| 7 |
+
def install():
|
| 8 |
+
global _INSTALLED
|
| 9 |
+
if _INSTALLED:return
|
| 10 |
+
from . import native
|
| 11 |
+
from .method import G256LinearMethod
|
| 12 |
+
path=Path(__file__).resolve().parents[2]/'models/source-prefill.safetensors'
|
| 13 |
+
original_load=G256LinearMethod.process_weights_after_loading
|
| 14 |
+
def load(self,layer):
|
| 15 |
+
original_load(self,layer)
|
| 16 |
+
if not self.prefix.endswith(('.linear_attn.in_proj_qkvz','.mlp.shared_expert.gate_up_proj','.mlp.shared_expert.down_proj')):return
|
| 17 |
+
name=self.prefix.replace('language_model.model.','model.language_model.',1)
|
| 18 |
+
with safe_open(str(path),framework='pt',device='cpu') as f:w=f.get_tensor(name)
|
| 19 |
+
assert w.dtype==torch.bfloat16 and tuple(w.shape)==(self.n,self.k)
|
| 20 |
+
w=w.to(device=layer.g256_codes.device)
|
| 21 |
+
layer.register_buffer('_source_prefill_weight',w,persistent=False)
|
| 22 |
+
_WEIGHTS[layer.g256_codes.data_ptr()]=w
|
| 23 |
+
if len(_WEIGHTS) in (1,110):print('ORNITH_SOURCE_PREFILL',len(_WEIGHTS),sum(t.numel()*t.element_size() for t in _WEIGHTS.values()),flush=True)
|
| 24 |
+
G256LinearMethod.process_weights_after_loading=load
|
| 25 |
+
original_dense=native._dense_impl
|
| 26 |
+
def dense(x,codes,metadata,workspace,out,flags,capacity,n,k,geometry,a8_max_rows):
|
| 27 |
+
# M64 complete-operation measurements favor these two source shapes.
|
| 28 |
+
# The resident weights already exist for prefill; no additional allocation.
|
| 29 |
+
if x.shape[0]>a8_max_rows or (x.shape[0]==64 and
|
| 30 |
+
(n,k) in ((12288,2048),(1024,2048))):
|
| 31 |
+
weight=_WEIGHTS.get(codes.data_ptr())
|
| 32 |
+
if weight is not None:
|
| 33 |
+
torch.mm(x,weight.t(),out=out)
|
| 34 |
+
return
|
| 35 |
+
return original_dense(x,codes,metadata,workspace,out,flags,capacity,n,k,geometry,a8_max_rows)
|
| 36 |
+
native._dense_impl=dense
|
| 37 |
+
_INSTALLED=True
|
bundle/plugin-site/ornith_g256/dflash_conv_boundary.py
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Keep DFlash convolution request boundaries dynamic inside token-count graphs."""
|
| 2 |
+
# Copyright 2026 Ciru. Original convolution arithmetic from vLLM, Apache-2.0.
|
| 3 |
+
from contextvars import ContextVar
|
| 4 |
+
import inspect
|
| 5 |
+
import linecache
|
| 6 |
+
import logging
|
| 7 |
+
import torch
|
| 8 |
+
import torch.nn.functional as F
|
| 9 |
+
|
| 10 |
+
_ACTIVE_MASK = ContextVar('ornith_dflash_query_mask', default=None)
|
| 11 |
+
_INSTALLED = False
|
| 12 |
+
_ORIGINAL_CONVOLVE = None
|
| 13 |
+
_INPUT_KERNEL = None
|
| 14 |
+
logger = logging.getLogger(__name__)
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def grouped_conv_dynamic(hidden_states, delta, base, query_mask, num_groups, group_size, taps):
|
| 18 |
+
# Float expressions and evaluation order are unchanged from upstream.
|
| 19 |
+
blocks = hidden_states.unflatten(-1, (num_groups, group_size))
|
| 20 |
+
coefficients = base.view(1, taps, num_groups, group_size) + delta.unsqueeze(-1)
|
| 21 |
+
output = coefficients[:, 0] * blocks
|
| 22 |
+
position = torch.arange(hidden_states.shape[0], device=hidden_states.device)
|
| 23 |
+
position = position & query_mask
|
| 24 |
+
for tap in range(1, taps):
|
| 25 |
+
shifted = F.pad(blocks[:-tap], (0, 0, 0, 0, tap, 0))
|
| 26 |
+
output += coefficients[:, tap] * shifted * (position >= tap).view(-1, 1, 1)
|
| 27 |
+
return output.flatten(-2)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def convolve(self, hidden_states, delta, side):
|
| 31 |
+
mask = getattr(self, '_ornith_query_mask', None)
|
| 32 |
+
if mask is None:
|
| 33 |
+
return _ORIGINAL_CONVOLVE(self, hidden_states, delta, side)
|
| 34 |
+
return grouped_conv_dynamic(hidden_states, delta, self.base_kernel[side], mask,
|
| 35 |
+
self.num_groups, self.group_size, self.taps)
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def build_input_kernel():
|
| 39 |
+
"""Add one scalar store to the existing input-preparation launch."""
|
| 40 |
+
global _INPUT_KERNEL
|
| 41 |
+
if _INPUT_KERNEL is not None:
|
| 42 |
+
return _INPUT_KERNEL
|
| 43 |
+
from vllm.v1.spec_decode import utils
|
| 44 |
+
original = utils.copy_and_expand_dflash_inputs_kernel
|
| 45 |
+
source = inspect.getsource(original.fn)
|
| 46 |
+
arg = ' out_token_indices_ptr, # [num_reqs * num_speculative_tokens] (output)\n'
|
| 47 |
+
body = ' block_idx = tl.program_id(axis=1)\n'
|
| 48 |
+
if source.count(arg) != 1 or source.count(body) != 1:
|
| 49 |
+
raise RuntimeError('Unsupported DFlash input-kernel source')
|
| 50 |
+
source = source.replace('def copy_and_expand_dflash_inputs_kernel(',
|
| 51 |
+
'def _copy_and_expand_with_query_mask(', 1)
|
| 52 |
+
source = source.replace(arg, arg + ' out_query_mask_ptr, # persistent scalar for captured convolutions\n', 1)
|
| 53 |
+
source = source.replace(body, body + ' tl.store(out_query_mask_ptr, num_query_per_req - 1,\n'
|
| 54 |
+
' mask=(req_idx == 0) & (block_idx == 0))\n', 1)
|
| 55 |
+
filename = __file__ + '.input.generated'
|
| 56 |
+
linecache.cache[filename] = (len(source), None, source.splitlines(True), filename)
|
| 57 |
+
namespace = dict(vars(utils))
|
| 58 |
+
exec(compile(source, filename, 'exec'), namespace)
|
| 59 |
+
_INPUT_KERNEL = namespace['_copy_and_expand_with_query_mask']
|
| 60 |
+
return _INPUT_KERNEL
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class _InputKernelProxy:
|
| 64 |
+
def __init__(self, original):
|
| 65 |
+
self.original = original
|
| 66 |
+
|
| 67 |
+
def __getitem__(self, grid):
|
| 68 |
+
def launch(*args, **kwargs):
|
| 69 |
+
mask = _ACTIVE_MASK.get()
|
| 70 |
+
if mask is None:
|
| 71 |
+
return self.original[grid](*args, **kwargs)
|
| 72 |
+
return build_input_kernel()[grid](*args, out_query_mask_ptr=mask, **kwargs)
|
| 73 |
+
return launch
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def install():
|
| 77 |
+
global _INSTALLED, _ORIGINAL_CONVOLVE
|
| 78 |
+
if _INSTALLED:
|
| 79 |
+
return
|
| 80 |
+
from vllm.v1.spec_decode import dflash
|
| 81 |
+
from vllm.model_executor.models.qwen3_dflash2 import DFlashGroupedConv
|
| 82 |
+
original_load = dflash.DFlashProposer.load_model
|
| 83 |
+
original_inputs = dflash.DFlashProposer.set_inputs_first_pass
|
| 84 |
+
_ORIGINAL_CONVOLVE = DFlashGroupedConv._convolve
|
| 85 |
+
|
| 86 |
+
def load_model(self, target_model):
|
| 87 |
+
result = original_load(self, target_model)
|
| 88 |
+
if not self.is_dflash2:
|
| 89 |
+
return result
|
| 90 |
+
width = 1 + self.speculative_config.num_speculative_tokens
|
| 91 |
+
if width not in (8, 16):
|
| 92 |
+
raise ValueError('Current boundary fix supports original Q8/Q16 only')
|
| 93 |
+
self._ornith_query_mask = torch.full((1,), width - 1, dtype=torch.int32, device=self.device)
|
| 94 |
+
model = self.model.unwrap() if hasattr(self.model, 'unwrap') else self.model
|
| 95 |
+
count = 0
|
| 96 |
+
for module in model.modules():
|
| 97 |
+
if isinstance(module, DFlashGroupedConv):
|
| 98 |
+
module.register_buffer('_ornith_query_mask', self._ornith_query_mask, persistent=False)
|
| 99 |
+
count += 1
|
| 100 |
+
if count != 12:
|
| 101 |
+
raise RuntimeError(f'Expected current six-layer DFlash2 with12 convolutions, got {count}')
|
| 102 |
+
logger.info('DFlash boundary correction:12 convs share one4-byte GPU mask; dynamicQ8/Q16')
|
| 103 |
+
return result
|
| 104 |
+
|
| 105 |
+
def set_inputs_first_pass(self, *args, **kwargs):
|
| 106 |
+
mask = getattr(self, '_ornith_query_mask', None)
|
| 107 |
+
if mask is None:
|
| 108 |
+
return original_inputs(self, *args, **kwargs)
|
| 109 |
+
width = 1 + self.num_speculative_tokens
|
| 110 |
+
if width not in (8, 16):
|
| 111 |
+
raise ValueError(f'Unexpected current DFlash query width{width}')
|
| 112 |
+
token = _ACTIVE_MASK.set(mask)
|
| 113 |
+
try:
|
| 114 |
+
return original_inputs(self, *args, **kwargs)
|
| 115 |
+
finally:
|
| 116 |
+
_ACTIVE_MASK.reset(token)
|
| 117 |
+
|
| 118 |
+
build_input_kernel()
|
| 119 |
+
dflash.copy_and_expand_dflash_inputs_kernel = _InputKernelProxy(dflash.copy_and_expand_dflash_inputs_kernel)
|
| 120 |
+
dflash.DFlashProposer.load_model = load_model
|
| 121 |
+
dflash.DFlashProposer.set_inputs_first_pass = set_inputs_first_pass
|
| 122 |
+
DFlashGroupedConv._convolve = convolve
|
| 123 |
+
_INSTALLED = True
|
bundle/plugin-site/ornith_g256/dflash_spec.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Keep DFlash query slots after the runner supplies target context metadata."""
|
| 2 |
+
# Copyright 2026 Ciru.
|
| 3 |
+
from vllm.v1.spec_decode.dflash import DFlashProposer
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
_upstream_set_inputs_first_pass = DFlashProposer.set_inputs_first_pass
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def set_inputs_first_pass(self, *args, **kwargs):
|
| 10 |
+
# The runner seeds per-group metadata with target/context slots. DFlash
|
| 11 |
+
# then generates separate context and query slots from the draft block
|
| 12 |
+
# table. The base proposer otherwise prefers the stale context slots to
|
| 13 |
+
# the new query view, overwriting the query mapping (and overflowing its
|
| 14 |
+
# 64-token buffer on a 128-token prefill).
|
| 15 |
+
if any(group.kv_cache_group_id != self.kv_cache_gid
|
| 16 |
+
for group in self.draft_attn_groups):
|
| 17 |
+
raise ValueError('Ornith DFlash requires one draft KV cache group')
|
| 18 |
+
result = _upstream_set_inputs_first_pass(self, *args, **kwargs)
|
| 19 |
+
_, _, query_metadata = result
|
| 20 |
+
self._per_group_slot_mappings[self.kv_cache_gid] = query_metadata.slot_mapping
|
| 21 |
+
return result
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def install():
|
| 25 |
+
"""Patch only DFlash's input hook; leave installed vLLM files untouched."""
|
| 26 |
+
current = DFlashProposer.set_inputs_first_pass
|
| 27 |
+
if current not in (_upstream_set_inputs_first_pass, set_inputs_first_pass):
|
| 28 |
+
raise RuntimeError('Another extension replaced DFlash input preparation')
|
| 29 |
+
DFlashProposer.set_inputs_first_pass = set_inputs_first_pass
|
bundle/plugin-site/ornith_g256/dynamic_graphs.py
ADDED
|
@@ -0,0 +1,266 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""C1 adaptive fallback: exact Q1/C1 target graph; retain Q16/Q8 keys.
|
| 2 |
+
|
| 3 |
+
Copy into the experimental ornith_g256 package and call install() before
|
| 4 |
+
GPUModelRunner construction. Installed vLLM files and drafter hooks stay intact.
|
| 5 |
+
"""
|
| 6 |
+
# Copyright 2026 Ciru.
|
| 7 |
+
from contextlib import contextmanager
|
| 8 |
+
from functools import wraps
|
| 9 |
+
|
| 10 |
+
|
| 11 |
+
_INSTALLED = False
|
| 12 |
+
_CONFIG_HOOK_INSTALLED = False
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _requested(config):
|
| 16 |
+
return config.additional_config.get("ornith_g256", {}).get(
|
| 17 |
+
"dynamic_spec_profile") is True
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _profile_errors(config):
|
| 21 |
+
"""Report actual values instead of silently opting an explicit profile out."""
|
| 22 |
+
spec = config.speculative_config
|
| 23 |
+
parallel = config.parallel_config
|
| 24 |
+
cache = config.cache_config
|
| 25 |
+
schedule = getattr(spec, "num_speculative_tokens_per_batch_size", None)
|
| 26 |
+
values = {
|
| 27 |
+
"speculative.method": (getattr(spec, "method", None), "dflash"),
|
| 28 |
+
"speculative.num_speculative_tokens": (
|
| 29 |
+
getattr(spec, "num_speculative_tokens", None), 15),
|
| 30 |
+
"speculative.schedule": (
|
| 31 |
+
tuple(tuple(row) for row in schedule) if schedule else None,
|
| 32 |
+
((1, 1, 15), (2, 8, 7))),
|
| 33 |
+
"max_num_seqs": (config.scheduler_config.max_num_seqs, 8),
|
| 34 |
+
"async_scheduling": (config.scheduler_config.async_scheduling, False),
|
| 35 |
+
"quantization": (config.model_config.quantization, "ornith_g256"),
|
| 36 |
+
"enable_prefix_caching": (cache.enable_prefix_caching, True),
|
| 37 |
+
"lora_config": (config.lora_config, None),
|
| 38 |
+
"use_v2_model_runner": (config.use_v2_model_runner, False),
|
| 39 |
+
"use_ubatching": (parallel.use_ubatching, False),
|
| 40 |
+
}
|
| 41 |
+
values.update((field, (getattr(parallel, field), 1)) for field in (
|
| 42 |
+
"tensor_parallel_size", "pipeline_parallel_size", "data_parallel_size"))
|
| 43 |
+
errors = [f"{name}={actual!r} (expected {expected!r})"
|
| 44 |
+
for name, (actual, expected) in values.items() if actual != expected]
|
| 45 |
+
# Retention changes CPU checkpoint eligibility, not page geometry or graph buffers.
|
| 46 |
+
# This private repair profile permits frontier-only retention with the same DF15/7 graphs.
|
| 47 |
+
if cache.prefix_cache_retention_interval not in (0, 1120):
|
| 48 |
+
errors.append(f"prefix_cache_retention_interval={cache.prefix_cache_retention_interval!r} (expected 0 or 1120)")
|
| 49 |
+
if not 65536 <= config.model_config.max_model_len <= 262144:
|
| 50 |
+
errors.append("max_model_len outside 65536..262144")
|
| 51 |
+
# EngineCore normalizes the shared config to the smallest participating
|
| 52 |
+
# group before worker KV initialization: draft560, target/Mamba1120.
|
| 53 |
+
# Physical block geometry remains unchanged for both supported retention policies.
|
| 54 |
+
if cache.block_size not in (560, 1120):
|
| 55 |
+
errors.append(f"cache.block_size={cache.block_size!r} (expected 560 or 1120)")
|
| 56 |
+
return errors
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def _reject(errors):
|
| 60 |
+
from vllm.logger import init_logger
|
| 61 |
+
|
| 62 |
+
message = "Dynamic target graph profile rejected: " + "; ".join(errors)
|
| 63 |
+
init_logger("vllm.ornith_dynamic_graphs").error(message)
|
| 64 |
+
raise ValueError(message)
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
def install_config_hook():
|
| 68 |
+
"""Call before AsyncEngineArgs builds its VllmConfig, and in the worker."""
|
| 69 |
+
global _CONFIG_HOOK_INSTALLED
|
| 70 |
+
if _CONFIG_HOOK_INSTALLED:
|
| 71 |
+
return
|
| 72 |
+
from vllm.config import CUDAGraphMode, VllmConfig
|
| 73 |
+
|
| 74 |
+
original = VllmConfig._maybe_override_dynamic_sd_cudagraph_mode
|
| 75 |
+
|
| 76 |
+
@wraps(original)
|
| 77 |
+
def keep_supported_full(config):
|
| 78 |
+
if not _requested(config):
|
| 79 |
+
return original(config)
|
| 80 |
+
# Only suppress the installed automatic FULL->PIECEWISE rewrite for
|
| 81 |
+
# our exact two-shape target profile. Other modes retain their rules.
|
| 82 |
+
if config.compilation_config.cudagraph_mode != CUDAGraphMode.FULL_DECODE_ONLY:
|
| 83 |
+
return original(config)
|
| 84 |
+
errors = _profile_errors(config)
|
| 85 |
+
sizes = config.compilation_config.cudagraph_capture_sizes
|
| 86 |
+
if sizes != [16, 32, 48, 64]:
|
| 87 |
+
errors.append(f"cudagraph_capture_sizes={sizes!r} (expected [16, 32, 48, 64])")
|
| 88 |
+
if errors:
|
| 89 |
+
_reject(errors)
|
| 90 |
+
from vllm.logger import init_logger
|
| 91 |
+
|
| 92 |
+
init_logger("vllm.ornith_dynamic_graphs").info(
|
| 93 |
+
"Dynamic target config retains FULL_DECODE_ONLY for C1/Q16 and Q8/C1..8")
|
| 94 |
+
|
| 95 |
+
VllmConfig._maybe_override_dynamic_sd_cudagraph_mode = keep_supported_full
|
| 96 |
+
_CONFIG_HOOK_INSTALLED = True
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def _enabled(runner):
|
| 100 |
+
config = runner.vllm_config
|
| 101 |
+
if not _requested(config):
|
| 102 |
+
return False
|
| 103 |
+
cached = getattr(runner, "_ornith_dynamic_graphs_enabled", None)
|
| 104 |
+
if cached is not None:
|
| 105 |
+
return cached
|
| 106 |
+
errors = _profile_errors(config)
|
| 107 |
+
if errors:
|
| 108 |
+
_reject(errors)
|
| 109 |
+
import torch
|
| 110 |
+
|
| 111 |
+
props = torch.cuda.get_device_properties(runner.device)
|
| 112 |
+
arch = getattr(props, "gcnArchName", "")
|
| 113 |
+
if not arch.startswith("gfx1151"):
|
| 114 |
+
_reject([f"gcnArchName={arch!r} (expected gfx1151)"])
|
| 115 |
+
runner._ornith_dynamic_graphs_enabled = True
|
| 116 |
+
return True
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def _supported_query_len(num_tokens, num_reqs, max_query_len):
|
| 120 |
+
if num_tokens != num_reqs * max_query_len:
|
| 121 |
+
return None
|
| 122 |
+
if max_query_len == 1 and num_reqs == 1:
|
| 123 |
+
return 1
|
| 124 |
+
if max_query_len == 16 and num_reqs == 1:
|
| 125 |
+
return 16
|
| 126 |
+
if max_query_len == 8 and 1 <= num_reqs <= 8:
|
| 127 |
+
return 8
|
| 128 |
+
return None
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
@contextmanager
|
| 132 |
+
def _query_len(runner, query_len):
|
| 133 |
+
# This profile is synchronous TP1/PP1/DP1 without microbatching. Nested
|
| 134 |
+
# capture -> dummy -> dispatch scopes restore their caller's values.
|
| 135 |
+
dispatcher = runner.cudagraph_dispatcher
|
| 136 |
+
runner_previous = runner.uniform_decode_query_len
|
| 137 |
+
dispatcher_previous = dispatcher.uniform_decode_query_len
|
| 138 |
+
runner.uniform_decode_query_len = query_len
|
| 139 |
+
dispatcher.uniform_decode_query_len = query_len
|
| 140 |
+
try:
|
| 141 |
+
yield
|
| 142 |
+
finally:
|
| 143 |
+
runner.uniform_decode_query_len = runner_previous
|
| 144 |
+
dispatcher.uniform_decode_query_len = dispatcher_previous
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def _replace_full_keys(runner):
|
| 148 |
+
from vllm.config import CUDAGraphMode
|
| 149 |
+
from vllm.forward_context import BatchDescriptor
|
| 150 |
+
|
| 151 |
+
dispatcher = runner.cudagraph_dispatcher
|
| 152 |
+
if dispatcher.cudagraph_mode != CUDAGraphMode.FULL_DECODE_ONLY:
|
| 153 |
+
_reject([f"resolved cudagraph_mode={dispatcher.cudagraph_mode.name}; "
|
| 154 |
+
"expected FULL_DECODE_ONLY (install_config_hook must run before config creation)"])
|
| 155 |
+
# Reuse vLLM's resolved capture-size lookup, including its Q16 rounding.
|
| 156 |
+
# Thus [16,32,48,64] yields Q8 captures at C2/C4/C6/C8; odd C pad up.
|
| 157 |
+
lookup = dispatcher._bs_to_padded_graph_size
|
| 158 |
+
if len(lookup) <= 64 or lookup[16] != 16 or lookup[64] != 64:
|
| 159 |
+
raise ValueError("Dynamic target graphs require capture sizes 16 and 64")
|
| 160 |
+
# Do not change global capture sizes: those also configure DFlash.
|
| 161 |
+
# Add only an exact target Q1/C1 key after upstream's Q16 initialization.
|
| 162 |
+
lookup[1] = 1
|
| 163 |
+
lookup[8] = 8
|
| 164 |
+
keys = {BatchDescriptor(num_tokens=1, num_reqs=1, uniform=True),
|
| 165 |
+
BatchDescriptor(num_tokens=16, num_reqs=1, uniform=True)}
|
| 166 |
+
for num_reqs in range(1, 9):
|
| 167 |
+
padded = lookup[num_reqs * 8]
|
| 168 |
+
if padded % 8 or not 8 <= padded <= 64:
|
| 169 |
+
raise ValueError("Dynamic Q8 graph padding must stay within C1..8")
|
| 170 |
+
keys.add(BatchDescriptor(
|
| 171 |
+
num_tokens=padded, num_reqs=padded // 8, uniform=True))
|
| 172 |
+
dispatcher.cudagraph_keys[CUDAGraphMode.FULL].clear()
|
| 173 |
+
dispatcher.cudagraph_keys[CUDAGraphMode.FULL].update(keys)
|
| 174 |
+
from vllm.logger import init_logger
|
| 175 |
+
|
| 176 |
+
init_logger("vllm.ornith_dynamic_graphs").info(
|
| 177 |
+
"Dynamic target FULL graph shapes (tokens, requests): %s; "
|
| 178 |
+
"cache_config.block_size=%s, KV group block sizes=%s",
|
| 179 |
+
sorted((key.num_tokens, key.num_reqs) for key in keys),
|
| 180 |
+
runner.vllm_config.cache_config.block_size,
|
| 181 |
+
[group.kv_cache_spec.block_size for group in runner.kv_cache_config.kv_cache_groups],
|
| 182 |
+
)
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def install():
|
| 186 |
+
"""Install process-local, config- and hardware-scoped target wrappers."""
|
| 187 |
+
global _INSTALLED
|
| 188 |
+
if _INSTALLED:
|
| 189 |
+
return
|
| 190 |
+
# The worker also creates replaced VllmConfigs while loading DFlash;
|
| 191 |
+
# dataclasses.replace reruns __post_init__ and would downgrade them again.
|
| 192 |
+
install_config_hook()
|
| 193 |
+
from vllm.v1.worker.gpu_model_runner import GPUModelRunner
|
| 194 |
+
|
| 195 |
+
original_resolve = GPUModelRunner._check_and_update_cudagraph_mode
|
| 196 |
+
original_dispatch = GPUModelRunner._determine_batch_execution_and_padding
|
| 197 |
+
original_capture = GPUModelRunner._warmup_and_capture
|
| 198 |
+
|
| 199 |
+
@wraps(original_resolve)
|
| 200 |
+
def resolve(runner, *args, **kwargs):
|
| 201 |
+
result = original_resolve(runner, *args, **kwargs)
|
| 202 |
+
if _enabled(runner):
|
| 203 |
+
_replace_full_keys(runner)
|
| 204 |
+
return result
|
| 205 |
+
|
| 206 |
+
@wraps(original_dispatch)
|
| 207 |
+
def dispatch(runner, num_tokens, num_reqs, num_scheduled_tokens_np,
|
| 208 |
+
max_num_scheduled_tokens, *args, **kwargs):
|
| 209 |
+
query_len = _supported_query_len(
|
| 210 |
+
num_tokens, num_reqs, max_num_scheduled_tokens)
|
| 211 |
+
if query_len == 1 and kwargs.get('force_uniform_decode') is None:
|
| 212 |
+
# A one-token prompt/tail is not proof of decode. CPU metadata is
|
| 213 |
+
# already available; no GPU read or synchronization is added.
|
| 214 |
+
batch = runner.input_batch
|
| 215 |
+
if not (batch.num_reqs == 1 and
|
| 216 |
+
batch.num_computed_tokens_cpu[0] >= batch.num_prompt_tokens[0]):
|
| 217 |
+
query_len = None
|
| 218 |
+
if query_len is None or not _enabled(runner):
|
| 219 |
+
return original_dispatch(
|
| 220 |
+
runner, num_tokens, num_reqs, num_scheduled_tokens_np,
|
| 221 |
+
max_num_scheduled_tokens, *args, **kwargs)
|
| 222 |
+
with _query_len(runner, query_len):
|
| 223 |
+
result = original_dispatch(
|
| 224 |
+
runner, num_tokens, num_reqs, num_scheduled_tokens_np,
|
| 225 |
+
max_num_scheduled_tokens, *args, **kwargs)
|
| 226 |
+
if kwargs.get('force_uniform_decode') is None:
|
| 227 |
+
seen = getattr(runner, '_ornith_dynamic_dispatch_seen', set())
|
| 228 |
+
key = (query_len, num_reqs, result[0].name)
|
| 229 |
+
if key not in seen:
|
| 230 |
+
from vllm.logger import init_logger
|
| 231 |
+
init_logger("vllm.ornith_dynamic_graphs").info('Dynamic target dispatch Q%d/C%d: %s', *key)
|
| 232 |
+
seen.add(key)
|
| 233 |
+
runner._ornith_dynamic_dispatch_seen = seen
|
| 234 |
+
return result
|
| 235 |
+
|
| 236 |
+
@wraps(original_capture)
|
| 237 |
+
def capture(runner, desc, *args, **kwargs):
|
| 238 |
+
query_len = None
|
| 239 |
+
if desc.uniform and desc.num_reqs:
|
| 240 |
+
query_len = _supported_query_len(
|
| 241 |
+
desc.num_tokens, desc.num_reqs, desc.num_tokens // desc.num_reqs)
|
| 242 |
+
if query_len is None or not _enabled(runner):
|
| 243 |
+
return original_capture(runner, desc, *args, **kwargs)
|
| 244 |
+
if query_len == 1:
|
| 245 |
+
# Only target Q1 is captured. The K0 diagnostic never uses the
|
| 246 |
+
# drafter, whose trained convolution cannot warm up a one-row input.
|
| 247 |
+
# Retain all its ordinary Q8/Q16 warmup and construction unchanged.
|
| 248 |
+
drafter = runner.drafter
|
| 249 |
+
had_override = 'dummy_run' in drafter.__dict__
|
| 250 |
+
previous_dummy = drafter.__dict__.get('dummy_run')
|
| 251 |
+
drafter.dummy_run = lambda *a, **k: None
|
| 252 |
+
try:
|
| 253 |
+
with _query_len(runner, query_len):
|
| 254 |
+
return original_capture(runner, desc, *args, **kwargs)
|
| 255 |
+
finally:
|
| 256 |
+
if had_override:
|
| 257 |
+
drafter.dummy_run = previous_dummy
|
| 258 |
+
else:
|
| 259 |
+
del drafter.dummy_run
|
| 260 |
+
with _query_len(runner, query_len):
|
| 261 |
+
return original_capture(runner, desc, *args, **kwargs)
|
| 262 |
+
|
| 263 |
+
GPUModelRunner._check_and_update_cudagraph_mode = resolve
|
| 264 |
+
GPUModelRunner._determine_batch_execution_and_padding = dispatch
|
| 265 |
+
GPUModelRunner._warmup_and_capture = capture
|
| 266 |
+
_INSTALLED = True
|
bundle/plugin-site/ornith_g256/gdn_compact.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""C8 recurrent update logs; reconstruct before upstream cache alignment."""
|
| 2 |
+
# Copyright 2026 Ciru.
|
| 3 |
+
from functools import wraps
|
| 4 |
+
import inspect
|
| 5 |
+
import torch
|
| 6 |
+
from . import gdn_compact_kernel as kernel
|
| 7 |
+
_ACTIVE=False
|
| 8 |
+
_CAPTURE=False
|
| 9 |
+
_INSTALLED=False
|
| 10 |
+
_BANKS={}
|
| 11 |
+
_LAYER=None
|
| 12 |
+
|
| 13 |
+
def install():
|
| 14 |
+
global _INSTALLED
|
| 15 |
+
if _INSTALLED:return
|
| 16 |
+
from vllm.v1.worker.gpu_model_runner import GPUModelRunner
|
| 17 |
+
from vllm.v1.worker import mamba_utils
|
| 18 |
+
from vllm.model_executor.layers.mamba.gdn import qwen_gdn_linear_attn
|
| 19 |
+
cls=qwen_gdn_linear_attn.QwenGatedDeltaNetAttention
|
| 20 |
+
core_original=cls._forward_core
|
| 21 |
+
@wraps(core_original)
|
| 22 |
+
def core(self,*args,**kwargs):
|
| 23 |
+
global _LAYER
|
| 24 |
+
previous=_LAYER;_LAYER=self.prefix
|
| 25 |
+
try:return core_original(self,*args,**kwargs)
|
| 26 |
+
finally:_LAYER=previous
|
| 27 |
+
cls._forward_core=core
|
| 28 |
+
original=qwen_gdn_linear_attn.fused_sigmoid_gating_delta_rule_update
|
| 29 |
+
signature=inspect.signature(original)
|
| 30 |
+
def update(*args,**kwargs):
|
| 31 |
+
if not _ACTIVE:return original(*args,**kwargs)
|
| 32 |
+
bound=signature.bind(*args,**kwargs);bound.apply_defaults();d=dict(bound.arguments)
|
| 33 |
+
state=d['initial_state'];q=d['q'];v=d['v'];cu=d['cu_seqlens'];ids=d['ssm_state_indices']
|
| 34 |
+
if cu is None or ids is None or ids.ndim!=2 or d['is_kda']:
|
| 35 |
+
raise RuntimeError('Compact GDN requires varlen indexed scalar decay')
|
| 36 |
+
N=len(cu)-1;HV=v.shape[2];V=v.shape[-1];K=q.shape[-1]
|
| 37 |
+
assert N<=8 and q.shape[1]<=128 and ids.shape[1]<=16 and (HV,V,K)==(32,128,128)
|
| 38 |
+
key=_LAYER
|
| 39 |
+
assert key is not None
|
| 40 |
+
if key not in _BANKS:
|
| 41 |
+
print('ORNITH_GDN_BANK',key,state.data_ptr(),tuple(state.shape),tuple(state.stride()),flush=True)
|
| 42 |
+
make=lambda *shape:torch.empty(*shape,device=q.device,dtype=torch.float32)
|
| 43 |
+
_BANKS[key]=dict(base=make(8,HV,V,K),keys=make(128,HV,K),values=make(128,HV,V),decays=make(128,HV),
|
| 44 |
+
cu=torch.empty(9,device=q.device,dtype=cu.dtype),
|
| 45 |
+
ids=torch.empty(8,16,device=q.device,dtype=ids.dtype),state=state)
|
| 46 |
+
bank=_BANKS[key]
|
| 47 |
+
bank['cu'][:N+1].copy_(cu)
|
| 48 |
+
bank['ids'][:N,:ids.shape[1]].copy_(ids)
|
| 49 |
+
return kernel.fused_sigmoid_gating_delta_rule_update(**d,compact_base=bank['base'],
|
| 50 |
+
compact_k=bank['keys'],compact_v=bank['values'],compact_g=bank['decays'])
|
| 51 |
+
qwen_gdn_linear_attn.fused_sigmoid_gating_delta_rule_update=update
|
| 52 |
+
execute_original=GPUModelRunner.execute_model
|
| 53 |
+
capture_original=GPUModelRunner._warmup_and_capture
|
| 54 |
+
dispatch_original=GPUModelRunner._determine_batch_execution_and_padding
|
| 55 |
+
post_original=mamba_utils.postprocess_mamba_align_gpu
|
| 56 |
+
@wraps(execute_original)
|
| 57 |
+
def execute(self,scheduler_output,*args,**kwargs):
|
| 58 |
+
global _ACTIVE
|
| 59 |
+
counts=scheduler_output.num_scheduled_tokens;cached=scheduler_output.scheduled_cached_reqs
|
| 60 |
+
_ACTIVE=(bool(counts) and not scheduler_output.scheduled_new_reqs and
|
| 61 |
+
all(c==8 for c in counts.values()) and
|
| 62 |
+
all(r in cached.req_ids and not cached.is_context_phase(r) for r in counts))
|
| 63 |
+
return execute_original(self,scheduler_output,*args,**kwargs)
|
| 64 |
+
@wraps(capture_original)
|
| 65 |
+
def capture(self,desc,*args,**kwargs):
|
| 66 |
+
global _ACTIVE,_CAPTURE
|
| 67 |
+
previous=(_ACTIVE,_CAPTURE)
|
| 68 |
+
_CAPTURE=True
|
| 69 |
+
_ACTIVE=bool(desc.uniform and desc.num_reqs and desc.num_tokens==desc.num_reqs*8)
|
| 70 |
+
try:return capture_original(self,desc,*args,**kwargs)
|
| 71 |
+
finally:_ACTIVE,_CAPTURE=previous
|
| 72 |
+
@wraps(dispatch_original)
|
| 73 |
+
def dispatch(self,num_tokens,num_reqs,num_scheduled_tokens_np,max_num_scheduled_tokens,*args,**kwargs):
|
| 74 |
+
# A prompt whose shape coincides with Q8 must not replay a compact decode graph.
|
| 75 |
+
if not _CAPTURE and not _ACTIVE and max_num_scheduled_tokens==8:
|
| 76 |
+
kwargs['force_eager']=True
|
| 77 |
+
return dispatch_original(self,num_tokens,num_reqs,num_scheduled_tokens_np,max_num_scheduled_tokens,*args,**kwargs)
|
| 78 |
+
@wraps(post_original)
|
| 79 |
+
def post(**kwargs):
|
| 80 |
+
global _ACTIVE
|
| 81 |
+
if _ACTIVE:
|
| 82 |
+
assert len(_BANKS)==30, f'Expected30targetGDN layers, got{len(_BANKS)}'
|
| 83 |
+
ctx=kwargs['bufs'].postprocess_align
|
| 84 |
+
for bank in _BANKS.values():
|
| 85 |
+
state=bank['state']
|
| 86 |
+
kernel.replay_physical[(4,kwargs['num_reqs']*32)](
|
| 87 |
+
bank['base'],bank['keys'],bank['values'],bank['decays'],
|
| 88 |
+
kwargs['num_accepted_tokens_gpu'],bank['cu'],bank['ids'],state,
|
| 89 |
+
ctx.num_computed_tokens_buf.gpu,ctx.num_scheduled_tokens_buf.gpu,ctx.num_draft_tokens_buf.gpu,
|
| 90 |
+
32,128,128,state.stride(0),1120,32,num_warps=4,num_stages=3)
|
| 91 |
+
if not getattr(post,'logged',False):
|
| 92 |
+
print('ORNITH_COMPACT_GDN replayed30layers before cache alignment',flush=True);post.logged=True
|
| 93 |
+
try:return post_original(**kwargs)
|
| 94 |
+
finally:_ACTIVE=False
|
| 95 |
+
GPUModelRunner.execute_model=execute
|
| 96 |
+
GPUModelRunner._warmup_and_capture=capture
|
| 97 |
+
GPUModelRunner._determine_batch_execution_and_padding=dispatch
|
| 98 |
+
mamba_utils.postprocess_mamba_align_gpu=post
|
| 99 |
+
_INSTALLED=True
|
bundle/plugin-site/ornith_g256/gdn_compact_kernel.py
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
| 3 |
+
# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
|
| 4 |
+
#
|
| 5 |
+
# This file contains code copied from the flash-linear-attention project.
|
| 6 |
+
# The original source code was licensed under the MIT license and included
|
| 7 |
+
# the following copyright notice:
|
| 8 |
+
# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
|
| 12 |
+
from vllm.triton_utils import tl, triton
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
@triton.heuristics(
|
| 16 |
+
{
|
| 17 |
+
"USE_INITIAL_STATE": lambda args: args["h0"] is not None,
|
| 18 |
+
"IS_VARLEN": lambda args: args["cu_seqlens"] is not None,
|
| 19 |
+
"IS_CONTINUOUS_BATCHING": lambda args: args["ssm_state_indices"] is not None,
|
| 20 |
+
"IS_SPEC_DECODING": lambda args: args["num_accepted_tokens"] is not None,
|
| 21 |
+
}
|
| 22 |
+
)
|
| 23 |
+
@triton.jit(do_not_specialize=["N", "T"])
|
| 24 |
+
def fused_sigmoid_gating_delta_rule_update_kernel(
|
| 25 |
+
A_log,
|
| 26 |
+
a,
|
| 27 |
+
b,
|
| 28 |
+
dt_bias,
|
| 29 |
+
beta,
|
| 30 |
+
threshold,
|
| 31 |
+
q,
|
| 32 |
+
k,
|
| 33 |
+
v,
|
| 34 |
+
o,
|
| 35 |
+
h0,
|
| 36 |
+
ht,
|
| 37 |
+
compact_base, compact_k, compact_v, compact_g,
|
| 38 |
+
cu_seqlens,
|
| 39 |
+
ssm_state_indices,
|
| 40 |
+
num_accepted_tokens,
|
| 41 |
+
scale,
|
| 42 |
+
N: tl.int64, # num of sequences
|
| 43 |
+
T: tl.int64, # num of tokens
|
| 44 |
+
B: tl.constexpr,
|
| 45 |
+
H: tl.constexpr,
|
| 46 |
+
HV: tl.constexpr,
|
| 47 |
+
K: tl.constexpr,
|
| 48 |
+
V: tl.constexpr,
|
| 49 |
+
BK: tl.constexpr,
|
| 50 |
+
BV: tl.constexpr,
|
| 51 |
+
stride_init_state_token: tl.constexpr,
|
| 52 |
+
stride_final_state_token: tl.constexpr,
|
| 53 |
+
stride_indices_seq: tl.constexpr,
|
| 54 |
+
stride_indices_tok: tl.constexpr,
|
| 55 |
+
USE_INITIAL_STATE: tl.constexpr, # whether to use initial state
|
| 56 |
+
INPLACE_FINAL_STATE: tl.constexpr, # whether to store final state inplace
|
| 57 |
+
USE_QK_L2NORM_IN_KERNEL: tl.constexpr,
|
| 58 |
+
IS_VARLEN: tl.constexpr,
|
| 59 |
+
IS_CONTINUOUS_BATCHING: tl.constexpr,
|
| 60 |
+
IS_SPEC_DECODING: tl.constexpr,
|
| 61 |
+
IS_KDA: tl.constexpr,
|
| 62 |
+
):
|
| 63 |
+
i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2)
|
| 64 |
+
i_n, i_hv = i_nh // HV, i_nh % HV
|
| 65 |
+
i_h = i_hv // (HV // H)
|
| 66 |
+
if IS_VARLEN:
|
| 67 |
+
bos, eos = (
|
| 68 |
+
tl.load(cu_seqlens + i_n).to(tl.int64),
|
| 69 |
+
tl.load(cu_seqlens + i_n + 1).to(tl.int64),
|
| 70 |
+
)
|
| 71 |
+
all = T
|
| 72 |
+
T = eos - bos
|
| 73 |
+
else:
|
| 74 |
+
bos, eos = i_n * T, i_n * T + T
|
| 75 |
+
all = B * T
|
| 76 |
+
|
| 77 |
+
if T == 0:
|
| 78 |
+
# no tokens to process for this sequence
|
| 79 |
+
return
|
| 80 |
+
|
| 81 |
+
o_k = i_k * BK + tl.arange(0, BK)
|
| 82 |
+
o_v = i_v * BV + tl.arange(0, BV)
|
| 83 |
+
|
| 84 |
+
p_q = q + (bos * H + i_h) * K + o_k
|
| 85 |
+
p_k = k + (bos * H + i_h) * K + o_k
|
| 86 |
+
p_v = v + (bos * HV + i_hv) * V + o_v
|
| 87 |
+
|
| 88 |
+
p_A_log = A_log + i_hv
|
| 89 |
+
if not IS_KDA:
|
| 90 |
+
p_a = a + bos * HV + i_hv
|
| 91 |
+
p_dt_bias = dt_bias + i_hv
|
| 92 |
+
else:
|
| 93 |
+
p_a = a + (bos * HV + i_hv) * K + o_k
|
| 94 |
+
p_dt_bias = dt_bias + i_hv * K + o_k
|
| 95 |
+
|
| 96 |
+
p_b = b + bos * HV + i_hv
|
| 97 |
+
p_o = o + ((i_k * all + bos) * HV + i_hv) * V + o_v
|
| 98 |
+
|
| 99 |
+
mask_k = o_k < K
|
| 100 |
+
mask_v = o_v < V
|
| 101 |
+
mask_h = mask_v[:, None] & mask_k[None, :]
|
| 102 |
+
|
| 103 |
+
b_h = tl.zeros([BV, BK], dtype=tl.float32)
|
| 104 |
+
if USE_INITIAL_STATE:
|
| 105 |
+
if IS_CONTINUOUS_BATCHING:
|
| 106 |
+
if IS_SPEC_DECODING:
|
| 107 |
+
i_t = tl.load(num_accepted_tokens + i_n).to(tl.int64) - 1
|
| 108 |
+
else:
|
| 109 |
+
i_t = 0
|
| 110 |
+
# Load state index and check for invalid entries
|
| 111 |
+
state_idx = tl.load(ssm_state_indices + i_n * stride_indices_seq + i_t).to(
|
| 112 |
+
tl.int64
|
| 113 |
+
)
|
| 114 |
+
# Skip if state index is invalid (NULL_BLOCK_ID=0)
|
| 115 |
+
if state_idx <= 0:
|
| 116 |
+
return
|
| 117 |
+
p_h0 = h0 + state_idx * stride_init_state_token
|
| 118 |
+
else:
|
| 119 |
+
p_h0 = h0 + bos * HV * V * K
|
| 120 |
+
p_h0 = p_h0 + i_hv * V * K + o_v[:, None] * K + o_k[None, :]
|
| 121 |
+
b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32)
|
| 122 |
+
p_base=compact_base + i_n*HV*V*K + i_hv*V*K + o_v[:,None]*K+o_k[None,:]
|
| 123 |
+
tl.store(p_base,b_h,mask=mask_h)
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
for i_t in range(0, T):
|
| 127 |
+
b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32)
|
| 128 |
+
b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32)
|
| 129 |
+
b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32)
|
| 130 |
+
b_b = tl.load(p_b).to(tl.float32)
|
| 131 |
+
|
| 132 |
+
# If the model is loaded in fp16, without the .float() here, A might be -inf
|
| 133 |
+
x = tl.load(p_a).to(tl.float32) + tl.load(p_dt_bias).to(tl.float32)
|
| 134 |
+
softplus_x = tl.where(
|
| 135 |
+
beta * x <= threshold, (1 / beta) * tl.log(1 + tl.exp(beta * x)), x
|
| 136 |
+
)
|
| 137 |
+
b_g = -tl.exp(tl.load(p_A_log).to(tl.float32)) * softplus_x
|
| 138 |
+
|
| 139 |
+
# compute beta_output = sigmoid(b)
|
| 140 |
+
b_beta = tl.sigmoid(b_b.to(tl.float32))
|
| 141 |
+
|
| 142 |
+
if USE_QK_L2NORM_IN_KERNEL:
|
| 143 |
+
b_q = b_q * (tl.rsqrt(tl.sum(b_q * b_q) + 1e-6))
|
| 144 |
+
b_k = b_k * (tl.rsqrt(tl.sum(b_k * b_k) + 1e-6))
|
| 145 |
+
b_q = b_q * scale
|
| 146 |
+
# [BV, BK]
|
| 147 |
+
if not IS_KDA:
|
| 148 |
+
b_decay=tl.exp(b_g)
|
| 149 |
+
b_h *= b_decay
|
| 150 |
+
else:
|
| 151 |
+
b_h *= tl.exp(b_g[None, :])
|
| 152 |
+
# [BV]
|
| 153 |
+
b_v -= tl.sum(b_h * b_k[None, :], 1)
|
| 154 |
+
b_v *= b_beta
|
| 155 |
+
log_token=bos+i_t
|
| 156 |
+
if i_v == 0:
|
| 157 |
+
tl.store(compact_k+(log_token*HV+i_hv)*K+o_k,b_k,mask=mask_k)
|
| 158 |
+
tl.store(compact_g+log_token*HV+i_hv,b_decay)
|
| 159 |
+
tl.store(compact_v+(log_token*HV+i_hv)*V+o_v,b_v,mask=mask_v)
|
| 160 |
+
|
| 161 |
+
# [BV, BK]
|
| 162 |
+
b_h += b_v[:, None] * b_k[None, :]
|
| 163 |
+
# [BV]
|
| 164 |
+
b_o = tl.sum(b_h * b_q[None, :], 1)
|
| 165 |
+
tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v)
|
| 166 |
+
|
| 167 |
+
# Update pointers for next timestep
|
| 168 |
+
p_q += H * K
|
| 169 |
+
p_k += H * K
|
| 170 |
+
p_o += HV * V
|
| 171 |
+
p_v += HV * V
|
| 172 |
+
p_b += HV
|
| 173 |
+
p_a += HV
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def fused_sigmoid_gating_delta_rule_update(
|
| 177 |
+
A_log: torch.Tensor,
|
| 178 |
+
a: torch.Tensor,
|
| 179 |
+
b: torch.Tensor,
|
| 180 |
+
dt_bias: torch.Tensor,
|
| 181 |
+
q: torch.Tensor,
|
| 182 |
+
k: torch.Tensor,
|
| 183 |
+
v: torch.Tensor,
|
| 184 |
+
beta: float = 1.0,
|
| 185 |
+
threshold: float = 20.0,
|
| 186 |
+
scale: float = None,
|
| 187 |
+
initial_state: torch.Tensor = None,
|
| 188 |
+
inplace_final_state: bool = True,
|
| 189 |
+
cu_seqlens: torch.Tensor | None = None,
|
| 190 |
+
ssm_state_indices: torch.Tensor | None = None,
|
| 191 |
+
num_accepted_tokens: torch.Tensor | None = None,
|
| 192 |
+
use_qk_l2norm_in_kernel: bool = False,
|
| 193 |
+
is_kda: bool = False,
|
| 194 |
+
compact_base=None, compact_k=None, compact_v=None, compact_g=None,
|
| 195 |
+
):
|
| 196 |
+
"""
|
| 197 |
+
Fused triton implementation of sigmoid gating delta rule update.
|
| 198 |
+
This function uses a single fused kernel that combines both sigmoid gating
|
| 199 |
+
computation and the recurrent delta rule update for better performance.
|
| 200 |
+
"""
|
| 201 |
+
B, T, H, K, V = *k.shape, v.shape[-1]
|
| 202 |
+
HV = v.shape[2]
|
| 203 |
+
N = B if cu_seqlens is None else len(cu_seqlens) - 1
|
| 204 |
+
BK, BV = triton.next_power_of_2(K), min(triton.next_power_of_2(V), 32)
|
| 205 |
+
NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV)
|
| 206 |
+
assert NK == 1, "NK > 1 is not supported yet"
|
| 207 |
+
num_stages = 3
|
| 208 |
+
num_warps = 4
|
| 209 |
+
|
| 210 |
+
if cu_seqlens is not None and q.shape[0] != 1:
|
| 211 |
+
raise ValueError(
|
| 212 |
+
f"The batch size is expected to be 1 rather than {q.shape[0]}"
|
| 213 |
+
f" when using `cu_seqlens`. Please flatten variable-length"
|
| 214 |
+
f" inputs before processing."
|
| 215 |
+
)
|
| 216 |
+
if scale is None:
|
| 217 |
+
scale = k.shape[-1] ** -0.5
|
| 218 |
+
else:
|
| 219 |
+
assert scale > 0, "scale must be positive"
|
| 220 |
+
|
| 221 |
+
o = q.new_zeros(NK, *v.shape)
|
| 222 |
+
if inplace_final_state:
|
| 223 |
+
final_state = initial_state
|
| 224 |
+
else:
|
| 225 |
+
final_state = q.new_empty(T, HV, V, K, dtype=initial_state.dtype)
|
| 226 |
+
|
| 227 |
+
stride_init_state_token = initial_state.stride(0)
|
| 228 |
+
stride_final_state_token = final_state.stride(0)
|
| 229 |
+
|
| 230 |
+
if ssm_state_indices is None:
|
| 231 |
+
stride_indices_seq, stride_indices_tok = 1, 1
|
| 232 |
+
elif ssm_state_indices.ndim == 1:
|
| 233 |
+
stride_indices_seq, stride_indices_tok = ssm_state_indices.stride(0), 1
|
| 234 |
+
else:
|
| 235 |
+
stride_indices_seq, stride_indices_tok = ssm_state_indices.stride()
|
| 236 |
+
|
| 237 |
+
grid = (NK, NV, N * HV)
|
| 238 |
+
fused_sigmoid_gating_delta_rule_update_kernel[grid](
|
| 239 |
+
A_log=A_log,
|
| 240 |
+
a=a.contiguous(),
|
| 241 |
+
b=b.contiguous(),
|
| 242 |
+
dt_bias=dt_bias,
|
| 243 |
+
beta=beta,
|
| 244 |
+
threshold=threshold,
|
| 245 |
+
q=q.contiguous(),
|
| 246 |
+
k=k.contiguous(),
|
| 247 |
+
v=v.contiguous(),
|
| 248 |
+
o=o,
|
| 249 |
+
h0=initial_state,
|
| 250 |
+
ht=final_state,
|
| 251 |
+
compact_base=compact_base, compact_k=compact_k, compact_v=compact_v, compact_g=compact_g,
|
| 252 |
+
cu_seqlens=cu_seqlens,
|
| 253 |
+
ssm_state_indices=ssm_state_indices,
|
| 254 |
+
num_accepted_tokens=num_accepted_tokens,
|
| 255 |
+
scale=scale,
|
| 256 |
+
N=N,
|
| 257 |
+
T=T,
|
| 258 |
+
B=B,
|
| 259 |
+
H=H,
|
| 260 |
+
HV=HV,
|
| 261 |
+
K=K,
|
| 262 |
+
V=V,
|
| 263 |
+
BK=BK,
|
| 264 |
+
BV=BV,
|
| 265 |
+
stride_init_state_token=stride_init_state_token,
|
| 266 |
+
stride_final_state_token=stride_final_state_token,
|
| 267 |
+
stride_indices_seq=stride_indices_seq,
|
| 268 |
+
stride_indices_tok=stride_indices_tok,
|
| 269 |
+
INPLACE_FINAL_STATE=inplace_final_state,
|
| 270 |
+
USE_QK_L2NORM_IN_KERNEL=use_qk_l2norm_in_kernel,
|
| 271 |
+
IS_KDA=is_kda,
|
| 272 |
+
num_warps=num_warps,
|
| 273 |
+
num_stages=num_stages,
|
| 274 |
+
)
|
| 275 |
+
o = o.squeeze(0)
|
| 276 |
+
return o, final_state
|
| 277 |
+
|
| 278 |
+
|
| 279 |
+
@triton.jit
|
| 280 |
+
def replay_physical(base,keys,values,decays,accepted,cu,indices,states,
|
| 281 |
+
computed,scheduled,drafted,
|
| 282 |
+
HV:tl.constexpr,K:tl.constexpr,V:tl.constexpr,
|
| 283 |
+
STATE_STRIDE:tl.constexpr,BLOCK:tl.constexpr,BV:tl.constexpr):
|
| 284 |
+
iv,nh=tl.program_id(0),tl.program_id(1)
|
| 285 |
+
n,h=nh//HV,nh%HV
|
| 286 |
+
kk=tl.arange(0,K);vv=iv*BV+tl.arange(0,BV)
|
| 287 |
+
offset=h*V*K+vv[:,None]*K+kk[None,:]
|
| 288 |
+
count=tl.load(accepted+n)
|
| 289 |
+
bos=tl.load(cu+n);eos=tl.load(cu+n+1)
|
| 290 |
+
count=tl.minimum(count,eos-bos)
|
| 291 |
+
if count<=0:return
|
| 292 |
+
state=tl.load(base+n*HV*V*K+offset)
|
| 293 |
+
running=tl.load(computed+n)+tl.load(scheduled+n)-tl.load(drafted+n)
|
| 294 |
+
for t in range(count):
|
| 295 |
+
token=bos+t
|
| 296 |
+
key=tl.load(keys+(token*HV+h)*K+kk)
|
| 297 |
+
value=tl.load(values+(token*HV+h)*V+vv)
|
| 298 |
+
decay=tl.load(decays+token*HV+h)
|
| 299 |
+
state=tl.fma(value[:,None],key[None,:],state*decay)
|
| 300 |
+
if t==count-1 or (running+t)%BLOCK==0:
|
| 301 |
+
dest=tl.load(indices+n*16+t).to(tl.int64)
|
| 302 |
+
if dest>0:tl.store(states+dest*STATE_STRIDE+offset,state)
|
bundle/plugin-site/ornith_g256/gdn_spec.py
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# SPDX-License-Identifier: Apache-2.0
|
| 2 |
+
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
| 3 |
+
# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
|
| 4 |
+
#
|
| 5 |
+
# This file contains code copied from the flash-linear-attention project.
|
| 6 |
+
# The original source code was licensed under the MIT license and included
|
| 7 |
+
# the following copyright notice:
|
| 8 |
+
# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
|
| 9 |
+
#
|
| 10 |
+
# Ciru modification (2026): zero-initialize output for skipped NULL state rows.
|
| 11 |
+
# Wrapper copied from vLLM third_party/flash_linear_attention/ops/
|
| 12 |
+
# fused_sigmoid_gating.py; the installed Triton kernel remains unchanged.
|
| 13 |
+
"""Defined GDN speculative padding outputs, using the original Triton kernel."""
|
| 14 |
+
import torch
|
| 15 |
+
from vllm.forward_context import get_forward_context
|
| 16 |
+
from vllm.logger import init_logger
|
| 17 |
+
from vllm.triton_utils import triton
|
| 18 |
+
from vllm.third_party.flash_linear_attention.ops.fused_sigmoid_gating import (
|
| 19 |
+
fused_sigmoid_gating_delta_rule_update as upstream_update,
|
| 20 |
+
fused_sigmoid_gating_delta_rule_update_kernel,
|
| 21 |
+
)
|
| 22 |
+
|
| 23 |
+
logger = init_logger(__name__)
|
| 24 |
+
_upstream_rocm_core = None
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def initialized_rocm_warmup(self, qkvz, ba, z_out, core_attn_out):
|
| 28 |
+
"""Define buffers skipped by the metadata-free upstream ROCm warmup."""
|
| 29 |
+
metadata = get_forward_context().attn_metadata
|
| 30 |
+
no_metadata = not isinstance(metadata, dict) or metadata.get(self.prefix) is None
|
| 31 |
+
result = _upstream_rocm_core(self, qkvz, ba, z_out, core_attn_out)
|
| 32 |
+
if no_metadata:
|
| 33 |
+
z_out.zero_()
|
| 34 |
+
core_attn_out.zero_()
|
| 35 |
+
logger.info_once('Ornith GDN metadata-free ROCm warmup initialized z/core outputs')
|
| 36 |
+
return result
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def fused_sigmoid_gating_delta_rule_update(
|
| 40 |
+
A_log: torch.Tensor,
|
| 41 |
+
a: torch.Tensor,
|
| 42 |
+
b: torch.Tensor,
|
| 43 |
+
dt_bias: torch.Tensor,
|
| 44 |
+
q: torch.Tensor,
|
| 45 |
+
k: torch.Tensor,
|
| 46 |
+
v: torch.Tensor,
|
| 47 |
+
beta: float = 1.0,
|
| 48 |
+
threshold: float = 20.0,
|
| 49 |
+
scale: float = None,
|
| 50 |
+
initial_state: torch.Tensor = None,
|
| 51 |
+
inplace_final_state: bool = True,
|
| 52 |
+
cu_seqlens: torch.Tensor | None = None,
|
| 53 |
+
ssm_state_indices: torch.Tensor | None = None,
|
| 54 |
+
num_accepted_tokens: torch.Tensor | None = None,
|
| 55 |
+
use_qk_l2norm_in_kernel: bool = False,
|
| 56 |
+
is_kda: bool = False,
|
| 57 |
+
):
|
| 58 |
+
B, T, H, K, V = *k.shape, v.shape[-1]
|
| 59 |
+
HV = v.shape[2]
|
| 60 |
+
N = B if cu_seqlens is None else len(cu_seqlens) - 1
|
| 61 |
+
BK, BV = triton.next_power_of_2(K), min(triton.next_power_of_2(V), 32)
|
| 62 |
+
NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV)
|
| 63 |
+
assert NK == 1, "NK > 1 is not supported yet"
|
| 64 |
+
num_stages = 3
|
| 65 |
+
num_warps = 4
|
| 66 |
+
|
| 67 |
+
if cu_seqlens is not None and q.shape[0] != 1:
|
| 68 |
+
raise ValueError(
|
| 69 |
+
f"The batch size is expected to be 1 rather than {q.shape[0]}"
|
| 70 |
+
f" when using `cu_seqlens`. Please flatten variable-length"
|
| 71 |
+
f" inputs before processing."
|
| 72 |
+
)
|
| 73 |
+
if scale is None:
|
| 74 |
+
scale = k.shape[-1] ** -0.5
|
| 75 |
+
else:
|
| 76 |
+
assert scale > 0, "scale must be positive"
|
| 77 |
+
|
| 78 |
+
# NULL_BLOCK_ID causes the original kernel to return without an output
|
| 79 |
+
# store. Valid rows are overwritten exactly as in the upstream wrapper.
|
| 80 |
+
o = q.new_zeros((NK, *v.shape))
|
| 81 |
+
if inplace_final_state:
|
| 82 |
+
final_state = initial_state
|
| 83 |
+
else:
|
| 84 |
+
final_state = q.new_empty(T, HV, V, K, dtype=initial_state.dtype)
|
| 85 |
+
|
| 86 |
+
stride_init_state_token = initial_state.stride(0)
|
| 87 |
+
stride_final_state_token = final_state.stride(0)
|
| 88 |
+
|
| 89 |
+
if ssm_state_indices is None:
|
| 90 |
+
stride_indices_seq, stride_indices_tok = 1, 1
|
| 91 |
+
elif ssm_state_indices.ndim == 1:
|
| 92 |
+
stride_indices_seq, stride_indices_tok = ssm_state_indices.stride(0), 1
|
| 93 |
+
else:
|
| 94 |
+
stride_indices_seq, stride_indices_tok = ssm_state_indices.stride()
|
| 95 |
+
|
| 96 |
+
grid = (NK, NV, N * HV)
|
| 97 |
+
fused_sigmoid_gating_delta_rule_update_kernel[grid](
|
| 98 |
+
A_log=A_log,
|
| 99 |
+
a=a.contiguous(),
|
| 100 |
+
b=b.contiguous(),
|
| 101 |
+
dt_bias=dt_bias,
|
| 102 |
+
beta=beta,
|
| 103 |
+
threshold=threshold,
|
| 104 |
+
q=q.contiguous(),
|
| 105 |
+
k=k.contiguous(),
|
| 106 |
+
v=v.contiguous(),
|
| 107 |
+
o=o,
|
| 108 |
+
h0=initial_state,
|
| 109 |
+
ht=final_state,
|
| 110 |
+
cu_seqlens=cu_seqlens,
|
| 111 |
+
ssm_state_indices=ssm_state_indices,
|
| 112 |
+
num_accepted_tokens=num_accepted_tokens,
|
| 113 |
+
scale=scale,
|
| 114 |
+
N=N,
|
| 115 |
+
T=T,
|
| 116 |
+
B=B,
|
| 117 |
+
H=H,
|
| 118 |
+
HV=HV,
|
| 119 |
+
K=K,
|
| 120 |
+
V=V,
|
| 121 |
+
BK=BK,
|
| 122 |
+
BV=BV,
|
| 123 |
+
stride_init_state_token=stride_init_state_token,
|
| 124 |
+
stride_final_state_token=stride_final_state_token,
|
| 125 |
+
stride_indices_seq=stride_indices_seq,
|
| 126 |
+
stride_indices_tok=stride_indices_tok,
|
| 127 |
+
INPLACE_FINAL_STATE=inplace_final_state,
|
| 128 |
+
USE_QK_L2NORM_IN_KERNEL=use_qk_l2norm_in_kernel,
|
| 129 |
+
IS_KDA=is_kda,
|
| 130 |
+
num_warps=num_warps,
|
| 131 |
+
num_stages=num_stages,
|
| 132 |
+
)
|
| 133 |
+
o = o.squeeze(0)
|
| 134 |
+
return o, final_state
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
def install():
|
| 138 |
+
"""Rebind only Qwen GDN's imported helper before MTP model construction."""
|
| 139 |
+
from vllm.model_executor.layers.mamba.gdn import qwen_gdn_linear_attn
|
| 140 |
+
current = qwen_gdn_linear_attn.fused_sigmoid_gating_delta_rule_update
|
| 141 |
+
if current not in (upstream_update, fused_sigmoid_gating_delta_rule_update):
|
| 142 |
+
raise RuntimeError('Another extension replaced the Qwen GDN update helper')
|
| 143 |
+
qwen_gdn_linear_attn.fused_sigmoid_gating_delta_rule_update = fused_sigmoid_gating_delta_rule_update
|
| 144 |
+
global _upstream_rocm_core
|
| 145 |
+
cls = qwen_gdn_linear_attn.QwenGatedDeltaNetAttention
|
| 146 |
+
current_core = cls._forward_core_rocm
|
| 147 |
+
if _upstream_rocm_core is None:
|
| 148 |
+
_upstream_rocm_core = current_core
|
| 149 |
+
elif current_core not in (_upstream_rocm_core, initialized_rocm_warmup):
|
| 150 |
+
raise RuntimeError('Another extension replaced the Qwen GDN ROCm core')
|
| 151 |
+
cls._forward_core_rocm = initialized_rocm_warmup
|