A restart with no room beside it waited the drain grace period before stopping the old replica, and stopped it even when the replacement would fit nowhere. It now stops at once when the replica alone blocks its replacement, gives the restart up (saying why) when nothing would fit, and ctl restart --in-place stops first on request. Refs #2 (item 3). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
82 lines
3.2 KiB
Go
82 lines
3.2 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
|
|
)
|
|
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" +
|
|
" 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})
|
|
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")
|
|
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
|
|
},
|
|
}
|
|
}
|