The VLM is to come first on the one card. Its class makes it take the card from flux, triposr or sf3d when asked for, but idle eviction ignored class: a 3D job two minutes into a lull would have taken the card from it. Idle eviction offers only replicas of no higher class than the model wanting room, and the wait such a model is told runs to the holder's idle timeout. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
967df77e53
commit
fa6e68c8e8
4 files changed
+69
-7
No files matched your search
@@ -86,6 +86,11 @@ APIs change between minors without a deprecation period.
|
||||
- A picture in a prompt was counted as its base64 by the context check, so
|
||||
one 1280x720 PNG was "138 thousand tokens" and a vision model refused it;
|
||||
a picture or a sound now counts as about a thousand.
|
||||
- An on-demand model of a higher class was evicted, idle, for a lower-class
|
||||
one two minutes into a lull; it keeps the card through its idle timeout,
|
||||
and a model waiting on it is told so.
|
||||
- A model removed from the catalog stayed wanted, so added again it loaded
|
||||
unasked.
|
||||
|
||||
|
||||
## v0.14.3 — 2026-10-10
|
||||
|
||||
@@ -90,6 +90,11 @@ func (r *Reconciler) evictable(wantedBy string) []evictionCandidate {
|
||||
cat := r.cat.Load()
|
||||
now := r.now()
|
||||
|
||||
wantRank := -1
|
||||
if w, ok := cat.Resolve(wantedBy); ok {
|
||||
wantRank = catalog.ClassRank(w.EffectiveClass())
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
|
||||
@@ -102,6 +107,12 @@ func (r *Reconciler) evictable(wantedBy string) []evictionCandidate {
|
||||
if !ok || !other.Scaling.OnDemand() {
|
||||
continue
|
||||
}
|
||||
// Not for a model of a lower class: the one put first keeps the
|
||||
// card through its idle timeout, rather than losing it two minutes
|
||||
// into a lull to whatever was asked for next.
|
||||
if catalog.ClassRank(other.EffectiveClass()) > wantRank {
|
||||
continue
|
||||
}
|
||||
// Unknown is not idle. A replica the router has said nothing about —
|
||||
// after a restart, that is all of them — must not be evicted on the
|
||||
// strength of a fact nobody has reported.
|
||||
|
||||
@@ -201,13 +201,17 @@ func (r *Reconciler) WaitFor(model string) (heldBy string, eta time.Duration, ok
|
||||
}
|
||||
h := holder{v: victim{ID: rep.ID, Model: rep.Model}}
|
||||
if !rep.LastUsedAt.IsZero() {
|
||||
evict := rep.LastUsedAt.Add(r.cfg.EvictionGrace)
|
||||
if !rep.ReadyAt.IsZero() && rep.ReadyAt.Add(r.cfg.MinLifetime).After(evict) {
|
||||
evict = rep.ReadyAt.Add(r.cfg.MinLifetime)
|
||||
}
|
||||
h.release = evict
|
||||
if idle := rep.LastUsedAt.Add(r.idleTimeout(other)); idle.Before(evict) {
|
||||
h.release = idle
|
||||
h.release = rep.LastUsedAt.Add(r.idleTimeout(other))
|
||||
// Evicted sooner, when it is no higher a class than the model
|
||||
// waiting; one put first is waited for to its idle timeout.
|
||||
if catalog.ClassRank(other.EffectiveClass()) <= catalog.ClassRank(m.EffectiveClass()) {
|
||||
evict := rep.LastUsedAt.Add(r.cfg.EvictionGrace)
|
||||
if !rep.ReadyAt.IsZero() && rep.ReadyAt.Add(r.cfg.MinLifetime).After(evict) {
|
||||
evict = rep.ReadyAt.Add(r.cfg.MinLifetime)
|
||||
}
|
||||
if evict.Before(h.release) {
|
||||
h.release = evict
|
||||
}
|
||||
}
|
||||
}
|
||||
holders = append(holders, h)
|
||||
|
||||
@@ -1337,6 +1337,48 @@ func TestAModelRemovedWhileRunningIsStopped(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestAModelPutFirstIsNotEvictedForALowerClassOne(t *testing.T) {
|
||||
// The VLM is put first: a picture or 3D model asked for in a lull must
|
||||
// wait for its idle timeout, not take the card two minutes in.
|
||||
h := newHarness(t, testModel(1), gpuNode("1", 4))
|
||||
cat := twoOnDemandModels(t)
|
||||
ms := cat.Models()
|
||||
for i := range ms {
|
||||
if ms[i].Name == "llama" {
|
||||
ms[i].Class = catalog.ClassStandard
|
||||
} else {
|
||||
ms[i].Class = catalog.ClassOpportunistic
|
||||
}
|
||||
}
|
||||
built, err := catalog.New(ms)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
h.r.cat.Store(built)
|
||||
h.r.Want("llama")
|
||||
h.r.Reconcile(t.Context())
|
||||
h.markReady()
|
||||
reps := h.r.Replicas()
|
||||
h.r.RecordUsage(map[string]time.Time{reps[0].ID: h.clock.now()})
|
||||
h.advance(DefaultConfig().MinLifetime + DefaultConfig().EvictionGrace + time.Minute)
|
||||
|
||||
if got := h.r.evictable("second"); len(got) != 0 {
|
||||
t.Errorf("a standard model was offered to an opportunistic one: %+v", got)
|
||||
}
|
||||
h.r.Want("second")
|
||||
h.r.Reconcile(t.Context())
|
||||
// It waits for the idle timeout (an hour here), and is told so.
|
||||
_, eta, ok := h.r.WaitFor("second")
|
||||
want := time.Hour - (DefaultConfig().MinLifetime + DefaultConfig().EvictionGrace + time.Minute)
|
||||
if !ok || eta != want {
|
||||
t.Errorf("WaitFor: %s %v, want %s", eta, ok, want)
|
||||
}
|
||||
// The other way, it is offered.
|
||||
if got := h.r.evictable("llama"); len(got) != 0 {
|
||||
t.Errorf("nothing of second runs, yet %+v was offered", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnOnDemandModelRemovedAndAddedAgainIsNotLoadedUnasked(t *testing.T) {
|
||||
// The VLM, removed and added again to give it a licence, came back
|
||||
// loading on its own: still on the wanted list from before its removal.
|
||||
|
||||
Reference in new issue
Block a user