1use anyhow::Result;
20#[path = "hyper_connection_lowrank_gemm.rs"]
21mod gemm;
22pub(crate) use gemm::hc_pre_gemm;
23
24use spark_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
25use spark_runtime::kernel_args::KernelLaunch;
26
27use crate::layers::qwen3_attention::HcLowRank;
28
29fn hc_gemm_disabled() -> bool {
33 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
34 *ON.get_or_init(|| std::env::var("ATLAS_QWEN4EXP_NO_HC_GEMM").as_deref() == Ok("1"))
35}
36
37fn hc_decode_split_forced() -> bool {
41 static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
42 *V.get_or_init(|| std::env::var("ATLAS_HC_DECODE_SPLIT").as_deref() == Ok("1"))
43}
44
45#[allow(clippy::too_many_arguments)]
50pub fn hc_pre_lowrank(
51 gpu: &dyn GpuBackend,
52 kernel: KernelHandle,
53 streams: DevicePtr,
54 w: &HcLowRank,
55 y_out: DevicePtr,
56 inj_out: DevicePtr,
57 scratch: DevicePtr,
58 num_tokens: u32,
59 hidden_size: u32,
60 hc_mult: u32,
61 norm_eps: f32,
62 stream: u64,
63) -> Result<()> {
64 anyhow::ensure!(
65 !w.inject_w.is_null(),
66 "hc_pre_lowrank needs block_inject_weight; a site loaded without one \
67 is the model-level mixer and must use hc_head_lowrank"
68 );
69 if num_tokens <= 64 && !scratch.is_null() {
75 if !hc_decode_split_forced() {
82 return hc_pre_gemm(
83 gpu,
84 streams,
85 w,
86 y_out,
87 inj_out,
88 scratch,
89 num_tokens,
90 hidden_size,
91 hc_mult,
92 norm_eps,
93 true,
94 true,
95 stream,
96 );
97 }
98 return hc_pre_split(
99 gpu,
100 streams,
101 w,
102 y_out,
103 inj_out,
104 scratch,
105 num_tokens,
106 hidden_size,
107 hc_mult,
108 norm_eps,
109 true,
110 stream,
111 );
112 }
113 if !scratch.is_null() && !hc_gemm_disabled() {
117 return hc_pre_gemm(
118 gpu,
119 streams,
120 w,
121 y_out,
122 inj_out,
123 scratch,
124 num_tokens,
125 hidden_size,
126 hc_mult,
127 norm_eps,
128 true,
129 false,
130 stream,
131 );
132 }
133 let smem = (hc_mult * hidden_size + w.rank as u32) * 4;
138 KernelLaunch::new(gpu, kernel)
139 .grid([num_tokens, 1, 1])
140 .block([1024, 1, 1])
141 .shared_mem(smem)
142 .arg_ptr(streams)
143 .arg_ptr(w.norm_w)
144 .arg_ptr(w.down_w)
145 .arg_ptr(w.up_w)
146 .arg_ptr(w.inject_w)
147 .arg_ptr(y_out)
148 .arg_ptr(inj_out)
149 .arg_u32(hidden_size)
150 .arg_u32(hc_mult)
151 .arg_u32(w.rank as u32)
152 .arg_f32(norm_eps)
153 .launch(stream)
154}
155
156#[allow(clippy::too_many_arguments)]
162pub fn hc_head_lowrank(
163 gpu: &dyn GpuBackend,
164 kernel: KernelHandle,
165 streams: DevicePtr,
166 w: &HcLowRank,
167 y_out: DevicePtr,
168 scratch: DevicePtr,
169 num_tokens: u32,
170 hidden_size: u32,
171 hc_mult: u32,
172 norm_eps: f32,
173 stream: u64,
174) -> Result<()> {
175 if num_tokens <= 64 && !scratch.is_null() {
176 if !hc_decode_split_forced() {
177 return hc_pre_gemm(
178 gpu,
179 streams,
180 w,
181 y_out,
182 DevicePtr::NULL,
183 scratch,
184 num_tokens,
185 hidden_size,
186 hc_mult,
187 norm_eps,
188 false,
189 true,
190 stream,
191 );
192 }
193 return hc_pre_split(
194 gpu,
195 streams,
196 w,
197 y_out,
198 DevicePtr::NULL,
199 scratch,
200 num_tokens,
201 hidden_size,
202 hc_mult,
203 norm_eps,
204 false,
205 stream,
206 );
207 }
208 if !scratch.is_null() && !hc_gemm_disabled() {
211 return hc_pre_gemm(
212 gpu,
213 streams,
214 w,
215 y_out,
216 DevicePtr::NULL,
217 scratch,
218 num_tokens,
219 hidden_size,
220 hc_mult,
221 norm_eps,
222 false,
223 false,
224 stream,
225 );
226 }
227 let smem = (hc_mult * hidden_size + w.rank as u32) * 4;
228 KernelLaunch::new(gpu, kernel)
229 .grid([num_tokens, 1, 1])
230 .block([1024, 1, 1])
231 .shared_mem(smem)
232 .arg_ptr(streams)
233 .arg_ptr(w.norm_w)
234 .arg_ptr(w.down_w)
235 .arg_ptr(w.up_w)
236 .arg_ptr(y_out)
237 .arg_u32(hidden_size)
238 .arg_u32(hc_mult)
239 .arg_u32(w.rank as u32)
240 .arg_f32(norm_eps)
241 .launch(stream)
242}
243
244#[allow(clippy::too_many_arguments)]
252pub fn hc_post_lowrank(
253 gpu: &dyn GpuBackend,
254 kernel: KernelHandle,
255 block_out: DevicePtr,
256 residual: DevicePtr,
257 inj: DevicePtr,
258 out: DevicePtr,
259 num_tokens: u32,
260 hidden_size: u32,
261 hc_mult: u32,
262 stream: u64,
263) -> Result<()> {
264 KernelLaunch::new(gpu, kernel)
265 .grid([num_tokens, 1, 1])
266 .block([256, 1, 1])
267 .arg_ptr(block_out)
268 .arg_ptr(residual)
269 .arg_ptr(inj)
270 .arg_ptr(out)
271 .arg_u32(hidden_size)
272 .arg_u32(hc_mult)
273 .launch(stream)
274}
275
276#[allow(clippy::too_many_arguments)]
279pub(crate) fn hc_pre_split(
280 gpu: &dyn GpuBackend,
281 streams: DevicePtr,
282 w: &HcLowRank,
283 y_out: DevicePtr,
284 inj_out: DevicePtr,
285 scratch: DevicePtr,
286 num_tokens: u32,
287 hidden_size: u32,
288 hc_mult: u32,
289 norm_eps: f32,
290 inject: bool,
291 stream: u64,
292) -> Result<()> {
293 let hc_dim = hc_mult * hidden_size;
294 let normed = scratch;
296 let low = scratch.offset(64 * hc_dim as usize * 4);
297
298 let k_stage = gpu.kernel("hyper_connection", "hc_pre_stage")?;
299 let k_down = gpu.kernel("hyper_connection", "hc_pre_down")?;
300 let k_fin = gpu.kernel("hyper_connection", "hc_pre_finish")?;
301
302 KernelLaunch::new(gpu, k_stage)
303 .grid([num_tokens, 1, 1])
304 .block([1024, 1, 1])
305 .arg_ptr(streams)
306 .arg_ptr(w.norm_w)
307 .arg_ptr(normed)
308 .arg_u32(hidden_size)
309 .arg_u32(hc_mult)
310 .arg_f32(norm_eps)
311 .launch(stream)?;
312
313 let dsplit = (48 / num_tokens.max(1)).clamp(1, 10);
315 KernelLaunch::new(gpu, k_down)
316 .grid([num_tokens, dsplit, 1])
317 .block([1024, 1, 1])
318 .arg_ptr(normed)
319 .arg_ptr(w.down_w)
320 .arg_ptr(low)
321 .arg_u32(hidden_size)
322 .arg_u32(hc_mult)
323 .arg_u32(w.rank as u32)
324 .launch(stream)?;
325
326 let fsplit = (48 / num_tokens.max(1)).clamp(1, 10);
327 KernelLaunch::new(gpu, k_fin)
328 .grid([num_tokens, fsplit, 1])
329 .block([256, 1, 1])
330 .shared_mem(w.rank as u32 * 4)
331 .arg_ptr(normed)
332 .arg_ptr(low)
333 .arg_ptr(w.up_w)
334 .arg_ptr(if inject { w.inject_w } else { DevicePtr::NULL })
335 .arg_ptr(y_out)
336 .arg_ptr(inj_out)
337 .arg_u32(hidden_size)
338 .arg_u32(hc_mult)
339 .arg_u32(w.rank as u32)
340 .launch(stream)
341}