Entropic 2.14.3
Local-first agentic inference engine
Loading...
Searching...
No Matches
vram_footprint.h
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
60#pragma once
61
62#include <algorithm>
63#include <cstdint>
64#include <string>
65#include <utility>
66
67namespace entropic {
68
70constexpr uint64_t kBaseKvPerTokenF16 = 16ull * 1024ull;
71
73constexpr int kAllLayersSentinel = 99;
74
76enum class Offload {
77 none,
78 full,
80};
81
130
133 bool known = false;
135 const char* reason = "";
136};
137
151inline double kv_scale_for_cache_type(const std::string& type) {
152 static const std::pair<const char*, double> kScales[] = {
153 {"f32", 2.0}, // 4 B/elem
154 {"f16", 1.0}, // 2 B/elem — the baseline
155 {"bf16", 1.0}, // 2 B/elem
156 {"q8_0", 0.53125}, // 34 B / 32 elem = 1.0625 B/elem
157 {"q5_1", 0.375}, // 24 B / 32 elem = 0.75 B/elem
158 {"q5_0", 0.34375}, // 22 B / 32 elem = 0.6875 B/elem
159 {"q4_1", 0.3125}, // 20 B / 32 elem = 0.625 B/elem
160 {"q4_0", 0.28125}, // 18 B / 32 elem = 0.5625 B/elem
161 };
162 double scale = 1.0;
163 for (const auto& entry : kScales) {
164 if (type == entry.first) {
165 scale = entry.second;
166 break;
167 }
168 }
169 return scale;
170}
171
184inline Offload classify_offload(int gpu_layers) {
188 } else if (gpu_layers == 0) {
190 }
191 return placement;
192}
193
205inline double kv_bytes_per_token(const FootprintInputs& in) {
206 const double avg = (kv_scale_for_cache_type(in.cache_type_k)
207 + kv_scale_for_cache_type(in.cache_type_v)) / 2.0;
208 return static_cast<double>(kBaseKvPerTokenF16) * avg;
209}
210
221 const Offload placement = classify_offload(in.gpu_layers);
222 // v2.13.1: a partial offload IS priceable once the GGUF's shape has been
223 // read. Without it we still decline, because guessing here refuses
224 // configurations that demonstrably work (gh#142) — but the shape is now
225 // available from a metadata-only read, so declining is the fallback
226 // rather than the rule.
227 const bool have_shape = in.block_count > 0 && in.block_bytes > 0;
229 est.reason = "partial offload: layer count is not knowable without "
230 "GGUF metadata, so the budget gate stays open";
231 return est;
232 }
233 uint64_t resident = 0;
234 if (placement == Offload::full) {
235 // The projector follows the weights: offload none and it lives in
236 // host RAM.
237 resident = in.weights_bytes + in.mmproj_bytes;
238 } else if (placement == Offload::partial_unknown) {
239 // Per-block cost from the block tensors ALONE. Dividing the whole
240 // file by the layer count folds in embeddings and the output head,
241 // which do not move with `gpu_layers`, and so overstates every
242 // layer.
243 const auto per_block =
244 in.block_bytes / static_cast<uint64_t>(in.block_count);
245 const auto offloaded = static_cast<uint64_t>(in.gpu_layers);
246 resident = per_block * offloaded;
247 }
248 // Experts held host-side by `cpu_moe_layers` are not on the card.
249 resident -= std::min(resident, in.host_expert_bytes);
250 // The draft head is resident whenever speculative decode is configured,
251 // at any placement.
252 resident += in.draft_bytes;
253 const int ctx = in.context_length > 0 ? in.context_length : 0;
254 const int sessions = in.max_sessions > 0 ? in.max_sessions : 1;
255 // gh#144 (v2.12.0): context_length is per session; the pool allocates
256 // ctx * sessions cells. Deliberately an over-count for an iSWA model
257 // like Gemma-4: kBaseKvPerTokenF16 is a flat rate and does not model
258 // sliding-window layers (5:1 SWA at window 512 under swa_full=false),
259 // so the real figure is well below this. That is the safe direction
260 // per this header's own rule — never silently under-count.
261 const uint64_t kv = static_cast<uint64_t>(
262 static_cast<double>(ctx) * static_cast<double>(sessions)
264 est.known = true;
265 est.bytes = resident + kv
266 + static_cast<uint64_t>(in.vram_reserve_mb) * 1024ull * 1024ull;
267 return est;
268}
269
289 if (!fixed.known || fixed.bytes >= available_bytes) {
290 return 0;
291 }
292 // gh#144 (v2.12.0): divide the budget across the pool, or the
293 // recommendation is a PER-SESSION window N times too large — handing an
294 // operator a config that aborts, from the very function whose job is to
295 // offer one that works.
296 const int sessions = in.max_sessions > 0 ? in.max_sessions : 1;
297 const double per_token =
298 kv_bytes_per_token(in) * static_cast<double>(sessions);
299 const uint64_t kv_budget = available_bytes - fixed.bytes;
300 const uint64_t fits = static_cast<uint64_t>(
301 static_cast<double>(kv_budget) / per_token);
302 const uint64_t requested = in.context_length > 0
303 ? static_cast<uint64_t>(in.context_length) : 0ull;
305 chosen -= chosen % 512ull;
306 return static_cast<int>(chosen);
307}
308
309} // namespace entropic
Activate model on GPU (WARM → ACTIVE).
FootprintEstimate estimate_vram_footprint(const FootprintInputs &in)
Estimate the VRAM a tier will occupy, or report that it cannot.
@ optional
optional: every escape asks the host's path approver
constexpr uint64_t kBaseKvPerTokenF16
KV bytes per token at f16, the rate the v2.2.4 estimate assumed.
constexpr int kAllLayersSentinel
gpu_layers at or above this means "every layer" (llama.cpp's 99).
double kv_scale_for_cache_type(const std::string &type)
Cost of a KV cache type relative to f16.
Offload classify_offload(int gpu_layers)
Classify a tier's offload request into a priceable placement.
@ none
Child tools share the session's working dir (default)
double kv_bytes_per_token(const FootprintInputs &in)
Per-token KV cost in bytes for this tier's cache configuration.
int recommend_context_length(const FootprintInputs &in, uint64_t available_bytes)
Largest context length that fits, for the "won't fit" recommendation.
Offload
Where a tier's weights land, as far as the estimate can tell.
@ none
gpu_layers == 0: weights cost no VRAM.
@ partial_unknown
A layer count we cannot price without GGUF metadata.
@ full
-1 or >= 99: the whole file is resident.
An estimate, or an explicit admission that there isn't one.
bool known
False means "do not gate on this".
uint64_t bytes
Estimated VRAM bytes; 0 when not known.
const char * reason
Why it is unknown, for the log.
Everything the estimate needs, with no orchestrator or filesystem.
int vram_reserve_mb
Configured headroom to leave free.
uint64_t non_block_bytes
Embeddings/output/final norms.
uint64_t mmproj_bytes
Size of the vision projector, 0 if none.
uint64_t block_bytes
Total bytes of per-block tensors.
uint64_t draft_bytes
Draft / MTP head bytes, resident alongside the target.
int max_sessions
Resident conversation sessions this tier keeps KV for (gh#144).
uint64_t host_expert_bytes
Expert bytes deliberately kept host-side by cpu_moe_layers.
int context_length
Tier's requested context window.
std::string cache_type_k
KV key cache quantization.
int block_count
Real layer count from <arch>.block_count.
uint64_t weights_bytes
Size of the tier's GGUF on disk.
std::string cache_type_v
KV value cache quantization.
int gpu_layers
Tier's requested offload (-1 = all).