Files
llmgw/internal/sim/cache.go
T
ilkerandClaude Opus 5 e07bc46c57 Faz 0: iskelet ve sim-engine
Plan v0.2'nin Faz 0 çıktısı. Yazılan sıraya göre:

- cobra tabanlı CLI iskeleti; server/agent/ctl komutları henüz stub.
- internal/sim: OpenAI uyumlu sahte inference engine. Router'ı GPU olmadan
  gerçekçi koşullarda test edebilmek için gerçek bir engine'in router
  açısından önemli davranışlarını modeller:
    * continuous batching ve KV blok muhasebesi (head-of-line blocking dahil)
    * zincirlenmiş blok hash'leri + LRU ile otomatik prefix cache
    * batch büyüdükçe artan token gecikmesi
    * istemci koptuğunda isteğin düşürülüp blokların bırakılması
    * vLLM metrik adlarıyla /metrics (plan §7.6 engine kontratı)
- Testler: prefix hash zincirlemesi, LRU tahliyesi, iptal propagasyonu,
  kuyruklama, SSE formatı, usage/cached_tokens, metriklerin gerçek
  Prometheus parser'ıyla okunabilirliği.
- Gitea Actions CI: gofmt, vet, race detector.

Modül yolu gitea.mynodes.xyz/ilker/llmgw; `make rename` ile değiştirilebilir.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-16 01:00:03 +02:00

129 lines
3.3 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package sim
import (
"container/list"
"encoding/binary"
"hash/fnv"
)
// BlockHash, KV cache'in muhasebe birimi olan bir bloğun kimliği.
type BlockHash uint64
// BlockHashes, token dizisi için zincirlenmiş blok hash'leri üretir.
//
// Zincirleme (h_i = hash(h_{i-1}, block_i)) vLLM'in yaptığının aynısıdır ve
// şart: blok kimliği sadece kendi içeriğini değil, kendisinden önce gelen tüm
// blokları da temsil etmeli. Aksi halde "aynı cümleyi farklı bağlamda içeren"
// iki istek yanlışlıkla cache hit sayılır.
//
// Son eksik blok yok sayılır — yarım blok cache'lenmez.
func BlockHashes(tokens []Token, blockSize int) []BlockHash {
if blockSize <= 0 || len(tokens) < blockSize {
return nil
}
n := len(tokens) / blockSize
out := make([]BlockHash, 0, n)
var prev BlockHash
buf := make([]byte, 8)
for i := range n {
h := fnv.New64a()
binary.LittleEndian.PutUint64(buf, uint64(prev))
_, _ = h.Write(buf)
for _, t := range tokens[i*blockSize : (i+1)*blockSize] {
binary.LittleEndian.PutUint32(buf[:4], uint32(t))
_, _ = h.Write(buf[:4])
}
prev = BlockHash(h.Sum64())
out = append(out, prev)
}
return out
}
// PrefixCache, engine'in otomatik prefix cache'ini modelleyen blok bazlı LRU.
//
// Gerçek engine'de bu bir radix tree'dir; ama yönlendirme kalitesini ölçmek için
// önemli olan davranış "hangi blokların hâlâ cache'te olduğu"dur ve onu LRU
// doğru modeller.
type PrefixCache struct {
capacity int // blok cinsinden
ll *list.List
m map[BlockHash]*list.Element
queries int64
hits int64
}
// NewPrefixCache, verilen blok kapasitesinde bir cache kurar.
// capacity <= 0 ise cache devre dışıdır (her zaman miss).
func NewPrefixCache(capacity int) *PrefixCache {
return &PrefixCache{
capacity: capacity,
ll: list.New(),
m: make(map[BlockHash]*list.Element),
}
}
// Enabled, cache'in açık olup olmadığını söyler.
func (c *PrefixCache) Enabled() bool { return c != nil && c.capacity > 0 }
// Lookup, hashes dizisinin cache'te bulunan **kesintisiz ön ek** uzunluğunu
// blok cinsinden döner ve bulunan blokları LRU'da tazeler.
//
// Kesintisiz olması önemli: ortadaki bir blok düşmüşse ondan sonrası da
// yeniden hesaplanmak zorundadır, çünkü attention önceki tüm bloklara bağlıdır.
func (c *PrefixCache) Lookup(hashes []BlockHash) int {
if !c.Enabled() {
return 0
}
c.queries += int64(len(hashes))
n := 0
for _, h := range hashes {
el, ok := c.m[h]
if !ok {
break
}
c.ll.MoveToFront(el)
n++
}
c.hits += int64(n)
return n
}
// Insert, blokları cache'e yazar (LRU'nun başına) ve kapasiteyi aşarsa en eskileri atar.
func (c *PrefixCache) Insert(hashes []BlockHash) {
if !c.Enabled() {
return
}
for _, h := range hashes {
if el, ok := c.m[h]; ok {
c.ll.MoveToFront(el)
continue
}
c.m[h] = c.ll.PushFront(h)
}
for c.ll.Len() > c.capacity {
el := c.ll.Back()
if el == nil {
break
}
c.ll.Remove(el)
delete(c.m, el.Value.(BlockHash))
}
}
// Stats, cache sorgu ve isabet sayılarını döner (blok cinsinden).
func (c *PrefixCache) Stats() (queries, hits int64) {
if c == nil {
return 0, 0
}
return c.queries, c.hits
}
// Len, cache'teki blok sayısı.
func (c *PrefixCache) Len() int {
if !c.Enabled() {
return 0
}
return c.ll.Len()
}