feat(host): support Hygon DCU isolated device and HAMI (#25247)

Add Hygon DCU container device passthrough, HAMI vGPU sharing, vendor
field/filter on isolated devices, remote path helper, and LLM SKU mapping.
This commit is contained in:
Zexi Li
2026-07-29 19:01:55 +08:00
committed by GitHub
parent 4ea44a5f17
commit a29c7ce9f1
21 changed files with 1188 additions and 3 deletions
+4
View File
@@ -44,6 +44,8 @@ const (
CONTAINER_DEV_ASCEND_NPU = "ASCEND_NPU"
CONTAINER_DEV_ASCEND_NPU_HAMI = "ASCEND_NPU_HAMI"
CONTAINER_DEV_VASTAITECH_GPU = "VASTAITECH_GPU"
CONTAINER_DEV_HYGON_DCU = "HYGON_DCU"
CONTAINER_DEV_HYGON_DCU_HAMI = "HYGON_DCU_HAMI"
)
var (
@@ -54,6 +56,8 @@ var (
CONTAINER_DEV_NVIDIA_HAMI,
CONTAINER_DEV_NVIDIA_GPU_SHARE,
CONTAINER_DEV_VASTAITECH_GPU,
CONTAINER_DEV_HYGON_DCU,
CONTAINER_DEV_HYGON_DCU_HAMI,
}
)
+9
View File
@@ -34,6 +34,9 @@ type IsolateDeviceDetails struct {
SIsolatedDevice
// 设备厂商,由 vendor_device_id 经 ID_VENDOR_MAP 翻译
Vendor string `json:"vendor"`
MemoryAllocated int
AllocatedCount int
@@ -73,6 +76,9 @@ type IsolatedDeviceListInput struct {
// 设备VENDOE编号
VendorDeviceId []string `json:"vendor_device_id"`
// 设备厂商,如 HYGON / NVIDIA
Vendor []string `json:"vendor"`
// NUMA节点序号
NumaNode []uint8 `json:"numa_node"`
@@ -413,4 +419,7 @@ type GuestIsolatedDeviceDetails struct {
SIsolatedDevice
HostResourceInfo
apis.SharableResourceBaseInfo
// 设备厂商,由 vendor_device_id 经 ID_VENDOR_MAP 翻译
Vendor string `json:"vendor"`
}
@@ -48,17 +48,20 @@ const (
NVIDIA_VENDOR_ID = "10de"
AMD_VENDOR_ID = "1002"
VASTAITECH_VENDOR_ID = "1ec6"
HYGON_VENDOR_ID = "1d94"
)
var ID_VENDOR_MAP = map[string]string{
NVIDIA_VENDOR_ID: "NVIDIA",
AMD_VENDOR_ID: "AMD",
VASTAITECH_VENDOR_ID: "VASTAITECH",
HYGON_VENDOR_ID: "HYGON",
}
var VENDOR_ID_MAP = map[string]string{
"NVIDIA": NVIDIA_VENDOR_ID,
"AMD": AMD_VENDOR_ID,
"HYGON": HYGON_VENDOR_ID,
}
const (
@@ -83,6 +86,7 @@ var GPU_TYPES = []string{
GPU_HPC_TYPE, GPU_VGA_TYPE, SRIOV_VGPU_TYPE, LEGACY_VGPU_TYPE,
CONTAINER_DEV_CPH_AMD_GPU, CONTAINER_DEV_NVIDIA_GPU, CONTAINER_DEV_NVIDIA_MPS, CONTAINER_DEV_NVIDIA_GPU_SHARE,
CONTAINER_DEV_NVIDIA_HAMI, CONTAINER_DEV_VASTAITECH_GPU,
CONTAINER_DEV_HYGON_DCU, CONTAINER_DEV_HYGON_DCU_HAMI,
}
var NETINT_TYPES = []string{
@@ -93,6 +97,7 @@ var VALID_CONTAINER_DEVICE_TYPES = []string{
CONTAINER_DEV_CPH_AMD_GPU, CONTAINER_DEV_CPH_AOSP_BINDER, CONTAINER_DEV_NETINT_CA_QUADRA,
CONTAINER_DEV_NETINT_CA_ASIC, CONTAINER_DEV_NVIDIA_GPU, CONTAINER_DEV_NVIDIA_MPS, CONTAINER_DEV_NVIDIA_GPU_SHARE, CONTAINER_DEV_NVIDIA_HAMI,
CONTAINER_DEV_ASCEND_NPU, CONTAINER_DEV_VASTAITECH_GPU,
CONTAINER_DEV_HYGON_DCU, CONTAINER_DEV_HYGON_DCU_HAMI,
}
var VALID_PASSTHROUGH_TYPES = []string{
@@ -111,6 +116,8 @@ var VITRUAL_DEVICE_TYPES = []string{
CONTAINER_DEV_ASCEND_NPU,
CONTAINER_DEV_NVIDIA_GPU_SHARE,
CONTAINER_DEV_VASTAITECH_GPU,
CONTAINER_DEV_HYGON_DCU,
CONTAINER_DEV_HYGON_DCU_HAMI,
}
func init() {
@@ -132,6 +132,7 @@ func (manager *SGuestIsolatedDeviceManager) FetchCustomizeColumns(
rows[i].SIsolatedDevice = isolatedDeviceRows[i].SIsolatedDevice
rows[i].HostResourceInfo = isolatedDeviceRows[i].HostResourceInfo
rows[i].SharableResourceBaseInfo = isolatedDeviceRows[i].SharableResourceBaseInfo
rows[i].Vendor = isolatedDeviceRows[i].Vendor
}
return rows
+24
View File
@@ -346,6 +346,13 @@ func (manager *SIsolatedDeviceManager) ListItemFilter(
if len(query.VendorDeviceId) > 0 {
q = q.In("vendor_device_id", query.VendorDeviceId)
}
if len(query.Vendor) > 0 {
conds := make([]sqlchemy.ICondition, 0, len(query.Vendor))
for _, v := range query.Vendor {
conds = append(conds, sqlchemy.Startswith(q.Field("vendor_device_id"), vendorDeviceIdPrefixForFilter(v)))
}
q = q.Filter(sqlchemy.OR(conds...))
}
if len(query.NumaNode) > 0 {
q = q.In("numa_node", query.NumaNode)
}
@@ -505,6 +512,22 @@ func GetVendorByVendorDeviceId(vendorDeviceId string) string {
}
}
func vendorDeviceIdPrefixForFilter(vendor string) string {
return resolveVendorIdForFilter(vendor) + ":"
}
func resolveVendorIdForFilter(vendor string) string {
if id, ok := api.VENDOR_ID_MAP[vendor]; ok {
return id
}
for name, id := range api.VENDOR_ID_MAP {
if strings.EqualFold(name, vendor) {
return id
}
}
return strings.ToLower(vendor)
}
func (self *SIsolatedDevice) IsGPU() bool {
return self.DevType == api.GPU_TYPE
}
@@ -1502,6 +1525,7 @@ func (manager *SIsolatedDeviceManager) FetchCustomizeColumns(
SharableResourceBaseInfo: shareRows[i],
}
dev := objs[i].(*SIsolatedDevice)
rows[i].Vendor = dev.getVendor()
if dev.SharingMode == api.DEVICE_SHARING_MODE_HAMI {
rows[i].MemoryAllocated, _ = dev.getAllocatedMemorySize()
} else {
@@ -0,0 +1,48 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package models
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGetVendorByVendorDeviceId(t *testing.T) {
assert.Equal(t, "HYGON", GetVendorByVendorDeviceId("1d94:6320"))
assert.Equal(t, "NVIDIA", GetVendorByVendorDeviceId("10de:1c82"))
assert.Equal(t, "AMD", GetVendorByVendorDeviceId("1002:6611"))
assert.Equal(t, "abcd", GetVendorByVendorDeviceId("abcd:0001"))
}
func TestSIsolatedDeviceGetVendor(t *testing.T) {
dev := &SIsolatedDevice{VendorDeviceId: "1d94:6320"}
assert.Equal(t, "HYGON", dev.getVendor())
}
func TestVendorDeviceIdPrefixForFilter(t *testing.T) {
cases := map[string]string{
"HYGON": "1d94:",
"NVIDIA": "10de:",
"1d94": "1d94:",
"hygon": "1d94:",
"Hygon": "1d94:",
"nvidia": "10de:",
"1D94": "1d94:",
}
for vendor, want := range cases {
assert.Equal(t, want, vendorDeviceIdPrefixForFilter(vendor), "vendor=%s", vendor)
}
}
@@ -83,6 +83,7 @@ func (manager *SIsolatedDeviceResourceBaseManager) FetchCustomizeColumns(
rows[i].HostResourceInfo = devRows[i].HostResourceInfo
rows[i].Guest = devRows[i].Guest
rows[i].GuestStatus = devRows[i].GuestStatus
rows[i].Vendor = devRows[i].Vendor
}
return rows
}
+12
View File
@@ -2325,6 +2325,15 @@ func (h *SHostInfo) probeSyncIsolatedDevices() (*jsonutils.JSONArray, error) {
if err != nil {
return nil, err
}
log.Infof("==== probeSyncIsolatedDevices: hostType=%s isContainerHost=%v isKvmSupport=%v",
options.HostOptions.HostType, h.IsContainerHost(), h.IsKvmSupport())
log.Infof("==== probeSyncIsolatedDevices hygon config: enableDCU=%v enableHAMI=%v hySmiPath=%s hyhalPath=%s dtkPath=%s",
options.HostOptions.EnableContainerHygonDCU,
options.HostOptions.EnableContainerHygonDCUHAMI,
options.HostOptions.HygonHySmiPath,
options.HostOptions.HygonHyhalPath,
options.HostOptions.HygonDtkPath,
)
probeOpts := &isolated_device.SIsolatedDeviceProbeOptions{
SkipGPUs: options.HostOptions.DisableGPU,
SkipUSBs: options.HostOptions.DisableUSB,
@@ -2333,6 +2342,8 @@ func (h *SHostInfo) probeSyncIsolatedDevices() (*jsonutils.JSONArray, error) {
EnableCudaMps: options.HostOptions.EnableCudaMPS,
EnableContainerAscendNpu: options.HostOptions.EnableContainerAscendNPU,
EnableContainerAscendNpuHAMI: options.HostOptions.EnableContainerAscendNPUHami,
EnableContainerHygonDCU: options.HostOptions.EnableContainerHygonDCU,
EnableContainerHygonDCUHAMI: options.HostOptions.EnableContainerHygonDCUHAMI,
EnableWhitelist: options.HostOptions.EnableIsolatedDeviceWhitelist,
SriovNics: sriovNics,
OvsOffloadNics: offloadNics,
@@ -2374,6 +2385,7 @@ func (h *SHostInfo) probeSyncIsolatedDevices() (*jsonutils.JSONArray, error) {
mtx := sync.Mutex{}
updateDevs := jsonutils.NewArray()
devs := h.IsolatedDeviceMan.GetDevices()
log.Infof("==== probeSyncIsolatedDevices: local isolated devices count=%d, syncing to region", len(devs))
for i := range devs {
dev := devs[i]
eg.Go(func() error {
@@ -48,6 +48,8 @@ const (
ContainerDeviceTypeAscendNpu ContainerDeviceType = api.CONTAINER_DEV_ASCEND_NPU
ContainerDeviceTypeAscendNpuHami ContainerDeviceType = api.CONTAINER_DEV_ASCEND_NPU_HAMI
ContainerDeviceTypeVastaitechGpu ContainerDeviceType = api.CONTAINER_DEV_VASTAITECH_GPU
ContainerDeviceTypeHygonDcu ContainerDeviceType = api.CONTAINER_DEV_HYGON_DCU
ContainerDeviceTypeHygonDcuHami ContainerDeviceType = api.CONTAINER_DEV_HYGON_DCU_HAMI
)
func GetContainerDeviceManager(devType ContainerDeviceType) (IContainerDeviceManager, error) {
@@ -0,0 +1,482 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package container_device
import (
"fmt"
"path"
"regexp"
"strconv"
"strings"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
hostapi "yunion.io/x/onecloud/pkg/apis/host"
"yunion.io/x/onecloud/pkg/hostman/hostinfo"
"yunion.io/x/onecloud/pkg/hostman/isolated_device"
"yunion.io/x/onecloud/pkg/hostman/options"
"yunion.io/x/onecloud/pkg/util/procutils"
)
func init() {
isolated_device.RegisterContainerDeviceManager(newHygonDCUManager())
}
type hygonDCUManager struct{}
func newHygonDCUManager() *hygonDCUManager {
return &hygonDCUManager{}
}
func (m *hygonDCUManager) GetRegisterType() isolated_device.ContainerDeviceType {
return isolated_device.ContainerDeviceTypeHygonDcu
}
func (m *hygonDCUManager) ProbeDevices() ([]isolated_device.IDevice, error) {
return getHygonDCUs(m, computeapi.DEVICE_SHARING_MODE_EXCLUSIVE)
}
func (m *hygonDCUManager) NewDevices(dev *isolated_device.ContainerDevice) ([]isolated_device.IDevice, error) {
return nil, nil
}
func (m *hygonDCUManager) GetDevType() string {
return computeapi.GPU_TYPE
}
func (m *hygonDCUManager) GetSharingMode() string {
return computeapi.DEVICE_SHARING_MODE_EXCLUSIVE
}
type hygonDCU struct {
manager isolated_device.IContainerDeviceManager
*BaseDevice
gpuIndex int
renderPath string
memorySize int
computeUnits int
}
func (dev *hygonDCU) GetMemorySize() int {
return dev.memorySize
}
func (dev *hygonDCU) GetIndex() int {
return dev.gpuIndex
}
func (dev *hygonDCU) GetRenderPath() string {
return dev.renderPath
}
func (dev *hygonDCU) GetComputeUnits() int {
return dev.computeUnits
}
func (dev *hygonDCU) GetContainerDeviceManager() isolated_device.IContainerDeviceManager {
return dev.manager
}
func (m *hygonDCUManager) GetContainerExtraConfigures(devs []*hostapi.ContainerDevice) ([]*runtimeapi.KeyValue, []*runtimeapi.Mount) {
indices := []string{}
for _, dev := range devs {
if dev.IsolatedDevice == nil {
continue
}
iDev := hostinfo.Instance().IsolatedDeviceMan.GetDeviceByCloudId(dev.IsolatedDevice.Id)
if iDev == nil {
continue
}
devMan := iDev.GetContainerDeviceManager()
if _, ok := devMan.(*hygonDCUManager); !ok {
continue
}
indices = append(indices, dev.IsolatedDevice.Path)
}
if len(indices) == 0 {
return nil, nil
}
return m.buildHygonExtraConfigures(indices)
}
func buildHygonRuntimeMounts() []*runtimeapi.Mount {
hyhalPath := options.HostOptions.HygonHyhalPath
dtkPath := options.HostOptions.HygonDtkPath
mounts := []*runtimeapi.Mount{}
if hygonPathExists(hyhalPath) {
mounts = append(mounts, &runtimeapi.Mount{
ContainerPath: hyhalPath,
HostPath: hyhalPath,
Readonly: true,
})
}
if hygonPathExists(dtkPath) {
mounts = append(mounts, &runtimeapi.Mount{
ContainerPath: dtkPath,
HostPath: dtkPath,
Readonly: true,
})
}
return mounts
}
func buildHygonRuntimeEnvs(indices []string) []*runtimeapi.KeyValue {
hyhalPath := options.HostOptions.HygonHyhalPath
dtkPath := options.HostOptions.HygonDtkPath
envs := []*runtimeapi.KeyValue{
{
Key: "HYGON_VISIBLE_DEVICES",
Value: strings.Join(indices, ","),
},
{
Key: "HIP_VISIBLE_DEVICES",
Value: strings.Join(indices, ","),
},
{
Key: "ROCM_PATH",
Value: dtkPath,
},
{
Key: "DTK_HOME",
Value: dtkPath,
},
{
Key: "ROCM_SMI_LIB_PATH",
Value: path.Join(hyhalPath, "lib"),
},
}
return envs
}
func (m *hygonDCUManager) buildHygonExtraConfigures(indices []string) ([]*runtimeapi.KeyValue, []*runtimeapi.Mount) {
return buildHygonRuntimeEnvs(indices), buildHygonRuntimeMounts()
}
func hygonCommonDevices() []*runtimeapi.Device {
perms := "rwm"
devs := []*runtimeapi.Device{}
for _, devPath := range []string{"/dev/kfd", "/dev/mkfd"} {
if hygonPathExists(devPath) {
devs = append(devs, &runtimeapi.Device{
ContainerPath: devPath,
HostPath: devPath,
Permissions: perms,
})
}
}
return devs
}
func (m *hygonDCUManager) NewContainerDevices(input *hostapi.ContainerCreateInput, dev *hostapi.ContainerDevice) ([]*runtimeapi.Device, []*runtimeapi.Device, error) {
if dev.IsolatedDevice == nil {
return nil, nil, errors.Errorf("isolated device is nil")
}
iDev := hostinfo.Instance().IsolatedDeviceMan.GetDeviceByCloudId(dev.IsolatedDevice.Id)
if iDev == nil {
return nil, nil, errors.Errorf("device %s not found", dev.IsolatedDevice.Id)
}
dcuDev, ok := iDev.(*hygonDCU)
if !ok {
return nil, nil, errors.Errorf("device %s is not hygon dcu", dev.IsolatedDevice.Id)
}
renderPath := dcuDev.GetRenderPath()
if renderPath == "" {
return nil, nil, errors.Errorf("hygon dcu %s has empty render path", dev.IsolatedDevice.Id)
}
perms := "rwm"
ctrDevs := []*runtimeapi.Device{
{
ContainerPath: renderPath,
HostPath: renderPath,
Permissions: perms,
},
}
return ctrDevs, hygonCommonDevices(), nil
}
func hygonDebugOutputSnippet(output string, maxLines int) string {
lines := strings.Split(output, "\n")
if len(lines) > maxLines {
lines = lines[:maxLines]
}
return strings.Join(lines, "\n")
}
func getHygonDCUs(manager isolated_device.IContainerDeviceManager, sharingMode string) ([]isolated_device.IDevice, error) {
log.Infof("==== getHygonDCUs start: sharingMode=%s", sharingMode)
hySmiPath := options.HostOptions.HygonHySmiPath
log.Infof("==== getHygonDCUs config: hySmiPath=%s hyhalPath=%s dtkPath=%s remoteExecutor=%v",
hySmiPath, options.HostOptions.HygonHyhalPath, options.HostOptions.HygonDtkPath, hygonUseRemoteFS())
if !hygonPathExists(hySmiPath) {
log.Infof("==== getHygonDCUs abort: hy-smi not found at %s (remote=%v)", hySmiPath, hygonUseRemoteFS())
return nil, nil
}
memOut, err := procutils.NewRemoteCommandAsFarAsPossible(hySmiPath, "--showmeminfo", "vram").Output()
if err != nil {
log.Warningf("==== getHygonDCUs: hy-smi --showmeminfo vram failed: %v, fallback to default output", err)
memOut, err = procutils.NewRemoteCommandAsFarAsPossible(hySmiPath).Output()
if err != nil {
log.Warningf("==== getHygonDCUs abort: hy-smi failed: %v", err)
return nil, errors.Wrap(err, "hy-smi")
}
}
log.Infof("==== getHygonDCUs hy-smi output (first 10 lines):\n%s", hygonDebugOutputSnippet(string(memOut), 10))
indices := parseHySmiDeviceIndices(string(memOut))
log.Infof("==== getHygonDCUs parsed indices: %v", indices)
if len(indices) == 0 {
log.Infof("==== getHygonDCUs abort: no HCU indices parsed from hy-smi output")
return nil, nil
}
memMap := parseHySmiMemInfoVram(string(memOut))
log.Infof("==== getHygonDCUs parsed memMap: %v", memMap)
renderToPCI, err := getHygonRenderPathToPCIMap()
if err != nil {
log.Warningf("==== getHygonDCUs abort: getHygonRenderPathToPCIMap failed: %v", err)
return nil, err
}
log.Infof("==== getHygonDCUs render to pci map: %v", renderToPCI)
computeUnitsMap := parseHySmiComputeUnits(string(memOut))
devs := make([]isolated_device.IDevice, 0, len(indices))
for i, idx := range indices {
renderPath := hygonRenderPathForHCUIndex(idx)
pciAddr := renderToPCI[renderPath]
if pciAddr == "" {
log.Warningf("==== getHygonDCUs: no pci addr for render path %s (HCU idx=%d)", renderPath, idx)
}
var pciDev *isolated_device.PCIDevice
if pciAddr != "" {
pciOutput, err := isolated_device.GetPCIStrByAddr(pciAddr)
if err != nil {
return nil, errors.Wrapf(err, "GetPCIStrByAddr %s", pciAddr)
}
pciDev = isolated_device.NewPCIDevice2(pciOutput[0])
} else {
pciDev = &isolated_device.PCIDevice{
VendorId: computeapi.HYGON_VENDOR_ID,
Addr: fmt.Sprintf("hygon-%d", idx),
}
}
modelName := hygonModelNameFromPCIDevice(pciDev)
memSize := memMap[idx]
if memSize == 0 {
memSize = memMap[i]
}
computeUnits := computeUnitsMap[idx]
if computeUnits == 0 {
computeUnits = 60
}
dcuDev := &hygonDCU{
manager: manager,
BaseDevice: NewBaseDevice(pciDev, computeapi.GPU_TYPE, strconv.Itoa(idx), sharingMode, 1),
gpuIndex: idx,
renderPath: renderPath,
memorySize: memSize,
computeUnits: computeUnits,
}
dcuDev.SetModelName(modelName)
log.Infof("==== getHygonDCUs device[%d]: idx=%d model=%s pci=%s render=%s vendorDeviceId=%s memMiB=%d cu=%d",
i, idx, modelName, pciAddr, renderPath, pciDev.GetVendorDeviceId(), memSize, computeUnits)
devs = append(devs, dcuDev)
}
if len(devs) == 0 {
log.Infof("==== getHygonDCUs finished: no devices built")
return nil, nil
}
log.Infof("==== getHygonDCUs finished: built %d devices", len(devs))
return devs, nil
}
func hygonRenderPathForHCUIndex(idx int) string {
return fmt.Sprintf("/dev/dri/renderD%d", 128+idx)
}
func hygonModelNameFromPCIDevice(pciDev *isolated_device.PCIDevice) string {
if pciDev == nil {
return "Hygon DCU"
}
if pciDev.ModelName != "" {
return pciDev.ModelName
}
if pciDev.DeviceName != "" {
return pciDev.DeviceName
}
return "Hygon DCU"
}
func buildHygonRenderPathToPCIMap(pciToRender map[string]string) map[string]string {
ret := make(map[string]string, len(pciToRender))
for pciAddr, renderPath := range pciToRender {
ret[renderPath] = pciAddr
}
return ret
}
func getHygonRenderPathToPCIMap() (map[string]string, error) {
pciToRender, err := getHygonRenderPathMap()
if err != nil {
return nil, err
}
return buildHygonRenderPathToPCIMap(pciToRender), nil
}
func getHygonRenderPathMap() (map[string]string, error) {
const byPathDir = "/dev/dri/by-path"
ret := map[string]string{}
if !hygonPathExists(byPathDir) {
return ret, nil
}
entries, err := hygonReadDir(byPathDir)
if err != nil {
return nil, errors.Wrapf(err, "read %s", byPathDir)
}
for _, entry := range entries {
entryName := entry.Name()
if !strings.HasSuffix(entryName, "-render") {
continue
}
pciAddr, err := getGPUPCIAddr(entryName)
if err != nil {
continue
}
fp := path.Join(byPathDir, entryName)
linkPath, err := hygonReadlink(fp)
if err != nil {
continue
}
renderPath := hygonRenderPathFromLink(linkPath)
ret[pciAddr] = renderPath
}
return ret, nil
}
var (
hySmiIndexRe = regexp.MustCompile(`(?m)^\s*(\d+)\s+`)
hySmiHCUBracketRe = regexp.MustCompile(`HCU\[(\d+)\]`)
hySmiHCUMemTotalRe = regexp.MustCompile(`HCU\[(\d+)\]\s*:\s*vram Total Memory \(MiB\):\s*(\d+)`)
hySmiComputeRe = regexp.MustCompile(`(?i)(?:Compute units|CU)\s*:\s*(\d+)`)
)
func parseHySmiDeviceIndices(output string) []int {
indices := []int{}
seen := map[int]bool{}
addIndex := func(idx int) {
if !seen[idx] {
seen[idx] = true
indices = append(indices, idx)
}
}
for _, line := range strings.Split(output, "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
if m := hySmiHCUBracketRe.FindStringSubmatch(line); len(m) == 2 {
idx, _ := strconv.Atoi(m[1])
addIndex(idx)
continue
}
if strings.HasPrefix(line, "GPU[") {
if m := regexp.MustCompile(`GPU\[(\d+)\]`).FindStringSubmatch(line); len(m) == 2 {
idx, _ := strconv.Atoi(m[1])
addIndex(idx)
}
continue
}
if strings.HasPrefix(line, "HCU") || strings.HasPrefix(line, "GPU") {
continue
}
fields := strings.Fields(line)
if len(fields) == 0 {
continue
}
idx, err := strconv.Atoi(fields[0])
if err != nil {
continue
}
addIndex(idx)
}
if len(indices) == 0 {
for i, line := range strings.Split(output, "\n") {
if m := hySmiIndexRe.FindStringSubmatch(line); len(m) == 2 {
idx, err := strconv.Atoi(m[1])
if err == nil && i > 0 {
addIndex(idx)
}
}
}
}
return indices
}
func parseHySmiMemInfoVram(output string) map[int]int {
ret := map[int]int{}
for _, line := range strings.Split(output, "\n") {
line = strings.TrimSpace(line)
if line == "" {
continue
}
// HCU[0] : vram Total Memory (MiB): 65520
if m := hySmiHCUMemTotalRe.FindStringSubmatch(line); len(m) == 3 {
idx, _ := strconv.Atoi(m[1])
mem, _ := strconv.Atoi(m[2])
ret[idx] = mem
continue
}
if m := regexp.MustCompile(`GPU\[(\d+)\]\s*:\s*(?:Total Memory|VRAM Total Memory).*?(\d+)`).FindStringSubmatch(line); len(m) == 3 {
idx, _ := strconv.Atoi(m[1])
val, _ := strconv.ParseInt(m[2], 10, 64)
if val > 1024*1024 {
ret[idx] = int(val / 1024 / 1024)
} else {
ret[idx] = int(val)
}
continue
}
if m := regexp.MustCompile(`(?i)^(\d+)\s+.*?(\d+)\s*MiB`).FindStringSubmatch(line); len(m) == 3 {
idx, _ := strconv.Atoi(m[1])
mem, _ := strconv.Atoi(m[2])
ret[idx] = mem
}
}
return ret
}
func parseHySmiComputeUnits(output string) map[int]int {
ret := map[int]int{}
currentIdx := -1
for _, line := range strings.Split(output, "\n") {
line = strings.TrimSpace(line)
if m := regexp.MustCompile(`(?i)(?:Device|GPU|HCU)\s*(\d+)`).FindStringSubmatch(line); len(m) == 2 {
currentIdx, _ = strconv.Atoi(m[1])
}
if m := hySmiComputeRe.FindStringSubmatch(line); len(m) == 2 && currentIdx >= 0 {
cu, _ := strconv.Atoi(m[1])
ret[currentIdx] = cu
}
}
return ret
}
@@ -0,0 +1,239 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package container_device
import (
"fmt"
"path"
"regexp"
"strconv"
"strings"
"sync"
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
computeapi "yunion.io/x/onecloud/pkg/apis/compute"
hostapi "yunion.io/x/onecloud/pkg/apis/host"
"yunion.io/x/onecloud/pkg/hostman/hostinfo"
"yunion.io/x/onecloud/pkg/hostman/isolated_device"
"yunion.io/x/onecloud/pkg/hostman/options"
"yunion.io/x/onecloud/pkg/util/procutils"
)
func init() {
isolated_device.RegisterContainerDeviceManager(newHygonDCUHamiManager())
}
type hygonDCUHamiManager struct {
*hygonDCUManager
vdevMu sync.Mutex
allocated map[string]int // containerDeviceId -> vdevIndex
vdevInUse map[int]string // vdevIndex -> containerDeviceId
}
func newHygonDCUHamiManager() *hygonDCUHamiManager {
return &hygonDCUHamiManager{
hygonDCUManager: newHygonDCUManager(),
allocated: make(map[string]int),
vdevInUse: make(map[int]string),
}
}
func (m *hygonDCUHamiManager) GetRegisterType() isolated_device.ContainerDeviceType {
return isolated_device.ContainerDeviceTypeHygonDcuHami
}
func (m *hygonDCUHamiManager) ProbeDevices() ([]isolated_device.IDevice, error) {
return getHygonDCUs(m, computeapi.DEVICE_SHARING_MODE_HAMI)
}
func (m *hygonDCUHamiManager) GetContainerExtraConfigures(devs []*hostapi.ContainerDevice) ([]*runtimeapi.KeyValue, []*runtimeapi.Mount) {
if len(devs) == 0 {
return nil, nil
}
dev := devs[0]
if dev.IsolatedDevice == nil {
return nil, nil
}
iDev := hostinfo.Instance().IsolatedDeviceMan.GetDeviceByCloudId(dev.IsolatedDevice.Id)
if iDev == nil {
return nil, nil
}
dcuDev, ok := iDev.(*hygonDCU)
if !ok {
return nil, nil
}
memLimitMiB := dev.IsolatedDevice.MemoryLimit
if memLimitMiB <= 0 {
memLimitMiB = dcuDev.GetMemorySize()
}
smLimit := dev.IsolatedDevice.SmUtilLimit
if smLimit <= 0 {
smLimit = 100
}
computeUnits := dcuDev.GetComputeUnits()
if computeUnits <= 0 {
computeUnits = 60
}
cuCount := computeUnits * smLimit / 100
if cuCount <= 0 {
cuCount = 1
}
vdevIdx, err := m.ensureVdev(dev.IsolatedDevice.Id, dcuDev.GetIndex(), memLimitMiB, cuCount)
if err != nil {
log.Errorf("ensure hygon vdev for device %s: %v", dev.IsolatedDevice.Id, err)
return nil, nil
}
envs := buildHygonRuntimeEnvs([]string{strconv.Itoa(vdevIdx)})
mounts := buildHygonRuntimeMounts()
vdevConfHost := path.Join(options.HostOptions.HygonVdevConfDir, fmt.Sprintf("vdev%d.conf", vdevIdx))
vdevConfContainer := path.Join("/etc/vdev/docker", fmt.Sprintf("vdev%d.conf", vdevIdx))
if hygonPathExists(vdevConfHost) {
mounts = append(mounts, &runtimeapi.Mount{
ContainerPath: vdevConfContainer,
HostPath: vdevConfHost,
Readonly: true,
})
}
return envs, mounts
}
func (m *hygonDCUHamiManager) NewContainerDevices(input *hostapi.ContainerCreateInput, dev *hostapi.ContainerDevice) ([]*runtimeapi.Device, []*runtimeapi.Device, error) {
return m.hygonDCUManager.NewContainerDevices(input, dev)
}
func (m *hygonDCUHamiManager) ensureVdev(containerDevId string, physIdx, memMiB, computeUnits int) (int, error) {
m.vdevMu.Lock()
defer m.vdevMu.Unlock()
if vdevIdx, ok := m.allocated[containerDevId]; ok {
return vdevIdx, nil
}
vdevIdx, err := m.findOrCreateVdev(physIdx, memMiB, computeUnits)
if err != nil {
return -1, err
}
m.allocated[containerDevId] = vdevIdx
m.vdevInUse[vdevIdx] = containerDevId
return vdevIdx, nil
}
func (m *hygonDCUHamiManager) findOrCreateVdev(physIdx, memMiB, computeUnits int) (int, error) {
existing, err := m.listAvailableVdevIndices(physIdx)
if err != nil {
log.Warningf("list hygon vdev indices: %v", err)
}
for _, idx := range existing {
if _, used := m.vdevInUse[idx]; !used {
return idx, nil
}
}
hySmiPath := options.HostOptions.HygonHySmiPath
out, err := procutils.NewRemoteCommandAsFarAsPossible(
hySmiPath, "virtual", "-create-vdevices", "1",
"-d", strconv.Itoa(physIdx),
"-vdevice-compute-units", strconv.Itoa(computeUnits),
"-vdevice-memory-size", strconv.Itoa(memMiB),
).Output()
if err != nil {
return -1, errors.Wrapf(err, "create vdev on dcu %d: %s", physIdx, out)
}
created, err := m.listAvailableVdevIndices(physIdx)
if err != nil || len(created) == 0 {
return physIdx, nil
}
for _, idx := range created {
if _, used := m.vdevInUse[idx]; !used {
return idx, nil
}
}
return created[len(created)-1], nil
}
func (m *hygonDCUHamiManager) listAvailableVdevIndices(physIdx int) ([]int, error) {
hySmiPath := options.HostOptions.HygonHySmiPath
out, err := procutils.NewRemoteCommandAsFarAsPossible(hySmiPath, "virtual", "-show-vdevice-info").Output()
if err != nil {
return nil, errors.Wrap(err, "hy-smi virtual -show-vdevice-info")
}
return parseHySmiVdeviceIndices(string(out), physIdx), nil
}
func parseHySmiVdeviceIndices(output string, physIdx int) []int {
indices := []int{}
currentVdev := -1
currentPhys := -1
for _, line := range strings.Split(output, "\n") {
line = strings.TrimSpace(line)
if strings.HasPrefix(line, "Virtual Device") {
if m := regexp.MustCompile(`Virtual Device\s*(\d+)`).FindStringSubmatch(line); len(m) == 2 {
currentVdev, _ = strconv.Atoi(m[1])
}
continue
}
if strings.HasPrefix(line, "Actual Device:") {
parts := strings.SplitN(line, ":", 2)
if len(parts) == 2 {
currentPhys, _ = strconv.Atoi(strings.TrimSpace(parts[1]))
}
continue
}
if currentVdev >= 0 && currentPhys == physIdx {
indices = append(indices, currentVdev)
currentVdev = -1
currentPhys = -1
}
}
if len(indices) == 0 {
vdevDir := options.HostOptions.HygonVdevConfDir
if hygonPathExists(vdevDir) {
entries, err := hygonReadDir(vdevDir)
if err == nil {
for _, e := range entries {
name := e.Name()
if strings.HasPrefix(name, "vdev") && strings.HasSuffix(name, ".conf") {
numStr := strings.TrimSuffix(strings.TrimPrefix(name, "vdev"), ".conf")
if idx, err := strconv.Atoi(numStr); err == nil {
indices = append(indices, idx)
}
}
}
}
}
}
return indices
}
func (m *hygonDCUHamiManager) releaseVdev(containerDevId string) {
m.vdevMu.Lock()
defer m.vdevMu.Unlock()
vdevIdx, ok := m.allocated[containerDevId]
if !ok {
return
}
delete(m.allocated, containerDevId)
delete(m.vdevInUse, vdevIdx)
}
@@ -0,0 +1,170 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package container_device
import (
"testing"
"github.com/stretchr/testify/assert"
"yunion.io/x/onecloud/pkg/hostman/isolated_device"
)
func TestParseHySmiDeviceIndices(t *testing.T) {
cases := []struct {
name string
input string
expect []int
}{
{
name: "default table",
input: `HCU Temp AvgPwr Perf PwrCap VRAM% HCU% Mode
0 61.0C 108.0W auto 400.0W 0% 0.0% Normal
1 62.0C 112.0W auto 400.0W 0% 0.0% Normal`,
expect: []int{0, 1},
},
{
name: "gpu bracket format",
input: `GPU[0] : VRAM Total Memory (B): 34342961152
GPU[1] : VRAM Total Memory (B): 34342961152`,
expect: []int{0, 1},
},
{
name: "hcu vram meminfo format",
input: `HCU[0] : vram Total Memory (MiB): 65520
HCU[0] : vram Total Used Memory (MiB): 2
HCU[1] : vram Total Memory (MiB): 65520
HCU[7] : vram Total Memory (MiB): 65520`,
expect: []int{0, 1, 7},
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got := parseHySmiDeviceIndices(c.input)
assert.Equal(t, c.expect, got)
})
}
}
func TestParseHySmiMemInfoVram(t *testing.T) {
t.Run("gpu bytes format", func(t *testing.T) {
input := `GPU[0] : VRAM Total Memory (B): 34342961152
GPU[1] : VRAM Total Memory (B): 34342961152`
memMap := parseHySmiMemInfoVram(input)
assert.Greater(t, memMap[0], 30000)
assert.Greater(t, memMap[1], 30000)
})
t.Run("hcu vram meminfo format", func(t *testing.T) {
input := `HCU[0] : vram Total Memory (MiB): 65520
HCU[0] : vram Total Used Memory (MiB): 2
HCU[1] : vram Total Memory (MiB): 65520
HCU[7] : vram Total Memory (MiB): 65520`
memMap := parseHySmiMemInfoVram(input)
assert.Equal(t, 65520, memMap[0])
assert.Equal(t, 65520, memMap[1])
assert.Equal(t, 65520, memMap[7])
assert.NotContains(t, memMap, 2) // should not pick up "Used Memory" value
})
}
func TestHygonModelNameFromPCIDevice(t *testing.T) {
t.Run("from lspci model name", func(t *testing.T) {
line := `09:00.0 "Display controller [0380]" "Chengdu Haiguang IC Design Co., Ltd. [1d94]" "Z100 [K100_AI] [6320]" -rxx "Chengdu Haiguang IC Design Co., Ltd. [1d94]" "Z100 [K100_AI] [6320]"`
pciDev := isolated_device.NewPCIDevice2(line)
assert.Equal(t, "K100_AI", hygonModelNameFromPCIDevice(pciDev))
})
t.Run("fallback to device name", func(t *testing.T) {
pciDev := &isolated_device.PCIDevice{
DeviceName: "Z100 [K100_AI]",
}
assert.Equal(t, "Z100 [K100_AI]", hygonModelNameFromPCIDevice(pciDev))
})
t.Run("fallback to default", func(t *testing.T) {
assert.Equal(t, "Hygon DCU", hygonModelNameFromPCIDevice(nil))
assert.Equal(t, "Hygon DCU", hygonModelNameFromPCIDevice(&isolated_device.PCIDevice{}))
})
}
func TestParseHySmiVdeviceIndices(t *testing.T) {
input := `Virtual Device 0:
Actual Device: 0
Compute units: 5
Global memory: 4294967296 bytes
Virtual Device 1:
Actual Device: 0
Compute units: 15
Global memory: 8589934592 bytes`
indices := parseHySmiVdeviceIndices(input, 0)
assert.Equal(t, []int{0, 1}, indices)
}
func TestHygonRenderPathFromLinkWithRemote(t *testing.T) {
assert.Equal(t, "/dev/dri/renderD128", hygonRenderPathFromLinkWithRemote("../renderD128", false))
assert.Equal(t, "/dev/dri/renderD128", hygonRenderPathFromLinkWithRemote("/dev/dri/renderD128", true))
}
func TestBuildHygonRenderPathMapFromEntry(t *testing.T) {
entryName := "pci-0000:03:00.0-render"
pciAddr, err := getGPUPCIAddr(entryName)
assert.NoError(t, err)
assert.Equal(t, "0000:03:00.0", pciAddr)
renderPath := hygonRenderPathFromLinkWithRemote("/dev/dri/renderD128", true)
assert.Equal(t, "/dev/dri/renderD128", renderPath)
}
func TestHygonRenderPathForHCUIndex(t *testing.T) {
assert.Equal(t, "/dev/dri/renderD128", hygonRenderPathForHCUIndex(0))
assert.Equal(t, "/dev/dri/renderD129", hygonRenderPathForHCUIndex(1))
assert.Equal(t, "/dev/dri/renderD135", hygonRenderPathForHCUIndex(7))
}
func TestBuildHygonRenderPathToPCIMapFromEntries(t *testing.T) {
// Sample by-path entries from Hygon node m06r2n19
entries := []struct {
entryName string
linkPath string
renderPath string
pciAddr string
}{
{"pci-0000:09:00.0-render", "../renderD128", "/dev/dri/renderD128", "0000:09:00.0"},
{"pci-0000:36:00.0-render", "../renderD129", "/dev/dri/renderD129", "0000:36:00.0"},
{"pci-0000:55:00.0-render", "../renderD130", "/dev/dri/renderD130", "0000:55:00.0"},
{"pci-0000:77:00.0-render", "../renderD131", "/dev/dri/renderD131", "0000:77:00.0"},
{"pci-0000:85:00.0-render", "../renderD132", "/dev/dri/renderD132", "0000:85:00.0"},
{"pci-0000:b5:00.0-render", "../renderD133", "/dev/dri/renderD133", "0000:b5:00.0"},
{"pci-0000:d5:00.0-render", "../renderD134", "/dev/dri/renderD134", "0000:d5:00.0"},
{"pci-0000:f5:00.0-render", "../renderD135", "/dev/dri/renderD135", "0000:f5:00.0"},
}
pciToRender := map[string]string{}
for _, e := range entries {
pciAddr, err := getGPUPCIAddr(e.entryName)
assert.NoError(t, err)
assert.Equal(t, e.pciAddr, pciAddr)
renderPath := hygonRenderPathFromLinkWithRemote(e.linkPath, false)
assert.Equal(t, e.renderPath, renderPath)
pciToRender[pciAddr] = renderPath
}
renderToPCI := buildHygonRenderPathToPCIMap(pciToRender)
assert.Len(t, renderToPCI, 8)
for hcuIdx := 0; hcuIdx < 8; hcuIdx++ {
renderPath := hygonRenderPathForHCUIndex(hcuIdx)
pciAddr, ok := renderToPCI[renderPath]
assert.True(t, ok, "HCU[%d] render path %s should exist in map", hcuIdx, renderPath)
assert.Equal(t, entries[hcuIdx].pciAddr, pciAddr)
}
}
@@ -0,0 +1,71 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the permissions and limitations under the License.
package container_device
import (
"os"
"path"
"yunion.io/x/onecloud/pkg/hostman/options"
"yunion.io/x/onecloud/pkg/util/fileutils2"
"yunion.io/x/onecloud/pkg/util/procutils"
)
func hygonUseRemoteFS() bool {
return options.HostOptions.EnableRemoteExecutor
}
func hygonPathExists(path string) bool {
if hygonUseRemoteFS() {
return procutils.RemotePathExists(path)
}
return fileutils2.Exists(path)
}
func hygonReadDir(dirname string) ([]os.FileInfo, error) {
if hygonUseRemoteFS() {
return procutils.RemoteReadDir(dirname)
}
entries, err := os.ReadDir(dirname)
if err != nil {
return nil, err
}
files := make([]os.FileInfo, 0, len(entries))
for _, entry := range entries {
info, err := entry.Info()
if err != nil {
continue
}
files = append(files, info)
}
return files, nil
}
func hygonReadlink(path string) (string, error) {
if hygonUseRemoteFS() {
return procutils.RemoteReadlink(path)
}
return os.Readlink(path)
}
func hygonRenderPathFromLink(linkPath string) string {
return hygonRenderPathFromLinkWithRemote(linkPath, hygonUseRemoteFS())
}
func hygonRenderPathFromLinkWithRemote(linkPath string, remote bool) string {
if remote {
return linkPath
}
return path.Join("/dev/dri", path.Base(linkPath))
}
+41 -1
View File
@@ -292,6 +292,38 @@ func (man *isolatedDeviceManager) probeContainerAscendNPUs(enable, enableHami bo
}
}
func (man *isolatedDeviceManager) probeContainerHygonDCUs(enable, enableHami bool) {
log.Infof("==== hygon dcu probe start: enable=%v enableHami=%v", enable, enableHami)
devType := ContainerDeviceTypeHygonDcu
if enableHami {
devType = ContainerDeviceTypeHygonDcuHami
} else if !enable {
log.Infof("==== hygon dcu probe skipped: enable_container_hygon_dcu=false and enable_container_hygon_dcu_hami=false")
return
}
log.Infof("==== hygon dcu probe using manager type: %s", devType)
devman, err := GetContainerDeviceManager(devType)
if err != nil {
log.Errorf("==== hygon dcu probe failed: no container device manager %s found: %v", devType, err)
return
}
devs, err := devman.ProbeDevices()
if err != nil {
log.Warningf("==== hygon dcu probe failed: ProbeDevices error: %v", err)
return
}
if len(devs) == 0 {
log.Infof("==== hygon dcu probe finished: no devices found")
return
}
for idx, dev := range devs {
man.devices = append(man.devices, dev)
log.Infof("==== hygon dcu probe add device: idx=%d dev=%#v", idx, dev)
}
log.Infof("==== hygon dcu probe finished: total %d devices", len(devs))
}
func (man *isolatedDeviceManager) probeGPUS(skipGPUs bool, amdVgpuPFs, nvidiaVgpuPFs []string, enableWhitelist bool, whitelistModels []IsolatedDeviceModel) {
if skipGPUs {
return
@@ -464,6 +496,8 @@ type SIsolatedDeviceProbeOptions struct {
EnableCudaMps bool
EnableContainerAscendNpu bool
EnableContainerAscendNpuHAMI bool
EnableContainerHygonDCU bool
EnableContainerHygonDCUHAMI bool
EnableWhitelist bool
SriovNics, OvsOffloadNics []HostNic
@@ -473,11 +507,16 @@ type SIsolatedDeviceProbeOptions struct {
func (man *isolatedDeviceManager) ProbePCIDevices(opts *SIsolatedDeviceProbeOptions) {
man.devices = make([]IDevice, 0)
if man.host.IsContainerHost() {
isContainerHost := man.host.IsContainerHost()
log.Infof("==== ProbePCIDevices start: isContainerHost=%v hygonEnable=%v hygonHami=%v skipGPUs=%v",
isContainerHost, opts.EnableContainerHygonDCU, opts.EnableContainerHygonDCUHAMI, opts.SkipGPUs)
if isContainerHost {
man.probeContainerDevices()
man.probeContainerNvidiaGPUs(opts.EnableCudaHAMI, opts.EnableCudaMps)
man.probeContainerAscendNPUs(opts.EnableContainerAscendNpu, opts.EnableContainerAscendNpuHAMI)
man.probeContainerHygonDCUs(opts.EnableContainerHygonDCU, opts.EnableContainerHygonDCUHAMI)
} else {
log.Infof("==== ProbePCIDevices: not container host, hygon container probe will NOT run (use host_type=container for hygon dcu)")
devModels, err := man.getCustomIsolatedDeviceModels()
if err != nil {
log.Errorf("get isolated device devModels %s", err.Error())
@@ -492,6 +531,7 @@ func (man *isolatedDeviceManager) ProbePCIDevices(opts *SIsolatedDeviceProbeOpti
man.probeNVIDIAVgpus(opts.NvidiaVgpuPFs)
man.probeGPUS(opts.SkipGPUs, opts.AmdVgpuPFs, opts.NvidiaVgpuPFs, opts.EnableWhitelist, devModels)
}
log.Infof("==== ProbePCIDevices finished: total isolated devices=%d", len(man.devices))
}
type IsolatedDeviceModel struct {
+7
View File
@@ -279,6 +279,13 @@ type SHostOptions struct {
AscendNpuHamiShmPath string `help:"ascend npu hami shm path" default:"/opt/cloud/hami-shared-region"`
AscendNpuHamiLibvnpuPath string `help:"ascend npu hami libvnpu.so path" default:"/opt/cloud/hami/libvnpu.so"`
EnableContainerHygonDCU bool `help:"enable container hygon dcu" default:"true"`
EnableContainerHygonDCUHAMI bool `help:"enable container hygon dcu hami" default:"false"`
HygonHyhalPath string `help:"hygon hyhal driver path" default:"/opt/hyhal"`
HygonDtkPath string `help:"hygon dtk toolkit path" default:"/opt/dtk"`
HygonHySmiPath string `help:"hygon hy-smi path" default:"/opt/hyhal/bin/hy-smi"`
HygonVdevConfDir string `help:"hygon vdcu config directory" default:"/etc/vdev"`
EnableDirtyRecoverySeconds int `help:"Seconds to delay enable dirty guests recovery feature, default 15 minutes" default:"900"`
EnableContainerCniPortmap bool `help:"Use container cni portmap plugin" default:"false"`
DisableReconcileContainer bool `help:"disable reconcile container" default:"false"`
+10
View File
@@ -119,6 +119,16 @@ func normalizeLLMSkuDevice(dev *api.Device) {
if dev.SharingMode == "" {
dev.SharingMode = computeapi.DEVICE_SHARING_MODE_HAMI
}
case computeapi.CONTAINER_DEV_HYGON_DCU:
dev.DevType = computeapi.GPU_TYPE
if dev.SharingMode == "" {
dev.SharingMode = computeapi.DEVICE_SHARING_MODE_EXCLUSIVE
}
case computeapi.CONTAINER_DEV_HYGON_DCU_HAMI:
dev.DevType = computeapi.GPU_TYPE
if dev.SharingMode == "" {
dev.SharingMode = computeapi.DEVICE_SHARING_MODE_HAMI
}
}
if dev.SharingMode == "" {
dev.SharingMode = computeapi.DEVICE_SHARING_MODE_HAMI
+12
View File
@@ -50,6 +50,18 @@ func TestNormalizeLLMSkuDeviceLegacyTypes(t *testing.T) {
wantDevType: computeapi.GPU_TYPE,
wantSharingMode: computeapi.DEVICE_SHARING_MODE_HAMI,
},
{
name: "HYGON_DCU",
in: api.Device{DevType: computeapi.CONTAINER_DEV_HYGON_DCU},
wantDevType: computeapi.GPU_TYPE,
wantSharingMode: computeapi.DEVICE_SHARING_MODE_EXCLUSIVE,
},
{
name: "HYGON_DCU_HAMI",
in: api.Device{DevType: computeapi.CONTAINER_DEV_HYGON_DCU_HAMI},
wantDevType: computeapi.GPU_TYPE,
wantSharingMode: computeapi.DEVICE_SHARING_MODE_HAMI,
},
{
name: "explicit sharing_mode preserved",
in: api.Device{DevType: computeapi.CONTAINER_DEV_NVIDIA_GPU_SHARE, SharingMode: computeapi.DEVICE_SHARING_MODE_MPS},
@@ -27,7 +27,7 @@ var (
func init() {
IsolatedDevices = modules.NewComputeManager("isolated_device", "isolated_devices",
[]string{"ID", "Dev_type", "Sharing_mode",
"Model", "Addr", "Vendor_device_id", "Mdev_id",
"Model", "Vendor", "Addr", "Vendor_device_id", "Mdev_id",
"Host_id", "Host", "numa_node",
"Guest_id", "Guest", "Guest_status", "Device_path", "Render_path", "PCIE_Info", "Index", "Device_minor"},
[]string{})
@@ -36,7 +36,7 @@ func init() {
ServerIsolatedDevices = modules.NewJointComputeManager(
"guestisolateddevice",
"guestisolateddevices",
[]string{"Guest_ID", "Guest", "Isolated_device_ID", "Index", "Dev_type", "Sharing_mode",
[]string{"Guest_ID", "Guest", "Isolated_device_ID", "Index", "Dev_type", "Sharing_mode", "Vendor",
"Device_memory_size", "Sm_util_limit", "Network_index", "Disk_index"},
[]string{},
&Servers,
@@ -31,6 +31,7 @@ type DeviceListOptions struct {
DevType []string `help:"filter by dev_type"`
Model []string `help:"filter by model"`
Vendor []string `help:"filter by vendor, e.g. HYGON, NVIDIA"`
Addr []string `help:"filter by addr"`
DevicePath []string `help:"filter by device path"`
VendorDeviceId []string `help:"filter by vendor device id(PCIID)"`
+9
View File
@@ -19,6 +19,15 @@ import (
"testing"
)
func TestRemotePathExists(t *testing.T) {
if !RemotePathExists("/") {
t.Errorf("RemotePathExists(\"/\") want true")
}
if RemotePathExists("/tmp/__0_1_2_3_a_b_c_d____") {
t.Errorf("RemotePathExists missing file want false")
}
}
func TestStat(t *testing.T) {
cases := []struct {
filename string
+36
View File
@@ -0,0 +1,36 @@
// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package procutils
import (
"strings"
"yunion.io/x/pkg/errors"
)
// RemotePathExists checks whether path exists on the host via executor.
func RemotePathExists(path string) bool {
_, err := RemoteStat(path)
return err == nil
}
// RemoteReadlink resolves a symlink on the host via executor.
func RemoteReadlink(path string) (string, error) {
out, err := NewRemoteCommandAsFarAsPossible("readlink", "-f", path).Output()
if err != nil {
return "", errors.Wrapf(err, "readlink -f %s: %s", path, out)
}
return strings.TrimSpace(string(out)), nil
}