rlx_models_core/
gguf_resolve.rs1use rlx_gguf::GgufFile;
19use std::sync::{Mutex, OnceLock};
20
21use crate::weight_loader::{
22 gguf_to_hf_name, gguf_to_hf_name_for_arch, hf_to_gguf_name, hf_to_gguf_name_for_arch,
23};
24
25pub trait GgufTensorNameResolver: Send + Sync {
27 fn matches_arch(&self, arch: &str) -> bool;
28 fn resolve(&self, file: &GgufFile, requested_key: &str) -> Option<String>;
29}
30
31pub struct LlamaFamilyGgufResolver;
33
34impl GgufTensorNameResolver for LlamaFamilyGgufResolver {
35 fn matches_arch(&self, arch: &str) -> bool {
36 matches!(
37 arch,
38 "llama"
39 | "llama4"
40 | "phi3"
41 | "phi4"
42 | "qwen3"
43 | "qwen2"
44 | "qwen35"
45 | "qwen35moe"
46 | "qwen36"
47 | "gemma"
48 | "gemma2"
49 | "mistral"
50 )
51 }
52
53 fn resolve(&self, file: &GgufFile, key: &str) -> Option<String> {
54 if file.tensors.contains_key(key) {
55 return Some(key.to_string());
56 }
57 if let Some(g) = hf_to_gguf_name(key) {
58 if file.tensors.contains_key(&g) {
59 return Some(g);
60 }
61 }
62 if let Some(h) = gguf_to_hf_name(key) {
63 if file.tensors.contains_key(&h) {
64 return Some(h);
65 }
66 }
67 None
68 }
69}
70
71pub struct PrefixStripGgufResolver;
73
74pub type PassThroughGgufResolver = PrefixStripGgufResolver;
76
77impl GgufTensorNameResolver for PrefixStripGgufResolver {
78 fn matches_arch(&self, _arch: &str) -> bool {
79 true
80 }
81
82 fn resolve(&self, file: &GgufFile, key: &str) -> Option<String> {
83 let mut k = key.to_string();
84 for prefix in [
85 "model.diffusion_model.",
86 "diffusion_model.",
87 "transformer.",
88 "model.",
89 ] {
90 if let Some(rest) = k.strip_prefix(prefix) {
91 k = rest.to_string();
92 break;
93 }
94 }
95 if file.tensors.contains_key(&k) {
96 return Some(k);
97 }
98 if file.tensors.contains_key(key) {
99 return Some(key.to_string());
100 }
101 None
102 }
103}
104
105pub struct Gemma2GgufResolver;
116
117impl GgufTensorNameResolver for Gemma2GgufResolver {
118 fn matches_arch(&self, arch: &str) -> bool {
119 matches!(
120 arch,
121 "gemma2" | "gemma3" | "gemma3n" | "gemma4" | "gemma4moe"
122 )
123 }
124
125 fn resolve(&self, file: &GgufFile, key: &str) -> Option<String> {
126 if file.tensors.contains_key(key) {
128 return Some(key.to_string());
129 }
130 if let Some(rest) = key.strip_prefix("model.layers.") {
136 if let Some((idx, tail)) = rest.split_once('.') {
137 let gguf_tail = match tail {
138 "post_attention_layernorm.weight" => Some("post_attention_norm.weight"),
139 "pre_feedforward_layernorm.weight" => Some("ffn_norm.weight"),
140 "post_feedforward_layernorm.weight" => Some("post_ffw_norm.weight"),
141 "self_attn.output_scale.weight" => Some("layer_output_scale.weight"),
145 _ => None,
146 };
147 if let Some(t) = gguf_tail {
148 let g = format!("blk.{idx}.{t}");
149 if file.tensors.contains_key(&g) {
150 return Some(g);
151 }
152 }
153 }
154 }
155 LlamaFamilyGgufResolver.resolve(file, key)
158 }
159}
160
161pub struct Phi3GgufResolver;
163
164impl GgufTensorNameResolver for Phi3GgufResolver {
165 fn matches_arch(&self, arch: &str) -> bool {
166 matches!(arch, "phi3" | "phi4")
167 }
168
169 fn resolve(&self, file: &GgufFile, key: &str) -> Option<String> {
170 if file.tensors.contains_key(key) {
171 return Some(key.to_string());
172 }
173 for arch in ["phi3", "phi4"] {
174 if let Some(g) = hf_to_gguf_name_for_arch(key, arch) {
175 if file.tensors.contains_key(&g) {
176 return Some(g);
177 }
178 }
179 if let Some(h) = gguf_to_hf_name_for_arch(key, arch) {
180 if file.tensors.contains_key(&h) {
181 return Some(h);
182 }
183 }
184 }
185 None
186 }
187}
188
189pub struct Qwen35NativeGgufResolver;
191
192impl GgufTensorNameResolver for Qwen35NativeGgufResolver {
193 fn matches_arch(&self, arch: &str) -> bool {
194 matches!(arch, "qwen35" | "qwen35moe" | "qwen36")
195 }
196
197 fn resolve(&self, file: &GgufFile, key: &str) -> Option<String> {
198 if file.tensors.contains_key(key) {
199 return Some(key.to_string());
200 }
201 LlamaFamilyGgufResolver.resolve(file, key)
202 }
203}
204
205static CUSTOM_RESOLVERS: Mutex<Vec<Box<dyn GgufTensorNameResolver>>> = Mutex::new(Vec::new());
206static BUILTIN_RESOLVERS: OnceLock<Vec<Box<dyn GgufTensorNameResolver>>> = OnceLock::new();
207
208fn builtin_resolvers() -> &'static Vec<Box<dyn GgufTensorNameResolver>> {
209 BUILTIN_RESOLVERS.get_or_init(|| {
210 vec![
211 Box::new(Qwen35NativeGgufResolver),
212 Box::new(Gemma2GgufResolver),
213 Box::new(Phi3GgufResolver),
214 Box::new(LlamaFamilyGgufResolver),
215 Box::new(PrefixStripGgufResolver),
216 ]
217 })
218}
219
220pub fn ensure_builtin_resolvers() {
223 let _ = builtin_resolvers();
224}
225
226pub fn register_gguf_tensor_resolver(resolver: Box<dyn GgufTensorNameResolver>) {
229 CUSTOM_RESOLVERS
230 .lock()
231 .expect("gguf resolver registry lock")
232 .push(resolver);
233}
234
235pub fn resolve_gguf_tensor_name(
237 file: &GgufFile,
238 arch: &str,
239 requested_key: &str,
240) -> Option<String> {
241 for r in builtin_resolvers().iter() {
242 if r.matches_arch(arch) {
243 if let Some(name) = r.resolve(file, requested_key) {
244 return Some(name);
245 }
246 }
247 }
248 let custom = CUSTOM_RESOLVERS
249 .lock()
250 .expect("gguf resolver registry lock");
251 for r in custom.iter() {
252 if r.matches_arch(arch) {
253 if let Some(name) = r.resolve(file, requested_key) {
254 return Some(name);
255 }
256 }
257 }
258 if file.tensors.contains_key(requested_key) {
259 return Some(requested_key.to_string());
260 }
261 if let Some(g) = hf_to_gguf_name(requested_key) {
262 if file.tensors.contains_key(&g) {
263 return Some(g);
264 }
265 }
266 if let Some(h) = gguf_to_hf_name(requested_key) {
267 if file.tensors.contains_key(&h) {
268 return Some(h);
269 }
270 }
271 None
272}