The quotas page gains a capacity section over the same API methods the CLI uses; a model's card sets its class, owner, memory and cores, applying only what changed. Headroom counts the replicas a model could get by preempting what its class outranks, and the card and ctl model target use it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
139 lines
4.0 KiB
Go
139 lines
4.0 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/store"
|
|
)
|
|
|
|
// CapacityAdmin sets what parts of the tenancy tree are guaranteed of the
|
|
// cluster and may hold. The admin API implements it; the page and the CLI go
|
|
// through the same checks.
|
|
type CapacityAdmin interface {
|
|
QuotaViews(ctx context.Context) ([]adminapi.QuotaView, error)
|
|
SetQuota(ctx context.Context, req adminapi.QuotaRequest, by string) (string, error)
|
|
ClearQuota(ctx context.Context, scope string) (string, error)
|
|
Scopes(ctx context.Context) ([]adminapi.ScopeName, error)
|
|
}
|
|
|
|
// SetCapacity gives the quotas page capacity quotas to show and set. Without
|
|
// it the section is left off.
|
|
func (p *AccountPages) SetCapacity(c CapacityAdmin) { p.capacity = c }
|
|
|
|
// capacityRow is one scope's capacity quota as the page shows it.
|
|
type capacityRow struct {
|
|
adminapi.QuotaView
|
|
GPUs, Cores, Memory string
|
|
}
|
|
|
|
// capacityForm is what the form starts with: a scope's quota to change, or
|
|
// nothing for a new one.
|
|
type capacityForm struct {
|
|
Scope string
|
|
GPUs, Cores, Memory string
|
|
LGPUs, LCores, LMem string
|
|
}
|
|
|
|
func (p *AccountPages) capacitySection(ctx context.Context, d *adminQuotasData, editing string) {
|
|
if p.capacity == nil {
|
|
return
|
|
}
|
|
d.HasCapacity = true
|
|
views, err := p.capacity.QuotaViews(ctx)
|
|
if err != nil {
|
|
d.Error = err.Error()
|
|
return
|
|
}
|
|
for _, v := range views {
|
|
d.Capacity = append(d.Capacity, capacityRow{QuotaView: v,
|
|
GPUs: capCell(v.Holds.GPUs, v.Guarantee.GPUs, v.Limit.GPUs),
|
|
Cores: capCell(v.Holds.CPUCores, v.Guarantee.CPUCores, v.Limit.CPUCores),
|
|
Memory: capCell(v.Holds.MemoryGB, v.Guarantee.MemoryGB, v.Limit.MemoryGB)})
|
|
if v.Scope == editing {
|
|
d.CapForm = capacityForm{Scope: v.Scope,
|
|
GPUs: intStr(v.Guarantee.GPUs), Cores: intStr(v.Guarantee.CPUCores), Memory: intStr(v.Guarantee.MemoryGB),
|
|
LGPUs: intStr(v.Limit.GPUs), LCores: intStr(v.Limit.CPUCores), LMem: intStr(v.Limit.MemoryGB)}
|
|
}
|
|
}
|
|
if d.Scopes, err = p.capacity.Scopes(ctx); err != nil {
|
|
d.Error = err.Error()
|
|
}
|
|
}
|
|
|
|
// capCell is "6 of 8 (at most 16)": held, guaranteed, limit.
|
|
func capCell(holds int, g, l *int) string {
|
|
if g == nil && l == nil {
|
|
return ""
|
|
}
|
|
out := strconv.Itoa(holds)
|
|
if g != nil {
|
|
out += " of " + strconv.Itoa(*g)
|
|
}
|
|
if l != nil {
|
|
out += fmt.Sprintf(" (at most %d)", *l)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func intStr(v *int) string {
|
|
if v == nil {
|
|
return ""
|
|
}
|
|
return strconv.Itoa(*v)
|
|
}
|
|
|
|
// applyCapacity sets or clears one scope's capacity quota from the form.
|
|
func (p *AccountPages) applyCapacity(r *http.Request, admin store.User) (string, error) {
|
|
scope := strings.TrimSpace(r.FormValue("scope"))
|
|
if scope == "" {
|
|
return "", fmt.Errorf("choose where the quota applies")
|
|
}
|
|
if r.FormValue("clear") != "" {
|
|
key, err := p.capacity.ClearQuota(r.Context(), scope)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return "capacity quota removed from " + key, nil
|
|
}
|
|
field := func(name, what string) (*int, error) {
|
|
v := strings.TrimSpace(r.FormValue(name))
|
|
if v == "" {
|
|
return nil, nil
|
|
}
|
|
n, err := strconv.Atoi(v)
|
|
if err != nil || n < 0 {
|
|
return nil, fmt.Errorf("%s must be a whole number, or blank for none", what)
|
|
}
|
|
return &n, nil
|
|
}
|
|
var req adminapi.QuotaRequest
|
|
req.Scope = scope
|
|
var err error
|
|
for _, f := range []struct {
|
|
dst **int
|
|
name string
|
|
what string
|
|
}{
|
|
{&req.Guarantee.GPUs, "gpus", "guaranteed GPUs"},
|
|
{&req.Guarantee.CPUCores, "cores", "guaranteed cores"},
|
|
{&req.Guarantee.MemoryGB, "memory", "guaranteed memory"},
|
|
{&req.Limit.GPUs, "limit_gpus", "the GPU limit"},
|
|
{&req.Limit.CPUCores, "limit_cores", "the core limit"},
|
|
{&req.Limit.MemoryGB, "limit_memory", "the memory limit"},
|
|
} {
|
|
if *f.dst, err = field(f.name, f.what); err != nil {
|
|
return "", err
|
|
}
|
|
}
|
|
key, err := p.capacity.SetQuota(r.Context(), req, admin.Email)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return "capacity quota set on " + key + "; it takes effect within half a minute", nil
|
|
}
|