jcbtc commited on
Commit
87f154b
·
verified ·
1 Parent(s): 80ba575

Release Ciru runtime 1.0.1 inference accuracy corrections

Browse files

Publish the qualified source-only runtime correction and a short model-card patch note. Preserve model tensors, quantization, native libraries and runtime wheels.

Files changed (24) hide show
  1. ACCURACY-PATCH-1.0.1.json +224 -0
  2. INSTALL.md +12 -0
  3. README.md +18 -0
  4. RELEASE.json +40 -17
  5. RUNTIME-FIXES.md +19 -0
  6. bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/INSTALLER +0 -0
  7. bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/METADATA +1 -1
  8. bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/RECORD +19 -13
  9. bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/REQUESTED +0 -0
  10. bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/WHEEL +0 -0
  11. bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/direct_url.json +0 -0
  12. bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/entry_points.txt +0 -0
  13. bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/licenses/LICENSE-APACHE-2.0 +0 -0
  14. bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/top_level.txt +0 -0
  15. bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/uv_build.json +0 -0
  16. bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/uv_cache.json +0 -0
  17. bundle/plugin-site/ornith_g256/__init__.py +4 -0
  18. bundle/plugin-site/ornith_g256/_vllm_correctness/__init__.py +1 -0
  19. bundle/plugin-site/ornith_g256/_vllm_correctness/causal_conv1d.py +1307 -0
  20. bundle/plugin-site/ornith_g256/_vllm_correctness/gdn_attn.py +616 -0
  21. bundle/plugin-site/ornith_g256/_vllm_correctness/gpu_model_runner.py +0 -0
  22. bundle/plugin-site/ornith_g256/_vllm_correctness/manifest.json +34 -0
  23. bundle/plugin-site/ornith_g256/_vllm_correctness/qwen_gdn_linear_attn.py +2089 -0
  24. bundle/plugin-site/ornith_g256/runtime_correctness.py +54 -0
ACCURACY-PATCH-1.0.1.json ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "1.0.1",
3
+ "date": "2026-09-14",
4
+ "release_gate": "PASS",
5
+ "source_fixes": [
6
+ "vLLM #52905: FP32 causal convolution operands",
7
+ "vLLM #55504: accepted GDN state and convolution history recovery",
8
+ "Ciru: persistent FULL-graph source/count buffers"
9
+ ],
10
+ "unchanged": {
11
+ "weights": true,
12
+ "quantization": true,
13
+ "native_libraries": true,
14
+ "runtime_wheels": true
15
+ },
16
+ "numerical_checks": {
17
+ "convolution": [
18
+ {
19
+ "kind": "prefill",
20
+ "batch": 1,
21
+ "length": 128,
22
+ "relative_rms": {
23
+ "original": 0.0042585392692000125,
24
+ "fixed": 0.0
25
+ },
26
+ "cache_identical": true,
27
+ "timing": {}
28
+ },
29
+ {
30
+ "kind": "prefill",
31
+ "batch": 1,
32
+ "length": 2048,
33
+ "relative_rms": {
34
+ "original": 0.004253745004015557,
35
+ "fixed": 5.327912728070686e-06
36
+ },
37
+ "cache_identical": true,
38
+ "timing": {}
39
+ },
40
+ {
41
+ "kind": "decode",
42
+ "batch": 1,
43
+ "length": 1,
44
+ "relative_rms": {
45
+ "original": 0.004216764923019954,
46
+ "fixed": 0.0
47
+ },
48
+ "cache_identical": true,
49
+ "timing": {
50
+ "samples_ms": {
51
+ "original": [
52
+ 0.004203464984893799,
53
+ 0.0041772699356079105,
54
+ 0.004181469976902008,
55
+ 0.0041772699356079105,
56
+ 0.004178870022296905,
57
+ 0.004173069894313812
58
+ ],
59
+ "fixed": [
60
+ 0.004222064912319183,
61
+ 0.004208469986915589,
62
+ 0.004195869863033295,
63
+ 0.004188069999217987,
64
+ 0.0041966700553894044,
65
+ 0.004196470081806183
66
+ ]
67
+ },
68
+ "median_ms": {
69
+ "original": 0.004178069978952408,
70
+ "fixed": 0.004196570068597794
71
+ }
72
+ }
73
+ },
74
+ {
75
+ "kind": "decode",
76
+ "batch": 8,
77
+ "length": 1,
78
+ "relative_rms": {
79
+ "original": 0.0042075355290598455,
80
+ "fixed": 0.0
81
+ },
82
+ "cache_identical": true,
83
+ "timing": {
84
+ "samples_ms": {
85
+ "original": [
86
+ 0.005656625032424927,
87
+ 0.005299034714698792,
88
+ 0.005289434790611267,
89
+ 0.005284035205841064,
90
+ 0.00528763473033905,
91
+ 0.005268034934997559
92
+ ],
93
+ "fixed": [
94
+ 0.005488629937171936,
95
+ 0.005388835072517395,
96
+ 0.005369830131530762,
97
+ 0.005377429723739624,
98
+ 0.005364835262298584,
99
+ 0.005378435254096985
100
+ ]
101
+ },
102
+ "median_ms": {
103
+ "original": 0.005288534760475159,
104
+ "fixed": 0.0053779324889183045
105
+ }
106
+ }
107
+ },
108
+ {
109
+ "kind": "spec_decode",
110
+ "batch": 8,
111
+ "length": 5,
112
+ "relative_rms": {
113
+ "original": 0.004256482905263262,
114
+ "fixed": 0.0
115
+ },
116
+ "cache_identical": true,
117
+ "timing": {
118
+ "samples_ms": {
119
+ "original": [
120
+ 0.007730159759521484,
121
+ 0.007706955075263977,
122
+ 0.007715359926223755,
123
+ 0.007733154892921448,
124
+ 0.007715759873390198,
125
+ 0.007782955169677735
126
+ ],
127
+ "fixed": [
128
+ 0.007816755175590516,
129
+ 0.007740954756736755,
130
+ 0.0077583551406860355,
131
+ 0.00775815486907959,
132
+ 0.007768959999084473,
133
+ 0.007762755155563354
134
+ ]
135
+ },
136
+ "median_ms": {
137
+ "original": 0.007722959816455841,
138
+ "fixed": 0.007760555148124695
139
+ }
140
+ }
141
+ }
142
+ ],
143
+ "metadata_fixtures_passed": 2,
144
+ "prior_accepted_state_replay": {
145
+ "bad_token_probability_before": 0.918995,
146
+ "bad_token_probability_after": 2.026e-09,
147
+ "correct_token_probability_after": 0.982934
148
+ },
149
+ "prior_eager_full_graph_agreement": {
150
+ "top20_distributions": 203,
151
+ "full_vocabulary_anchors": 16,
152
+ "bitwise_identical": true
153
+ }
154
+ },
155
+ "performance": {
156
+ "model": "Ornith1.5",
157
+ "hardware": "AMD Ryzen AI Max+ 395 / Radeon 8060S gfx1151, 128 GB",
158
+ "context_limit": 262144,
159
+ "max_sequences": 8,
160
+ "cache_gib": 44,
161
+ "prompt_tokens": 1400,
162
+ "output_tokens": 512,
163
+ "c1_median_decode_tps": {
164
+ "baseline": 52.408086959561246,
165
+ "candidate": 48.729841865167515
166
+ },
167
+ "c1_change_percent": -7.018468537559697,
168
+ "c1_rows_per_variant": 6,
169
+ "c8_median_aggregate_tps": {
170
+ "baseline": 110.74597353115307,
171
+ "candidate": 112.64843345286091
172
+ },
173
+ "c8_change_percent": 1.7178592241754664,
174
+ "c8_rows_per_variant": 3,
175
+ "accepted_tradeoff": "Owner explicitly accepted the approximately 7% C1 throughput regression for immediate release.",
176
+ "scope": "Bounded serving screen at the full configured context limit; not a long-generation quality evaluation."
177
+ },
178
+ "limitations": [
179
+ "This patch does not establish resolution of all long-generation quality issues.",
180
+ "Apodex throughput was not rebenchmarked for this patch."
181
+ ],
182
+ "hardening_experiment_included": false,
183
+ "model": "Ornith1.5",
184
+ "package_serving_check": {
185
+ "host": "Ciru",
186
+ "model_loaded": true,
187
+ "c1_c8_requests_succeeded": true
188
+ },
189
+ "runtime_accuracy_source_commit": "2c50cb2aa31abd0537e9c84b7ec43307e3ea40c7",
190
+ "source_manifest": {
191
+ "runtime_version": "1.0.1",
192
+ "modules": {
193
+ "vllm.model_executor.layers.mamba.gdn.qwen_gdn_linear_attn": {
194
+ "file": "qwen_gdn_linear_attn.py",
195
+ "native_path": "model_executor/layers/mamba/gdn/qwen_gdn_linear_attn.py",
196
+ "native_sha256": "89f04b36241add75cadbc00e0a2dfe5b8a9b325cd107f7f1373639d83d037668",
197
+ "sha256": "3e58fdddc7a0b42ae3d9b5eabb375a993a4110bad499c37846bde466901dbeb3"
198
+ },
199
+ "vllm.model_executor.layers.mamba.ops.causal_conv1d": {
200
+ "file": "causal_conv1d.py",
201
+ "native_path": "model_executor/layers/mamba/ops/causal_conv1d.py",
202
+ "native_sha256": "044d005cfe59fd0818ed421274e04f3e8dd679b8cdb64fca9f4422f2643484b2",
203
+ "sha256": "230f3e554f5f1d609a37022578c84e42d5c71d46e684101278a7e21af4f7d418"
204
+ },
205
+ "vllm.v1.attention.backends.gdn_attn": {
206
+ "file": "gdn_attn.py",
207
+ "native_path": "v1/attention/backends/gdn_attn.py",
208
+ "native_sha256": "c65552d9aad86472544033d44ad8a872221a83e6b60ba9918cc049ab0c580c7c",
209
+ "sha256": "a3c361d502e8bca2aa46a0baed5ec753a3c62e3d027a298e3ed4692794f9f6ec"
210
+ },
211
+ "vllm.v1.worker.gpu_model_runner": {
212
+ "file": "gpu_model_runner.py",
213
+ "native_path": "v1/worker/gpu_model_runner.py",
214
+ "native_sha256": "4706fcf4b85158173d88e3b19ffb0f6a280fcb60554cfdac861a01a0cab4d4df",
215
+ "sha256": "ef0009acaa67a1a5e066984f34bf619b06e85220ff953a3d9511376f0146dccf"
216
+ }
217
+ },
218
+ "upstream": [
219
+ "https://github.com/vllm-project/vllm/pull/52905",
220
+ "https://github.com/vllm-project/vllm/pull/55504"
221
+ ],
222
+ "local_extension": "Persistent recovery source/count tensors for FULL graphs."
223
+ }
224
+ }
INSTALL.md CHANGED
@@ -1,5 +1,17 @@
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
 
1
  # Install and run Ornith1.5 Ciru Halo Agent
2
 
3
+ ## Updating to Ciru runtime 1.0.1
4
+
5
+ Download the current `bundle/plugin-site/` contents and restart the model
6
+ process. The package selects its corrected vLLM source modules automatically;
7
+ the pinned runtime wheel and native libraries do not need reinstalling. When
8
+ updating an existing local download, remove the obsolete
9
+ `bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/` metadata directory once
10
+ `ciru_ornith_g256-1.0.1.dist-info/` is present. A fresh snapshot has only the new
11
+ metadata directory. The loader verifies its source hashes and the pinned vLLM
12
+ source before model construction.
13
+
14
+
15
  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.**
16
 
17
  ## Hardware and platform
README.md CHANGED
@@ -25,6 +25,24 @@ tags:
25
 
26
  > **Known correctness issues:** This release currently has correctness issues. I'm actively working on fixing them.
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  # Ornith1.5 Ciru Halo Agent (vllm strix halo)
29
 
30
  ![Ornith1.5 Ciru Halo Agent — local agents on AMD Strix Halo](assets/ciru-halo-agent.png)
 
25
 
26
  > **Known correctness issues:** This release currently has correctness issues. I'm actively working on fixing them.
27
 
