jcbtc commited on
Commit
ef506b7
·
verified ·
1 Parent(s): 5c6722c

Add pinned runtime, native kernels and production launcher

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +10 -0
  2. INSTALL.md +75 -0
  3. assets/ciru-halo-agent.png +3 -0
  4. bundle/native/libornith_attention_iu4.so +3 -0
  5. bundle/native/libornith_dense_g256.so +0 -0
  6. bundle/native/libornith_dense_g256_n32.so +0 -0
  7. bundle/native/libornith_head_i8_tile.so +3 -0
  8. bundle/native/libornith_persistent_iu4.so +3 -0
  9. bundle/native/libornith_routed_direct.so +3 -0
  10. bundle/native/libornith_routed_n32.so +3 -0
  11. bundle/native/libornith_routed_storage_n32.so +3 -0
  12. bundle/packaging/serve.sh +31 -0
  13. bundle/paths.env +4 -0
  14. bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/INSTALLER +1 -0
  15. bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/METADATA +8 -0
  16. bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/RECORD +32 -0
  17. bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/REQUESTED +0 -0
  18. bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/WHEEL +5 -0
  19. bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/direct_url.json +1 -0
  20. bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/entry_points.txt +5 -0
  21. bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/licenses/LICENSE-APACHE-2.0 +202 -0
  22. bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/top_level.txt +1 -0
  23. bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/uv_build.json +1 -0
  24. bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/uv_cache.json +1 -0
  25. bundle/plugin-site/ornith_g256/__init__.py +26 -0
  26. bundle/plugin-site/ornith_g256/adaptive_c1.py +340 -0
  27. bundle/plugin-site/ornith_g256/attention.py +19 -0
  28. bundle/plugin-site/ornith_g256/attention_compact.py +204 -0
  29. bundle/plugin-site/ornith_g256/attention_fast.py +216 -0
  30. bundle/plugin-site/ornith_g256/attention_folded.py +170 -0
  31. bundle/plugin-site/ornith_g256/attention_iu4.py +41 -0
  32. bundle/plugin-site/ornith_g256/attention_iu4_persistent.py +254 -0
  33. bundle/plugin-site/ornith_g256/attention_mixed.py +75 -0
  34. bundle/plugin-site/ornith_g256/attention_partition.py +162 -0
  35. bundle/plugin-site/ornith_g256/attention_storage.py +93 -0
  36. bundle/plugin-site/ornith_g256/attention_tile.py +160 -0
  37. bundle/plugin-site/ornith_g256/attention_verify.py +64 -0
  38. bundle/plugin-site/ornith_g256/attention_window.py +74 -0
  39. bundle/plugin-site/ornith_g256/cache_full1120.py +72 -0
  40. bundle/plugin-site/ornith_g256/column_backend.py +93 -0
  41. bundle/plugin-site/ornith_g256/column_kernel.py +263 -0
  42. bundle/plugin-site/ornith_g256/config.py +58 -0
  43. bundle/plugin-site/ornith_g256/dense_n32.py +53 -0
  44. bundle/plugin-site/ornith_g256/dense_source.py +37 -0
  45. bundle/plugin-site/ornith_g256/dflash_conv_boundary.py +123 -0
  46. bundle/plugin-site/ornith_g256/dflash_spec.py +29 -0
  47. bundle/plugin-site/ornith_g256/dynamic_graphs.py +266 -0
  48. bundle/plugin-site/ornith_g256/gdn_compact.py +99 -0
  49. bundle/plugin-site/ornith_g256/gdn_compact_kernel.py +302 -0
  50. 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

  • SHA256: 2dda3ce144f139947260b33cd0216a2d231bbbb6ba1801752633b8f4082700f4
  • Pointer size: 132 Bytes
  • Size of remote file: 2.42 MB
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