swadhinbiswas WIP save-point: production hardening, H3 removal, fmt gate, and accumulated phase work · 7/15/2026
a293d8c
324 lines · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
//! LoRA adapter registry and routing.
//!
//! Pure data structures — no weight tensors. The registry decides which adapter
//! to activate for a given request and can resolve multi-LoRA routing keys.
//! Actual weight loading is handled by `model.rs` behind the `real` feature.

use std::collections::HashMap;
use std::sync::{Arc, RwLock};

/// LoRA rank configuration for a single adapter.
#[derive(Debug, Clone)]
pub struct LoraConfig {
    /// Adapter rank (typically 8–128).
    pub rank: usize,
    /// Alpha scaling factor (often equal to rank).
    pub alpha: usize,
    /// Dropout probability (0.0 = no dropout; used during training only).
    pub dropout: f32,
    /// Target module name patterns (e.g. "q_proj", "v_proj", "gate_proj").
    pub target_modules: Vec<String>,
}

/// A loaded LoRA adapter (weights live in `model.rs` behind `real` feature).
#[derive(Debug, Clone)]
pub struct LoraAdapter {
    /// Unique adapter name (e.g. "adapter_v2_math").
    pub name: String,
    /// Optional base model hash — adapters are only valid for matching bases.
    pub base_model_hash: Option<String>,
    /// LoRA hyper-parameters.
    pub config: LoraConfig,
    /// Routing key used by the multi-LoRA scheduler. If set, requests matching
    /// this key are routed to this adapter.
    pub routing_key: Option<String>,
    /// Adapter is active and ready to serve.
    pub active: bool,
}

impl LoraAdapter {
    /// Create a new adapter with sensible defaults.
    pub fn new(name: impl Into<String>, rank: usize, alpha: usize) -> Self {
        Self {
            name: name.into(),
            base_model_hash: None,
            config: LoraConfig { rank, alpha, dropout: 0.0, target_modules: Vec::new() },
            routing_key: None,
            active: true,
        }
    }

    /// Builder-style setter for routing key.
    pub fn with_routing_key(mut self, key: impl Into<String>) -> Self {
        self.routing_key = Some(key.into());
        self
    }

    /// Builder-style setter for base model hash.
    pub fn with_base_model(mut self, hash: impl Into<String>) -> Self {
        self.base_model_hash = Some(hash.into());
        self
    }

    /// Builder-style setter for target modules.
    pub fn with_targets(mut self, modules: Vec<String>) -> Self {
        self.config.target_modules = modules;
        self
    }

    /// Effective scaling factor: `alpha / rank`.
    pub fn scaling(&self) -> f32 {
        self.config.alpha as f32 / self.config.rank.max(1) as f32
    }
}

/// Thread-safe registry for managing multiple LoRA adapters.
#[derive(Clone)]
pub struct LoraRegistry {
    inner: Arc<RwLock<LoraRegistryInner>>,
}

struct LoraRegistryInner {
    adapters: HashMap<String, LoraAdapter>,
    /// Routing-key → adapter-name lookup (built on register/unregister).
    routing: HashMap<String, String>,
}

impl LoraRegistry {
    /// Create an empty registry.
    pub fn new() -> Self {
        Self {
            inner: Arc::new(RwLock::new(LoraRegistryInner {
                adapters: HashMap::new(),
                routing: HashMap::new(),
            })),
        }
    }

    /// Register an adapter. Replaces any existing adapter with the same name.
    pub fn register(&self, adapter: LoraAdapter) {
        let mut inner = self.inner.write().unwrap();
        if let Some(key) = &adapter.routing_key {
            inner.routing.insert(key.clone(), adapter.name.clone());
        }
        inner.adapters.insert(adapter.name.clone(), adapter);
    }

    /// Unregister an adapter by name.
    pub fn unregister(&self, name: &str) -> Option<LoraAdapter> {
        let mut inner = self.inner.write().unwrap();
        let adapter = inner.adapters.remove(name)?;
        if let Some(key) = &adapter.routing_key {
            inner.routing.remove(key);
        }
        Some(adapter)
    }

    /// Look up an adapter by name.
    pub fn get(&self, name: &str) -> Option<LoraAdapter> {
        self.inner.read().unwrap().adapters.get(name).cloned()
    }

    /// Look up an adapter by routing key.
    pub fn get_by_routing_key(&self, key: &str) -> Option<LoraAdapter> {
        let inner = self.inner.read().unwrap();
        let name = inner.routing.get(key)?;
        inner.adapters.get(name).cloned()
    }

    /// List all active adapter names.
    pub fn active_adapters(&self) -> Vec<String> {
        self.inner
            .read()
            .unwrap()
            .adapters
            .values()
            .filter(|a| a.active)
            .map(|a| a.name.clone())
            .collect()
    }

    /// List all adapter names (active + inactive).
    pub fn all_adapters(&self) -> Vec<String> {
        self.inner.read().unwrap().adapters.keys().cloned().collect()
    }

    /// Number of registered adapters.
    pub fn len(&self) -> usize {
        self.inner.read().unwrap().adapters.len()
    }

    /// Whether the registry is empty.
    pub fn is_empty(&self) -> bool {
        self.inner.read().unwrap().adapters.is_empty()
    }

