mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-30 16:05:30 +00:00
670 lines
21 KiB
Go
670 lines
21 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
|
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/accelerator"
|
|
"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{}}
|
|
ok, client := accelerator.New()
|
|
if !ok {
|
|
return result
|
|
}
|
|
snapshot, err := client.Collect(context.Background())
|
|
if err != nil {
|
|
result.Warnings = append(result.Warnings, err.Error())
|
|
return result
|
|
}
|
|
result.Warnings = append(result.Warnings, snapshot.Info.Warnings...)
|
|
for _, monitored := range snapshot.Devices {
|
|
if monitored.ParentID != "" {
|
|
continue
|
|
}
|
|
device := dto.ModelGPUDevice{Available: true}
|
|
devicePath := ""
|
|
switch {
|
|
case monitored.GPU != nil:
|
|
if monitored.Vendor == "nvidia" && !re.GetRegex(re.NvidiaGPUUUIDPattern).MatchString(monitored.GPU.UUID) {
|
|
result.Warnings = append(result.Warnings, fmt.Sprintf("invalid NVIDIA GPU UUID from nvidia-smi: %s", monitored.GPU.UUID))
|
|
continue
|
|
}
|
|
device.ID = monitored.ID
|
|
device.Name = fmt.Sprintf("GPU %d · %s", monitored.Index, monitored.Name)
|
|
device.Type = monitored.Vendor
|
|
device.UUID = monitored.GPU.UUID
|
|
case monitored.XPU != nil:
|
|
devicePath = monitored.XPU.Basic.DrmDevice
|
|
if !re.GetRegex(re.IntelGPUDevicePathPattern).MatchString(devicePath) {
|
|
directory := filepath.Join("/sys/bus/pci/devices", filepath.Base(monitored.BusID), "drm")
|
|
entries, err := os.ReadDir(directory)
|
|
if err != nil {
|
|
result.Warnings = append(result.Warnings, fmt.Sprintf("resolve Intel GPU %d render device: %v", monitored.Index, err))
|
|
continue
|
|
}
|
|
devicePath = ""
|
|
for _, entry := range entries {
|
|
if re.GetRegex(re.IntelGPURenderNamePattern).MatchString(entry.Name()) {
|
|
devicePath = filepath.Join("/dev/dri", entry.Name())
|
|
break
|
|
}
|
|
}
|
|
if devicePath == "" {
|
|
result.Warnings = append(result.Warnings, fmt.Sprintf("Intel GPU %d has no render device", monitored.Index))
|
|
continue
|
|
}
|
|
}
|
|
device.ID = "intel:" + devicePath
|
|
device.Name = fmt.Sprintf("XPU %d · %s", monitored.Index, monitored.Name)
|
|
device.Type = "intel"
|
|
case monitored.NPU != nil:
|
|
device.ID = fmt.Sprintf("ascend:%d", monitored.Index)
|
|
device.Name = fmt.Sprintf("NPU %d · Chip %d · %s", monitored.NPUIndex, monitored.ChipIndex, monitored.Name)
|
|
device.Type = "ascend"
|
|
devicePath = fmt.Sprintf("/dev/davinci%d", monitored.Index)
|
|
default:
|
|
continue
|
|
}
|
|
if devicePath != "" {
|
|
info, err := os.Stat(devicePath)
|
|
device.Available = err == nil && info.Mode()&os.ModeCharDevice != 0
|
|
if err != nil {
|
|
result.Warnings = append(result.Warnings, err.Error())
|
|
}
|
|
}
|
|
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{}
|
|
entries, err := os.ReadDir("/dev")
|
|
if err != nil {
|
|
return ids
|
|
}
|
|
for _, entry := range entries {
|
|
if !re.GetRegex(re.AscendGPUDeviceNamePattern).MatchString(entry.Name()) {
|
|
continue
|
|
}
|
|
info, err := os.Stat(filepath.Join("/dev", entry.Name()))
|
|
if err == nil && info.Mode()&os.ModeCharDevice != 0 {
|
|
ids = append(ids, strings.TrimPrefix(entry.Name(), "davinci"))
|
|
}
|
|
}
|
|
slices.SortFunc(ids, func(a, b string) int {
|
|
left, _ := strconv.Atoi(a)
|
|
right, _ := strconv.Atoi(b)
|
|
return left - right
|
|
})
|
|
return ids
|
|
}
|