2023-08-18 23:49:33 +00:00
|
|
|
package model
|
|
|
|
|
|
|
|
|
|
import (
|
2024-04-29 13:11:42 +00:00
|
|
|
"errors"
|
2023-08-18 23:49:33 +00:00
|
|
|
"fmt"
|
|
|
|
|
"os"
|
2024-08-23 22:27:14 +00:00
|
|
|
"path/filepath"
|
2023-08-18 23:49:33 +00:00
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
2025-03-31 22:01:10 +00:00
|
|
|
"time"
|
2023-08-18 23:49:33 +00:00
|
|
|
|
|
|
|
|
"github.com/hpcloud/tail"
|
2025-10-09 10:36:45 +00:00
|
|
|
"github.com/mudler/LocalAI/pkg/signals"
|
2023-08-18 23:49:33 +00:00
|
|
|
process "github.com/mudler/go-processmanager"
|
2025-12-21 18:33:13 +00:00
|
|
|
"github.com/mudler/xlog"
|
2023-08-18 23:49:33 +00:00
|
|
|
)
|
|
|
|
|
|
2025-03-31 22:01:10 +00:00
|
|
|
var forceBackendShutdown bool = os.Getenv("LOCALAI_FORCE_BACKEND_SHUTDOWN") == "true"
|
|
|
|
|
|
2026-02-19 16:18:38 +00:00
|
|
|
var (
|
|
|
|
|
modelNotFoundErr = errors.New("model not found")
|
|
|
|
|
)
|
|
|
|
|
|
2023-08-18 23:49:33 +00:00
|
|
|
func (ml *ModelLoader) deleteProcess(s string) error {
|
2025-03-31 22:01:10 +00:00
|
|
|
model, ok := ml.models[s]
|
|
|
|
|
if !ok {
|
2025-12-21 18:33:13 +00:00
|
|
|
xlog.Debug("Model not found", "model", s)
|
2026-02-19 16:18:38 +00:00
|
|
|
return modelNotFoundErr
|
2025-03-31 22:01:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
retries := 1
|
|
|
|
|
for model.GRPC(false, ml.wd).IsBusy() {
|
2025-12-21 18:33:13 +00:00
|
|
|
xlog.Debug("Model busy. Waiting.", "model", s)
|
2025-03-31 22:01:10 +00:00
|
|
|
dur := time.Duration(retries*2) * time.Second
|
|
|
|
|
if dur > retryTimeout {
|
|
|
|
|
dur = retryTimeout
|
|
|
|
|
}
|
|
|
|
|
time.Sleep(dur)
|
|
|
|
|
retries++
|
2024-10-02 18:37:40 +00:00
|
|
|
|
2025-03-31 22:01:10 +00:00
|
|
|
if retries > 10 && forceBackendShutdown {
|
2025-12-21 18:33:13 +00:00
|
|
|
xlog.Warn("Model is still busy after retries. Forcing shutdown.", "model", s, "retries", retries)
|
2025-03-31 22:01:10 +00:00
|
|
|
break
|
|
|
|
|
}
|
2024-10-02 06:55:58 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-21 18:33:13 +00:00
|
|
|
xlog.Debug("Deleting process", "model", s)
|
2025-03-31 22:01:10 +00:00
|
|
|
|
|
|
|
|
process := model.Process()
|
2024-10-02 06:55:58 +00:00
|
|
|
if process == nil {
|
2025-12-21 18:33:13 +00:00
|
|
|
xlog.Error("No process", "model", s)
|
2024-10-02 06:55:58 +00:00
|
|
|
// Nothing to do as there is no process
|
2026-01-10 22:33:44 +00:00
|
|
|
delete(ml.models, s)
|
2024-10-02 06:55:58 +00:00
|
|
|
return nil
|
2023-08-18 23:49:33 +00:00
|
|
|
}
|
2024-10-02 06:55:58 +00:00
|
|
|
|
|
|
|
|
err := process.Stop()
|
|
|
|
|
if err != nil {
|
2025-12-21 18:33:13 +00:00
|
|
|
xlog.Error("(deleteProcess) error while deleting process", "error", err, "model", s)
|
2024-10-02 06:55:58 +00:00
|
|
|
}
|
|
|
|
|
|
2026-01-10 22:33:44 +00:00
|
|
|
if err == nil {
|
|
|
|
|
delete(ml.models, s)
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-02 06:55:58 +00:00
|
|
|
return err
|
2023-08-18 23:49:33 +00:00
|
|
|
}
|
|
|
|
|
|
2024-04-29 13:11:42 +00:00
|
|
|
func (ml *ModelLoader) StopGRPC(filter GRPCProcessFilter) error {
|
|
|
|
|
var err error = nil
|
2025-03-31 22:01:10 +00:00
|
|
|
ml.mu.Lock()
|
|
|
|
|
defer ml.mu.Unlock()
|
|
|
|
|
|
2024-09-26 10:44:55 +00:00
|
|
|
for k, m := range ml.models {
|
|
|
|
|
if filter(k, m.Process()) {
|
2025-03-31 22:01:10 +00:00
|
|
|
e := ml.deleteProcess(k)
|
2024-04-29 13:11:42 +00:00
|
|
|
err = errors.Join(err, e)
|
2023-08-18 23:49:33 +00:00
|
|
|
}
|
|
|
|
|
}
|
2024-04-29 13:11:42 +00:00
|
|
|
return err
|
2023-08-18 23:49:33 +00:00
|
|
|
}
|
|
|
|
|
|
2024-04-29 13:11:42 +00:00
|
|
|
func (ml *ModelLoader) StopAllGRPC() error {
|
2024-09-17 14:51:40 +00:00
|
|
|
return ml.StopGRPC(all)
|
2023-08-18 23:49:33 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ml *ModelLoader) GetGRPCPID(id string) (int, error) {
|
2024-09-17 14:51:40 +00:00
|
|
|
ml.mu.Lock()
|
|
|
|
|
defer ml.mu.Unlock()
|
2024-09-26 10:44:55 +00:00
|
|
|
p, exists := ml.models[id]
|
2023-08-18 23:49:33 +00:00
|
|
|
if !exists {
|
|
|
|
|
return -1, fmt.Errorf("no grpc backend found for %s", id)
|
|
|
|
|
}
|
2024-09-26 10:44:55 +00:00
|
|
|
if p.Process() == nil {
|
|
|
|
|
return -1, fmt.Errorf("no grpc backend found for %s", id)
|
|
|
|
|
}
|
|
|
|
|
return strconv.Atoi(p.Process().PID)
|
2023-08-18 23:49:33 +00:00
|
|
|
}
|
|
|
|
|
|
2024-09-26 10:44:55 +00:00
|
|
|
func (ml *ModelLoader) startProcess(grpcProcess, id string, serverAddress string, args ...string) (*process.Process, error) {
|
2023-08-18 23:49:33 +00:00
|
|
|
// Make sure the process is executable
|
2025-08-13 10:08:53 +00:00
|
|
|
// Check first if it has executable permissions
|
|
|
|
|
if fi, err := os.Stat(grpcProcess); err == nil {
|
|
|
|
|
if fi.Mode()&0111 == 0 {
|
2025-12-21 18:33:13 +00:00
|
|
|
xlog.Debug("Process is not executable. Making it executable.", "process", grpcProcess)
|
2025-08-13 10:08:53 +00:00
|
|
|
if err := os.Chmod(grpcProcess, 0700); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-08-18 23:49:33 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-21 18:33:13 +00:00
|
|
|
xlog.Debug("Loading GRPC Process", "process", grpcProcess)
|
2023-08-18 23:49:33 +00:00
|
|
|
|
2025-12-21 18:33:13 +00:00
|
|
|
xlog.Debug("GRPC Service will be running", "id", id, "address", serverAddress)
|
2023-08-18 23:49:33 +00:00
|
|
|
|
2024-08-23 22:27:14 +00:00
|
|
|
workDir, err := filepath.Abs(filepath.Dir(grpcProcess))
|
|
|
|
|
if err != nil {
|
2024-09-26 10:44:55 +00:00
|
|
|
return nil, err
|
2024-08-23 22:27:14 +00:00
|
|
|
}
|
|
|
|
|
|
2023-08-18 23:49:33 +00:00
|
|
|
grpcControlProcess := process.New(
|
|
|
|
|
process.WithTemporaryStateDir(),
|
2024-08-23 22:27:14 +00:00
|
|
|
process.WithName(filepath.Base(grpcProcess)),
|
2024-06-18 20:43:43 +00:00
|
|
|
process.WithArgs(append(args, []string{"--addr", serverAddress}...)...),
|
2023-08-18 23:49:33 +00:00
|
|
|
process.WithEnvironment(os.Environ()...),
|
2024-08-23 22:27:14 +00:00
|
|
|
process.WithWorkDir(workDir),
|
2023-08-18 23:49:33 +00:00
|
|
|
)
|
|
|
|
|
|
2023-11-26 17:36:23 +00:00
|
|
|
if ml.wd != nil {
|
|
|
|
|
ml.wd.Add(serverAddress, grpcControlProcess)
|
|
|
|
|
ml.wd.AddAddressModelMap(serverAddress, id)
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-18 23:49:33 +00:00
|
|
|
if err := grpcControlProcess.Run(); err != nil {
|
2024-09-26 10:44:55 +00:00
|
|
|
return grpcControlProcess, err
|
2023-08-18 23:49:33 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-21 18:33:13 +00:00
|
|
|
xlog.Debug("GRPC Service state dir", "dir", grpcControlProcess.StateDir())
|
2025-10-09 10:36:45 +00:00
|
|
|
|
|
|
|
|
signals.RegisterGracefulTerminationHandler(func() {
|
2024-06-24 06:34:36 +00:00
|
|
|
err := grpcControlProcess.Stop()
|
|
|
|
|
if err != nil {
|
2025-12-21 18:33:13 +00:00
|
|
|
xlog.Error("error while shutting down grpc process", "error", err)
|
2024-06-24 06:34:36 +00:00
|
|
|
}
|
2025-10-09 10:36:45 +00:00
|
|
|
})
|
2023-08-18 23:49:33 +00:00
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
|
t, err := tail.TailFile(grpcControlProcess.StderrPath(), tail.Config{Follow: true})
|
|
|
|
|
if err != nil {
|
2025-12-21 18:33:13 +00:00
|
|
|
xlog.Debug("Could not tail stderr")
|
2023-08-18 23:49:33 +00:00
|
|
|
}
|
|
|
|
|
for line := range t.Lines {
|
2025-12-21 18:33:13 +00:00
|
|
|
xlog.Debug("GRPC stderr", "id", strings.Join([]string{id, serverAddress}, "-"), "line", line.Text)
|
2023-08-18 23:49:33 +00:00
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
go func() {
|
|
|
|
|
t, err := tail.TailFile(grpcControlProcess.StdoutPath(), tail.Config{Follow: true})
|
|
|
|
|
if err != nil {
|
2025-12-21 18:33:13 +00:00
|
|
|
xlog.Debug("Could not tail stdout")
|
2023-08-18 23:49:33 +00:00
|
|
|
}
|
|
|
|
|
for line := range t.Lines {
|
2025-12-21 18:33:13 +00:00
|
|
|
xlog.Debug("GRPC stdout", "id", strings.Join([]string{id, serverAddress}, "-"), "line", line.Text)
|
2023-08-18 23:49:33 +00:00
|
|
|
}
|
|
|
|
|
}()
|
|
|
|
|
|
2024-09-26 10:44:55 +00:00
|
|
|
return grpcControlProcess, nil
|
2023-08-18 23:49:33 +00:00
|
|
|
}
|