metrale_model_layers/layers/ops/
dense_gemv_tc.rs1use std::sync::{Mutex, OnceLock};
21
22use anyhow::Result;
23use metrale_gpu_runtime::gpu::{DevicePtr, GpuBackend, KernelHandle};
24use metrale_gpu_runtime::kernel_args::{KernelLaunch, div_ceil};
25
26use crate::weight_map::DenseWeight;
27
28pub const TC8_MAX_M: u32 = 8;
31pub const TC16_MAX_M: u32 = 16;
32pub const TC32_MAX_M: u32 = 32;
33pub const ROWS_PER_CTA: u32 = 16;
35pub const BLOCK: u32 = 256;
37pub const K_STEP: u32 = 64;
40pub const MIN_M: u32 = 2;
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum DtcKind {
46 M8,
47 M16,
48 M32,
49}
50
51impl DtcKind {
52 pub fn entry(self) -> &'static str {
53 match self {
54 DtcKind::M8 => "dense_gemv_bf16_tc8",
55 DtcKind::M16 => "dense_gemv_bf16_tc16",
56 DtcKind::M32 => "dense_gemv_bf16_tc32",
57 }
58 }
59}
60
61pub fn route(m: u32, n: u32, k: u32, enabled: bool, have: [bool; 3]) -> Option<DtcKind> {
70 if !enabled || m < MIN_M || n == 0 || k == 0 || !k.is_multiple_of(K_STEP) {
71 return None;
72 }
73 [
74 (TC8_MAX_M, DtcKind::M8),
75 (TC16_MAX_M, DtcKind::M16),
76 (TC32_MAX_M, DtcKind::M32),
77 ]
78 .into_iter()
79 .zip(have)
80 .find(|&((cap, _), ok)| ok && m <= cap)
81 .map(|((_, kind), _)| kind)
82}
83
84pub fn mtp_tc_from(kill: Option<&std::ffi::OsStr>) -> bool {
87 kill.is_none_or(|v| v.is_empty())
88}
89
90pub fn mtp_tc_enabled() -> bool {
93 static ON: OnceLock<bool> = OnceLock::new();
94 *ON.get_or_init(|| mtp_tc_from(std::env::var_os("METRALE_NO_MTP_TC").as_deref()))
95}
96
97fn handles(gpu: &dyn GpuBackend) -> [KernelHandle; 3] {
101 static CACHE: OnceLock<Mutex<Vec<(usize, [KernelHandle; 3])>>> = OnceLock::new();
102 let key = gpu as *const dyn GpuBackend as *const () as usize;
103 let cache = CACHE.get_or_init(|| Mutex::new(Vec::new()));
104 let mut guard = cache.lock().unwrap_or_else(|p| p.into_inner());
105 if let Some((_, h)) = guard.iter().find(|(k, _)| *k == key) {
106 return *h;
107 }
108 let h = [DtcKind::M8, DtcKind::M16, DtcKind::M32]
109 .map(|kind| crate::layers::try_kernel(gpu, "dense_gemv_bf16_tc", kind.entry()));
110 guard.push((key, h));
111 h
112}
113
114pub fn kernel_for(gpu: &dyn GpuBackend, m: u32, n: u32, k: u32) -> Option<(KernelHandle, u32)> {
117 if !mtp_tc_enabled() {
118 return None;
119 }
120 let h = handles(gpu);
121 let kind = route(m, n, k, true, h.map(|x| x.0 != 0))?;
122 let handle = match kind {
123 DtcKind::M8 => h[0],
124 DtcKind::M16 => h[1],
125 DtcKind::M32 => h[2],
126 };
127 Some((handle, div_ceil(n, ROWS_PER_CTA)))
128}
129
130#[allow(clippy::too_many_arguments)]
133pub fn launch(
134 gpu: &dyn GpuBackend,
135 kernel: KernelHandle,
136 grid_x: u32,
137 input: DevicePtr,
138 weight: &DenseWeight,
139 output: DevicePtr,
140 m: u32,
141 n: u32,
142 k: u32,
143 out_stride: u32,
144 stream: u64,
145) -> Result<()> {
146 KernelLaunch::new(gpu, kernel)
147 .grid([grid_x, 1, 1])
148 .block([BLOCK, 1, 1])
149 .arg_ptr(input)
150 .arg_ptr(weight.weight)
151 .arg_ptr(output)
152 .arg_u32(m)
153 .arg_u32(n)
154 .arg_u32(k)
155 .arg_u32(out_stride)
156 .launch(stream)
157}
158
159#[allow(clippy::too_many_arguments)]
163pub fn try_dense_gemv_tc(
164 gpu: &dyn GpuBackend,
165 input: DevicePtr,
166 weight: &DenseWeight,
167 output: DevicePtr,
168 m: u32,
169 n: u32,
170 k: u32,
171 out_stride: u32,
172 stream: u64,
173) -> Result<bool> {
174 let Some((kernel, grid_x)) = kernel_for(gpu, m, n, k) else {
175 return Ok(false);
176 };
177 launch(
178 gpu, kernel, grid_x, input, weight, output, m, n, k, out_stride, stream,
179 )?;
180 Ok(true)
181}
182
183#[cfg(test)]
184#[path = "dense_gemv_tc_tests.rs"]
185mod dense_gemv_tc_tests;