metrale_model_layers/layers/ops/
hyper_connection_lowrank.rs1use anyhow::Result;
18#[path = "hyper_connection_lowrank_gemm.rs"]
19mod gemm;
20pub(crate) use gemm::hc_pre_gemm;
21
22use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
23use metrale_gpu_runtime::kernel_args::KernelLaunch;
24
25use crate::layers::qwen3_attention::HcLowRank;
26
27fn hc_gemm_disabled() -> bool {
31 static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
32 *ON.get_or_init(|| std::env::var("METRALE_QWEN4EXP_NO_HC_GEMM").as_deref() == Ok("1"))
33}
34
35fn hc_decode_split_forced() -> bool {
38 static V: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
39 *V.get_or_init(|| std::env::var("METRALE_HC_DECODE_SPLIT").as_deref() == Ok("1"))
40}
41
42#[allow(clippy::too_many_arguments)]
47pub fn hc_pre_lowrank(
48 gpu: &dyn GpuBackend,
49 kernel: KernelHandle,
50 streams: DevicePtr,
51 w: &HcLowRank,
52 y_out: DevicePtr,
53 inj_out: DevicePtr,
54 scratch: DevicePtr,
55 num_tokens: u32,
56 hidden_size: u32,
57 hc_mult: u32,
58 norm_eps: f32,
59 stream: u64,
60) -> Result<()> {
61 anyhow::ensure!(
62 !w.inject_w.is_null(),
63 "hc_pre_lowrank needs block_inject_weight; a site loaded without one \
64 is the model-level mixer and must use hc_head_lowrank"
65 );
66 if num_tokens <= 64 && !scratch.is_null() {
69 if !hc_decode_split_forced() {
70 return hc_pre_gemm(
71 gpu,
72 streams,
73 w,
74 y_out,
75 inj_out,
76 scratch,
77 num_tokens,
78 hidden_size,
79 hc_mult,
80 norm_eps,
81 true,
82 true,
83 stream,
84 );
85 }
86 return hc_pre_split(
87 gpu,
88 streams,
89 w,
90 y_out,
91 inj_out,
92 scratch,
93 num_tokens,
94 hidden_size,
95 hc_mult,
96 norm_eps,
97 true,
98 stream,
99 );
100 }
101 if !scratch.is_null() && !hc_gemm_disabled() {
104 return hc_pre_gemm(
105 gpu,
106 streams,
107 w,
108 y_out,
109 inj_out,
110 scratch,
111 num_tokens,
112 hidden_size,
113 hc_mult,
114 norm_eps,
115 true,
116 false,
117 stream,
118 );
119 }
120 let smem = (hc_mult * hidden_size + w.rank as u32) * 4;
123 KernelLaunch::new(gpu, kernel)
124 .grid([num_tokens, 1, 1])
125 .block([1024, 1, 1])
126 .shared_mem(smem)
127 .arg_ptr(streams)
128 .arg_ptr(w.norm_w)
129 .arg_ptr(w.down_w)
130 .arg_ptr(w.up_w)
131 .arg_ptr(w.inject_w)
132 .arg_ptr(y_out)
133 .arg_ptr(inj_out)
134 .arg_u32(hidden_size)
135 .arg_u32(hc_mult)
136 .arg_u32(w.rank as u32)
137 .arg_f32(norm_eps)
138 .launch(stream)
139}
140
141#[allow(clippy::too_many_arguments)]
146pub fn hc_head_lowrank(
147 gpu: &dyn GpuBackend,
148 kernel: KernelHandle,
149 streams: DevicePtr,
150 w: &HcLowRank,
151 y_out: DevicePtr,
152 scratch: DevicePtr,
153 num_tokens: u32,
154 hidden_size: u32,
155 hc_mult: u32,
156 norm_eps: f32,
157 stream: u64,
158) -> Result<()> {
159 if num_tokens <= 64 && !scratch.is_null() {
160 if !hc_decode_split_forced() {
161 return hc_pre_gemm(
162 gpu,
163 streams,
164 w,
165 y_out,
166 DevicePtr::NULL,
167 scratch,
168 num_tokens,
169 hidden_size,
170 hc_mult,
171 norm_eps,
172 false,
173 true,
174 stream,
175 );
176 }
177 return hc_pre_split(
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 stream,
190 );
191 }
192 if !scratch.is_null() && !hc_gemm_disabled() {
195 return hc_pre_gemm(
196 gpu,
197 streams,
198 w,
199 y_out,
200 DevicePtr::NULL,
201 scratch,
202 num_tokens,
203 hidden_size,
204 hc_mult,
205 norm_eps,
206 false,
207 false,
208 stream,
209 );
210 }
211 let smem = (hc_mult * hidden_size + w.rank as u32) * 4;
212 KernelLaunch::new(gpu, kernel)
213 .grid([num_tokens, 1, 1])
214 .block([1024, 1, 1])
215 .shared_mem(smem)
216 .arg_ptr(streams)
217 .arg_ptr(w.norm_w)
218 .arg_ptr(w.down_w)
219 .arg_ptr(w.up_w)
220 .arg_ptr(y_out)
221 .arg_u32(hidden_size)
222 .arg_u32(hc_mult)
223 .arg_u32(w.rank as u32)
224 .arg_f32(norm_eps)
225 .launch(stream)
226}
227
228#[allow(clippy::too_many_arguments)]
232pub fn hc_post_lowrank(
233 gpu: &dyn GpuBackend,
234 kernel: KernelHandle,
235 block_out: DevicePtr,
236 residual: DevicePtr,
237 inj: DevicePtr,
238 out: DevicePtr,
239 num_tokens: u32,
240 hidden_size: u32,
241 hc_mult: u32,
242 stream: u64,
243) -> Result<()> {
244 KernelLaunch::new(gpu, kernel)
245 .grid([num_tokens, 1, 1])
246 .block([256, 1, 1])
247 .arg_ptr(block_out)
248 .arg_ptr(residual)
249 .arg_ptr(inj)
250 .arg_ptr(out)
251 .arg_u32(hidden_size)
252 .arg_u32(hc_mult)
253 .launch(stream)
254}
255
256#[allow(clippy::too_many_arguments)]
260pub(crate) fn hc_pre_split(
261 gpu: &dyn GpuBackend,
262 streams: DevicePtr,
263 w: &HcLowRank,
264 y_out: DevicePtr,
265 inj_out: DevicePtr,
266 scratch: DevicePtr,
267 num_tokens: u32,
268 hidden_size: u32,
269 hc_mult: u32,
270 norm_eps: f32,
271 inject: bool,
272 stream: u64,
273) -> Result<()> {
274 let hc_dim = hc_mult * hidden_size;
275 let normed = scratch;
277 let low = scratch.offset(64 * hc_dim as usize * 4);
278
279 let k_stage = gpu.kernel("hyper_connection", "hc_pre_stage")?;
280 let k_down = gpu.kernel("hyper_connection", "hc_pre_down")?;
281 let k_fin = gpu.kernel("hyper_connection", "hc_pre_finish")?;
282
283 KernelLaunch::new(gpu, k_stage)
284 .grid([num_tokens, 1, 1])
285 .block([1024, 1, 1])
286 .arg_ptr(streams)
287 .arg_ptr(w.norm_w)
288 .arg_ptr(normed)
289 .arg_u32(hidden_size)
290 .arg_u32(hc_mult)
291 .arg_f32(norm_eps)
292 .launch(stream)?;
293
294 let dsplit = (48 / num_tokens.max(1)).clamp(1, 10);
296 KernelLaunch::new(gpu, k_down)
297 .grid([num_tokens, dsplit, 1])
298 .block([1024, 1, 1])
299 .arg_ptr(normed)
300 .arg_ptr(w.down_w)
301 .arg_ptr(low)
302 .arg_u32(hidden_size)
303 .arg_u32(hc_mult)
304 .arg_u32(w.rank as u32)
305 .launch(stream)?;
306
307 let fsplit = (48 / num_tokens.max(1)).clamp(1, 10);
308 KernelLaunch::new(gpu, k_fin)
309 .grid([num_tokens, fsplit, 1])
310 .block([256, 1, 1])
311 .shared_mem(w.rank as u32 * 4)
312 .arg_ptr(normed)
313 .arg_ptr(low)
314 .arg_ptr(w.up_w)
315 .arg_ptr(if inject { w.inject_w } else { DevicePtr::NULL })
316 .arg_ptr(y_out)
317 .arg_ptr(inj_out)
318 .arg_u32(hidden_size)
319 .arg_u32(hc_mult)
320 .arg_u32(w.rank as u32)
321 .launch(stream)
322}