    /// Activate an adapter by name. Returns `false` if not found.
    pub fn activate(&self, name: &str) -> bool {
        let mut inner = self.inner.write().unwrap();
        if let Some(a) = inner.adapters.get_mut(name) {
            a.active = true;
            true
        } else {
            false
        }
    }

    /// Deactivate an adapter by name. Returns `false` if not found.
    pub fn deactivate(&self, name: &str) -> bool {
        let mut inner = self.inner.write().unwrap();
        if let Some(a) = inner.adapters.get_mut(name) {
            a.active = false;
            true
        } else {
            false
        }
    }

    /// Resolve which adapter to use for a request.
    ///
    /// Priority: explicit adapter name → routing key → `None` (base model).
    pub fn resolve(
        &self,
        adapter_name: Option<&str>,
        routing_key: Option<&str>,
    ) -> Option<LoraAdapter> {
        if let Some(name) = adapter_name {
            let adapter = self.get(name)?;
            return if adapter.active { Some(adapter) } else { None };
        }
        if let Some(key) = routing_key {
            let adapter = self.get_by_routing_key(key)?;
            return if adapter.active { Some(adapter) } else { None };
        }
        None
    }

    /// Check if any registered adapter targets a given module.
    pub fn active_for_module(&self, module_name: &str) -> bool {
        self.inner
            .read()
            .unwrap()
            .adapters
            .values()
            .filter(|a| a.active)
            .any(|a| a.config.target_modules.iter().any(|m| m == module_name))
    }
}

impl Default for LoraRegistry {
    fn default() -> Self {
        Self::new()
    }
}

// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn adapter_new_and_scaling() {
        let a = LoraAdapter::new("math", 64, 128);
        assert_eq!(a.name, "math");
        assert_eq!(a.config.rank, 64);
        assert_eq!(a.config.alpha, 128);
        assert!((a.scaling() - 2.0).abs() < f32::EPSILON);
        assert!(a.active);
    }

    #[test]
    fn adapter_builder_chain() {
        let a = LoraAdapter::new("code", 32, 32)
            .with_routing_key("task:code")
            .with_base_model("llama-7b-hash")
            .with_targets(vec!["q_proj".into(), "v_proj".into()]);
        assert_eq!(a.routing_key.as_deref(), Some("task:code"));
        assert_eq!(a.base_model_hash.as_deref(), Some("llama-7b-hash"));
        assert_eq!(a.config.target_modules, vec!["q_proj", "v_proj"]);
    }

    #[test]
    fn registry_register_and_get() {
        let reg = LoraRegistry::new();
        assert!(reg.is_empty());
        reg.register(LoraAdapter::new("a", 8, 8));
        reg.register(LoraAdapter::new("b", 16, 16));
        assert_eq!(reg.len(), 2);
        assert!(reg.get("a").is_some());
        assert!(reg.get("c").is_none());
    }

    #[test]
    fn registry_routing_key() {
        let reg = LoraRegistry::new();
        reg.register(LoraAdapter::new("math_adapter", 32, 32).with_routing_key("domain:math"));
        let found = reg.get_by_routing_key("domain:math");
        assert!(found.is_some());
        assert_eq!(found.unwrap().name, "math_adapter");
        assert!(reg.get_by_routing_key("domain:code").is_none());
    }

    #[test]
    fn registry_activate_deactivate() {
        let reg = LoraRegistry::new();
        reg.register(LoraAdapter::new("a", 8, 8));
        assert!(reg.activate("a"));
        assert!(reg.deactivate("a"));
        assert!(!reg.get("a").unwrap().active);
        assert!(reg.active_adapters().is_empty());
        assert_eq!(reg.all_adapters(), vec!["a"]);
    }

    #[test]
    fn registry_resolve_priority() {
        let reg = LoraRegistry::new();
        reg.register(LoraAdapter::new("explicit", 8, 8).with_routing_key("r1"));
        reg.register(LoraAdapter::new("routed", 8, 8).with_routing_key("r2"));

        // Explicit name takes priority.
        let a = reg.resolve(Some("explicit"), Some("r2"));
        assert_eq!(a.unwrap().name, "explicit");

        // Fallback to routing key.
        let a = reg.resolve(None, Some("r2"));
        assert_eq!(a.unwrap().name, "routed");

        // No match → None.
        assert!(reg.resolve(None, None).is_none());
    }

    #[test]
    fn registry_resolve_inactive_returns_none() {
        let reg = LoraRegistry::new();
        reg.register(LoraAdapter::new("a", 8, 8).with_routing_key("k"));
        reg.deactivate("a");
        assert!(reg.resolve(Some("a"), None).is_none());
        assert!(reg.resolve(None, Some("k")).is_none());
    }

    #[test]
    fn registry_active_for_module() {
        let reg = LoraRegistry::new();
        reg.register(
            LoraAdapter::new("a", 8, 8).with_targets(vec!["q_proj".into(), "v_proj".into()]),
        );
        assert!(reg.active_for_module("q_proj"));
        assert!(reg.active_for_module("v_proj"));
        assert!(!reg.active_for_module("gate_proj"));
    }

    #[test]
    fn registry_unregister() {
        let reg = LoraRegistry::new();
        reg.register(LoraAdapter::new("a", 8, 8).with_routing_key("k1"));
        let removed = reg.unregister("a");
        assert!(removed.is_some());
        assert!(reg.is_empty());
        assert!(reg.get_by_routing_key("k1").is_none());
    }
}