el-engine-candle — Candle inference engine adapter
The inference-engine adapter over Candle
(ADR-002). It implements the runtime's InferenceEngine port (RuntimeAcl) and
the LlmProvider trait (ADR-010), so the same el_runtime::InferenceSession
decode loop drives everything — nothing in the SDK pipeline is bypassed.
Float logits are quantised to integer milli-logits at the anti-corruption-layer
boundary, so Candle's Tensor/Device types never cross into the domain. No
unsafe (#![forbid(unsafe_code)]).
What it provides
This crate ships two engines and two providers, from "seam proof" to "real on-device chat":
| Type | What it is |
|---|---|
CandleEngine |
The engine-seam proof: one real Candle forward, embed[last] · w_out — a single linear projection. toy() builds deterministic synthetic weights; from_path/from_bytes load token_embd.weight + output.weight from a GGUF. Transformer blocks, attention, RoPE, and norms are ignored — logits won't match a real model. |
LocalLlmProvider |
Wraps CandleEngine behind LlmProvider with a byte-level tokenizer. Good for exercising the binding layer end-to-end. |
QwenEngine |
A real Qwen2 transformer InferenceEngine via candle-transformers, holding Candle's stateful KV cache. |
QwenChatProvider |
A real local chat backend: a Qwen2 GGUF + its tokenizer.json, rendered to Qwen2.5 ChatML and driven through the standard provenance-gated session. This is what powers apps/el-chat. |
Expected GGUF tensor names (CandleEngine)
token_embd.weight— embedding table[vocab, dim]output.weightorlm_head.weight— lm-head[vocab, dim](standard Llama layout)
Mismatched shapes are rejected at load time, not silently at inference.
Usage
use ;
use QwenChatProvider;
// Real on-device chat: a local Qwen2 GGUF + its tokenizer (no network egress).
let provider = from_paths?;
let req = new
.with_max_tokens;
let reply = provider.chat?;
println!;
# Ok::
Each chat call builds a fresh QwenEngine (Candle exposes no public KV-cache
reset) and runs the standard SDK path: provenance permit → load_prompt
(prefill) → generate (grammar mask → safety steer → chunk-guard + checkpointed
rollback → greedy commit). Decoding is deterministic greedy argmax, so replies
are reproducible.
On-device safety (ADR-005 + ADR-012)
QwenChatProvider is secure by default: from_paths resolves a conservative
built-in unsafe-word list against the model's tokenizer into the token-id data
the runtime's float-free safety loop consumes — a LightweightFilter hard-ban
steerer plus an AnchorGuard (weights-free token-anchor chunk guard) — and wires
them into the session's Ports. The ADR-012 control loop then runs on every
reply: guard the trajectory, roll the KV cache + output back to the last safe
checkpoint on a hard breach, and fail closed with a deterministic refusal once
max_rollbacks is exhausted. The owning app does not depend on el-safety;
the adapter is the one layer that bridges tokenizer text ↔ token ids.
use SafetyMode;
# use QwenChatProvider;
#
SecDecoding/Csd need model assets not shipped here and fall back to the
Lightweight wiring. Token-anchor heuristics match whole token n-grams, so they
are a defence-in-depth net, not a complete guard — production swaps in the active
tier's real safety model (ADR-012 model inventory).
ADR-013 model-backed layers. The built-in anchor patterns drive both the
output chunk guard and ingress triage (the prompt is scored before
generation; a hard breach fails closed with no decode). --guard-word extras are
guard-only — they never trigger an ingress refusal. with_expert_model(path)
enables contrastive steering: a second QwenExpert (any same-tokenizer Qwen
GGUF) implements ExpertLogits and a ContrastiveSteerer applies
base + α·(expert − base) over the early-token window only; supplying an expert
promotes the session to SecDecoding so SafetyModeSelector gates it on device
class. The expert re-primes to the prompt on a base rollback (no stale-branch
logits). It loads through the ADR-006 LoadPermit gate using the local
trust-the-file path for user GGUFs; this is not cryptographic integrity over the
weights. Production signed weights must verify the whole artifact plus detached
signature before issuing the permit. The chat model as its own expert is a
≈no-op; a safety-tuned Qwen gives real steering.
Benchmark instrumentation
Setting EL_BENCH=1 makes QwenChatProvider::chat print a per-phase breakdown
(model load / tokenize / prefill / decode / detokenize) plus per-forward
attribution (model compute vs. seam quantisation vs. runtime loop) to stderr.
It is zero-cost when unset and is a diagnostic only — not part of public behaviour.
Features & dependencies
candle-core+candle-transformers(0.8), andtokenizers0.21 with the pure-Rustfancy-regexbackend (no C/C++onig/esaxx, per ADR-008).metalfeature → enablescandle-core/metalfor Apple GPUs.
Status
Implemented; runs real on-device chat. The CandleEngine linear projection is
the ADR-002 engine-seam proof; QwenEngine/QwenChatProvider are the real
transformer path.
Part of the Edge Intelligence workspace. Realizes ADR-002 and ADR-010, and wires the on-device safety of ADR-005 / ADR-012 into the chat provider; see the Inference Runtime context.