35 static constexpr const char*
names[] = {
"COLD",
"WARM",
"ACTIVE"};
36 int idx =
static_cast<int>(
s);
55 std::lock_guard<std::mutex> lock(transition_mutex_);
68 logger->info(
"[VRAM] Loading: {}",
config.
path.string());
69 auto start = entropic::log::now();
74 logger->error(
"[VRAM] Load failed: {}",
last_error_);
77 logger->info(
"[VRAM] Warm in {:.2f}ms", entropic::log::elapsed_ms(start, entropic::log::now()));
89 std::lock_guard<std::mutex> lock(transition_mutex_);
107 logger->info(
"[VRAM] activate() no-op: already ACTIVE");
111 logger->error(
"[VRAM] activate() failed: not WARM ({})",
state_name(
state()));
115 logger->info(
"[VRAM] Activating");
116 auto start = entropic::log::now();
119 logger->error(
"[VRAM] Activate failed: {}",
last_error_);
122 logger->info(
"[VRAM] Active in {:.2f}ms", entropic::log::elapsed_ms(start, entropic::log::now()));
133 std::lock_guard<std::mutex> lock(transition_mutex_);
140 logger->info(
"[VRAM] Deactivating");
141 auto start = entropic::log::now();
146 logger->info(
"[VRAM] Deactivated in {:.2f}ms", entropic::log::elapsed_ms(start, entropic::log::now()));
160 std::lock_guard<std::mutex> lock(transition_mutex_);
163 if (hooks_.fire_info !=
nullptr) {
164 std::string json =
"{\"state\":\""
166 hooks_.fire_info(hooks_.registry,
175 logger->info(
"[VRAM] Unloaded");
201 std::lock_guard<std::mutex> lock(transition_mutex_);
208 logger->info(
"[VRAM] Loading into target residency: {} (gpu_layers={})",
210 auto start = entropic::log::now();
215 logger->error(
"[VRAM] Load+activate failed: {}",
last_error_);
218 logger->info(
"[VRAM] Active in {:.2f}ms (one file read)",
219 entropic::log::elapsed_ms(start, entropic::log::now()));
235 const std::vector<Message>& messages,
241 err.error_message =
"generate() requires ACTIVE state";
242 err.finish_reason =
"error";
243 logger->error(
"{}",
err.error_message);
247 auto start = entropic::log::now();
249 result.generation_time_ms = entropic::log::elapsed_ms(start, entropic::log::now());
265 const std::vector<Message>& messages,
267 std::atomic<bool>&
cancel)
272 err.error_message =
"generate() requires ACTIVE state";
273 err.finish_reason =
"error";
274 logger->error(
"{}",
err.error_message);
278 auto start = entropic::log::now();
280 result.generation_time_ms = entropic::log::elapsed_ms(start, entropic::log::now());
294 const std::vector<std::vector<Message>>&
requests,
295 const std::vector<GenerationParams>&
params,
296 std::atomic<bool>&
cancel)
301 err.error_message =
"generate_batch() requires ACTIVE state";
302 err.finish_reason =
"error";
303 logger->error(
"{}",
err.error_message);
306 auto start = entropic::log::now();
308 double ms = entropic::log::elapsed_ms(start, entropic::log::now());
326 const std::vector<Message>& messages,
328 std::function<
void(std::string_view token)> on_token,
329 std::atomic<bool>&
cancel)
334 err.error_message =
"generate_streaming() requires ACTIVE state";
335 err.finish_reason =
"error";
336 logger->error(
"{}",
err.error_message);
340 auto start = entropic::log::now();
342 result.generation_time_ms = entropic::log::elapsed_ms(start, entropic::log::now());
363 const std::vector<Message>& messages,
365 std::function<
void(std::string_view token)> on_token,
366 std::atomic<bool>&
cancel)
372 "generate_speculative() requires ACTIVE state";
373 err.finish_reason =
"error";
374 logger->error(
"{}",
err.error_message);
377 auto start = entropic::log::now();
380 result.generation_time_ms =
381 entropic::log::elapsed_ms(start, entropic::log::now());
401 const std::vector<Message>& ,
403 std::function<
void(std::string_view)> ,
409 "speculative decoding not implemented for this backend";
423 const std::string& prompt,
429 err.error_message =
"complete() requires ACTIVE state";
430 err.finish_reason =
"error";
431 logger->error(
"{}",
err.error_message);
435 auto start = entropic::log::now();
437 result.generation_time_ms = entropic::log::elapsed_ms(start, entropic::log::now());
464 logger->error(
"evaluate_logprobs: model not ACTIVE (state={})",
466 throw std::runtime_error(
"Model must be ACTIVE for evaluation");
470 logger->error(
"evaluate_logprobs: need >= 2 tokens, got {}",
472 throw std::runtime_error(
473 "Need at least 2 tokens for logprob evaluation");
476 std::lock_guard<std::mutex> lock(eval_mutex_);
478 logger->info(
"evaluate_logprobs: {} tokens, first=[{},{},{}...]",
480 n_tokens > 1 ?
tokens[1] : 0,
481 n_tokens > 2 ?
tokens[2] : 0);
482 auto start = entropic::log::now();
494 auto ms = entropic::log::elapsed_ms(start, entropic::log::now());
495 logger->info(
"evaluate_logprobs: perplexity={:.2f}, "
496 "total_lp={:.4f}, {:.2f}ms",
499 logger->info(
" logprob[{}]={:.4f}",
i, result.
logprobs[
i]);
534 if (hooks_.fire_pre ==
nullptr) {
537 std::string json =
"{\"model_path\":\""
540 int rc = hooks_.fire_pre(hooks_.registry,
544 logger->info(
"[VRAM] ON_MODEL_LOAD hook cancelled");
562 return static_cast<int>(text.size()) / 4;
592 std::vector<BackendCapability> result;
597 result.push_back(
cap);
626 int seq_id, std::vector<uint8_t>&
buffer)
const
632 auto start = entropic::log::now();
635 logger->info(
"save_state: seq={} {}B {:.2f}ms",
636 seq_id,
buffer.size(), entropic::log::elapsed_ms(start, entropic::log::now()));
651 int seq_id,
const std::vector<uint8_t>&
buffer)
654 logger->warn(
"restore_state: not ACTIVE ({})",
658 auto start = entropic::log::now();
661 logger->info(
"restore_state: seq={} {}B {:.2f}ms",
662 seq_id,
buffer.size(), entropic::log::elapsed_ms(start, entropic::log::now()));
677 logger->warn(
"clear_state: model is COLD");
682 logger->info(
"clear_state: seq={}", seq_id);
700 const std::vector<Message>& messages,
706 err.error_message =
"generate_seq() requires ACTIVE state";
707 err.finish_reason =
"error";
708 logger->error(
"{}",
err.error_message);
712 auto start = entropic::log::now();
714 result.generation_time_ms = entropic::log::elapsed_ms(start, entropic::log::now());
715 result.seq_id = seq_id;
732 const std::vector<Message>& messages,
734 std::function<
void(std::string_view token)> on_token,
735 std::atomic<bool>&
cancel)
741 "generate_streaming_seq() requires ACTIVE state";
742 err.finish_reason =
"error";
743 logger->error(
"{}",
err.error_message);
747 auto start = entropic::log::now();
750 result.generation_time_ms = entropic::log::elapsed_ms(start, entropic::log::now());
751 result.seq_id = seq_id;
789 int , std::vector<uint8_t>& )
const
803 int ,
const std::vector<uint8_t>& )
830 const std::vector<Message>& messages,
849 const std::vector<Message>& messages,
851 std::function<
void(std::string_view token)> on_token,
852 std::atomic<bool>&
cancel)
bool cancelled
The run was interrupted and the group was killed (gh#172)
virtual bool do_load_active(const ModelConfig &config)
Load COLD → ACTIVE in one step.
virtual GenerationResult do_complete(const std::string &prompt, const GenerationParams ¶ms)=0
Subclass raw completion.
virtual GenerationResult do_generate_streaming_seq(int seq_id, const std::vector< Message > &messages, const GenerationParams ¶ms, std::function< void(std::string_view token)> on_token, std::atomic< bool > &cancel)
Streaming generation with sequence ID.
GenerationResult generate_seq(int seq_id, const std::vector< Message > &messages, const GenerationParams ¶ms)
Generate with explicit sequence ID.
virtual LogprobResult do_evaluate_logprobs(const int32_t *tokens, int n_tokens)=0
Backend-specific logprob evaluation.
GenerationResult generate_speculative(const std::vector< Message > &messages, const GenerationParams ¶ms, std::function< void(std::string_view token)> on_token, std::atomic< bool > &cancel)
Generate via the speculative-decoding kernel (v2.1.11).
float compute_perplexity(const int32_t *tokens, int n_tokens)
Compute perplexity for a token sequence.
virtual std::vector< GenerationResult > do_generate_batch(const std::vector< std::vector< Message > > &requests, const std::vector< GenerationParams > ¶ms, std::atomic< bool > &cancel)
Subclass same-prefix batch generation (gh#98, v2.8.0).
std::string last_error_
Last error message for diagnostics.
virtual BackendInfo do_info() const
Populate backend metadata.
virtual GenerationResult do_generate_streaming(const std::vector< Message > &messages, const GenerationParams ¶ms, std::function< void(std::string_view token)> on_token, std::atomic< bool > &cancel)=0
Subclass streaming generation.
bool save_state(int seq_id, std::vector< uint8_t > &buffer) const
Save model state to buffer.
bool supports(BackendCapability cap) const
Query whether this backend supports a capability.
bool restore_state(int seq_id, const std::vector< uint8_t > &buffer)
Restore model state from buffer.
bool activate()
Promote to GPU (WARM → ACTIVE).
virtual bool do_restore_state(int seq_id, const std::vector< uint8_t > &buffer)
Restore model state.
virtual int do_count_tokens(const std::string &text) const =0
Subclass token counting.
virtual bool do_supports(BackendCapability cap) const
Declare supported capabilities.
void deactivate()
Release GPU layers (ACTIVE → WARM).
bool promote_to_active()
WARM → ACTIVE promotion.
virtual void do_unload()=0
Full unload.
virtual bool do_activate()=0
Promote loaded model to GPU.
BackendInfo info() const
Get backend metadata.
bool is_active() const
True when state is ACTIVE.
ModelState state() const
Current lifecycle state (lock-free read).
virtual bool do_load(const ModelConfig &config)=0
Load model into CPU RAM.
virtual GenerationResult do_generate_speculative(const std::vector< Message > &messages, const GenerationParams ¶ms, std::function< void(std::string_view token)> on_token, std::atomic< bool > &cancel)
Subclass speculative-decoding streaming generation.
virtual void do_deactivate()=0
Release GPU, keep CPU.
virtual GenerationResult do_generate(const std::vector< Message > &messages, const GenerationParams ¶ms)=0
Subclass generation.
std::vector< BackendCapability > capabilities() const
Get all supported capabilities as a vector.
void unload()
Full unload (→ COLD).
const ModelConfig & config() const
Stored model config.
bool clear_state(int seq_id=-1)
Clear/reset model state for a sequence.
virtual GenerationResult do_generate_seq(int seq_id, const std::vector< Message > &messages, const GenerationParams ¶ms)
Generate with sequence ID.
virtual std::string do_backend_name() const =0
Return backend name identifier.
bool is_loaded() const
True when state is WARM or ACTIVE.
std::vector< GenerationResult > generate_batch(const std::vector< std::vector< Message > > &requests, const std::vector< GenerationParams > ¶ms, std::atomic< bool > &cancel)
Generate N independent same-prefix requests together.
GenerationResult generate(const std::vector< Message > &messages, const GenerationParams ¶ms)
Generate a complete response.
bool load(const ModelConfig &config)
Load model into CPU RAM (COLD → WARM).
virtual bool do_clear_state(int seq_id)
Clear/reset model state.
int count_tokens(const std::string &text) const
Count tokens using model's tokenizer.
virtual bool do_save_state(int seq_id, std::vector< uint8_t > &buffer) const
Save model state (KV cache or hidden state).
bool fire_model_load_hook(const ModelConfig &config)
Fire ON_MODEL_LOAD pre-hook.
GenerationResult generate_streaming(const std::vector< Message > &messages, const GenerationParams ¶ms, std::function< void(std::string_view token)> on_token, std::atomic< bool > &cancel)
Generate with per-token streaming callback.
LogprobResult evaluate_logprobs(const int32_t *tokens, int n_tokens)
Evaluate per-token log-probabilities for a token sequence.
bool load_and_activate(const ModelConfig &config)
Bring a model to ACTIVE in as few file reads as possible.
GenerationResult complete(const std::string &prompt, const GenerationParams ¶ms)
Raw text completion without chat template.
GenerationResult generate_streaming_seq(int seq_id, const std::vector< Message > &messages, const GenerationParams ¶ms, std::function< void(std::string_view token)> on_token, std::atomic< bool > &cancel)
Streaming generation with explicit sequence ID.
std::atomic< ModelState > state_
State transition slot accessible to subclasses for test-only injection.
int * cancel
Consumer's cancel flag, nullable.
@ ENTROPIC_ERROR_NOT_SUPPORTED
Capability not supported by this backend (v1.9.13)
@ ENTROPIC_ERROR_INVALID_STATE
Operation not valid in current state (e.g., generate before activate)
std::string err
size_exceeded, when set
@ ENTROPIC_HOOK_ON_MODEL_UNLOAD
14: Model unloaded from backend
@ ENTROPIC_HOOK_ON_MODEL_LOAD
13: Model loaded into backend
InferenceBackend concrete base class.
spdlog initialization and logger access.
ENTROPIC_EXPORT std::shared_ptr< spdlog::logger > get(const std::string &name)
Get or create a named logger.
Activate model on GPU (WARM → ACTIVE).
BackendCapability
Capabilities that an inference backend may or may not support.
@ _COUNT
Sentinel — must be last. Used for iteration/array sizing.
@ tokens
Gate on generated tokens since the last tool call.
@ ok
Tool dispatched, returned non-empty content.
@ optional
optional: every escape asks the host's path approver
@ count
Sentinel — MUST remain last.
ModelState
C++ enum class for model VRAM lifecycle states.
@ WARM
mmap'd + mlock'd in RAM
@ ACTIVE
GPU layers loaded, full speed.
@ COLD
On disk only, no RAM consumed.
Backend metadata for introspection.
std::string name
Backend identifier (e.g. "llama.cpp", "axcl")
Generation parameters for a single inference call.
Result of a single generation call.
entropic_error_t error_code
Error code (ENTROPIC_OK if no error)
std::string finish_reason
Finish reason: "stop", "length", "error".
std::string error_message
Error description (empty if no error)
Per-token log-probability evaluation result.
std::vector< float > logprobs
Log-prob for each token transition (N-1 values)
int n_logprobs
Number of logprob values (n_tokens - 1)
float total_logprob
Sum of all logprob values.
float perplexity
exp(-mean(logprobs)) — lower = less surprising
Model configuration for a single tier.
int gpu_layers
GPU offload layers (-1 = all)
std::filesystem::path path
Resolved model file path.