Files
jbergner a6bc71fb3a
release-tag / Resolve release metadata (push) Successful in 30s
release-tag / Build knowledge (push) Failing after 4m51s
release-tag / Build control (push) Failing after 5m0s
release-tag / Build agent (push) Failing after 5m0s
release-tag / Build agent-data-init (push) Failing after 5m5s
release-tag / Build neuroforge-worker (push) Failing after 5m7s
release-tag / Build neuroforge (push) Failing after 5m9s
Init
2026-08-26 18:34:41 +02:00

230 lines
7.5 KiB
Go

package main
import (
"encoding/json"
"flag"
"fmt"
"math"
"os"
"path/filepath"
"runtime"
"time"
"neuroforge/internal/core"
"neuroforge/internal/store"
)
type result struct {
Mode string `json:"mode"`
Memories int `json:"memories"`
Dimensions int `json:"dimensions"`
Batch int `json:"batch"`
IngestSeconds float64 `json:"ingest_seconds"`
IngestPerSecond float64 `json:"ingest_per_second"`
CheckpointSeconds float64 `json:"checkpoint_seconds"`
DiskANNBuildSeconds float64 `json:"disk_ann_build_seconds,omitempty"`
DiskPQItems int `json:"disk_pq_items,omitempty"`
DiskPQBytes int64 `json:"disk_pq_bytes,omitempty"`
Queries int `json:"queries"`
QueryP50MS float64 `json:"query_p50_ms"`
QueryP95MS float64 `json:"query_p95_ms"`
QueryP99MS float64 `json:"query_p99_ms"`
HeapAllocBytes uint64 `json:"heap_alloc_bytes"`
SysBytes uint64 `json:"sys_bytes"`
DiskBytes int64 `json:"disk_bytes"`
HNSWNodes any `json:"hnsw_nodes"`
Tiering any `json:"tiering"`
DataDir string `json:"data_dir"`
}
func syntheticVector(i, dim int) []float32 {
v := make([]float32, dim)
x := uint64(i+1)*0x9e3779b97f4a7c15 + 0x632be59bd9b4e019
var norm float64
for j := range v {
x ^= x >> 12
x ^= x << 25
x ^= x >> 27
y := x * 2685821657736338717
f := float32(int32(y>>32)) / float32(math.MaxInt32)
v[j] = f
norm += float64(f * f)
}
if norm > 0 {
inv := float32(1 / math.Sqrt(norm))
for j := range v {
v[j] *= inv
}
}
return v
}
func percentile(xs []float64, p float64) float64 {
if len(xs) == 0 {
return 0
}
// insertion sort is fine for the small query sample.
for i := 1; i < len(xs); i++ {
for j := i; j > 0 && xs[j] < xs[j-1]; j-- {
xs[j], xs[j-1] = xs[j-1], xs[j]
}
}
idx := int(math.Ceil(p*float64(len(xs)))) - 1
if idx < 0 {
idx = 0
}
if idx >= len(xs) {
idx = len(xs) - 1
}
return xs[idx]
}
func dirSize(root string) int64 {
var n int64
_ = filepath.Walk(root, func(_ string, info os.FileInfo, err error) error {
if err == nil && info != nil && !info.IsDir() {
n += info.Size()
}
return nil
})
return n
}
func main() {
count := flag.Int("memories", 1000000, "synthetic memories")
dim := flag.Int("dim", 32, "vector dimensions")
batch := flag.Int("batch", 256, "ingest batch size (max 4096)")
queries := flag.Int("queries", 100, "search queries")
k := flag.Int("k", 10, "neighbors per query")
data := flag.String("data", "", "data directory; temporary by default")
keep := flag.Bool("keep", false, "keep temporary data")
durable := flag.Bool("durable", false, "fsync WAL for each batch")
mode := flag.String("mode", "full", "benchmark mode: full, storage, or pq")
tierEvery := flag.Int("tier-every", 100000, "cool resident bodies every N ingested memories; 0 disables periodic tiering")
progressEvery := flag.Int("progress-every", 0, "write ingest progress to stderr every N memories; 0 disables")
flag.Parse()
if *count < 1 || *dim < 2 || *batch < 1 || *batch > 4096 || (*mode != "full" && *mode != "storage" && *mode != "pq") {
fmt.Fprintln(os.Stderr, "invalid benchmark flags")
os.Exit(2)
}
dir := *data
temp := false
if dir == "" {
var err error
dir, err = os.MkdirTemp("", "neuroforge-bench-")
if err != nil {
panic(err)
}
temp = true
}
if temp && !*keep {
defer os.RemoveAll(dir)
}
s, err := store.New(dir)
if err != nil {
panic(err)
}
defer s.Close()
cfg := s.Config()
cfg.Storage.WALSync = *durable
cfg.Storage.CheckpointEvery = 1000000
if *mode == "pq" || *mode == "storage" {
// Memory-segment checkpoints are O(1)-ish in these modes and allow the
// benchmark to prune already-durable WAL payloads during long ingests.
cfg.Storage.CheckpointEvery = 64
}
cfg.Brain.Index.M = 8
cfg.Brain.Index.EfConstruction = 48
cfg.Brain.Index.EfSearch = 48
cfg.Brain.Index.CandidateScale = 2
if *mode == "storage" {
cfg.Brain.Index.Enabled = false
} else if *mode == "pq" {
cfg.Brain.Index.Enabled = true
cfg.Brain.Index.Mode = "disk-pq"
cfg.Brain.Index.DiskPQ.MinMemories = 0
}
cfg.Storage.PageCache.Enabled = true
cfg.Storage.PageCache.MaxBytes = 64 << 20
cfg.Storage.Tiering.Enabled = true
cfg.Storage.Tiering.HotMaxBytes = 64 << 20
cfg.Storage.Tiering.HotAgeMinutes = 24 * 60
cfg.Storage.Tiering.IntervalMinutes = 5
cfg.Storage.IndexSegments.BackgroundMergeMinutes = 10
cfg.Storage.IndexSegments.MergeAtDeltas = 8
if err := s.UpdateConfig(cfg); err != nil {
panic(err)
}
start := time.Now()
samples := make([][]float32, 0, *queries)
for base := 0; base < *count; base += *batch {
n := *batch
if base+n > *count {
n = *count - base
}
items := make([]core.Memory, n)
for j := 0; j < n; j++ {
i := base + j
v := syntheticVector(i, *dim)
items[j] = core.Memory{ID: fmt.Sprintf("bench_%09d", i), Kind: "benchmark", MemoryType: core.MemorySemantic, Text: fmt.Sprintf("synthetic memory %d", i), Vector: v, Salience: 1, Confidence: 1}
if len(samples) < *queries && i%max(1, *count/max(1, *queries)) == 0 {
samples = append(samples, append([]float32(nil), v...))
}
}
if err := s.AddMemoriesBatch(items); err != nil {
panic(err)
}
if *tierEvery > 0 && base > 0 && base%*tierEvery < *batch {
s.TierMemoryBodies(time.Now().UTC())
}
if *progressEvery > 0 && (base+n)%*progressEvery < n {
fmt.Fprintf(os.Stderr, "progress memories=%d elapsed=%s\n", base+n, time.Since(start).Round(time.Millisecond))
}
}
ingest := time.Since(start)
if *progressEvery > 0 {
fmt.Fprintf(os.Stderr, "phase ingest done=%s\n", ingest.Round(time.Millisecond))
}
var diskBuildSeconds float64
var diskPQItems int
var diskPQBytes int64
if *mode == "pq" {
b := time.Now()
res, err := s.RebuildDiskANN()
if err != nil {
panic(err)
}
diskBuildSeconds = time.Since(b).Seconds()
diskPQItems, diskPQBytes = res.TotalItems, res.TotalBytes
if *progressEvery > 0 {
fmt.Fprintf(os.Stderr, "phase disk-pq done=%s items=%d bytes=%d\n", time.Since(b).Round(time.Millisecond), diskPQItems, diskPQBytes)
}
}
cpStart := time.Now()
if err := s.ForceCheckpoint(); err != nil {
panic(err)
}
cpDur := time.Since(cpStart)
if *progressEvery > 0 {
fmt.Fprintf(os.Stderr, "phase checkpoint done=%s\n", cpDur.Round(time.Millisecond))
}
tierStart := time.Now()
s.TierMemoryBodies(time.Now().UTC())
if *progressEvery > 0 {
fmt.Fprintf(os.Stderr, "phase final-tier done=%s\n", time.Since(tierStart).Round(time.Millisecond))
}
lat := make([]float64, 0, len(samples))
if *mode == "full" || *mode == "pq" {
for _, q := range samples {
t := time.Now()
_ = s.SearchVector(q, *k, -1, 0)
lat = append(lat, float64(time.Since(t).Microseconds())/1000)
}
}
var ms runtime.MemStats
runtime.GC()
runtime.ReadMemStats(&ms)
st := s.Stats()
out := result{Mode: *mode, Memories: *count, Dimensions: *dim, Batch: *batch, IngestSeconds: ingest.Seconds(), IngestPerSecond: float64(*count) / ingest.Seconds(), CheckpointSeconds: cpDur.Seconds(), DiskANNBuildSeconds: diskBuildSeconds, DiskPQItems: diskPQItems, DiskPQBytes: diskPQBytes, Queries: len(lat), QueryP50MS: percentile(append([]float64(nil), lat...), .50), QueryP95MS: percentile(append([]float64(nil), lat...), .95), QueryP99MS: percentile(append([]float64(nil), lat...), .99), HeapAllocBytes: ms.HeapAlloc, SysBytes: ms.Sys, DiskBytes: dirSize(dir), HNSWNodes: st["hnsw_nodes"], Tiering: s.TieringStatus(), DataDir: dir}
b, _ := json.MarshalIndent(out, "", " ")
fmt.Println(string(b))
}