28
+
29
+ **Ciru runtime 1.0.1 — September 14, 2026.** This accuracy patch promotes
30
+ BF16 causal-convolution operands to FP32 before multiplication, preventing an
31
+ extra rounding step, and restores the accepted GDN recurrent state and
32
+ convolution history when a speculative batch returns to ordinary decoding.
33
+ Recovery metadata uses persistent buffers so the correction also works with
34
+ captured FULL graphs. IU4 weights and native libraries are unchanged.
35
+
36
+ On Ornith, the matched local speed screen found approximately 7% lower single-request
37
+ throughput and roughly unchanged eight-request throughput. This tradeoff is
38
+ accepted for this correctness release.
39
+
40
+ The convolution correction follows [vLLM #52905](https://github.com/vllm-project/vllm/pull/52905);
41
+ accepted-state recovery follows [vLLM #55504](https://github.com/vllm-project/vllm/pull/55504),
42
+ with Ciru's graph-buffer extension. See [runtime patch validation](ACCURACY-PATCH-1.0.1.json)
43
+ for the bounded correctness and speed checks. This patch does not establish that
44
+ all long-generation quality issues are resolved.
45
+
46
  # Ornith1.5 Ciru Halo Agent (vllm strix halo)
47
 
48
  ![Ornith1.5 Ciru Halo Agent — local agents on AMD Strix Halo](assets/ciru-halo-agent.png)
RELEASE.json CHANGED
@@ -1,13 +1,13 @@
1
  {
2
  "model": "Ornith1.5 Ciru Halo Agent",
3
  "source_repository": "https://github.com/ciru-ai/ornith-ciru-halo-agent",
4
- "source_commit": "b28cd9967ae8bb58f74c7143a7ac6045404d075f",
5
  "benchmarks": "https://llm.ciru.ai/research/ornith-strix/",
6
  "target_revision": "10fbf86fed7ecee4a061f8b499a618f46001cac1",
7
  "draft_revision": "9b4852c05fd00b672b7434b1bb105bc03c8682b0",
8
  "files_bytes": {
9
- "INSTALL.md": 5431,
10
- "README.md": 18320,
11
  "CREDITS.md": 6275,
12
  ".gitattributes": 220,
13
  "NOTICE": 1088,
@@ -47,17 +47,7 @@
47
  "bundle/native/libornith_routed_storage_n32.so": 158536,
48
  "bundle/packaging/serve.sh": 1474,
49
  "bundle/cache/aiter/module_aiter_core.so": 567024,
50
- "bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/INSTALLER": 2,
51
- "bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/METADATA": 256,
52
- "bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/RECORD": 4895,
53
- "bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/REQUESTED": 0,
54
- "bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/WHEEL": 91,
55
- "bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/direct_url.json": 105,
56
- "bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/entry_points.txt": 121,
57
- "bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/top_level.txt": 12,
58
- "bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/uv_build.json": 2,
59
- "bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/uv_cache.json": 137,
60
- "bundle/plugin-site/ornith_g256/__init__.py": 1278,
61
  "bundle/plugin-site/ornith_g256/adaptive_c1.py": 14985,
62
  "bundle/plugin-site/ornith_g256/attention.py": 772,
63
  "bundle/plugin-site/ornith_g256/attention_compact.py": 10412,
@@ -96,7 +86,6 @@
96
  "bundle/plugin-site/ornith_g256/runtime.py": 6009,
97
  "bundle/plugin-site/ornith_g256/worker.py": 13579,
98
  "bundle/plugin-site/ornith_g256/worker_base.py": 4601,
99
- "bundle/plugin-site/ciru_ornith_g256-0.0.2a0.dist-info/licenses/LICENSE-APACHE-2.0": 11358,
100
  "bundle/models/draft/LICENSE": 11358,
101
  "bundle/models/draft/README.md": 7675,
102
  "bundle/models/draft/config.json": 1311,
@@ -183,7 +172,26 @@
183
  "bundle/plugin-site/ornith_g256/strict_qwen/__init__.py": 81,
184
  "bundle/plugin-site/ornith_g256/strict_qwen/qwen3.py": 11658,
185
  "bundle/plugin-site/ornith_g256/strict_qwen/qwen3_contract.py": 8320,
186
- "RUNTIME-FIXES.md": 4485
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
187
  },
188
  "native_sha256": {
189
  "libornith_attention_iu4.so": "2905806824bece62ce9d9140608859df869f3e3d3663c67bf79ad4ea495cd61f",
@@ -248,5 +256,20 @@
248
  "new_public_package_gpu_run": false
249
  },
250
  "native_parameter_whitespace": "Original Qwen XML wrapping-newline convention retained; exact boundary whitespace not guaranteed."
251
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
252
  }
 
1
  {
2
  "model": "Ornith1.5 Ciru Halo Agent",
3
  "source_repository": "https://github.com/ciru-ai/ornith-ciru-halo-agent",
4
+ "source_commit": "2c50cb2aa31abd0537e9c84b7ec43307e3ea40c7",
5
  "benchmarks": "https://llm.ciru.ai/research/ornith-strix/",
6
  "target_revision": "10fbf86fed7ecee4a061f8b499a618f46001cac1",
7
  "draft_revision": "9b4852c05fd00b672b7434b1bb105bc03c8682b0",
8
  "files_bytes": {
9
+ "INSTALL.md": 6029,
10
+ "README.md": 19507,
11
  "CREDITS.md": 6275,
12
  ".gitattributes": 220,
13
  "NOTICE": 1088,
 
47
  "bundle/native/libornith_routed_storage_n32.so": 158536,
48
  "bundle/packaging/serve.sh": 1474,
49
  "bundle/cache/aiter/module_aiter_core.so": 567024,
50
+ "bundle/plugin-site/ornith_g256/__init__.py": 1368,
 
 
 
 
 
 
 
 
 
 
51
  "bundle/plugin-site/ornith_g256/adaptive_c1.py": 14985,
52
  "bundle/plugin-site/ornith_g256/attention.py": 772,
53
  "bundle/plugin-site/ornith_g256/attention_compact.py": 10412,
 
86
  "bundle/plugin-site/ornith_g256/runtime.py": 6009,
87
  "bundle/plugin-site/ornith_g256/worker.py": 13579,
88
  "bundle/plugin-site/ornith_g256/worker_base.py": 4601,
 
89
  "bundle/models/draft/LICENSE": 11358,
90
  "bundle/models/draft/README.md": 7675,
91
  "bundle/models/draft/config.json": 1311,
 
172
  "bundle/plugin-site/ornith_g256/strict_qwen/__init__.py": 81,
173
  "bundle/plugin-site/ornith_g256/strict_qwen/qwen3.py": 11658,
174
  "bundle/plugin-site/ornith_g256/strict_qwen/qwen3_contract.py": 8320,
175
+ "RUNTIME-FIXES.md": 5591,
176
+ "ACCURACY-PATCH-1.0.1.json": 7126,
177
+ "bundle/plugin-site/ornith_g256/runtime_correctness.py": 2151,
178
+ "bundle/plugin-site/ciru_ornith_g256-1.0.1.dist-info/REQUESTED": 0,
179
+ "bundle/plugin-site/ciru_ornith_g256-1.0.1.dist-info/RECORD": 5505,
180
+ "bundle/plugin-site/ciru_ornith_g256-1.0.1.dist-info/WHEEL": 91,
181
+ "bundle/plugin-site/ciru_ornith_g256-1.0.1.dist-info/top_level.txt": 12,
182
+ "bundle/plugin-site/ciru_ornith_g256-1.0.1.dist-info/INSTALLER": 2,
183
+ "bundle/plugin-site/ciru_ornith_g256-1.0.1.dist-info/direct_url.json": 105,
184
+ "bundle/plugin-site/ciru_ornith_g256-1.0.1.dist-info/entry_points.txt": 121,
185
+ "bundle/plugin-site/ciru_ornith_g256-1.0.1.dist-info/METADATA": 254,
186
+ "bundle/plugin-site/ciru_ornith_g256-1.0.1.dist-info/uv_build.json": 2,
187
+ "bundle/plugin-site/ciru_ornith_g256-1.0.1.dist-info/uv_cache.json": 137,
188
+ "bundle/plugin-site/ciru_ornith_g256-1.0.1.dist-info/licenses/LICENSE-APACHE-2.0": 11358,
189
+ "bundle/plugin-site/ornith_g256/_vllm_correctness/qwen_gdn_linear_attn.py": 78334,
190
+ "bundle/plugin-site/ornith_g256/_vllm_correctness/causal_conv1d.py": 51774,
191
+ "bundle/plugin-site/ornith_g256/_vllm_correctness/gdn_attn.py": 26224,
192
+ "bundle/plugin-site/ornith_g256/_vllm_correctness/gpu_model_runner.py": 345523,
193
+ "bundle/plugin-site/ornith_g256/_vllm_correctness/__init__.py": 79,
194
+ "bundle/plugin-site/ornith_g256/_vllm_correctness/manifest.json": 1607
195
  },
196
  "native_sha256": {
197
  "libornith_attention_iu4.so": "2905806824bece62ce9d9140608859df869f3e3d3663c67bf79ad4ea495cd61f",
 
256
  "new_public_package_gpu_run": false
257
  },
258
  "native_parameter_whitespace": "Original Qwen XML wrapping-newline convention retained; exact boundary whitespace not guaranteed."
259
+ },
260
+ "runtime_version": "1.0.1",
261
+ "release_date": "2026-09-14",
262
+ "accuracy_patch": {
263
+ "version": "1.0.1",
264
+ "details": "ACCURACY-PATCH-1.0.1.json",
265
+ "weights_changed": false,
266
+ "native_libraries_changed": false,
267
+ "fixes": [
268
+ "FP32 products in BF16 causal convolution",
269
+ "Accepted GDN state and convolution history recovery",
270
+ "Persistent accepted-state source/count buffers for FULL graphs"
271
+ ],
272
+ "single_request_throughput_tradeoff": "Approximately 7% lower in the Ornith screen, accepted by the owner for this correctness release."
273
+ },
274
+ "runtime_accuracy_source_commit": "2c50cb2aa31abd0537e9c84b7ec43307e3ea40c7"
275
  }
RUNTIME-FIXES.md CHANGED
@@ -1,3 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # Runtime fixes — 13 September 2026
2
 
3
  This update prevents a reproduced cache-corruption crash and makes malformed tool output fail explicitly. It updates the serving plugin; the released weights, native libraries, runtime wheels, sampler, context pool and adaptive DFlash2 policy are unchanged.
 
1
+ # Ciru runtime 1.0.1 accuracy patch
2
+
3
+ **Ciru runtime 1.0.1 — September 14, 2026.** This accuracy patch promotes
4
+ BF16 causal-convolution operands to FP32 before multiplication, preventing an
5
+ extra rounding step, and restores the accepted GDN recurrent state and
6
+ convolution history when a speculative batch returns to ordinary decoding.
7
+ Recovery metadata uses persistent buffers so the correction also works with
8
+ captured FULL graphs. IU4 weights and native libraries are unchanged.
9
+
10
+ On Ornith, the matched local speed screen found approximately 7% lower single-request
11
+ throughput and roughly unchanged eight-request throughput. This tradeoff is
12
+ accepted for this correctness release.
13
+
14
+ The convolution correction follows [vLLM #52905](https://github.com/vllm-project/vllm/pull/52905);
15
+ accepted-state recovery follows [vLLM #55504](https://github.com/vllm-project/vllm/pull/55504),
16
+ with Ciru's graph-buffer extension. See [runtime patch validation](ACCURACY-PATCH-1.0.1.json)
17
+ for the bounded correctness and speed checks. This patch does not establish that
18
+ all long-generation quality issues are resolved.
19
+
20
  # Runtime fixes — 13 September 2026
21
 
22
  This update prevents a reproduced cache-corruption crash and makes malformed tool output fail explicitly. It updates the serving plugin; the released weights, native libraries, runtime wheels, sampler, context pool and adaptive DFlash2 policy are unchanged.
bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/INSTALLER RENAMED
File without changes
bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/METADATA RENAMED
@@ -1,6 +1,6 @@
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
 
1
  Metadata-Version: 2.4
2
  Name: ciru-ornith-g256
3
+ Version: 1.0.1
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
bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/RECORD RENAMED
@@ -1,16 +1,20 @@
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=oVy7BnmpzEdmp3xXWU6UHpqRE4H0LIFc_eyxici2Y1w,1278
 
 
 
 
14
  ornith_g256/adaptive_c1.py,sha256=jkj9h0-99lGDwgvMfHSNJWLbyvqX4ATxiigYuYmAqAk,14985
15
  ornith_g256/attention.py,sha256=L908DthNywUqno-d9AaN34CpaZMb6gopXgItNrdTzaE,772
16
  ornith_g256/attention_compact.py,sha256=ZrrGSCCgtUdW2amQLV-AZmdaorCz6w7HP_G0O0NAeWM,10412
@@ -49,8 +53,10 @@ ornith_g256/phase_dispatch.py,sha256=Gk698q9wnmylGeCgj2pTdgLfF4yldTMgRWF2MWQPgTc
49
  ornith_g256/prefill_draft.py,sha256=15uymhJGOVQ6ZOGYkI4urvTLz0kvLl4U3rz8nIZ4FvU,5630
50
  ornith_g256/prefix_cache.py,sha256=ZfuXLiheN3QfU7Ih0vY4DsRk00o582u2aoLcqsq3J9A,15261
51
  ornith_g256/runtime.py,sha256=20Oxw3ZnIODb7k6GF67mc6Uzww02_MH79DkZcX2KsUk,6009
 
52
  ornith_g256/strict_qwen/__init__.py,sha256=7opQVSSfFBP8IvRkIat3tUhao5sZnGjrW1bHXcxAki0,81
53
  ornith_g256/strict_qwen/qwen3.py,sha256=M8ZTRHoUS3IlflCMzZJhDbbTBXjij5jGnJkoF3PerRw,11658
54
  ornith_g256/strict_qwen/qwen3_contract.py,sha256=dKCwPjUyiD-k1wSU-67K53YOKTy7a05YhkCITzCTBSo,8320
55
  ornith_g256/worker.py,sha256=En2VXcVk520ONHYD_aQmSaisvXJHfIMfg5Z5g--n63o,13579
56
  ornith_g256/worker_base.py,sha256=oA7xKVFy5lXIs8fXkM_QMWEUqUkuSD3t_eoDbTt7-Gg,4601
 
 
1
+ ciru_ornith_g256-1.0.1.dist-info/INSTALLER,sha256=5hhM4Q4mYTT9z6QB6PGpUAW81PGNFrYrdXMj4oM_6ak,2
2
+ ciru_ornith_g256-1.0.1.dist-info/METADATA,sha256=3sWz5jvdEBdhhUarJZrqGKYLZgBv-xw1It9DUn11B8A,254
3
+ ciru_ornith_g256-1.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ ciru_ornith_g256-1.0.1.dist-info/WHEEL,sha256=SmOxYU7pzNKBqASvQJ7DjX3XGUF92lrGhMb3R6_iiqI,91
5
+ ciru_ornith_g256-1.0.1.dist-info/direct_url.json,sha256=MwFMTtCGcu6fn-TruM1lo2XsI7q0gT0JZbfW7sb3OEA,105
6
+ ciru_ornith_g256-1.0.1.dist-info/entry_points.txt,sha256=ESe1wopdkiGfr5DNWZcSemlgPr3uvRre1OyHkl88QqU,121
7
+ ciru_ornith_g256-1.0.1.dist-info/licenses/LICENSE-APACHE-2.0,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
8
+ ciru_ornith_g256-1.0.1.dist-info/top_level.txt,sha256=EdCMLXnn8tDNBUR3JhkMvOi8hEw6IuhYFYEcfdhekxU,12
9
+ ciru_ornith_g256-1.0.1.dist-info/uv_build.json,sha256=RBNvo1WzZ4oRRq0W9-hknpT7T8If536DEMBg9hyq_4o,2
10
+ ciru_ornith_g256-1.0.1.dist-info/uv_cache.json,sha256=L73WKGonFia8yJ8vK90qJmbQxtUhlvCNsq7D4umUJX4,137
11
+ ornith_g256/__init__.py,sha256=tqhBbqjsIA7JBuJINSvgLpqqNdviaoSPeG-jINdHqPM,1368
12
+ ornith_g256/_vllm_correctness/__init__.py,sha256=ljCXEGQHqHtisnUEFRYUTeFXE97mUQiszCnicDb_wFk,79
13
+ ornith_g256/_vllm_correctness/causal_conv1d.py,sha256=Iw8-VU9fHWCaNwIleMhOQtXHHUbmhBASeKfiGvT31Bg,51774
14
+ ornith_g256/_vllm_correctness/gdn_attn.py,sha256=o8Nh1QLovKKqRqC67V7HU6PGLj0CeimOPtRpJ5T59uw,26224
15
+ ornith_g256/_vllm_correctness/gpu_model_runner.py,sha256=7wAJrKpnoaXgZphPNL9hmwboUiD_lTo9lRE3bwFG3M8,345523
16
+ ornith_g256/_vllm_correctness/manifest.json,sha256=Dd-kifT56tE1AG4pqmdedSKvpc7hBZbCEV4H8TGGR4w,1607
17
+ ornith_g256/_vllm_correctness/qwen_gdn_linear_attn.py,sha256=Plj93cegtCrj2bXquzdamTpBELrUmcN4Rr3kZpAdvrM,78334
18
  ornith_g256/adaptive_c1.py,sha256=jkj9h0-99lGDwgvMfHSNJWLbyvqX4ATxiigYuYmAqAk,14985
19
  ornith_g256/attention.py,sha256=L908DthNywUqno-d9AaN34CpaZMb6gopXgItNrdTzaE,772
20
  ornith_g256/attention_compact.py,sha256=ZrrGSCCgtUdW2amQLV-AZmdaorCz6w7HP_G0O0NAeWM,10412
 
53
  ornith_g256/prefill_draft.py,sha256=15uymhJGOVQ6ZOGYkI4urvTLz0kvLl4U3rz8nIZ4FvU,5630
54
  ornith_g256/prefix_cache.py,sha256=ZfuXLiheN3QfU7Ih0vY4DsRk00o582u2aoLcqsq3J9A,15261
55
  ornith_g256/runtime.py,sha256=20Oxw3ZnIODb7k6GF67mc6Uzww02_MH79DkZcX2KsUk,6009
56
+ ornith_g256/runtime_correctness.py,sha256=CPQXc-YjpPmc-KsZ5DTX4zusnPpesFqdwPRRGquLpLw,2151
57
  ornith_g256/strict_qwen/__init__.py,sha256=7opQVSSfFBP8IvRkIat3tUhao5sZnGjrW1bHXcxAki0,81
58
  ornith_g256/strict_qwen/qwen3.py,sha256=M8ZTRHoUS3IlflCMzZJhDbbTBXjij5jGnJkoF3PerRw,11658
59
  ornith_g256/strict_qwen/qwen3_contract.py,sha256=dKCwPjUyiD-k1wSU-67K53YOKTy7a05YhkCITzCTBSo,8320
60
  ornith_g256/worker.py,sha256=En2VXcVk520ONHYD_aQmSaisvXJHfIMfg5Z5g--n63o,13579
61
  ornith_g256/worker_base.py,sha256=oA7xKVFy5lXIs8fXkM_QMWEUqUkuSD3t_eoDbTt7-Gg,4601
62
+ ciru_ornith_g256-1.0.1.dist-info/RECORD,,
bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/REQUESTED RENAMED
File without changes
bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/WHEEL RENAMED
File without changes
bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/direct_url.json RENAMED
File without changes
bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/entry_points.txt RENAMED
File without changes
bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/licenses/LICENSE-APACHE-2.0 RENAMED
File without changes
bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/top_level.txt RENAMED
File without changes
bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/uv_build.json RENAMED
File without changes
bundle/plugin-site/{ciru_ornith_g256-0.0.2a0.dist-info → ciru_ornith_g256-1.0.1.dist-info}/uv_cache.json RENAMED
File without changes
bundle/plugin-site/ornith_g256/__init__.py CHANGED
@@ -1,6 +1,10 @@
1
  """Ciru G256 prototype; no installed vLLM files are modified."""
2
 
3
 
 
 
 
 
4
  def register():
5
  from .graph_phase_guard import install as install_graph_phase_guard
6
  install_graph_phase_guard()
 
1
  """Ciru G256 prototype; no installed vLLM files are modified."""
2
 
3
 
4
+ from .runtime_correctness import install as _install_correctness
5
+ _install_correctness()
6
+
7
+
8
  def register():
9
  from .graph_phase_guard import install as install_graph_phase_guard
10
  install_graph_phase_guard()
bundle/plugin-site/ornith_g256/_vllm_correctness/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Pinned vLLM source modules carrying the Ciru 1.0.1 accuracy corrections."""
bundle/plugin-site/ornith_g256/_vllm_correctness/causal_conv1d.py ADDED
@@ -0,0 +1,1307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+
4
+ # Copyright (c) 2024, Tri Dao.
5
+ # Adapted from https://github.com/Dao-AILab/causal-conv1d/blob/main/causal_conv1d/causal_conv1d_interface.py
6
+
7
+
8
+ import numpy as np
9
+ import torch
10
+
11
+ from vllm.platforms import current_platform
12
+ from vllm.triton_utils import tl, triton
13
+ from vllm.v1.attention.backends.utils import NULL_BLOCK_ID, PAD_SLOT_ID
14
+
15
+
16
+ @triton.jit(do_not_specialize_on_alignment=["num_cache_lines"])
17
+ def _causal_conv1d_fwd_kernel( # continuous batching
18
+ # Pointers to matrices
19
+ x_ptr, # (dim, cu_seqlen) holding `batch` of actual sequences + padded sequences
20
+ w_ptr, # (dim, width)
21
+ bias_ptr,
22
+ initial_states_ptr, # conv_states_ptr
23
+ cache_indices_ptr, # (batch, n_blocks + padding) The second dimension contains
24
+ # the block indices relevant for each sequence
25
+ # plus potential 0-padding at the beginning and at the end
26
+ has_initial_states_ptr,
27
+ query_start_loc_ptr,
28
+ batch_ptr,
29
+ token_chunk_offset_ptr,
30
+ block_idx_first_scheduled_token, # (batch,)
31
+ block_idx_last_scheduled_token, # (batch,)
32
+ initial_state_idx, # (batch,)
33
+ num_computed_tokens, # (batch,)
34
+ num_accepted_tokens_ptr, # (batch,) or None
35
+ o_ptr, # (dim, seqlen) - actually pointing to x_ptr
36
+ # Matrix dimensions
37
+ dim: tl.constexpr,
38
+ num_cache_lines, # added to support vLLM larger cache lines
39
+ # Strides
40
+ stride_x_dim: tl.constexpr, # stride to get to next feature-value,
41
+ stride_x_token: tl.int64, # stride to get to next token (same feature-index, same sequence-index)
42
+ stride_w_dim: tl.constexpr, # stride to get to next dim-axis value
43
+ stride_w_width: tl.constexpr, # stride to get to next width-axis value
44
+ stride_istate_seq: tl.constexpr,
45
+ stride_istate_dim: tl.constexpr,
46
+ stride_istate_token: tl.constexpr,
47
+ stride_cache_indices: tl.constexpr,
48
+ stride_o_dim: tl.constexpr,
49
+ stride_o_token: tl.int64,
50
+ stride_block_m: tl.constexpr, # Stride block to align divided by BLOCK_M
51
+ # others
52
+ pad_slot_id: tl.constexpr,
53
+ null_block_id: tl.constexpr,
54
+ # Meta-parameters
55
+ HAS_BIAS: tl.constexpr,
56
+ KERNEL_WIDTH: tl.constexpr,
57
+ SILU_ACTIVATION: tl.constexpr,
58
+ IS_APC_ENABLED: tl.constexpr,
59
+ IS_SPEC_DECODING: tl.constexpr,
60
+ HAS_NULL_BLOCK: tl.constexpr,
61
+ NP2_STATELEN: tl.constexpr,
62
+ BLOCK_M: tl.constexpr,
63
+ BLOCK_N: tl.constexpr,
64
+ launch_pdl: tl.constexpr,
65
+ ):
66
+ conv_states_ptr = initial_states_ptr
67
+ conv_state_indices_ptr = cache_indices_ptr
68
+ stride_conv_state_seq = stride_istate_seq
69
+ stride_conv_state_dim = stride_istate_dim
70
+ stride_conv_state_tok = stride_istate_token
71
+ state_len = (
72
+ KERNEL_WIDTH - 1
73
+ ) # can be passed via argument if it's not the same as this value
74
+
75
+ if launch_pdl:
76
+ tl.extra.cuda.gdc_wait()
77
+
78
+ # one program handles one chunk in a single sequence
79
+ # rather than mixing sequences - to make updating initial_states across sequences efficiently
80
+
81
+ # single-sequence id
82
+ idx_seq = tl.load(batch_ptr + tl.program_id(0)).to(tl.int64)
83
+
84
+ if IS_SPEC_DECODING:
85
+ conv_state_token_offset = (
86
+ tl.load(num_accepted_tokens_ptr + idx_seq).to(tl.int64) - 1
87
+ )
88
+ else:
89
+ conv_state_token_offset = 0
90
+ chunk_offset = tl.load(token_chunk_offset_ptr + tl.program_id(0))
91
+
92
+ # BLOCK_N elements along the feature-dimension (channel)
93
+ idx_feats = tl.program_id(1) * BLOCK_N + tl.arange(0, BLOCK_N)
94
+
95
+ if idx_seq == pad_slot_id:
96
+ if launch_pdl:
97
+ tl.extra.cuda.gdc_launch_dependents()
98
+ return
99
+
100
+ sequence_start_index = tl.load(query_start_loc_ptr + idx_seq)
101
+ sequence_end_index = tl.load(query_start_loc_ptr + idx_seq + 1)
102
+ # find the actual sequence length
103
+ seqlen = sequence_end_index - sequence_start_index
104
+
105
+ B_size: tl.constexpr = stride_block_m * BLOCK_M
106
+
107
+ if IS_APC_ENABLED:
108
+ # Handle the case if prefix caching is enabled.
109
+ # In particular, if prefix caching is enabled, the program write additional cache states to "cache_indices_ptr"
110
+
111
+ # Get the length of the completed sequence so far and compute the offset.
112
+ current_first_index = tl.load(block_idx_first_scheduled_token + idx_seq)
113
+ current_last_index = tl.load(block_idx_last_scheduled_token + idx_seq)
114
+ sequence_completed_index = tl.load(num_computed_tokens + idx_seq)
115
+
116
+ # Compute the offset where the first stride_block_m-aligned first full block is
117
+ # Value in "token-space"
118
+ sequence_completed_offset_token = sequence_completed_index % B_size
119
+ seq_completed_offset = B_size - sequence_completed_offset_token
120
+ seq_end_offset = (seqlen - seq_completed_offset) % B_size
121
+ last_full_block_token_index = sequence_end_index - seq_end_offset
122
+ # If the sequence without the sequence_offset_index is stride_cache_chunk-aligned, then the last full chunk is the second-to-last one
123
+ if seq_end_offset == 0:
124
+ last_full_block_token_index = last_full_block_token_index - B_size
125
+
126
+ # Get the number of blocks to be filled for the current sequence
127
+ # If n_block_to_fill = 0, then only the state at the sequence end is stored
128
+ n_block_to_fill = current_last_index - current_first_index
129
+
130
+ # Get the index of the init block
131
+ conv_state_init_index = tl.load(initial_state_idx + idx_seq)
132
+ else:
133
+ n_block_to_fill = 0
134
+ current_last_index = 0
135
+ conv_state_init_index = 0
136
+ current_first_index = 0
137
+ last_full_block_token_index = 0
138
+
139
+ token_offset = BLOCK_M * chunk_offset
140
+ segment_len = min(BLOCK_M, seqlen - token_offset)
141
+
142
+ # base of the sequence
143
+ x_base = (
144
+ x_ptr + sequence_start_index * stride_x_token + idx_feats * stride_x_dim
145
+ ) # [BLOCK_N,]
146
+
147
+ # cache_idx
148
+ conv_states_input_coord = tl.load(
149
+ conv_state_indices_ptr + idx_seq * stride_cache_indices + conv_state_init_index
150
+ ).to(tl.int64)
151
+
152
+ if HAS_NULL_BLOCK: # noqa
153
+ if conv_states_input_coord == null_block_id:
154
+ # not processing as this is a null block (padding)
155
+ if launch_pdl:
156
+ tl.extra.cuda.gdc_launch_dependents()
157
+ return
158
+ conv_states_base = (
159
+ conv_states_ptr
160
+ + (conv_states_input_coord * stride_conv_state_seq)
161
+ + (idx_feats * stride_conv_state_dim)
162
+ ) # [BLOCK_N,]
163
+
164
+ w_base = w_ptr + (idx_feats * stride_w_dim) # [BLOCK_N,]
165
+
166
+ # Does 2 things:
167
+ # 1. READ prior-block init-state data - [done by every Triton programs]
168
+ # 2. update conv_state with new data [only by the Triton program handles chunk_offset=0]
169
+ if chunk_offset == 0:
170
+ # read from conv_states
171
+ load_init_state = tl.load(has_initial_states_ptr + idx_seq).to(tl.int1)
172
+ if load_init_state:
173
+ # load from conv_states
174
+ prior_tokens = (
175
+ conv_states_base
176
+ + (state_len - 1 + conv_state_token_offset) * stride_conv_state_tok
177
+ )
178
+ mask_w = idx_feats < dim
179
+ if KERNEL_WIDTH == 2:
180
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
181
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0)
182
+ if KERNEL_WIDTH == 3:
183
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
184
+ col1 = tl.load(conv_states_ptrs, mask_w, 0.0)
185
+ conv_states_ptrs = prior_tokens - 1 * stride_conv_state_tok # [BLOCK_N]
186
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0)
187
+ if KERNEL_WIDTH == 4:
188
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
189
+ col2 = tl.load(conv_states_ptrs, mask_w, 0.0)
190
+ conv_states_ptrs = prior_tokens - 1 * stride_conv_state_tok # [BLOCK_N]
191
+ col1 = tl.load(conv_states_ptrs, mask_w, 0.0)
192
+ conv_states_ptrs = prior_tokens - 2 * stride_conv_state_tok # [BLOCK_N]
193
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0)
194
+ if KERNEL_WIDTH == 5:
195
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
196
+ col3 = tl.load(conv_states_ptrs, mask_w, 0.0)
197
+ conv_states_ptrs = prior_tokens - 1 * stride_conv_state_tok # [BLOCK_N]
198
+ col2 = tl.load(conv_states_ptrs, mask_w, 0.0)
199
+ conv_states_ptrs = prior_tokens - 2 * stride_conv_state_tok # [BLOCK_N]
200
+ col1 = tl.load(conv_states_ptrs, mask_w, 0.0)
201
+ conv_states_ptrs = prior_tokens - 3 * stride_conv_state_tok # [BLOCK_N]
202
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0)
203
+ else:
204
+ # prior-tokens are zeros
205
+ if KERNEL_WIDTH >= 2: # STRATEGY1
206
+ # first chunk and does not have prior-token, so just set to 0
207
+ col0 = tl.zeros((BLOCK_N,), dtype=x_ptr.dtype.element_ty)
208
+ if KERNEL_WIDTH >= 3: # STRATEGY1
209
+ col1 = tl.zeros((BLOCK_N,), dtype=x_ptr.dtype.element_ty)
210
+ if KERNEL_WIDTH >= 4: # STRATEGY1
211
+ col2 = tl.zeros((BLOCK_N,), dtype=x_ptr.dtype.element_ty)
212
+ if KERNEL_WIDTH >= 5: # STRATEGY1
213
+ col3 = tl.zeros((BLOCK_N,), dtype=x_ptr.dtype.element_ty)
214
+
215
+ # STEP 2:
216
+ # here prepare data for updating conv_state
217
+ if (
218
+ state_len <= seqlen
219
+ ): # SMALL_CACHE=True (only move part of 'x' into conv_state cache)
220
+ # just read from 'x'
221
+ # copy 'x' data to conv_state
222
+ # load only 'x' data (and set 0 before 'x' if seqlen < state_len)
223
+ idx_tokens_last = (seqlen - state_len) + tl.arange(
224
+ 0, NP2_STATELEN
225
+ ) # [BLOCK_M]
226
+ x_ptrs = (
227
+ x_ptr
228
+ + ((sequence_start_index + idx_tokens_last) * stride_x_token)[:, None]
229
+ + (idx_feats * stride_x_dim)[None, :]
230
+ ) # [BLOCK_M,BLOCK_N,]
231
+ mask_x = (
232
+ (idx_tokens_last >= 0)[:, None]
233
+ & (idx_tokens_last < seqlen)[:, None]
234
+ & (idx_feats < dim)[None, :]
235
+ ) # token-index # token-index # feature-index
236
+ loaded_x = tl.load(x_ptrs, mask_x, 0.0)
237
+ idx_tokens_conv = tl.arange(0, NP2_STATELEN) # [BLOCK_M]
238
+
239
+ # Compute the offset where the last block should be written in the conv_states
240
+ conv_states_output_coord = tl.load(
241
+ conv_state_indices_ptr
242
+ + idx_seq * stride_cache_indices
243
+ + current_last_index
244
+ ).to(tl.int64)
245
+
246
+ conv_states_ptrs_target = (
247
+ conv_states_ptr
248
+ + (conv_states_output_coord * stride_conv_state_seq) # Offset from seq
249
+ + (idx_feats * stride_conv_state_dim)
250
+ )[None, :] + ( # [BLOCK_N,]
251
+ idx_tokens_conv * stride_conv_state_tok
252
+ )[:, None]
253
+
254
+ mask = (idx_tokens_conv < state_len)[:, None] & (idx_feats < dim)[None, :]
255
+ tl.debug_barrier() # NOTE: use this due to bug in Triton compiler
256
+ tl.store(conv_states_ptrs_target, loaded_x, mask)
257
+
258
+ else:
259
+ if load_init_state:
260
+ # update conv_state by shifting left, i.e. take last few cols from conv_state + cols from 'x'
261
+ idx_tokens_conv = tl.arange(0, NP2_STATELEN) # [BLOCK_M]
262
+
263
+ conv_states_ptrs_source = (
264
+ conv_states_ptr
265
+ + (conv_states_input_coord * stride_conv_state_seq)
266
+ + (idx_feats * stride_conv_state_dim)[None, :]
267
+ + (
268
+ (idx_tokens_conv + seqlen + conv_state_token_offset)
269
+ * stride_conv_state_tok
270
+ )[:, None]
271
+ ) # [BLOCK_M, BLOCK_N]
272
+ mask = (
273
+ (conv_states_input_coord < num_cache_lines)
274
+ & ((idx_tokens_conv + seqlen) < state_len)[:, None]
275
+ & (idx_feats < dim)[None, :]
276
+ )
277
+ conv_state = tl.load(conv_states_ptrs_source, mask, other=0.0)
278
+
279
+ VAL = state_len - seqlen
280
+
281
+ x_ptrs = (
282
+ x_base[None, :]
283
+ + ((idx_tokens_conv - VAL) * stride_x_token)[:, None]
284
+ ) # [BLOCK_M, BLOCK_N]
285
+
286
+ mask_x = (
287
+ (idx_tokens_conv - VAL >= 0)[:, None]
288
+ & (idx_tokens_conv - VAL < seqlen)[:, None]
289
+ & (idx_feats < dim)[None, :]
290
+ ) # token-index # token-index # feature-index
291
+ loaded_x = tl.load(x_ptrs, mask_x, 0.0)
292
+
293
+ tl.debug_barrier() # need this due to the bug in tl.where not enforcing this when data is the result of another tl.load
294
+ new_conv_state = tl.where(
295
+ mask, conv_state, loaded_x
296
+ ) # BUG in 'tl.where' which requires a barrier before this
297
+ conv_states_ptrs_target = (
298
+ conv_states_base
299
+ + (idx_tokens_conv * stride_conv_state_tok)[:, None]
300
+ ) # [BLOCK_M, BLOCK_N]
301
+ mask = (idx_tokens_conv < state_len)[:, None] & (idx_feats < dim)[
302
+ None, :
303
+ ]
304
+ tl.store(conv_states_ptrs_target, new_conv_state, mask)
305
+ else: # load_init_state == False
306
+ # update conv_state by shifting left, BUT
307
+ # set cols prior to 'x' as zeros + cols from 'x'
308
+ idx_tokens_conv = tl.arange(0, NP2_STATELEN) # [BLOCK_M]
309
+
310
+ VAL = state_len - seqlen
311
+
312
+ x_ptrs = (
313
+ x_base[None, :]
314
+ + ((idx_tokens_conv - VAL) * stride_x_token)[:, None]
315
+ ) # [BLOCK_M, BLOCK_N]
316
+
317
+ mask_x = (
318
+ (idx_tokens_conv - VAL >= 0)[:, None]
319
+ & (idx_tokens_conv - VAL < seqlen)[:, None]
320
+ & (idx_feats < dim)[None, :]
321
+ ) # token-index # token-index # feature-index
322
+ new_conv_state = tl.load(x_ptrs, mask_x, 0.0)
323
+
324
+ conv_states_ptrs_target = (
325
+ conv_states_base
326
+ + (idx_tokens_conv * stride_conv_state_tok)[:, None]
327
+ ) # [BLOCK_M, BLOCK_N]
328
+ mask = (idx_tokens_conv < state_len)[:, None] & (idx_feats < dim)[
329
+ None, :
330
+ ]
331
+ tl.store(conv_states_ptrs_target, new_conv_state, mask)
332
+
333
+ else: # chunk_offset > 0
334
+ # read prior-token data from `x`
335
+ load_init_state = True
336
+ prior_tokens = x_base + (token_offset - 1) * stride_x_token
337
+ mask_w = idx_feats < dim
338
+ if KERNEL_WIDTH == 2:
339
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
340
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
341
+ if KERNEL_WIDTH == 3:
342
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
343
+ col1 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
344
+ conv_states_ptrs = prior_tokens - 1 * stride_x_token # [BLOCK_N]
345
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
346
+ if KERNEL_WIDTH == 4:
347
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
348
+ col2 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
349
+ conv_states_ptrs = prior_tokens - 1 * stride_x_token # [BLOCK_N]
350
+ col1 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
351
+ conv_states_ptrs = prior_tokens - 2 * stride_x_token # [BLOCK_N]
352
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
353
+ if KERNEL_WIDTH == 5:
354
+ # ruff: noqa: F841
355
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
356
+ col3 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
357
+ conv_states_ptrs = prior_tokens - 1 * stride_x_token # [BLOCK_N]
358
+ col2 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
359
+ conv_states_ptrs = prior_tokens - 2 * stride_x_token # [BLOCK_N]
360
+ col1 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
361
+ conv_states_ptrs = prior_tokens - 3 * stride_x_token # [BLOCK_N]
362
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0, cache_modifier=".ca")
363
+
364
+ # Store intermediate states aligned with stride_block_m
365
+ # The additional states are cached starting from the last stride_block_m.
366
+ # For example:
367
+ # If n_block_to_fill = 0, then only the state at the sequence end is cached and the process below is not involved.
368
+ # If n_block_to_fill > 0, then the states at the sequence end and at the n_block_to_fill-last
369
+ # stride_block_m are cached.
370
+ # For example chunk_offset = n_block_to_fill stores the state at last_full_block
371
+ if (chunk_offset - 1) < n_block_to_fill:
372
+ # Store the states at the chunk boundaries from the start of the sequence
373
+ idx_tokens_last = (
374
+ last_full_block_token_index
375
+ - (n_block_to_fill - chunk_offset) * B_size
376
+ - state_len
377
+ ) + tl.arange(0, NP2_STATELEN) # [BLOCK_M]
378
+ x_ptrs = (
379
+ x_ptr
380
+ + (idx_tokens_last * stride_x_token)[:, None]
381
+ + (idx_feats * stride_x_dim)[None, :]
382
+ ) # [BLOCK_M,BLOCK_N,]
383
+
384
+ mask_x = (idx_tokens_last >= 0)[:, None] & (idx_feats < dim)[
385
+ None, :
386
+ ] # token-index # token-index # feature-index
387
+ loaded_x = tl.load(x_ptrs, mask_x, 0.0)
388
+ idx_tokens_conv = tl.arange(0, NP2_STATELEN) # [BLOCK_M]
389
+
390
+ # cache_idx
391
+ conv_states_output_coord = tl.load(
392
+ conv_state_indices_ptr
393
+ + idx_seq * stride_cache_indices
394
+ + current_first_index
395
+ + (chunk_offset - 1)
396
+ ).to(tl.int64)
397
+
398
+ conv_states_ptrs_target = (
399
+ conv_states_ptr
400
+ + (conv_states_output_coord * stride_conv_state_seq) # Offset from seq
401
+ + (idx_feats * stride_conv_state_dim)
402
+ )[None, :] + ( # [BLOCK_N,]
403
+ idx_tokens_conv * stride_conv_state_tok
404
+ )[:, None]
405
+
406
+ mask = (idx_tokens_conv < state_len)[:, None] & (idx_feats < dim)[None, :]
407
+ tl.debug_barrier() # NOTE: use this due to bug in Triton compiler
408
+ tl.store(conv_states_ptrs_target, loaded_x, mask)
409
+
410
+ if HAS_BIAS:
411
+ bias = bias_ptr + idx_feats
412
+ mask_bias = idx_feats < dim
413
+ acc_preload = tl.load(bias, mask=mask_bias, other=0.0).to(
414
+ tl.float32
415
+ ) # [BLOCK_N]
416
+ else:
417
+ acc_preload = tl.zeros((BLOCK_N,), dtype=tl.float32)
418
+
419
+ x_base_1d = x_base + token_offset * stride_x_token # starting of chunk
420
+
421
+ # PRE-LOAD WEIGHTS
422
+ mask_w = idx_feats < dim
423
+ if KERNEL_WIDTH >= 2:
424
+ w_ptrs = w_base + (0 * stride_w_width) # [BLOCK_N] tensor
425
+ w_col0 = tl.load(w_ptrs, mask_w, other=0.0)
426
+ w_ptrs = w_base + (1 * stride_w_width) # [BLOCK_N] tensor
427
+ w_col1 = tl.load(w_ptrs, mask_w, other=0.0)
428
+ if KERNEL_WIDTH >= 3:
429
+ w_ptrs = w_base + (2 * stride_w_width) # [BLOCK_N] tensor
430
+ w_col2 = tl.load(w_ptrs, mask_w, other=0.0)
431
+ if KERNEL_WIDTH >= 4:
432
+ w_ptrs = w_base + (3 * stride_w_width) # [BLOCK_N] tensor
433
+ w_col3 = tl.load(w_ptrs, mask_w, other=0.0)
434
+ mask_x_1d = idx_feats < dim
435
+
436
+ if launch_pdl:
437
+ tl.extra.cuda.gdc_launch_dependents()
438
+
439
+ for idx_token in range(segment_len):
440
+ acc = acc_preload
441
+
442
+ matrix_w = w_col0
443
+ matrix_x = col0
444
+ for j in tl.static_range(KERNEL_WIDTH):
445
+ if KERNEL_WIDTH == 2:
446
+ if j == 1: # KERNEL_WIDTH-1:
447
+ matrix_w = w_col1
448
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token # [BLOCK_N]
449
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
450
+ elif KERNEL_WIDTH == 3:
451
+ if j == 1:
452
+ matrix_w = w_col1
453
+ matrix_x = col1
454
+ elif j == 2:
455
+ matrix_w = w_col2
456
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token # [BLOCK_N]
457
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
458
+ elif KERNEL_WIDTH == 4:
459
+ if j == 1:
460
+ matrix_w = w_col1
461
+ matrix_x = col1
462
+ elif j == 2:
463
+ matrix_w = w_col2
464
+ matrix_x = col2
465
+ elif j == 3:
466
+ matrix_w = w_col3
467
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token # [BLOCK_N]
468
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
469
+
470
+ acc += matrix_x.to(tl.float32) * matrix_w.to(tl.float32) # [BLOCK_N]
471
+
472
+ if KERNEL_WIDTH == 2:
473
+ col0 = matrix_x
474
+ elif KERNEL_WIDTH == 3:
475
+ col0 = col1
476
+ col1 = matrix_x
477
+ elif KERNEL_WIDTH == 4:
478
+ col0 = col1
479
+ col1 = col2
480
+ col2 = matrix_x
481
+
482
+ if SILU_ACTIVATION:
483
+ acc = acc / (1 + tl.exp(-acc))
484
+ mask_1d = (idx_token < segment_len) & (
485
+ idx_feats < dim
486
+ ) # token-index # feature-index
487
+ o_ptrs = (
488
+ o_ptr
489
+ + (sequence_start_index + token_offset + idx_token) * stride_o_token
490
+ + (idx_feats * stride_o_dim)
491
+ )
492
+
493
+ tl.store(o_ptrs, acc, mask=mask_1d)
494
+
495
+
496
+ def causal_conv1d_fn(
497
+ x: torch.Tensor,
498
+ weight: torch.Tensor,
499
+ bias: torch.Tensor | None,
500
+ conv_states: torch.Tensor,
501
+ query_start_loc: torch.Tensor,
502
+ cache_indices: torch.Tensor | None = None,
503
+ has_initial_state: torch.Tensor | None = None,
504
+ activation: str | None = "silu",
505
+ pad_slot_id: int = PAD_SLOT_ID,
506
+ null_block_id: int = NULL_BLOCK_ID,
507
+ num_accepted_tokens: torch.Tensor | None = None,
508
+ block_idx_first_scheduled_token: torch.Tensor | None = None,
509
+ block_idx_last_scheduled_token: torch.Tensor | None = None,
510
+ initial_state_idx: torch.Tensor | None = None,
511
+ num_computed_tokens: torch.Tensor | None = None,
512
+ block_size_to_align=0,
513
+ metadata=None,
514
+ validate_data=False,
515
+ ):
516
+ """support varlen + continuous batching when x is 2D tensor
517
+
518
+ x: (dim,cu_seq_len)
519
+ cu_seq_len = total tokens of all seqs in that batch
520
+ sequences are concatenated from left to right for varlen
521
+ weight: (dim, width)
522
+ conv_states: (...,dim,width - 1) itype
523
+ updated inplace if cache_indices are not provided
524
+ [it use `cache_indices` to get the index to the cache of conv_state for that sequence
525
+
526
+ conv_state[cache_indices[i]] for seq-i - to be used as initial_state when has_initial_state[i] = True
527
+ and after that conv_state[cache_indices[i]] need to be shift-left and updated with values from 'x'
528
+ ]
529
+ query_start_loc: (batch + 1) int32
530
+ The cumulative sequence lengths of the sequences in
531
+ the batch, used to index into sequence. prepended by 0.
532
+ if
533
+ x = [5, 1, 1, 1] <- continuous batching (batch=4)
534
+ then
535
+ query_start_loc = [0, 5, 6, 7, 8] <- the starting index of the next sequence; while the last value is
536
+ the ending index of the last sequence
537
+ [length(query_start_loc)-1 == batch]
538
+ for example: query_start_loc = torch.Tensor([0,10,16,17]),
539
+ x.shape=(dim,17)
540
+ cache_indices: (batch) int32
541
+ indicates the corresponding state index,
542
+ like so: conv_state = conv_states[cache_indices[batch_id]]
543
+ has_initial_state: (batch) bool
544
+ indicates whether should the kernel take the current state as initial
545
+ state for the calculations
546
+ [single boolean for each sequence in the batch: True or False]
547
+ bias: (dim,)
548
+ activation: either None or "silu" or "swish" or True
549
+ pad_slot_id: int
550
+ if cache_indices is passed, lets the kernel identify padded
551
+ entries that will not be processed,
552
+ for example: cache_indices = [pad_slot_id, 1, 20, pad_slot_id]
553
+ in this case, the kernel will not process entries at
554
+ indices 0 and 3
555
+ block_idx_first_scheduled_token: (batch,), dtype int32
556
+ The pointer into cache_indices, where the first cache block to be filled is located.
557
+ block_idx_last_scheduled_token: (batch,), dtype int32
558
+ The pointer into cache_indices, where the last cache block to be filled is located.
559
+ initial_state_idx: (batch,), dtype int32
560
+ The pointer into cache_indices, where the cache block containing the initial state is located.
561
+ num_computed_tokens: (batch,), dtype int32
562
+ The number of tokens already completed for each sequence
563
+ block_size_to_align: int
564
+ The block size to align the cached states to
565
+ out: same shape as `x`
566
+ """
567
+ if isinstance(activation, bool) and activation:
568
+ activation = "silu"
569
+
570
+ args = None
571
+ # Store original dtype to cast back at the end
572
+ original_x_dtype = x.dtype
573
+ x = x.to(conv_states.dtype)
574
+ out = torch.empty_like(x)
575
+ if metadata is not None:
576
+ nums_dict = metadata.nums_dict
577
+ args = nums_dict
578
+ batch_ptr = metadata.batch_ptr
579
+ token_chunk_offset_ptr = metadata.token_chunk_offset_ptr
580
+ else:
581
+ seqlens = query_start_loc.diff().to("cpu")
582
+ args = seqlens
583
+ MAX_NUM_PROGRAMS = 1024
584
+
585
+ batch_ptr = torch.full(
586
+ (MAX_NUM_PROGRAMS,), PAD_SLOT_ID, dtype=torch.int32, device=x.device
587
+ ) # tracking which seq-idx the Triton program is handling
588
+ token_chunk_offset_ptr = torch.full(
589
+ (MAX_NUM_PROGRAMS,), PAD_SLOT_ID, dtype=torch.int32, device=x.device
590
+ ) # tracking BLOCK_M-based index in the sequence the Triton program is handling
591
+
592
+ is_channel_last = (x.stride(0) == 1) & (x.stride(1) > 1)
593
+ dim, cu_seqlen = x.shape
594
+ _, width = weight.shape
595
+ state_len = width - 1
596
+ np2_statelen = triton.next_power_of_2(state_len)
597
+
598
+ padded_batch = query_start_loc.size(0) - 1
599
+ stride_x_dim = x.stride(0)
600
+ stride_x_token = x.stride(1)
601
+ stride_w_dim = weight.stride(0)
602
+ stride_w_width = weight.stride(1)
603
+ stride_istate_seq = 0
604
+ stride_istate_dim = 0
605
+ stride_istate_token = 0
606
+ num_cache_lines = 0
607
+ BLOCK_M = 8
608
+ if conv_states is not None:
609
+ # extensions to support vLLM:
610
+ # 1. conv_states is used to replaced initial_states
611
+ # 2. conv_states serve as a cache with num cache lines can be larger than batch size
612
+ # 3. mapping from sequence x[idx] to a cache line at index as specified via cache_indices[idx]
613
+ # 4. computation can be skipped if cache_indices[idx] == pad_slot_id
614
+ num_cache_lines = conv_states.size(0)
615
+ assert (
616
+ num_cache_lines == conv_states.shape[0]
617
+ and dim == conv_states.shape[1]
618
+ and width - 1 <= conv_states.shape[2]
619
+ )
620
+ stride_istate_seq = conv_states.stride(0)
621
+ stride_istate_dim = conv_states.stride(1)
622
+ stride_istate_token = conv_states.stride(2)
623
+ if out.dim() == 2:
624
+ stride_o_dim = out.stride(0)
625
+ stride_o_token = out.stride(1)
626
+ else:
627
+ stride_o_dim = out.stride(1)
628
+ stride_o_token = out.stride(2)
629
+ stride_cache_indices = cache_indices.stride(0) if cache_indices is not None else 0
630
+
631
+ if validate_data:
632
+ assert x.dim() == 2
633
+ assert query_start_loc is not None
634
+ assert query_start_loc.dim() == 1
635
+ assert x.stride(0) == 1 or x.stride(1) == 1
636
+ if bias is not None:
637
+ assert bias.dim() == 1
638
+ assert dim == bias.size(0)
639
+ if cache_indices is not None:
640
+ assert cache_indices.dim() == 1
641
+ assert padded_batch == cache_indices.size(0)
642
+ if has_initial_state is not None:
643
+ assert has_initial_state.size() == (padded_batch,)
644
+ assert conv_states is not None, (
645
+ "ERROR: `has_initial_state` is used, which needs also `conv_states`"
646
+ )
647
+ assert weight.stride(1) == 1
648
+ assert (dim, width) == weight.shape
649
+ assert is_channel_last, "Need to run in channel-last layout"
650
+ if block_size_to_align is not None and block_size_to_align > 0:
651
+ assert (block_size_to_align % BLOCK_M) == 0, (
652
+ "The mamba block size needs to be divisible by the BLOCK_M"
653
+ )
654
+ else:
655
+ block_size_to_align = BLOCK_M
656
+
657
+ if metadata is None:
658
+
659
+ def num_program(META, seqlens):
660
+ tot = 0
661
+
662
+ mlist = []
663
+ offsetlist = [] # type: ignore
664
+
665
+ nums = -(-seqlens // META["BLOCK_M"])
666
+
667
+ tot = nums.sum().item()
668
+ mlist = np.repeat(np.arange(len(nums)), nums)
669
+ for idx, num in enumerate(nums):
670
+ offsetlist.extend(
671
+ range(num)
672
+ ) # chunk-idx if a sequence is split into multiple chunks
673
+
674
+ if META["batch_ptr"].nelement() < len(mlist):
675
+ newlen = len(mlist) + 1
676
+ META["batch_ptr"].resize_(newlen).fill_(PAD_SLOT_ID)
677
+ META["token_chunk_offset_ptr"].resize_(newlen).fill_(PAD_SLOT_ID)
678
+
679
+ if META["batch_ptr"].nelement() >= len(mlist):
680
+ META["batch_ptr"][0 : len(mlist)].copy_(
681
+ torch.from_numpy(np.array(mlist))
682
+ )
683
+ META["token_chunk_offset_ptr"][0 : len(mlist)].copy_(
684
+ torch.from_numpy(np.array(offsetlist))
685
+ )
686
+
687
+ META["batch_ptr"] = META["batch_ptr"].to(META["x_ptr"].device)
688
+ META["token_chunk_offset_ptr"] = META["token_chunk_offset_ptr"].to(
689
+ META["x_ptr"].device
690
+ )
691
+ return tot
692
+ else:
693
+
694
+ def num_program(META, nums_dict):
695
+ tot = nums_dict[META["BLOCK_M"]]["tot"]
696
+
697
+ mlist = nums_dict[META["BLOCK_M"]]["mlist"]
698
+ mlist_len = nums_dict[META["BLOCK_M"]]["mlist_len"]
699
+
700
+ offsetlist = nums_dict[META["BLOCK_M"]]["offsetlist"]
701
+
702
+ if nums_dict[META["BLOCK_M"]]["batch_ptr"] is not None:
703
+ META["batch_ptr"] = nums_dict[META["BLOCK_M"]]["batch_ptr"]
704
+ META["token_chunk_offset_ptr"] = nums_dict[META["BLOCK_M"]][
705
+ "token_chunk_offset_ptr"
706
+ ]
707
+ else:
708
+ if META["batch_ptr"].nelement() < mlist_len:
709
+ newlen = mlist_len + 1
710
+ META["batch_ptr"].resize_(newlen).fill_(PAD_SLOT_ID)
711
+ META["token_chunk_offset_ptr"].resize_(newlen).fill_(PAD_SLOT_ID)
712
+
713
+ if META["batch_ptr"].nelement() >= mlist_len:
714
+ META["batch_ptr"][0:mlist_len].copy_(mlist)
715
+ META["token_chunk_offset_ptr"][0:mlist_len].copy_(offsetlist)
716
+ return tot
717
+
718
+ def grid(META):
719
+ return (
720
+ num_program(META, args),
721
+ triton.cdiv(dim, META["BLOCK_N"]),
722
+ )
723
+
724
+ if batch_ptr.device != x.device:
725
+ batch_ptr = batch_ptr.to(x.device)
726
+ token_chunk_offset_ptr = token_chunk_offset_ptr.to(x.device)
727
+
728
+ _causal_conv1d_fwd_kernel[grid](
729
+ # Pointers to matrices
730
+ x,
731
+ weight,
732
+ bias,
733
+ conv_states,
734
+ cache_indices,
735
+ has_initial_state,
736
+ query_start_loc,
737
+ batch_ptr,
738
+ token_chunk_offset_ptr,
739
+ block_idx_first_scheduled_token,
740
+ block_idx_last_scheduled_token,
741
+ initial_state_idx,
742
+ num_computed_tokens,
743
+ num_accepted_tokens,
744
+ out,
745
+ # Matrix dimensions
746
+ dim,
747
+ num_cache_lines,
748
+ # stride
749
+ stride_x_dim,
750
+ stride_x_token,
751
+ stride_w_dim,
752
+ stride_w_width,
753
+ stride_istate_seq,
754
+ stride_istate_dim,
755
+ stride_istate_token,
756
+ stride_cache_indices,
757
+ stride_o_dim,
758
+ stride_o_token,
759
+ block_size_to_align // BLOCK_M,
760
+ # others
761
+ pad_slot_id,
762
+ null_block_id,
763
+ # META
764
+ HAS_BIAS=bias is not None,
765
+ KERNEL_WIDTH=width,
766
+ SILU_ACTIVATION=activation in ["silu", "swish"],
767
+ IS_APC_ENABLED=block_idx_last_scheduled_token is not None,
768
+ IS_SPEC_DECODING=num_accepted_tokens is not None,
769
+ HAS_NULL_BLOCK=null_block_id is not None,
770
+ NP2_STATELEN=np2_statelen,
771
+ # launch_cooperative_grid=True
772
+ BLOCK_M=BLOCK_M,
773
+ BLOCK_N=256,
774
+ num_stages=2,
775
+ launch_pdl=current_platform.is_arch_support_pdl(),
776
+ )
777
+ return out.to(original_x_dtype)
778
+
779
+
780
+ @triton.jit(do_not_specialize_on_alignment=["num_cache_lines"])
781
+ def _causal_conv1d_update_kernel(
782
+ # Pointers to matrices
783
+ x_ptr, # (batch, dim, seqlen)
784
+ w_ptr, # (dim, width)
785
+ bias_ptr,
786
+ conv_state_ptr,
787
+ conv_state_indices_ptr,
788
+ num_accepted_tokens_ptr,
789
+ query_start_loc_ptr, # (batch + 1)
790
+ block_idx_last_scheduled_token, # (batch,)
791
+ initial_state_idx, # (batch,)
792
+ o_ptr, # (batch, dim, seqlen)
793
+ # Matrix dimensions
794
+ batch: int,
795
+ dim: tl.constexpr,
796
+ seqlen: tl.constexpr,
797
+ state_len: tl.constexpr,
798
+ num_cache_lines, # added to support vLLM larger cache lines
799
+ # Strides
800
+ stride_x_seq: tl.constexpr,
801
+ stride_x_dim: tl.constexpr,
802
+ stride_x_token: tl.int64,
803
+ stride_w_dim: tl.constexpr,
804
+ stride_w_width: tl.constexpr,
805
+ stride_conv_state_seq: tl.constexpr,
806
+ stride_conv_state_dim: tl.constexpr,
807
+ stride_conv_state_tok: tl.constexpr,
808
+ stride_state_indices: tl.constexpr,
809
+ stride_o_seq: tl.constexpr,
810
+ stride_o_dim: tl.constexpr,
811
+ stride_o_token: tl.int64,
812
+ # others
813
+ null_block_id: tl.constexpr,
814
+ # Meta-parameters
815
+ HAS_BIAS: tl.constexpr,
816
+ KERNEL_WIDTH: tl.constexpr,
817
+ SILU_ACTIVATION: tl.constexpr,
818
+ IS_VARLEN: tl.constexpr,
819
+ IS_APC_ENABLED: tl.constexpr,
820
+ IS_SPEC_DECODING: tl.constexpr,
821
+ NP2_STATELEN: tl.constexpr,
822
+ HAS_NULL_BLOCK: tl.constexpr,
823
+ BLOCK_N: tl.constexpr,
824
+ launch_pdl: tl.constexpr,
825
+ ):
826
+ if launch_pdl:
827
+ tl.extra.cuda.gdc_wait()
828
+
829
+ # ruff: noqa: E501
830
+ idx_seq = tl.program_id(0)
831
+ if idx_seq >= batch:
832
+ if launch_pdl:
833
+ tl.extra.cuda.gdc_launch_dependents()
834
+ return
835
+
836
+ # [BLOCK_N,] elements along the feature-dimension (channel)
837
+ idx_feats = tl.program_id(1) * BLOCK_N + tl.arange(0, BLOCK_N)
838
+
839
+ if IS_APC_ENABLED:
840
+ # Get the state from the initial_state_idx
841
+ conv_state_init = tl.load(initial_state_idx + idx_seq)
842
+ current_last_index = tl.load(block_idx_last_scheduled_token + idx_seq)
843
+ else:
844
+ conv_state_init = 0
845
+ current_last_index = 0
846
+
847
+ # cache_idx
848
+ conv_states_input_coord = tl.load(
849
+ conv_state_indices_ptr + idx_seq * stride_state_indices + conv_state_init
850
+ ).to(tl.int64)
851
+
852
+ if HAS_NULL_BLOCK: # noqa
853
+ if conv_states_input_coord == null_block_id:
854
+ # not processing as this is not the actual sequence
855
+ if launch_pdl:
856
+ tl.extra.cuda.gdc_launch_dependents()
857
+ return
858
+
859
+ if IS_VARLEN:
860
+ query_start_index = tl.load(query_start_loc_ptr + idx_seq).to(tl.int64)
861
+ query_end_index = tl.load(query_start_loc_ptr + (idx_seq + 1)).to(tl.int64)
862
+ # revise state_len and seqlen
863
+ state_len = state_len - (seqlen - (query_end_index - query_start_index))
864
+ seqlen = query_end_index - query_start_index
865
+ x_offset = query_start_index * stride_x_token
866
+ o_offset = query_start_index * stride_o_token
867
+ else:
868
+ query_start_index = idx_seq * seqlen
869
+ query_end_index = query_start_index + seqlen
870
+ x_offset = idx_seq * stride_x_seq
871
+ o_offset = idx_seq * stride_o_seq
872
+
873
+ if query_start_index == query_end_index:
874
+ if launch_pdl:
875
+ tl.extra.cuda.gdc_launch_dependents()
876
+ return
877
+
878
+ if IS_SPEC_DECODING:
879
+ # The rolling of conv state:
880
+ #
881
+ # Before forward, the conv_state is:
882
+ # [history1, history2, ..., historyM].
883
+ #
884
+ # After forward, the conv_state becomes:
885
+ # [history2, ..., historyM, draft1, draft2, ..., draftN].
886
+ #
887
+ # After acceptance, it becomes:
888
+ #
889
+ # - accept 1 tokens: [history2, ..., historyM, draft1]
890
+ # - accept 2 tokens: [history3, ..., historyM, draft1, draft2]
891
+ # - and so on.
892
+ conv_state_token_offset = (
893
+ tl.load(num_accepted_tokens_ptr + idx_seq).to(tl.int64) - 1
894
+ )
895
+ else:
896
+ conv_state_token_offset = 0
897
+
898
+ # STEP 1: READ init_state data
899
+ conv_states_base = (
900
+ conv_state_ptr
901
+ + (conv_states_input_coord * stride_conv_state_seq)
902
+ + (idx_feats * stride_conv_state_dim)
903
+ )
904
+ mask_w = idx_feats < dim
905
+
906
+ prior_tokens = conv_states_base + conv_state_token_offset * stride_conv_state_tok
907
+ if KERNEL_WIDTH >= 2:
908
+ conv_states_ptrs = prior_tokens # [BLOCK_N]
909
+ col0 = tl.load(conv_states_ptrs, mask_w, 0.0)
910
+ if KERNEL_WIDTH >= 3:
911
+ conv_states_ptrs = prior_tokens + 1 * stride_conv_state_tok # [BLOCK_N]
912
+ col1 = tl.load(conv_states_ptrs, mask_w, 0.0)
913
+ if KERNEL_WIDTH >= 4:
914
+ conv_states_ptrs = prior_tokens + 2 * stride_conv_state_tok # [BLOCK_N]
915
+ col2 = tl.load(conv_states_ptrs, mask_w, 0.0)
916
+ if KERNEL_WIDTH >= 5:
917
+ conv_states_ptrs = prior_tokens + 3 * stride_conv_state_tok # [BLOCK_N]
918
+ col3 = tl.load(conv_states_ptrs, mask_w, 0.0)
919
+ if KERNEL_WIDTH >= 6:
920
+ conv_states_ptrs = prior_tokens + 4 * stride_conv_state_tok # [BLOCK_N]
921
+ col4 = tl.load(conv_states_ptrs, mask_w, 0.0)
922
+
923
+ # STEP 2: assume state_len > seqlen
924
+ idx_tokens = tl.arange(0, NP2_STATELEN) # [BLOCK_M]
925
+
926
+ # With speculative decoding, the conv_state updates works in a sliding
927
+ # window manner, at each forward pass, the tokens are shift by 1, so we
928
+ # load since idx_tokens + 1.
929
+ conv_state_ptrs_source = (
930
+ conv_state_ptr
931
+ + (conv_states_input_coord * stride_conv_state_seq)
932
+ + conv_state_token_offset * stride_conv_state_tok
933
+ + (idx_feats * stride_conv_state_dim)[None, :]
934
+ + ((idx_tokens + (1 if IS_SPEC_DECODING else seqlen)) * stride_conv_state_tok)[
935
+ :, None
936
+ ]
937
+ ) # [BLOCK_M, BLOCK_N]
938
+ mask = (
939
+ (conv_states_input_coord < num_cache_lines)
940
+ & ((idx_tokens + seqlen) < state_len)[:, None]
941
+ & (idx_feats < dim)[None, :]
942
+ )
943
+ conv_state = tl.load(conv_state_ptrs_source, mask, other=0.0)
944
+
945
+ VAL = state_len - seqlen
946
+ x_base = x_ptr + x_offset + (idx_feats * stride_x_dim) # [BLOCK_N]
947
+
948
+ x_ptrs = (
949
+ x_base[None, :] + ((idx_tokens - VAL) * stride_x_token)[:, None]
950
+ ) # [BLOCK_M, BLOCK_N]
951
+
952
+ mask_x = (
953
+ (idx_tokens - VAL >= 0)[:, None]
954
+ & (idx_tokens - VAL < seqlen)[:, None]
955
+ & (idx_feats < dim)[None, :]
956
+ ) # token-index # token-index # feature-index
957
+ loaded_x = tl.load(x_ptrs, mask_x, 0.0)
958
+ tl.debug_barrier()
959
+
960
+ new_conv_state = tl.where(mask, conv_state, loaded_x)
961
+
962
+ # Get the state from the initial_state_idx
963
+ # cache_idx
964
+ conv_states_offset = tl.load(
965
+ conv_state_indices_ptr + idx_seq * stride_state_indices + current_last_index
966
+ ).to(tl.int64)
967
+ conv_state_ptrs_target = (
968
+ conv_state_ptr
969
+ + (conv_states_offset * stride_conv_state_seq) # Offset from seq
970
+ + (idx_feats * stride_conv_state_dim)
971
+ )[None, :] + ( # [BLOCK_N,]
972
+ idx_tokens * stride_conv_state_tok
973
+ )[:, None]
974
+ mask = (idx_tokens < state_len)[:, None] & (idx_feats < dim)[None, :]
975
+ tl.store(conv_state_ptrs_target, new_conv_state, mask)
976
+
977
+ # STEP 3: init accumulator
978
+ if HAS_BIAS:
979
+ bias = bias_ptr + idx_feats
980
+ mask_bias = idx_feats < dim
981
+ acc_preload = tl.load(bias, mask=mask_bias, other=0.0).to(
982
+ tl.float32
983
+ ) # [BLOCK_N]
984
+ else:
985
+ acc_preload = tl.zeros((BLOCK_N,), dtype=tl.float32)
986
+
987
+ # STEP 4:
988
+ # PRE-LOAD WEIGHTS
989
+ # first kernel column, configured for weights to handle BLOCK_N features in range
990
+ w_base = w_ptr + (idx_feats * stride_w_dim) # [BLOCK_N,]
991
+ mask_w = idx_feats < dim
992
+ if KERNEL_WIDTH >= 2:
993
+ w_ptrs = w_base + (0 * stride_w_width) # [BLOCK_N] tensor
994
+ w_col0 = tl.load(w_ptrs, mask_w, other=0.0)
995
+ w_ptrs = w_base + (1 * stride_w_width) # [BLOCK_N] tensor
996
+ w_col1 = tl.load(w_ptrs, mask_w, other=0.0)
997
+ if KERNEL_WIDTH >= 3:
998
+ w_ptrs = w_base + (2 * stride_w_width) # [BLOCK_N] tensor
999
+ w_col2 = tl.load(w_ptrs, mask_w, other=0.0)
1000
+ if KERNEL_WIDTH >= 4:
1001
+ w_ptrs = w_base + (3 * stride_w_width) # [BLOCK_N] tensor
1002
+ w_col3 = tl.load(w_ptrs, mask_w, other=0.0)
1003
+ if KERNEL_WIDTH >= 5:
1004
+ w_ptrs = w_base + (4 * stride_w_width) # [BLOCK_N] tensor
1005
+ w_col4 = tl.load(w_ptrs, mask_w, other=0.0)
1006
+ if KERNEL_WIDTH >= 6:
1007
+ w_ptrs = w_base + (5 * stride_w_width) # [BLOCK_N] tensor
1008
+ w_col5 = tl.load(w_ptrs, mask_w, other=0.0)
1009
+
1010
+ x_base_1d = x_base # starting of chunk [BLOCK_N]
1011
+ mask_x_1d = idx_feats < dim
1012
+
1013
+ # STEP 5: compute each token
1014
+ if launch_pdl:
1015
+ tl.extra.cuda.gdc_launch_dependents()
1016
+
1017
+ for idx_token in tl.range(seqlen):
1018
+ acc = acc_preload
1019
+
1020
+ matrix_w = w_col0
1021
+ matrix_x = col0
1022
+ for j in tl.static_range(KERNEL_WIDTH):
1023
+ if KERNEL_WIDTH == 2:
1024
+ if j == 1: # KERNEL_WIDTH-1:
1025
+ matrix_w = w_col1
1026
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token # [BLOCK_N]
1027
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
1028
+ elif KERNEL_WIDTH == 3:
1029
+ if j == 1:
1030
+ matrix_w = w_col1
1031
+ matrix_x = col1
1032
+ elif j == 2:
1033
+ matrix_w = w_col2
1034
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token # [BLOCK_N]
1035
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
1036
+ elif KERNEL_WIDTH == 4:
1037
+ if j == 1:
1038
+ matrix_w = w_col1
1039
+ matrix_x = col1
1040
+ elif j == 2:
1041
+ matrix_w = w_col2
1042
+ matrix_x = col2
1043
+ elif j == 3:
1044
+ matrix_w = w_col3
1045
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token # [BLOCK_N]
1046
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
1047
+ elif KERNEL_WIDTH == 5:
1048
+ if j == 1:
1049
+ matrix_w = w_col1
1050
+ matrix_x = col1
1051
+ elif j == 2:
1052
+ matrix_w = w_col2
1053
+ matrix_x = col2
1054
+ elif j == 3:
1055
+ matrix_w = w_col3
1056
+ matrix_x = col3
1057
+ elif j == 4:
1058
+ matrix_w = w_col4
1059
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token # [BLOCK_N]
1060
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
1061
+ elif KERNEL_WIDTH == 6:
1062
+ if j == 1:
1063
+ matrix_w = w_col1
1064
+ matrix_x = col1
1065
+ elif j == 2:
1066
+ matrix_w = w_col2
1067
+ matrix_x = col2
1068
+ elif j == 3:
1069
+ matrix_w = w_col3
1070
+ matrix_x = col3
1071
+ elif j == 4:
1072
+ matrix_w = w_col4
1073
+ matrix_x = col4
1074
+ elif j == 5:
1075
+ matrix_w = w_col5
1076
+ x_ptrs_1d = x_base_1d + idx_token * stride_x_token # [BLOCK_N]
1077
+ matrix_x = tl.load(x_ptrs_1d, mask=mask_x_1d)
1078
+
1079
+ acc += matrix_x.to(tl.float32) * matrix_w.to(tl.float32) # [BLOCK_N]
1080
+
1081
+ if KERNEL_WIDTH == 2:
1082
+ col0 = matrix_x
1083
+ elif KERNEL_WIDTH == 3:
1084
+ col0 = col1
1085
+ col1 = matrix_x
1086
+ elif KERNEL_WIDTH == 4:
1087
+ col0 = col1
1088
+ col1 = col2
1089
+ col2 = matrix_x
1090
+ elif KERNEL_WIDTH == 5:
1091
+ col0 = col1
1092
+ col1 = col2
1093
+ col2 = col3
1094
+ col3 = matrix_x
1095
+ elif KERNEL_WIDTH == 6:
1096
+ col0 = col1
1097
+ col1 = col2
1098
+ col2 = col3
1099
+ col3 = col4
1100
+ col4 = matrix_x
1101
+
1102
+ if SILU_ACTIVATION:
1103
+ acc = acc / (1 + tl.exp(-acc))
1104
+ mask_1d = (idx_token < seqlen) & (
1105
+ idx_feats < dim
1106
+ ) # token-index # feature-index
1107
+ o_ptrs = (
1108
+ o_ptr + o_offset + idx_token * stride_o_token + (idx_feats * stride_o_dim)
1109
+ )
1110
+
1111
+ tl.store(o_ptrs, acc, mask=mask_1d)
1112
+
1113
+
1114
+ def causal_conv1d_update(
1115
+ x: torch.Tensor,
1116
+ conv_state: torch.Tensor,
1117
+ weight: torch.Tensor,
1118
+ bias: torch.Tensor | None = None,
1119
+ activation: bool | str | None = None,
1120
+ conv_state_indices: torch.Tensor | None = None,
1121
+ num_accepted_tokens: torch.Tensor | None = None,
1122
+ query_start_loc: torch.Tensor | None = None,
1123
+ max_query_len: int = -1,
1124
+ null_block_id: int = NULL_BLOCK_ID,
1125
+ block_idx_last_scheduled_token: torch.Tensor | None = None,
1126
+ initial_state_idx: torch.Tensor | None = None,
1127
+ validate_data=False,
1128
+ out: torch.Tensor | None = None,
1129
+ ):
1130
+ """
1131
+ x: Input tensor which can take the following shapes:
1132
+
1133
+ - `[batch, dim]` - single token prediction
1134
+ - `[batch, dim, seqlen]` - single or multiple tokens prediction
1135
+ - `[num_tokens, dim]` - continuous batching, where num_tokens is
1136
+ the total tokens of all sequences in that batch
1137
+
1138
+ conv_state: (..., dim, state_len), where state_len >= width - 1
1139
+ weight: (dim, width)
1140
+ bias: (dim,)
1141
+ conv_state_indices: (batch,), dtype int32
1142
+ If not None, the conv_state is a larger tensor along the batch dim,
1143
+ and we are selecting the batch coords specified by conv_state_indices.
1144
+ Useful for a continuous batching scenario.
1145
+ block_idx_last_scheduled_token: (batch,), dtype int32
1146
+ The pointer into conv_state_indices, where the last cache block to be filled is located.
1147
+ initial_state_idx: (batch,), dtype int32
1148
+ The pointer into conv_state_indices, where the cache block containing the initial state is located.
1149
+ num_accepted_tokens: (batch,), dtype int32
1150
+ If not None, it indicates the number of accepted tokens for each
1151
+ sequence in the batch.
1152
+ This is used in speculative decoding, where the conv_state is updated
1153
+ in a sliding window manner.
1154
+ query_start_loc: (batch + 1,) int32
1155
+ If not None, the inputs is given in a varlen fashion and this indicates
1156
+ the starting index of each sequence in the batch.
1157
+ max_query_len: int
1158
+ If query_start_loc is not None, this indicates the maximum query
1159
+ length in the batch.
1160
+ null_block_id: int
1161
+ Block ID used to identify padded entries in
1162
+ conv_state_indices. Block 0 is the null block.
1163
+ for example: conv_state_indices = [null_block_id, 1, 20, null_block_id]
1164
+ in this case, the kernel will not process entries at
1165
+ indices 0 and 3
1166
+ out: optional output tensor with the same shape as `x`. When omitted,
1167
+ the input is overwritten.
1168
+ """
1169
+ if validate_data:
1170
+ assert null_block_id is not None
1171
+ assert x.stride(1) == 1
1172
+ if isinstance(activation, bool):
1173
+ activation = "silu" if activation is True else None
1174
+ elif activation is not None:
1175
+ assert activation in ["silu", "swish"]
1176
+
1177
+ original_x_dtype = x.dtype
1178
+ x = x.to(conv_state.dtype)
1179
+ if out is None:
1180
+ out = x
1181
+ else:
1182
+ if out.shape != x.shape:
1183
+ raise ValueError(
1184
+ f"`out` shape {tuple(out.shape)} must match `x` shape {tuple(x.shape)}."
1185
+ )
1186
+ if out.dtype != original_x_dtype or out.device != x.device:
1187
+ raise ValueError(
1188
+ "`out` must have the same dtype and device as the input `x`."
1189
+ )
1190
+ unsqueeze = query_start_loc is None and x.dim() == 2
1191
+ if unsqueeze:
1192
+ # make it (batch, dim, seqlen) with seqlen == 1
1193
+ x = x.unsqueeze(-1)
1194
+ out = out.unsqueeze(-1)
1195
+ if query_start_loc is None:
1196
+ batch, dim, seqlen = x.shape
1197
+ else:
1198
+ assert conv_state_indices is not None
1199
+ batch = conv_state_indices.size(0)
1200
+ dim = x.size(1)
1201
+ seqlen = max_query_len
1202
+ _, width = weight.shape
1203
+ # conv_state: (..., dim, state_len), where state_len >= width - 1
1204
+ num_cache_lines, _, state_len = conv_state.size()
1205
+
1206
+ if validate_data:
1207
+ assert dim == weight.size(0)
1208
+ assert state_len >= width - 1
1209
+ # when above happens, we don't shift-left to keep any records in conv_state
1210
+ assert dim == conv_state.size(1)
1211
+ if conv_state_indices is None:
1212
+ assert conv_state.size(0) >= batch
1213
+ else:
1214
+ assert batch == conv_state_indices.shape[0], (
1215
+ f"ERROR: conv_state_indices should have shape ({batch},*) but got {conv_state_indices.shape}"
1216
+ )
1217
+
1218
+ assert num_cache_lines >= batch
1219
+ assert weight.stride(1) == 1 # Need this
1220
+
1221
+ stride_w_dim, stride_w_width = weight.stride()
1222
+
1223
+ if query_start_loc is None:
1224
+ # X (batch, dim, seqlen)
1225
+ stride_x_seq, stride_x_dim, stride_x_token = x.stride()
1226
+ stride_o_seq, stride_o_dim, stride_o_token = out.stride()
1227
+ else:
1228
+ # X (dim, cu_seqlen)
1229
+ stride_x_token, stride_x_dim = x.stride()
1230
+ stride_x_seq = 0
1231
+ stride_o_token, stride_o_dim = out.stride()
1232
+ stride_o_seq = 0
1233
+
1234
+ stride_istate_seq, stride_istate_dim, stride_istate_token = conv_state.stride()
1235
+ stride_state_indices = (
1236
+ conv_state_indices.stride(0) if conv_state_indices is not None else 0
1237
+ )
1238
+ if num_accepted_tokens is not None:
1239
+ state_len = width - 1 + (seqlen - 1) # effective state_len needed
1240
+ else:
1241
+ state_len = width - 1
1242
+ np2_statelen = triton.next_power_of_2(state_len)
1243
+
1244
+ def grid(META):
1245
+ return (
1246
+ batch,
1247
+ triton.cdiv(dim, META["BLOCK_N"]),
1248
+ )
1249
+
1250
+ _causal_conv1d_update_kernel[grid](
1251
+ # Pointers to matrices
1252
+ x,
1253
+ weight,
1254
+ bias,
1255
+ conv_state,
1256
+ conv_state_indices,
1257
+ num_accepted_tokens,
1258
+ query_start_loc,
1259
+ block_idx_last_scheduled_token,
1260
+ initial_state_idx,
1261
+ out,
1262
+ # Matrix dimensions
1263
+ batch,
1264
+ dim,
1265
+ seqlen,
1266
+ state_len,
1267
+ num_cache_lines,
1268
+ # stride
1269
+ stride_x_seq,
1270
+ stride_x_dim,
1271
+ stride_x_token,
1272
+ stride_w_dim,
1273
+ stride_w_width,
1274
+ stride_istate_seq,
1275
+ stride_istate_dim,
1276
+ stride_istate_token,
1277
+ stride_state_indices,
1278
+ stride_o_seq,
1279
+ stride_o_dim,
1280
+ stride_o_token,
1281
+ # others
1282
+ null_block_id,
1283
+ # META
1284
+ HAS_BIAS=bias is not None,
1285
+ KERNEL_WIDTH=width,
1286
+ SILU_ACTIVATION=activation in ["silu", "swish"],
1287
+ IS_VARLEN=query_start_loc is not None,
1288
+ IS_APC_ENABLED=block_idx_last_scheduled_token is not None,
1289
+ IS_SPEC_DECODING=num_accepted_tokens is not None,
1290
+ NP2_STATELEN=np2_statelen,
1291
+ HAS_NULL_BLOCK=null_block_id is not None,
1292
+ BLOCK_N=256,
1293
+ launch_pdl=current_platform.is_arch_support_pdl(),
1294
+ )
1295
+ if unsqueeze:
1296
+ out = out.squeeze(-1)
1297
+ return out.to(original_x_dtype)
1298
+
1299
+
1300
+ if current_platform.is_cpu():
1301
+ from vllm.model_executor.layers.mamba.ops.cpu.causal_conv1d import (
1302
+ causal_conv1d_fn_cpu,
1303
+ causal_conv1d_update_cpu,
1304
+ )
1305
+
1306
+ causal_conv1d_fn = causal_conv1d_fn_cpu # type: ignore
1307
+ causal_conv1d_update = causal_conv1d_update_cpu # type: ignore
bundle/plugin-site/ornith_g256/_vllm_correctness/gdn_attn.py ADDED
@@ -0,0 +1,616 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+ """Backend for GatedDeltaNet attention."""
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Literal
7
+
8
+ import torch
9
+
10
+ from vllm.config import VllmConfig
11
+ from vllm.utils.torch_utils import async_tensor_h2d
12
+ from vllm.v1.attention.backend import (
13
+ AttentionBackend,
14
+ AttentionCGSupport,
15
+ AttentionMetadataBuilder,
16
+ CommonAttentionMetadata,
17
+ )
18
+ from vllm.v1.attention.backends.utils import (
19
+ NULL_BLOCK_ID,
20
+ compute_causal_conv1d_metadata,
21
+ mamba_get_block_table_tensor,
22
+ split_decodes_and_prefills,
23
+ )
24
+ from vllm.v1.kv_cache_interface import MambaSpec
25
+
26
+
27
+ class GDNAttentionBackend(AttentionBackend):
28
+ @staticmethod
29
+ def get_name() -> str:
30
+ return "GDN_ATTN"
31
+
32
+ @staticmethod
33
+ def get_builder_cls() -> type["GDNAttentionMetadataBuilder"]:
34
+ return GDNAttentionMetadataBuilder
35
+
36
+ @classmethod
37
+ def is_ssm(cls) -> bool:
38
+ return True
39
+
40
+
41
+ @dataclass
42
+ class GDNAttentionMetadata:
43
+ num_prefills: int
44
+ num_prefill_tokens: int
45
+ num_decodes: int
46
+ num_decode_tokens: int
47
+ num_spec_decodes: int
48
+ num_spec_decode_tokens: int
49
+ num_actual_tokens: int
50
+
51
+ has_initial_state: torch.Tensor | None = None
52
+
53
+ spec_query_start_loc: torch.Tensor | None = None # shape: [num_spec_decodes + 1,]
54
+ non_spec_query_start_loc: torch.Tensor | None = (
55
+ None # shape: [batch - num_spec_decodes + 1,]
56
+ )
57
+
58
+ spec_state_indices_tensor: torch.Tensor | None = None # shape: [batch, num_spec]
59
+ non_spec_state_indices_tensor: torch.Tensor | None = (
60
+ None # shape: [batch - num_spec_decodes,]
61
+ )
62
+ spec_sequence_masks: torch.Tensor | None = None # shape: [batch,]
63
+ spec_token_indx: torch.Tensor | None = None
64
+ non_spec_token_indx: torch.Tensor | None = None
65
+
66
+ num_accepted_tokens: torch.Tensor | None = None # shape: [batch,]
67
+
68
+ # 1D source block indices for state recovery after spec decode.
69
+ # When set, conv/ssm state must be copied from these blocks to the
70
+ # blocks in non_spec_state_indices_tensor before the decode kernel.
71
+ spec_decode_src_indices: torch.Tensor | None = None
72
+ non_spec_num_accepted: torch.Tensor | None = None
73
+
74
+ # Pre-computed FLA chunk metadata (avoids GPU->CPU sync in prepare_chunk_indices)
75
+ chunk_indices: torch.Tensor | None = None
76
+ chunk_offsets: torch.Tensor | None = None
77
+ # Chunk-kernel inputs for prefill
78
+ prefill_query_start_loc: torch.Tensor | None = None
79
+ prefill_state_indices: torch.Tensor | None = None
80
+ prefill_has_initial_state: torch.Tensor | None = None
81
+
82
+ # The following attributes are for triton implementation of causal_conv1d
83
+ nums_dict: dict | None = None
84
+ batch_ptr: torch.Tensor | None = None
85
+ token_chunk_offset_ptr: torch.Tensor | None = None
86
+
87
+
88
+ class GDNAttentionMetadataBuilder(AttentionMetadataBuilder[GDNAttentionMetadata]):
89
+ kv_cache_spec: MambaSpec
90
+ _cudagraph_support = AttentionCGSupport.UNIFORM_BATCH
91
+
92
+ reorder_batch_threshold: int = 1
93
+
94
+ def __init__(
95
+ self,
96
+ kv_cache_spec: MambaSpec,
97
+ layer_names: list[str],
98
+ vllm_config: VllmConfig,
99
+ device: torch.device,
100
+ ):
101
+ self.vllm_config = vllm_config
102
+ self.compilation_config = vllm_config.compilation_config
103
+ self.speculative_config = vllm_config.speculative_config
104
+ self.kv_cache_spec = kv_cache_spec
105
+ from vllm.model_executor.layers.mamba.gdn.qwen_gdn_linear_attn import (
106
+ _resolve_gdn_prefill_backend,
107
+ )
108
+
109
+ self.gdn_prefill_backend: Literal["triton", "flashinfer", "cutedsl"]
110
+ _, self.gdn_prefill_backend = _resolve_gdn_prefill_backend(vllm_config)
111
+
112
+ if self.speculative_config:
113
+ assert self.speculative_config.num_speculative_tokens is not None
114
+ self.num_spec: int = self.speculative_config.num_speculative_tokens
115
+ else:
116
+ self.num_spec = 0
117
+ self.use_spec_decode: bool = self.num_spec > 0
118
+ self._init_reorder_batch_threshold(1, self.use_spec_decode)
119
+
120
+ self.use_full_cuda_graph: bool = (
121
+ self.compilation_config.cudagraph_mode.has_full_cudagraphs()
122
+ )
123
+
124
+ self.decode_cudagraph_max_bs: int = (
125
+ self.vllm_config.scheduler_config.max_num_seqs * (self.num_spec + 1)
126
+ )
127
+ if self.compilation_config.max_cudagraph_capture_size is not None:
128
+ self.decode_cudagraph_max_bs = min(
129
+ self.decode_cudagraph_max_bs,
130
+ self.compilation_config.max_cudagraph_capture_size,
131
+ )
132
+
133
+ self.spec_state_indices_tensor: torch.Tensor = torch.empty(
134
+ (self.decode_cudagraph_max_bs, self.num_spec + 1),
135
+ dtype=torch.int32,
136
+ device=device,
137
+ )
138
+ self.non_spec_state_indices_tensor: torch.Tensor = torch.empty(
139
+ (self.decode_cudagraph_max_bs,),
140
+ dtype=torch.int32,
141
+ device=device,
142
+ )
143
+ self.spec_sequence_masks: torch.Tensor = torch.empty(
144
+ (self.decode_cudagraph_max_bs,),
145
+ dtype=torch.bool,
146
+ device=device,
147
+ )
148
+ self.spec_token_indx: torch.Tensor = torch.empty(
149
+ (self.decode_cudagraph_max_bs * (self.num_spec + 1),),
150
+ dtype=torch.int32,
151
+ device=device,
152
+ )
153
+ self.non_spec_token_indx: torch.Tensor = torch.empty(
154
+ (self.decode_cudagraph_max_bs * (self.num_spec + 1),),
155
+ dtype=torch.int32,
156
+ device=device,
157
+ )
158
+ self.spec_query_start_loc: torch.Tensor = torch.empty(
159
+ (self.decode_cudagraph_max_bs + 1,),
160
+ dtype=torch.int32,
161
+ device=device,
162
+ )
163
+ self.non_spec_query_start_loc: torch.Tensor = torch.empty(
164
+ (self.decode_cudagraph_max_bs + 1,),
165
+ dtype=torch.int32,
166
+ device=device,
167
+ )
168
+ self.num_accepted_tokens: torch.Tensor = torch.empty(
169
+ (self.decode_cudagraph_max_bs,),
170
+ dtype=torch.int32,
171
+ device=device,
172
+ )
173
+
174
+ def _build_chunk_metadata(
175
+ self,
176
+ prefill_query_start_loc: torch.Tensor,
177
+ prefill_query_start_loc_cpu: torch.Tensor,
178
+ device: torch.device,
179
+ ) -> tuple[torch.Tensor, torch.Tensor]:
180
+ from vllm.third_party.flash_linear_attention.ops.utils import FLA_CHUNK_SIZE
181
+
182
+ if self.gdn_prefill_backend == "cutedsl":
183
+ from vllm.model_executor.layers.mamba.ops.gdn_chunk_cutedsl import (
184
+ prepare_metadata_cutedsl,
185
+ )
186
+
187
+ assert prefill_query_start_loc is not None
188
+ assert prefill_query_start_loc_cpu is not None
189
+ total_tokens = int(prefill_query_start_loc_cpu[-1].item())
190
+ return prepare_metadata_cutedsl(
191
+ prefill_query_start_loc,
192
+ total_tokens,
193
+ FLA_CHUNK_SIZE,
194
+ )
195
+
196
+ # Only prefill batches use FLA chunk ops.
197
+ # Pre-compute on CPU and async-copy to GPU to avoid
198
+ # GPU→CPU sync (.tolist()) in prepare_chunk_indices.
199
+ from vllm.third_party.flash_linear_attention.ops.index import (
200
+ prepare_chunk_indices,
201
+ prepare_chunk_offsets,
202
+ )
203
+
204
+ assert prefill_query_start_loc_cpu is not None
205
+ return (
206
+ async_tensor_h2d(
207
+ prepare_chunk_indices(prefill_query_start_loc_cpu, FLA_CHUNK_SIZE),
208
+ device=device,
209
+ ),
210
+ async_tensor_h2d(
211
+ prepare_chunk_offsets(prefill_query_start_loc_cpu, FLA_CHUNK_SIZE),
212
+ device=device,
213
+ ),
214
+ )
215
+
216
+ def build( # type: ignore[override]
217
+ self,
218
+ common_prefix_len: int,
219
+ common_attn_metadata: CommonAttentionMetadata,
220
+ num_accepted_tokens: torch.Tensor | None = None,
221
+ num_decode_draft_tokens_cpu: torch.Tensor | None = None,
222
+ fast_build: bool = False,
223
+ ) -> GDNAttentionMetadata:
224
+ m = common_attn_metadata
225
+
226
+ query_start_loc = m.query_start_loc
227
+ query_start_loc_cpu = m.query_start_loc_cpu
228
+ nums_dict, batch_ptr, token_chunk_offset_ptr = None, None, None
229
+ block_table_tensor = mamba_get_block_table_tensor(
230
+ m.block_table_tensor,
231
+ m.seq_lens,
232
+ self.kv_cache_spec,
233
+ self.vllm_config.cache_config.mamba_cache_mode,
234
+ )
235
+
236
+ spec_sequence_masks_cpu: torch.Tensor | None = None
237
+ if not self.use_spec_decode or num_decode_draft_tokens_cpu is None:
238
+ spec_sequence_masks = None
239
+ num_spec_decodes = 0
240
+ else:
241
+ spec_sequence_masks_cpu = num_decode_draft_tokens_cpu >= 0
242
+ num_spec_decodes = spec_sequence_masks_cpu.sum().item()
243
+ if (
244
+ num_spec_decodes == 0
245
+ or num_decode_draft_tokens_cpu[spec_sequence_masks_cpu].sum().item()
246
+ == 0
247
+ ):
248
+ num_spec_decodes = 0
249
+ spec_sequence_masks = None
250
+ spec_sequence_masks_cpu = None
251
+ else:
252
+ spec_sequence_masks = async_tensor_h2d(
253
+ spec_sequence_masks_cpu, device=query_start_loc.device
254
+ )
255
+
256
+ spec_decode_src_indices = None
257
+ non_spec_num_accepted = None
258
+ if spec_sequence_masks is None:
259
+ num_decodes, num_prefills, num_decode_tokens, num_prefill_tokens = (
260
+ split_decodes_and_prefills(m, decode_threshold=1)
261
+ )
262
+ num_spec_decode_tokens = 0
263
+ spec_token_indx = None
264
+ non_spec_token_indx = None
265
+ spec_state_indices_tensor = None
266
+ spec_query_start_loc = None
267
+ non_spec_query_start_loc = query_start_loc
268
+ non_spec_query_start_loc_cpu = query_start_loc_cpu
269
+ non_spec_state_indices_tensor = block_table_tensor[:, 0]
270
+ if (
271
+ self.use_spec_decode
272
+ and num_accepted_tokens is not None
273
+ and num_decodes > 0
274
+ ):
275
+ col_indices = (num_accepted_tokens[:num_decodes] - 1).clamp(min=0)
276
+ spec_decode_src_indices = block_table_tensor[
277
+ torch.arange(num_decodes, device=block_table_tensor.device),
278
+ col_indices,
279
+ ]
280
+ num_accepted_tokens = num_accepted_tokens[:num_decodes]
281
+ if num_prefills > 0:
282
+ num_accepted_tokens = torch.cat(
283
+ [
284
+ num_accepted_tokens,
285
+ torch.ones(
286
+ num_prefills,
287
+ dtype=num_accepted_tokens.dtype,
288
+ device=num_accepted_tokens.device,
289
+ ),
290
+ ]
291
+ )
292
+ else:
293
+ num_accepted_tokens = None
294
+ else:
295
+ query_lens = query_start_loc[1:] - query_start_loc[:-1]
296
+ assert spec_sequence_masks_cpu is not None
297
+ non_spec_sequence_masks_cpu = ~spec_sequence_masks_cpu
298
+ query_lens_cpu = query_start_loc_cpu[1:] - query_start_loc_cpu[:-1]
299
+
300
+ # Use CPU tensors to avoid CPU-GPU sync
301
+ non_spec_query_lens_cpu = query_lens_cpu[non_spec_sequence_masks_cpu]
302
+ num_decodes = (non_spec_query_lens_cpu == 1).sum().item()
303
+ # Exclude zero-length padded sequences from prefill count.
304
+ num_zero_len = (non_spec_query_lens_cpu == 0).sum().item()
305
+ num_prefills = non_spec_query_lens_cpu.size(0) - num_decodes - num_zero_len
306
+ num_decode_tokens = num_decodes
307
+ num_prefill_tokens = (
308
+ non_spec_query_lens_cpu.sum().item() - num_decode_tokens
309
+ )
310
+ num_spec_decode_tokens = (
311
+ query_lens_cpu.sum().item() - num_prefill_tokens - num_decode_tokens
312
+ )
313
+
314
+ # num_decodes and num_spec_decodes are mutually exclusive.
315
+ # Reclassify non-spec decodes as prefills when spec decodes
316
+ # exist — the prefill kernel handles 1-token sequences with
317
+ # initial state correctly, producing identical results.
318
+ if num_decodes > 0 and num_spec_decodes > 0:
319
+ num_prefills += num_decodes
320
+ num_prefill_tokens += num_decode_tokens
321
+ num_decodes = 0
322
+ num_decode_tokens = 0
323
+
324
+ if num_prefills == 0 and num_decodes == 0:
325
+ spec_token_size = min(
326
+ num_spec_decodes * (self.num_spec + 1),
327
+ query_start_loc_cpu[-1].item(),
328
+ )
329
+ spec_token_indx = torch.arange(
330
+ spec_token_size,
331
+ dtype=torch.int32,
332
+ device=query_start_loc.device,
333
+ )
334
+ non_spec_token_indx = torch.empty(
335
+ 0, dtype=torch.int32, device=query_start_loc.device
336
+ )
337
+ # Filter by spec_sequence_masks to exclude padded sequences
338
+ spec_state_indices_tensor = block_table_tensor[
339
+ spec_sequence_masks_cpu, : self.num_spec + 1
340
+ ]
341
+ non_spec_state_indices_tensor = None
342
+ # Padded sequences are always at the back, so the first
343
+ # num_spec_decodes + 1 entries of query_start_loc already
344
+ # contain the correct cumulative token counts.
345
+ spec_query_start_loc = query_start_loc[: num_spec_decodes + 1]
346
+ non_spec_query_start_loc = None
347
+ non_spec_query_start_loc_cpu = None
348
+ else:
349
+ spec_token_masks = torch.repeat_interleave(
350
+ spec_sequence_masks,
351
+ query_lens,
352
+ output_size=query_start_loc_cpu[-1].item(),
353
+ )
354
+ index = torch.argsort(spec_token_masks, stable=True)
355
+ num_non_spec_tokens = num_prefill_tokens + num_decode_tokens
356
+ non_spec_token_indx = index[:num_non_spec_tokens]
357
+ spec_token_indx = index[num_non_spec_tokens:]
358
+
359
+ spec_state_indices_tensor = block_table_tensor[
360
+ spec_sequence_masks_cpu, : self.num_spec + 1
361
+ ]
362
+ non_spec_state_indices_tensor = block_table_tensor[
363
+ non_spec_sequence_masks_cpu, 0
364
+ ]
365
+
366
+ spec_query_start_loc = torch.zeros(
367
+ num_spec_decodes + 1,
368
+ dtype=torch.int32,
369
+ device=query_start_loc.device,
370
+ )
371
+ torch.cumsum(
372
+ query_lens[spec_sequence_masks_cpu],
373
+ dim=0,
374
+ out=spec_query_start_loc[1:],
375
+ )
376
+ non_spec_query_start_loc = torch.zeros(
377
+ query_lens.size(0) - num_spec_decodes + 1,
378
+ dtype=torch.int32,
379
+ device=query_start_loc.device,
380
+ )
381
+ torch.cumsum(
382
+ query_lens[non_spec_sequence_masks_cpu],
383
+ dim=0,
384
+ out=non_spec_query_start_loc[1:],
385
+ )
386
+ non_spec_query_start_loc_cpu = torch.zeros(
387
+ query_lens_cpu.size(0) - num_spec_decodes + 1,
388
+ dtype=torch.int32,
389
+ )
390
+ torch.cumsum(
391
+ query_lens_cpu[non_spec_sequence_masks_cpu],
392
+ dim=0,
393
+ out=non_spec_query_start_loc_cpu[1:],
394
+ )
395
+
396
+ assert num_accepted_tokens is not None
397
+ non_spec_num_accepted = num_accepted_tokens[
398
+ non_spec_sequence_masks_cpu
399
+ ].clamp(min=1)
400
+ non_spec_block_rows = block_table_tensor[non_spec_sequence_masks_cpu]
401
+ source_columns = non_spec_num_accepted - 1
402
+ spec_decode_src_indices = non_spec_block_rows[
403
+ torch.arange(
404
+ non_spec_block_rows.size(0),
405
+ device=block_table_tensor.device,
406
+ ),
407
+ source_columns,
408
+ ]
409
+
410
+ assert num_accepted_tokens is not None
411
+ num_accepted_tokens = num_accepted_tokens[spec_sequence_masks_cpu]
412
+
413
+ chunk_indices: torch.Tensor | None = None
414
+ chunk_offsets: torch.Tensor | None = None
415
+ prefill_query_start_loc: torch.Tensor | None = None
416
+ prefill_state_indices: torch.Tensor | None = None
417
+ prefill_has_initial_state: torch.Tensor | None = None
418
+ if num_prefills > 0:
419
+ # In a mixed non-spec batch, decodes are peeled off to the recurrent
420
+ # kernel (decode-first front slice), so build chunk metadata from the
421
+ # rebased prefill-only cu_seqlens; otherwise use the full non-spec one.
422
+ # _forward_core keys off the same condition, so they agree.
423
+ if spec_sequence_masks is None and num_decodes > 0:
424
+ assert non_spec_query_start_loc is not None
425
+ assert non_spec_query_start_loc_cpu is not None
426
+ assert non_spec_state_indices_tensor is not None
427
+ prefill_query_start_loc = (
428
+ non_spec_query_start_loc[num_decodes:] - num_decode_tokens
429
+ )
430
+ prefill_query_start_loc_cpu = (
431
+ non_spec_query_start_loc_cpu[num_decodes:] - num_decode_tokens
432
+ )
433
+ prefill_state_indices = non_spec_state_indices_tensor[num_decodes:]
434
+ else:
435
+ prefill_query_start_loc = non_spec_query_start_loc
436
+ prefill_query_start_loc_cpu = non_spec_query_start_loc_cpu
437
+ prefill_state_indices = non_spec_state_indices_tensor
438
+
439
+ chunk_indices, chunk_offsets = self._build_chunk_metadata(
440
+ prefill_query_start_loc,
441
+ prefill_query_start_loc_cpu,
442
+ query_start_loc.device,
443
+ )
444
+
445
+ if num_prefills > 0:
446
+ context_lens_tensor = m.compute_num_computed_tokens()
447
+ has_initial_state = context_lens_tensor > 0
448
+ if spec_sequence_masks_cpu is not None:
449
+ has_initial_state = has_initial_state[~spec_sequence_masks_cpu]
450
+ assert non_spec_query_start_loc_cpu is not None
451
+ nums_dict, batch_ptr, token_chunk_offset_ptr = (
452
+ compute_causal_conv1d_metadata(
453
+ non_spec_query_start_loc_cpu,
454
+ device=query_start_loc.device,
455
+ )
456
+ )
457
+ if spec_sequence_masks is None and num_decodes > 0:
458
+ prefill_has_initial_state = has_initial_state[num_decodes:]
459
+ else:
460
+ prefill_has_initial_state = has_initial_state
461
+ else:
462
+ has_initial_state = None
463
+
464
+ # Function code counted on either presency non-spec decode or spec decode,
465
+ # but not both.
466
+ assert not (num_decodes > 0 and num_spec_decodes > 0), (
467
+ f"num_decodes: {num_decodes}, num_spec_decodes: {num_spec_decodes}"
468
+ )
469
+
470
+ # Prepare per-request tensors for cudagraph. m.num_actual_tokens is
471
+ # token-padded for FULL graph replay, but the GDN state/query/accepted
472
+ # metadata below is indexed by request.
473
+ batch_size = m.num_reqs
474
+
475
+ if (
476
+ self.use_full_cuda_graph
477
+ and num_prefills == 0
478
+ and num_decodes == 0
479
+ and num_spec_decodes <= self.decode_cudagraph_max_bs
480
+ and num_spec_decode_tokens <= self.decode_cudagraph_max_bs
481
+ ):
482
+ assert spec_sequence_masks is not None
483
+ self.spec_state_indices_tensor[:num_spec_decodes].copy_(
484
+ spec_state_indices_tensor, non_blocking=True
485
+ )
486
+ spec_state_indices_tensor = self.spec_state_indices_tensor[:batch_size]
487
+ spec_state_indices_tensor[num_spec_decodes:].fill_(NULL_BLOCK_ID)
488
+
489
+ self.spec_sequence_masks[:num_spec_decodes].copy_(
490
+ spec_sequence_masks[:num_spec_decodes], non_blocking=True
491
+ )
492
+ spec_sequence_masks = self.spec_sequence_masks[:batch_size]
493
+ spec_sequence_masks[num_spec_decodes:].fill_(False)
494
+
495
+ assert non_spec_token_indx is not None and spec_token_indx is not None
496
+ self.non_spec_token_indx[: non_spec_token_indx.size(0)].copy_(
497
+ non_spec_token_indx, non_blocking=True
498
+ )
499
+ non_spec_token_indx = self.non_spec_token_indx[
500
+ : non_spec_token_indx.size(0)
501
+ ]
502
+
503
+ self.spec_token_indx[: spec_token_indx.size(0)].copy_(
504
+ spec_token_indx, non_blocking=True
505
+ )
506
+ spec_token_indx = self.spec_token_indx[: spec_token_indx.size(0)]
507
+
508
+ self.spec_query_start_loc[: num_spec_decodes + 1].copy_(
509
+ spec_query_start_loc, non_blocking=True
510
+ )
511
+ spec_num_query_tokens = spec_query_start_loc[-1] # type: ignore[index]
512
+ spec_query_start_loc = self.spec_query_start_loc[: batch_size + 1]
513
+ spec_query_start_loc[num_spec_decodes + 1 :].fill_(spec_num_query_tokens)
514
+
515
+ self.num_accepted_tokens[:num_spec_decodes].copy_(
516
+ num_accepted_tokens, non_blocking=True
517
+ )
518
+ num_accepted_tokens = self.num_accepted_tokens[:batch_size]
519
+ num_accepted_tokens[num_spec_decodes:].fill_(1)
520
+
521
+ if (
522
+ self.use_full_cuda_graph
523
+ and num_prefills == 0
524
+ and num_spec_decodes == 0
525
+ and num_decodes <= self.decode_cudagraph_max_bs
526
+ ):
527
+ self.non_spec_state_indices_tensor[:num_decodes].copy_(
528
+ non_spec_state_indices_tensor, non_blocking=True
529
+ )
530
+ non_spec_state_indices_tensor = self.non_spec_state_indices_tensor[
531
+ :batch_size
532
+ ]
533
+ non_spec_state_indices_tensor[num_decodes:].fill_(NULL_BLOCK_ID)
534
+
535
+ # Recovery metadata is also consumed inside the FULL decode graph.
536
+ # Capture and real requests must address the same source/count
537
+ # buffers; fresh gathered tensors would leave replay using the
538
+ # capture-time source indices and accepted count.
539
+ if spec_decode_src_indices is not None:
540
+ assert num_accepted_tokens is not None
541
+ if not hasattr(self, "_non_spec_recovery_sources"):
542
+ self._non_spec_recovery_sources = torch.empty_like(
543
+ self.non_spec_state_indices_tensor
544
+ )
545
+ self._non_spec_recovery_sources[:num_decodes].copy_(
546
+ spec_decode_src_indices, non_blocking=True
547
+ )
548
+ spec_decode_src_indices = self._non_spec_recovery_sources[:batch_size]
549
+ spec_decode_src_indices[num_decodes:].fill_(NULL_BLOCK_ID)
550
+ self.num_accepted_tokens[:num_decodes].copy_(
551
+ num_accepted_tokens[:num_decodes], non_blocking=True
552
+ )
553
+ num_accepted_tokens = self.num_accepted_tokens[:batch_size]
554
+ num_accepted_tokens[num_decodes:].fill_(1)
555
+
556
+ self.non_spec_query_start_loc[: num_decodes + 1].copy_(
557
+ non_spec_query_start_loc, non_blocking=True
558
+ )
559
+ non_spec_num_query_tokens = non_spec_query_start_loc[-1] # type: ignore[index]
560
+ non_spec_query_start_loc = self.non_spec_query_start_loc[: batch_size + 1]
561
+ non_spec_query_start_loc[num_decodes + 1 :].fill_(non_spec_num_query_tokens)
562
+
563
+ attn_metadata = GDNAttentionMetadata(
564
+ num_prefills=num_prefills,
565
+ num_prefill_tokens=num_prefill_tokens,
566
+ num_decodes=num_decodes,
567
+ num_decode_tokens=num_decode_tokens,
568
+ num_spec_decodes=num_spec_decodes,
569
+ num_spec_decode_tokens=num_spec_decode_tokens,
570
+ num_actual_tokens=m.num_actual_tokens,
571
+ has_initial_state=has_initial_state,
572
+ chunk_indices=chunk_indices,
573
+ chunk_offsets=chunk_offsets,
574
+ prefill_query_start_loc=prefill_query_start_loc,
575
+ prefill_state_indices=prefill_state_indices,
576
+ prefill_has_initial_state=prefill_has_initial_state,
577
+ spec_query_start_loc=spec_query_start_loc,
578
+ non_spec_query_start_loc=non_spec_query_start_loc,
579
+ spec_state_indices_tensor=spec_state_indices_tensor,
580
+ non_spec_state_indices_tensor=non_spec_state_indices_tensor,
581
+ spec_sequence_masks=spec_sequence_masks,
582
+ spec_token_indx=spec_token_indx,
583
+ non_spec_token_indx=non_spec_token_indx,
584
+ num_accepted_tokens=num_accepted_tokens,
585
+ spec_decode_src_indices=spec_decode_src_indices,
586
+ non_spec_num_accepted=non_spec_num_accepted,
587
+ nums_dict=nums_dict,
588
+ batch_ptr=batch_ptr,
589
+ token_chunk_offset_ptr=token_chunk_offset_ptr,
590
+ )
591
+ return attn_metadata
592
+
593
+ def build_for_cudagraph_capture(
594
+ self, common_attn_metadata: CommonAttentionMetadata
595
+ ):
596
+ """
597
+ This method builds the metadata for full cudagraph capture.
598
+ Currently, only decode is supported for full cudagraphs with Mamba.
599
+ """
600
+ m = common_attn_metadata
601
+
602
+ assert (
603
+ m.num_reqs <= self.decode_cudagraph_max_bs
604
+ and m.num_actual_tokens <= self.decode_cudagraph_max_bs
605
+ ), (
606
+ f"GDN only supports decode-only full CUDAGraph capture. "
607
+ f"Make sure batch size ({m.num_reqs}) <= "
608
+ f"cudagraph capture sizes ({self.decode_cudagraph_max_bs}), "
609
+ f"and number of tokens ({m.num_actual_tokens}) <= "
610
+ f"cudagraph capture sizes ({self.decode_cudagraph_max_bs})."
611
+ )
612
+
613
+ num_accepted_tokens = torch.diff(m.query_start_loc)
614
+ num_decode_draft_tokens_cpu = (num_accepted_tokens - 1).cpu()
615
+
616
+ return self.build(0, m, num_accepted_tokens, num_decode_draft_tokens_cpu)
bundle/plugin-site/ornith_g256/_vllm_correctness/gpu_model_runner.py ADDED
The diff for this file is too large to render. See raw diff
 
bundle/plugin-site/ornith_g256/_vllm_correctness/manifest.json ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "runtime_version": "1.0.1",
3
+ "modules": {
4
+ "vllm.model_executor.layers.mamba.gdn.qwen_gdn_linear_attn": {
5
+ "file": "qwen_gdn_linear_attn.py",
6
+ "native_path": "model_executor/layers/mamba/gdn/qwen_gdn_linear_attn.py",
7
+ "native_sha256": "89f04b36241add75cadbc00e0a2dfe5b8a9b325cd107f7f1373639d83d037668",
8
+ "sha256": "3e58fdddc7a0b42ae3d9b5eabb375a993a4110bad499c37846bde466901dbeb3"
9
+ },
10
+ "vllm.model_executor.layers.mamba.ops.causal_conv1d": {
11
+ "file": "causal_conv1d.py",
12
+ "native_path": "model_executor/layers/mamba/ops/causal_conv1d.py",
13
+ "native_sha256": "044d005cfe59fd0818ed421274e04f3e8dd679b8cdb64fca9f4422f2643484b2",
14
+ "sha256": "230f3e554f5f1d609a37022578c84e42d5c71d46e684101278a7e21af4f7d418"
15
+ },
16
+ "vllm.v1.attention.backends.gdn_attn": {
17
+ "file": "gdn_attn.py",
18
+ "native_path": "v1/attention/backends/gdn_attn.py",
19
+ "native_sha256": "c65552d9aad86472544033d44ad8a872221a83e6b60ba9918cc049ab0c580c7c",
20
+ "sha256": "a3c361d502e8bca2aa46a0baed5ec753a3c62e3d027a298e3ed4692794f9f6ec"
21
+ },
22
+ "vllm.v1.worker.gpu_model_runner": {
23
+ "file": "gpu_model_runner.py",
24
+ "native_path": "v1/worker/gpu_model_runner.py",
25
+ "native_sha256": "4706fcf4b85158173d88e3b19ffb0f6a280fcb60554cfdac861a01a0cab4d4df",
26
+ "sha256": "ef0009acaa67a1a5e066984f34bf619b06e85220ff953a3d9511376f0146dccf"
27
+ }
28
+ },
29
+ "upstream": [
30
+ "https://github.com/vllm-project/vllm/pull/52905",
31
+ "https://github.com/vllm-project/vllm/pull/55504"
32
+ ],
33
+ "local_extension": "Persistent recovery source/count tensors for FULL graphs."
34
+ }
bundle/plugin-site/ornith_g256/_vllm_correctness/qwen_gdn_linear_attn.py ADDED
@@ -0,0 +1,2089 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
+ """Inference-only Qwen3-Next/Qwen3.5 model."""
4
+
5
+ import os
6
+ from typing import Literal
7
+
8
+ import torch
9
+ from einops import rearrange
10
+ from torch import nn
11
+
12
+ from vllm import _custom_ops as ops
13
+ from vllm import envs
14
+ from vllm._aiter_ops import rocm_aiter_ops
15
+ from vllm.config import (
16
+ VllmConfig,
17
+ get_current_vllm_config,
18
+ )
19
+ from vllm.distributed import (
20
+ divide,
21
+ )
22
+ from vllm.forward_context import ForwardContext, get_forward_context
23
+ from vllm.logger import init_logger
24
+ from vllm.model_executor.custom_op import CustomOp, PluggableLayer
25
+ from vllm.model_executor.layers.layernorm import RMSNormGated
26
+ from vllm.model_executor.layers.linear import (
27
+ ColumnParallelLinear,
28
+ MergedColumnParallelLinear,
29
+ RowParallelLinear,
30
+ )
31
+ from vllm.model_executor.layers.mamba.gdn.base import GatedDeltaNetAttention
32
+ from vllm.model_executor.layers.mamba.mamba_mixer2 import mamba_v2_sharded_weight_loader
33
+ from vllm.model_executor.layers.mamba.mamba_utils import (
34
+ MambaStateShapeCalculator,
35
+ is_conv_state_dim_first,
36
+ )
37
+ from vllm.model_executor.layers.mamba.ops.causal_conv1d import (
38
+ causal_conv1d_fn,
39
+ causal_conv1d_update,
40
+ )
41
+ from vllm.model_executor.layers.quantization import QuantizationConfig
42
+ from vllm.model_executor.layers.quantization.auto_awq import AutoAWQConfig
43
+ from vllm.model_executor.layers.quantization.auto_gptq import AutoGPTQConfig
44
+ from vllm.model_executor.layers.quantization.inc import INCConfig
45
+ from vllm.model_executor.model_loader.weight_utils import (
46
+ sharded_weight_loader,
47
+ )
48
+ from vllm.model_executor.utils import set_weight_attrs
49
+ from vllm.platforms import current_platform
50
+ from vllm.third_party.flash_linear_attention.ops import (
51
+ chunk_gated_delta_rule as fla_chunk_gated_delta_rule,
52
+ )
53
+ from vllm.third_party.flash_linear_attention.ops import (
54
+ fused_post_conv_prep,
55
+ fused_recurrent_gated_delta_rule_packed_decode,
56
+ fused_sigmoid_gating_delta_rule_update,
57
+ )
58
+ from vllm.third_party.flash_linear_attention.ops.chunk import l2norm_fwd
59
+ from vllm.third_party.flash_linear_attention.ops.utils import FLA_CHUNK_SIZE
60
+ from vllm.transformers_utils.configs.qwen3_next import Qwen3NextConfig
61
+ from vllm.triton_utils import tl, triton
62
+ from vllm.utils.torch_utils import (
63
+ LayerNameType,
64
+ _encode_layer_name,
65
+ _resolve_layer_name,
66
+ direct_register_custom_op,
67
+ )
68
+ from vllm.v1.attention.backends.gdn_attn import GDNAttentionMetadata
69
+
70
+ # Optional ROCm AITER Triton kernels for the GDN decode path.
71
+ # Availability is checked centrally via rocm_aiter_ops; the actual function
72
+ # references are imported here so that they can be called without per-call
73
+ # import overhead.
74
+ GDN_AITER_TRITON_AVAILABLE = (
75
+ rocm_aiter_ops.are_gdn_triton_kernels_available()
76
+ or rocm_aiter_ops.is_rdna_gdn_triton_kernels_available()
77
+ )
78
+
79
+ if GDN_AITER_TRITON_AVAILABLE:
80
+ from aiter.ops.triton.causal_conv1d_update_single_token import (
81
+ fused_reshape_causal_conv1d_update_single_token as gdn_aiter_fused_reshape_causal_conv1d_update_single_token, # noqa: E501
82
+ )
83
+ from aiter.ops.triton.gated_delta_net.fused_rearrange_sigmoid_gdr import (
84
+ fused_rearrange_sigmoid_gated_delta_rule as gdn_aiter_fused_rearrange_sigmoid_gated_delta_rule, # noqa: E501
85
+ )
86
+
87
+ logger = init_logger(__name__)
88
+
89
+ MAX_FUSED_GDN_MTP_TOKENS = 8
90
+ FUSED_GDN_STATE_DTYPES = (torch.float32, torch.bfloat16)
91
+
92
+
93
+ def _resolve_gdn_prefill_backend(
94
+ vllm_config: VllmConfig,
95
+ ) -> tuple[str, Literal["triton", "flashinfer", "cutedsl"]]:
96
+ """Resolve GDN prefill backend.
97
+
98
+ FlashInfer's GDN prefill kernel is chosen when:
99
+ * ``requested in ["flashinfer", "auto"]``;
100
+ * ``platform == cuda``;
101
+ * one of the following:
102
+ - Hopper (SM90) — no further constraints;
103
+ - Blackwell (SM10.x) with ``head_k_dim == 128``, ``cuda_runtime >= 13``.
104
+
105
+ In-tree CuteDSL GDN prefill kernel is chosen when:
106
+ * "cutedsl" is requested; (opt-in only)
107
+ * Blackwell (SM10.x) with ``head_k_dim == 128``;
108
+ """
109
+ additional_config = vllm_config.additional_config
110
+ backend_cfg = (
111
+ additional_config.get("gdn_prefill_backend", "auto")
112
+ if isinstance(additional_config, dict)
113
+ else "auto"
114
+ )
115
+ backend = str(backend_cfg).strip().lower()
116
+
117
+ if not current_platform.is_cuda():
118
+ return backend, "triton"
119
+
120
+ head_k_dim = getattr(
121
+ vllm_config.model_config.hf_text_config, "linear_key_head_dim", None
122
+ )
123
+
124
+ supports_flashinfer = False
125
+ supports_cutedsl = False
126
+
127
+ if current_platform.is_device_capability(90):
128
+ supports_flashinfer = True
129
+ elif (
130
+ current_platform.is_device_capability_family(100)
131
+ and head_k_dim == 128
132
+ and current_platform.get_cuda_runtime_major() >= 13
133
+ ):
134
+ supports_flashinfer = True
135
+ supports_cutedsl = True
136
+
137
+ if backend in ["flashinfer", "auto"] and supports_flashinfer:
138
+ return backend, "flashinfer"
139
+ if backend == "cutedsl" and supports_cutedsl:
140
+ return backend, "cutedsl"
141
+ return backend, "triton"
142
+
143
+
144
+ def _log_gdn_backend_decision(
145
+ vllm_config: VllmConfig,
146
+ requested_backend: str,
147
+ active_backend: str,
148
+ ) -> None:
149
+ """Log the GDN prefill backend choice in the attention-selector style."""
150
+ head_k_dim = getattr(
151
+ vllm_config.model_config.hf_text_config, "linear_key_head_dim", None
152
+ )
153
+ chosen = {
154
+ "flashinfer": "FlashInfer",
155
+ "cutedsl": "CuteDSL",
156
+ "triton": "Triton/FLA",
157
+ }[active_backend]
158
+ logger.info_once(
159
+ "Using %s GDN prefill kernel (requested=%s, head_k_dim=%s).",
160
+ chosen,
161
+ requested_backend,
162
+ head_k_dim,
163
+ )
164
+ if active_backend == "flashinfer" and current_platform.is_device_capability(90):
165
+ logger.warning_once(
166
+ "FlashInfer GDN prefill is JIT-compiled; first run may take a "
167
+ "while. Set --gdn-prefill-backend triton to skip JIT.",
168
+ )
169
+
170
+
171
+ def fi_chunk_gated_delta_rule(
172
+ q: torch.Tensor,
173
+ k: torch.Tensor,
174
+ v: torch.Tensor,
175
+ g: torch.Tensor,
176
+ beta: torch.Tensor,
177
+ initial_state: torch.Tensor,
178
+ output_final_state: bool,
179
+ cu_seqlens: torch.Tensor | None = None,
180
+ use_qk_l2norm_in_kernel: bool = True,
181
+ ):
182
+ from flashinfer.gdn_prefill import (
183
+ chunk_gated_delta_rule as chunk_gated_delta_rule_fi,
184
+ )
185
+
186
+ if use_qk_l2norm_in_kernel:
187
+ q = l2norm_fwd(q)
188
+ k = l2norm_fwd(k)
189
+
190
+ # use flashinfer implementation
191
+ q = q.squeeze(0).contiguous()
192
+ k = k.squeeze(0).contiguous()
193
+ v = v.squeeze(0).contiguous()
194
+
195
+ g = g.squeeze(0).contiguous()
196
+ beta = beta.squeeze(0).contiguous()
197
+ fi_state = initial_state.to(torch.float32)
198
+ fi_g = g.to(torch.float32)
199
+ fi_beta = beta.to(torch.float32)
200
+ if cu_seqlens is not None:
201
+ cu_seqlens = cu_seqlens.to(torch.int64)
202
+ result = chunk_gated_delta_rule_fi(
203
+ q=q,
204
+ k=k,
205
+ v=v,
206
+ g=torch.exp(fi_g),
207
+ beta=fi_beta,
208
+ initial_state=fi_state,
209
+ output_final_state=output_final_state,
210
+ cu_seqlens=cu_seqlens,
211
+ )
212
+ # FlashInfer returns (output, state) when output_final_state=True,
213
+ # or just output when output_final_state=False.
214
+ # Unsqueeze back to 4D (1, L, H, D) to match fla output format
215
+ if output_final_state:
216
+ output, final_state = result
217
+ return output.unsqueeze(0), final_state
218
+ else:
219
+ return result.unsqueeze(0), None
220
+
221
+
222
+ @CustomOp.register("chunk_gated_delta_rule")
223
+ class ChunkGatedDeltaRule(CustomOp):
224
+ def __init__(self) -> None:
225
+ super().__init__()
226
+ vllm_config = get_current_vllm_config()
227
+ backend, active_backend = _resolve_gdn_prefill_backend(vllm_config)
228
+ self.gdn_prefill_backend = active_backend
229
+
230
+ if backend in ("flashinfer", "cutedsl") and active_backend != backend:
231
+ logger.warning_once(
232
+ "GDN prefill backend '%s' is selected but cannot use this "
233
+ "kernel on the current platform. Falling back to Triton/FLA.",
234
+ backend,
235
+ )
236
+ _log_gdn_backend_decision(vllm_config, backend, active_backend)
237
+
238
+ if active_backend == "flashinfer":
239
+ self._forward_method = self.forward_cuda
240
+ elif active_backend == "cutedsl":
241
+ self._forward_method = self.forward_cutedsl
242
+ else:
243
+ self._forward_method = self.forward_native
244
+
245
+ def forward_cuda(
246
+ self,
247
+ q: torch.Tensor,
248
+ k: torch.Tensor,
249
+ v: torch.Tensor,
250
+ g: torch.Tensor,
251
+ beta: torch.Tensor,
252
+ initial_state: torch.Tensor,
253
+ output_final_state: bool,
254
+ cu_seqlens: torch.Tensor | None = None,
255
+ chunk_indices: torch.Tensor | None = None,
256
+ chunk_offsets: torch.Tensor | None = None,
257
+ use_qk_l2norm_in_kernel: bool = True,
258
+ core_attn_out: torch.Tensor | None = None,
259
+ ):
260
+ o, final_state = fi_chunk_gated_delta_rule(
261
+ q=q,
262
+ k=k,
263
+ v=v,
264
+ g=g,
265
+ beta=beta,
266
+ initial_state=initial_state,
267
+ output_final_state=output_final_state,
268
+ cu_seqlens=cu_seqlens,
269
+ use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
270
+ )
271
+ if core_attn_out is not None:
272
+ o_flat = o.squeeze(0).reshape(-1)
273
+ co_flat = core_attn_out.reshape(-1)
274
+ co_flat[: o_flat.numel()].copy_(o_flat)
275
+ return o, final_state
276
+
277
+ def forward_native(
278
+ self,
279
+ q: torch.Tensor,
280
+ k: torch.Tensor,
281
+ v: torch.Tensor,
282
+ g: torch.Tensor,
283
+ beta: torch.Tensor,
284
+ initial_state: torch.Tensor,
285
+ output_final_state: bool,
286
+ cu_seqlens: torch.Tensor | None = None,
287
+ chunk_indices: torch.Tensor | None = None,
288
+ chunk_offsets: torch.Tensor | None = None,
289
+ use_qk_l2norm_in_kernel: bool = True,
290
+ core_attn_out: torch.Tensor | None = None,
291
+ ):
292
+ return fla_chunk_gated_delta_rule(
293
+ q=q,
294
+ k=k,
295
+ v=v,
296
+ g=g,
297
+ beta=beta,
298
+ initial_state=initial_state,
299
+ output_final_state=output_final_state,
300
+ cu_seqlens=cu_seqlens,
301
+ chunk_indices=chunk_indices,
302
+ chunk_offsets=chunk_offsets,
303
+ use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
304
+ core_attn_out=core_attn_out,
305
+ )
306
+
307
+ def forward_cutedsl(
308
+ self,
309
+ q: torch.Tensor,
310
+ k: torch.Tensor,
311
+ v: torch.Tensor,
312
+ g: torch.Tensor,
313
+ beta: torch.Tensor,
314
+ initial_state: torch.Tensor,
315
+ output_final_state: bool,
316
+ cu_seqlens: torch.Tensor | None = None,
317
+ chunk_indices: torch.Tensor | None = None,
318
+ chunk_offsets: torch.Tensor | None = None,
319
+ use_qk_l2norm_in_kernel: bool = True,
320
+ core_attn_out: torch.Tensor | None = None,
321
+ ):
322
+ from vllm.model_executor.layers.mamba.ops.gdn_chunk_cutedsl import (
323
+ chunk_gated_delta_rule_cutedsl,
324
+ )
325
+
326
+ if use_qk_l2norm_in_kernel:
327
+ q = l2norm_fwd(q)
328
+ k = l2norm_fwd(k)
329
+
330
+ assert cu_seqlens is not None
331
+ assert chunk_indices is not None
332
+ assert chunk_offsets is not None
333
+
334
+ o, final_state = chunk_gated_delta_rule_cutedsl(
335
+ q=q,
336
+ k=k,
337
+ v=v,
338
+ g=g,
339
+ beta=beta,
340
+ initial_state=initial_state,
341
+ cu_seqlens=cu_seqlens,
342
+ chunk_indices=chunk_indices,
343
+ chunk_offsets=chunk_offsets,
344
+ core_attn_out=core_attn_out,
345
+ )
346
+ if not output_final_state:
347
+ final_state = None
348
+ return o, final_state
349
+
350
+
351
+ @PluggableLayer.register("qwen_gated_delta_net_attention")
352
+ class QwenGatedDeltaNetAttention(GatedDeltaNetAttention):
353
+ def get_state_shape(
354
+ self,
355
+ ) -> tuple[tuple[int, ...], tuple[int, ...], tuple[int, ...], tuple[int, ...]]:
356
+ return MambaStateShapeCalculator.gated_delta_net_state_shape(
357
+ self.tp_size,
358
+ self.num_k_heads,
359
+ self.num_v_heads,
360
+ self.head_k_dim,
361
+ self.head_v_dim,
362
+ self.conv_kernel_size,
363
+ self.num_spec,
364
+ )
365
+
366
+ def __init__(
367
+ self,
368
+ config: Qwen3NextConfig,
369
+ vllm_config: VllmConfig,
370
+ prefix: str = "",
371
+ gqa_interleaved_layout=False,
372
+ reduce_results: bool = True,
373
+ ) -> None:
374
+ super().__init__(config, vllm_config, prefix)
375
+
376
+ self.num_k_heads = config.linear_num_key_heads
377
+ self.num_v_heads = config.linear_num_value_heads
378
+ self.head_k_dim = config.linear_key_head_dim
379
+ self.head_v_dim = config.linear_value_head_dim
380
+ self.conv_kernel_size = config.linear_conv_kernel_dim
381
+ self.key_dim = self.head_k_dim * self.num_k_heads
382
+ self.value_dim = self.head_v_dim * self.num_v_heads
383
+ self.gqa_interleaved_layout = gqa_interleaved_layout
384
+ if current_platform.is_xpu():
385
+ self._forward_method = self.forward_xpu
386
+ elif current_platform.is_cpu():
387
+ from vllm.model_executor.layers.mamba.ops.cpu.gdn_attention import (
388
+ register_cpu_gdn_attention_ops,
389
+ )
390
+
391
+ register_cpu_gdn_attention_ops()
392
+ self._forward_method = self.forward_cpu
393
+ elif current_platform.is_rocm():
394
+ self._forward_method = self.forward_hip
395
+ else:
396
+ self._forward_method = self.forward_cuda
397
+
398
+ # QKV
399
+ self.conv_dim = self.key_dim * 2 + self.value_dim
400
+ self.conv1d = ColumnParallelLinear(
401
+ input_size=self.conv_kernel_size,
402
+ output_size=self.conv_dim,
403
+ bias=False,
404
+ prefix=f"{prefix}.conv1d",
405
+ )
406
+ self.conv1d.weight.data = self.conv1d.weight.data.unsqueeze(1)
407
+
408
+ # projection of the input hidden states
409
+ # Qwen3-Next and Qwen3.5 has a different qkv_proj layout,
410
+ # we need to create qkvz_proj adaptively here.
411
+ # When create_in_proj_qkvz is False (e.g. LoRA enabled in Qwen3.5),
412
+ # in_proj_qkv and in_proj_z are created separately instead.
413
+ self.in_proj_qkvz = self.create_qkvz_proj(
414
+ hidden_size=self.hidden_size,
415
+ key_dim=self.key_dim,
416
+ value_dim=self.value_dim,
417
+ quant_config=self.quant_config,
418
+ prefix=f"{prefix}.in_proj_qkvz",
419
+ )
420
+
421
+ # ba_proj doesn't support blockwise fp8 quantization.
422
+ # Qwen3-Next and Qwen3.5 have different in_proj_ba checkpoint
423
+ # layouts, so we use a factory method to create the projection.
424
+ self.in_proj_ba = self.create_ba_proj(
425
+ hidden_size=self.hidden_size,
426
+ num_v_heads=self.num_v_heads,
427
+ quant_config=self.quant_config,
428
+ prefix=f"{prefix}.in_proj_ba",
429
+ )
430
+ self.disable_tp_for_ba_proj = self.maybe_disable_tp(self.quant_config)
431
+
432
+ query_key_settings = (self.key_dim, 0, False)
433
+ value_settings = (self.value_dim, 0, False)
434
+
435
+ self.conv1d.weight.weight_loader = mamba_v2_sharded_weight_loader(
436
+ [
437
+ query_key_settings,
438
+ query_key_settings,
439
+ value_settings,
440
+ ],
441
+ self.tp_size,
442
+ self.tp_rank,
443
+ )
444
+
445
+ # selective projection used to make dt, B and C input dependent
446
+
447
+ # time step projection (discretization)
448
+ # instantiate once and copy inv_dt in init_weights of PretrainedModel
449
+ self.dt_bias = nn.Parameter(
450
+ torch.ones(self.num_v_heads // self.tp_size),
451
+ )
452
+ self.A_log = nn.Parameter(
453
+ torch.empty(
454
+ divide(self.num_v_heads, self.tp_size),
455
+ dtype=torch.float32,
456
+ )
457
+ )
458
+
459
+ set_weight_attrs(self.A_log, {"weight_loader": sharded_weight_loader(0)})
460
+ set_weight_attrs(self.dt_bias, {"weight_loader": sharded_weight_loader(0)})
461
+
462
+ output_gate_type = getattr(config, "output_gate_type", "silu")
463
+ if output_gate_type == "swish":
464
+ output_gate_type = "silu"
465
+ assert output_gate_type in ["silu", "swish", "sigmoid"], (
466
+ f"unsupported {output_gate_type=}"
467
+ )
468
+
469
+ self.norm = RMSNormGated(
470
+ self.head_v_dim,
471
+ eps=self.layer_norm_epsilon,
472
+ group_size=None,
473
+ norm_before_gate=True,
474
+ activation=output_gate_type,
475
+ device=current_platform.current_device(),
476
+ )
477
+
478
+ self.out_proj = RowParallelLinear(
479
+ self.value_dim,
480
+ self.hidden_size,
481
+ bias=False,
482
+ input_is_parallel=True,
483
+ reduce_results=reduce_results,
484
+ quant_config=self.quant_config,
485
+ prefix=f"{prefix}.out_proj",
486
+ )
487
+
488
+ self.chunk_gated_delta_rule = ChunkGatedDeltaRule()
489
+ self.gdn_prefill_backend = self.chunk_gated_delta_rule.gdn_prefill_backend
490
+ self._prefill_kernels_warmed_up = False
491
+ self.enable_packed_recurrent_decode = (
492
+ envs.VLLM_ENABLE_FLA_PACKED_RECURRENT_DECODE
493
+ )
494
+ self.gdn_decode_kernel = envs.VLLM_GDN_DECODE_KERNEL.strip().lower()
495
+ if self.gdn_decode_kernel == "cuda":
496
+ reason = self._fused_gdn_decode_unsupported_reason(vllm_config)
497
+ if reason is not None:
498
+ if "VLLM_GDN_DECODE_KERNEL" in os.environ:
499
+ raise ValueError(
500
+ f"VLLM_GDN_DECODE_KERNEL=cuda is not supported: {reason}"
501
+ )
502
+ logger.info_once(
503
+ "Falling back to the Triton GDN decode path: %s", reason
504
+ )
505
+ self.gdn_decode_kernel = "triton"
506
+ self.enable_fused_gdn_decode = self.gdn_decode_kernel == "cuda"
507
+ logger.info_once("GDN decode kernel: %s", self.gdn_decode_kernel)
508
+
509
+ compilation_config = get_current_vllm_config().compilation_config
510
+ if prefix in compilation_config.static_forward_context:
511
+ raise ValueError(f"Duplicate layer name: {prefix}")
512
+ compilation_config.static_forward_context[prefix] = self
513
+
514
+ def _fused_gdn_decode_unsupported_reason(
515
+ self, vllm_config: VllmConfig
516
+ ) -> str | None:
517
+ conv_state_dtype, recurrent_state_dtype = self.get_state_dtype()
518
+ if (
519
+ self.gqa_interleaved_layout
520
+ or self.head_k_dim != 128
521
+ or self.head_v_dim != 128
522
+ or self.norm.activation != "silu"
523
+ or vllm_config.model_config.dtype != torch.bfloat16
524
+ or conv_state_dtype != torch.bfloat16
525
+ or recurrent_state_dtype not in FUSED_GDN_STATE_DTYPES
526
+ or not current_platform.has_device_capability(80)
527
+ ):
528
+ return (
529
+ "the fused CUDA kernel requires a BF16 GDN model with "
530
+ "K=V=128, SiLU gating, non-interleaved GQA layout, BF16 "
531
+ "convolution cache, BF16 or FP32 recurrent state, and a "
532
+ "GPU with compute capability 8.0+"
533
+ )
534
+ if not hasattr(torch.ops._C, "fused_gdn_decode_post_conv_mtp"):
535
+ return "torch.ops._C.fused_gdn_decode_post_conv_mtp is not built"
536
+ return None
537
+
538
+ def create_qkvz_proj(
539
+ self,
540
+ hidden_size: int,
541
+ key_dim: int,
542
+ value_dim: int,
543
+ quant_config: QuantizationConfig | None,
544
+ prefix: str,
545
+ ) -> MergedColumnParallelLinear:
546
+ # When gqa_interleaved_layout=True (Qwen3-Next), qkvz weights are
547
+ # stored as a single fused tensor with interleaved GQA layout, so we
548
+ # use one output shard to preserve the interleaving across TP ranks.
549
+ # When gqa_interleaved_layout=False (Qwen3.5), the checkpoint has
550
+ # separate q, k, v, z weights, so we use 4 independent output sizes.
551
+ output_sizes = (
552
+ [sum((key_dim, key_dim, value_dim, value_dim))]
553
+ if self.gqa_interleaved_layout
554
+ else [key_dim, key_dim, value_dim, value_dim]
555
+ )
556
+ return MergedColumnParallelLinear(
557
+ input_size=hidden_size,
558
+ output_sizes=output_sizes,
559
+ bias=False,
560
+ quant_config=quant_config,
561
+ prefix=prefix,
562
+ )
563
+
564
+ def create_ba_proj(
565
+ self,
566
+ hidden_size: int,
567
+ num_v_heads: int,
568
+ quant_config: QuantizationConfig | None,
569
+ prefix: str,
570
+ ) -> MergedColumnParallelLinear:
571
+ # When gqa_interleaved_layout=True (Qwen3-Next), in_proj_ba is stored
572
+ # as a single fused weight [b_g0, a_g0, b_g1, a_g1, ...] interleaved
573
+ # by key-head group; a single output shard preserves this across TP.
574
+ # When gqa_interleaved_layout=False (Qwen3.5), in_proj_b and in_proj_a
575
+ # are separate checkpoint weights, so we use 2 independent output sizes.
576
+ output_sizes = (
577
+ [num_v_heads * 2] if self.gqa_interleaved_layout else [num_v_heads] * 2
578
+ )
579
+ return MergedColumnParallelLinear(
580
+ input_size=hidden_size,
581
+ output_sizes=output_sizes,
582
+ bias=False,
583
+ quant_config=quant_config,
584
+ prefix=prefix,
585
+ disable_tp=self.maybe_disable_tp(quant_config),
586
+ )
587
+
588
+ def maybe_disable_tp(self, quant_config: QuantizationConfig | None) -> bool:
589
+ """Whether to replicate ba_proj instead of TP-sharding it.
590
+
591
+ Marlin requires output_size_per_partition >= MIN_THREAD_N=64, which
592
+ the Qwen3.5 non-interleaved [num_v_heads]*2 layout violates at TP>=2
593
+ (e.g. num_v_heads=64, TP=4 -> 16). Replicating the projection keeps
594
+ each rank above the Marlin threshold; forward() then slices b/a to
595
+ the local TP partition. Qwen3-Next's interleaved [num_v_heads*2]
596
+ layout is unaffected and stays TP-sharded.
597
+
598
+ See https://github.com/vllm-project/vllm/issues/35924
599
+ """
600
+ return (
601
+ current_platform.is_cuda()
602
+ and not self.gqa_interleaved_layout
603
+ and isinstance(quant_config, (AutoAWQConfig, AutoGPTQConfig, INCConfig))
604
+ )
605
+
606
+ def split_ba(self, ba: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
607
+ b, a = ba.chunk(2, dim=-1)
608
+ if self.disable_tp_for_ba_proj and self.tp_size > 1:
609
+ # ba_proj is replicated for Marlin; slice b/a to local TP rank.
610
+ ba_chunk = self.num_v_heads // self.tp_size
611
+ ba_start = self.tp_rank * ba_chunk
612
+ b = b[:, ba_start : ba_start + ba_chunk]
613
+ a = a[:, ba_start : ba_start + ba_chunk]
614
+ return b, a
615
+
616
+ def fix_query_key_value_ordering(
617
+ self,
618
+ mixed_qkvz: torch.Tensor,
619
+ mixed_ba: torch.Tensor,
620
+ ):
621
+ """
622
+ Derives `query`, `key` and `value` tensors from `mixed_qkvzba`.
623
+ """
624
+ new_tensor_shape_qkvz = mixed_qkvz.size()[:-1] + (
625
+ self.num_k_heads // self.tp_size,
626
+ (
627
+ self.head_k_dim
628
+ + self.head_k_dim
629
+ + (self.head_v_dim + self.head_v_dim)
630
+ * self.num_v_heads
631
+ // self.num_k_heads
632
+ ),
633
+ )
634
+ new_tensor_shape_ba = mixed_ba.size()[:-1] + (
635
+ self.num_k_heads // self.tp_size,
636
+ 2 * self.num_v_heads // self.num_k_heads,
637
+ )
638
+
639
+ mixed_qkvz = mixed_qkvz.view(*new_tensor_shape_qkvz)
640
+ mixed_ba = mixed_ba.view(*new_tensor_shape_ba)
641
+
642
+ split_arg_list_qkvz = [
643
+ self.head_k_dim,
644
+ self.head_k_dim,
645
+ (self.num_v_heads // self.num_k_heads * self.head_v_dim),
646
+ (self.num_v_heads // self.num_k_heads * self.head_v_dim),
647
+ ]
648
+ split_arg_list_ba = [
649
+ self.num_v_heads // self.num_k_heads,
650
+ self.num_v_heads // self.num_k_heads,
651
+ ]
652
+
653
+ # [b, sq, ng, (hn + hn + np/ng * hn + np/ng + np/ng)]
654
+ # --> [b, sq, ng, hn], [b, sq, ng, hn], [b, sq, ng, np/ng * hn],
655
+ # [b, sq, ng, np/ng * hn], [b, sq, ng, np/ng], [b, sq, ng, np/ng]
656
+ (query, key, value, z) = torch.split(mixed_qkvz, split_arg_list_qkvz, dim=2)
657
+ (b, a) = torch.split(mixed_ba, split_arg_list_ba, dim=2)
658
+
659
+ # [b, sq, ng, np/ng * hn] -> [b, sq, np, hn]
660
+ value = value.reshape(value.size(0), -1, self.head_v_dim)
661
+ z = z.reshape(z.size(0), -1, self.head_v_dim)
662
+ b = b.reshape(b.size(0), self.num_v_heads // self.tp_size)
663
+ a = a.reshape(a.size(0), self.num_v_heads // self.tp_size)
664
+
665
+ return query, key, value, z, b, a
666
+
667
+ @torch.compile(fullgraph=True)
668
+ def prepare_gdn_attention_core_inputs(
669
+ self,
670
+ mixed_qkvz: torch.Tensor,
671
+ mixed_ba: torch.Tensor,
672
+ num_tokens: int,
673
+ ):
674
+ """
675
+ Derives mixed_qkv, z, b, a from projected qkvz/ba for the GDN custom op.
676
+
677
+ For gqa_interleaved_layout (Qwen3-Next): unpack the interleaved
678
+ [ng, (hk + hk + np/ng*hv + np/ng*hv)] layout into contiguous qkv.
679
+ For non-interleaved layout (Qwen3.5): simple split along last dim.
680
+ """
681
+ if not self.gqa_interleaved_layout:
682
+ # Qwen3.5: weights are in [q, k, v, z] order
683
+ assert num_tokens == mixed_qkvz.shape[0]
684
+ qkv_size = (self.key_dim * 2 + self.value_dim) // self.tp_size
685
+ z_size = self.value_dim // self.tp_size
686
+ mixed_qkv, z_flat = mixed_qkvz.split([qkv_size, z_size], dim=-1)
687
+ n = mixed_qkvz.shape[0]
688
+ z_out = z_flat.reshape(n, -1, self.head_v_dim)
689
+ b, a = mixed_ba.chunk(2, dim=-1)
690
+ return mixed_qkv, z_out, b, a
691
+
692
+ # Qwen3-Next: interleaved GQA layout
693
+ base_shape_qkvz = mixed_qkvz.size()[:-1]
694
+ base_shape_ba = mixed_ba.size()[:-1]
695
+ ng = self.num_k_heads // self.tp_size
696
+
697
+ new_tensor_shape_qkvz = base_shape_qkvz + (
698
+ ng,
699
+ (
700
+ self.head_k_dim
701
+ + self.head_k_dim
702
+ + (self.head_v_dim + self.head_v_dim)
703
+ * self.num_v_heads
704
+ // self.num_k_heads
705
+ ),
706
+ )
707
+ new_tensor_shape_ba = base_shape_ba + (
708
+ ng,
709
+ 2 * self.num_v_heads // self.num_k_heads,
710
+ )
711
+
712
+ mixed_qkvz = mixed_qkvz.view(*new_tensor_shape_qkvz)
713
+ mixed_ba = mixed_ba.view(*new_tensor_shape_ba)
714
+
715
+ split_arg_list_qkvz = [
716
+ self.head_k_dim,
717
+ self.head_k_dim,
718
+ (self.num_v_heads // self.num_k_heads * self.head_v_dim),
719
+ (self.num_v_heads // self.num_k_heads * self.head_v_dim),
720
+ ]
721
+ split_arg_list_ba = [
722
+ self.num_v_heads // self.num_k_heads,
723
+ self.num_v_heads // self.num_k_heads,
724
+ ]
725
+
726
+ (query, key, value, z) = torch.split(mixed_qkvz, split_arg_list_qkvz, dim=-1)
727
+ (b, a) = torch.split(mixed_ba, split_arg_list_ba, dim=-1)
728
+
729
+ mixed_qkv_logical = torch.cat(
730
+ [
731
+ query.reshape(num_tokens, -1),
732
+ key.reshape(num_tokens, -1),
733
+ value.reshape(num_tokens, -1),
734
+ ],
735
+ dim=-1,
736
+ )
737
+
738
+ # The split above produces non-contiguous views into the interleaved
739
+ # buffer. Concatenating everything into a single flat tensor forces a
740
+ # contiguous copy, then slicing back out gives contiguous q/k/v/z/b/a
741
+ # tensors that downstream kernels require. Doing this in one cat+slice
742
+ # keeps torch.compile in a single Triton graph instead of emitting
743
+ # separate copy kernels per tensor. The original code used
744
+ # rearrange(...).contiguous() on each tensor individually.
745
+ fused = torch.cat(
746
+ [
747
+ mixed_qkv_logical.reshape(-1),
748
+ z.reshape(-1),
749
+ b.reshape(-1),
750
+ a.reshape(-1),
751
+ ],
752
+ dim=0,
753
+ )
754
+
755
+ curr = 0
756
+ qkv_numel = mixed_qkv_logical.numel()
757
+ z_numel = z.numel()
758
+ b_numel = b.numel()
759
+ a_numel = a.numel()
760
+
761
+ mixed_qkv_out = fused[curr : curr + qkv_numel].view(num_tokens, -1)
762
+ curr += qkv_numel
763
+
764
+ z_out = fused[curr : curr + z_numel].view(
765
+ num_tokens, self.num_v_heads // self.tp_size, self.head_v_dim
766
+ )
767
+ curr += z_numel
768
+
769
+ b_out = fused[curr : curr + b_numel].view(
770
+ num_tokens, self.num_v_heads // self.tp_size
771
+ )
772
+ curr += b_numel
773
+
774
+ a_out = fused[curr : curr + a_numel].view(
775
+ num_tokens, self.num_v_heads // self.tp_size
776
+ )
777
+
778
+ return mixed_qkv_out, z_out, b_out, a_out
779
+
780
+ def rearrange_mixed_qkv(self, mixed_qkv):
781
+ """Split packed qkv into contiguous (1, seq, heads, dim) tensors.
782
+
783
+ The original code used ``rearrange(x, "l (h d) -> 1 l h d", d=...)``
784
+ followed by ``.contiguous()`` on each tensor. This version flattens
785
+ all three splits into a single buffer via ``torch.cat`` so that
786
+ torch.compile emits one Triton copy kernel instead of three separate
787
+ contiguous() calls.
788
+ """
789
+ if mixed_qkv is None:
790
+ return None, None, None
791
+
792
+ seq_len = mixed_qkv.shape[0]
793
+ q_dim = self.key_dim // self.tp_size
794
+ k_dim = self.key_dim // self.tp_size
795
+ v_dim = self.value_dim // self.tp_size
796
+
797
+ query, key, value = torch.split(mixed_qkv, [q_dim, k_dim, v_dim], dim=-1)
798
+
799
+ fused = torch.cat(
800
+ [query.reshape(-1), key.reshape(-1), value.reshape(-1)], dim=0
801
+ )
802
+
803
+ q_size = seq_len * q_dim
804
+ k_size = seq_len * k_dim
805
+
806
+ q_contig = fused[0:q_size]
807
+ k_contig = fused[q_size : q_size + k_size]
808
+ v_contig = fused[q_size + k_size :]
809
+
810
+ query = q_contig.view(1, seq_len, -1, self.head_k_dim)
811
+ key = k_contig.view(1, seq_len, -1, self.head_k_dim)
812
+ value = v_contig.view(1, seq_len, -1, self.head_v_dim)
813
+
814
+ return query, key, value
815
+
816
+ def forward(
817
+ self,
818
+ hidden_states: torch.Tensor,
819
+ ) -> torch.Tensor:
820
+ return self._forward_method(hidden_states)
821
+
822
+ def _output_projection(
823
+ self,
824
+ core_attn_out: torch.Tensor,
825
+ z: torch.Tensor,
826
+ ) -> torch.Tensor:
827
+ """Part 3: RMSNormGated + output linear projection.
828
+
829
+ The RMSNormGated + quant sequence is eligible for fusion
830
+ by the compilation pass when fuse_norm_quant is enabled.
831
+ """
832
+ z_shape_og = z.shape
833
+ core_attn_out = core_attn_out.reshape(-1, core_attn_out.shape[-1])
834
+ z = z.reshape(-1, z.shape[-1])
835
+ core_attn_out = self.norm(core_attn_out, z)
836
+ core_attn_out = core_attn_out.reshape(z_shape_og)
837
+ core_attn_out = core_attn_out.flatten(-2) # ... h d -> ... (h d)
838
+ output, _ = self.out_proj(core_attn_out)
839
+ return output
840
+
841
+ def forward_hip(
842
+ self,
843
+ hidden_states: torch.Tensor,
844
+ ) -> torch.Tensor:
845
+ """ROCm forward using AITER Triton fused projection+attention when
846
+ available, otherwise falling back to the generic CUDA path."""
847
+ if GDN_AITER_TRITON_AVAILABLE:
848
+ num_tokens = hidden_states.size(0)
849
+ projected_states_qkvz, _ = self.in_proj_qkvz(hidden_states)
850
+ projected_states_ba, _ = self.in_proj_ba(hidden_states)
851
+ projected_states_qkvz = projected_states_qkvz.view(num_tokens, -1)
852
+ projected_states_ba = projected_states_ba.view(num_tokens, -1)
853
+ core_attn_out = torch.empty(
854
+ (num_tokens, self.num_v_heads // self.tp_size, self.head_v_dim),
855
+ dtype=hidden_states.dtype,
856
+ device=hidden_states.device,
857
+ )
858
+ z = torch.empty(
859
+ (num_tokens, self.num_v_heads // self.tp_size, self.head_v_dim),
860
+ dtype=projected_states_qkvz.dtype,
861
+ device=projected_states_qkvz.device,
862
+ )
863
+
864
+ torch.ops.vllm.qwen_gdn_attention_core(
865
+ projected_states_qkvz,
866
+ projected_states_ba,
867
+ z,
868
+ core_attn_out,
869
+ layer_name=_encode_layer_name(self.prefix),
870
+ use_aiter=True,
871
+ )
872
+
873
+ return self._output_projection(core_attn_out, z)
874
+ else:
875
+ return self.forward_cuda(hidden_states)
876
+
877
+ def forward_cuda(
878
+ self,
879
+ hidden_states: torch.Tensor,
880
+ ) -> torch.Tensor:
881
+ """
882
+ Forward pass with three parts:
883
+ 1. Input projection
884
+ 2. Core attention (custom op)
885
+ 3. Output projection
886
+ """
887
+ num_tokens = hidden_states.size(0)
888
+ # ============================================================
889
+ # Part 1: Input Projection
890
+ # ============================================================
891
+ mixed_qkvz, _ = self.in_proj_qkvz(hidden_states)
892
+ ba, _ = self.in_proj_ba(hidden_states)
893
+
894
+ use_fused_gdn_decode = (
895
+ self.enable_fused_gdn_decode
896
+ and hidden_states.dtype == torch.bfloat16
897
+ and self.norm.weight.dtype in (torch.bfloat16, torch.float32)
898
+ )
899
+ if use_fused_gdn_decode:
900
+ core_attn_out = torch.zeros(
901
+ (num_tokens, self.num_v_heads // self.tp_size, self.head_v_dim),
902
+ dtype=hidden_states.dtype,
903
+ device=hidden_states.device,
904
+ )
905
+ torch.ops.vllm.qwen_gdn_attention_core_fused_norm_packed(
906
+ mixed_qkvz,
907
+ ba,
908
+ core_attn_out,
909
+ layer_name=_encode_layer_name(self.prefix),
910
+ )
911
+ output, _ = self.out_proj(core_attn_out.flatten(-2))
912
+ return output
913
+
914
+ if self.gqa_interleaved_layout:
915
+ # Qwen3-Next: unpack the interleaved GQA layout
916
+ query, key, value, z, b, a = self.fix_query_key_value_ordering(
917
+ mixed_qkvz, ba
918
+ )
919
+ query, key, value = map(
920
+ lambda x: rearrange(x, "l p d -> l (p d)"), (query, key, value)
921
+ )
922
+ mixed_qkv = torch.cat((query, key, value), dim=-1)
923
+ else:
924
+ # Qwen3.5: weights are already in [q, k, v, z] and [b, a] order
925
+ qkv_size = (self.key_dim * 2 + self.value_dim) // self.tp_size
926
+ z_size = self.value_dim // self.tp_size
927
+ mixed_qkv, z = mixed_qkvz.split([qkv_size, z_size], dim=-1)
928
+ z = z.reshape(z.size(0), -1, self.head_v_dim)
929
+ b, a = self.split_ba(ba)
930
+
931
+ # ============================================================
932
+ # Part 2: Core Attention (Custom Op)
933
+ # ============================================================
934
+ # Note: we should not use torch.empty here like other attention backends,
935
+ # see discussions in https://github.com/vllm-project/vllm/pull/28182
936
+ core_attn_out = torch.zeros(
937
+ (num_tokens, self.num_v_heads // self.tp_size, self.head_v_dim),
938
+ dtype=hidden_states.dtype,
939
+ device=hidden_states.device,
940
+ )
941
+
942
+ torch.ops.vllm.qwen_gdn_attention_core(
943
+ mixed_qkv,
944
+ b.contiguous(),
945
+ a.contiguous(),
946
+ core_attn_out,
947
+ layer_name=_encode_layer_name(self.prefix),
948
+ )
949
+
950
+ # ============================================================
951
+ # Part 3: Output Projection
952
+ # ============================================================
953
+ return self._output_projection(core_attn_out, z)
954
+
955
+ def forward_xpu(
956
+ self,
957
+ hidden_states: torch.Tensor,
958
+ ) -> torch.Tensor:
959
+ """
960
+ Forward pass with three parts:
961
+ 1. Input projection
962
+ 2. Core attention (custom op)
963
+ 3. Output projection
964
+ """
965
+ num_tokens = hidden_states.size(0)
966
+
967
+ # ============================================================
968
+ # Part 1: Input Projection
969
+ # ============================================================
970
+ projected_states_qkvz, _ = self.in_proj_qkvz(hidden_states)
971
+ projected_states_ba, _ = self.in_proj_ba(hidden_states)
972
+
973
+ # ============================================================
974
+ # Part 2: Core Attention
975
+ # ============================================================
976
+ core_attn_out = torch.zeros(
977
+ (num_tokens, self.num_v_heads // self.tp_size, self.head_v_dim),
978
+ dtype=hidden_states.dtype,
979
+ device=hidden_states.device,
980
+ )
981
+ z = torch.empty_like(core_attn_out)
982
+
983
+ torch.ops.vllm.gdn_attention_core_xpu(
984
+ core_attn_out,
985
+ z,
986
+ projected_states_qkvz,
987
+ projected_states_ba,
988
+ self.prefix,
989
+ )
990
+
991
+ # ============================================================
992
+ # Part 3: Output Projection
993
+ # ============================================================
994
+ z_shape_og = z.shape
995
+ # Reshape input data into 2D tensor
996
+ core_attn_out = core_attn_out.reshape(-1, core_attn_out.shape[-1])
997
+ z = z.reshape(-1, z.shape[-1])
998
+ core_attn_out = self.norm(core_attn_out, z)
999
+ core_attn_out = core_attn_out.reshape(z_shape_og)
1000
+ core_attn_out = core_attn_out.flatten(-2) # ... h d -> ... (h d)
1001
+ out, _ = self.out_proj(core_attn_out)
1002
+ return out
1003
+
1004
+ def forward_cpu(
1005
+ self,
1006
+ hidden_states: torch.Tensor,
1007
+ ) -> torch.Tensor:
1008
+ assert not hasattr(self, "in_proj_qkv"), "lora isn't supported on CPU."
1009
+
1010
+ mixed_qkvz, _ = self.in_proj_qkvz(hidden_states)
1011
+ ba, _ = self.in_proj_ba(hidden_states)
1012
+
1013
+ if self.gqa_interleaved_layout:
1014
+ # Qwen3-Next: unpack the interleaved GQA layout
1015
+ query, key, value, z, b, a = self.fix_query_key_value_ordering(
1016
+ mixed_qkvz, ba
1017
+ )
1018
+ query, key, value = map(
1019
+ lambda x: rearrange(x, "l p d -> l (p d)"), (query, key, value)
1020
+ )
1021
+ mixed_qkv = torch.cat((query, key, value), dim=-1)
1022
+ else:
1023
+ # Qwen3.5: weights are already in [q, k, v, z] and [b, a] order
1024
+ qkv_size = (self.key_dim * 2 + self.value_dim) // self.tp_size
1025
+ z_size = self.value_dim // self.tp_size
1026
+ mixed_qkv, z = mixed_qkvz.split([qkv_size, z_size], dim=-1)
1027
+ z = z.reshape(z.size(0), -1, self.head_v_dim)
1028
+ b, a = ba.chunk(2, dim=-1)
1029
+
1030
+ num_tokens = hidden_states.size(0)
1031
+ core_attn_out = torch.zeros(
1032
+ (num_tokens, self.num_v_heads // self.tp_size, self.head_v_dim),
1033
+ dtype=hidden_states.dtype,
1034
+ device=hidden_states.device,
1035
+ )
1036
+
1037
+ torch.ops.vllm.cpu_gdn_attention_core(
1038
+ mixed_qkv,
1039
+ b,
1040
+ a,
1041
+ core_attn_out,
1042
+ _encode_layer_name(self.prefix),
1043
+ )
1044
+
1045
+ z_shape_og = z.shape
1046
+ core_attn_out = core_attn_out.reshape(-1, core_attn_out.shape[-1])
1047
+ z = z.reshape(-1, z.shape[-1])
1048
+ core_attn_out = self.norm(core_attn_out, z)
1049
+ core_attn_out = core_attn_out.reshape(z_shape_og)
1050
+ core_attn_out = core_attn_out.flatten(-2) # ... h d -> ... (h d)
1051
+ out, _ = self.out_proj(core_attn_out)
1052
+ return out
1053
+
1054
+ def _warmup_prefill_kernels(self, qkv_or_qkvz: torch.Tensor, v_dim: int) -> None:
1055
+ """Warm up GDN prefill kernels during V1 profiling.
1056
+
1057
+ During V1 profile runs, ``_forward_core`` returns early because
1058
+ ``attn_metadata`` is ``None``, so the autotuned kernels used by
1059
+ ``chunk_gated_delta_rule`` (e.g. ``solve_tril``,
1060
+ ``chunk_scaled_dot_kkt``) are never invoked. After profiling,
1061
+ vLLM allocates KV cache using most of the remaining GPU memory.
1062
+ When the first real inference triggers the autotuner it OOMs
1063
+ because there is not enough memory left for benchmarking.
1064
+
1065
+ This method runs minimal forward passes through
1066
+ ``chunk_gated_delta_rule`` with small dummy tensors to force
1067
+ autotuning while GPU memory is still plentiful. The autotuner
1068
+ results are cached globally, so only the first layer incurs
1069
+ actual benchmarking cost.
1070
+
1071
+ All kernels including ``chunk_fwd_kernel_o`` now use a fixed
1072
+ ``BT = chunk_size`` (64). A single warmup pass with T = 64
1073
+ is sufficient to populate the autotuner cache.
1074
+
1075
+ The decode path uses ``gdn_aiter_fused_rearrange_sigmoid_gated_delta_rule``
1076
+ which has fixed kernel parameters (no autotuning), so only the
1077
+ prefill (chunked) path needs warming up.
1078
+ """
1079
+ if self._prefill_kernels_warmed_up:
1080
+ return
1081
+ self._prefill_kernels_warmed_up = True
1082
+
1083
+ device = qkv_or_qkvz.device
1084
+ dtype = qkv_or_qkvz.dtype
1085
+ num_k_heads = self.num_k_heads // self.tp_size
1086
+ num_v_heads = self.num_v_heads // self.tp_size
1087
+ _, state_dtype = self.get_state_dtype()
1088
+
1089
+ # All kernels use BT = chunk_size, so a single pass with T = chunk_size
1090
+ # is sufficient to populate every autotuner cache. Mirror the real
1091
+ # prefill path here: build q/k/v/g/beta via fused_post_conv_prep and
1092
+ # then run chunk_gated_delta_rule with in-kernel L2 norm disabled.
1093
+ T = FLA_CHUNK_SIZE
1094
+ dummy_mixed_qkv = torch.randn(
1095
+ T, qkv_or_qkvz.shape[-1] - v_dim, device=device, dtype=dtype
1096
+ )
1097
+ dummy_a = torch.randn(T, num_v_heads, device=device, dtype=dtype)
1098
+ dummy_b = torch.randn(T, num_v_heads, device=device, dtype=dtype)
1099
+ q, k, v, g, beta = fused_post_conv_prep(
1100
+ conv_output=dummy_mixed_qkv,
1101
+ a=dummy_a,
1102
+ b=dummy_b,
1103
+ A_log=self.A_log,
1104
+ dt_bias=self.dt_bias,
1105
+ num_k_heads=num_k_heads,
1106
+ head_k_dim=self.head_k_dim,
1107
+ head_v_dim=self.head_v_dim,
1108
+ apply_l2norm=True,
1109
+ output_g_exp=False,
1110
+ )
1111
+ q = q.unsqueeze(0)
1112
+ k = k.unsqueeze(0)
1113
+ v = v.unsqueeze(0)
1114
+ g = g.unsqueeze(0)
1115
+ beta = beta.unsqueeze(0)
1116
+ state = torch.zeros(
1117
+ 1,
1118
+ num_v_heads,
1119
+ self.head_v_dim,
1120
+ self.head_k_dim,
1121
+ device=device,
1122
+ dtype=state_dtype,
1123
+ )
1124
+ cu_seqlens = torch.tensor([0, T], device=device, dtype=torch.int32)
1125
+
1126
+ # CuteDSL kernels require metadata
1127
+ chunk_indices = None
1128
+ chunk_offsets = None
1129
+ if self.gdn_prefill_backend == "cutedsl":
1130
+ from vllm.model_executor.layers.mamba.ops.gdn_chunk_cutedsl import (
1131
+ prepare_metadata_cutedsl,
1132
+ )
1133
+
1134
+ chunk_indices, chunk_offsets = prepare_metadata_cutedsl(cu_seqlens, T)
1135
+
1136
+ try:
1137
+ self.chunk_gated_delta_rule(
1138
+ q=q,
1139
+ k=k,
1140
+ v=v,
1141
+ g=g,
1142
+ beta=beta,
1143
+ initial_state=state,
1144
+ output_final_state=True,
1145
+ cu_seqlens=cu_seqlens,
1146
+ chunk_indices=chunk_indices,
1147
+ chunk_offsets=chunk_offsets,
1148
+ use_qk_l2norm_in_kernel=False,
1149
+ )
1150
+ except Exception:
1151
+ logger.warning(
1152
+ "GDN prefill kernel warmup (T=%d) failed for "
1153
+ "layer %s. First inference may OOM due to "
1154
+ "autotuner.",
1155
+ T,
1156
+ self.prefix,
1157
+ exc_info=True,
1158
+ )
1159
+ else:
1160
+ logger.debug(
1161
+ "GDN prefill kernel warmup (T=%d) completed for layer %s",
1162
+ T,
1163
+ self.prefix,
1164
+ )
1165
+ finally:
1166
+ del (
1167
+ dummy_mixed_qkv,
1168
+ q,
1169
+ k,
1170
+ v,
1171
+ dummy_a,
1172
+ dummy_b,
1173
+ g,
1174
+ beta,
1175
+ state,
1176
+ cu_seqlens,
1177
+ chunk_indices,
1178
+ chunk_offsets,
1179
+ )
1180
+
1181
+ torch.accelerator.empty_cache()
1182
+
1183
+ def _forward_core_rocm(
1184
+ self,
1185
+ qkvz: torch.Tensor,
1186
+ ba: torch.Tensor,
1187
+ z_out: torch.Tensor,
1188
+ core_attn_out: torch.Tensor,
1189
+ ):
1190
+ """ROCm AITER fast path: conv1d + recurrent attention from packed
1191
+ qkvz/ba layout.
1192
+
1193
+ For decode-only (no spec, no prefill) interleaved-GQA layouts,
1194
+ dispatches directly to ``_forward_core_decode_aiter`` unless recovery
1195
+ from a previous speculative step is required. Otherwise unpacks the
1196
+ packed layout and falls through to ``_forward_core`` for state recovery.
1197
+
1198
+ Args:
1199
+ qkvz: packed [q, k, v, z] projection (num_tokens, qkvz_dim)
1200
+ ba: packed [b, a] gating vectors (num_tokens, 2*num_heads)
1201
+ z_out: **output** buffer for z (num_tokens, num_heads,
1202
+ head_dim); mutated in-place.
1203
+ core_attn_out: Pre-allocated output buffer for attention results.
1204
+ """
1205
+ forward_context = get_forward_context()
1206
+ attn_metadata_raw = forward_context.attn_metadata
1207
+
1208
+ attn_metadata = None
1209
+ if isinstance(attn_metadata_raw, dict):
1210
+ attn_metadata = attn_metadata_raw.get(self.prefix)
1211
+ if attn_metadata is None:
1212
+ v_dim = core_attn_out.shape[-1] * core_attn_out.shape[-2]
1213
+ self._warmup_prefill_kernels(qkvz, v_dim)
1214
+ return
1215
+
1216
+ assert isinstance(attn_metadata, GDNAttentionMetadata)
1217
+
1218
+ # The AITER fused reshape/conv kernel expects Qwen3-Next's interleaved
1219
+ # GQA layout. Qwen3.5 uses a non-interleaved q/k/v/z layout and must use
1220
+ # the generic path below to split/rearrange inputs correctly.
1221
+ if (
1222
+ self.gqa_interleaved_layout
1223
+ and attn_metadata.spec_sequence_masks is None
1224
+ and attn_metadata.spec_decode_src_indices is None
1225
+ and attn_metadata.num_prefills == 0
1226
+ and attn_metadata.num_decodes > 0
1227
+ ):
1228
+ return self._forward_core_decode_aiter(
1229
+ qkvz=qkvz,
1230
+ ba=ba,
1231
+ z_out=z_out,
1232
+ core_attn_out=core_attn_out,
1233
+ attn_metadata=attn_metadata,
1234
+ )
1235
+
1236
+ core_attn_out.zero_()
1237
+ num_tokens_all = qkvz.shape[0]
1238
+ mixed_qkv, z, b, a = self.prepare_gdn_attention_core_inputs(
1239
+ qkvz, ba, num_tokens_all
1240
+ )
1241
+ z_out[:] = z
1242
+ self._forward_core(
1243
+ mixed_qkv=mixed_qkv,
1244
+ b=b,
1245
+ a=a,
1246
+ core_attn_out=core_attn_out,
1247
+ )
1248
+
1249
+ def _forward_core(
1250
+ self,
1251
+ mixed_qkv: torch.Tensor,
1252
+ b: torch.Tensor,
1253
+ a: torch.Tensor,
1254
+ core_attn_out: torch.Tensor,
1255
+ ):
1256
+ """Core conv1d + recurrent attention (standard path).
1257
+
1258
+ Args:
1259
+ mixed_qkv: packed [q, k, v] projection (num_tokens, qkv_dim)
1260
+ b: beta gating vector (num_tokens, num_heads)
1261
+ a: alpha gating vector (num_tokens, num_heads)
1262
+ core_attn_out: Pre-allocated output buffer for attention results.
1263
+ """
1264
+ forward_context = get_forward_context()
1265
+ attn_metadata_raw = forward_context.attn_metadata
1266
+
1267
+ attn_metadata = None
1268
+ if isinstance(attn_metadata_raw, dict):
1269
+ attn_metadata = attn_metadata_raw.get(self.prefix)
1270
+ if attn_metadata is None:
1271
+ self._warmup_prefill_kernels(mixed_qkv, 0)
1272
+ return
1273
+
1274
+ assert isinstance(attn_metadata, GDNAttentionMetadata)
1275
+
1276
+ if (
1277
+ self.enable_packed_recurrent_decode
1278
+ and attn_metadata.spec_sequence_masks is None
1279
+ and attn_metadata.num_prefills == 0
1280
+ and attn_metadata.num_decodes > 0
1281
+ ):
1282
+ return self._forward_core_decode_non_spec(
1283
+ mixed_qkv=mixed_qkv,
1284
+ b=b,
1285
+ a=a,
1286
+ core_attn_out=core_attn_out,
1287
+ attn_metadata=attn_metadata,
1288
+ )
1289
+
1290
+ has_initial_state = attn_metadata.has_initial_state
1291
+ spec_query_start_loc = attn_metadata.spec_query_start_loc
1292
+ non_spec_query_start_loc = attn_metadata.non_spec_query_start_loc
1293
+ spec_sequence_masks = attn_metadata.spec_sequence_masks
1294
+ spec_token_indx = attn_metadata.spec_token_indx
1295
+ non_spec_token_indx = attn_metadata.non_spec_token_indx
1296
+ spec_state_indices_tensor = attn_metadata.spec_state_indices_tensor # noqa: E501
1297
+ non_spec_state_indices_tensor = attn_metadata.non_spec_state_indices_tensor # noqa: E501
1298
+ self_kv_cache = self.kv_cache
1299
+ # conv_state must be (..., dim, width-1) for the conv kernels.
1300
+ # DS layout stores it that way directly; SD layout needs a transpose.
1301
+ conv_state = (
1302
+ self_kv_cache[0]
1303
+ if is_conv_state_dim_first()
1304
+ else self_kv_cache[0].transpose(-1, -2)
1305
+ )
1306
+ ssm_state = self_kv_cache[1]
1307
+ num_actual_tokens = attn_metadata.num_actual_tokens
1308
+ num_accepted_tokens = attn_metadata.num_accepted_tokens
1309
+ spec_decode_src_indices = attn_metadata.spec_decode_src_indices
1310
+
1311
+ if spec_decode_src_indices is not None:
1312
+ assert non_spec_state_indices_tensor is not None
1313
+ num_corrected_states = spec_decode_src_indices.shape[0]
1314
+ destination_indices = non_spec_state_indices_tensor[:num_corrected_states]
1315
+ ssm_state[destination_indices] = ssm_state[spec_decode_src_indices]
1316
+
1317
+ mixed_qkv = mixed_qkv[:num_actual_tokens]
1318
+ b = b[:num_actual_tokens]
1319
+ a = a[:num_actual_tokens]
1320
+
1321
+ # 1. Convolution sequence transformation
1322
+ conv_weights = self.conv1d.weight.view(
1323
+ self.conv1d.weight.size(0), self.conv1d.weight.size(2)
1324
+ )
1325
+
1326
+ if spec_sequence_masks is not None:
1327
+ if attn_metadata.num_prefills == 0 and attn_metadata.num_decodes == 0:
1328
+ mixed_qkv_spec = mixed_qkv
1329
+ a_spec = a
1330
+ b_spec = b
1331
+ mixed_qkv_non_spec = None
1332
+ else:
1333
+ mixed_qkv_spec = mixed_qkv.index_select(0, spec_token_indx)
1334
+ a_spec = a.index_select(0, spec_token_indx)
1335
+ b_spec = b.index_select(0, spec_token_indx)
1336
+ mixed_qkv_non_spec = mixed_qkv.index_select(0, non_spec_token_indx)
1337
+ else:
1338
+ mixed_qkv_spec = None
1339
+ mixed_qkv_non_spec = mixed_qkv
1340
+
1341
+ # 1.1: Process the multi-query part
1342
+ if spec_sequence_masks is not None:
1343
+ # spec_state_indices_tensor is always set when spec_sequence_masks is set
1344
+ assert spec_state_indices_tensor is not None
1345
+ mixed_qkv_spec = causal_conv1d_update(
1346
+ mixed_qkv_spec,
1347
+ conv_state,
1348
+ conv_weights,
1349
+ self.conv1d.bias,
1350
+ self.activation,
1351
+ conv_state_indices=spec_state_indices_tensor[:, 0][ # type: ignore[index]
1352
+ : attn_metadata.num_spec_decodes # type: ignore[attr-defined]
1353
+ ],
1354
+ num_accepted_tokens=num_accepted_tokens,
1355
+ query_start_loc=spec_query_start_loc,
1356
+ max_query_len=spec_state_indices_tensor.size(-1),
1357
+ validate_data=False,
1358
+ )
1359
+
1360
+ # 1.2: Process the remaining part
1361
+ if attn_metadata.num_prefills > 0:
1362
+ assert mixed_qkv_non_spec is not None
1363
+ mixed_qkv_non_spec_T = mixed_qkv_non_spec.transpose(0, 1)
1364
+ conv_num_accepted = (
1365
+ attn_metadata.non_spec_num_accepted
1366
+ if attn_metadata.non_spec_num_accepted is not None
1367
+ else num_accepted_tokens
1368
+ )
1369
+ # - "cache_indices" updates the conv_state cache in positions
1370
+ # pointed to by "state_indices_tensor"
1371
+ mixed_qkv_non_spec = causal_conv1d_fn(
1372
+ mixed_qkv_non_spec_T,
1373
+ conv_weights,
1374
+ self.conv1d.bias,
1375
+ activation=self.activation,
1376
+ conv_states=conv_state,
1377
+ has_initial_state=has_initial_state,
1378
+ cache_indices=non_spec_state_indices_tensor,
1379
+ query_start_loc=non_spec_query_start_loc,
1380
+ num_accepted_tokens=conv_num_accepted,
1381
+ metadata=attn_metadata,
1382
+ ).transpose(0, 1)
1383
+ elif attn_metadata.num_decodes > 0:
1384
+ assert mixed_qkv_non_spec is not None
1385
+ conv_num_accepted = (
1386
+ num_accepted_tokens if spec_decode_src_indices is not None else None
1387
+ )
1388
+ mixed_qkv_non_spec = causal_conv1d_update(
1389
+ mixed_qkv_non_spec,
1390
+ conv_state,
1391
+ conv_weights,
1392
+ self.conv1d.bias,
1393
+ self.activation,
1394
+ conv_state_indices=non_spec_state_indices_tensor[ # type: ignore[index]
1395
+ : attn_metadata.num_actual_tokens # type: ignore[attr-defined]
1396
+ ],
1397
+ num_accepted_tokens=conv_num_accepted,
1398
+ validate_data=True,
1399
+ )
1400
+ else:
1401
+ mixed_qkv_non_spec = None
1402
+
1403
+ query_spec, key_spec, value_spec = self.rearrange_mixed_qkv(mixed_qkv_spec)
1404
+
1405
+ # Split mixed non-spec-decode+prefill to process independently
1406
+ split_non_spec = (
1407
+ spec_sequence_masks is None
1408
+ and attn_metadata.num_prefills > 0
1409
+ and attn_metadata.num_decodes > 0
1410
+ )
1411
+ num_decode_tokens = attn_metadata.num_decode_tokens
1412
+
1413
+ if attn_metadata.num_prefills > 0:
1414
+ assert mixed_qkv_non_spec is not None, (
1415
+ "mixed_qkv_non_spec must be provided for prefill path"
1416
+ )
1417
+ if spec_sequence_masks is not None:
1418
+ a_non_spec = a.index_select(0, non_spec_token_indx)
1419
+ b_non_spec = b.index_select(0, non_spec_token_indx)
1420
+ else:
1421
+ a_non_spec = a
1422
+ b_non_spec = b
1423
+
1424
+ if split_non_spec:
1425
+ conv_output_prefill = mixed_qkv_non_spec[num_decode_tokens:]
1426
+ a_prefill = a_non_spec[num_decode_tokens:]
1427
+ b_prefill = b_non_spec[num_decode_tokens:]
1428
+ else:
1429
+ conv_output_prefill = mixed_qkv_non_spec
1430
+ a_prefill = a_non_spec
1431
+ b_prefill = b_non_spec
1432
+
1433
+ (
1434
+ query_non_spec,
1435
+ key_non_spec,
1436
+ value_non_spec,
1437
+ g_non_spec,
1438
+ beta_non_spec,
1439
+ ) = fused_post_conv_prep(
1440
+ conv_output=conv_output_prefill,
1441
+ a=a_prefill,
1442
+ b=b_prefill,
1443
+ A_log=self.A_log,
1444
+ dt_bias=self.dt_bias,
1445
+ num_k_heads=self.num_k_heads // self.tp_size,
1446
+ head_k_dim=self.head_k_dim,
1447
+ head_v_dim=self.head_v_dim,
1448
+ apply_l2norm=True,
1449
+ output_g_exp=False,
1450
+ )
1451
+ query_non_spec = query_non_spec.unsqueeze(0)
1452
+ key_non_spec = key_non_spec.unsqueeze(0)
1453
+ value_non_spec = value_non_spec.unsqueeze(0)
1454
+ g_non_spec = g_non_spec.unsqueeze(0)
1455
+ beta_non_spec = beta_non_spec.unsqueeze(0)
1456
+ else:
1457
+ query_non_spec, key_non_spec, value_non_spec = self.rearrange_mixed_qkv(
1458
+ mixed_qkv_non_spec
1459
+ )
1460
+ g_non_spec = None
1461
+ beta_non_spec = None
1462
+
1463
+ # 2. Recurrent attention
1464
+
1465
+ # 2.1: Process the multi-query part
1466
+ if spec_sequence_masks is not None:
1467
+ core_attn_out_spec, last_recurrent_state = (
1468
+ fused_sigmoid_gating_delta_rule_update(
1469
+ A_log=self.A_log,
1470
+ a=a_spec,
1471
+ b=b_spec,
1472
+ dt_bias=self.dt_bias,
1473
+ q=query_spec,
1474
+ k=key_spec,
1475
+ v=value_spec,
1476
+ initial_state=ssm_state,
1477
+ inplace_final_state=True,
1478
+ cu_seqlens=spec_query_start_loc[ # type: ignore[index]
1479
+ : attn_metadata.num_spec_decodes
1480
+ + 1 # type: ignore[attr-defined]
1481
+ ],
1482
+ ssm_state_indices=spec_state_indices_tensor,
1483
+ num_accepted_tokens=num_accepted_tokens,
1484
+ use_qk_l2norm_in_kernel=True,
1485
+ )
1486
+ )
1487
+ else:
1488
+ core_attn_out_spec, last_recurrent_state = None, None
1489
+
1490
+ # 2.2: Process non-spec-decode part
1491
+ if split_non_spec:
1492
+ query_decode, key_decode, value_decode = self.rearrange_mixed_qkv(
1493
+ mixed_qkv_non_spec[:num_decode_tokens] # type: ignore[index]
1494
+ )
1495
+ core_attn_out_decode, _ = fused_sigmoid_gating_delta_rule_update(
1496
+ A_log=self.A_log,
1497
+ a=a[:num_decode_tokens],
1498
+ b=b[:num_decode_tokens],
1499
+ dt_bias=self.dt_bias,
1500
+ q=query_decode,
1501
+ k=key_decode,
1502
+ v=value_decode,
1503
+ initial_state=ssm_state,
1504
+ inplace_final_state=True,
1505
+ cu_seqlens=non_spec_query_start_loc[ # type: ignore[index]
1506
+ : attn_metadata.num_decodes + 1
1507
+ ],
1508
+ ssm_state_indices=non_spec_state_indices_tensor,
1509
+ use_qk_l2norm_in_kernel=True,
1510
+ )
1511
+ else:
1512
+ core_attn_out_decode = None
1513
+
1514
+ # 2.3: Process the remaining part (prefill chunk, or non-spec decode-only)
1515
+ if attn_metadata.num_prefills > 0:
1516
+ # State indices, initial-state mask and cu_seqlens for the chunk
1517
+ # kernel are precomputed by the metadata builder (the prefill tail
1518
+ # when decodes are peeled off, else the full non-spec batch), so they
1519
+ # don't need to be re-derived per layer.
1520
+ prefill_state_indices = attn_metadata.prefill_state_indices
1521
+ prefill_has_initial_state = attn_metadata.prefill_has_initial_state
1522
+ assert prefill_state_indices is not None
1523
+ assert prefill_has_initial_state is not None
1524
+ initial_state = ssm_state[prefill_state_indices]
1525
+ initial_state[~prefill_has_initial_state, ...] = 0
1526
+ (
1527
+ core_attn_out_non_spec,
1528
+ last_recurrent_state,
1529
+ ) = self.chunk_gated_delta_rule(
1530
+ q=query_non_spec,
1531
+ k=key_non_spec,
1532
+ v=value_non_spec,
1533
+ g=g_non_spec,
1534
+ beta=beta_non_spec,
1535
+ initial_state=initial_state,
1536
+ output_final_state=True,
1537
+ cu_seqlens=attn_metadata.prefill_query_start_loc,
1538
+ chunk_indices=attn_metadata.chunk_indices,
1539
+ chunk_offsets=attn_metadata.chunk_offsets,
1540
+ use_qk_l2norm_in_kernel=False,
1541
+ )
1542
+ # Init cache
1543
+ ssm_state[prefill_state_indices] = last_recurrent_state.to(ssm_state.dtype)
1544
+
1545
+ if split_non_spec:
1546
+ # Stitch the peeled decode outputs in front of the prefill
1547
+ # outputs (decode-first order).
1548
+ core_attn_out_non_spec = torch.cat(
1549
+ [core_attn_out_decode, core_attn_out_non_spec], dim=1
1550
+ )
1551
+ elif attn_metadata.num_decodes > 0:
1552
+ core_attn_out_non_spec, last_recurrent_state = (
1553
+ fused_sigmoid_gating_delta_rule_update(
1554
+ A_log=self.A_log,
1555
+ a=a,
1556
+ b=b,
1557
+ dt_bias=self.dt_bias,
1558
+ q=query_non_spec,
1559
+ k=key_non_spec,
1560
+ v=value_non_spec,
1561
+ initial_state=ssm_state,
1562
+ inplace_final_state=True,
1563
+ cu_seqlens=non_spec_query_start_loc[ # type: ignore[index]
1564
+ : attn_metadata.num_decodes
1565
+ + 1 # type: ignore[attr-defined]
1566
+ ],
1567
+ ssm_state_indices=non_spec_state_indices_tensor,
1568
+ use_qk_l2norm_in_kernel=True,
1569
+ )
1570
+ )
1571
+ else:
1572
+ core_attn_out_non_spec, last_recurrent_state = None, None
1573
+
1574
+ # 3. Merge core attention output
1575
+ if spec_sequence_masks is not None and core_attn_out_non_spec is not None:
1576
+ merged_out = torch.empty(
1577
+ (1, num_actual_tokens, *core_attn_out_spec.shape[2:]),
1578
+ dtype=core_attn_out_non_spec.dtype,
1579
+ device=core_attn_out_non_spec.device,
1580
+ )
1581
+ merged_out.index_copy_(1, spec_token_indx, core_attn_out_spec)
1582
+ merged_out.index_copy_(1, non_spec_token_indx, core_attn_out_non_spec)
1583
+ core_attn_out[:num_actual_tokens] = merged_out.squeeze(0)
1584
+ elif spec_sequence_masks is not None:
1585
+ core_attn_out[:num_actual_tokens] = core_attn_out_spec.squeeze(0)
1586
+ else:
1587
+ core_attn_out[:num_actual_tokens] = core_attn_out_non_spec.squeeze(0)
1588
+
1589
+ def _forward_core_decode_aiter(
1590
+ self,
1591
+ qkvz: torch.Tensor,
1592
+ ba: torch.Tensor,
1593
+ z_out: torch.Tensor,
1594
+ core_attn_out: torch.Tensor,
1595
+ attn_metadata: GDNAttentionMetadata,
1596
+ ):
1597
+ non_spec_query_start_loc = attn_metadata.non_spec_query_start_loc
1598
+ non_spec_state_indices_tensor = attn_metadata.non_spec_state_indices_tensor # noqa: E501
1599
+ self_kv_cache = self.kv_cache
1600
+ # conv_state must be (..., dim, width-1) for the conv kernels.
1601
+ # DS layout stores it that way directly; SD layout needs a transpose.
1602
+ conv_state = (
1603
+ self_kv_cache[0]
1604
+ if is_conv_state_dim_first()
1605
+ else self_kv_cache[0].transpose(-1, -2)
1606
+ )
1607
+ ssm_state = self_kv_cache[1]
1608
+
1609
+ # 1. Convolution sequence transformation
1610
+ conv_weights = self.conv1d.weight.view(
1611
+ self.conv1d.weight.size(0), self.conv1d.weight.size(2)
1612
+ )
1613
+
1614
+ mixed_qkv_non_spec, b, a = (
1615
+ gdn_aiter_fused_reshape_causal_conv1d_update_single_token(
1616
+ qkvz,
1617
+ attn_metadata.num_actual_tokens,
1618
+ self.num_k_heads // self.tp_size,
1619
+ self.num_v_heads // self.tp_size,
1620
+ self.head_k_dim,
1621
+ self.head_v_dim,
1622
+ ba,
1623
+ z_out,
1624
+ core_attn_out,
1625
+ conv_state,
1626
+ conv_weights,
1627
+ self.conv1d.bias,
1628
+ self.activation,
1629
+ conv_state_indices=non_spec_state_indices_tensor[ # type: ignore[index]
1630
+ : attn_metadata.num_actual_tokens
1631
+ ],
1632
+ validate_data=True,
1633
+ )
1634
+ )
1635
+
1636
+ # 2. Recurrent attention
1637
+ gdn_aiter_fused_rearrange_sigmoid_gated_delta_rule(
1638
+ A_log=self.A_log,
1639
+ a=a,
1640
+ b=b,
1641
+ dt_bias=self.dt_bias,
1642
+ qkv=mixed_qkv_non_spec,
1643
+ key_dim=self.key_dim // self.tp_size,
1644
+ value_dim=self.value_dim // self.tp_size,
1645
+ head_k_dim=self.head_k_dim,
1646
+ head_v_dim=self.head_v_dim,
1647
+ initial_state=ssm_state,
1648
+ inplace_final_state=True,
1649
+ cu_seqlens=non_spec_query_start_loc[: attn_metadata.num_decodes + 1], # type: ignore[index]
1650
+ ssm_state_indices=non_spec_state_indices_tensor,
1651
+ use_qk_l2norm_in_kernel=True,
1652
+ core_attn_out=core_attn_out.reshape(-1),
1653
+ )
1654
+
1655
+ def _forward_core_decode_non_spec(
1656
+ self,
1657
+ mixed_qkv: torch.Tensor,
1658
+ b: torch.Tensor,
1659
+ a: torch.Tensor,
1660
+ core_attn_out: torch.Tensor,
1661
+ attn_metadata: GDNAttentionMetadata,
1662
+ ):
1663
+ """
1664
+ Core attention computation with a packed non-spec decode fast path.
1665
+ """
1666
+ non_spec_state_indices_tensor = attn_metadata.non_spec_state_indices_tensor # noqa: E501
1667
+ self_kv_cache = self.kv_cache
1668
+ # conv_state must be (..., dim, width-1) for the conv kernels.
1669
+ # DS layout stores it that way directly; SD layout needs a transpose.
1670
+ conv_state = (
1671
+ self_kv_cache[0]
1672
+ if is_conv_state_dim_first()
1673
+ else self_kv_cache[0].transpose(-1, -2)
1674
+ )
1675
+ ssm_state = self_kv_cache[1]
1676
+ num_actual_tokens = attn_metadata.num_actual_tokens
1677
+ num_accepted_tokens = attn_metadata.num_accepted_tokens
1678
+ spec_decode_src_indices = attn_metadata.spec_decode_src_indices
1679
+
1680
+ if spec_decode_src_indices is not None:
1681
+ assert non_spec_state_indices_tensor is not None
1682
+ num_corrected_states = spec_decode_src_indices.shape[0]
1683
+ destination_indices = non_spec_state_indices_tensor[:num_corrected_states]
1684
+ ssm_state[destination_indices] = ssm_state[spec_decode_src_indices]
1685
+
1686
+ mixed_qkv = mixed_qkv[:num_actual_tokens]
1687
+ b = b[:num_actual_tokens]
1688
+ a = a[:num_actual_tokens]
1689
+
1690
+ conv_weights = self.conv1d.weight.view(
1691
+ self.conv1d.weight.size(0), self.conv1d.weight.size(2)
1692
+ )
1693
+ mixed_qkv_non_spec = causal_conv1d_update(
1694
+ mixed_qkv,
1695
+ conv_state,
1696
+ conv_weights,
1697
+ self.conv1d.bias,
1698
+ self.activation,
1699
+ conv_state_indices=non_spec_state_indices_tensor[:num_actual_tokens], # type: ignore[index]
1700
+ num_accepted_tokens=num_accepted_tokens,
1701
+ validate_data=False,
1702
+ )
1703
+ out_buf = core_attn_out[:num_actual_tokens].unsqueeze(1)
1704
+ fused_recurrent_gated_delta_rule_packed_decode(
1705
+ mixed_qkv=mixed_qkv_non_spec,
1706
+ a=a,
1707
+ b=b,
1708
+ A_log=self.A_log,
1709
+ dt_bias=self.dt_bias,
1710
+ scale=self.head_k_dim**-0.5,
1711
+ initial_state=ssm_state,
1712
+ out=out_buf,
1713
+ ssm_state_indices=non_spec_state_indices_tensor[:num_actual_tokens], # type: ignore[index]
1714
+ use_qk_l2norm_in_kernel=True,
1715
+ )
1716
+ return
1717
+
1718
+ def _forward_core_decode_spec_fused_norm(
1719
+ self,
1720
+ mixed_qkv: torch.Tensor,
1721
+ b: torch.Tensor,
1722
+ a: torch.Tensor,
1723
+ output_gate: torch.Tensor,
1724
+ core_attn_out: torch.Tensor,
1725
+ attn_metadata: GDNAttentionMetadata,
1726
+ ) -> None:
1727
+ state_indices = attn_metadata.spec_state_indices_tensor
1728
+ cu_seqlens = attn_metadata.spec_query_start_loc
1729
+ num_accepted_tokens = attn_metadata.num_accepted_tokens
1730
+ assert state_indices is not None
1731
+ assert cu_seqlens is not None
1732
+ assert num_accepted_tokens is not None
1733
+
1734
+ num_requests = attn_metadata.num_spec_decodes
1735
+ num_actual_tokens = attn_metadata.num_actual_tokens
1736
+ conv_state = (
1737
+ self.kv_cache[0]
1738
+ if is_conv_state_dim_first()
1739
+ else self.kv_cache[0].transpose(-1, -2)
1740
+ )
1741
+ conv_weights = self.conv1d.weight.view(
1742
+ self.conv1d.weight.size(0), self.conv1d.weight.size(2)
1743
+ )
1744
+ mixed_qkv = causal_conv1d_update(
1745
+ mixed_qkv[:num_actual_tokens],
1746
+ conv_state,
1747
+ conv_weights,
1748
+ self.conv1d.bias,
1749
+ self.activation,
1750
+ conv_state_indices=state_indices[:num_requests, 0],
1751
+ num_accepted_tokens=num_accepted_tokens[:num_requests],
1752
+ query_start_loc=cu_seqlens[: num_requests + 1],
1753
+ max_query_len=state_indices.size(1),
1754
+ validate_data=False,
1755
+ )
1756
+ self._forward_core_decode_spec_post_conv_fused_norm(
1757
+ mixed_qkv=mixed_qkv,
1758
+ b=b[:num_actual_tokens],
1759
+ a=a[:num_actual_tokens],
1760
+ output_gate=output_gate[:num_actual_tokens],
1761
+ core_attn_out=core_attn_out[:num_actual_tokens],
1762
+ attn_metadata=attn_metadata,
1763
+ )
1764
+
1765
+ def _forward_core_decode_spec_post_conv_fused_norm(
1766
+ self,
1767
+ mixed_qkv: torch.Tensor,
1768
+ b: torch.Tensor,
1769
+ a: torch.Tensor,
1770
+ output_gate: torch.Tensor,
1771
+ core_attn_out: torch.Tensor,
1772
+ attn_metadata: GDNAttentionMetadata,
1773
+ ) -> None:
1774
+ state_indices = attn_metadata.spec_state_indices_tensor
1775
+ cu_seqlens = attn_metadata.spec_query_start_loc
1776
+ num_accepted_tokens = attn_metadata.num_accepted_tokens
1777
+ assert state_indices is not None
1778
+ assert cu_seqlens is not None
1779
+ assert num_accepted_tokens is not None
1780
+
1781
+ num_requests = attn_metadata.num_spec_decodes
1782
+ ops.fused_gdn_decode_post_conv_mtp(
1783
+ mixed_qkv=mixed_qkv,
1784
+ a=a,
1785
+ b=b,
1786
+ A_log=self.A_log,
1787
+ dt_bias=self.dt_bias,
1788
+ state_indices=state_indices[:num_requests],
1789
+ cu_seqlens=cu_seqlens[: num_requests + 1],
1790
+ num_accepted_tokens=num_accepted_tokens[:num_requests],
1791
+ state=self.kv_cache[1],
1792
+ output_gate=output_gate,
1793
+ norm_weight=self.norm.weight,
1794
+ out=core_attn_out,
1795
+ scale=self.head_k_dim**-0.5,
1796
+ norm_eps=self.layer_norm_epsilon,
1797
+ )
1798
+
1799
+ def _forward_core_fused_norm_packed(
1800
+ self,
1801
+ mixed_qkvz: torch.Tensor,
1802
+ ba: torch.Tensor,
1803
+ core_attn_out: torch.Tensor,
1804
+ ) -> None:
1805
+ forward_context = get_forward_context()
1806
+ attn_metadata_raw = forward_context.attn_metadata
1807
+ qkv_size = (self.key_dim * 2 + self.value_dim) // self.tp_size
1808
+ attn_metadata = None
1809
+ if isinstance(attn_metadata_raw, dict):
1810
+ attn_metadata = attn_metadata_raw.get(self.prefix)
1811
+ if attn_metadata is None:
1812
+ self._warmup_prefill_kernels(mixed_qkvz[:, :qkv_size], 0)
1813
+ return
1814
+
1815
+ assert isinstance(attn_metadata, GDNAttentionMetadata)
1816
+ mixed_qkv, output_gate_flat = mixed_qkvz.split(
1817
+ [qkv_size, self.value_dim // self.tp_size], dim=-1
1818
+ )
1819
+ output_gate = output_gate_flat.reshape(
1820
+ output_gate_flat.size(0), -1, self.head_v_dim
1821
+ )
1822
+ b, a = self.split_ba(ba)
1823
+ self._forward_core_fused_norm(
1824
+ mixed_qkv=mixed_qkv,
1825
+ b=b,
1826
+ a=a,
1827
+ output_gate=output_gate,
1828
+ core_attn_out=core_attn_out,
1829
+ )
1830
+
1831
+ def _can_use_fused_gdn_mtp_decode(
1832
+ self, attn_metadata: GDNAttentionMetadata
1833
+ ) -> bool:
1834
+ state_indices = attn_metadata.spec_state_indices_tensor
1835
+ return (
1836
+ attn_metadata.spec_sequence_masks is not None
1837
+ and attn_metadata.num_decodes == 0
1838
+ and attn_metadata.num_spec_decodes > 0
1839
+ and self.kv_cache[1].dtype in FUSED_GDN_STATE_DTYPES
1840
+ and self.gdn_decode_kernel == "cuda"
1841
+ and self.num_v_heads % self.num_k_heads == 0
1842
+ and self.num_v_heads // self.num_k_heads in (1, 2, 3, 4, 8)
1843
+ and state_indices is not None
1844
+ and state_indices.size(1) <= MAX_FUSED_GDN_MTP_TOKENS
1845
+ and hasattr(torch.ops._C, "fused_gdn_decode_post_conv_mtp")
1846
+ )
1847
+
1848
+ def _rms_norm_gated_cuda(
1849
+ self,
1850
+ x: torch.Tensor,
1851
+ output_gate: torch.Tensor,
1852
+ out: torch.Tensor,
1853
+ ) -> None:
1854
+ from vllm.third_party.flash_linear_attention.ops.layernorm_guard import (
1855
+ layer_norm_fwd,
1856
+ )
1857
+
1858
+ x_shape = x.shape
1859
+ assert output_gate.shape == x_shape
1860
+ assert out.shape == x_shape
1861
+ x_2d = x.reshape(-1, x_shape[-1])
1862
+ output_gate_2d = output_gate.reshape(-1, x_shape[-1])
1863
+ out_2d = out.reshape(-1, x_shape[-1])
1864
+ assert x_2d.stride(-1) == 1
1865
+ assert output_gate_2d.stride(-1) == 1
1866
+ assert out_2d.stride(-1) == 1
1867
+ layer_norm_fwd(
1868
+ x_2d,
1869
+ self.norm.weight.contiguous(),
1870
+ self.norm.bias,
1871
+ self.norm.eps,
1872
+ z=output_gate_2d,
1873
+ out=out_2d,
1874
+ group_size=(
1875
+ x_shape[-1] if self.norm.group_size is None else self.norm.group_size
1876
+ ),
1877
+ norm_before_gate=self.norm.norm_before_gate,
1878
+ is_rms_norm=True,
1879
+ activation=self.norm.activation,
1880
+ )
1881
+
1882
+ def _forward_core_fused_norm(
1883
+ self,
1884
+ mixed_qkv: torch.Tensor,
1885
+ b: torch.Tensor,
1886
+ a: torch.Tensor,
1887
+ output_gate: torch.Tensor,
1888
+ core_attn_out: torch.Tensor,
1889
+ ) -> None:
1890
+ forward_context = get_forward_context()
1891
+ attn_metadata_raw = forward_context.attn_metadata
1892
+ attn_metadata = None
1893
+ if isinstance(attn_metadata_raw, dict):
1894
+ attn_metadata = attn_metadata_raw.get(self.prefix)
1895
+ if attn_metadata is None:
1896
+ self._warmup_prefill_kernels(mixed_qkv, 0)
1897
+ return
1898
+
1899
+ assert isinstance(attn_metadata, GDNAttentionMetadata)
1900
+ if (
1901
+ self._can_use_fused_gdn_mtp_decode(attn_metadata)
1902
+ and attn_metadata.num_prefills == 0
1903
+ ):
1904
+ self._forward_core_decode_spec_fused_norm(
1905
+ mixed_qkv=mixed_qkv,
1906
+ b=b,
1907
+ a=a,
1908
+ output_gate=output_gate,
1909
+ core_attn_out=core_attn_out,
1910
+ attn_metadata=attn_metadata,
1911
+ )
1912
+ return
1913
+ self._forward_core(
1914
+ mixed_qkv=mixed_qkv,
1915
+ b=b.contiguous(),
1916
+ a=a.contiguous(),
1917
+ core_attn_out=core_attn_out,
1918
+ )
1919
+ num_actual_tokens = attn_metadata.num_actual_tokens
1920
+ self._rms_norm_gated_cuda(
1921
+ core_attn_out[:num_actual_tokens],
1922
+ output_gate[:num_actual_tokens],
1923
+ core_attn_out[:num_actual_tokens],
1924
+ )
1925
+
1926
+
1927
+ def qwen_gdn_attention_core(
1928
+ qkv_or_qkvz: torch.Tensor,
1929
+ b_or_ba: torch.Tensor,
1930
+ a_or_z_out: torch.Tensor,
1931
+ core_attn_out: torch.Tensor,
1932
+ layer_name: LayerNameType,
1933
+ use_aiter: bool = False,
1934
+ ) -> None:
1935
+ """Custom op dispatching to _forward_core or _forward_core_rocm.
1936
+
1937
+ Handles conv1d + recurrent attention only; input/output projections
1938
+ are performed by the caller.
1939
+
1940
+ When ``use_aiter=False`` (standard path):
1941
+ qkv_or_qkvz is [q, k, v], b_or_ba is b, a_or_z_out is a (read-only).
1942
+ When ``use_aiter=True`` (AITER Triton path, ROCm only):
1943
+ qkv_or_qkvz is [q, k, v, z], b_or_ba is [b, a], a_or_z_out is the
1944
+ z output buffer (mutated in-place).
1945
+
1946
+ ``core_attn_out`` is always mutated in-place.
1947
+ """
1948
+ layer_name = _resolve_layer_name(layer_name)
1949
+ forward_context: ForwardContext = get_forward_context()
1950
+ self = forward_context.no_compile_layers[layer_name]
1951
+ if use_aiter:
1952
+ self._forward_core_rocm(
1953
+ qkvz=qkv_or_qkvz,
1954
+ ba=b_or_ba,
1955
+ z_out=a_or_z_out,
1956
+ core_attn_out=core_attn_out,
1957
+ )
1958
+ else:
1959
+ self._forward_core(
1960
+ mixed_qkv=qkv_or_qkvz,
1961
+ b=b_or_ba,
1962
+ a=a_or_z_out,
1963
+ core_attn_out=core_attn_out,
1964
+ )
1965
+
1966
+
1967
+ def gdn_attention_core_fake(
1968
+ qkv_or_qkvz: torch.Tensor,
1969
+ b_or_ba: torch.Tensor,
1970
+ a_or_z_out: torch.Tensor,
1971
+ core_attn_out: torch.Tensor,
1972
+ layer_name: LayerNameType,
1973
+ use_aiter: bool = False,
1974
+ ) -> None:
1975
+ """Fake implementation for torch.compile."""
1976
+ return
1977
+
1978
+
1979
+ direct_register_custom_op(
1980
+ op_name="qwen_gdn_attention_core",
1981
+ op_func=qwen_gdn_attention_core,
1982
+ mutates_args=["a_or_z_out", "core_attn_out"],
1983
+ fake_impl=gdn_attention_core_fake,
1984
+ )
1985
+
1986
+
1987
+ def qwen_gdn_attention_core_fused_norm_packed(
1988
+ mixed_qkvz: torch.Tensor,
1989
+ ba: torch.Tensor,
1990
+ core_attn_out: torch.Tensor,
1991
+ layer_name: LayerNameType,
1992
+ ) -> None:
1993
+ layer_name = _resolve_layer_name(layer_name)
1994
+ forward_context: ForwardContext = get_forward_context()
1995
+ self = forward_context.no_compile_layers[layer_name]
1996
+ self._forward_core_fused_norm_packed(
1997
+ mixed_qkvz=mixed_qkvz,
1998
+ ba=ba,
1999
+ core_attn_out=core_attn_out,
2000
+ )
2001
+
2002
+
2003
+ def gdn_attention_core_fused_norm_packed_fake(
2004
+ mixed_qkvz: torch.Tensor,
2005
+ ba: torch.Tensor,
2006
+ core_attn_out: torch.Tensor,
2007
+ layer_name: LayerNameType,
2008
+ ) -> None:
2009
+ return
2010
+
2011
+
2012
+ direct_register_custom_op(
2013
+ op_name="qwen_gdn_attention_core_fused_norm_packed",
2014
+ op_func=qwen_gdn_attention_core_fused_norm_packed,
2015
+ mutates_args=["core_attn_out"],
2016
+ fake_impl=gdn_attention_core_fused_norm_packed_fake,
2017
+ )
2018
+
2019
+
2020
+ @triton.jit
2021
+ def fused_gdn_gating_kernel(
2022
+ g,
2023
+ beta_output,
2024
+ A_log,
2025
+ a,
2026
+ b,
2027
+ dt_bias,
2028
+ seq_len,
2029
+ NUM_HEADS: tl.constexpr,
2030
+ beta: tl.constexpr,
2031
+ threshold: tl.constexpr,
2032
+ BLK_HEADS: tl.constexpr,
2033
+ ):
2034
+ i_b, i_s, i_d = tl.program_id(0), tl.program_id(1), tl.program_id(2)
2035
+ head_off = i_d * BLK_HEADS + tl.arange(0, BLK_HEADS)
2036
+ off = i_b * seq_len * NUM_HEADS + i_s * NUM_HEADS + head_off
2037
+ mask = head_off < NUM_HEADS
2038
+ blk_A_log = tl.load(A_log + head_off, mask=mask)
2039
+ blk_a = tl.load(a + off, mask=mask)
2040
+ blk_b = tl.load(b + off, mask=mask)
2041
+ blk_bias = tl.load(dt_bias + head_off, mask=mask)
2042
+ # If the model is loaded in fp16, without the .float() here, A might be -inf
2043
+ x = blk_a.to(tl.float32) + blk_bias.to(tl.float32)
2044
+ softplus_x = tl.where(
2045
+ beta * x <= threshold, (1 / beta) * tl.log(1 + tl.exp(beta * x)), x
2046
+ )
2047
+ blk_g = -tl.exp(blk_A_log.to(tl.float32)) * softplus_x
2048
+ tl.store(g + off, blk_g.to(g.dtype.element_ty), mask=mask)
2049
+ # compute beta_output = sigmoid(b)
2050
+ blk_beta_output = tl.sigmoid(blk_b.to(tl.float32))
2051
+ tl.store(
2052
+ beta_output + off, blk_beta_output.to(beta_output.dtype.element_ty), mask=mask
2053
+ )
2054
+
2055
+
2056
+ def fused_gdn_gating(
2057
+ A_log: torch.Tensor,
2058
+ a: torch.Tensor,
2059
+ b: torch.Tensor,
2060
+ dt_bias: torch.Tensor,
2061
+ beta: float = 1.0,
2062
+ threshold: float = 20.0,
2063
+ ) -> tuple[torch.Tensor, torch.Tensor]:
2064
+ """
2065
+ Fused computation of g and beta for Gated Delta Net.
2066
+ g = -self.A_log.float().exp() * F.softplus(a.float() + self.dt_bias)
2067
+ beta_output = b.sigmoid()
2068
+ TODO maybe use torch.compile to replace this triton kernel
2069
+ """
2070
+ batch, num_heads = a.shape
2071
+ seq_len = 1
2072
+ grid = (batch, seq_len, triton.cdiv(num_heads, 8))
2073
+ g = torch.empty(1, batch, num_heads, dtype=torch.float32, device=a.device)
2074
+ beta_output = torch.empty(1, batch, num_heads, dtype=b.dtype, device=b.device)
2075
+ fused_gdn_gating_kernel[grid](
2076
+ g,
2077
+ beta_output,
2078
+ A_log,
2079
+ a,
2080
+ b,
2081
+ dt_bias,
2082
+ seq_len,
2083
+ num_heads,
2084
+ beta,
2085
+ threshold,
2086
+ 8,
2087
+ num_warps=1,
2088
+ )
2089
+ return g, beta_output
bundle/plugin-site/ornith_g256/runtime_correctness.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Load the four source-only corrections for the pinned Ciru vLLM runtime.
2
+
3
+ The installed runtime and its compiled libraries are left intact. This finder
4
+ only selects versioned Python modules shipped with this integration package.
5
+ """
6
+ # Copyright 2026 Ciru. Licensed under Apache-2.0.
7
+ import hashlib
8
+ import importlib.abc
9
+ import importlib.machinery
10
+ import importlib.util
11
+ import json
12
+ from pathlib import Path
13
+ import sys
14
+
15
+ VERSION = "1.0.1"
16
+ _ROOT = Path(__file__).parent / "_vllm_correctness"
17
+ _FINDER = None
18
+
19
+
20
+ class _CorrectnessSources(importlib.abc.MetaPathFinder):
21
+ def __init__(self, modules):
22
+ self.modules = modules
23
+
24
+ def find_spec(self, fullname, path=None, target=None):
25
+ source = self.modules.get(fullname)
26
+ if source is None:
27
+ return None
28
+ return importlib.util.spec_from_file_location(fullname, source)
29
+
30
+
31
+ def install():
32
+ """Register before vLLM worker/model imports; reject an incompatible runtime."""
33
+ global _FINDER
34
+ if _FINDER is not None:
35
+ return
36
+ manifest = json.loads((_ROOT / "manifest.json").read_text())
37
+ spec = importlib.machinery.PathFinder.find_spec("vllm", sys.path)
38
+ if spec is None or not spec.submodule_search_locations:
39
+ raise RuntimeError("Ciru runtime 1.0.1 requires the supplied vLLM installation")
40
+ native_root = Path(next(iter(spec.submodule_search_locations)))
41
+ modules = {}
42
+ for name, entry in manifest["modules"].items():
43
+ source = _ROOT / entry["file"]
44
+ native = native_root / entry["native_path"]
45
+ if hashlib.sha256(source.read_bytes()).hexdigest() != entry["sha256"]:
46
+ raise RuntimeError(f"Ciru runtime source integrity check failed: {name}")
47
+ if hashlib.sha256(native.read_bytes()).hexdigest() != entry["native_sha256"]:
48
+ raise RuntimeError(f"Ciru runtime 1.0.1 requires its pinned vLLM source: {name}")
49
+ loaded = sys.modules.get(name)
50
+ if loaded is not None:
51
+ raise RuntimeError(f"Import ornith_g256 before {name} to load Ciru runtime 1.0.1")
52
+ modules[name] = source
53
+ _FINDER = _CorrectnessSources(modules)
54
+ sys.meta_path.insert(0, _FINDER)