feat: support GB10 shared memory and refine AI model UI (#14254)

This commit is contained in:
ssongliu
2026-10-30 18:09:51 +08:00
committed by GitHub
parent 2f7bab67b3
commit 02031e4d7c
25 changed files with 330 additions and 106 deletions
+1
View File
@@ -177,6 +177,7 @@ type DiskInfo struct {
}
type GPUInfo struct {
SharedMemory bool `json:"sharedMemory"`
Type string `json:"type"`
Index uint `json:"index"`
NPUIndex uint `json:"npuIndex"`
+12 -11
View File
@@ -56,17 +56,18 @@ type MonitorGPUOptions struct {
Options []string `json:"options"`
}
type GPUChartHide struct {
DeviceID string `json:"deviceID"`
Legacy bool `json:"legacy"`
ProductName string `json:"productName"`
Type string `json:"type"`
Process bool `json:"process"`
GPU bool `json:"gpu"`
Memory bool `json:"memory"`
Power bool `json:"power"`
PowerLimit bool `json:"powerLimit"`
Temperature bool `json:"temperature"`
Speed bool `json:"speed"`
SharedMemory bool `json:"sharedMemory"`
DeviceID string `json:"deviceID"`
Legacy bool `json:"legacy"`
ProductName string `json:"productName"`
Type string `json:"type"`
Process bool `json:"process"`
GPU bool `json:"gpu"`
Memory bool `json:"memory"`
Power bool `json:"power"`
PowerLimit bool `json:"powerLimit"`
Temperature bool `json:"temperature"`
Speed bool `json:"speed"`
}
type MonitorGPUSearch struct {
Aggregation string `json:"aggregation" validate:"omitempty,oneof=avg max"`
+14 -4
View File
@@ -8,6 +8,7 @@ import (
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"time"
@@ -19,6 +20,7 @@ import (
"github.com/1Panel-dev/1Panel/agent/app/model"
"github.com/1Panel-dev/1Panel/agent/global"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/accelerator"
"github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu"
"github.com/1Panel-dev/1Panel/agent/utils/common"
"github.com/1Panel-dev/1Panel/agent/utils/psutil"
"github.com/robfig/cron/v3"
@@ -205,7 +207,14 @@ func (m *MonitorService) LoadGPUOptions() dto.MonitorGPUOptions {
continue
}
seen[key] = true
data.ChartHide = append(data.ChartHide, dto.GPUChartHide{DeviceID: device.DeviceID, ProductName: device.ProductName, Type: device.DeviceType, Legacy: device.DeviceID == ""})
_, productName, _ := strings.Cut(device.ProductName, " - ")
data.ChartHide = append(data.ChartHide, dto.GPUChartHide{
DeviceID: device.DeviceID,
ProductName: device.ProductName,
Type: device.DeviceType,
Legacy: device.DeviceID == "",
SharedMemory: strings.HasPrefix(device.DeviceID, "nvidia:") && gpu.UsesSystemMemory(productName),
})
data.Options = append(data.Options, device.ProductName)
}
return data
@@ -800,9 +809,10 @@ func loadGPUOptions(snapshot *accelerator.Snapshot) dto.MonitorGPUOptions {
})
for _, item := range snapshot.Devices {
chartHide := dto.GPUChartHide{
DeviceID: item.ID,
ProductName: item.Label,
Type: string(item.Kind),
SharedMemory: item.GPU != nil && item.GPU.SharedMemory,
DeviceID: item.ID,
ProductName: item.Label,
Type: string(item.Kind),
}
data.ChartHide = append(data.ChartHide, chartHide)
data.Options = append(data.Options, chartHide.ProductName)
+34 -1
View File
@@ -4,10 +4,12 @@ import (
"context"
"encoding/xml"
"fmt"
"slices"
"strings"
"time"
"github.com/1Panel-dev/1Panel/agent/utils/cmd"
"github.com/shirou/gopsutil/v4/mem"
)
const nvidiaSMICommand = "nvidia-smi"
@@ -20,7 +22,31 @@ func (n nvidiaSMI) LoadInfo(ctx context.Context) (*Info, error) {
if err != nil {
return nil, fmt.Errorf("calling %s failed: %w", nvidiaSMICommand, err)
}
return parseNvidiaSMI([]byte(itemData))
info, err := parseNvidiaSMI([]byte(itemData))
if err != nil {
return nil, err
}
if slices.ContainsFunc(info.Devices, func(device Device) bool { return device.SharedMemory }) {
memory, err := mem.VirtualMemoryWithContext(ctx)
if err != nil {
info.Warnings = append(info.Warnings, fmt.Sprintf("read system shared memory: %v", err))
} else {
for index := range info.Devices {
device := &info.Devices[index]
if !device.SharedMemory {
continue
}
device.MemUsed = fmt.Sprintf("%d B", memory.Used)
device.MemTotal = fmt.Sprintf("%d B", memory.Total)
device.MemoryFree = fmt.Sprintf("%.2f MiB", float64(memory.Available)/(1024*1024))
}
}
}
return info, nil
}
func UsesSystemMemory(productName string) bool {
return strings.EqualFold(strings.TrimSpace(productName), "NVIDIA GB10")
}
func parseNvidiaSMI(buf []byte) (*Info, error) {
@@ -38,6 +64,7 @@ func parseNvidiaSMI(buf []byte) (*Info, error) {
for i := range s.Gpu {
gpuItem := Device{
Type: "nvidia",
SharedMemory: UsesSystemMemory(s.Gpu[i].ProductName),
MemoryActivity: s.Gpu[i].Utilization.MemoryUtil,
EncoderUtil: s.Gpu[i].Utilization.EncoderUtil,
DecoderUtil: s.Gpu[i].Utilization.DecoderUtil,
@@ -72,6 +99,12 @@ func parseNvidiaSMI(buf []byte) (*Info, error) {
ComputeMode: s.Gpu[i].ComputeMode,
MigMode: s.Gpu[i].MigMode.CurrentMig,
}
if gpuItem.SharedMemory {
gpuItem.MemUsed = "N/A"
gpuItem.MemTotal = "N/A"
gpuItem.MemoryFree = "N/A"
gpuItem.MemoryReserved = "N/A"
}
gpuItem.ECCPending = s.Gpu[i].EccMode.PendingEcc
gpuItem.ECCErrors = []ECCError{
{Scope: "Volatile Total", Correctable: s.Gpu[i].EccErrors.Volatile.SingleBit.Total, Uncorrectable: s.Gpu[i].EccErrors.Volatile.DoubleBit.Total},
+1
View File
@@ -17,6 +17,7 @@ type ECCError struct {
}
type Device struct {
SharedMemory bool `json:"sharedMemory"`
ECCPending string `json:"eccPending"`
ECCErrors []ECCError `json:"eccErrors"`
MemoryActivity string `json:"memoryActivity"`
+2
View File
@@ -49,6 +49,7 @@ export namespace AI {
xpu: XpuInfo['xpu'];
}
export interface GPU {
sharedMemory?: boolean;
eccPending?: string;
eccErrors?: { scope: string; correctable: string; uncorrectable: string }[];
memoryActivity?: string;
@@ -139,6 +140,7 @@ export namespace AI {
chartHide: Array<ChartHide>;
}
export interface ChartHide {
sharedMemory?: boolean;
deviceID?: string;
legacy?: boolean;
productName: string;
+1
View File
@@ -151,6 +151,7 @@ export namespace Dashboard {
inodesUsedPercent: number;
}
export interface GPUInfo {
sharedMemory?: boolean;
type: string;
index: number;
npuIndex: number;
+7 -3
View File
@@ -1071,11 +1071,11 @@ const message = {
onlineModel: 'Online models',
instanceDeployment: 'Deploy by instance',
nvidiaDeployment: 'NVIDIA-optimized deployment',
engines: 'Inference engines',
engines: 'Local inference engines',
modelManagement: 'Model management',
serviceDetails: 'Service details',
deployments: 'Local deployments',
files: 'Model files',
files: 'Model hub',
connectExternal: 'Connect external service',
deploymentTemplates: 'vLLM startup command templates',
engineProvider: 'Engine / Provider',
@@ -1643,6 +1643,11 @@ const message = {
timePerOutputTokenChart: 'Inter-token latency',
},
gpu: {
systemMemory: 'Shared system memory',
systemMemoryUsed: 'System memory used',
systemMemoryTotal: 'Total system memory',
systemMemoryAvailable: 'Available system memory',
systemMemoryHelper: 'Shared by the CPU and GPU. Shows system-wide memory usage, not GPU-only usage.',
memoryActivity: 'Memory read/write activity',
hotspotTemperature: 'Hotspot temperature',
fanRPM: 'Fan speed (RPM)',
@@ -1678,7 +1683,6 @@ const message = {
historyPeak: 'Peak',
historyAggregated:
'{count} samples aggregated into {seconds}-second intervals; processes show counts only.',
historyRaw: '{count} original samples.',
historyMetricEmpty: 'No data for this metric in the selected time range',
historyLoadFailed: 'Failed to load history. Please retry.',
collectedAt: 'Collected at',
+8 -3
View File
@@ -1073,11 +1073,11 @@ const message = {
onlineModel: 'Modelos en línea',
instanceDeployment: 'Despliegue por instancia',
nvidiaDeployment: 'Despliegue optimizado para NVIDIA',
engines: 'Motores de inferencia',
engines: 'Motores de inferencia locales',
modelManagement: 'Gestión de modelos',
serviceDetails: 'Detalles del servicio',
deployments: 'Despliegues locales',
files: 'Archivos de modelos',
files: 'Catálogo de modelos',
connectExternal: 'Conectar servicio externo',
deploymentTemplates: 'Plantillas de comandos de inicio de vLLM',
engineProvider: 'Motor / Proveedor',
@@ -1653,6 +1653,12 @@ const message = {
timePerOutputTokenChart: 'Latencia entre tokens',
},
gpu: {
systemMemory: 'Memoria compartida del sistema',
systemMemoryUsed: 'Memoria del sistema utilizada',
systemMemoryTotal: 'Memoria total del sistema',
systemMemoryAvailable: 'Memoria disponible del sistema',
systemMemoryHelper:
'Compartida por la CPU y la GPU. Muestra el uso de memoria de todo el sistema, no solo de la GPU.',
memoryActivity: 'Actividad de lectura/escritura de memoria',
hotspotTemperature: 'Temperatura del punto caliente',
fanRPM: 'Velocidad del ventilador (RPM)',
@@ -1688,7 +1694,6 @@ const message = {
historyPeak: 'Máximo',
historyAggregated:
'{count} muestras agrupadas en intervalos de {seconds} segundos; los procesos solo muestran cantidades.',
historyRaw: '{count} muestras originales.',
historyMetricEmpty: 'No hay datos de esta métrica en el período seleccionado',
historyLoadFailed: 'No se pudo cargar el historial. Inténtelo de nuevo.',
collectedAt: 'Fecha de recopilación',
+7 -3
View File
@@ -1055,11 +1055,11 @@ const message = {
onlineModel: 'مدل‌های آنلاین',
instanceDeployment: 'استقرار بر اساس نمونه',
nvidiaDeployment: 'استقرار بهینه‌شده برای NVIDIA',
engines: 'موتورهای استنتاج',
engines: 'موتورهای استنتاج محلی',
modelManagement: 'مدیریت مدل‌ها',
serviceDetails: 'جزئیات سرویس',
deployments: 'استقرارهای محلی',
files: 'فایل‌های مدل',
files: 'مرکز مدل‌ها',
connectExternal: 'اتصال به سرویس خارجی',
deploymentTemplates: 'الگوهای فرمان راه‌اندازی vLLM',
engineProvider: 'موتور / ارائه‌دهنده',
@@ -1625,6 +1625,11 @@ const message = {
timePerOutputTokenChart: 'تأخیر بین توکن‌ها',
},
gpu: {
systemMemory: 'حافظهٔ مشترک سیستم',
systemMemoryUsed: 'حافظهٔ مصرف‌شدهٔ سیستم',
systemMemoryTotal: 'کل حافظهٔ سیستم',
systemMemoryAvailable: 'حافظهٔ در دسترس سیستم',
systemMemoryHelper: 'بین CPU و GPU مشترک است. مصرف حافظهٔ کل سیستم را نشان می‌دهد، نه فقط مصرف GPU.',
memoryActivity: 'فعالیت خواندن/نوشتن حافظه',
hotspotTemperature: 'دمای نقطه داغ',
fanRPM: 'سرعت فن (RPM)',
@@ -1660,7 +1665,6 @@ const message = {
historyPeak: 'بیشینه',
historyAggregated:
'{count} نمونه در بازه‌های {seconds} ثانیه‌ای تجمیع شده‌اند؛ فقط تعداد فرایندها نمایش داده می‌شود.',
historyRaw: '{count} نمونه اصلی.',
historyMetricEmpty: 'در بازه زمانی انتخاب‌شده داده‌ای برای این شاخص وجود ندارد',
historyLoadFailed: 'بارگیری سابقه ناموفق بود. دوباره تلاش کنید.',
collectedAt: 'زمان گردآوری',
+7 -3
View File
@@ -1059,11 +1059,11 @@ const message = {
onlineModel: 'オンラインモデル',
instanceDeployment: 'インスタンス単位でデプロイ',
nvidiaDeployment: 'NVIDIA 向け最適化デプロイ',
engines: '推論エンジン',
engines: 'ローカル推論エンジン',
modelManagement: 'モデル管理',
serviceDetails: 'サービス詳細',
deployments: 'ローカルデプロイ',
files: 'モデルファイル',
files: 'モデル広場',
connectExternal: '外部サービスに接続',
deploymentTemplates: 'vLLM 起動コマンドテンプレート',
engineProvider: 'エンジン / プロバイダー',
@@ -1629,6 +1629,11 @@ const message = {
timePerOutputTokenChart: 'トークン間遅延',
},
gpu: {
systemMemory: '共有システムメモリ',
systemMemoryUsed: 'システムメモリ使用量',
systemMemoryTotal: 'システムメモリ合計',
systemMemoryAvailable: '利用可能なシステムメモリ',
systemMemoryHelper: 'CPU と GPU で共有します。GPU 単体ではなく、システム全体のメモリ使用量を表示します。',
memoryActivity: 'メモリ読み書き稼働率',
hotspotTemperature: 'ホットスポット温度',
fanRPM: 'ファン回転数(RPM)',
@@ -1663,7 +1668,6 @@ const message = {
historyAverage: '平均値',
historyPeak: '最大値',
historyAggregated: '{count} 件のサンプルを {seconds} 秒間隔で集計。プロセスは件数のみ表示します。',
historyRaw: '元のサンプル {count} 件。',
historyMetricEmpty: '選択した期間にこの指標のデータはありません',
historyLoadFailed: '履歴の読み込みに失敗しました。再試行してください。',
collectedAt: '収集日時',
+7 -3
View File
@@ -1049,11 +1049,11 @@ const message = {
onlineModel: '온라인 모델',
instanceDeployment: '인스턴스별 배포',
nvidiaDeployment: 'NVIDIA 최적화 배포',
engines: '추론 엔진',
engines: '로컬 추론 엔진',
modelManagement: '모델 관리',
serviceDetails: '서비스 상세 정보',
deployments: '로컬 배포',
files: '모델 파일',
files: '모델 허브',
connectExternal: '외부 서비스 연결',
deploymentTemplates: 'vLLM 시작 명령 템플릿',
engineProvider: '엔진 / 공급자',
@@ -1614,6 +1614,11 @@ const message = {
timePerOutputTokenChart: '토큰 간 지연',
},
gpu: {
systemMemory: '공유 시스템 메모리',
systemMemoryUsed: '사용 중인 시스템 메모리',
systemMemoryTotal: '전체 시스템 메모리',
systemMemoryAvailable: '사용 가능한 시스템 메모리',
systemMemoryHelper: 'CPU와 GPU가 공유합니다. GPU만의 사용량이 아닌 시스템 전체 메모리 사용량을 표시합니다.',
memoryActivity: '메모리 읽기/쓰기 활성률',
hotspotTemperature: '핫스폿 온도',
fanRPM: '팬 회전 속도 (RPM)',
@@ -1648,7 +1653,6 @@ const message = {
historyAverage: '평균',
historyPeak: '최댓값',
historyAggregated: '샘플 {count}개를 {seconds}초 간격으로 집계합니다. 프로세스는 개수만 표시합니다.',
historyRaw: '원본 샘플 {count}개.',
historyMetricEmpty: '선택한 기간에 이 지표의 데이터가 없습니다',
historyLoadFailed: '기록을 불러오지 못했습니다. 다시 시도하세요.',
collectedAt: '수집 시간',
+7 -3
View File
@@ -1046,11 +1046,11 @@ const message = {
onlineModel: 'ໂມເດວອອນລາຍ',
instanceDeployment: 'ນຳໃຊ້ຕາມອິນສະແຕນ',
nvidiaDeployment: 'ການນຳໃຊ້ທີ່ປັບໃຫ້ເໝາະກັບ NVIDIA',
engines: 'ເຄື່ອງຈັກອະນຸມານ',
engines: 'ເຄື່ອງຈັກອະນຸມານພາຍໃນ',
modelManagement: 'ຈັດການໂມເດວ',
serviceDetails: 'ລາຍລະອຽດບໍລິການ',
deployments: 'ການນຳໃຊ້ພາຍໃນເຄື່ອງ',
files: 'ໄຟລ໌ໂມເດວ',
files: 'ສູນລວມໂມເດວ',
connectExternal: 'ເຊື່ອມຕໍ່ບໍລິການພາຍນອກ',
deploymentTemplates: 'ແມ່ແບບຄຳສັ່ງເລີ່ມຕົ້ນ vLLM',
engineProvider: 'ເຄື່ອງຈັກ / ຜູ້ໃຫ້ບໍລິການ',
@@ -1606,6 +1606,11 @@ const message = {
timePerOutputTokenChart: 'ເວລາລໍຖ້າລະຫວ່າງ Token',
},
gpu: {
systemMemory: 'ໜ່ວຍຄວາມຈຳລະບົບທີ່ໃຊ້ຮ່ວມກັນ',
systemMemoryUsed: 'ໜ່ວຍຄວາມຈຳລະບົບທີ່ໃຊ້ແລ້ວ',
systemMemoryTotal: 'ໜ່ວຍຄວາມຈຳລະບົບທັງໝົດ',
systemMemoryAvailable: 'ໜ່ວຍຄວາມຈຳລະບົບທີ່ພ້ອມໃຊ້',
systemMemoryHelper: 'CPU ແລະ GPU ໃຊ້ຮ່ວມກັນ. ສະແດງການໃຊ້ໜ່ວຍຄວາມຈຳຂອງທັງລະບົບ ບໍ່ແມ່ນສະເພາະ GPU.',
memoryActivity: 'ກິດຈະກຳອ່ານ/ຂຽນໜ່ວຍຄວາມຈຳ',
hotspotTemperature: 'ອຸນຫະພູມຈຸດຮ້ອນ',
fanRPM: 'ຄວາມໄວພັດລົມ (RPM)',
@@ -1640,7 +1645,6 @@ const message = {
historyAverage: 'ຄ່າສະເລ່ຍ',
historyPeak: 'ຄ່າສູງສຸດ',
historyAggregated: 'ລວມ {count} ຕົວຢ່າງເປັນຊ່ວງ {seconds} ວິນາທີ; ສະແດງພຽງຈຳນວນໂປຣເຊສ.',
historyRaw: '{count} ຕົວຢ່າງຕົ້ນສະບັບ.',
historyMetricEmpty: 'ບໍ່ມີຂໍ້ມູນຕົວຊີ້ວັດນີ້ໃນຊ່ວງເວລາທີ່ເລືອກ',
historyLoadFailed: 'ໂຫຼດປະຫວັດບໍ່ສຳເລັດ. ກະລຸນາລອງໃໝ່.',
collectedAt: 'ເວລາເກັບຂໍ້ມູນ',
+8 -3
View File
@@ -1081,11 +1081,11 @@ const message = {
onlineModel: 'Model dalam talian',
instanceDeployment: 'Pengerahan mengikut instans',
nvidiaDeployment: 'Pengerahan dioptimumkan untuk NVIDIA',
engines: 'Enjin inferens',
engines: 'Enjin inferens setempat',
modelManagement: 'Pengurusan model',
serviceDetails: 'Butiran perkhidmatan',
deployments: 'Pengerahan setempat',
files: 'Fail model',
files: 'Hab model',
connectExternal: 'Sambungkan perkhidmatan luaran',
deploymentTemplates: 'Templat arahan permulaan vLLM',
engineProvider: 'Enjin / Pembekal',
@@ -1655,6 +1655,12 @@ const message = {
timePerOutputTokenChart: 'Kependaman antara token',
},
gpu: {
systemMemory: 'Memori sistem dikongsi',
systemMemoryUsed: 'Memori sistem digunakan',
systemMemoryTotal: 'Jumlah memori sistem',
systemMemoryAvailable: 'Memori sistem tersedia',
systemMemoryHelper:
'Dikongsi oleh CPU dan GPU. Memaparkan penggunaan memori seluruh sistem, bukan GPU sahaja.',
memoryActivity: 'Aktiviti baca/tulis memori',
hotspotTemperature: 'Suhu titik panas',
fanRPM: 'Kelajuan kipas (RPM)',
@@ -1690,7 +1696,6 @@ const message = {
historyPeak: 'Puncak',
historyAggregated:
'{count} sampel diagregatkan dalam sela {seconds} saat; proses hanya memaparkan bilangan.',
historyRaw: '{count} sampel asal.',
historyMetricEmpty: 'Tiada data untuk metrik ini dalam julat masa yang dipilih',
historyLoadFailed: 'Gagal memuatkan sejarah. Sila cuba lagi.',
collectedAt: 'Masa pengumpulan',
+8 -3
View File
@@ -1077,11 +1077,11 @@ const message = {
onlineModel: 'Modelos online',
instanceDeployment: 'Implantação por instância',
nvidiaDeployment: 'Implantação otimizada para NVIDIA',
engines: 'Mecanismos de inferência',
engines: 'Mecanismos de inferência locais',
modelManagement: 'Gerenciamento de modelos',
serviceDetails: 'Detalhes do serviço',
deployments: 'Implantações locais',
files: 'Arquivos de modelos',
files: 'Central de modelos',
connectExternal: 'Conectar serviço externo',
deploymentTemplates: 'Modelos de comandos de inicialização do vLLM',
engineProvider: 'Mecanismo / Provedor',
@@ -1654,6 +1654,12 @@ const message = {
timePerOutputTokenChart: 'Latência entre tokens',
},
gpu: {
systemMemory: 'Memória compartilhada do sistema',
systemMemoryUsed: 'Memória do sistema em uso',
systemMemoryTotal: 'Memória total do sistema',
systemMemoryAvailable: 'Memória disponível do sistema',
systemMemoryHelper:
'Compartilhada pela CPU e GPU. Exibe o uso de memória de todo o sistema, não apenas da GPU.',
memoryActivity: 'Atividade de leitura/escrita da memória',
hotspotTemperature: 'Temperatura do ponto quente',
fanRPM: 'Rotação da ventoinha (RPM)',
@@ -1689,7 +1695,6 @@ const message = {
historyPeak: 'Pico',
historyAggregated:
'{count} amostras agregadas em intervalos de {seconds} segundos; processos exibem apenas contagens.',
historyRaw: '{count} amostras originais.',
historyMetricEmpty: 'Sem dados desta métrica no período selecionado',
historyLoadFailed: 'Falha ao carregar o histórico. Tente novamente.',
collectedAt: 'Data da coleta',
+8 -3
View File
@@ -1070,11 +1070,11 @@ const message = {
onlineModel: 'Онлайн-модели',
instanceDeployment: 'Развертывание по экземплярам',
nvidiaDeployment: 'Развертывание с оптимизацией для NVIDIA',
engines: 'Движки инференса',
engines: 'Локальные движки инференса',
modelManagement: 'Управление моделями',
serviceDetails: 'Сведения о сервисе',
deployments: 'Локальные развертывания',
files: 'Файлы моделей',
files: 'Каталог моделей',
connectExternal: 'Подключить внешний сервис',
deploymentTemplates: 'Шаблоны команд запуска vLLM',
engineProvider: 'Движок / Поставщик',
@@ -1644,6 +1644,12 @@ const message = {
timePerOutputTokenChart: 'Задержка между токенами',
},
gpu: {
systemMemory: 'Общая системная память',
systemMemoryUsed: 'Использовано системной памяти',
systemMemoryTotal: 'Всего системной памяти',
systemMemoryAvailable: 'Доступная системная память',
systemMemoryHelper:
'Память совместно используется CPU и GPU. Показано потребление всей системы, а не только GPU.',
memoryActivity: 'Активность чтения/записи памяти',
hotspotTemperature: 'Температура горячей точки',
fanRPM: 'Скорость вентилятора (об/мин)',
@@ -1679,7 +1685,6 @@ const message = {
historyPeak: 'Максимум',
historyAggregated:
'{count} измерений сгруппированы по {seconds} с; для процессов показано только количество.',
historyRaw: 'Исходных измерений: {count}.',
historyMetricEmpty: 'Нет данных этой метрики за выбранный период',
historyLoadFailed: 'Не удалось загрузить историю. Повторите попытку.',
collectedAt: 'Время сбора',
+8 -3
View File
@@ -1080,11 +1080,11 @@ const message = {
onlineModel: 'Çevrimiçi modeller',
instanceDeployment: 'Örnek bazında dağıtım',
nvidiaDeployment: 'NVIDIA için optimize edilmiş dağıtım',
engines: 'Çıkarım motorları',
engines: 'Yerel çıkarım motorları',
modelManagement: 'Model yönetimi',
serviceDetails: 'Hizmet ayrıntıları',
deployments: 'Yerel dağıtımlar',
files: 'Model dosyaları',
files: 'Model merkezi',
connectExternal: 'Harici hizmete bağlan',
deploymentTemplates: 'vLLM başlatma komutu şablonları',
engineProvider: 'Motor / Sağlayıcı',
@@ -1652,6 +1652,12 @@ const message = {
timePerOutputTokenChart: 'Token arası gecikme',
},
gpu: {
systemMemory: 'Paylaşılan sistem belleği',
systemMemoryUsed: 'Kullanılan sistem belleği',
systemMemoryTotal: 'Toplam sistem belleği',
systemMemoryAvailable: 'Kullanılabilir sistem belleği',
systemMemoryHelper:
'CPU ve GPU tarafından paylaşılır. Yalnızca GPU kullanımını değil, tüm sistemin bellek kullanımını gösterir.',
memoryActivity: 'Bellek okuma/yazma etkinliği',
hotspotTemperature: 'Sıcak nokta sıcaklığı',
fanRPM: 'Fan hızı (RPM)',
@@ -1687,7 +1693,6 @@ const message = {
historyPeak: 'En yüksek',
historyAggregated:
'{count} örnek {seconds} saniyelik aralıklarla toplandı; işlemler için yalnızca sayılar gösterilir.',
historyRaw: '{count} ham örnek.',
historyMetricEmpty: 'Seçilen zaman aralığında bu metriğe ait veri yok',
historyLoadFailed: 'Geçmiş yüklenemedi. Lütfen tekrar deneyin.',
collectedAt: 'Toplanma zamanı',
+7 -3
View File
@@ -1009,11 +1009,11 @@ const message = {
onlineModel: '線上模型',
instanceDeployment: '按執行個體部署',
nvidiaDeployment: 'NVIDIA 最佳化部署',
engines: '推理引擎',
engines: '本機推理引擎',
modelManagement: '模型管理',
serviceDetails: '服務詳情',
deployments: '本機部署',
files: '模型檔案',
files: '模型廣場',
connectExternal: '接入外部服務',
deploymentTemplates: 'vLLM 啟動命令範本',
engineProvider: '引擎 / 供應商',
@@ -1555,6 +1555,11 @@ const message = {
timePerOutputTokenChart: 'Token 間延遲',
},
gpu: {
systemMemory: '系統共享記憶體',
systemMemoryUsed: '系統已用記憶體',
systemMemoryTotal: '系統總記憶體',
systemMemoryAvailable: '系統可用記憶體',
systemMemoryHelper: 'CPU 與 GPU 共用,顯示整機記憶體使用情況,不代表 GPU 獨占用量。',
memoryActivity: '顯示記憶體讀寫活躍率',
hotspotTemperature: '熱點溫度',
fanRPM: '風扇轉速(RPM)',
@@ -1589,7 +1594,6 @@ const message = {
historyAverage: '平均值',
historyPeak: '峰值',
historyAggregated: '共 {count} 筆取樣,按 {seconds} 秒彙整;程序僅顯示統計數量。',
historyRaw: '共 {count} 筆原始取樣。',
historyMetricEmpty: '所選時間範圍內沒有此指標的資料',
historyLoadFailed: '歷史記錄載入失敗,請重試',
collectedAt: '採集時間',
+7 -3
View File
@@ -1022,11 +1022,11 @@ const message = {
onlineModel: '在线模型',
instanceDeployment: '按实例部署',
nvidiaDeployment: 'NVIDIA 优化部署',
engines: '推理引擎',
engines: '本地推理引擎',
modelManagement: '模型管理',
serviceDetails: '服务详情',
deployments: '本地部署',
files: '模型文件',
files: '模型广场',
connectExternal: '接入外部服务',
deploymentTemplates: 'vLLM 启动命令模板',
engineProvider: '引擎 / 供应商',
@@ -1568,6 +1568,11 @@ const message = {
timePerOutputTokenChart: 'Token 间延迟',
},
gpu: {
systemMemory: '系统共享内存',
systemMemoryUsed: '系统已用内存',
systemMemoryTotal: '系统总内存',
systemMemoryAvailable: '系统可用内存',
systemMemoryHelper: 'CPU 与 GPU 共用,显示整机内存使用情况,不代表 GPU 独占用量。',
memoryActivity: '显存读写活跃率',
hotspotTemperature: '热点温度',
fanRPM: '风扇转速(RPM)',
@@ -1602,7 +1607,6 @@ const message = {
historyAverage: '平均值',
historyPeak: '峰值',
historyAggregated: '共 {count} 条采样,按 {seconds} 秒聚合;进程仅展示统计数量。',
historyRaw: '共 {count} 条原始采样。',
historyMetricEmpty: '所选时间范围内没有该指标的数据',
historyLoadFailed: '历史记录加载失败,请重试',
collectedAt: '采集时间',
+50 -3
View File
@@ -79,6 +79,9 @@
>
{{ $t('commons.button.view') }}
</el-button>
<el-button type="primary" link @click="openHistory(item)">
{{ $t('aiTools.gpu.history') }}
</el-button>
</div>
</div>
</template>
@@ -97,7 +100,21 @@
/>
</div>
<div class="metric-item metric-primary metric-memory">
<span>{{ $t('aiTools.gpu.memoryUsed') }}</span>
<span>
{{
$t(
item.type !== 'ascend' && item.sharedMemory
? 'aiTools.gpu.systemMemory'
: 'aiTools.gpu.memoryUsed',
)
}}
<el-tooltip
v-if="item.type !== 'ascend' && item.sharedMemory"
:content="$t('aiTools.gpu.systemMemoryHelper')"
>
<el-icon><InfoFilled /></el-icon>
</el-tooltip>
</span>
<strong>{{ formatMemory(item.memUsed, item.memTotal) }}</strong>
<el-progress
v-if="memoryPercentage(item) !== null"
@@ -191,6 +208,9 @@
>
{{ $t('commons.button.view') }}
</el-button>
<el-button type="primary" link @click="openHistory(item)">
{{ $t('aiTools.gpu.history') }}
</el-button>
</div>
</div>
</template>
@@ -321,6 +341,8 @@
<script lang="ts" setup>
import { computed, onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import { InfoFilled } from '@element-plus/icons-vue';
import { loadGPUInfo } from '@/api/modules/ai';
import RouterMenu from '@/views/ai/gpu/index.vue';
import { AI } from '@/api/interface/ai';
@@ -328,6 +350,7 @@ import i18n from '@/lang';
import { MsgWarning } from '@/utils/message';
import DeviceDetails, { type DetailSection } from './components/DeviceDetails.vue';
const router = useRouter();
const loading = ref();
const processDrawerVisible = ref(false);
const processDeviceKey = ref('');
@@ -574,10 +597,14 @@ const deviceDetailSections = (item: AcceleratorDevice): DetailSection[] => {
},
{
id: 'memory',
title: t('aiTools.gpu.memory'),
title: t(item.sharedMemory ? 'aiTools.gpu.systemMemory' : 'aiTools.gpu.memory'),
items: [
{ label: t('aiTools.gpu.memoryActivity'), value: item.memoryActivity },
{ label: t('aiTools.gpu.freeMemory'), value: item.memoryFree },
{
label: t(item.sharedMemory ? 'aiTools.gpu.systemMemoryAvailable' : 'aiTools.gpu.freeMemory'),
value: item.memoryFree,
help: item.sharedMemory ? t('aiTools.gpu.systemMemoryHelper') : undefined,
},
{ label: t('aiTools.gpu.memoryReserved'), value: item.memoryReserved },
],
},
@@ -733,6 +760,26 @@ const openXPUProcesses = (deviceID: number) => {
processDrawerVisible.value = true;
};
const stableDeviceID = (vendor: string, identifier: string | undefined, fallback: string) => {
return `${vendor}:${identifier && identifier.toUpperCase() !== 'N/A' ? identifier : fallback}`;
};
const openHistory = (item: AcceleratorDevice | AI.XpuInfo['xpu'][number]) => {
let deviceID: string;
if ('basic' in item) {
deviceID = stableDeviceID(
'xpu',
item.basic.uuid,
stableDeviceID('pci', item.basic.pciBdfAddress, String(item.basic.deviceID)),
);
} else if (item.type === 'ascend') {
deviceID = `${stableDeviceID('ascend', item.busID, String(item.npuIndex))}:${item.chipIndex}`;
} else {
deviceID = stableDeviceID(item.type, item.uuid, stableDeviceID('pci', item.busID, String(item.index)));
}
void router.push({ path: '/ai/gpu/history', query: { deviceID } });
};
const deviceTitle = (item: AcceleratorDevice) => {
return item.type === 'ascend' ? `Chip ${item.chipIndex}` : `GPU ${item.index}`;
};
+75 -40
View File
@@ -4,45 +4,40 @@
<template v-if="supported">
<el-card class="history-toolbar">
<div class="flex flex-wrap gap-3 items-center">
<el-date-picker
v-model="timeRangeGlobal"
type="datetimerange"
style="max-width: 100%; width: 360px; flex-grow: 0"
range-separator="-"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
:clearable="false"
:size="isMobile ? 'small' : 'default'"
@change="changeTimeRange"
/>
<el-select style="max-width: 100%" class="p-w-300" v-model="selectedDevice" @change="search">
<el-select style="max-width: 100%" class="p-w-400" v-model="selectedDevice" @change="search">
<template #prefix>{{ $t('aiTools.gpu.deviceInfo') }}</template>
<el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
<el-radio-group v-if="history && history.bucketSeconds > 0" v-model="aggregation" @change="search">
<el-radio-button value="avg">{{ $t('aiTools.gpu.historyAverage') }}</el-radio-button>
<el-radio-button value="max">{{ $t('aiTools.gpu.historyPeak') }}</el-radio-button>
</el-radio-group>
<TableRefresh @search="refresh" />
<div class="ml-auto flex flex-wrap gap-3 items-center">
<el-date-picker
v-model="timeRangeGlobal"
type="datetimerange"
class="history-time-range"
style="max-width: 100%; width: 400px; flex-grow: 0"
:prefix-icon="timeRangePrefix"
range-separator="-"
:start-placeholder="$t('commons.search.timeStart')"
:end-placeholder="$t('commons.search.timeEnd')"
:shortcuts="shortcuts"
:clearable="false"
@change="changeTimeRange"
/>
<TableRefresh @search="refresh" />
</div>
</div>
<div v-if="history?.sampleCount" class="history-summary">
<div v-if="history?.sampleCount && history.bucketSeconds > 0" class="history-summary">
{{
history.bucketSeconds > 0
? $t('aiTools.gpu.historyAggregated', {
seconds: history.bucketSeconds,
count: history.sampleCount,
})
: $t('aiTools.gpu.historyRaw', { count: history.sampleCount })
$t('aiTools.gpu.historyAggregated', {
seconds: history.bucketSeconds,
count: history.sampleCount,
})
}}
</div>
</el-card>
<el-alert
v-if="currentDevice?.legacy"
:title="$t('aiTools.gpu.legacyHistory')"
type="info"
:closable="false"
show-icon
/>
<el-alert
v-if="failed"
:title="$t('aiTools.gpu.historyLoadFailed')"
@@ -58,7 +53,15 @@
<el-row v-else-if="history?.sampleCount" :gutter="12">
<el-col v-for="chart in charts" :key="chart.id" :xs="24" :md="12">
<el-card class="card-interval">
<template #header>{{ chart.title }}</template>
<template #header>
{{ chart.title }}
<el-tooltip
v-if="chart.id === 'gpu-memory' && currentDevice?.sharedMemory"
:content="$t('aiTools.gpu.systemMemoryHelper')"
>
<el-icon><InfoFilled /></el-icon>
</el-tooltip>
</template>
<v-charts :id="chart.id" height="320px" type="line" :option="chart.option" :dataZoom="true" />
</el-card>
</el-col>
@@ -90,7 +93,9 @@
</template>
<script setup lang="ts">
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
import { ref, computed, watch, onBeforeUnmount, h } from 'vue';
import { useRoute } from 'vue-router';
import { InfoFilled } from '@element-plus/icons-vue';
import { loadGPUMonitor, getGPUOptions } from '@/api/modules/ai';
import RouterMenu from '@/views/ai/gpu/index.vue';
import { shortcuts } from '@/utils/shortcuts';
@@ -98,6 +103,7 @@ import i18n from '@/lang';
import { AI } from '@/api/interface/ai';
import { useGlobalStore } from '@/composables/useGlobalStore';
const route = useRoute();
const { isMobile } = useGlobalStore();
const loading = ref(false);
const failed = ref(false);
@@ -108,6 +114,7 @@ const aggregation = ref<'avg' | 'max'>('avg');
const history = ref<AI.MonitorGPUData>();
const currentDevice = computed(() => options.value.find((item) => item.value === selectedDevice.value));
const timeRangeGlobal = ref<[Date, Date]>([new Date(new Date().setHours(0, 0, 0, 0)), new Date()]);
const timeRangePrefix = () => h('span', i18n.global.t('commons.table.date'));
let followNow = true;
let requestID = 0;
@@ -124,10 +131,18 @@ const charts = computed(() => {
},
{
id: 'gpu-memory',
title: 'memory',
title: currentDevice.value?.sharedMemory ? 'systemMemory' : 'memory',
series: [
{ key: 'memoryUsed', label: 'memoryUsed', unit: 'MiB' },
{ key: 'memoryTotal', label: 'memoryTotal', unit: 'MiB' },
{
key: 'memoryUsed',
label: currentDevice.value?.sharedMemory ? 'systemMemoryUsed' : 'memoryUsed',
unit: 'MiB',
},
{
key: 'memoryTotal',
label: currentDevice.value?.sharedMemory ? 'systemMemoryTotal' : 'memoryTotal',
unit: 'MiB',
},
{ key: 'memoryPercent', label: 'percent', unit: '%', percent: true },
],
},
@@ -338,12 +353,16 @@ const loadOptions = async () => {
const response = await getGPUOptions();
if (id !== requestID) return;
supported.value = response.data.supported;
options.value = (supported.value ? response.data.chartHide || [] : []).map((item) => ({
...item,
value: item.deviceID || `legacy:${item.productName}`,
label: `${item.productName} · ${item.deviceID || i18n.global.t('aiTools.gpu.legacyDevice')}`,
}));
if (!currentDevice.value) selectedDevice.value = options.value[0]?.value || '';
options.value = (supported.value ? response.data.chartHide || [] : [])
.filter((item) => item.deviceID && !item.legacy)
.map((item) => ({
...item,
value: item.deviceID!,
label: item.productName
.replace(/^(\d+) - /, `${item.type === 'xpu' ? 'XPU' : 'GPU'} $1 · `)
.replace(' - ', ' · '),
}));
if (!currentDevice.value && !route.query.deviceID) selectedDevice.value = options.value[0]?.value || '';
await search();
} catch {
if (id === requestID) failed.value = true;
@@ -401,7 +420,15 @@ const loadProcessType = (val: string) => {
return val;
};
onMounted(loadOptions);
watch(
() => [route.path, route.query.deviceID],
() => {
if (route.path !== '/ai/gpu/history') return;
if (typeof route.query.deviceID === 'string') selectedDevice.value = route.query.deviceID;
void loadOptions();
},
{ immediate: true },
);
onBeforeUnmount(() => {
requestID++;
});
@@ -412,6 +439,14 @@ onBeforeUnmount(() => {
margin: 7px 0 12px;
--el-card-padding: 12px;
}
.history-toolbar :deep(.history-time-range .el-range__icon) {
width: auto;
flex-shrink: 0;
margin-left: 0;
margin-right: 6px;
font-style: normal;
white-space: nowrap;
}
.history-summary {
margin-top: 12px;
color: var(--el-text-color-secondary);
@@ -65,7 +65,7 @@
? { highlightDiff: 210 }
: view === TaskLog
? { width: '70%' }
: actionName === 'config'
: ['config', 'monitor'].includes(actionName)
? {}
: { dialog: true }
"
+1 -1
View File
@@ -357,8 +357,8 @@ const isManagement = computed(
() => isCurrentPage.value && isLocal && ['vllm', 'tensorrt'].includes(String(route.query.tab)),
);
const tabs = computed(() => [
{ label: i18n.global.t('aiTools.model.services'), path: '/ai/model/account' },
{ label: i18n.global.t('aiTools.model.files'), path: '/ai/model/files' },
{ label: i18n.global.t('aiTools.model.services'), path: '/ai/model/account' },
{ label: i18n.global.t('aiTools.model.engines'), path: '/ai/model/local' },
]);
const downloaderModules = import.meta.glob('/src/xpack/views/model-downloader/index.vue');
@@ -55,10 +55,16 @@
<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 })" />
<el-button icon="Folder" @click="selectModelDir" />
</template>
</el-input>
</el-form-item>
</div>
</details>
<GPUConfig ref="gpuRef" v-model="tensorRTLLM.gpu" />
<details open class="form-section">
<summary class="form-section-header">{{ $t('runtime.runScript') }}</summary>
<div class="form-section-body">
<el-form-item :label="$t('runtime.runScript')" prop="command">
<el-input v-model="tensorRTLLM.command"></el-input>
<span class="input-help">
@@ -67,7 +73,6 @@
</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">
@@ -103,7 +108,7 @@ 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, isMobile } = useGlobalStore();
const { isFxplay, isMobile, isProductPro } = useGlobalStore();
const loading = ref(false);
const gpuRef = ref<InstanceType<typeof GPUConfig>>();
@@ -189,6 +194,24 @@ const handleClose = () => {
emit('close');
};
const downloaderModules = import.meta.glob<{
getModelDownloaderSettings: () => Promise<{ data: { modelDir: string } }>;
}>('/src/xpack/api/modules/model-downloader.ts');
const selectModelDir = async () => {
let path = tensorRTLLM.value.modelDir;
const loadDownloader = Object.values(downloaderModules)[0];
if (isProductPro.value && loadDownloader) {
try {
const { getModelDownloaderSettings } = await loadDownloader();
const { data } = await getModelDownloaderSettings();
path = data.modelDir;
} catch {
path = '/';
}
}
modelDirRef.value.acceptParams({ dir: true, path });
};
const getModelDir = (path: string) => {
tensorRTLLM.value.modelDir = path;
};
+23 -6
View File
@@ -18,11 +18,18 @@
<span v-for="model in modelGroups" :key="model.name">{{ model.name }} × {{ model.count }}</span>
</div>
</div>
<div class="summary-memory">
<div v-if="dedicatedMemoryDevices.length" class="summary-memory">
<span class="metric-label">{{ $t('aiTools.gpu.memoryTotal') }}</span>
<strong>{{ formatMemory(totalMemory) }}</strong>
<span class="metric-label">{{ $t('home.used') }} {{ formatMemory(usedMemory) }}</span>
</div>
<div v-if="sharedMemoryDevice" class="summary-memory" :title="$t('aiTools.gpu.systemMemoryHelper')">
<span class="metric-label">{{ $t('aiTools.gpu.systemMemory') }}</span>
<strong>{{ formatMemory(sharedMemoryDevice.memoryTotal) }}</strong>
<span class="metric-label">
{{ $t('home.used') }} {{ formatMemory(sharedMemoryDevice.memoryUsed) }}
</span>
</div>
</div>
<div class="gpu-grid">
@@ -59,7 +66,12 @@
</div>
<div class="device-metric">
<div class="metric-heading">
<span class="metric-label">{{ $t('aiTools.gpu.memoryUsed') }}</span>
<span
class="metric-label"
:title="device.sharedMemory ? $t('aiTools.gpu.systemMemoryHelper') : undefined"
>
{{ $t(device.sharedMemory ? 'aiTools.gpu.systemMemory' : 'aiTools.gpu.memoryUsed') }}
</span>
<strong>
{{ formatMemory(device.memoryUsed) }}
<span class="metric-total">/ {{ formatMemory(device.memoryTotal) }}</span>
@@ -139,6 +151,7 @@ const devices = computed(() =>
...(props.current.gpuData || []).map((item) => ({
key: `gpu-${item.type}-${item.busID || item.index}`,
type: 'GPU',
sharedMemory: !!item.sharedMemory,
card: `${item.type}-${item.busID || item.index}`,
id: `GPU ${item.index}`,
name: item.productName || 'GPU',
@@ -157,6 +170,7 @@ const devices = computed(() =>
...(props.current.npuData || []).map((item) => ({
key: `npu-${item.npuIndex}-${item.chipIndex}`,
type: 'NPU',
sharedMemory: false,
card: item.npuIndex,
id: `NPU ${item.npuIndex} · Chip ${item.chipIndex}`,
name: item.productName || 'NPU',
@@ -176,6 +190,7 @@ const devices = computed(() =>
...(props.current.xpuData || []).map((item) => ({
key: `xpu-${item.deviceID}`,
type: 'XPU',
sharedMemory: false,
card: item.deviceID,
id: `XPU ${item.deviceID}`,
name: item.deviceName || 'XPU',
@@ -222,15 +237,17 @@ const modelGroups = computed(() => {
}
return Array.from(models, ([name, cards]) => ({ name, count: cards.size }));
});
const sharedMemoryDevice = computed(() => devices.value.find((item) => item.sharedMemory));
const dedicatedMemoryDevices = computed(() => devices.value.filter((item) => !item.sharedMemory));
const totalMemory = computed(() =>
devices.value.some((item) => item.memoryTotal === null)
dedicatedMemoryDevices.value.some((item) => item.memoryTotal === null)
? null
: devices.value.reduce((sum, item) => sum + item.memoryTotal!, 0),
: dedicatedMemoryDevices.value.reduce((sum, item) => sum + item.memoryTotal!, 0),
);
const usedMemory = computed(() =>
devices.value.some((item) => item.memoryUsed === null)
dedicatedMemoryDevices.value.some((item) => item.memoryUsed === null)
? null
: devices.value.reduce((sum, item) => sum + item.memoryUsed!, 0),
: dedicatedMemoryDevices.value.reduce((sum, item) => sum + item.memoryUsed!, 0),
);
</script>