mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-31 00:04:44 +00:00
feat(ai): add unified GPU configuration and collapsible deployment cards (#14253)
This commit is contained in:
@@ -3,11 +3,16 @@ package v2
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/service"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/accelerator"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func (b *BaseApi) LoadModelGPUDevices(c *gin.Context) {
|
||||
helper.SuccessWithData(c, service.LoadModelGPUDevices())
|
||||
}
|
||||
|
||||
// @Tags AI
|
||||
// @Summary Load gpu / xpu info
|
||||
// @Accept json
|
||||
|
||||
@@ -2,6 +2,28 @@ package dto
|
||||
|
||||
import "time"
|
||||
|
||||
type ModelGPUDevice struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
Available bool `json:"available"`
|
||||
}
|
||||
|
||||
type ModelGPUDevices struct {
|
||||
Devices []ModelGPUDevice `json:"devices"`
|
||||
Warnings []string `json:"warnings"`
|
||||
}
|
||||
|
||||
type ModelGPUConfig struct {
|
||||
Mode string `json:"mode"`
|
||||
Count int `json:"count,omitempty"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Enabled bool `json:"enabled"`
|
||||
DeviceIDs []string `json:"deviceIDs"`
|
||||
Capabilities []string `json:"capabilities"`
|
||||
}
|
||||
|
||||
type OllamaModelInfo struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -22,6 +22,9 @@ type AppInstallCreate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
Services map[string]string `json:"services"`
|
||||
TaskID string `json:"taskID"`
|
||||
GPU *dto.ModelGPUConfig `json:"gpu"`
|
||||
|
||||
GPUImageType string `json:"-"`
|
||||
|
||||
SyncModelAccount *bool `json:"syncModelAccount"`
|
||||
ModelAccountBaseURLType string `json:"modelAccountBaseURLType" validate:"omitempty,oneof=container localhost systemIP custom"`
|
||||
@@ -128,6 +131,7 @@ type AppInstallDelete struct {
|
||||
}
|
||||
|
||||
type AppInstalledUpdate struct {
|
||||
GPU *dto.ModelGPUConfig `json:"gpu"`
|
||||
SyncModelAccount *bool `json:"syncModelAccount"`
|
||||
ModelAccountBaseURLType string `json:"modelAccountBaseURLType" validate:"omitempty,oneof=container localhost systemIP custom"`
|
||||
ModelAccountBaseURL string `json:"modelAccountBaseURL"`
|
||||
|
||||
@@ -8,14 +8,15 @@ type TensorRTLLMSearch struct {
|
||||
}
|
||||
|
||||
type TensorRTLLMCreate struct {
|
||||
Name string `json:"name" validate:"required"`
|
||||
ContainerName string `json:"containerName" validate:"required"`
|
||||
Version string `json:"version" validate:"required"`
|
||||
ModelDir string `json:"modelDir" validate:"required"`
|
||||
Image string `json:"image" validate:"required"`
|
||||
Command string `json:"command" validate:"required"`
|
||||
ModelType string `json:"modelType"`
|
||||
ModelSpeedup bool `json:"modelSpeedup"`
|
||||
Name string `json:"name" validate:"required"`
|
||||
ContainerName string `json:"containerName" validate:"required"`
|
||||
Version string `json:"version" validate:"required"`
|
||||
ModelDir string `json:"modelDir" validate:"required"`
|
||||
Image string `json:"image" validate:"required"`
|
||||
Command string `json:"command" validate:"required"`
|
||||
ModelType string `json:"modelType"`
|
||||
ModelSpeedup bool `json:"modelSpeedup"`
|
||||
GPU *dto.ModelGPUConfig `json:"gpu"`
|
||||
DockerConfig
|
||||
}
|
||||
|
||||
|
||||
@@ -186,13 +186,14 @@ type AppParam struct {
|
||||
}
|
||||
|
||||
type AppConfig struct {
|
||||
AppKey string `json:"appKey"`
|
||||
Name string `json:"name"`
|
||||
HttpPort int `json:"httpPort"`
|
||||
SyncModelAccount bool `json:"syncModelAccount"`
|
||||
ModelAccountBaseURLType string `json:"modelAccountBaseURLType"`
|
||||
ModelAccountBaseURL string `json:"modelAccountBaseURL"`
|
||||
Params []AppParam `json:"params"`
|
||||
RawCompose string `json:"rawCompose"`
|
||||
GPU *dto.ModelGPUConfig `json:"gpu,omitempty"`
|
||||
AppKey string `json:"appKey"`
|
||||
Name string `json:"name"`
|
||||
HttpPort int `json:"httpPort"`
|
||||
SyncModelAccount bool `json:"syncModelAccount"`
|
||||
ModelAccountBaseURLType string `json:"modelAccountBaseURLType"`
|
||||
ModelAccountBaseURL string `json:"modelAccountBaseURL"`
|
||||
Params []AppParam `json:"params"`
|
||||
RawCompose string `json:"rawCompose"`
|
||||
request.AppContainerConfig
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto/request"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
)
|
||||
@@ -12,12 +13,13 @@ type TensorRTLLMsRes struct {
|
||||
|
||||
type TensorRTLLMDTO struct {
|
||||
model.TensorRTLLM
|
||||
Version string `json:"version"`
|
||||
Model string `json:"model"`
|
||||
Dir string `json:"dir"`
|
||||
ModelDir string `json:"modelDir"`
|
||||
Image string `json:"image"`
|
||||
Command string `json:"command"`
|
||||
Version string `json:"version"`
|
||||
Model string `json:"model"`
|
||||
Dir string `json:"dir"`
|
||||
ModelDir string `json:"modelDir"`
|
||||
Image string `json:"image"`
|
||||
Command string `json:"command"`
|
||||
GPU *dto.ModelGPUConfig `json:"gpu"`
|
||||
|
||||
ExposedPorts []request.ExposedPort `json:"exposedPorts"`
|
||||
Environments []request.Environment `json:"environments"`
|
||||
|
||||
@@ -542,6 +542,26 @@ func (a AppService) installWithHooks(req request.AppInstallCreate, executeScript
|
||||
composeByte []byte
|
||||
paramByte []byte
|
||||
)
|
||||
if (app.Key == "ollama" || app.Key == "vllm") && req.GPU != nil {
|
||||
gpuServiceName := appInstall.ServiceName
|
||||
imageType := "nvidia"
|
||||
if app.Key == "vllm" {
|
||||
imageType = req.GPUImageType
|
||||
if len(servicesMap) > 1 {
|
||||
gpuServiceName = "vllm"
|
||||
if _, ok := servicesMap[gpuServiceName]; !ok {
|
||||
gpuServiceName = "vllm-dspark"
|
||||
}
|
||||
}
|
||||
}
|
||||
service, ok := servicesMap[gpuServiceName].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, buserr.New("ErrFileParse")
|
||||
}
|
||||
if err = ApplyModelGPU(service, req.GPU, req.Params, imageType); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
composeByte, err = yaml.Marshal(composeMap)
|
||||
if err != nil {
|
||||
|
||||
@@ -493,9 +493,10 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
||||
}
|
||||
|
||||
backupDockerCompose := installed.DockerCompose
|
||||
if req.Advanced {
|
||||
updateGPU := installed.App.Key == "ollama" && req.GPU != nil
|
||||
if req.Advanced || updateGPU {
|
||||
composeMap := make(map[string]interface{})
|
||||
if req.EditCompose {
|
||||
if req.Advanced && req.EditCompose {
|
||||
if err = yaml.Unmarshal([]byte(req.DockerCompose), &composeMap); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -504,16 +505,35 @@ func (a *AppInstallService) Update(req request.AppInstalledUpdate) error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !req.SkipComposeCommonConfig {
|
||||
if req.Advanced && !req.SkipComposeCommonConfig {
|
||||
if err = addDockerComposeCommonParam(composeMap, installed.ServiceName, req.AppContainerConfig, req.Params); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if updateGPU {
|
||||
services, _ := composeMap["services"].(map[string]interface{})
|
||||
service, ok := services[installed.ServiceName].(map[string]interface{})
|
||||
if !ok {
|
||||
return buserr.New("ErrFileParse")
|
||||
}
|
||||
gpuParams := map[string]interface{}{}
|
||||
if err := json.Unmarshal([]byte(installed.Env), &gpuParams); err != nil {
|
||||
return err
|
||||
}
|
||||
for key, value := range req.Params {
|
||||
gpuParams[key] = value
|
||||
}
|
||||
if err := ApplyModelGPU(service, req.GPU, gpuParams, "nvidia"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
composeByte, err := yaml.Marshal(composeMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
installed.DockerCompose = string(composeByte)
|
||||
}
|
||||
if req.Advanced {
|
||||
if req.ContainerName == "" {
|
||||
req.Params[constant.ContainerName] = installed.ContainerName
|
||||
} else {
|
||||
@@ -1013,6 +1033,16 @@ func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) {
|
||||
res.ModelAccountBaseURLType = install.ModelAccountBaseURLType
|
||||
res.ModelAccountBaseURL = install.ModelAccountBaseURL
|
||||
if install.App.Key == "ollama" {
|
||||
var project map[string]interface{}
|
||||
if err := yaml.Unmarshal([]byte(install.DockerCompose), &project); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
services, _ := project["services"].(map[string]interface{})
|
||||
service, ok := services[install.ServiceName].(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, buserr.New("ErrFileParse")
|
||||
}
|
||||
res.GPU = ReadModelGPU(service, envs, "nvidia")
|
||||
res.ModelAccountBaseURLType, res.ModelAccountBaseURL, err = loadOllamaModelAccountAddress(install)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -0,0 +1,663 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/utils/re"
|
||||
"github.com/compose-spec/compose-go/v2/interpolation"
|
||||
"github.com/compose-spec/compose-go/v2/template"
|
||||
)
|
||||
|
||||
func LoadModelGPUDevices() dto.ModelGPUDevices {
|
||||
result := dto.ModelGPUDevices{Devices: []dto.ModelGPUDevice{}, Warnings: []string{}}
|
||||
uuidPattern := re.GetRegex(re.NvidiaGPUUUIDPattern)
|
||||
renderPattern := re.GetRegex(re.IntelGPURenderNamePattern)
|
||||
ascendPattern := re.GetRegex(re.AscendGPUDeviceNamePattern)
|
||||
for _, directory := range []string{"/proc/driver/nvidia/gpus", "/sys/class/drm", "/dev"} {
|
||||
entries, err := os.ReadDir(directory)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
result.Warnings = append(result.Warnings, err.Error())
|
||||
}
|
||||
continue
|
||||
}
|
||||
for _, entry := range entries {
|
||||
var device dto.ModelGPUDevice
|
||||
var devicePath string
|
||||
switch directory {
|
||||
case "/proc/driver/nvidia/gpus":
|
||||
informationPath := filepath.Join(directory, entry.Name(), "information")
|
||||
content, err := os.ReadFile(informationPath)
|
||||
if err != nil {
|
||||
result.Warnings = append(result.Warnings, err.Error())
|
||||
continue
|
||||
}
|
||||
fields := map[string]string{}
|
||||
for _, line := range strings.Split(string(content), "\n") {
|
||||
key, value, found := strings.Cut(line, ":")
|
||||
if found {
|
||||
fields[strings.TrimSpace(key)] = strings.TrimSpace(value)
|
||||
}
|
||||
}
|
||||
uuid := fields["GPU UUID"]
|
||||
minor, err := strconv.Atoi(fields["Device Minor"])
|
||||
if !uuidPattern.MatchString(uuid) || err != nil || minor < 0 {
|
||||
result.Warnings = append(result.Warnings, fmt.Sprintf("invalid NVIDIA GPU UUID or device minor in %s", informationPath))
|
||||
continue
|
||||
}
|
||||
name := fields["Model"]
|
||||
if name == "" {
|
||||
name = "NVIDIA"
|
||||
}
|
||||
device = dto.ModelGPUDevice{ID: "nvidia:" + uuid, Name: name + " · " + entry.Name(), Type: "nvidia", UUID: uuid}
|
||||
devicePath = filepath.Join("/dev", "nvidia"+strconv.Itoa(minor))
|
||||
case "/sys/class/drm":
|
||||
if !renderPattern.MatchString(entry.Name()) {
|
||||
continue
|
||||
}
|
||||
vendor, err := os.ReadFile(filepath.Join(directory, entry.Name(), "device/vendor"))
|
||||
if err != nil {
|
||||
result.Warnings = append(result.Warnings, err.Error())
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(string(vendor)) != "0x8086" {
|
||||
continue
|
||||
}
|
||||
devicePath = filepath.Join("/dev/dri", entry.Name())
|
||||
device = dto.ModelGPUDevice{ID: "intel:" + devicePath, Name: "Intel · " + entry.Name(), Type: "intel"}
|
||||
case "/dev":
|
||||
if !ascendPattern.MatchString(entry.Name()) {
|
||||
continue
|
||||
}
|
||||
devicePath = filepath.Join(directory, entry.Name())
|
||||
device = dto.ModelGPUDevice{ID: "ascend:" + strings.TrimPrefix(entry.Name(), "davinci"), Name: "Ascend · " + entry.Name(), Type: "ascend"}
|
||||
}
|
||||
info, err := os.Stat(devicePath)
|
||||
if err != nil {
|
||||
result.Warnings = append(result.Warnings, err.Error())
|
||||
} else {
|
||||
device.Available = info.Mode()&os.ModeCharDevice != 0
|
||||
}
|
||||
result.Devices = append(result.Devices, device)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func ApplyModelGPU(service map[string]interface{}, config *dto.ModelGPUConfig, params map[string]interface{}, imageType string) error {
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
if err := validateModelGPU(config, imageType); err != nil {
|
||||
return err
|
||||
}
|
||||
resolved, err := resolveModelGPUService(service, params, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
devicePaths := []string{}
|
||||
ids := []string{}
|
||||
logicalIDs := []string{}
|
||||
if config.Enabled && imageType != "nvidia" {
|
||||
for _, deviceID := range config.DeviceIDs {
|
||||
id := strings.TrimPrefix(deviceID, imageType+":")
|
||||
if !slices.Contains(ids, id) {
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
if imageType == "ascend" {
|
||||
slices.SortFunc(ids, func(a, b string) int {
|
||||
left, _ := strconv.Atoi(a)
|
||||
right, _ := strconv.Atoi(b)
|
||||
return left - right
|
||||
})
|
||||
visibleIDs := ids
|
||||
if config.Mode == "all" || resolved["privileged"] == true || resolved["privileged"] == "true" {
|
||||
visibleIDs = loadModelAscendDeviceIDs()
|
||||
if len(visibleIDs) == 0 {
|
||||
return fmt.Errorf("no available Ascend devices")
|
||||
}
|
||||
}
|
||||
if config.Mode == "all" {
|
||||
ids = visibleIDs
|
||||
} else {
|
||||
for _, id := range ids {
|
||||
index := slices.Index(visibleIDs, id)
|
||||
if index < 0 {
|
||||
return fmt.Errorf("Ascend GPU is unavailable: %s", id)
|
||||
}
|
||||
logicalIDs = append(logicalIDs, strconv.Itoa(index))
|
||||
}
|
||||
}
|
||||
for _, id := range ids {
|
||||
devicePaths = append(devicePaths, "/dev/davinci"+id)
|
||||
}
|
||||
devicePaths = append(devicePaths, "/dev/davinci_manager", "/dev/devmm_svm", "/dev/hisi_hdc")
|
||||
} else if config.Mode == "all" {
|
||||
devicePaths = append(devicePaths, "/dev/dri")
|
||||
} else {
|
||||
devicePaths = append(devicePaths, ids...)
|
||||
}
|
||||
}
|
||||
if imageType == "nvidia" {
|
||||
delete(service, "gpus")
|
||||
}
|
||||
if imageType == "nvidia" && resolved["runtime"] == "nvidia" && !config.Enabled {
|
||||
delete(service, "runtime")
|
||||
}
|
||||
deploy, _ := service["deploy"].(map[string]interface{})
|
||||
resources, _ := deploy["resources"].(map[string]interface{})
|
||||
reservations, _ := resources["reservations"].(map[string]interface{})
|
||||
devices, _ := reservations["devices"].([]interface{})
|
||||
resolvedDevices, _ := resolved["reservations"].([]interface{})
|
||||
kept := []interface{}{}
|
||||
var gpuDevice map[string]interface{}
|
||||
for index, item := range devices {
|
||||
device, _ := resolvedDevices[index].(map[string]interface{})
|
||||
capabilities, _ := device["capabilities"].([]interface{})
|
||||
driver, _ := device["driver"].(string)
|
||||
isNvidiaGPU := driver == "nvidia" || (driver == "" && slices.Contains(capabilities, interface{}("gpu")))
|
||||
if imageType != "nvidia" || !isNvidiaGPU {
|
||||
kept = append(kept, item)
|
||||
} else if gpuDevice == nil {
|
||||
gpuDevice, _ = item.(map[string]interface{})
|
||||
}
|
||||
}
|
||||
if config.Enabled && imageType == "nvidia" {
|
||||
device := gpuDevice
|
||||
if device == nil {
|
||||
device = map[string]interface{}{}
|
||||
}
|
||||
device["driver"] = "nvidia"
|
||||
device["capabilities"] = config.Capabilities
|
||||
delete(device, "count")
|
||||
delete(device, "device_ids")
|
||||
switch config.Mode {
|
||||
case "all":
|
||||
device["count"] = "all"
|
||||
case "count":
|
||||
device["count"] = config.Count
|
||||
default:
|
||||
ids := []string{}
|
||||
for _, id := range config.DeviceIDs {
|
||||
ids = append(ids, strings.TrimPrefix(id, "nvidia:"))
|
||||
}
|
||||
device["device_ids"] = ids
|
||||
}
|
||||
kept = append(kept, device)
|
||||
}
|
||||
|
||||
if len(kept) > 0 {
|
||||
if deploy == nil {
|
||||
deploy = map[string]interface{}{}
|
||||
service["deploy"] = deploy
|
||||
}
|
||||
if resources == nil {
|
||||
resources = map[string]interface{}{}
|
||||
deploy["resources"] = resources
|
||||
}
|
||||
if reservations == nil {
|
||||
reservations = map[string]interface{}{}
|
||||
resources["reservations"] = reservations
|
||||
}
|
||||
reservations["devices"] = kept
|
||||
} else {
|
||||
delete(reservations, "devices")
|
||||
if len(reservations) == 0 {
|
||||
delete(resources, "reservations")
|
||||
}
|
||||
if len(resources) == 0 {
|
||||
delete(deploy, "resources")
|
||||
}
|
||||
if len(deploy) == 0 {
|
||||
delete(service, "deploy")
|
||||
}
|
||||
}
|
||||
mappedPaths := []string{}
|
||||
for _, key := range []string{"devices", "volumes"} {
|
||||
mappings, _ := service[key].([]interface{})
|
||||
resolvedMappings, _ := resolved[key].([]interface{})
|
||||
kept := []interface{}{}
|
||||
for index, item := range mappings {
|
||||
source := modelGPUDeviceSource(resolvedMappings[index])
|
||||
managed := false
|
||||
switch imageType {
|
||||
case "nvidia":
|
||||
managed = strings.HasPrefix(source, "/dev/nvidia") || strings.HasPrefix(source, "nvidia.com/gpu=")
|
||||
case "intel":
|
||||
managed = source == "/dev/dri" || strings.HasPrefix(source, "/dev/dri/")
|
||||
case "ascend":
|
||||
managed = strings.HasPrefix(source, "/dev/davinci") || source == "/dev/devmm_svm" || source == "/dev/hisi_hdc"
|
||||
}
|
||||
if managed {
|
||||
selected := slices.Contains(devicePaths, source)
|
||||
if config.Enabled && config.Mode == "all" && imageType == "intel" {
|
||||
selected = true
|
||||
}
|
||||
if selected {
|
||||
kept = append(kept, item)
|
||||
mappedPaths = append(mappedPaths, source)
|
||||
}
|
||||
continue
|
||||
}
|
||||
kept = append(kept, item)
|
||||
}
|
||||
if len(kept) == 0 {
|
||||
delete(service, key)
|
||||
} else {
|
||||
service[key] = kept
|
||||
}
|
||||
}
|
||||
if config.Enabled && imageType != "nvidia" {
|
||||
devices, _ := service["devices"].([]interface{})
|
||||
for _, path := range devicePaths {
|
||||
if !slices.Contains(mappedPaths, path) {
|
||||
devices = append(devices, path+":"+path)
|
||||
}
|
||||
}
|
||||
if len(devices) > 0 {
|
||||
service["devices"] = devices
|
||||
}
|
||||
}
|
||||
gpuEnvironment := map[string]string{}
|
||||
if config.Enabled && imageType == "ascend" && config.Mode != "all" {
|
||||
gpuEnvironment["ASCEND_RT_VISIBLE_DEVICES"] = strings.Join(logicalIDs, ",")
|
||||
}
|
||||
var environmentKeys []string
|
||||
switch imageType {
|
||||
case "nvidia":
|
||||
environmentKeys = []string{"NVIDIA_VISIBLE_DEVICES", "NVIDIA_DRIVER_CAPABILITIES"}
|
||||
case "ascend":
|
||||
environmentKeys = []string{"ASCEND_RT_VISIBLE_DEVICES"}
|
||||
if params != nil {
|
||||
params["ASCEND_RT_VISIBLE_DEVICES"] = strings.Join(logicalIDs, ",")
|
||||
}
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
switch environment := service["environment"].(type) {
|
||||
case map[string]interface{}:
|
||||
for _, key := range environmentKeys {
|
||||
delete(environment, key)
|
||||
}
|
||||
for key, value := range gpuEnvironment {
|
||||
environment[key] = value
|
||||
}
|
||||
case []interface{}:
|
||||
kept := []interface{}{}
|
||||
for _, item := range environment {
|
||||
value, ok := item.(string)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid Compose environment")
|
||||
}
|
||||
key, _, _ := strings.Cut(value, "=")
|
||||
if !slices.Contains(environmentKeys, key) {
|
||||
kept = append(kept, item)
|
||||
}
|
||||
}
|
||||
for key, value := range gpuEnvironment {
|
||||
kept = append(kept, key+"="+value)
|
||||
}
|
||||
service["environment"] = kept
|
||||
case nil:
|
||||
if len(gpuEnvironment) > 0 {
|
||||
environment := map[string]interface{}{}
|
||||
for key, value := range gpuEnvironment {
|
||||
environment[key] = value
|
||||
}
|
||||
service["environment"] = environment
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("invalid Compose environment")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ReadModelGPU(service map[string]interface{}, params map[string]interface{}, imageType string) *dto.ModelGPUConfig {
|
||||
resolved, err := resolveModelGPUService(service, params, true)
|
||||
if err != nil {
|
||||
return &dto.ModelGPUConfig{Mode: "devices", DeviceIDs: []string{}, Capabilities: []string{"gpu"}, Error: err.Error()}
|
||||
}
|
||||
if imageType == "nvidia" {
|
||||
config, err := readModelGPU(service, resolved)
|
||||
if err != nil {
|
||||
return &dto.ModelGPUConfig{Mode: "devices", DeviceIDs: []string{}, Capabilities: []string{"gpu"}, Error: err.Error()}
|
||||
}
|
||||
return config
|
||||
}
|
||||
config := &dto.ModelGPUConfig{Mode: "devices", DeviceIDs: []string{}, Capabilities: []string{"gpu"}}
|
||||
privileged := resolved["privileged"] == true || resolved["privileged"] == "true"
|
||||
all := false
|
||||
for _, key := range []string{"devices", "volumes"} {
|
||||
mappings, _ := resolved[key].([]interface{})
|
||||
for _, item := range mappings {
|
||||
source := modelGPUDeviceSource(item)
|
||||
id := ""
|
||||
if imageType == "intel" {
|
||||
switch {
|
||||
case source == "/dev/dri":
|
||||
all = true
|
||||
case re.GetRegex(re.IntelGPUDevicePathPattern).MatchString(source):
|
||||
id = imageType + ":" + source
|
||||
case strings.HasPrefix(source, "/dev/dri/"):
|
||||
config.Error = "unsupported Intel GPU mapping requires an explicit selection: " + source
|
||||
return config
|
||||
}
|
||||
}
|
||||
if imageType == "ascend" && re.GetRegex(re.AscendGPUDevicePathPattern).MatchString(source) {
|
||||
id = imageType + ":" + strings.TrimPrefix(source, "/dev/davinci")
|
||||
}
|
||||
if id != "" && !slices.Contains(config.DeviceIDs, id) {
|
||||
config.DeviceIDs = append(config.DeviceIDs, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
config.Enabled = all || len(config.DeviceIDs) > 0
|
||||
config.Mode = "devices"
|
||||
if all {
|
||||
config.Mode = "all"
|
||||
config.DeviceIDs = []string{}
|
||||
}
|
||||
if imageType == "ascend" {
|
||||
const key = "ASCEND_RT_VISIBLE_DEVICES"
|
||||
visible := ""
|
||||
switch environment := service["environment"].(type) {
|
||||
case map[string]interface{}:
|
||||
if value, exists := environment[key]; exists {
|
||||
if value == nil {
|
||||
visible = "${" + key + "}"
|
||||
} else {
|
||||
visible = fmt.Sprint(value)
|
||||
}
|
||||
}
|
||||
case []interface{}:
|
||||
for _, item := range environment {
|
||||
value, _ := item.(string)
|
||||
if value == key {
|
||||
visible = "${" + key + "}"
|
||||
} else if strings.HasPrefix(value, key+"=") {
|
||||
visible = strings.TrimPrefix(value, key+"=")
|
||||
}
|
||||
}
|
||||
}
|
||||
visible, err := resolveModelGPUValue(visible, params)
|
||||
if err != nil {
|
||||
config.Error = err.Error()
|
||||
return config
|
||||
}
|
||||
visible = strings.TrimSpace(visible)
|
||||
if visible == "all" || (visible == "" && config.Enabled) {
|
||||
config.Enabled = true
|
||||
config.Mode = "all"
|
||||
config.DeviceIDs = []string{}
|
||||
} else if visible != "" {
|
||||
physicalIDs := []string{}
|
||||
for _, id := range config.DeviceIDs {
|
||||
physicalIDs = append(physicalIDs, strings.TrimPrefix(id, "ascend:"))
|
||||
}
|
||||
slices.SortFunc(physicalIDs, func(a, b string) int {
|
||||
left, _ := strconv.Atoi(a)
|
||||
right, _ := strconv.Atoi(b)
|
||||
return left - right
|
||||
})
|
||||
if privileged {
|
||||
physicalIDs = loadModelAscendDeviceIDs()
|
||||
}
|
||||
selected := []string{}
|
||||
for _, id := range strings.Split(visible, ",") {
|
||||
id = strings.TrimSpace(id)
|
||||
if !re.GetRegex(re.AscendGPUDeviceIDPattern).MatchString(id) {
|
||||
config.Error = "invalid Ascend GPU device: " + id
|
||||
return config
|
||||
}
|
||||
index, err := strconv.Atoi(id)
|
||||
if err != nil || index < 0 || index >= len(physicalIDs) {
|
||||
config.Error = "Ascend logical GPU is not available in the container: " + id
|
||||
return config
|
||||
}
|
||||
selected = append(selected, "ascend:"+physicalIDs[index])
|
||||
}
|
||||
config.Enabled = true
|
||||
config.Mode = "devices"
|
||||
config.DeviceIDs = selected
|
||||
}
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func readModelGPU(service, resolved map[string]interface{}) (*dto.ModelGPUConfig, error) {
|
||||
config := &dto.ModelGPUConfig{Mode: "all", DeviceIDs: []string{}, Capabilities: []string{"gpu"}}
|
||||
devices, _ := resolved["reservations"].([]interface{})
|
||||
if service["gpus"] != nil {
|
||||
return nil, fmt.Errorf("custom GPU declarations require an explicit selection")
|
||||
}
|
||||
for _, item := range devices {
|
||||
device, ok := item.(map[string]interface{})
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("invalid GPU reservation")
|
||||
}
|
||||
driver, _ := device["driver"].(string)
|
||||
capabilities, _ := device["capabilities"].([]interface{})
|
||||
isGPU := driver == "nvidia" || (driver == "" && slices.Contains(capabilities, interface{}("gpu")))
|
||||
if !isGPU {
|
||||
continue
|
||||
}
|
||||
if config.Enabled {
|
||||
return nil, fmt.Errorf("multiple GPU declarations require an explicit selection")
|
||||
}
|
||||
config.Enabled = true
|
||||
ids, _ := device["device_ids"].([]interface{})
|
||||
if len(ids) > 0 {
|
||||
if device["count"] != nil {
|
||||
return nil, fmt.Errorf("GPU count and device_ids cannot be combined")
|
||||
}
|
||||
config.Mode = "devices"
|
||||
for _, id := range ids {
|
||||
config.DeviceIDs = append(config.DeviceIDs, "nvidia:"+fmt.Sprint(id))
|
||||
}
|
||||
} else if count := device["count"]; count != nil && fmt.Sprint(count) != "all" && fmt.Sprint(count) != "-1" {
|
||||
config.Mode = "count"
|
||||
countValue, err := strconv.Atoi(fmt.Sprint(count))
|
||||
config.Count = countValue
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid GPU count: %v", count)
|
||||
}
|
||||
}
|
||||
for _, capability := range capabilities {
|
||||
value, ok := capability.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported GPU capabilities: %v", capabilities)
|
||||
}
|
||||
if !slices.Contains(config.Capabilities, value) {
|
||||
config.Capabilities = append(config.Capabilities, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, key := range []string{"devices", "volumes"} {
|
||||
mappings, _ := resolved[key].([]interface{})
|
||||
for _, item := range mappings {
|
||||
source := modelGPUDeviceSource(item)
|
||||
if strings.HasPrefix(source, "/dev/nvidia") || strings.HasPrefix(source, "nvidia.com/gpu=") {
|
||||
return nil, fmt.Errorf("direct NVIDIA device mappings require an explicit selection")
|
||||
}
|
||||
}
|
||||
}
|
||||
if !config.Enabled && resolved["runtime"] == "nvidia" {
|
||||
return nil, fmt.Errorf("custom NVIDIA runtime configuration requires an explicit selection")
|
||||
}
|
||||
if err := validateModelGPU(config, "nvidia"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func validateModelGPU(config *dto.ModelGPUConfig, imageType string) error {
|
||||
if config.Error != "" {
|
||||
return fmt.Errorf("GPU configuration must be selected explicitly: %s", config.Error)
|
||||
}
|
||||
if !config.Enabled {
|
||||
return nil
|
||||
}
|
||||
patterns := map[string]string{
|
||||
"nvidia": re.NvidiaGPUDeviceIDPattern,
|
||||
"intel": re.IntelGPUDevicePathPattern,
|
||||
"ascend": re.AscendGPUDeviceIDPattern,
|
||||
}
|
||||
pattern, supported := patterns[imageType]
|
||||
if !supported {
|
||||
return fmt.Errorf("unsupported GPU type: %s", imageType)
|
||||
}
|
||||
switch config.Mode {
|
||||
case "all":
|
||||
if config.Count != 0 || len(config.DeviceIDs) != 0 {
|
||||
return fmt.Errorf("all GPUs cannot be combined with count or device IDs")
|
||||
}
|
||||
case "count":
|
||||
if imageType != "nvidia" || config.Count < 1 || len(config.DeviceIDs) != 0 {
|
||||
return fmt.Errorf("GPU count requires a positive number and runtime device allocation")
|
||||
}
|
||||
case "devices", "":
|
||||
if config.Count != 0 || len(config.DeviceIDs) == 0 {
|
||||
return fmt.Errorf("select at least one GPU device")
|
||||
}
|
||||
for _, deviceID := range config.DeviceIDs {
|
||||
id := strings.TrimPrefix(deviceID, imageType+":")
|
||||
if !re.GetRegex(pattern).MatchString(id) {
|
||||
return fmt.Errorf("invalid %s GPU device: %s", imageType, deviceID)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("unsupported GPU selection mode: %s", config.Mode)
|
||||
}
|
||||
|
||||
if imageType != "nvidia" {
|
||||
return nil
|
||||
}
|
||||
if !slices.Contains(config.Capabilities, "gpu") {
|
||||
return fmt.Errorf("NVIDIA capabilities must include gpu")
|
||||
}
|
||||
for _, capability := range config.Capabilities {
|
||||
if capability != "gpu" && capability != "utility" && capability != "compute" {
|
||||
return fmt.Errorf("unsupported NVIDIA capability: %s", capability)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveModelGPUService(service map[string]interface{}, params map[string]interface{}, resolveSelection bool) (map[string]interface{}, error) {
|
||||
fields := map[string]interface{}{}
|
||||
fields["runtime"] = service["runtime"]
|
||||
deploy, _ := service["deploy"].(map[string]interface{})
|
||||
resources, _ := deploy["resources"].(map[string]interface{})
|
||||
reservations, _ := resources["reservations"].(map[string]interface{})
|
||||
fields["reservations"] = reservations["devices"]
|
||||
if devices, ok := reservations["devices"].([]interface{}); ok && !resolveSelection {
|
||||
declarations := make([]interface{}, 0, len(devices))
|
||||
for _, item := range devices {
|
||||
device, _ := item.(map[string]interface{})
|
||||
declarations = append(declarations, map[string]interface{}{"driver": device["driver"], "capabilities": device["capabilities"]})
|
||||
}
|
||||
fields["reservations"] = declarations
|
||||
}
|
||||
resolved, err := interpolation.Interpolate(fields, interpolation.Options{
|
||||
Substitute: func(value string, _ template.Mapping) (string, error) { return resolveModelGPUValue(value, params) },
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, key := range []string{"devices", "volumes"} {
|
||||
mappings, _ := service[key].([]interface{})
|
||||
sources := make([]interface{}, 0, len(mappings))
|
||||
for _, item := range mappings {
|
||||
source, err := template.SubstituteWithOptions(modelGPUDeviceSource(item), func(name string) (string, bool) {
|
||||
value, ok := params[name]
|
||||
if !ok || value == nil {
|
||||
return "", false
|
||||
}
|
||||
return fmt.Sprint(value), true
|
||||
}, template.WithoutLogging)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, short := item.(string); short {
|
||||
source = modelGPUDeviceSource(source)
|
||||
}
|
||||
sources = append(sources, map[string]interface{}{"source": source})
|
||||
}
|
||||
resolved[key] = sources
|
||||
}
|
||||
resolved["privileged"] = service["privileged"]
|
||||
if value, ok := service["privileged"].(string); ok {
|
||||
if value, err := resolveModelGPUValue(value, params); err == nil {
|
||||
resolved["privileged"] = value
|
||||
}
|
||||
}
|
||||
return resolved, nil
|
||||
}
|
||||
|
||||
func modelGPUDeviceSource(item interface{}) string {
|
||||
switch mapping := item.(type) {
|
||||
case string:
|
||||
if strings.HasPrefix(mapping, "nvidia.com/gpu=") {
|
||||
return mapping
|
||||
}
|
||||
depth := 0
|
||||
for index, ch := range mapping {
|
||||
switch ch {
|
||||
case '{':
|
||||
depth++
|
||||
case '}':
|
||||
depth--
|
||||
case ':':
|
||||
if depth == 0 {
|
||||
return mapping[:index]
|
||||
}
|
||||
}
|
||||
}
|
||||
return mapping
|
||||
case map[string]interface{}:
|
||||
source, _ := mapping["source"].(string)
|
||||
return source
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func resolveModelGPUValue(value string, params map[string]interface{}) (string, error) {
|
||||
return template.SubstituteWithOptions(value, func(name string) (string, bool) {
|
||||
value, ok := params[name]
|
||||
if !ok || value == nil {
|
||||
return "", false
|
||||
}
|
||||
return fmt.Sprint(value), true
|
||||
}, template.WithoutLogging, template.WithReplacementFunction(func(value string, mapping template.Mapping, config *template.Config) (string, error) {
|
||||
resolved, applied, err := template.DefaultReplacementAppliedFunc(value, mapping, config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !applied {
|
||||
return "", fmt.Errorf("unresolved GPU variable: %s", value)
|
||||
}
|
||||
return resolved, nil
|
||||
}))
|
||||
}
|
||||
|
||||
func loadModelAscendDeviceIDs() []string {
|
||||
ids := []string{}
|
||||
for _, device := range LoadModelGPUDevices().Devices {
|
||||
if device.Type == "ascend" && device.Available {
|
||||
ids = append(ids, strings.TrimPrefix(device.ID, "ascend:"))
|
||||
}
|
||||
}
|
||||
slices.SortFunc(ids, func(a, b string) int {
|
||||
left, _ := strconv.Atoi(a)
|
||||
right, _ := strconv.Atoi(b)
|
||||
return left - right
|
||||
})
|
||||
return ids
|
||||
}
|
||||
@@ -57,12 +57,31 @@ func (t TensorRTLLMService) Page(req request.TensorRTLLMSearch) response.TensorR
|
||||
serverDTO.Dir = path.Join(global.Dir.TensorRTLLMDir, item.Name)
|
||||
serverDTO.Image = envs["IMAGE"]
|
||||
serverDTO.Command = getCommand(item.Env)
|
||||
var project map[string]interface{}
|
||||
if yaml.Unmarshal([]byte(item.DockerCompose), &project) == nil {
|
||||
services, _ := project["services"].(map[string]interface{})
|
||||
if service, ok := services[item.Name].(map[string]interface{}); ok {
|
||||
params := make(map[string]interface{}, len(envs))
|
||||
for key, value := range envs {
|
||||
params[key] = value
|
||||
}
|
||||
serverDTO.GPU = ReadModelGPU(service, params, "nvidia")
|
||||
}
|
||||
}
|
||||
|
||||
serverDTO.ExposedPorts, _ = loadComposeExposedPortsFromEnv(envs, "0.0.0.0", false)
|
||||
|
||||
composeByte, err := files.NewFileOp().GetContent(path.Join(global.Dir.TensorRTLLMDir, item.Name, "docker-compose.yml"))
|
||||
if err == nil {
|
||||
serverDTO.Environments, _ = getDockerComposeEnvironments(composeByte)
|
||||
environments := make([]request.Environment, 0, len(serverDTO.Environments))
|
||||
for _, item := range serverDTO.Environments {
|
||||
if item.Key == "MODEL_PATH" || (serverDTO.GPU != nil && (item.Key == "NVIDIA_VISIBLE_DEVICES" || item.Key == "NVIDIA_DRIVER_CAPABILITIES")) {
|
||||
continue
|
||||
}
|
||||
environments = append(environments, item)
|
||||
}
|
||||
serverDTO.Environments = environments
|
||||
}
|
||||
volumes, err := getDockerComposeVolumes(composeByte)
|
||||
if err == nil {
|
||||
@@ -133,6 +152,9 @@ func handleLLMParams(llm *model.TensorRTLLM, create request.TensorRTLLMCreate) e
|
||||
var environments []interface{}
|
||||
environments = append(environments, fmt.Sprintf("MODEL_PATH=%s", create.ModelDir))
|
||||
for _, e := range create.Environments {
|
||||
if e.Key == "MODEL_PATH" {
|
||||
continue
|
||||
}
|
||||
environments = append(environments, fmt.Sprintf("%s=%s", e.Key, e.Value))
|
||||
}
|
||||
serviceValue["environment"] = environments
|
||||
@@ -148,6 +170,18 @@ func handleLLMParams(llm *model.TensorRTLLM, create request.TensorRTLLMCreate) e
|
||||
volumes = append(volumes, formatComposeVolume(volume.Source, volume.Target, volume.Mode))
|
||||
}
|
||||
serviceValue["volumes"] = volumes
|
||||
gpuParams := map[string]interface{}{}
|
||||
existingEnv, err := gotenv.Unmarshal(llm.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for key, value := range existingEnv {
|
||||
gpuParams[key] = value
|
||||
}
|
||||
gpuParams["MODEL_PATH"] = create.ModelDir
|
||||
if err := ApplyModelGPU(serviceValue, create.GPU, gpuParams, "nvidia"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
services[llm.Name] = serviceValue
|
||||
composeByte, err := yaml.Marshal(composeMap)
|
||||
|
||||
@@ -26,6 +26,7 @@ func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
aiToolsRouter.POST("/domain/update", baseApi.UpdateBindDomain)
|
||||
|
||||
aiToolsRouter.GET("/gpu/load", baseApi.LoadGpuInfo)
|
||||
aiToolsRouter.GET("/model/gpu/devices", baseApi.LoadModelGPUDevices)
|
||||
aiToolsRouter.POST("/gpu/search", baseApi.LoadGPUMonitor)
|
||||
aiToolsRouter.GET("/gpu/options", baseApi.GetCPUOptions)
|
||||
aiToolsRouter.POST("/vllm/monitor/search", baseApi.LoadVLLMMonitor)
|
||||
|
||||
@@ -61,6 +61,13 @@ const (
|
||||
AcceleratorMetricValuePattern = `^\s*([+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+))\s*(.*?)\s*$`
|
||||
AscendVersionPattern = `(?i)\bVersion:\s*([^\s|]+)`
|
||||
AscendMemoryPattern = `([0-9]+(?:\.[0-9]+)?)\s*/\s*([0-9]+(?:\.[0-9]+)?)`
|
||||
NvidiaGPUUUIDPattern = `^GPU-[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$`
|
||||
NvidiaGPUDeviceIDPattern = `^([0-9]+|GPU-[a-zA-Z0-9-]+|MIG-[a-zA-Z0-9/-]+)$`
|
||||
IntelGPURenderNamePattern = `^renderD[0-9]+$`
|
||||
IntelGPUDevicePathPattern = `^/dev/dri/renderD[0-9]+$`
|
||||
AscendGPUDeviceNamePattern = `^davinci[0-9]+$`
|
||||
AscendGPUDevicePathPattern = `^/dev/davinci[0-9]+$`
|
||||
AscendGPUDeviceIDPattern = `^[0-9]+$`
|
||||
)
|
||||
|
||||
var regexMap = make(map[string]*regexp.Regexp)
|
||||
@@ -122,6 +129,13 @@ func Init() {
|
||||
AcceleratorMetricValuePattern,
|
||||
AscendVersionPattern,
|
||||
AscendMemoryPattern,
|
||||
NvidiaGPUUUIDPattern,
|
||||
NvidiaGPUDeviceIDPattern,
|
||||
IntelGPURenderNamePattern,
|
||||
IntelGPUDevicePathPattern,
|
||||
AscendGPUDeviceNamePattern,
|
||||
AscendGPUDevicePathPattern,
|
||||
AscendGPUDeviceIDPattern,
|
||||
}
|
||||
|
||||
for _, pattern := range patterns {
|
||||
|
||||
@@ -1,6 +1,21 @@
|
||||
import { ReqPage } from '.';
|
||||
|
||||
export namespace AI {
|
||||
export interface ModelGPUDevice {
|
||||
id: string;
|
||||
name: string;
|
||||
type: string;
|
||||
uuid?: string;
|
||||
available: boolean;
|
||||
}
|
||||
export interface ModelGPUConfig {
|
||||
mode: 'all' | 'count' | 'devices';
|
||||
count?: number;
|
||||
error?: string;
|
||||
enabled: boolean;
|
||||
deviceIDs: string[];
|
||||
capabilities: string[];
|
||||
}
|
||||
export interface ModelAccountSync {
|
||||
id: number;
|
||||
modelAccountBaseURLType: 'container' | 'localhost' | 'systemIP' | 'custom';
|
||||
@@ -392,6 +407,7 @@ export namespace AI {
|
||||
}
|
||||
|
||||
export interface TensorRTLLM {
|
||||
gpu?: ModelGPUConfig;
|
||||
id?: number;
|
||||
name: string;
|
||||
containerName: string;
|
||||
|
||||
@@ -137,6 +137,7 @@ export namespace App {
|
||||
}
|
||||
|
||||
export interface AppInstall {
|
||||
gpu?: AI.ModelGPUConfig;
|
||||
syncModelAccount?: boolean;
|
||||
modelAccountBaseURLType?: string;
|
||||
modelAccountBaseURL?: string;
|
||||
@@ -310,6 +311,7 @@ export namespace App {
|
||||
}
|
||||
|
||||
export interface AppConfig {
|
||||
gpu?: AI.ModelGPUConfig;
|
||||
appKey: string;
|
||||
name: string;
|
||||
httpPort: number;
|
||||
|
||||
@@ -43,6 +43,10 @@ export const updateBindDomain = (req: AI.BindDomain) => {
|
||||
return http.post(`/ai/domain/update`, req);
|
||||
};
|
||||
|
||||
export const loadModelGPUDevices = () => {
|
||||
return http.get<{ devices: AI.ModelGPUDevice[]; warnings: string[] }>('/ai/model/gpu/devices');
|
||||
};
|
||||
|
||||
export const loadGPUInfo = () => {
|
||||
return http.get<AI.Info>(`/ai/gpu/load`);
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-form-item prop="advanced">
|
||||
<el-form-item v-if="showToggle" prop="advanced">
|
||||
<el-checkbox v-model="form.advanced" :label="$t('app.advanced')" size="large" />
|
||||
</el-form-item>
|
||||
<div v-if="form.advanced">
|
||||
@@ -83,6 +83,7 @@ const props = withDefaults(
|
||||
editCompose: boolean;
|
||||
dockerCompose: string;
|
||||
};
|
||||
showToggle?: boolean;
|
||||
showAllowPort?: boolean;
|
||||
showSpecifyIP?: boolean;
|
||||
showContainerName?: boolean;
|
||||
@@ -93,6 +94,7 @@ const props = withDefaults(
|
||||
autoLoadLimit?: boolean;
|
||||
}>(),
|
||||
{
|
||||
showToggle: true,
|
||||
showAllowPort: true,
|
||||
showSpecifyIP: true,
|
||||
showContainerName: true,
|
||||
|
||||
@@ -1037,6 +1037,23 @@ const message = {
|
||||
validateImageAvailabilityHelper: 'Generates one minimal image before saving and may incur charges',
|
||||
},
|
||||
model: {
|
||||
gpuAcceleration: 'GPU acceleration',
|
||||
gpuMode: 'Accelerator selection',
|
||||
gpuAll: 'All accelerators',
|
||||
gpuCount: 'Specify count',
|
||||
gpuSpecific: 'Select devices',
|
||||
gpuAllHelper: 'Use all available accelerators of this type on the current node.',
|
||||
gpuCountHelper:
|
||||
'Let the runtime allocate the requested number of accelerators without binding specific devices.',
|
||||
gpuSpecificHelper: 'Manually select specific accelerators.',
|
||||
gpuCountRequired: 'Enter an integer greater than 0',
|
||||
gpuDevices: 'GPU devices',
|
||||
gpuCapabilitiesHelper:
|
||||
'GPU access is required; enable monitoring and management (utility) and computing support (compute) as needed.',
|
||||
gpuSelectRequired: 'Select at least one available GPU device.',
|
||||
gpuUnsupported: 'Unsupported',
|
||||
gpuNotDetected: 'No supported accelerators detected',
|
||||
gpuMissing: 'Unavailable',
|
||||
modelCategory: 'Model category',
|
||||
modelName: 'Model name',
|
||||
modelArchitecture: 'Model type',
|
||||
@@ -1075,7 +1092,7 @@ const message = {
|
||||
container_conn_helper: 'Use this address for inter-container access or connection',
|
||||
from_remote: 'This model was not downloaded via 1Panel, no related pull logs.',
|
||||
no_logs: 'The pull logs for this model have been deleted and cannot be viewed.',
|
||||
vllmVersionHelper: 'For FusionXpark GB 10 servers, please select the -cu130 version.',
|
||||
vllmVersionHelper: 'For FusionXpark GB10 servers, please select the -cu130 version.',
|
||||
vllmDeployProfile: 'Deployment model',
|
||||
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
@@ -1734,8 +1751,8 @@ const message = {
|
||||
prohibitedHelper: 'Prohibited: Concurrent process execution is not allowed',
|
||||
migModeHelper: 'Used to create MIG instances, implementing physical GPU isolation at the user layer.',
|
||||
migModeNA: 'Not Supported',
|
||||
current: 'Real-time Monitoring',
|
||||
history: 'Historical Records',
|
||||
current: 'Real-time Status',
|
||||
history: 'Monitoring Data',
|
||||
notSupport: 'The current version or driver does not support displaying this parameter.',
|
||||
},
|
||||
mcp: {
|
||||
@@ -2043,8 +2060,6 @@ const message = {
|
||||
retry: 'Retry selected points',
|
||||
environment: 'Environment',
|
||||
plan: 'Frozen configuration',
|
||||
clientHelp:
|
||||
'The client container runs on the main node and sends requests to an existing service. It does not start inference or require a GPU.',
|
||||
retryHelp: 'Only selected abnormal points are retried, with the same parameters and new seeds.',
|
||||
secretHelp: 'API keys and headers are excluded from copies. Enter them again if needed.',
|
||||
resultHelp:
|
||||
|
||||
@@ -1039,6 +1039,23 @@ const message = {
|
||||
validateImageAvailabilityHelper: 'Genera una imagen mínima antes de guardar y puede generar costes',
|
||||
},
|
||||
model: {
|
||||
gpuAcceleration: 'Aceleración GPU',
|
||||
gpuMode: 'Selección de aceleradores',
|
||||
gpuAll: 'Todos los aceleradores',
|
||||
gpuCount: 'Especificar cantidad',
|
||||
gpuSpecific: 'Seleccionar dispositivos',
|
||||
gpuAllHelper: 'Usar todos los aceleradores disponibles de este tipo en el nodo actual.',
|
||||
gpuCountHelper:
|
||||
'El entorno de ejecución asigna la cantidad solicitada sin vincular dispositivos específicos.',
|
||||
gpuSpecificHelper: 'Seleccione manualmente los aceleradores específicos.',
|
||||
gpuCountRequired: 'Introduzca un entero mayor que 0',
|
||||
gpuDevices: 'Dispositivos GPU',
|
||||
gpuCapabilitiesHelper:
|
||||
'El acceso a la GPU es obligatorio; active la supervisión y gestión (utility) y el cálculo (compute) según sea necesario.',
|
||||
gpuSelectRequired: 'Seleccione al menos un dispositivo GPU disponible.',
|
||||
gpuUnsupported: 'No compatible',
|
||||
gpuNotDetected: 'No se detectaron aceleradores compatibles',
|
||||
gpuMissing: 'No disponible',
|
||||
modelCategory: 'Categoría del modelo',
|
||||
modelName: 'Nombre del modelo',
|
||||
modelArchitecture: 'Tipo de modelo',
|
||||
@@ -1077,7 +1094,7 @@ const message = {
|
||||
container_conn_helper: 'Utilice esta dirección para el acceso o conexión entre contenedores',
|
||||
from_remote: 'Este modelo no fue descargado vía 1Panel, no hay registros de descarga relacionados.',
|
||||
no_logs: 'Los registros de descarga de este modelo han sido eliminados y no se pueden consultar.',
|
||||
vllmVersionHelper: 'Para servidores FusionXpark GB 10, seleccione la versión -cu130.',
|
||||
vllmVersionHelper: 'Para servidores FusionXpark GB10, seleccione la versión -cu130.',
|
||||
vllmDeployProfile: 'Modelo de despliegue',
|
||||
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
@@ -1745,8 +1762,8 @@ const message = {
|
||||
migModeHelper:
|
||||
'Se utiliza para crear instancias MIG, implementando aislamiento físico de GPU en la capa de usuario.',
|
||||
migModeNA: 'No Compatible',
|
||||
current: 'Monitoreo en Tiempo Real',
|
||||
history: 'Registros Históricos',
|
||||
current: 'Estado en tiempo real',
|
||||
history: 'Datos de monitoreo',
|
||||
notSupport: 'La versión actual o el controlador no admiten mostrar este parámetro.',
|
||||
processCount: 'Procesos',
|
||||
},
|
||||
@@ -2061,8 +2078,6 @@ const message = {
|
||||
retry: 'Repetir puntos seleccionados',
|
||||
environment: 'Entorno',
|
||||
plan: 'Configuración fijada',
|
||||
clientHelp:
|
||||
'El contenedor cliente se ejecuta en el nodo principal y envía solicitudes a un servicio existente. No inicia inferencia ni requiere GPU.',
|
||||
retryHelp: 'Solo se repiten los puntos anómalos seleccionados, con los mismos parámetros y nuevas semillas.',
|
||||
secretHelp: 'Las copias no incluyen claves API ni cabeceras. Vuelva a introducirlas si es necesario.',
|
||||
resultHelp:
|
||||
|
||||
@@ -1022,6 +1022,22 @@ const message = {
|
||||
validateImageAvailabilityHelper: 'پیش از ذخیره یک تصویر حداقلی تولید میشود و ممکن است هزینه داشته باشد',
|
||||
},
|
||||
model: {
|
||||
gpuAcceleration: 'شتابدهی GPU',
|
||||
gpuMode: 'انتخاب شتابدهنده',
|
||||
gpuAll: 'همه شتابدهندهها',
|
||||
gpuCount: 'تعیین تعداد',
|
||||
gpuSpecific: 'انتخاب دستگاهها',
|
||||
gpuAllHelper: 'از همه شتابدهندههای موجود از این نوع در گره فعلی استفاده شود.',
|
||||
gpuCountHelper: 'محیط اجرا تعداد درخواستی شتابدهندهها را بدون اتصال به دستگاههای مشخص تخصیص میدهد.',
|
||||
gpuSpecificHelper: 'شتابدهندههای مشخص را بهصورت دستی انتخاب کنید.',
|
||||
gpuCountRequired: 'یک عدد صحیح بزرگتر از صفر وارد کنید',
|
||||
gpuDevices: 'دستگاههای GPU',
|
||||
gpuCapabilitiesHelper:
|
||||
'دسترسی به GPU الزامی است؛ نظارت و مدیریت (utility) و پشتیبانی محاسباتی (compute) را در صورت نیاز فعال کنید.',
|
||||
gpuSelectRequired: 'حداقل یک دستگاه GPU موجود را انتخاب کنید.',
|
||||
gpuUnsupported: 'پشتیبانی نمیشود',
|
||||
gpuNotDetected: 'هیچ شتابدهنده پشتیبانیشدهای شناسایی نشد',
|
||||
gpuMissing: 'در دسترس نیست',
|
||||
modelCategory: 'دستهبندی مدل',
|
||||
modelName: 'نام مدل',
|
||||
modelArchitecture: 'نوع مدل',
|
||||
@@ -1060,7 +1076,7 @@ const message = {
|
||||
container_conn_helper: 'از این آدرس برای دسترسی بین کانتینرها یا اتصال استفاده کنید',
|
||||
from_remote: 'این مدل از طریق 1Panel دانلود نشده است، لاگ مربوط به دریافت وجود ندارد.',
|
||||
no_logs: 'لاگ دریافت این مدل حذف شده است و قابل مشاهده نیست.',
|
||||
vllmVersionHelper: 'برای سرورهای FusionXpark GB 10، لطفاً نسخه -cu130 را انتخاب کنید.',
|
||||
vllmVersionHelper: 'برای سرورهای FusionXpark GB10، لطفاً نسخه -cu130 را انتخاب کنید.',
|
||||
vllmDeployProfile: 'مدل استقرار',
|
||||
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
@@ -1717,8 +1733,8 @@ const message = {
|
||||
migModeHelper:
|
||||
'برای ایجاد نمونههای MIG استفاده میشود و ایزولهسازی فیزیکی GPU را در لایه کاربر پیادهسازی میکند.',
|
||||
migModeNA: 'پشتیبانی نمیشود',
|
||||
current: 'نظارت بلادرنگ',
|
||||
history: 'سوابق تاریخی',
|
||||
current: 'وضعیت بلادرنگ',
|
||||
history: 'دادههای نظارتی',
|
||||
notSupport: 'نسخه فعلی یا درایور از نمایش این پارامتر پشتیبانی نمیکند.',
|
||||
},
|
||||
mcp: {
|
||||
@@ -2021,8 +2037,6 @@ const message = {
|
||||
retry: 'تکرار نقاط انتخابشده',
|
||||
environment: 'محیط اجرا',
|
||||
plan: 'پیکربندی ثابت',
|
||||
clientHelp:
|
||||
'کانتینر کلاینت در گره اصلی به سرویس موجود درخواست میفرستد. سرویس استنتاج راهاندازی نمیکند و به GPU نیاز ندارد.',
|
||||
retryHelp: 'فقط نقاط غیرعادی انتخابشده با همان پارامترها و بذرهای جدید تکرار میشوند.',
|
||||
secretHelp: 'کپیها شامل کلید API و سرآیندها نیستند. در صورت نیاز دوباره وارد کنید.',
|
||||
resultHelp:
|
||||
|
||||
@@ -1026,6 +1026,22 @@ const message = {
|
||||
validateImageAvailabilityHelper: '保存前に最小画像を1枚生成するため、料金が発生する場合があります',
|
||||
},
|
||||
model: {
|
||||
gpuAcceleration: 'GPU アクセラレーション',
|
||||
gpuMode: 'アクセラレーターの選択',
|
||||
gpuAll: 'すべてのアクセラレーター',
|
||||
gpuCount: '台数を指定',
|
||||
gpuSpecific: 'デバイスを指定',
|
||||
gpuAllHelper: '現在のノードで利用可能な同じ種類のアクセラレーターをすべて使用します。',
|
||||
gpuCountHelper: '特定のデバイスに固定せず、ランタイムが指定台数を割り当てます。',
|
||||
gpuSpecificHelper: '使用するアクセラレーターを手動で選択します。',
|
||||
gpuCountRequired: '0 より大きい整数を入力してください',
|
||||
gpuDevices: 'GPU デバイス',
|
||||
gpuCapabilitiesHelper:
|
||||
'GPU アクセスは必須です。必要に応じて監視と管理(utility)や計算サポート(compute)を有効にしてください。',
|
||||
gpuSelectRequired: '使用可能な GPU を少なくとも 1 つ選択してください。',
|
||||
gpuUnsupported: '非対応',
|
||||
gpuNotDetected: '対応するアクセラレーターが検出されませんでした',
|
||||
gpuMissing: '利用不可',
|
||||
modelCategory: 'モデルカテゴリ',
|
||||
modelName: 'モデル名',
|
||||
modelArchitecture: 'モデルの種類',
|
||||
@@ -1064,7 +1080,7 @@ const message = {
|
||||
container_conn_helper: 'コンテナ間のアクセスまたは接続にこのアドレスを使用',
|
||||
from_remote: 'このモデルは1Panelを介してダウンロードされておらず、関連するプルログはありません。',
|
||||
no_logs: 'このモデルのプルログは削除されており、関連するログを表示できません。',
|
||||
vllmVersionHelper: 'FusionXpark GB 10 サーバーでは -cu130 バージョンを選択してください。',
|
||||
vllmVersionHelper: 'FusionXpark GB10 サーバーでは -cu130 バージョンを選択してください。',
|
||||
vllmDeployProfile: 'デプロイモデル',
|
||||
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
@@ -1717,8 +1733,8 @@ const message = {
|
||||
prohibitedHelper: '禁止: プロセスの同時実行は許可されません',
|
||||
migModeHelper: 'MIGインスタンスを作成するために使用され、ユーザーレイヤーでGPUの物理的隔離を実装します。',
|
||||
migModeNA: 'サポートされていません',
|
||||
current: 'リアルタイム監視',
|
||||
history: '履歴記録',
|
||||
current: 'リアルタイム状態',
|
||||
history: '監視データ',
|
||||
notSupport: '現在のバージョンまたはドライバーはこのパラメータの表示をサポートしていません。',
|
||||
processCount: 'プロセス数',
|
||||
},
|
||||
@@ -2026,8 +2042,6 @@ const message = {
|
||||
retry: '選択した測定点を再試行',
|
||||
environment: '実行環境',
|
||||
plan: '固定された設定',
|
||||
clientHelp:
|
||||
'クライアントコンテナはメインノードで既存サービスに要求を送信します。推論サービスの起動や GPU の割り当ては不要です。',
|
||||
retryHelp: '選択した異常な測定点のみ、同じパラメータと新しいシードで再試行します。',
|
||||
secretHelp: 'コピーに API キーとヘッダーは含まれません。必要に応じて再入力してください。',
|
||||
resultHelp:
|
||||
|
||||
@@ -1016,6 +1016,22 @@ const message = {
|
||||
validateImageAvailabilityHelper: '저장 전에 최소 이미지 한 장을 생성하며 비용이 발생할 수 있습니다',
|
||||
},
|
||||
model: {
|
||||
gpuAcceleration: 'GPU 가속',
|
||||
gpuMode: '가속기 선택',
|
||||
gpuAll: '모든 가속기',
|
||||
gpuCount: '수량 지정',
|
||||
gpuSpecific: '장치 선택',
|
||||
gpuAllHelper: '현재 노드에서 사용 가능한 같은 유형의 가속기를 모두 사용합니다.',
|
||||
gpuCountHelper: '특정 장치에 고정하지 않고 런타임이 지정한 수량의 가속기를 할당합니다.',
|
||||
gpuSpecificHelper: '사용할 가속기를 직접 선택합니다.',
|
||||
gpuCountRequired: '0보다 큰 정수를 입력하세요',
|
||||
gpuDevices: 'GPU 장치',
|
||||
gpuCapabilitiesHelper:
|
||||
'GPU 접근은 필수이며, 필요에 따라 모니터링 및 관리(utility)와 연산 지원(compute)을 활성화할 수 있습니다.',
|
||||
gpuSelectRequired: '사용 가능한 GPU 장치를 하나 이상 선택하세요.',
|
||||
gpuUnsupported: '지원 안 됨',
|
||||
gpuNotDetected: '지원되는 가속기가 감지되지 않았습니다',
|
||||
gpuMissing: '사용 불가',
|
||||
modelCategory: '모델 카테고리',
|
||||
modelName: '모델 이름',
|
||||
modelArchitecture: '모델 유형',
|
||||
@@ -1054,7 +1070,7 @@ const message = {
|
||||
container_conn_helper: '컨테이너 간 접근 또는 연결에 이 주소를 사용',
|
||||
from_remote: '이 모델은 1Panel을 통해 다운로드되지 않았으며 관련 풀 로그가 없습니다.',
|
||||
no_logs: '이 모델의 풀 로그가 삭제되어 관련 로그를 볼 수 없습니다.',
|
||||
vllmVersionHelper: 'FusionXpark GB 10 서버는 -cu130 버전을 선택하세요.',
|
||||
vllmVersionHelper: 'FusionXpark GB10 서버는 -cu130 버전을 선택하세요.',
|
||||
vllmDeployProfile: '배포 모델',
|
||||
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
@@ -1702,8 +1718,8 @@ const message = {
|
||||
prohibitedHelper: '금지됨: 프로세스 동시 실행이 허용되지 않음',
|
||||
migModeHelper: 'MIG 인스턴스를 생성하는 데 사용되며 사용자 레이어에서 GPU의 물리적 격리를 구현합니다.',
|
||||
migModeNA: '지원되지 않음',
|
||||
current: '실시간 모니터링',
|
||||
history: '기록',
|
||||
current: '실시간 상태',
|
||||
history: '모니터링 데이터',
|
||||
notSupport: '현재 버전 또는 드라이버는 이 매개변수 표시를 지원하지 않습니다.',
|
||||
processCount: '프로세스 수',
|
||||
},
|
||||
@@ -2002,8 +2018,6 @@ const message = {
|
||||
retry: '선택한 측정점 재시도',
|
||||
environment: '실행 환경',
|
||||
plan: '고정 설정',
|
||||
clientHelp:
|
||||
'클라이언트 컨테이너는 주 노드에서 기존 서비스에 요청을 보냅니다. 추론 서비스를 시작하거나 GPU를 할당하지 않습니다.',
|
||||
retryHelp: '선택한 비정상 측정점만 같은 매개변수와 새 시드로 재시도합니다.',
|
||||
secretHelp: '복사본에는 API 키와 헤더가 없습니다. 필요한 경우 다시 입력하세요.',
|
||||
resultHelp:
|
||||
|
||||
@@ -1013,6 +1013,22 @@ const message = {
|
||||
validateImageAvailabilityHelper: 'ສ້າງຮູບຂະໜາດນ້ອຍໜຶ່ງຮູບກ່ອນບັນທຶກ ແລະ ອາດມີຄ່າໃຊ້ຈ່າຍ',
|
||||
},
|
||||
model: {
|
||||
gpuAcceleration: 'ການເລັ່ງ GPU',
|
||||
gpuMode: 'ເລືອກຕົວເລັ່ງ',
|
||||
gpuAll: 'ຕົວເລັ່ງທັງໝົດ',
|
||||
gpuCount: 'ລະບຸຈຳນວນ',
|
||||
gpuSpecific: 'ເລືອກອຸປະກອນ',
|
||||
gpuAllHelper: 'ໃຊ້ຕົວເລັ່ງປະເພດດຽວກັນທັງໝົດທີ່ມີໃນໂນດປັດຈຸບັນ.',
|
||||
gpuCountHelper: 'ລະບົບຈະຈັດສັນຕົວເລັ່ງຕາມຈຳນວນທີ່ລະບຸ ໂດຍບໍ່ຜູກກັບອຸປະກອນສະເພາະ.',
|
||||
gpuSpecificHelper: 'ເລືອກຕົວເລັ່ງທີ່ຕ້ອງການດ້ວຍຕົນເອງ.',
|
||||
gpuCountRequired: 'ກະລຸນາປ້ອນຈຳນວນເຕັມທີ່ຫຼາຍກວ່າ 0',
|
||||
gpuDevices: 'ອຸປະກອນ GPU',
|
||||
gpuCapabilitiesHelper:
|
||||
'ຕ້ອງເປີດການເຂົ້າເຖິງ GPU ແລະ ສາມາດເປີດການຕິດຕາມແລະຈັດການ (utility) ແລະ ການຄຳນວນ (compute) ໄດ້ຕາມຕ້ອງການ.',
|
||||
gpuSelectRequired: 'ກະລຸນາເລືອກ GPU ທີ່ພ້ອມໃຊ້ຢ່າງໜ້ອຍໜຶ່ງອຸປະກອນ',
|
||||
gpuUnsupported: 'ບໍ່ຮອງຮັບ',
|
||||
gpuNotDetected: 'ບໍ່ພົບຕົວເລັ່ງທີ່ຮອງຮັບ',
|
||||
gpuMissing: 'ບໍ່ພ້ອມໃຊ້',
|
||||
modelCategory: 'ໝວດໝູ່ໂມເດວ',
|
||||
modelName: 'ຊື່ໂມເດວ',
|
||||
modelArchitecture: 'ປະເພດໂມເດວ',
|
||||
@@ -1051,7 +1067,7 @@ const message = {
|
||||
container_conn_helper: 'ໃຊ້ທີ່ຢູ່ນີ້ສຳລັບການເຂົ້າເຖິງ ຫຼື ເຊື່ອມຕໍ່ລະຫວ່າງຄອນເທນເນີ',
|
||||
from_remote: 'ໂມເດວນີ້ບໍ່ໄດ້ດາວໂຫຼດຜ່ານ 1Panel, ບໍ່ມີລັອກການດຶງຂໍ້ມູນທີ່ກ່ຽວຂ້ອງ.',
|
||||
no_logs: 'ລັອກການດຶງຂໍ້ມູນຂອງໂມເດວນີ້ຖືກລຶບແລ້ວ ແລະ ບໍ່ສາມາດເບິ່ງໄດ້.',
|
||||
vllmVersionHelper: 'ສຳລັບເຊີເວີ FusionXpark GB 10, ກະລຸນາເລືອກເວີຊັນ -cu130.',
|
||||
vllmVersionHelper: 'ສຳລັບເຊີເວີ FusionXpark GB10, ກະລຸນາເລືອກເວີຊັນ -cu130.',
|
||||
vllmDeployProfile: 'ໂມເດວສຳລັບຕິດຕັ້ງ',
|
||||
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
@@ -1694,8 +1710,8 @@ const message = {
|
||||
prohibitedHelper: 'ຫ້າມ: ບໍ່ອະນຸຍາດໃຫ້ລັນໂປຣເຊສພ້ອມກັນ',
|
||||
migModeHelper: 'ໃຊ້ສຳລັບສ້າງ MIG instances ເພື່ອແຍກສ່ວນ GPU ທາງກາຍະພາບ.',
|
||||
migModeNA: 'ບໍ່ຮອງຮັບ',
|
||||
current: 'ຕິດຕາມແບບ Real-time',
|
||||
history: 'ບັນທຶກຍ້ອນຫຼັງ',
|
||||
current: 'ສະຖານະແບບທັນທີ',
|
||||
history: 'ຂໍ້ມູນການຕິດຕາມ',
|
||||
notSupport: 'ເວີຊັນ ຫຼື driver ປັດຈຸບັນບໍ່ຮອງຮັບການສະແດງຄ່ານີ້.',
|
||||
},
|
||||
mcp: {
|
||||
@@ -1991,8 +2007,6 @@ const message = {
|
||||
retry: 'ທົດສອບຈຸດທີ່ເລືອກຄືນ',
|
||||
environment: 'ສະພາບແວດລ້ອມ',
|
||||
plan: 'ການຕັ້ງຄ່າຄົງທີ່',
|
||||
clientHelp:
|
||||
'ຄອນເທນເນີລູກຂ່າຍເຮັດວຽກຢູ່ໂນດຫຼັກ ແລະ ສົ່ງຄຳຮ້ອງຂໍໄປບໍລິການທີ່ມີແລ້ວ. ບໍ່ເລີ່ມບໍລິການອະນຸມານ ແລະ ບໍ່ຕ້ອງການ GPU.',
|
||||
retryHelp: 'ທົດສອບຄືນສະເພາະຈຸດຜິດປົກກະຕິທີ່ເລືອກ ດ້ວຍພາລາມິເຕີເດີມ ແລະ ຄ່າສຸ່ມໃໝ່.',
|
||||
secretHelp: 'ສຳເນົາບໍ່ມີລະຫັດ API ແລະ ສ່ວນຫົວ. ກະລຸນາປ້ອນໃໝ່ຖ້າຈຳເປັນ.',
|
||||
resultHelp:
|
||||
|
||||
@@ -1048,6 +1048,22 @@ const message = {
|
||||
validateImageAvailabilityHelper: 'Menjana satu imej minimum sebelum menyimpan dan mungkin dikenakan caj',
|
||||
},
|
||||
model: {
|
||||
gpuAcceleration: 'Pecutan GPU',
|
||||
gpuMode: 'Pemilihan pemecut',
|
||||
gpuAll: 'Semua pemecut',
|
||||
gpuCount: 'Nyatakan bilangan',
|
||||
gpuSpecific: 'Pilih peranti',
|
||||
gpuAllHelper: 'Gunakan semua pemecut jenis ini yang tersedia pada nod semasa.',
|
||||
gpuCountHelper: 'Masa jalan memperuntukkan bilangan pemecut yang diminta tanpa mengikat peranti tertentu.',
|
||||
gpuSpecificHelper: 'Pilih pemecut tertentu secara manual.',
|
||||
gpuCountRequired: 'Masukkan integer yang lebih besar daripada 0',
|
||||
gpuDevices: 'Peranti GPU',
|
||||
gpuCapabilitiesHelper:
|
||||
'Akses GPU diperlukan; dayakan pemantauan dan pengurusan (utility) serta sokongan pengiraan (compute) mengikut keperluan.',
|
||||
gpuSelectRequired: 'Pilih sekurang-kurangnya satu peranti GPU yang tersedia.',
|
||||
gpuUnsupported: 'Tidak disokong',
|
||||
gpuNotDetected: 'Tiada pemecut yang disokong dikesan',
|
||||
gpuMissing: 'Tidak tersedia',
|
||||
modelCategory: 'Kategori model',
|
||||
modelName: 'Nama model',
|
||||
modelArchitecture: 'Jenis model',
|
||||
@@ -1086,7 +1102,7 @@ const message = {
|
||||
container_conn_helper: 'Gunakan alamat ini untuk akses atau sambungan antara kontena',
|
||||
from_remote: 'Model ini tidak dimuat turun melalui 1Panel; tiada log muat turun berkaitan.',
|
||||
no_logs: 'Log muat turun model ini telah dipadam dan tidak boleh dilihat.',
|
||||
vllmVersionHelper: 'Untuk pelayan FusionXpark GB 10, sila pilih versi -cu130.',
|
||||
vllmVersionHelper: 'Untuk pelayan FusionXpark GB10, sila pilih versi -cu130.',
|
||||
vllmDeployProfile: 'Model penggunaan',
|
||||
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
@@ -1747,8 +1763,8 @@ const message = {
|
||||
migModeHelper:
|
||||
'Digunakan untuk mencipta contoh MIG, melaksanakan pengasingan fizikal GPU pada lapisan pengguna.',
|
||||
migModeNA: 'Tidak Disokong',
|
||||
current: 'Pemantauan Masa Nyata',
|
||||
history: 'Rekod Sejarah',
|
||||
current: 'Status Masa Nyata',
|
||||
history: 'Data Pemantauan',
|
||||
notSupport: 'Versi atau pemacu semasa tidak menyokong paparan parameter ini.',
|
||||
processCount: 'Bilangan proses',
|
||||
},
|
||||
@@ -2058,8 +2074,6 @@ const message = {
|
||||
retry: 'Ulang titik terpilih',
|
||||
environment: 'Persekitaran',
|
||||
plan: 'Konfigurasi dibekukan',
|
||||
clientHelp:
|
||||
'Kontena klien berjalan pada nod utama dan menghantar permintaan kepada perkhidmatan sedia ada. Ia tidak memulakan inferens atau memerlukan GPU.',
|
||||
retryHelp: 'Hanya titik bermasalah yang dipilih diulang, dengan parameter sama dan benih baharu.',
|
||||
secretHelp: 'Salinan tidak mengandungi kunci API atau pengepala. Masukkan semula jika perlu.',
|
||||
resultHelp:
|
||||
|
||||
@@ -1043,6 +1043,23 @@ const message = {
|
||||
validateImageAvailabilityHelper: 'Gera uma imagem mínima antes de salvar e pode gerar custos',
|
||||
},
|
||||
model: {
|
||||
gpuAcceleration: 'Aceleração de GPU',
|
||||
gpuMode: 'Seleção de aceleradores',
|
||||
gpuAll: 'Todos os aceleradores',
|
||||
gpuCount: 'Especificar quantidade',
|
||||
gpuSpecific: 'Selecionar dispositivos',
|
||||
gpuAllHelper: 'Usar todos os aceleradores disponíveis deste tipo no nó atual.',
|
||||
gpuCountHelper:
|
||||
'O ambiente de execução aloca a quantidade solicitada sem vincular dispositivos específicos.',
|
||||
gpuSpecificHelper: 'Selecione manualmente os aceleradores específicos.',
|
||||
gpuCountRequired: 'Insira um número inteiro maior que 0',
|
||||
gpuDevices: 'Dispositivos GPU',
|
||||
gpuCapabilitiesHelper:
|
||||
'O acesso à GPU é obrigatório; habilite o monitoramento e gerenciamento (utility) e o suporte a cálculos (compute) conforme necessário.',
|
||||
gpuSelectRequired: 'Selecione pelo menos um dispositivo GPU disponível.',
|
||||
gpuUnsupported: 'Não suportado',
|
||||
gpuNotDetected: 'Nenhum acelerador compatível detectado',
|
||||
gpuMissing: 'Indisponível',
|
||||
modelCategory: 'Categoria do modelo',
|
||||
modelName: 'Nome do modelo',
|
||||
modelArchitecture: 'Tipo de modelo',
|
||||
@@ -1081,7 +1098,7 @@ const message = {
|
||||
container_conn_helper: 'Use este endereço para acesso ou conexão entre contêineres',
|
||||
from_remote: 'Este modelo não foi baixado pelo 1Panel; não há logs de download relacionados.',
|
||||
no_logs: 'Os logs de download deste modelo foram excluídos e não podem ser visualizados.',
|
||||
vllmVersionHelper: 'Para servidores FusionXpark GB 10, selecione a versão -cu130.',
|
||||
vllmVersionHelper: 'Para servidores FusionXpark GB10, selecione a versão -cu130.',
|
||||
vllmDeployProfile: 'Modelo de implantação',
|
||||
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
@@ -1746,8 +1763,8 @@ const message = {
|
||||
migModeHelper:
|
||||
'Usado para criar instâncias MIG, implementando isolamento físico da GPU na camada do usuário.',
|
||||
migModeNA: 'Não Suportado',
|
||||
current: 'Monitoramento em Tempo Real',
|
||||
history: 'Registros Históricos',
|
||||
current: 'Status em tempo real',
|
||||
history: 'Dados de monitoramento',
|
||||
notSupport: 'A versão atual ou o driver não suportam exibir este parâmetro.',
|
||||
processCount: 'Qtd. de processos',
|
||||
},
|
||||
@@ -2059,8 +2076,6 @@ const message = {
|
||||
retry: 'Repetir pontos selecionados',
|
||||
environment: 'Ambiente',
|
||||
plan: 'Configuração fixada',
|
||||
clientHelp:
|
||||
'O contêiner cliente roda no nó principal e envia requisições a um serviço existente. Não inicia inferência nem exige GPU.',
|
||||
retryHelp: 'Apenas os pontos anormais selecionados são repetidos, com os mesmos parâmetros e novas sementes.',
|
||||
secretHelp: 'As cópias não incluem chaves de API nem cabeçalhos. Informe-os novamente se necessário.',
|
||||
resultHelp:
|
||||
|
||||
@@ -1036,6 +1036,23 @@ const message = {
|
||||
'Перед сохранением создаётся одно минимальное изображение; возможна оплата',
|
||||
},
|
||||
model: {
|
||||
gpuAcceleration: 'Ускорение GPU',
|
||||
gpuMode: 'Выбор ускорителей',
|
||||
gpuAll: 'Все ускорители',
|
||||
gpuCount: 'Указать количество',
|
||||
gpuSpecific: 'Выбрать устройства',
|
||||
gpuAllHelper: 'Использовать все доступные ускорители этого типа на текущем узле.',
|
||||
gpuCountHelper:
|
||||
'Среда выполнения выделяет указанное количество ускорителей без привязки к конкретным устройствам.',
|
||||
gpuSpecificHelper: 'Выберите конкретные ускорители вручную.',
|
||||
gpuCountRequired: 'Введите целое число больше 0',
|
||||
gpuDevices: 'Устройства GPU',
|
||||
gpuCapabilitiesHelper:
|
||||
'Доступ к GPU обязателен; при необходимости включите мониторинг и управление (utility) и поддержку вычислений (compute).',
|
||||
gpuSelectRequired: 'Выберите хотя бы одно доступное устройство GPU.',
|
||||
gpuUnsupported: 'Не поддерживается',
|
||||
gpuNotDetected: 'Поддерживаемые ускорители не обнаружены',
|
||||
gpuMissing: 'Недоступно',
|
||||
modelCategory: 'Категория модели',
|
||||
modelName: 'Название модели',
|
||||
modelArchitecture: 'Тип модели',
|
||||
@@ -1074,7 +1091,7 @@ const message = {
|
||||
container_conn_helper: 'Используйте этот адрес для доступа или подключения между контейнерами',
|
||||
from_remote: 'Эта модель не была загружена через 1Panel, нет связанных журналов извлечения.',
|
||||
no_logs: 'Журналы извлечения для этой модели были удалены и не могут быть просмотрены.',
|
||||
vllmVersionHelper: 'Для серверов FusionXpark GB 10 выберите версию -cu130.',
|
||||
vllmVersionHelper: 'Для серверов FusionXpark GB10 выберите версию -cu130.',
|
||||
vllmDeployProfile: 'Модель развертывания',
|
||||
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
@@ -1735,8 +1752,8 @@ const message = {
|
||||
migModeHelper:
|
||||
'Используется для создания экземпляров MIG, реализуя физическую изоляцию GPU на пользовательском уровне.',
|
||||
migModeNA: 'Не Поддерживается',
|
||||
current: 'Мониторинг в Реальном Времени',
|
||||
history: 'Исторические Записи',
|
||||
current: 'Состояние в реальном времени',
|
||||
history: 'Данные мониторинга',
|
||||
notSupport: 'Текущая версия или драйвер не поддерживает отображение этого параметра.',
|
||||
processCount: 'Кол-во процессов',
|
||||
},
|
||||
@@ -2052,8 +2069,6 @@ const message = {
|
||||
retry: 'Повторить выбранные точки',
|
||||
environment: 'Среда выполнения',
|
||||
plan: 'Зафиксированная конфигурация',
|
||||
clientHelp:
|
||||
'Клиентский контейнер работает на главном узле и отправляет запросы существующему сервису. Он не запускает модель и не требует GPU.',
|
||||
retryHelp: 'Повторяются только выбранные проблемные точки с прежними параметрами и новыми начальными числами.',
|
||||
secretHelp: 'Копии не содержат ключи API и заголовки. При необходимости введите их заново.',
|
||||
resultHelp:
|
||||
|
||||
@@ -1047,6 +1047,22 @@ const message = {
|
||||
validateImageAvailabilityHelper: 'Kaydetmeden önce bir küçük görsel üretir ve ücret doğurabilir',
|
||||
},
|
||||
model: {
|
||||
gpuAcceleration: 'GPU hızlandırma',
|
||||
gpuMode: 'Hızlandırıcı seçimi',
|
||||
gpuAll: 'Tüm hızlandırıcılar',
|
||||
gpuCount: 'Sayı belirt',
|
||||
gpuSpecific: 'Cihaz seç',
|
||||
gpuAllHelper: 'Geçerli düğümde bu türdeki tüm kullanılabilir hızlandırıcıları kullanın.',
|
||||
gpuCountHelper: 'Çalışma zamanı, belirli cihazlara bağlamadan istenen sayıda hızlandırıcı tahsis eder.',
|
||||
gpuSpecificHelper: 'Belirli hızlandırıcıları elle seçin.',
|
||||
gpuCountRequired: '0’dan büyük bir tam sayı girin',
|
||||
gpuDevices: 'GPU aygıtları',
|
||||
gpuCapabilitiesHelper:
|
||||
'GPU erişimi zorunludur; izleme ve yönetimi (utility) ve hesaplama desteğini (compute) gerektiğinde etkinleştirin.',
|
||||
gpuSelectRequired: 'En az bir kullanılabilir GPU aygıtı seçin.',
|
||||
gpuUnsupported: 'Desteklenmiyor',
|
||||
gpuNotDetected: 'Desteklenen hızlandırıcı algılanmadı',
|
||||
gpuMissing: 'Kullanılamıyor',
|
||||
modelCategory: 'Model kategorisi',
|
||||
modelName: 'Model adı',
|
||||
modelArchitecture: 'Model türü',
|
||||
@@ -1085,7 +1101,7 @@ const message = {
|
||||
container_conn_helper: 'Konteynerler arası erişim veya bağlantı için bu adresi kullanın',
|
||||
from_remote: 'Bu model 1Panel aracılığıyla indirilmedi, ilgili çekme logları yok.',
|
||||
no_logs: 'Bu modelin çekme logları silindi ve görüntülenemiyor.',
|
||||
vllmVersionHelper: 'FusionXpark GB 10 sunucuları için lütfen -cu130 sürümünü seçin.',
|
||||
vllmVersionHelper: 'FusionXpark GB10 sunucuları için lütfen -cu130 sürümünü seçin.',
|
||||
vllmDeployProfile: 'Dağıtım modeli',
|
||||
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
@@ -1743,8 +1759,8 @@ const message = {
|
||||
migModeHelper:
|
||||
"MIG örnekleri oluşturmak için kullanılır, GPU'nun kullanıcı katmanında fiziksel izolasyonunu uygular.",
|
||||
migModeNA: 'Desteklenmiyor',
|
||||
current: 'Gerçek Zamanlı İzleme',
|
||||
history: 'Geçmiş Kayıtlar',
|
||||
current: 'Gerçek Zamanlı Durum',
|
||||
history: 'İzleme Verileri',
|
||||
notSupport: 'Mevcut sürüm veya sürücü bu parametrenin görüntülenmesini desteklemiyor.',
|
||||
processCount: 'Süreç sayısı',
|
||||
},
|
||||
@@ -2058,8 +2074,6 @@ const message = {
|
||||
retry: 'Seçili ölçümleri yinele',
|
||||
environment: 'Çalışma ortamı',
|
||||
plan: 'Sabitlenmiş yapılandırma',
|
||||
clientHelp:
|
||||
'İstemci konteyneri ana düğümde çalışır ve mevcut hizmete istek gönderir. Çıkarım hizmeti başlatmaz ve GPU gerektirmez.',
|
||||
retryHelp: 'Yalnızca seçili sorunlu ölçümler aynı parametreler ve yeni tohumlarla yinelenir.',
|
||||
secretHelp: 'Kopyalar API anahtarlarını ve başlıkları içermez. Gerekiyorsa yeniden girin.',
|
||||
resultHelp:
|
||||
|
||||
@@ -977,6 +977,21 @@ const message = {
|
||||
validateImageAvailabilityHelper: '儲存前會實際產生一張最小圖片,可能產生費用',
|
||||
},
|
||||
model: {
|
||||
gpuAcceleration: 'GPU 加速',
|
||||
gpuMode: '加速卡選擇',
|
||||
gpuAll: '全部加速卡',
|
||||
gpuCount: '指定數量',
|
||||
gpuSpecific: '指定裝置',
|
||||
gpuAllHelper: '容器使用目前節點所有可用的同類型加速卡。',
|
||||
gpuCountHelper: '由執行環境分配指定數量的加速卡,不綁定特定裝置。',
|
||||
gpuSpecificHelper: '手動選擇特定加速卡。',
|
||||
gpuCountRequired: '請輸入大於 0 的整數',
|
||||
gpuDevices: 'GPU 裝置',
|
||||
gpuCapabilitiesHelper: 'GPU 存取為必選項,可依需求啟用監控管理(utility)和運算支援(compute)。',
|
||||
gpuSelectRequired: '請選擇至少一個目前可用的 GPU 裝置',
|
||||
gpuUnsupported: '不支援',
|
||||
gpuNotDetected: '未偵測到可用加速卡',
|
||||
gpuMissing: '無法使用',
|
||||
modelCategory: '模型類別',
|
||||
modelName: '模型名稱',
|
||||
modelArchitecture: '模型類型',
|
||||
@@ -1015,7 +1030,7 @@ const message = {
|
||||
container_conn_helper: '容器間瀏覽或連接使用此地址',
|
||||
from_remote: '該模型並非透過 1Panel 下載,無相關拉取日誌。',
|
||||
no_logs: '該模型的拉取日誌已被刪除,無法檢視相關日誌。',
|
||||
vllmVersionHelper: 'FusionXpark GB 10 伺服器請選擇 -cu130 版本',
|
||||
vllmVersionHelper: 'FusionXpark GB10 伺服器請選擇 -cu130 版本',
|
||||
vllmDeployProfile: '部署模型',
|
||||
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
@@ -1643,8 +1658,8 @@ const message = {
|
||||
prohibitedHelper: '禁止: 不允許行程同時執行',
|
||||
migModeHelper: '用於建立 MIG 實例,在使用者層實現 GPU 的物理隔離。',
|
||||
migModeNA: '不支援',
|
||||
current: '即時監控',
|
||||
history: '歷史記錄',
|
||||
current: '即時狀態',
|
||||
history: '監控資料',
|
||||
notSupport: '當前版本或驅動不支援顯示該參數。',
|
||||
processCount: '行程數',
|
||||
},
|
||||
@@ -1928,7 +1943,6 @@ const message = {
|
||||
retry: '重測選取測點',
|
||||
environment: '執行環境',
|
||||
plan: '凍結設定',
|
||||
clientHelp: '壓測容器在主節點執行,向既有服務傳送請求。不會啟動推論服務,也不需分配 GPU。',
|
||||
retryHelp: '本輪只重測選取的異常測點,保留參數並重新分配隨機種子。',
|
||||
secretHelp: '複製設定不包含 API Key 與請求標頭,請視需要重新填寫。',
|
||||
resultHelp:
|
||||
|
||||
@@ -990,6 +990,21 @@ const message = {
|
||||
validateImageAvailabilityHelper: '保存前会真实生成一张最小图片,可能产生费用',
|
||||
},
|
||||
model: {
|
||||
gpuAcceleration: 'GPU 加速',
|
||||
gpuMode: '加速卡选择',
|
||||
gpuAll: '全部加速卡',
|
||||
gpuCount: '指定数量',
|
||||
gpuSpecific: '指定设备',
|
||||
gpuAllHelper: '容器使用当前节点所有可用的同类型加速卡。',
|
||||
gpuCountHelper: '由运行时分配指定数量的加速卡,不绑定具体设备。',
|
||||
gpuSpecificHelper: '手动选择具体加速卡。',
|
||||
gpuCountRequired: '请输入大于 0 的整数',
|
||||
gpuDevices: 'GPU 设备',
|
||||
gpuCapabilitiesHelper: 'GPU 访问为必选项,可按需启用监控管理(utility)和计算支持(compute)。',
|
||||
gpuSelectRequired: '请选择至少一个当前可用的 GPU 设备',
|
||||
gpuUnsupported: '不支持',
|
||||
gpuNotDetected: '未检测到可用加速卡',
|
||||
gpuMissing: '不可用',
|
||||
modelCategory: '模型类别',
|
||||
modelName: '模型名称',
|
||||
modelArchitecture: '模型类型',
|
||||
@@ -1028,7 +1043,7 @@ const message = {
|
||||
container_conn_helper: '容器间访问或连接使用此地址',
|
||||
from_remote: '该模型并非通过 1Panel 下载,无相关拉取日志。',
|
||||
no_logs: '该模型的拉取日志已被删除,无法查看相关日志。',
|
||||
vllmVersionHelper: 'FusionXpark GB 10 服务器请选择 -cu130 版本',
|
||||
vllmVersionHelper: 'FusionXpark GB10 服务器请选择 -cu130 版本',
|
||||
vllmDeployProfile: '部署模型',
|
||||
vllmProfileFlash0731: 'DeepSeek V4 Flash 0731',
|
||||
vllmProfileVisionExp: 'DeepSeek V4 Flash Vision Exp',
|
||||
@@ -1658,8 +1673,8 @@ const message = {
|
||||
prohibitedHelper: '禁止: 不允许进程同时执行',
|
||||
migModeHelper: '用于创建 MIG 实例,在用户层实现 GPU 的物理隔离。',
|
||||
migModeNA: '不支持',
|
||||
current: '实时监控',
|
||||
history: '历史记录',
|
||||
current: '实时状态',
|
||||
history: '监控数据',
|
||||
notSupport: '当前版本或驱动不支持显示该参数。',
|
||||
},
|
||||
mcp: {
|
||||
@@ -1936,7 +1951,6 @@ const message = {
|
||||
retry: '补测选中测点',
|
||||
environment: '执行环境',
|
||||
plan: '冻结配置',
|
||||
clientHelp: '压测容器在主节点运行,向已有服务发送请求。不会启动推理服务,也不需要分配 GPU。',
|
||||
retryHelp: '本轮只补测选中的异常测点,保留参数并重新分配随机种子。',
|
||||
secretHelp: '复制配置不包含 API Key 和请求头,请按需重新填写。',
|
||||
resultHelp:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-form-item v-if="showSync">
|
||||
<el-form-item v-if="showSync && !hideSyncToggle">
|
||||
<el-checkbox v-model="form.syncModelAccount">{{ $t('aiTools.model.syncModelAccount') }}</el-checkbox>
|
||||
</el-form-item>
|
||||
<template v-if="!showSync || form.syncModelAccount">
|
||||
@@ -52,6 +52,7 @@ const props = defineProps<{
|
||||
containerPort: string;
|
||||
hostMode?: boolean;
|
||||
showSync?: boolean;
|
||||
hideSyncToggle?: boolean;
|
||||
}>();
|
||||
const systemIP = ref('');
|
||||
const addressTypes = computed(() => [
|
||||
|
||||
@@ -57,13 +57,17 @@
|
||||
v-if="visible && view && view !== VllmPlaceholder && !failed && !missing"
|
||||
ref="contentRef"
|
||||
v-bind="
|
||||
view === EngineConnection || view === AppParams
|
||||
? {}
|
||||
: view === ContainerLogs
|
||||
? { highlightDiff: 210 }
|
||||
: view === TaskLog
|
||||
? { width: '70%' }
|
||||
: { dialog: true }
|
||||
view === AppParams
|
||||
? { modelGPU: true }
|
||||
: view === EngineConnection
|
||||
? {}
|
||||
: view === ContainerLogs
|
||||
? { highlightDiff: 210 }
|
||||
: view === TaskLog
|
||||
? { width: '70%' }
|
||||
: actionName === 'config'
|
||||
? {}
|
||||
: { dialog: true }
|
||||
"
|
||||
@close="returnToManagement"
|
||||
@search="emit('search')"
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
.form-section {
|
||||
margin-bottom: 12px;
|
||||
border: 1px solid var(--el-border-color-light);
|
||||
border-radius: 4px;
|
||||
background: var(--el-bg-color-overlay);
|
||||
}
|
||||
.form-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 40px;
|
||||
padding: 6px 12px;
|
||||
color: var(--el-text-color-regular);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 1.5;
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
|
||||
&::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
margin-left: auto;
|
||||
flex-shrink: 0;
|
||||
border-right: 1px solid currentColor;
|
||||
border-bottom: 1px solid currentColor;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
}
|
||||
.form-section[open] > .form-section-header::after {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
.form-section-body {
|
||||
padding: 12px;
|
||||
border-top: 1px solid var(--el-border-color-light);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { FormInstance } from 'element-plus';
|
||||
|
||||
export const expandInvalidFormSection = (form: FormInstance, prop: string | string[], valid: boolean) => {
|
||||
if (valid) return;
|
||||
const section = form?.getField(prop)?.$el?.closest<HTMLDetailsElement>('details.form-section');
|
||||
if (section) section.open = true;
|
||||
};
|
||||
@@ -0,0 +1,246 @@
|
||||
<template>
|
||||
<details open class="form-section">
|
||||
<summary class="form-section-header">
|
||||
<span>{{ $t('aiTools.model.gpuAcceleration') }}</span>
|
||||
<el-switch
|
||||
@click.stop
|
||||
@keydown.stop
|
||||
v-model="model.enabled"
|
||||
:disabled="loading"
|
||||
:aria-label="$t('aiTools.model.gpuAcceleration')"
|
||||
@change="changeEnabled"
|
||||
/>
|
||||
<el-text v-if="!loading && !hasSupportedGPU" type="info" size="small">
|
||||
{{ $t(gpuDevices ? 'aiTools.model.gpuNotDetected' : 'aiTools.model.loadFailed') }}
|
||||
</el-text>
|
||||
</summary>
|
||||
<div v-if="model.enabled || model.error" v-show="!loading" class="form-section-body">
|
||||
<el-form-item
|
||||
prop="gpu.mode"
|
||||
:rules="gpuModeRules"
|
||||
:error="model.error"
|
||||
:label="model.enabled ? $t('aiTools.model.gpuMode') : undefined"
|
||||
>
|
||||
<el-radio-group v-if="model.enabled" v-model="model.mode" @change="changeMode">
|
||||
<el-radio-button value="all">
|
||||
{{ $t('aiTools.model.gpuAll') }}
|
||||
</el-radio-button>
|
||||
<el-radio-button v-if="imageType === 'nvidia'" value="count">
|
||||
{{ $t('aiTools.model.gpuCount') }}
|
||||
</el-radio-button>
|
||||
<el-radio-button value="devices">
|
||||
{{ $t('aiTools.model.gpuSpecific') }}
|
||||
</el-radio-button>
|
||||
</el-radio-group>
|
||||
<span v-if="model.enabled && model.mode === 'all'" class="input-help">
|
||||
{{ $t('aiTools.model.gpuAllHelper') }}
|
||||
</span>
|
||||
<span v-if="model.enabled && model.mode === 'count'" class="input-help">
|
||||
{{ $t('aiTools.model.gpuCountHelper') }}
|
||||
</span>
|
||||
<span v-if="model.enabled && model.mode === 'devices'" class="input-help">
|
||||
{{ $t('aiTools.model.gpuSpecificHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<template v-if="model.enabled">
|
||||
<el-form-item
|
||||
v-if="model.mode === 'count'"
|
||||
:label="$t('aiTools.model.gpuCount')"
|
||||
prop="gpu.count"
|
||||
:rules="gpuCountRules"
|
||||
>
|
||||
<el-input-number
|
||||
v-model="model.count"
|
||||
:min="1"
|
||||
:precision="0"
|
||||
:step="1"
|
||||
step-strictly
|
||||
@change="model.error = undefined"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
v-if="model.mode === 'devices'"
|
||||
:label="$t('aiTools.model.gpuDevices')"
|
||||
prop="gpu.deviceIDs"
|
||||
:rules="gpuDeviceRules"
|
||||
>
|
||||
<el-select
|
||||
v-model="model.deviceIDs"
|
||||
@change="model.error = undefined"
|
||||
popper-class="model-gpu-device-select"
|
||||
multiple
|
||||
filterable
|
||||
class="w-full"
|
||||
>
|
||||
<el-option
|
||||
v-for="device in gpuDevices || []"
|
||||
:key="device.id"
|
||||
:value="device.id"
|
||||
:label="device.name"
|
||||
:disabled="device.type !== imageType || !device.available"
|
||||
>
|
||||
<div class="flex items-center justify-between gap-2">
|
||||
<span class="truncate">{{ device.name }}</span>
|
||||
<span class="flex shrink-0 items-center gap-1">
|
||||
<el-tag v-if="device.type !== imageType" size="small" type="warning">
|
||||
{{ $t('aiTools.model.gpuUnsupported') }}
|
||||
</el-tag>
|
||||
<el-tag v-else-if="!device.available" size="small" type="danger">
|
||||
{{ $t('aiTools.model.gpuMissing') }}
|
||||
</el-tag>
|
||||
<el-tag size="small" type="info">
|
||||
{{ gpuTypeLabels[device.type] || device.type.toUpperCase() }}
|
||||
</el-tag>
|
||||
</span>
|
||||
</div>
|
||||
</el-option>
|
||||
<el-option
|
||||
v-for="id in missingGPUDevices"
|
||||
:key="id"
|
||||
:value="id"
|
||||
:label="`${id} (${$t('aiTools.model.gpuMissing')})`"
|
||||
disabled
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="imageType === 'nvidia'" label="Capabilities" prop="gpu.capabilities">
|
||||
<el-checkbox-group v-model="model.capabilities">
|
||||
<el-checkbox value="gpu" disabled>gpu</el-checkbox>
|
||||
<el-checkbox value="utility">utility</el-checkbox>
|
||||
<el-checkbox value="compute">compute</el-checkbox>
|
||||
</el-checkbox-group>
|
||||
<span class="input-help">{{ $t('aiTools.model.gpuCapabilitiesHelper') }}</span>
|
||||
</el-form-item>
|
||||
</template>
|
||||
<span v-if="selectingDevices && gpuWarnings.length" class="input-help">{{ gpuWarnings.join('; ') }}</span>
|
||||
</div>
|
||||
</details>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import { loadModelGPUDevices } from '@/api/modules/ai';
|
||||
import i18n from '@/lang';
|
||||
|
||||
const gpuTypeLabels: Record<string, string> = { nvidia: 'NVIDIA', intel: 'Intel', ascend: 'Ascend', amd: 'AMD' };
|
||||
const props = withDefaults(defineProps<{ imageType?: string }>(), { imageType: 'nvidia' });
|
||||
const model = defineModel<AI.ModelGPUConfig>({ required: true });
|
||||
const loading = ref(false);
|
||||
const gpuWarnings = ref<string[]>([]);
|
||||
const gpuDevices = ref<AI.ModelGPUDevice[]>();
|
||||
const hasSupportedGPU = computed(() =>
|
||||
Boolean(gpuDevices.value?.some((item) => item.type === props.imageType && item.available)),
|
||||
);
|
||||
const selectingDevices = computed(() => model.value.enabled && model.value.mode === 'devices');
|
||||
const missingGPUDevices = computed(() =>
|
||||
model.value.deviceIDs.filter((id) => !(gpuDevices.value || []).some((item) => item.id === id)),
|
||||
);
|
||||
const gpuModeRules = [
|
||||
{
|
||||
validator: (_rule: unknown, _value: string, callback: (error?: Error) => void) => {
|
||||
if (model.value.error) {
|
||||
callback(new Error(model.value.error));
|
||||
} else if (model.value.enabled && !gpuDevices.value) {
|
||||
callback(new Error(i18n.global.t('aiTools.model.loadFailed')));
|
||||
} else if (model.value.enabled && !hasSupportedGPU.value) {
|
||||
callback(new Error(i18n.global.t('aiTools.model.gpuNotDetected')));
|
||||
} else if (
|
||||
model.value.enabled &&
|
||||
(!['all', 'count', 'devices'].includes(model.value.mode) ||
|
||||
(model.value.mode === 'count' && props.imageType !== 'nvidia'))
|
||||
) {
|
||||
callback(new Error(i18n.global.t('aiTools.model.gpuSelectRequired')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
];
|
||||
const gpuCountRules = [
|
||||
{
|
||||
validator: (_rule: unknown, value: number, callback: (error?: Error) => void) => {
|
||||
const availableCount =
|
||||
gpuDevices.value?.filter((item) => item.type === props.imageType && item.available).length || 0;
|
||||
if (model.value.enabled && model.value.mode === 'count' && (!Number.isInteger(value) || value < 1)) {
|
||||
callback(new Error(i18n.global.t('aiTools.model.gpuCountRequired')));
|
||||
} else if (
|
||||
model.value.enabled &&
|
||||
model.value.mode === 'count' &&
|
||||
availableCount > 0 &&
|
||||
value > availableCount
|
||||
) {
|
||||
callback(new Error(i18n.global.t('commons.rule.numberRange', [1, availableCount])));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
];
|
||||
const gpuDeviceRules = [
|
||||
{
|
||||
validator: (_rule: unknown, value: string[], callback: (error?: Error) => void) => {
|
||||
if (selectingDevices.value && !value.length) {
|
||||
callback(new Error(i18n.global.t('aiTools.model.gpuSelectRequired')));
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
];
|
||||
|
||||
const changeMode = () => {
|
||||
model.value.error = undefined;
|
||||
model.value.deviceIDs = [];
|
||||
model.value.count = model.value.mode === 'count' ? 1 : undefined;
|
||||
};
|
||||
|
||||
const changeEnabled = () => {
|
||||
model.value.error = undefined;
|
||||
if (!model.value.enabled) {
|
||||
model.value.mode = 'all';
|
||||
model.value.count = undefined;
|
||||
model.value.deviceIDs = [];
|
||||
}
|
||||
};
|
||||
|
||||
let requestVersion = 0;
|
||||
const init = async (create: boolean) => {
|
||||
const version = ++requestVersion;
|
||||
loading.value = true;
|
||||
|
||||
gpuDevices.value = undefined;
|
||||
gpuWarnings.value = [];
|
||||
if (create) {
|
||||
model.value.enabled = false;
|
||||
model.value.mode = 'all';
|
||||
model.value.count = undefined;
|
||||
model.value.deviceIDs = [];
|
||||
model.value.error = undefined;
|
||||
}
|
||||
try {
|
||||
const { data } = await loadModelGPUDevices();
|
||||
if (version !== requestVersion) return;
|
||||
gpuDevices.value = data.devices || [];
|
||||
gpuWarnings.value = data.warnings || [];
|
||||
if (create) model.value.enabled = hasSupportedGPU.value;
|
||||
} catch {
|
||||
} finally {
|
||||
if (version === requestVersion) {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
defineExpose({ init, loading });
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use '@/views/ai/model/components/form-section.scss';
|
||||
:global(.model-gpu-device-select .el-select-dropdown__item.is-selected::after) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
@@ -80,13 +80,6 @@
|
||||
/>
|
||||
</template>
|
||||
<template #main>
|
||||
<el-alert
|
||||
v-if="loadFailed"
|
||||
type="error"
|
||||
:closable="false"
|
||||
:title="$t('aiTools.model.loadFailed')"
|
||||
class="mb-4"
|
||||
/>
|
||||
<ComplexTable
|
||||
v-if="!isLocal"
|
||||
:data="connections"
|
||||
@@ -381,7 +374,6 @@ const poolRef = ref();
|
||||
const terminalRef = ref<InstanceType<typeof TerminalDialog>>();
|
||||
const syncRef = ref<InstanceType<typeof SyncService>>();
|
||||
const loading = ref(false);
|
||||
const loadFailed = ref(false);
|
||||
const connections = ref<AI.AgentAccountItem[]>([]);
|
||||
const accountColumns = ref<FuTableColumnConfig[]>([]);
|
||||
const providers = ref<AI.ProviderInfo[]>([]);
|
||||
@@ -507,7 +499,6 @@ const search = async () => {
|
||||
const version = ++searchVersion;
|
||||
if (!isCurrentPage.value || isFiles || isManagement.value) return;
|
||||
loading.value = true;
|
||||
loadFailed.value = false;
|
||||
try {
|
||||
if (isLocal) {
|
||||
const data = await loadModelOverview(isProductPro.value, isFxplay.value);
|
||||
@@ -533,7 +524,6 @@ const search = async () => {
|
||||
connections.value = [];
|
||||
pagination.total = 0;
|
||||
deployments.value = [];
|
||||
loadFailed.value = true;
|
||||
} finally {
|
||||
if (version === searchVersion) loading.value = false;
|
||||
}
|
||||
|
||||
@@ -5,15 +5,30 @@
|
||||
@update:model-value="open = $event"
|
||||
@close="handleClose"
|
||||
>
|
||||
<AppInstallForm ref="formRef" v-model="form" :loading="loading">
|
||||
<AppInstallForm ref="formRef" v-model="form" :loading="loading" sectioned hide-gpu-config>
|
||||
<template #after-params>
|
||||
<AccountAddress
|
||||
ref="accountRef"
|
||||
show-sync
|
||||
:form="form"
|
||||
:host-port="Number(form.params.PANEL_APP_PORT_HTTP) || 11434"
|
||||
container-port="11434"
|
||||
/>
|
||||
<GPUConfig ref="gpuRef" v-model="form.gpu" />
|
||||
<details open class="form-section">
|
||||
<summary class="form-section-header">
|
||||
<span>{{ $t('aiTools.model.syncModelAccount') }}</span>
|
||||
<el-switch
|
||||
@click.stop
|
||||
@keydown.stop
|
||||
v-model="form.syncModelAccount"
|
||||
:aria-label="$t('aiTools.model.syncModelAccount')"
|
||||
/>
|
||||
</summary>
|
||||
<div v-show="form.syncModelAccount" class="form-section-body">
|
||||
<AccountAddress
|
||||
ref="accountRef"
|
||||
show-sync
|
||||
hide-sync-toggle
|
||||
:form="form"
|
||||
:host-port="Number(form.params.PANEL_APP_PORT_HTTP) || 11434"
|
||||
container-port="11434"
|
||||
/>
|
||||
</div>
|
||||
</details>
|
||||
</template>
|
||||
</AppInstallForm>
|
||||
<template v-if="!inline" #footer>
|
||||
@@ -22,7 +37,7 @@
|
||||
v-permission
|
||||
type="primary"
|
||||
:loading="loading"
|
||||
:disabled="loading || !form.appDetailId"
|
||||
:disabled="loading || gpuRef?.loading || !form.appDetailId"
|
||||
@click="submit"
|
||||
>
|
||||
{{ $t('commons.button.confirm') }}
|
||||
@@ -40,6 +55,8 @@ import i18n from '@/lang';
|
||||
import DrawerPro from '@/components/drawer-pro/index.vue';
|
||||
import AppInstallForm from '@/views/app-store/detail/form/index.vue';
|
||||
import AccountAddress from '../../components/account-address/index.vue';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import GPUConfig from '../../components/gpu-config/index.vue';
|
||||
|
||||
const props = defineProps({ inline: Boolean });
|
||||
const emit = defineEmits(['search', 'task', 'close']);
|
||||
@@ -47,7 +64,14 @@ const open = ref(false);
|
||||
const loading = ref(false);
|
||||
const formRef = ref<InstanceType<typeof AppInstallForm>>();
|
||||
const accountRef = ref();
|
||||
const gpuRef = ref<InstanceType<typeof GPUConfig>>();
|
||||
const form = ref({
|
||||
gpu: {
|
||||
enabled: true,
|
||||
mode: 'all',
|
||||
deviceIDs: [],
|
||||
capabilities: ['gpu', 'utility', 'compute'],
|
||||
} as AI.ModelGPUConfig,
|
||||
appDetailId: 0,
|
||||
name: '',
|
||||
params: {} as Record<string, any>,
|
||||
@@ -69,12 +93,15 @@ const openCreate = async () => {
|
||||
if (!open.value) return;
|
||||
formRef.value.resetForm();
|
||||
formRef.value.setFormData({
|
||||
gpu: { enabled: true, mode: 'all', deviceIDs: [], capabilities: ['gpu', 'utility', 'compute'] },
|
||||
restartPolicy: 'unless-stopped',
|
||||
syncModelAccount: true,
|
||||
modelAccountBaseURLType: 'container',
|
||||
modelAccountBaseURL: '',
|
||||
});
|
||||
await formRef.value.initForm('ollama', true);
|
||||
if (!open.value) return;
|
||||
await gpuRef.value?.init(true);
|
||||
await nextTick();
|
||||
formRef.value?.clearValidate();
|
||||
} finally {
|
||||
@@ -82,7 +109,7 @@ const openCreate = async () => {
|
||||
}
|
||||
};
|
||||
const submit = async () => {
|
||||
if (!form.value.appDetailId || !(await formRef.value?.validate())) return;
|
||||
if (loading.value || gpuRef.value?.loading || !form.value.appDetailId || !(await formRef.value?.validate())) return;
|
||||
const data = formRef.value.getFormData();
|
||||
if (data.editCompose && !data.dockerCompose.trim()) {
|
||||
MsgError(i18n.global.t('app.composeNullErr'));
|
||||
@@ -112,5 +139,14 @@ watch(
|
||||
watch(open, (value) => {
|
||||
if (props.inline && !value) handleClose();
|
||||
});
|
||||
defineExpose({ openCreate, submit, loading, disabled: computed(() => !form.value.appDetailId) });
|
||||
defineExpose({
|
||||
openCreate,
|
||||
submit,
|
||||
loading,
|
||||
disabled: computed(() => !form.value.appDetailId || gpuRef.value?.loading),
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use '@/views/ai/model/components/form-section.scss';
|
||||
</style>
|
||||
|
||||
@@ -1,66 +1,84 @@
|
||||
<template>
|
||||
<component
|
||||
:is="inline ? 'div' : dialog ? DialogPro : DrawerPro"
|
||||
:is="inline ? 'div' : DrawerPro"
|
||||
v-bind="
|
||||
inline
|
||||
? {}
|
||||
: {
|
||||
modelValue: drawerVisiable,
|
||||
header: $t('commons.button.' + mode),
|
||||
title: $t('commons.button.' + mode),
|
||||
size: 'large',
|
||||
size: isMobile ? 'full' : 'large',
|
||||
}
|
||||
"
|
||||
@update:model-value="drawerVisiable = $event"
|
||||
@close="handleClose"
|
||||
>
|
||||
<el-alert :title="$t('aiTools.tensorRT.imageAlert')" class="common-prompt" :closable="false" type="warning" />
|
||||
<el-form ref="formRef" label-position="top" :model="tensorRTLLM" :rules="rules" v-loading="loading">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input clearable v-model.trim="tensorRTLLM.name" :disabled="mode == 'edit'" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('app.containerName')" prop="containerName">
|
||||
<el-input v-model.trim="tensorRTLLM.containerName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.image')" prop="image">
|
||||
<el-input v-model.trim="tensorRTLLM.image" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('app.version')" prop="version">
|
||||
<el-input v-model.trim="tensorRTLLM.version" />
|
||||
</el-form-item>
|
||||
<div v-if="isFxplay">
|
||||
<el-form-item :label="$t('aiTools.tensorRT.modelSpeedup')" prop="modelSpeedup">
|
||||
<el-switch v-model="tensorRTLLM.modelSpeedup" @change="changeModelSpeedup"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('aiTools.tensorRT.modelType')"
|
||||
prop="modelType"
|
||||
v-if="tensorRTLLM.modelSpeedup"
|
||||
>
|
||||
<el-select v-model="tensorRTLLM.modelType">
|
||||
<el-option label="Qwen3" value="Qwen3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item :label="$t('aiTools.tensorRT.modelDir')" prop="modelDir">
|
||||
<el-input v-model="tensorRTLLM.modelDir">
|
||||
<template #prepend>
|
||||
<el-button icon="Folder" @click="modelDirRef.acceptParams({ dir: true })" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('runtime.runScript')" prop="command">
|
||||
<el-input v-model="tensorRTLLM.command"></el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('aiTools.tensorRT.commandHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<NodeConfig v-model="tensorRTLLM" />
|
||||
<el-form
|
||||
@validate="(prop, valid) => expandInvalidFormSection(formRef, prop, valid)"
|
||||
scroll-to-error
|
||||
ref="formRef"
|
||||
label-position="top"
|
||||
:model="tensorRTLLM"
|
||||
:rules="rules"
|
||||
v-loading="loading"
|
||||
>
|
||||
<details open class="form-section">
|
||||
<summary class="form-section-header">{{ $t('home.baseInfo') }}</summary>
|
||||
<div class="form-section-body">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input clearable v-model.trim="tensorRTLLM.name" :disabled="mode == 'edit'" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('app.containerName')" prop="containerName">
|
||||
<el-input v-model.trim="tensorRTLLM.containerName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.image')" prop="image">
|
||||
<el-input v-model.trim="tensorRTLLM.image" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('app.version')" prop="version">
|
||||
<el-input v-model.trim="tensorRTLLM.version" />
|
||||
</el-form-item>
|
||||
<div v-if="isFxplay">
|
||||
<el-form-item :label="$t('aiTools.tensorRT.modelSpeedup')" prop="modelSpeedup">
|
||||
<el-switch v-model="tensorRTLLM.modelSpeedup" @change="changeModelSpeedup"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('aiTools.tensorRT.modelType')"
|
||||
prop="modelType"
|
||||
v-if="tensorRTLLM.modelSpeedup"
|
||||
>
|
||||
<el-select v-model="tensorRTLLM.modelType">
|
||||
<el-option label="Qwen3" value="Qwen3" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<el-form-item :label="$t('aiTools.tensorRT.modelDir')" prop="modelDir">
|
||||
<el-input v-model="tensorRTLLM.modelDir">
|
||||
<template #prepend>
|
||||
<el-button icon="Folder" @click="modelDirRef.acceptParams({ dir: true })" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('runtime.runScript')" prop="command">
|
||||
<el-input v-model="tensorRTLLM.command"></el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('aiTools.tensorRT.commandHelper') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</details>
|
||||
<GPUConfig ref="gpuRef" v-model="tensorRTLLM.gpu" />
|
||||
<details open class="form-section">
|
||||
<summary class="form-section-header">{{ $t('app.advanced') }}</summary>
|
||||
<div class="form-section-body">
|
||||
<NodeConfig v-model="tensorRTLLM" />
|
||||
</div>
|
||||
</details>
|
||||
</el-form>
|
||||
<template v-if="!inline" #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button @click="drawerVisiable = false">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button v-permission :disabled="loading" type="primary" @click="onSubmit">
|
||||
<el-button v-permission :disabled="loading || gpuRef?.loading" type="primary" @click="onSubmit">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</span>
|
||||
@@ -70,26 +88,35 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import DialogPro from '@/components/dialog-pro/index.vue';
|
||||
const props = defineProps({ dialog: Boolean, inline: Boolean });
|
||||
import { expandInvalidFormSection } from '@/views/ai/model/components/form-section';
|
||||
const props = defineProps({ inline: Boolean });
|
||||
import NodeConfig from '@/views/website/runtime//components/node-config.vue';
|
||||
import DrawerPro from '@/components/drawer-pro/index.vue';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
|
||||
import { reactive, ref } from 'vue';
|
||||
import { computed, nextTick, reactive, ref } from 'vue';
|
||||
import { AI } from '@/api/interface/ai';
|
||||
import GPUConfig from '../../components/gpu-config/index.vue';
|
||||
import { Rules } from '@/global/form-rules';
|
||||
import i18n from '@/lang';
|
||||
import { ElForm, FormInstance } from 'element-plus';
|
||||
import { createTensorRTLLM, updateTensorRTLLM } from '@/api/modules/ai';
|
||||
import { MsgSuccess } from '@/utils/message';
|
||||
import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
const { isFxplay } = useGlobalStore();
|
||||
const { isFxplay, isMobile } = useGlobalStore();
|
||||
|
||||
const loading = ref(false);
|
||||
const gpuRef = ref<InstanceType<typeof GPUConfig>>();
|
||||
const mode = ref('create');
|
||||
const drawerVisiable = ref(false);
|
||||
const newTensorRTLLM = () => {
|
||||
return {
|
||||
gpu: {
|
||||
enabled: true,
|
||||
mode: 'all',
|
||||
deviceIDs: [],
|
||||
capabilities: ['gpu', 'utility', 'compute'],
|
||||
} as AI.ModelGPUConfig,
|
||||
name: '',
|
||||
containerName: '',
|
||||
version: '1.2.0rc0',
|
||||
@@ -108,15 +135,32 @@ const modelDirRef = ref();
|
||||
const tensorRTLLM = ref(newTensorRTLLM());
|
||||
const emit = defineEmits(['search', 'close']);
|
||||
|
||||
const openCreate = (): void => {
|
||||
const openCreate = async () => {
|
||||
mode.value = 'create';
|
||||
drawerVisiable.value = true;
|
||||
tensorRTLLM.value = newTensorRTLLM();
|
||||
loading.value = true;
|
||||
try {
|
||||
await nextTick();
|
||||
await gpuRef.value?.init(true);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const openEdit = (rowData: any): void => {
|
||||
const openEdit = async (rowData: any) => {
|
||||
mode.value = 'edit';
|
||||
tensorRTLLM.value = { ...rowData };
|
||||
tensorRTLLM.value = {
|
||||
...rowData,
|
||||
gpu: {
|
||||
...rowData.gpu,
|
||||
mode: rowData.gpu?.mode || 'devices',
|
||||
error: rowData.gpu ? rowData.gpu.error : i18n.global.t('aiTools.model.loadFailed'),
|
||||
enabled: rowData.gpu?.enabled ?? false,
|
||||
deviceIDs: [...(rowData.gpu?.deviceIDs || [])],
|
||||
capabilities: [...(rowData.gpu?.capabilities || ['gpu', 'utility', 'compute'])],
|
||||
},
|
||||
};
|
||||
if (tensorRTLLM.value.environments == null) {
|
||||
tensorRTLLM.value.environments = [];
|
||||
}
|
||||
@@ -131,6 +175,13 @@ const openEdit = (rowData: any): void => {
|
||||
tensorRTLLM.value.extraHosts = [];
|
||||
}
|
||||
drawerVisiable.value = true;
|
||||
loading.value = true;
|
||||
try {
|
||||
await nextTick();
|
||||
await gpuRef.value?.init(false);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
@@ -164,6 +215,7 @@ const rules = reactive({
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (loading.value || gpuRef.value?.loading) return;
|
||||
formRef.value?.validate(async (valid) => {
|
||||
if (!valid) return;
|
||||
loading.value = true;
|
||||
@@ -212,5 +264,10 @@ defineExpose({
|
||||
submit: onSubmit,
|
||||
loading,
|
||||
openEdit,
|
||||
disabled: computed(() => gpuRef.value?.loading),
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use '@/views/ai/model/components/form-section.scss';
|
||||
</style>
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
/>
|
||||
|
||||
<el-form
|
||||
@validate="(prop, valid) => expandInvalidFormSection(formRef, prop, valid)"
|
||||
scroll-to-error
|
||||
v-loading="loading"
|
||||
@submit.prevent
|
||||
ref="formRef"
|
||||
@@ -25,128 +27,137 @@
|
||||
:rules="formRules"
|
||||
:validate-on-rule-change="false"
|
||||
>
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input v-model.trim="formData.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('app.version')" prop="version">
|
||||
<el-select v-model="formData.version" @change="handleVersionChange">
|
||||
<el-option
|
||||
v-for="(version, index) in appVersions"
|
||||
:key="index"
|
||||
:label="version"
|
||||
:value="version"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<component :is="sectioned ? 'details' : 'section'" :open="sectioned" :class="{ 'form-section': sectioned }">
|
||||
<summary v-if="sectioned" class="form-section-header">{{ $t('home.baseInfo') }}</summary>
|
||||
<div :class="{ 'form-section-body': sectioned }">
|
||||
<el-form-item :label="$t('commons.table.name')" prop="name">
|
||||
<el-input v-model.trim="formData.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('app.version')" prop="version">
|
||||
<el-select v-model="formData.version" @change="handleVersionChange">
|
||||
<el-option
|
||||
v-for="(version, index) in appVersions"
|
||||
:key="index"
|
||||
:label="version"
|
||||
:value="version"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<Params
|
||||
:key="paramKey"
|
||||
v-if="showParams"
|
||||
v-model:form="formData.params"
|
||||
v-model:params="installParams"
|
||||
v-model:rules="formRules.params"
|
||||
:propStart="'params.'"
|
||||
/>
|
||||
<Params
|
||||
:key="paramKey"
|
||||
v-if="showParams"
|
||||
v-model:form="formData.params"
|
||||
v-model:params="installParams"
|
||||
v-model:rules="formRules.params"
|
||||
:propStart="'params.'"
|
||||
/>
|
||||
</div>
|
||||
</component>
|
||||
|
||||
<slot name="after-params" />
|
||||
|
||||
<el-form-item prop="advanced">
|
||||
<el-checkbox v-model="formData.advanced" :label="$t('app.advanced')" size="large" />
|
||||
</el-form-item>
|
||||
|
||||
<div v-if="formData.advanced">
|
||||
<el-form-item :label="$t('app.containerName')" prop="containerName">
|
||||
<el-input v-model.trim="formData.containerName" :placeholder="$t('app.containerNameHelper')" />
|
||||
<component :is="sectioned ? 'details' : 'section'" :open="sectioned" :class="{ 'form-section': sectioned }">
|
||||
<summary v-if="sectioned" class="form-section-header">{{ $t('app.advanced') }}</summary>
|
||||
<el-form-item v-if="!sectioned" prop="advanced">
|
||||
<el-checkbox v-model="formData.advanced" :label="$t('app.advanced')" size="large" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="allowPort" v-if="!isHostMode">
|
||||
<el-checkbox v-model="formData.allowPort" :label="$t('app.allowPort')" size="large" />
|
||||
<span class="input-help">{{ $t('app.allowPortHelper') }}</span>
|
||||
</el-form-item>
|
||||
<div v-if="formData.advanced" :class="{ 'form-section-body': sectioned }">
|
||||
<el-form-item :label="$t('app.containerName')" prop="containerName">
|
||||
<el-input v-model.trim="formData.containerName" :placeholder="$t('app.containerNameHelper')" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('app.specifyIP')" v-if="formData.allowPort" prop="specifyIP">
|
||||
<el-input v-model="formData.specifyIP" />
|
||||
<span class="input-help">{{ $t('app.specifyIPHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item prop="allowPort" v-if="!isHostMode">
|
||||
<el-checkbox v-model="formData.allowPort" :label="$t('app.allowPort')" size="large" />
|
||||
<span class="input-help">{{ $t('app.allowPortHelper') }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item :label="$t('container.restartPolicy')" prop="restartPolicy">
|
||||
<el-select v-model="formData.restartPolicy" class="p-w-300">
|
||||
<el-option :label="$t('container.no')" value="no"></el-option>
|
||||
<el-option :label="$t('container.always')" value="always"></el-option>
|
||||
<el-option :label="$t('container.onFailure')" value="on-failure"></el-option>
|
||||
<el-option :label="$t('container.unlessStopped')" value="unless-stopped"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('app.specifyIP')" v-if="formData.allowPort" prop="specifyIP">
|
||||
<el-input v-model="formData.specifyIP" />
|
||||
<span class="input-help">{{ $t('app.specifyIPHelper') }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
:label="$t('container.cpuQuota')"
|
||||
prop="cpuQuota"
|
||||
:rules="checkNumberRange(0, limits.cpu)"
|
||||
>
|
||||
<el-input type="number" class="!w-2/5" v-model.number="formData.cpuQuota" maxlength="5">
|
||||
<template #append>{{ $t('app.cpuCore') }}</template>
|
||||
</el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('container.limitHelper', [limits.cpu]) }}{{ $t('commons.units.core') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.restartPolicy')" prop="restartPolicy">
|
||||
<el-select v-model="formData.restartPolicy" class="p-w-300">
|
||||
<el-option :label="$t('container.no')" value="no"></el-option>
|
||||
<el-option :label="$t('container.always')" value="always"></el-option>
|
||||
<el-option :label="$t('container.onFailure')" value="on-failure"></el-option>
|
||||
<el-option :label="$t('container.unlessStopped')" value="unless-stopped"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
:label="$t('container.memoryLimit')"
|
||||
prop="memoryLimit"
|
||||
:rules="checkNumberRange(0, limits.memory)"
|
||||
>
|
||||
<el-input class="!w-2/5" v-model.number="formData.memoryLimit" maxlength="10">
|
||||
<template #append>
|
||||
<el-select
|
||||
v-model="formData.memoryUnit"
|
||||
placeholder="Select"
|
||||
class="p-w-100"
|
||||
@change="changeUnit"
|
||||
>
|
||||
<el-option label="MB" value="M" />
|
||||
<el-option label="GB" value="G" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('container.limitHelper', [limits.memory]) }}{{ formData.memoryUnit }}B
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('container.cpuQuota')"
|
||||
prop="cpuQuota"
|
||||
:rules="checkNumberRange(0, limits.cpu)"
|
||||
>
|
||||
<el-input type="number" class="!w-2/5" v-model.number="formData.cpuQuota" maxlength="5">
|
||||
<template #append>{{ $t('app.cpuCore') }}</template>
|
||||
</el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('container.limitHelper', [limits.cpu]) }}{{ $t('commons.units.core') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item pro="gpuConfig" v-if="gpuSupport">
|
||||
<el-checkbox v-model="formData.gpuConfig" :label="$t('app.gpuConfig')" size="large" />
|
||||
<span class="input-help">{{ $t('app.gpuConfigHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:label="$t('container.memoryLimit')"
|
||||
prop="memoryLimit"
|
||||
:rules="checkNumberRange(0, limits.memory)"
|
||||
>
|
||||
<el-input class="!w-2/5" v-model.number="formData.memoryLimit" maxlength="10">
|
||||
<template #append>
|
||||
<el-select
|
||||
v-model="formData.memoryUnit"
|
||||
placeholder="Select"
|
||||
class="p-w-100"
|
||||
@change="changeUnit"
|
||||
>
|
||||
<el-option label="MB" value="M" />
|
||||
<el-option label="GB" value="G" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('container.limitHelper', [limits.memory]) }}{{ formData.memoryUnit }}B
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item pro="pullImage">
|
||||
<el-checkbox v-model="formData.pullImage" :label="$t('app.pullImage')" size="large" />
|
||||
<span class="input-help">{{ $t('app.pullImageHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item pro="gpuConfig" v-if="gpuSupport && !hideGpuConfig">
|
||||
<el-checkbox v-model="formData.gpuConfig" :label="$t('app.gpuConfig')" size="large" />
|
||||
<span class="input-help">{{ $t('app.gpuConfigHelper') }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<PushToNode
|
||||
v-if="isMaster && isXpackOrEE && batchInstallSupport"
|
||||
:push-node="formData.pushNode"
|
||||
:nodes="formData.nodes"
|
||||
type="app"
|
||||
@update:push-node="formData.pushNode = $event"
|
||||
@update:nodes="formData.nodes = $event"
|
||||
/>
|
||||
<el-form-item pro="pullImage">
|
||||
<el-checkbox v-model="formData.pullImage" :label="$t('app.pullImage')" size="large" />
|
||||
<span class="input-help">{{ $t('app.pullImageHelper') }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="editCompose">
|
||||
<el-checkbox v-model="formData.editCompose" :label="$t('app.editCompose')" size="large" />
|
||||
<span class="input-help">{{ $t('app.editComposeHelper') }}</span>
|
||||
</el-form-item>
|
||||
<PushToNode
|
||||
v-if="isMaster && isXpackOrEE && batchInstallSupport"
|
||||
:push-node="formData.pushNode"
|
||||
:nodes="formData.nodes"
|
||||
type="app"
|
||||
@update:push-node="formData.pushNode = $event"
|
||||
@update:nodes="formData.nodes = $event"
|
||||
/>
|
||||
|
||||
<div v-if="formData.editCompose">
|
||||
<CodemirrorPro v-model="formData.dockerCompose" mode="yaml" />
|
||||
<el-form-item prop="editCompose">
|
||||
<el-checkbox v-model="formData.editCompose" :label="$t('app.editCompose')" size="large" />
|
||||
<span class="input-help">{{ $t('app.editComposeHelper') }}</span>
|
||||
</el-form-item>
|
||||
|
||||
<div v-if="formData.editCompose">
|
||||
<CodemirrorPro v-model="formData.dockerCompose" mode="yaml" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</el-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { expandInvalidFormSection } from '@/views/ai/model/components/form-section';
|
||||
import { App } from '@/api/interface/app';
|
||||
import { getAppByKey, getAppDetail, getAppInstalledByID } from '@/api/modules/app';
|
||||
import { getAppStoreConfig } from '@/api/modules/setting';
|
||||
@@ -183,6 +194,8 @@ interface Props {
|
||||
loading?: boolean;
|
||||
modelValue?: any;
|
||||
batchInstallSupport?: boolean;
|
||||
sectioned?: boolean;
|
||||
hideGpuConfig?: boolean;
|
||||
}
|
||||
|
||||
const limits = ref<Container.ResourceLimit>({
|
||||
@@ -193,6 +206,7 @@ const limits = ref<Container.ResourceLimit>({
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
loading: false,
|
||||
batchInstallSupport: false,
|
||||
sectioned: false,
|
||||
});
|
||||
|
||||
interface Emits {
|
||||
@@ -460,3 +474,7 @@ defineExpose({
|
||||
initClusterForm,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use '@/views/ai/model/components/form-section.scss';
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<DrawerPro v-model="open" :header="$t('app.param')" @close="handleClose" size="normal">
|
||||
<DrawerPro
|
||||
v-model="open"
|
||||
:header="$t('app.param')"
|
||||
@close="handleClose"
|
||||
:size="isMobile ? 'full' : appKey === 'ollama' ? 'large' : 'normal'"
|
||||
>
|
||||
<template #buttons>
|
||||
<el-button type="primary" plain @click="editParam" :disabled="loading" :icon="edit ? '' : 'Edit'">
|
||||
{{ edit ? $t('app.detail') : $t('commons.button.edit') }}
|
||||
@@ -60,127 +65,168 @@
|
||||
</div>
|
||||
<div v-else v-loading="loading">
|
||||
<el-alert :title="$t('app.updateHelper')" type="warning" :closable="false" class="common-prompt" />
|
||||
<el-form @submit.prevent ref="paramForm" :model="paramModel" label-position="top" :rules="rules">
|
||||
<div v-for="(p, index) in params" :key="index">
|
||||
<el-form-item :prop="'params.' + p.key" :label="getLabel(p)">
|
||||
<el-input
|
||||
v-if="p.type == 'number'"
|
||||
type="number"
|
||||
v-model.number="paramModel.params[p.key]"
|
||||
:disabled="!p.edit"
|
||||
></el-input>
|
||||
<el-input
|
||||
v-else-if="p.type == 'password'"
|
||||
:type="p.showPassword ? 'text' : 'password'"
|
||||
v-model="paramModel.params[p.key]"
|
||||
:disabled="!p.edit"
|
||||
>
|
||||
<template #append>
|
||||
<el-button
|
||||
:icon="p.showPassword ? 'Hide' : 'View'"
|
||||
@click="p.showPassword = !p.showPassword"
|
||||
/>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-select
|
||||
v-model="paramModel.params[p.key]"
|
||||
v-else-if="p.type == 'select'"
|
||||
:multiple="p.multiple"
|
||||
>
|
||||
<el-option
|
||||
v-for="value in p.values"
|
||||
:key="value.label"
|
||||
:value="value.value"
|
||||
:label="value.label"
|
||||
:disabled="!p.edit"
|
||||
></el-option>
|
||||
</el-select>
|
||||
<el-input v-else v-model.trim="paramModel.params[p.key]" :disabled="!p.edit"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<AccountAddress
|
||||
v-if="appKey === 'ollama'"
|
||||
ref="accountRef"
|
||||
show-sync
|
||||
:form="paramModel"
|
||||
:host-port="Number(paramModel.params.PANEL_APP_PORT_HTTP) || paramModel.httpPort"
|
||||
container-port="11434"
|
||||
:host-mode="paramModel.isHostMode"
|
||||
/>
|
||||
<el-form-item prop="advanced">
|
||||
<el-checkbox v-model="paramModel.advanced" :label="$t('app.advanced')" size="large" />
|
||||
</el-form-item>
|
||||
<div v-if="paramModel.advanced">
|
||||
<el-form-item :label="$t('app.containerName')" prop="containerName">
|
||||
<el-input
|
||||
v-model.trim="paramModel.containerName"
|
||||
:placeholder="$t('app.containerNameHelper')"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="allowPort" v-if="!paramModel.isHostMode">
|
||||
<el-checkbox
|
||||
v-model="paramModel.allowPort"
|
||||
:label="$t('app.allowPort')"
|
||||
size="large"
|
||||
@change="changeAllowPort"
|
||||
/>
|
||||
<span class="input-help">{{ $t('app.allowPortHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('app.specifyIP')" v-if="paramModel.allowPort" prop="specifyIP">
|
||||
<el-input v-model="paramModel.specifyIP"></el-input>
|
||||
<span class="input-help">{{ $t('app.specifyIPHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.restartPolicy')" prop="restartPolicy">
|
||||
<el-select v-model="paramModel.restartPolicy" class="p-w-300">
|
||||
<el-option :label="$t('container.no')" value="no"></el-option>
|
||||
<el-option :label="$t('container.always')" value="always"></el-option>
|
||||
<el-option :label="$t('container.onFailure')" value="on-failure"></el-option>
|
||||
<el-option :label="$t('container.unlessStopped')" value="unless-stopped"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.cpuQuota')" prop="cpuQuota">
|
||||
<el-input type="number" class="!w-2/5" v-model.number="paramModel.cpuQuota" maxlength="5">
|
||||
<template #append>{{ $t('app.cpuCore') }}</template>
|
||||
</el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('container.limitHelper', [limits.cpu]) }}{{ $t('commons.units.core') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.memoryLimit')" prop="memoryLimit">
|
||||
<el-input class="!w-2/5" v-model.number="paramModel.memoryLimit" maxlength="10">
|
||||
<template #append>
|
||||
<el-select
|
||||
v-model="paramModel.memoryUnit"
|
||||
placeholder="Select"
|
||||
class="!w-[85px]"
|
||||
@change="changeUnit"
|
||||
<el-form
|
||||
@validate="(prop, valid) => expandInvalidFormSection(paramForm, prop, valid)"
|
||||
scroll-to-error
|
||||
@submit.prevent
|
||||
ref="paramForm"
|
||||
:model="paramModel"
|
||||
label-position="top"
|
||||
:rules="rules"
|
||||
>
|
||||
<component
|
||||
:is="appKey === 'ollama' ? 'details' : 'section'"
|
||||
:open="appKey === 'ollama'"
|
||||
:class="{ 'form-section': appKey === 'ollama' }"
|
||||
>
|
||||
<summary v-if="appKey === 'ollama'" class="form-section-header">{{ $t('home.baseInfo') }}</summary>
|
||||
<div :class="{ 'form-section-body': appKey === 'ollama' }">
|
||||
<div v-for="(p, index) in params" :key="index">
|
||||
<el-form-item :prop="'params.' + p.key" :label="getLabel(p)">
|
||||
<el-input
|
||||
v-if="p.type == 'number'"
|
||||
type="number"
|
||||
v-model.number="paramModel.params[p.key]"
|
||||
:disabled="!p.edit"
|
||||
></el-input>
|
||||
<el-input
|
||||
v-else-if="p.type == 'password'"
|
||||
:type="p.showPassword ? 'text' : 'password'"
|
||||
v-model="paramModel.params[p.key]"
|
||||
:disabled="!p.edit"
|
||||
>
|
||||
<el-option label="MB" value="M" />
|
||||
<el-option label="GB" value="G" />
|
||||
<template #append>
|
||||
<el-button
|
||||
:icon="p.showPassword ? 'Hide' : 'View'"
|
||||
@click="p.showPassword = !p.showPassword"
|
||||
/>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-select
|
||||
v-model="paramModel.params[p.key]"
|
||||
v-else-if="p.type == 'select'"
|
||||
:multiple="p.multiple"
|
||||
>
|
||||
<el-option
|
||||
v-for="value in p.values"
|
||||
:key="value.label"
|
||||
:value="value.value"
|
||||
:label="value.label"
|
||||
:disabled="!p.edit"
|
||||
></el-option>
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('container.limitHelper', [limits.memory]) }}{{ paramModel.memoryUnit }}B
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="editCompose">
|
||||
<el-checkbox v-model="paramModel.editCompose" :label="$t('app.editCompose')" size="large" />
|
||||
<span class="input-help">{{ $t('app.editComposeHelper') }}</span>
|
||||
</el-form-item>
|
||||
<div v-if="paramModel.editCompose">
|
||||
<el-button @click="openDiff()" type="primary" class="!mb-2">
|
||||
{{ $t('app.showDiff') }}
|
||||
</el-button>
|
||||
<CodemirrorPro v-model="paramModel.dockerCompose" mode="yaml"></CodemirrorPro>
|
||||
<el-input v-else v-model.trim="paramModel.params[p.key]" :disabled="!p.edit"></el-input>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
<GPUConfig v-if="showModelGPU" ref="gpuRef" v-model="paramModel.gpu" />
|
||||
<details v-if="appKey === 'ollama'" open class="form-section">
|
||||
<summary class="form-section-header">
|
||||
<span>{{ $t('aiTools.model.syncModelAccount') }}</span>
|
||||
<el-switch
|
||||
@click.stop
|
||||
@keydown.stop
|
||||
v-model="paramModel.syncModelAccount"
|
||||
:aria-label="$t('aiTools.model.syncModelAccount')"
|
||||
/>
|
||||
</summary>
|
||||
<div v-show="paramModel.syncModelAccount" class="form-section-body">
|
||||
<AccountAddress
|
||||
ref="accountRef"
|
||||
show-sync
|
||||
hide-sync-toggle
|
||||
:form="paramModel"
|
||||
:host-port="Number(paramModel.params.PANEL_APP_PORT_HTTP) || paramModel.httpPort"
|
||||
container-port="11434"
|
||||
:host-mode="paramModel.isHostMode"
|
||||
/>
|
||||
</div>
|
||||
</details>
|
||||
<component
|
||||
:is="appKey === 'ollama' ? 'details' : 'section'"
|
||||
:open="appKey === 'ollama'"
|
||||
:class="{ 'form-section': appKey === 'ollama' }"
|
||||
>
|
||||
<summary v-if="appKey === 'ollama'" class="form-section-header">{{ $t('app.advanced') }}</summary>
|
||||
<el-form-item v-if="appKey !== 'ollama'" prop="advanced">
|
||||
<el-checkbox v-model="paramModel.advanced" :label="$t('app.advanced')" size="large" />
|
||||
</el-form-item>
|
||||
<div
|
||||
v-if="appKey === 'ollama' || paramModel.advanced"
|
||||
:class="{ 'form-section-body': appKey === 'ollama' }"
|
||||
>
|
||||
<el-form-item :label="$t('app.containerName')" prop="containerName">
|
||||
<el-input
|
||||
v-model.trim="paramModel.containerName"
|
||||
:placeholder="$t('app.containerNameHelper')"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="allowPort" v-if="!paramModel.isHostMode">
|
||||
<el-checkbox
|
||||
v-model="paramModel.allowPort"
|
||||
:label="$t('app.allowPort')"
|
||||
size="large"
|
||||
@change="changeAllowPort"
|
||||
/>
|
||||
<span class="input-help">{{ $t('app.allowPortHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('app.specifyIP')" v-if="paramModel.allowPort" prop="specifyIP">
|
||||
<el-input v-model="paramModel.specifyIP"></el-input>
|
||||
<span class="input-help">{{ $t('app.specifyIPHelper') }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.restartPolicy')" prop="restartPolicy">
|
||||
<el-select v-model="paramModel.restartPolicy" class="p-w-300">
|
||||
<el-option :label="$t('container.no')" value="no"></el-option>
|
||||
<el-option :label="$t('container.always')" value="always"></el-option>
|
||||
<el-option :label="$t('container.onFailure')" value="on-failure"></el-option>
|
||||
<el-option :label="$t('container.unlessStopped')" value="unless-stopped"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.cpuQuota')" prop="cpuQuota">
|
||||
<el-input type="number" class="!w-2/5" v-model.number="paramModel.cpuQuota" maxlength="5">
|
||||
<template #append>{{ $t('app.cpuCore') }}</template>
|
||||
</el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('container.limitHelper', [limits.cpu]) }}{{ $t('commons.units.core') }}
|
||||
</span>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('container.memoryLimit')" prop="memoryLimit">
|
||||
<el-input class="!w-2/5" v-model.number="paramModel.memoryLimit" maxlength="10">
|
||||
<template #append>
|
||||
<el-select
|
||||
v-model="paramModel.memoryUnit"
|
||||
placeholder="Select"
|
||||
class="!w-[85px]"
|
||||
@change="changeUnit"
|
||||
>
|
||||
<el-option label="MB" value="M" />
|
||||
<el-option label="GB" value="G" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-input>
|
||||
<span class="input-help">
|
||||
{{ $t('container.limitHelper', [limits.memory]) }}{{ paramModel.memoryUnit }}B
|
||||
</span>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="editCompose">
|
||||
<el-checkbox v-model="paramModel.editCompose" :label="$t('app.editCompose')" size="large" />
|
||||
<span class="input-help">{{ $t('app.editComposeHelper') }}</span>
|
||||
</el-form-item>
|
||||
<div v-if="paramModel.editCompose">
|
||||
<el-button @click="openDiff()" type="primary" class="!mb-2">
|
||||
{{ $t('app.showDiff') }}
|
||||
</el-button>
|
||||
<CodemirrorPro v-model="paramModel.dockerCompose" mode="yaml"></CodemirrorPro>
|
||||
</div>
|
||||
</div>
|
||||
</component>
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer v-if="edit">
|
||||
<el-button @click="handleClose" :disabled="loading">{{ $t('commons.button.cancel') }}</el-button>
|
||||
<el-button type="primary" :disabled="loading" @click="submit(paramForm)">
|
||||
<el-button type="primary" :disabled="loading || gpuRef?.loading" @click="submit(paramForm)">
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
@@ -188,12 +234,14 @@
|
||||
</DrawerPro>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { expandInvalidFormSection } from '@/views/ai/model/components/form-section';
|
||||
import AccountAddress from '@/views/ai/model/components/account-address/index.vue';
|
||||
import GPUConfig from '@/views/ai/model/components/gpu-config/index.vue';
|
||||
import Diff from '@/views/app-store/installed/upgrade/diff/index.vue';
|
||||
|
||||
import { App } from '@/api/interface/app';
|
||||
import { getAppInstallParams, updateAppInstallParams, updateInstallConfig } from '@/api/modules/app';
|
||||
import { onBeforeUnmount, reactive, ref } from 'vue';
|
||||
import { computed, nextTick, onBeforeUnmount, reactive, ref } from 'vue';
|
||||
import { FormInstance } from 'element-plus';
|
||||
import { Rules, checkNumberRange } from '@/global/form-rules';
|
||||
import { MsgError, MsgSuccess } from '@/utils/message';
|
||||
@@ -218,8 +266,9 @@ interface EditForm extends App.InstallParams {
|
||||
default: any;
|
||||
}
|
||||
|
||||
const props = defineProps({ modelGPU: Boolean });
|
||||
const emit = defineEmits(['close']);
|
||||
const { currentNode } = useGlobalStore();
|
||||
const { currentNode, isMobile } = useGlobalStore();
|
||||
const open = ref(false);
|
||||
const loading = ref(false);
|
||||
const params = ref<EditForm[]>();
|
||||
@@ -228,6 +277,16 @@ const paramForm = ref<FormInstance>();
|
||||
const paramModel = reactive<any>({
|
||||
params: {},
|
||||
});
|
||||
const advancedFields = [
|
||||
'memoryLimit',
|
||||
'cpuQuota',
|
||||
'memoryUnit',
|
||||
'allowPort',
|
||||
'specifyIP',
|
||||
'containerName',
|
||||
'restartPolicy',
|
||||
];
|
||||
let initialAdvancedConfig: Record<string, unknown> = {};
|
||||
const rules = reactive({
|
||||
params: {},
|
||||
cpuQuota: [Rules.requiredInput, checkNumberRange(0, 999)],
|
||||
@@ -237,7 +296,9 @@ const rules = reactive({
|
||||
});
|
||||
const appType = ref('');
|
||||
const appKey = ref('');
|
||||
const showModelGPU = computed(() => props.modelGPU && appKey.value === 'ollama');
|
||||
const accountRef = ref<InstanceType<typeof AccountAddress>>();
|
||||
const gpuRef = ref<InstanceType<typeof GPUConfig>>();
|
||||
const appConfigUpdate = ref<App.AppConfigUpdate>({
|
||||
installID: 0,
|
||||
webUI: '',
|
||||
@@ -305,19 +366,24 @@ const acceptParams = async (props: ParamProps) => {
|
||||
if (!(await get())) return;
|
||||
open.value = true;
|
||||
openConfig.value = false;
|
||||
if (props.edit) editParam();
|
||||
if (props.edit) await editParam();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
emit('close');
|
||||
open.value = false;
|
||||
};
|
||||
const editParam = () => {
|
||||
const editParam = async () => {
|
||||
params.value.forEach((param: EditForm) => {
|
||||
paramModel.params[param.key] = param.value;
|
||||
});
|
||||
|
||||
edit.value = !edit.value;
|
||||
loadLimit();
|
||||
if (edit.value && showModelGPU.value) {
|
||||
await nextTick();
|
||||
await gpuRef.value?.init(false);
|
||||
}
|
||||
};
|
||||
|
||||
const changeAllowPort = () => {
|
||||
@@ -386,11 +452,21 @@ const get = async () => {
|
||||
}
|
||||
appType.value = res.data.type;
|
||||
appKey.value = res.data.appKey;
|
||||
if (showModelGPU.value) {
|
||||
paramModel.gpu = res.data.gpu || {
|
||||
enabled: false,
|
||||
mode: 'devices',
|
||||
deviceIDs: [],
|
||||
capabilities: ['gpu', 'utility', 'compute'],
|
||||
error: i18n.global.t('aiTools.model.loadFailed'),
|
||||
};
|
||||
}
|
||||
paramModel.name = res.data.name;
|
||||
paramModel.httpPort = res.data.httpPort;
|
||||
paramModel.syncModelAccount = res.data.syncModelAccount;
|
||||
paramModel.modelAccountBaseURLType = res.data.modelAccountBaseURLType || 'container';
|
||||
paramModel.modelAccountBaseURL = res.data.modelAccountBaseURL;
|
||||
initialAdvancedConfig = Object.fromEntries(advancedFields.map((key) => [key, paramModel[key]]));
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
@@ -400,13 +476,11 @@ const get = async () => {
|
||||
};
|
||||
|
||||
const submit = async (formEl: FormInstance) => {
|
||||
if (!formEl) return;
|
||||
if (!formEl || loading.value || gpuRef.value?.loading) return;
|
||||
const node = currentNode.value;
|
||||
const installId = paramData.value.id;
|
||||
await formEl.validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
if (!valid) return;
|
||||
ElMessageBox.confirm(i18n.global.t('app.updateWarn'), i18n.global.t('commons.button.update'), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
@@ -414,24 +488,26 @@ const submit = async (formEl: FormInstance) => {
|
||||
}).then(async () => {
|
||||
if (!open.value || node !== currentNode.value || installId !== paramData.value.id) return;
|
||||
const submitModel: Record<string, any> = { installId, params: paramModel.params };
|
||||
if (showModelGPU.value) submitModel.gpu = paramModel.gpu;
|
||||
if (appKey.value === 'ollama') {
|
||||
submitModel.syncModelAccount = paramModel.syncModelAccount;
|
||||
submitModel.modelAccountBaseURLType = paramModel.modelAccountBaseURLType;
|
||||
submitModel.modelAccountBaseURL = accountRef.value.baseURL;
|
||||
}
|
||||
if (paramModel.advanced) {
|
||||
submitModel.advanced = paramModel.advanced;
|
||||
submitModel.memoryLimit = paramModel.memoryLimit;
|
||||
submitModel.cpuQuota = paramModel.cpuQuota;
|
||||
submitModel.memoryUnit = paramModel.memoryUnit;
|
||||
submitModel.allowPort = paramModel.allowPort;
|
||||
submitModel.specifyIP = paramModel.specifyIP;
|
||||
submitModel.containerName = paramModel.containerName;
|
||||
const updateAdvanced =
|
||||
appKey.value === 'ollama'
|
||||
? paramModel.editCompose ||
|
||||
advancedFields.some((key) => paramModel[key] !== initialAdvancedConfig[key])
|
||||
: paramModel.advanced;
|
||||
if (updateAdvanced) {
|
||||
submitModel.advanced = true;
|
||||
for (const key of advancedFields) {
|
||||
submitModel[key] = paramModel[key];
|
||||
}
|
||||
if (paramModel.editCompose) {
|
||||
submitModel.editCompose = paramModel.editCompose;
|
||||
submitModel.dockerCompose = paramModel.dockerCompose;
|
||||
}
|
||||
submitModel.restartPolicy = paramModel.restartPolicy;
|
||||
}
|
||||
try {
|
||||
loading.value = true;
|
||||
@@ -505,6 +581,7 @@ onBeforeUnmount(() => {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@use '@/views/ai/model/components/form-section.scss';
|
||||
.change-button {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user