Weight residency¶
Barrel inference loads a model by mmapping its GGUF, so weights live in
the kernel's page cache rather than the BEAM heap. The weight_residency
knob controls which of those pages the kernel pulls in eagerly,
whether they get pinned, and what your process actually accounts for in
RSS. Four modes ship; one is the default; the rest exist for specific
operational shapes.
The right mode rarely affects tokens-per-second on a given box — pages that are resident cost the same to read regardless of which knob put them there. The point is which pages are resident when.
The four modes¶
| mode | use_mmap | use_mlock | madvise | meant for |
|---|---|---|---|---|
eager (default) |
true | false | WILLNEED |
warm boxes, single-tenant, plenty of RAM |
lazy |
true | false | RANDOM |
sparse-prompt agent workloads, multi-tenant boxes that share one model |
pinned |
true | true | WILLNEED |
latency-sensitive fleet nodes that cannot tolerate paging jitter |
lazy_then_pin_resident |
true | false then true on first request done | RANDOM then WILLNEED |
"discover the working set, then pin it"; closest to the per-prompt expert routing pattern from Apple's AFM3 / instruction-following pruning research |
When each makes sense¶
eager: the right default. The kernel reads weights ahead at load time so the first prefill does not stall waiting on the disk. If you have enough RAM and one model per box, leave this on.lazy: skip the read-ahead. Weights page in only when a prompt touches them. Process RSS stays small (on macOS the shared file-backed pages are not billed against the BEAM until they are mlocked). The first prefill takes a hair longer; steady-state tok/s is identical.pinned:mlockthe entire model. Pages cannot be evicted under memory pressure. Predictable latency, biggest RSS bill. Use when a jitter SLO matters and the host has spare RAM.lazy_then_pin_resident: load lazy, then after the first request completes the scheduler callsmincore(2)to find which pages got faulted in andmlock(2)s just that set. New regions touched on later prompts still page in lazily but are not pinned. One-shot per model load; failures (e.g.RLIMIT_MEMLOCKexhausted) are logged and the model continues unpinned.
Setting the mode¶
Three places, in precedence order (top wins):
-
Modelfile parameter (override per running model)
-
Manifest (set at pull time or via
/api/edit) -
App env default (fleet-wide)
Unknown values fall back to the default with a logger:warning/2; they
do not refuse the load.
Observability¶
The Prometheus gauge barrel_inference_resident_bytes{model=...} reports
the bytes of a loaded model's mmap regions currently faulted in. It's
sampled per /metrics scrape via mincore(2) and reports the same
quantity as the runtime API barrel_inference:resident_bytes/1. Note
that mincore counts pages in the page cache regardless of which
process triggered them, so once a host has loaded any copy of the
model, the gauge will saturate at that model's effective working-set
size.
For one-shot diagnostic checks, call from a remote shell:
Pair with process RSS (ps -o rss= -p <pid>) to see the lazy / pinned
distinction: on macOS shared file-backed pages do not count against the
BEAM's RSS unless they are mlocked, so a lazy model with 13 GB
resident may show only a few hundred MB of process RSS.
Bench numbers¶
See Weight residency bench for measured values across the four modes on Devstral 24B Q4 on an Apple M4 Pro. Headline:
| mode | RSS @ idle | mincore-resident | tok/s |
|---|---|---|---|
eager |
8.5 GB | 13.3 GB | 7.8 |
lazy |
0.7 GB | 13.3 GB | 7.8 |
pinned |
14.0 GB | 13.3 GB | 7.7 |
lazy_then_pin_resident |
14.0 GB (post-first-request) | 13.3 GB | 7.7 |
Platform caveats¶
- macOS:
mlockneedsRLIMIT_MEMLOCKraised. The hard limit on a default workstation is enough for a 24 GB model; for larger models or multi-model boxes raise it vialaunchctl limit memlock. - Linux:
mlockneedsCAP_IPC_LOCKor a raisedmemlocklimit (security.conf). Containerised deployments typically pass--cap-add IPC_LOCKor setmemlock: -1in the compose file. - All
mlockfailures (EAGAIN,EPERM,ENOMEM) degrade to a logger warning, not a crash. The model keeps serving; the gauge will show the actual resident set so you can confirm the pin did not happen.
How it maps to llama.cpp¶
Internally the mode resolves to a triple on llama_model_params:
| mode | use_mmap |
use_mlock |
prefetch |
|---|---|---|---|
eager |
true | false | true |
lazy |
true | false | false |
pinned |
true | true | true |
lazy_then_pin_resident |
true | false | false |
The prefetch field is a barrel-local addition to the vendored
llama.cpp tree (it threads through llama_model_loader::init_mappings
to flip posix_madvise between WILLNEED and RANDOM). The
post-load mlock for lazy_then_pin_resident uses two more local
accessors (llama_model_n_mappings, llama_model_get_mapping) so the
NIF can call mincore/mlock against the model's mmap regions without
breaking llama.cpp's pimpl encapsulation.