A model's replicas restarted one at a time, always. RestartPace says how many restart at once: MaxSurge beside themselves (replacement first), MaxUnavailable stopped first (no room needed, out while loading); the reconciler begins that many per model per pass. The admin API takes max_surge and max_unavailable on POST /api/v1/restart. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
91 lines
3.9 KiB
Go
91 lines
3.9 KiB
Go
package cli
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/control/adminapi"
|
|
)
|
|
|
|
func newRestartCmd(client func() *ctlClient) *cobra.Command {
|
|
var (
|
|
models []string
|
|
all bool
|
|
inPlace bool
|
|
surge, unavailable int
|
|
)
|
|
c := &cobra.Command{
|
|
Use: "restart [REPLICA...]",
|
|
Short: "Replace replicas with new ones, starting each replacement before stopping the old",
|
|
Long: "How a running engine picks up what only applies at start: an engine key,\n" +
|
|
"an edited profile, a new engine version.\n\n" +
|
|
"Each replacement is placed beside the replica it replaces, and the old one\n" +
|
|
"keeps serving until the new one is ready. Replicas of one model are\n" +
|
|
"replaced one at a time.\n\n" +
|
|
"Where there is no room for a second copy and the old replica is all that\n" +
|
|
"is in the way — one GPU, every card that fits held — it is stopped at once\n" +
|
|
"and the new one starts in its place; a model with only that replica is\n" +
|
|
"unserved while it loads. Where the new one would not fit even then — a\n" +
|
|
"profile edited to need more than any node has — the restart is given up\n" +
|
|
"and the old replica keeps serving: llmgw ctl replicas says so, and why.\n\n" +
|
|
"--in-place stops each replica first, whatever room there is.\n\n" +
|
|
"A model's replicas are replaced one at a time. --max-surge N replaces N at\n" +
|
|
"once, each beside itself, needing room for N more; --max-unavailable N\n" +
|
|
"stops N at once first, needing no room and leaving N fewer serving while\n" +
|
|
"they load. Together, the first N are stopped first and the rest surged.\n\n" +
|
|
" llmgw ctl restart qwen2.5-7b-3f2a91c0\n" +
|
|
" llmgw ctl restart --model qwen2.5-7b\n" +
|
|
" llmgw ctl restart --all",
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
if len(args) == 0 && len(models) == 0 && !all {
|
|
return errors.New("name replicas, --model, or --all")
|
|
}
|
|
body, _ := json.Marshal(adminapi.RestartRequest{Replicas: args, Models: models, All: all, InPlace: inPlace,
|
|
MaxSurge: surge, MaxUnavailable: unavailable})
|
|
var res adminapi.RestartResult
|
|
if err := client().post(cmd.Context(), "/api/v1/restart", body, &res); err != nil {
|
|
return err
|
|
}
|
|
fmt.Printf("%d replica(s) queued for restart; watch with: llmgw ctl replicas\n", len(res.Queued))
|
|
return nil
|
|
},
|
|
}
|
|
c.Flags().StringArrayVar(&models, "model", nil, "every replica of this model; repeatable")
|
|
c.Flags().BoolVar(&all, "all", false, "every ready replica in the cluster")
|
|
c.Flags().BoolVar(&inPlace, "in-place", false, "stop each replica before starting its replacement")
|
|
c.Flags().IntVar(&surge, "max-surge", 0, "replicas of a model replaced beside themselves at once (default 1)")
|
|
c.Flags().IntVar(&unavailable, "max-unavailable", 0,
|
|
"replicas of a model stopped first at once, each out while its replacement loads")
|
|
return c
|
|
}
|
|
|
|
func newRetryCmd(client func() *ctlClient) *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "retry MODEL",
|
|
Short: "Try a parked model again, and clear its backoff",
|
|
Long: "A model whose replicas fail every time they start is parked after five\n" +
|
|
"failures in a row: it is not placed again until its definition changes\n" +
|
|
"or this is run. Run it once the cause — the weights, the engine, the\n" +
|
|
"profile — has been dealt with; `llmgw ctl status` lists parked models with\n" +
|
|
"their last error.",
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
var res struct {
|
|
WasParked bool `json:"was_parked"`
|
|
}
|
|
if err := client().post(cmd.Context(), "/api/v1/models/"+args[0]+"/retry", []byte(`{}`), &res); err != nil {
|
|
return err
|
|
}
|
|
if res.WasParked {
|
|
fmt.Printf("%s unparked; it is placed on the next pass\n", args[0])
|
|
} else {
|
|
fmt.Printf("%s was not parked; its backoff is cleared\n", args[0])
|
|
}
|
|
return nil
|
|
},
|
|
}
|
|
}
|