Repository navigation
v0.2.0
Smarter residency on one card: models are evicted by what they would cost
to lose, models that do not fit together take turns instead of thrashing,
no class starves, and a request can name a model family and get the variant
that fits the moment. Measured on an RTX 4060 Ti 16 GB with llama.cpp b11060.
Added
- Model families (
families:withpreferred/balanced/compact
tiers and aliases). Interactive work gets the best variant it can have
(evicting as for a plain model, falling back only when the better ones
cannot be made resident); background and batch work get the best variant
that runs without evicting anything or sharing the model the user is
chatting with."gridcore": {"quality": ...}/X-GridCore-Quality
excludes lower tiers; requests with images (audio) only go to variants
with thevision(new:audio) capability. Responses name the variant
(X-GridCore-Model,X-GridCore-Family);variantevents explain the
choice;gridcore_variant_selected_total{family,variant,class};
/v1/modelslists families with their variants. - Cost-based eviction (
policy.eviction: cost, the default): among the
models the residency rules allow, the scheduler evicts the set that is
cheapest to lose — reload time times recent demand, a request count that
halves every 5 minutes and is weighted by class priority. Equal costs
fall back to LRU;eviction: lrurestores the 0.1 order. Each resident
model's cost is in/admin/state(evict_cost) andgridcore status,
andevictevents state it. policy.min_residency(default 30s) against model thrash: batch work
does not evict a model background used that recently, and a model loaded
for background (batch) work is not replaced by other background (batch)
work before it has been resident that long. Interactive work is not held
back. Two models 200 MB over budget were reloaded 60 times in 5 minutes by
0.1, 5 times by 0.2.policy.background_share(default 0.5) caps how much of the time under
continuous interactive load background/batch steps run alongside it:
after a step of length d the next one waits d·(1/share − 1), so steps of
any length add up to the share. With six chatting clients it took chat
p50 from 4.0 s to 2.7 s (p95 4.8 → 3.5 s) and halved background
throughput while the chat lasted;1is the 0.1 behaviour.policy.batch_max_starvation(default 2m): a batch job that has made no
progress for this long gets one step even while background has work
queued, and may take a model background is using (never a hot or pinned
one).0srestores strict ordering.- Thrash detection: a model loaded 6 times within 5 minutes produces a
thrashevent, a warning with a hint andgridcore_model_thrash_total. - A job waiting for VRAM says which models are kept and why
(waiting for VRAM (kept: e4b in use by background)); a job held back by
the share says when its next step may run. gridcore profileslists stored measurements and says which ones the
scheduler would use and why the others no longer apply (other runtime
build, other GPU, changed model settings, model gone from the config);
gridcore profiles prune [--dry-run]removes the stale ones.scripts/loadtest -scenario ambient: chat, a coding agent, screenshot
analysis, a file indexer and OCR on one GPU, with per-role latency, the
model that served each role, and loads / evictions / thrash from
/metrics.- VRAM estimate honours the llama.cpp offload flags in a model's args
(-ngl/--n-gpu-layers,--cpu-moe,--n-cpu-moe,-ot <regex>=CPU)
and skips multi-token-prediction blocks, so models larger than the card
(Qwen3.8-27B with-ngl 64) and MoE models with experts in host RAM
(Ornith-1.5-35B-A3B with--cpu-moe) are admitted without a prior
gridcore bench.gridcore models --explainshows the host part. gridcore statusdashboard: colors, per-model VRAM bars, relative event
times, flicker-free--watch; honours--no-colorandNO_COLOR.- Demo tools:
make toolsbuildsbin/gc-chat(streaming chat with queue
time, TTFT and tokens/s) andbin/gc-indexer(background embedding or
LLM classification load with a progress bar that visibly pauses);
scripts/demo-tmux.shopens the demo layout (--simulationworks without a
GPU);scripts/gpu-watch.shlists GPU processes by GridCore model. config.example.yaml: agemma4family, Qwen3.8-27B (IQ4_XS on the GPU,
Q4_K_M with one layer on the CPU) and Ornith-1.5-35B-A3B with experts in
RAM;examples/simulation.yaml: aqwen3family to try without a GPU.
Changed
- Measured profiles are keyed by the llama.cpp build (
llama-server --version: build number, commit, GPU backend) instead of the binary's
path, mtime and size, so reinstalling or redeploying the same build keeps
them. Profiles recorded by 0.1.0 for the binary that is installed now are
carried over on start;gridcore profiles pruneremoves the old entries. - README: a coding agent that works on its own should send
background.
In the acceptance scenario that cost the agent ~10% and gave the indexer
next to it 4.6 times the throughput. - CI and release builds use the Go toolchain pinned in
go.mod
(toolchain go1.26.3) instead of Go 1.25.0; the minimum Go for building
from source is still 1.25. GitHub Actions moved to Node 24 releases. make deploychecks forrsyncandHOSTbefore building.
Fixed
gridcore bench(or a prune) while the daemon was running lost its
results: the daemon rewroteprofiles.jsonfrom memory on its next
update. The store now merges changes made by other processes before it
writes, and skips writes that change nothing (it used to rewrite the
file on every GPU poll).- An instance that was exiting between the two
nvidia-smiqueries made
its memory look like external usage for one poll, and the planner
evicted a second, healthy model to make room. - The orphan reaper identifies processes by command line and port instead
of/proc/<pid>/exe, which pointed at the interpreter for wrapped
runtimes and leaked the child.