Files
1Panel/agent/utils/ai_tools/gpu/ascend.go
ssongliu 17a8835d59 feat: support Ascend 910B GPU monitoring (#13579)
* feat: support Ascend 910B GPU monitoring

* chore: remove GPU test files
2026-08-17 17:38:43 +08:00

165 lines
4.3 KiB
Go

package gpu
import (
"fmt"
"regexp"
"strconv"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
)
var (
ascendVersionPattern = regexp.MustCompile(`(?i)\bVersion:\s*([^\s|]+)`)
ascendMemoryPattern = regexp.MustCompile(`([0-9]+(?:\.[0-9]+)?)\s*/\s*([0-9]+(?:\.[0-9]+)?)`)
)
type AscendSMI struct{}
func (a AscendSMI) LoadGpuInfo() (*common.GpuInfo, error) {
cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Second))
itemData, err := cmdMgr.RunWithStdout("npu-smi", "info")
if err != nil {
return nil, fmt.Errorf("calling npu-smi failed, %v", err)
}
return parseAscendSMI(itemData), nil
}
func parseAscendSMI(data string) *common.GpuInfo {
info := &common.GpuInfo{Type: "ascend"}
if match := ascendVersionPattern.FindStringSubmatch(data); len(match) == 2 {
info.DriverVersion = match[1]
}
processSection := false
deviceIndexes := make(map[uint]int)
var pending *common.GPU
for _, line := range strings.Split(data, "\n") {
if strings.Contains(line, "Process id") && strings.Contains(line, "Process memory") {
processSection = true
pending = nil
continue
}
cells := ascendTableCells(line)
if processSection {
if len(cells) != 4 && len(cells) != 5 {
continue
}
deviceFields := strings.Fields(cells[0])
if len(deviceFields) == 0 {
continue
}
deviceID, err := strconv.ParseUint(deviceFields[0], 10, 64)
if err != nil {
continue
}
index, ok := deviceIndexes[uint(deviceID)]
if !ok {
continue
}
processOffset := len(cells) - 3
info.GPUs[index].Processes = append(info.GPUs[index].Processes, common.Process{
Pid: strings.TrimSpace(cells[processOffset]),
Type: "NPU",
ProcessName: strings.TrimSpace(cells[processOffset+1]),
UsedMemory: ascendValueWithUnit(cells[processOffset+2], "MB"),
})
continue
}
if len(cells) != 3 {
continue
}
if pending == nil {
fields := strings.Fields(cells[0])
if len(fields) < 2 {
continue
}
deviceID, err := strconv.ParseUint(fields[0], 10, 64)
if err != nil {
continue
}
metrics := strings.Fields(cells[2])
if len(metrics) < 2 {
continue
}
pending = &common.GPU{
Type: "ascend",
Index: uint(deviceID),
ProductName: strings.Join(fields[1:], " "),
PersistenceMode: "N/A",
DisplayActive: "N/A",
ECC: "N/A",
FanSpeed: "N/A",
Temperature: ascendValueWithUnit(metrics[1], "C"),
PerformanceState: strings.TrimSpace(cells[1]),
PowerDraw: ascendValueWithUnit(metrics[0], "W"),
MaxPowerLimit: "N/A",
ComputeMode: "N/A",
MigMode: "N/A",
}
continue
}
metrics := strings.Fields(cells[2])
if len(metrics) == 0 {
pending = nil
continue
}
pending.BusID = strings.TrimSpace(cells[1])
pending.GPUUtil = ascendValueWithUnit(metrics[0], "%")
pending.MemUsed, pending.MemTotal = ascendMemoryUsage(cells[2])
deviceIndexes[pending.Index] = len(info.GPUs)
info.GPUs = append(info.GPUs, *pending)
pending = nil
}
return info
}
func ascendTableCells(line string) []string {
line = strings.TrimSpace(line)
if !strings.HasPrefix(line, "|") || !strings.HasSuffix(line, "|") {
return nil
}
parts := strings.Split(line, "|")
if len(parts) < 3 {
return nil
}
cells := make([]string, 0, len(parts)-2)
for _, part := range parts[1 : len(parts)-1] {
cells = append(cells, strings.TrimSpace(part))
}
return cells
}
func ascendMemoryUsage(value string) (string, string) {
matches := ascendMemoryPattern.FindAllStringSubmatch(value, -1)
if len(matches) == 0 {
return "N/A", "N/A"
}
// 910B exposes both generic memory and HBM. Prefer the last memory pool
// with a non-zero capacity, which selects HBM while retaining compatibility
// with devices that only expose Memory-Usage.
selected := matches[len(matches)-1]
for i := len(matches) - 1; i >= 0; i-- {
if total, err := strconv.ParseFloat(matches[i][2], 64); err == nil && total > 0 {
selected = matches[i]
break
}
}
return selected[1] + " MB", selected[2] + " MB"
}
func ascendValueWithUnit(value, unit string) string {
value = strings.TrimSpace(value)
if value == "" || strings.EqualFold(value, "N/A") || strings.EqualFold(value, "NA") {
return "N/A"
}
return value + " " + unit
}