internal/autogen/metacache.go
Rama main · réplica de lectura del repositorio original.
package autogen
import (
"os"
"path/filepath"
"sync"
)
// Reading GGUF header metadata is the dominant cost of a config regen: every
// save/reload re-parses the header of every model. A regen happens inside the
// running process (UI save, -watch-models, SIGHUP), so an in-memory cache keyed
// by the file fingerprint (size + mtime) lets a repeated regen skip the parse
// entirely and only re-run the cheap sizing math. The fingerprint invalidates
// the entry the moment a GGUF is replaced, so a changed model is always re-read.
var (
metaCacheMu sync.Mutex
metaCache = map[string]cachedMeta{}
)
type cachedMeta struct {
size int64
mtime int64
meta Metadata
}
// ReadGgufMetadataCached returns parsed metadata for path, reusing a previously
// parsed result when the file's size and mtime are unchanged. On a miss or a
// stale fingerprint it falls back to a full header read via ReadGgufMetadata and
// caches the result. Safe for concurrent use. When the path can't be stat'd it
// degrades to an uncached read (ReadGgufMetadata reports the real error).
//
// For a split set the fingerprint is shard 1's alone, so replacing only a later
// shard does not invalidate the entry even though FileSizeGB sums the whole set
// (ggufSetSizeBytes). Re-quantizing a model rewrites shard 1 too, so the case
// that matters in practice still invalidates.
func ReadGgufMetadataCached(path string) (Metadata, error) {
key := filepath.ToSlash(path)
fi, statErr := os.Stat(path)
if statErr == nil {
metaCacheMu.Lock()
e, ok := metaCache[key]
metaCacheMu.Unlock()
if ok && e.size == fi.Size() && e.mtime == fi.ModTime().UnixNano() {
return e.meta, nil
}
}
meta, err := ReadGgufMetadata(path)
if err != nil {
return Metadata{}, err
}
if statErr == nil {
metaCacheMu.Lock()
metaCache[key] = cachedMeta{size: fi.Size(), mtime: fi.ModTime().UnixNano(), meta: meta}
metaCacheMu.Unlock()
}
return meta, nil
}