19#include <nlohmann/json.hpp>
38void apply_adapter(llama_context* ctx,
39 llama_adapter_lora* adapter,
float scale)
41 llama_set_adapters_lora(ctx, &adapter, 1, &scale);
50void clear_adapters(llama_context* ctx) {
51 llama_set_adapters_lora(ctx,
nullptr, 0,
nullptr);
75 const std::string& name,
76 const std::filesystem::path& adapter_path,
80 std::lock_guard<std::mutex> lock(adapter_mutex_);
82 bool precondition_failed =
83 adapters_.find(name) != adapters_.end() || !model;
84 if (precondition_failed) {
85 logger->error(
"Cannot load adapter '{}': {}",
86 name, !model ?
"base model is null" :
"duplicate name");
91 auto* lora = llama_adapter_lora_init(model, adapter_path.c_str());
93 logger->error(
"llama_adapter_lora_init failed for '{}' at {}",
94 name, adapter_path.string());
100 entry.path = adapter_path;
105 adapters_.emplace(name, std::move(entry));
107 logger->info(
"Loaded adapter '{}' from {} in {:.1f}ms (scale={:.2f})",
108 name, adapter_path.string(),
109 elapsed_ms(t_start, now()), scale);
127 std::lock_guard<std::mutex> lock(adapter_mutex_);
129 auto it = adapters_.find(name);
130 if (it == adapters_.end()) {
134 auto& entry = it->second;
137 active_name_.clear();
141 llama_adapter_lora_free(entry.handle);
142 entry.handle =
nullptr;
146 logger->info(
"Unloaded adapter '{}'", name);
168 std::lock_guard<std::mutex> lock(adapter_mutex_);
170 auto it = adapters_.find(name);
171 bool cannot_activate =
176 if (cannot_activate) {
177 logger->error(
"Cannot activate adapter '{}': {}",
178 name, it == adapters_.end() ?
"not found" :
"state is COLD");
182 auto& entry = it->second;
185 if (!active_name_.empty() && active_name_ != name) {
186 auto active_it = adapters_.find(active_name_);
187 if (active_it != adapters_.end()) {
192 apply_adapter(ctx, entry.handle, entry.scale);
196 logger->info(
"Activated adapter '{}' (scale={:.2f})", name, entry.scale);
212 std::lock_guard<std::mutex> lock(adapter_mutex_);
214 if (active_name_.empty()) {
218 auto it = adapters_.find(active_name_);
219 if (it != adapters_.end()) {
226 logger->info(
"Deactivated adapter '{}'", active_name_);
227 active_name_.clear();
247 std::lock_guard<std::mutex> lock(adapter_mutex_);
249 auto it = adapters_.find(name);
250 bool cannot_swap = it == adapters_.end()
252 if (active_name_ == name && !cannot_swap) {
255 if (cannot_swap || !fire_swap_hook(active_name_, name, it->second.path)) {
256 logger->error(
"Cannot swap to adapter '{}': {}",
257 name, cannot_swap ?
"not found or COLD" :
"cancelled by hook");
261 auto t_start = now();
264 if (!active_name_.empty()) {
265 auto active_it = adapters_.find(active_name_);
266 if (active_it != adapters_.end()) {
272 auto& target = it->second;
273 apply_adapter(ctx, target.handle, target.scale);
276 std::string previous = active_name_;
279 logger->info(
"Swapped adapter '{}' -> '{}' in {:.1f}ms",
280 previous, name, elapsed_ms(t_start, now()));
298 llama_model* model, llama_context* ctx)
300 std::lock_guard<std::mutex> lock(adapter_mutex_);
302 bool cleared_context =
false;
303 std::vector<std::string> to_remove;
305 for (
auto& [name, entry] : adapters_) {
306 if (entry.model != model) {
311 && ctx && !cleared_context)
314 cleared_context =
true;
318 llama_adapter_lora_free(entry.handle);
319 entry.handle =
nullptr;
323 to_remove.push_back(name);
326 for (
const auto& name : to_remove) {
327 if (name == active_name_) {
328 active_name_.clear();
330 adapters_.erase(name);
333 logger->info(
"Unloaded {} adapter(s) for model", to_remove.size());
349 std::lock_guard<std::mutex> lock(adapter_mutex_);
351 for (
auto& [name, entry] : adapters_) {
353 llama_adapter_lora_free(entry.handle);
354 entry.handle =
nullptr;
360 active_name_.clear();
362 logger->info(
"Unloaded all {} adapter(s) on shutdown", freed);
376 std::lock_guard<std::mutex> lock(adapter_mutex_);
377 auto it = adapters_.find(name);
378 if (it == adapters_.end()) {
381 return it->second.state;
392 std::lock_guard<std::mutex> lock(adapter_mutex_);
393 auto it = adapters_.find(name);
394 if (it == adapters_.end()) {
397 return make_info(it->second);
407 std::lock_guard<std::mutex> lock(adapter_mutex_);
408 std::vector<AdapterInfo> result;
409 result.reserve(adapters_.size());
410 for (
const auto& [name, entry] : adapters_) {
411 result.push_back(make_info(entry));
424 std::lock_guard<std::mutex> lock(adapter_mutex_);
447AdapterInfo AdapterManager::make_info(
const AdapterEntry& entry) {
473bool AdapterManager::fire_swap_hook(
474 const std::string& current,
475 const std::string& target,
476 const std::filesystem::path& target_path)
478 if (!hooks_.fire_pre || !hooks_.registry) {
483 ctx[
"current_adapter"] = current;
484 ctx[
"target_adapter"] = target;
485 ctx[
"adapter_path"] = target_path.string();
486 std::string ctx_str = ctx.dump();
488 char* modified =
nullptr;
489 int rc = hooks_.fire_pre(
AdapterManager — LoRA adapter lifecycle and hot-swap.
bool swap(const std::string &name, llama_context *ctx)
Swap to a different adapter atomically.
std::vector< AdapterInfo > list_adapters() const
List all known adapters.
bool activate(const std::string &name, llama_context *ctx)
Activate adapter on context (WARM -> HOT).
std::string active_adapter() const
Get the currently HOT adapter name.
void unload_all_for_model(llama_model *model, llama_context *ctx)
Unload all adapters for a given base model.
void deactivate(llama_context *ctx)
Deactivate current HOT adapter (HOT -> WARM).
bool load(const std::string &name, const std::filesystem::path &adapter_path, llama_model *model, float scale=1.0f)
Load a LoRA adapter into RAM (COLD -> WARM).
void unload(const std::string &name, llama_context *ctx)
Unload adapter (any state -> COLD).
AdapterInfo info(const std::string &name) const
Get metadata for an adapter.
void unload_all()
Free every loaded adapter handle (gh#58 close-out, v2.3.0).
AdapterState state(const std::string &name) const
Get adapter state.
void set_hook_interface(const HookInterface &hooks)
Set hook interface for ON_ADAPTER_SWAP dispatch.
@ ENTROPIC_HOOK_ON_ADAPTER_SWAP
16: Adapter/LoRA swap requested
spdlog initialization and logger access.
auto now()
Get current time for timing measurements.
ENTROPIC_EXPORT std::shared_ptr< spdlog::logger > get(const std::string &name)
Get or create a named logger.
double elapsed_ms(std::chrono::steady_clock::time_point start, std::chrono::steady_clock::time_point end)
Compute elapsed milliseconds between two time points.
Activate model on GPU (WARM → ACTIVE).
AdapterState
LoRA adapter lifecycle state.
@ WARM
Loaded in RAM via llama_adapter_lora_init(). Ready to activate.
@ COLD
Not loaded. No resources consumed.
@ HOT
Active on context via llama_set_adapter_lora(). Influencing generation.
Metadata for a loaded LoRA adapter.
std::string tier_name
Tier this adapter is assigned to (empty = unassigned)
size_t ram_bytes
RAM consumption when WARM/HOT (0 if COLD)
std::filesystem::path path
Resolved path to .gguf adapter file.
AdapterState state
Current lifecycle state.
std::string name
Unique adapter identifier.
std::unordered_map< std::string, std::string > metadata
Adapter-specific metadata for routing decisions.
float scale
LoRA scaling factor (alpha/rank)