Repository navigation
v0.3.1
[0.3.1] - 2026-03-10
Fixed
-
Crash on hybrid/recurrent models (
src/engine/model.rs,src/engine/mod.rs)- Qwen3.5, Mamba, and other hybrid architectures use
llama_memory_recurrentinstead of
the standard attention KV cache. Callingllama_memory_seq_cpon those models triggered
GGML_ASSERT(is_full && "seq_cp() is only supported for full KV buffers")inside
llama.cpp, terminating the process withSIGABRTon the second request with an identical
prompt. - Added
Model::supports_seq_copy()backed byllama_memory_can_shift(): returnstrue
only for full KV cache backends (standard attention-only transformers). InferenceEngine::new()stores the result assupports_prefix_cacheand logs it at
startup.do_prefillnow guardsllama_memory_seq_cpwith acan_shiftcheck as a second safety
net.- Prefix caching is automatically disabled for incompatible models; all other features
(stop sequences, metrics, streaming usage) remain fully functional.
- Qwen3.5, Mamba, and other hybrid architectures use
-
CUDA build (
build.rs,Cargo.toml)- Removed the optional
cudarcdependency (only used to query GPU memory, but its
build.rsrequires a CUDA-version feature flag that caused--features cudato fail with
a compile error). Replaced with anvidia-smisubprocess call — no extra dependencies. build.rsnow linksggml-cuda,libcuda(driver API),libcudart,libcublas, and
libcublasLt, searching both/cuda/lib64and/cuda/targets/x86_64-linux/libto
support different CUDA installation layouts.
- Removed the optional
-
Prefix-cache boundary token position (
src/engine/model.rs)do_prefillwas copying positions0..skip_prefix_tokensviaseq_cpand then
submitting the last prompt token at the wrong position (context_leninstead of
skip_prefix_tokens - 1). Changed to copy0..skip_prefix_tokens-1and always
re-submit the boundary token in the batch at the correct position, ensuring valid
positional encodings and correct logits.
[0.3.0] - 2026-03-10
Added
-
PageTable — explicit logical→physical block mapping (
src/kv_cache/mod.rs)- Replaced the flat
kv_block_ids: Vec<BlockId>field inInferenceRequestwith a named
PageTablestruct. The struct encapsulates theentriesvector
(logical_block_index → physical_block_id) and exposesblock_ids(),len(),
is_empty(),clear(), andextend(). - Added
ref_count: Vec<AtomicUsize>toKVCacheManager(one entry per physical block).
allocatesetsref_count = 1;free_blocksdecrements and only returns the block to the
free list when the count reaches zero. retain_block(id)— increments ref_count (used when a block is shared for prefix caching).copy_on_write(id) -> Option<BlockId>— allocates a new exclusive block and decrements the
shared one's ref_count; foundational for future true memory-sharing CoW.
- Replaced the flat
-
Prefix caching — skip re-prefill for identical prompts (
src/scheduler/mod.rs,src/engine/mod.rs,src/engine/model.rs)Schedulernow embeds aPrefixCache: HashMap<u64, PrefixCacheEntry>keyed by
hash(prompt_tokens). Max capacity =max_batch_size / 4entries.- When a request finishes (EOS, Length, or StopSequence),
InferenceEnginecalls
Scheduler::try_insert_prefixwhich atomically transfers the request'skv_seq_idand
page_tableblocks into the cache (the KV data in llama.cpp is preserved —clear_sequence
is skipped for cached entries). - On the next admission of a request with the same prompt hash,
schedule_stepdetects the
hit, transfers the cached blocks to the new request'sPageTable, allocates only the
generation blocks, setsskip_prefix_tokensandprefix_seq_idon the request. do_prefillcallsllama_memory_seq_cp(mem, prefix_seq_id, new_seq_id, 0, skip_tokens)
inside the blocking task before building the batch, then submits only
prompt_tokens[skip_prefix_tokens..]starting at the correct absolute position.
After prefill, the engine clears the now-redundant prefix sequence and returns its ID to
the pool viaScheduler::return_prefix_seq_id.- New
Modeltrait method:copy_sequence_range(src, dst, token_count), backed by
llama_memory_seq_cp; no-op in the stub. - Counters
prefix_hits/prefix_misses(atomic) onScheduler; exposed onInferenceEngine.
-
Stop sequences (
src/scheduler/batch.rs,src/engine/mod.rs,src/api/types.rs)SamplingParamsgainsstop: Option<Vec<String>>.ChatCompletionRequest.stopaccepts both a JSON string and an array (OpenAI spec). Uses a
customdeserialize_stopSerde helper.StopReason::StopSequencevariant added.handle_logitsnow runs a rolling-buffer stop-sequence check (last2 × max_stop_lenchars)
per request. The stop string is not emitted in the output; only the prefix before the
match is sent to the client (OpenAI behaviour). Detection works across token boundaries.- Output filtering (
<think>suppression, special token stripping) and stop sequence detection
now share a single lock acquisition onper_request_stateto prevent deadlocks.
-
Prometheus
/metricsendpoint (src/metrics.rs,src/api/routes.rs,src/main.rs)- New
GET /metricsroute returning Prometheus text exposition format (version 0.0.4). - Metrics registered at startup via
Metrics::new():ferrum_requests_total{finish_reason}— counterferrum_tokens_generated_total— counterferrum_request_latency_seconds— histogram (10 buckets: 0.05 s … 60 s)ferrum_kv_cache_usage_ratio— gaugeferrum_queue_depth— gaugeferrum_active_requests— gaugeferrum_prefix_cache_hits_total— counterferrum_prefix_cache_misses_total— counter
- Dependency added:
prometheus = "0.14". - Gauges and counter deltas are refreshed on every engine scheduling step.
- New
-
Streaming
usagein the final chunk (src/api/routes.rs,src/api/types.rs)- The last SSE chunk (the one that carries
finish_reason) now includes ausageobject
withprompt_tokens,completion_tokens, andtotal_tokens, matching the OpenAI
streaming spec. Intermediate chunks omit the field (skip_serializing_if = "Option::is_none").
- The last SSE chunk (the one that carries
Changed
InferenceRequestfields:kv_block_ids: Vec<BlockId>→page_table: PageTable;
new fieldsskip_prefix_tokens: usize,prefix_seq_id: Option<i32>,
submitted_at: Instant(for latency metrics).InferenceEngine::newnow acceptsmetrics: Option<Arc<Metrics>>.OutputFilterStaterenamed toPerRequestState; itstext_bufferfield drives stop
sequence detection.PLAN.mdupdated: Phase 1 marked completed with v0.1.0/v0.2.0 summaries; Phase 2
progress tracked.