A profile's replicas, set on any of a model's profiles, is how many of its replicas run there; the scaling becomes the sum. The reconciler starts the ones short of their profile before stopping the ones over, so a change of split never runs fewer. ctl model replicas sets them, adding a profile on CPUs to a GGUF model; the router already fills the higher-priority pool first, which the CPU profile is put below. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
65 lines
1.6 KiB
Go
65 lines
1.6 KiB
Go
package reconciler
|
|
|
|
import (
|
|
"slices"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/control/catalog"
|
|
)
|
|
|
|
// splitGaps is, for a model whose replicas are counted per profile, the
|
|
// profiles short of theirs and the replicas over: of a profile with more
|
|
// than it asks, or of one it no longer has. Counted as reconcileModel
|
|
// counts — a replica being retired is not one it has — so a replica
|
|
// restarted is replaced on its own profile.
|
|
func (r *Reconciler) splitGaps(m catalog.Model) (deficit map[string]bool, surplus []string) {
|
|
live := map[string][]string{}
|
|
r.mu.Lock()
|
|
for _, rep := range r.replicas {
|
|
if rep.Model == m.Name && rep.State.Counts() && rep.RetiringAt.IsZero() {
|
|
b := catalog.ProfileBase(rep.Profile)
|
|
live[b] = append(live[b], rep.ID)
|
|
}
|
|
}
|
|
r.mu.Unlock()
|
|
deficit = map[string]bool{}
|
|
for _, p := range m.Profiles {
|
|
ids := live[p.Name]
|
|
delete(live, p.Name)
|
|
slices.Sort(ids)
|
|
switch {
|
|
case len(ids) < p.Replicas:
|
|
deficit[p.Name] = true
|
|
case len(ids) > p.Replicas:
|
|
surplus = append(surplus, ids[p.Replicas:]...)
|
|
}
|
|
}
|
|
for _, ids := range live {
|
|
surplus = append(surplus, ids...)
|
|
}
|
|
slices.Sort(surplus)
|
|
return deficit, surplus
|
|
}
|
|
|
|
// pickSurplus is n of the live replicas to stop: those over their
|
|
// profile's count first, then any.
|
|
func pickSurplus(surplus, live []string, n int) []string {
|
|
out := make([]string, 0, n)
|
|
for _, id := range surplus {
|
|
if len(out) == n {
|
|
return out
|
|
}
|
|
if slices.Contains(live, id) {
|
|
out = append(out, id)
|
|
}
|
|
}
|
|
for _, id := range live {
|
|
if len(out) == n {
|
|
break
|
|
}
|
|
if !slices.Contains(out, id) {
|
|
out = append(out, id)
|
|
}
|
|
}
|
|
return out
|
|
}
|