Skip to content

Implement ADR-018 (persistent model + stateful sessions) + P0 performance ADRs#10

Merged
Tovli merged 3 commits into
masterfrom
ktlo/performance-work
Jun 22, 2026
Merged

Implement ADR-018 (persistent model + stateful sessions) + P0 performance ADRs#10
Tovli merged 3 commits into
masterfrom
ktlo/performance-work

Conversation

@Tovli

@Tovli Tovli commented Jun 22, 2026

Copy link
Copy Markdown
Owner

Summary

Authors the first-milestone (EPIC 1–4) ADRs for the edge-inference performance work derived from docs/research/improvements-plan.md, and implements ADR-018 (persistent model instances + stateful inference sessions).

Headline change: the local Qwen chat backend now loads model weights once and reuses a resident session across turns, instead of rebuilding the engine and re-reading the GGUF on every chat — the dominant per-turn cost for a small on-device model.

ADRs (docs)

ADR Decision Status
018 Persistent model instances + stateful sessions implemented here
019 In-loop incremental decoding + token streaming proposed
020 Batched (single-pass) prefill proposed
021 Memory-mapped verified GGUF loading proposed
022 Two-tier quantized KV cache + attention-aware eviction proposed
023 Baseline performance instrumentation proposed

019–023 are design records only; just 018 is implemented in this PR.

ADR-018 implementation

Verified enabler: candle quantized_qwen2 attention discards/replaces its KV cache on a forward at index_pos == 0, so the engine is reusable without reloading weights.

  • el-runtime — new InferenceEngine::reset_cache (release a conversation's KV, keep weights), distinct from rollback. reset()/close() return Result and propagate failures (state untouched on error); close(&mut self) releases the conversation while keeping the model resident and the session reusable; reset() preserves undrained events (turn isolation is the provider's job).
  • el-engine-candleQwenChatProvider holds the engine in a resident Mutex<ChatSession> (lazy LoadedActive so builder config is final), reused per turn — the per-turn QwenEngine::from_path disk reload is gone. QwenEngine::reset_cache evicts candle's KV in place via a benign position-0 forward, gated by a cache_dirty flag so a partially-failed eviction is retried, not skipped; prompt/logits buffers are released (Vec::new), not just cleared. QwenChatProvider::end_session() exposes the explicit conversation release.
  • el-chat/reset, /system, and generation-error recovery release the conversation before reporting success, and stop (dropping the provider → KV freed via ownership) if it can't be cleared, rather than failing open.

Scope

In scope: AC-1 (weights load once), AC-2 (no per-turn reload), AC-4 (conversation memory measurable + explicitly released), and the engine seam.

Deferred (documented in the ADR-018 implementation status): AC-3 cross-turn prefix reuse / incremental prefill; safety-expert persistence; concurrent multi-session weight sharing (candle couples weights + KV cache — same root constraint noted in ADR-022).

Verification

  • cargo test --workspace — green (el-runtime 24, el-engine-candle 20, 0 failures).
  • cargo fmt --all -- --check — clean.
  • cargo clippy --all-targets -- -D warnings — clean on the changed crates + el-chat.

Notes

  • The safety follow-up ADRs (014–017, from docs/followups.md) are in a separate open PR (add safety ADRs #9) on codex/implement-followups; they are intentionally not part of this branch, so the index here lists 013 → 018–023.
  • Hardened over several review rounds: KV-cleared-on-session-end privacy, fallible cache-clear retry, provider-owned event isolation, buffer release, and CLI fail-closed cleanup (see the ADR-018 "Review fixes" section).

🤖 Generated with Claude Code

Tovli added 3 commits June 22, 2026 18:14
…estone

Record the first-milestone (EPIC 1-4) architectural decisions derived from
docs/research/improvements-plan.md, scoped to EdgeIntelligence's edge goals:

- ADR-018 persistent model instances and stateful inference sessions
- ADR-019 in-loop incremental decoding and real token streaming
- ADR-020 batched (single-pass) prefill
- ADR-021 memory-mapped verified GGUF loading
- ADR-022 two-tier quantized KV cache with attention-aware eviction
- ADR-023 baseline performance instrumentation

Each ADR is grounded in the current code seams and links to the existing ADR
chain (provenance gate, air-gap, decode-time safety loop, memory plan). Adds the
source plan and the index table + relationship-graph rows for 018-023.
… sessions

Load model weights once and reuse a resident session across turns instead of
rebuilding the engine and re-reading the GGUF on every chat (the dominant
per-turn cost for a small local model). Verified enabler: candle quantized_qwen2
attention discards/replaces its KV cache on a forward at index_pos == 0, so the
engine is reusable without reloading weights.

- el-runtime: add required InferenceEngine::reset_cache (release a conversation's
  KV, keep weights), distinct from rollback. reset()/close() return Result and
  propagate a reset_cache failure (state untouched on error); close(&mut self)
  releases the conversation while keeping the model resident and the session
  reusable; reset() preserves undrained events (turn isolation is the provider's
  job). Implemented for every engine + test engines.
- el-engine-candle: QwenChatProvider holds the QwenEngine in a resident
  Mutex<ChatSession> (lazy Loaded->Active so builder config is final), reused per
  turn; the per-turn QwenEngine::from_path disk reload is gone.
  QwenEngine::reset_cache evicts candle's KV in place via a benign position-0
  forward, gated by a cache_dirty flag so a partially-failed eviction is retried,
  not skipped; prompt/logits buffers are released (Vec::new), not just cleared.
  QwenChatProvider::end_session exposes the explicit conversation release.
- el-chat: /reset, /system, and generation-error recovery release the
  conversation before reporting success, and stop (dropping the provider, freeing
  KV via ownership) if it cannot be cleared rather than failing open.

Scope: AC-1/AC-2/AC-4 + the engine seam. AC-3 (cross-turn prefix reuse), expert
persistence, and concurrent multi-session weight sharing are deferred (see the
ADR-018 implementation status). Workspace tests, cargo fmt --check, and
clippy -D warnings are green.
@Tovli
Tovli merged commit c3dc94d into master Jun 22, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant