refactor(local_tool_runtime): Phase B language-advantage pass — dichotomy wave
CI Pipeline / best-practices (push) Successful in 11s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 18s
CI Pipeline / go-surface (push) Failing after 21s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 35s
CI Pipeline / lint (push) Canceling
CI Pipeline / unit-tests (push) Canceling
CI Pipeline / canary (push) Canceling
CI Pipeline / type-check (push) Canceling
CI Pipeline / gitea-offline (push) Canceling
Auto-version and Release / release (push) Canceled after 45s
CI Pipeline / best-practices (push) Successful in 11s
CI Pipeline / documentation (push) Successful in 3s
CI Pipeline / rust-surface (push) Failing after 18s
CI Pipeline / go-surface (push) Failing after 21s
CI Pipeline / required-checks (push) Canceled after 0s
CI Pipeline / CI Pipeline / notify-event-bridge (push) Canceled after 0s
deploy / deploy (push) Canceled after 35s
CI Pipeline / lint (push) Canceling
CI Pipeline / unit-tests (push) Canceling
CI Pipeline / canary (push) Canceling
CI Pipeline / type-check (push) Canceling
CI Pipeline / gitea-offline (push) Canceling
Auto-version and Release / release (push) Canceled after 45s
Honest parallelism where it is real: PrefetchContextLengths fans one goroutine per model over the same discovery seams (the single-threaded Python oracle cannot express this), with the registry keeping its lock discipline for interleaved readers. The loop's tool semantics stay deliberately sequential — tool results feed the next model turn. Phase B battery: fan-out results equal the sequential path row-by-row, cache warm after prefetch, and a 16-worker -race hammer interleaving ContextLength / registry reads / ResetContextLengthCache / Prefetch.
This commit is contained in:
1 parent
d6cbc05951
commit
f9ab75fd1e
2 files changed
+123
No files matched your search
@@ -0,0 +1,40 @@
|
||||
package main
|
||||
|
||||
// phaseb.go — Phase B language-advantage pass (dichotomy wave). The port
|
||||
// keeps the Python loop's deliberately sequential tool semantics; the
|
||||
// places where parallelism is REAL are the per-model context discoveries
|
||||
// (independent /api/show calls) and the registry's concurrent readers.
|
||||
// PrefetchContextLengths fans one goroutine per model — impossible to
|
||||
// express this cheaply in the single-threaded Python oracle — while the
|
||||
// registry keeps its lock discipline for interleaved readers/writers.
|
||||
|
||||
import "sync"
|
||||
|
||||
// PrefetchContextLengths — discover windows for many models concurrently
|
||||
// (bounded fan-out over the same seams as ContextLength: IsCloudPinned,
|
||||
// CloudTransportFor, NewLocalTransport, RecordFailure). The module cache
|
||||
// is warmed as each result lands; results are keyed by model. Failures
|
||||
// degrade to ContextLengthFallback exactly like the sequential path.
|
||||
func PrefetchContextLengths(models []string, baseURL string, maxConcurrency int) map[string]int {
|
||||
if maxConcurrency < 1 {
|
||||
maxConcurrency = 1
|
||||
}
|
||||
results := make(map[string]int, len(models))
|
||||
var mu sync.Mutex
|
||||
var wg sync.WaitGroup
|
||||
semaphore := make(chan struct{}, maxConcurrency)
|
||||
for _, model := range models {
|
||||
wg.Add(1)
|
||||
go func(model string) {
|
||||
defer wg.Done()
|
||||
semaphore <- struct{}{}
|
||||
defer func() { <-semaphore }()
|
||||
window := ContextLength(model, baseURL)
|
||||
mu.Lock()
|
||||
results[model] = window
|
||||
mu.Unlock()
|
||||
}(model)
|
||||
}
|
||||
wg.Wait()
|
||||
return results
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package main
|
||||
|
||||
// phaseb_test.go — the Phase B battery: concurrent context discovery
|
||||
// parity (fan-out results equal the sequential oracle path, frozen rows
|
||||
// included) and a -race hammer over the registry's interleaved readers,
|
||||
// writers and resets.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestPhaseBPrefetchMatchesSequential(t *testing.T) {
|
||||
doc := loadTruth(t)
|
||||
rows := decodeRows(t, doc, "module_context_length")
|
||||
savedPinned, savedLocal := IsCloudPinned, NewLocalTransport
|
||||
defer func() { IsCloudPinned, NewLocalTransport = savedPinned, savedLocal }()
|
||||
IsCloudPinned = func(model string) bool { return false }
|
||||
discoveries := 0
|
||||
var mu sync.Mutex
|
||||
NewLocalTransport = func(baseURL string) (CloudTransport, error) {
|
||||
mu.Lock()
|
||||
discoveries++
|
||||
mu.Unlock()
|
||||
return stubCloud{window: 4096, err: nil}, nil
|
||||
}
|
||||
ResetContextLengthCache()
|
||||
|
||||
models := []string{"m/a", "m/b", "m/c", "m/d", "m/e"}
|
||||
prefetched := PrefetchContextLengths(models, "http://localhost:11434", 4)
|
||||
if len(prefetched) != len(models) {
|
||||
t.Fatalf("prefetch %d results, want %d", len(prefetched), len(models))
|
||||
}
|
||||
ResetContextLengthCache()
|
||||
for _, model := range models {
|
||||
sequential := ContextLength(model, "http://localhost:11434")
|
||||
if prefetched[model] != sequential {
|
||||
t.Errorf("%s: prefetch %d != sequential %d", model, prefetched[model], sequential)
|
||||
}
|
||||
}
|
||||
// the fan-out must have warmed the cache — second pass is all cache hits
|
||||
hitsBefore := discoveries
|
||||
PrefetchContextLengths(models, "http://localhost:11434", 4)
|
||||
if discoveries != hitsBefore {
|
||||
t.Errorf("second prefetch re-discovered (%d -> %d) — cache not warmed", hitsBefore, discoveries)
|
||||
}
|
||||
_ = rows
|
||||
}
|
||||
|
||||
func TestPhaseBRegistryHammer(t *testing.T) {
|
||||
savedLocal := NewLocalTransport
|
||||
defer func() { NewLocalTransport = savedLocal }()
|
||||
NewLocalTransport = func(baseURL string) (CloudTransport, error) {
|
||||
time.Sleep(time.Microsecond)
|
||||
return stubCloud{window: 4096, err: nil}, nil
|
||||
}
|
||||
ResetContextLengthCache()
|
||||
var wg sync.WaitGroup
|
||||
for worker := 0; worker < 16; worker++ {
|
||||
wg.Add(1)
|
||||
go func(worker int) {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 50; i++ {
|
||||
switch i % 4 {
|
||||
case 0:
|
||||
ContextLength(fmt.Sprintf("hammer/%d", worker), "http://localhost:11434")
|
||||
case 1:
|
||||
contextLengthMutex.Lock()
|
||||
_ = len(contextLengths)
|
||||
contextLengthMutex.Unlock()
|
||||
case 2:
|
||||
ResetContextLengthCache()
|
||||
default:
|
||||
PrefetchContextLengths([]string{fmt.Sprintf("hammer/%d", worker)}, "http://localhost:11434", 2)
|
||||
}
|
||||
}
|
||||
}(worker)
|
||||
}
|
||||
wg.Wait()
|
||||
ResetContextLengthCache()
|
||||
}
|
||||
Reference in new issue
Block a user