1use crate::backend::Backend;
16
17#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23pub enum LinearProjectionRole {
24 Qkv,
25 Query,
26 Key,
27 Value,
28 GdnQkv,
29 GdnZ,
30 GdnQkvz,
31 GdnB,
32 GdnA,
33 GdnBa,
34 Output,
35 GateUp,
36 Gate,
37 Up,
38 Down,
39 LmHead,
40}
41
42#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
44pub struct LinearMetadata {
45 pub layer_index: Option<usize>,
46 pub role: Option<LinearProjectionRole>,
47}
48
49impl LinearMetadata {
50 pub const fn new(layer_index: Option<usize>, role: Option<LinearProjectionRole>) -> Self {
51 Self { layer_index, role }
52 }
53
54 pub const fn is_empty(self) -> bool {
55 self.layer_index.is_none() && self.role.is_none()
56 }
57
58 pub fn from_name(name: &str) -> Self {
59 let base = strip_tensor_suffix(name);
60 Self {
61 layer_index: parse_layer_index(base),
62 role: parse_projection_role(base),
63 }
64 }
65
66 pub fn from_fused_names<'a>(names: impl IntoIterator<Item = &'a str>) -> Self {
67 let mut layer_index = None;
68 let mut roles = Vec::new();
69
70 for name in names {
71 let metadata = Self::from_name(name);
72 if layer_index.is_none() {
73 layer_index = metadata.layer_index;
74 }
75 if let Some(role) = metadata.role {
76 roles.push(role);
77 }
78 }
79
80 let role = match roles.as_slice() {
81 [LinearProjectionRole::Query, LinearProjectionRole::Key, LinearProjectionRole::Value] => {
82 Some(LinearProjectionRole::Qkv)
83 }
84 [LinearProjectionRole::GdnQkv, LinearProjectionRole::GdnZ] => {
85 Some(LinearProjectionRole::GdnQkvz)
86 }
87 [LinearProjectionRole::GdnB, LinearProjectionRole::GdnA] => {
88 Some(LinearProjectionRole::GdnBa)
89 }
90 [LinearProjectionRole::Gate, LinearProjectionRole::Up] => {
91 Some(LinearProjectionRole::GateUp)
92 }
93 [single] => Some(*single),
94 _ => None,
95 };
96
97 Self { layer_index, role }
98 }
99}
100
101pub trait Linear<B: Backend>: Send + Sync {
107 fn in_features(&self) -> usize;
108 fn out_features(&self) -> usize;
109
110 fn metadata(&self) -> LinearMetadata {
111 LinearMetadata::default()
112 }
113
114 #[cfg(feature = "cuda")]
115 fn cuda_marlin_touch_ref(
116 &self,
117 ) -> Option<crate::quant_linear::cuda_marlin::CudaMarlinTouchRef<'_>> {
118 None
119 }
120
121 fn forward(&self, ctx: &mut B::Context, input: &B::Buffer, out: &mut B::Buffer, m: usize);
124}
125
126fn strip_tensor_suffix(name: &str) -> &str {
127 for suffix in [
128 ".weight", ".qweight", ".scales", ".qzeros", ".g_idx", ".bias",
129 ] {
130 if let Some(stripped) = name.strip_suffix(suffix) {
131 return stripped;
132 }
133 }
134 name
135}
136
137fn parse_layer_index(name: &str) -> Option<usize> {
138 let mut prev_was_layers = false;
139 for part in name.split('.') {
140 if prev_was_layers {
141 return part.parse::<usize>().ok();
142 }
143 prev_was_layers = part == "layers";
144 }
145 None
146}
147
148fn parse_projection_role(name: &str) -> Option<LinearProjectionRole> {
149 let tail = name.rsplit('.').next().unwrap_or(name);
150 match tail {
151 "qkv_proj" => Some(LinearProjectionRole::Qkv),
152 "q_proj" => Some(LinearProjectionRole::Query),
153 "k_proj" => Some(LinearProjectionRole::Key),
154 "v_proj" => Some(LinearProjectionRole::Value),
155 "in_proj_qkv" => Some(LinearProjectionRole::GdnQkv),
156 "in_proj_z" => Some(LinearProjectionRole::GdnZ),
157 "in_proj_qkvz" => Some(LinearProjectionRole::GdnQkvz),
158 "in_proj_b" => Some(LinearProjectionRole::GdnB),
159 "in_proj_a" => Some(LinearProjectionRole::GdnA),
160 "in_proj_ba" => Some(LinearProjectionRole::GdnBa),
161 "o_proj" => Some(LinearProjectionRole::Output),
162 "gate_up_proj" => Some(LinearProjectionRole::GateUp),
163 "gate_proj" => Some(LinearProjectionRole::Gate),
164 "up_proj" => Some(LinearProjectionRole::Up),
165 "down_proj" => Some(LinearProjectionRole::Down),
166 "lm_head" => Some(LinearProjectionRole::LmHead),
167 _ => None,
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::{LinearMetadata, LinearProjectionRole};
174
175 #[test]
176 fn metadata_parses_llama_layer_projection_roles() {
177 assert_eq!(
178 LinearMetadata::from_name("model.layers.17.mlp.down_proj.qweight"),
179 LinearMetadata::new(Some(17), Some(LinearProjectionRole::Down))
180 );
181 assert_eq!(
182 LinearMetadata::from_name("language_model.model.layers.3.self_attn.o_proj.weight"),
183 LinearMetadata::new(Some(3), Some(LinearProjectionRole::Output))
184 );
185 assert_eq!(
186 LinearMetadata::from_name("lm_head.weight"),
187 LinearMetadata::new(None, Some(LinearProjectionRole::LmHead))
188 );
189 }
190
191 #[test]
192 fn metadata_parses_fused_projection_roles() {
193 assert_eq!(
194 LinearMetadata::from_fused_names([
195 "model.layers.2.self_attn.q_proj",
196 "model.layers.2.self_attn.k_proj",
197 "model.layers.2.self_attn.v_proj",
198 ]),
199 LinearMetadata::new(Some(2), Some(LinearProjectionRole::Qkv))
200 );
201 assert_eq!(
202 LinearMetadata::from_fused_names([
203 "model.layers.9.mlp.gate_proj.qweight",
204 "model.layers.9.mlp.up_proj.qweight",
205 ]),
206 LinearMetadata::new(Some(9), Some(LinearProjectionRole::GateUp))
207 );
208 assert_eq!(
209 LinearMetadata::from_fused_names([
210 "model.layers.2.linear_attn.in_proj_qkv.qweight",
211 "model.layers.2.linear_attn.in_proj_z.qweight",
212 ]),
213 LinearMetadata::new(Some(2), Some(LinearProjectionRole::GdnQkvz))
214 );
215 assert_eq!(
216 LinearMetadata::from_fused_names([
217 "model.layers.2.linear_attn.in_proj_b.qweight",
218 "model.layers.2.linear_attn.in_proj_a.qweight",
219 ]),
220 LinearMetadata::new(Some(2), Some(LinearProjectionRole::GdnBa))
221 );
222 }
223}