Repository: koordinator-sh/koordinator

Issue:
koord-scheduler supports NUMA Topology Alignment policy
<!-- Please only use this template for submitting proposal -->

**What is your proposal**:

The Topology Manager is a kubelet component that aims to coordinate the set of components that are responsible for these optimizations. After Topology Manager was introduced the problem of launching pod in the cluster where worker nodes have different NUMA topology and different amount of resources in that topology became actual. The Pod could be scheduled in the node where the total amount of resources is enough, but resource distribution could not satisfy the appropriate Topology policy. 

When allocating CPUs for LSE/LSR Pods to implement static binding, NUMA Topology also needs to be considered.

More details please refer to #96 

TODO:
- [x] koordlet reports complete NUMA Node information
- [x] koordlet supports NUMA Share Pool
- [x] koord-scheduler supports NUMA Topology Alignment scheduling with CPU/Memory
- [x] koord-scheduler supports scoring
- [x] [koord-scheduler supports numa-node level scoring](https://github.com/koordinator-sh/koordinator/issues/1680)
- [x] [koord-scheduler supports NUMA Topology Alignment scheduling with Device](https://github.com/koordinator-sh/koordinator/pull/1774)
- [x] koord-scheduler supports Pod allocate from Reservation according NUMA Topology
- [x] koord-scheduler supports preemption according NUMA Topology

Discussion:
@FillZpp: /milestone v1.2@AdrianMachao: do you need help? i can do one task@songtao98:  - [ ] koord-scheduler supports Pod allocate from Reservation according NUMA Topology 1.6
- [ ]  koord-scheduler supports preemption according NUMA Topology someday@songtao98:  - [ ] koord-scheduler supports Pod allocate from Reservation according NUMA Topology 1.6
- [ ]  koord-scheduler supports preemption according NUMA Topology someday@ZiMengSheng: Already implemented but lack a concept doc!

Solution patch:
diff --git a/pkg/features/koordlet_features.go b/pkg/features/koordlet_features.go
index cb14d7cd..cb76621a 100644
--- a/pkg/features/koordlet_features.go
+++ b/pkg/features/koordlet_features.go
@@ -133,6 +133,14 @@ const (
 	//
 	// ColdPageCollector enables coldPageCollector feature of koordlet.
 	ColdPageCollector featuregate.Feature = "ColdPageCollector"
+
+	// HugePageReport enables hugepage collector feature of koordlet.
+	// This feature supports reporting of hugepages.
+	// The koord-scheduler will allocate hugepage information based on the user's hugepage request and add it to the Pod's annotations.
+	// Format: scheduling.koordinator.sh/resource-status: '{"numaNodeResources":[{"node":1,"resources":{"hugepages-1Gi":"50Gi"}}]}'.
+	// Backend applications can enable the hugepages based on the allocation results.
+	// For example, the CSI mounts the pre-allocated hugepages into the pod.
+	HugePageReport featuregate.Feature = "HugePageReport"
 )
 
 func init() {
@@ -161,6 +169,7 @@ var (
 		PSICollector:           {Default: false, PreRelease: featuregate.Alpha},
 		BlkIOReconcile:         {Default: false, PreRelease: featuregate.Alpha},
 		ColdPageCollector:      {Default: false, PreRelease: featuregate.Alpha},
+		HugePageReport:         {Default: false, PreRelease: featuregate.Alpha},
 	}
 )
 
diff --git a/pkg/koordlet/metricsadvisor/collectors/nodeinfo/node_info_collector.go b/pkg/koordlet/metricsadvisor/collectors/nodeinfo/node_info_collector.go
index d894bb6c..22c79338 100644
--- a/pkg/koordlet/metricsadvisor/collectors/nodeinfo/node_info_collector.go
+++ b/pkg/koordlet/metricsadvisor/collectors/nodeinfo/node_info_collector.go
@@ -23,6 +23,7 @@ import (
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/klog/v2"
 
+	"github.com/koordinator-sh/koordinator/pkg/features"
 	"github.com/koordinator-sh/koordinator/pkg/koordlet/metriccache"
 	"github.com/koordinator-sh/koordinator/pkg/koordlet/metrics"
 	"github.com/koordinator-sh/koordinator/pkg/koordlet/metricsadvisor/framework"
@@ -111,6 +112,9 @@ func (n *nodeInfoCollector) collectNodeNUMAInfo() error {
 		metrics.RecordCollectNodeNUMAInfoStatus(err)
 		return err
 	}
+	if features.DefaultKoordletFeatureGate.Enabled(features.HugePageReport) {
+		koordletutil.GetAndMergeHugepageToNumaInfo(nodeNUMAInfo)
+	}
 	klog.V(6).Infof("collect NUMA info successfully, info %+v", nodeNUMAInfo)
 
 	n.storage.Set(metriccache.NodeNUMAInfoKey, nodeNUMAInfo)
diff --git a/pkg/koordlet/metricsadvisor/devices/gpu/collector_gpu_linux.go b/pkg/koordlet/metricsadvisor/devices/gpu/collector_gpu_linux.go
index 3cc2f5ae..c86c85aa 100644
--- a/pkg/koordlet/metricsadvisor/devices/gpu/collector_gpu_linux.go
+++ b/pkg/koordlet/metricsadvisor/devices/gpu/collector_gpu_linux.go
@@ -51,6 +51,9 @@ type device struct {
 	Minor       int32 // index starting from 0
 	DeviceUUID  string
 	MemoryTotal uint64
+	NodeID      int32
+	PCIE        string
+	BusID       string
 	Device      nvml.Device
 }
 
@@ -114,10 +117,21 @@ func (g *gpuDeviceManager) initGPUData() error {
 		if ret != nvml.SUCCESS {
 			return fmt.Errorf("unable to get device memory info: %v", nvml.ErrorString(ret))
 		}
+		pciInfo, ret := gpudevice.GetPciInfo()
+		if ret != nvml.SUCCESS {
+			return fmt.Errorf("unable to get pci info: %v", nvml.ErrorString(ret))
+		}
+		nodeID, pcie, busID, err := parseGPUPCIInfo(pciInfo.BusIdLegacy)
+		if err != nil {
+			return err
+		}
 		devices[deviceIndex] = &device{
 			DeviceUUID:  uuid,
 			Minor:       int32(minor),
 			MemoryTotal: memory.Total,
+			NodeID:      nodeID,
+			PCIE:        pcie,
+			BusID:       busID,
 			Device:      gpudevice,
 		}
 	}
@@ -134,7 +148,14 @@ func (g *gpuDeviceManager) deviceInfos() metriccache.Devices {
 	defer g.RUnlock()
 	gpuDevices := util.GPUDevices{}
 	for _, device := range g.devices {
-		gpuDevices = append(gpuDevices, util.GPUDeviceInfo{UUID: device.DeviceUUID, Minor: device.Minor, MemoryTotal: device.MemoryTotal})
+		gpuDevices = append(gpuDevices, util.GPUDeviceInfo{
+			UUID:        device.DeviceUUID,
+			Minor:       device.Minor,
+			MemoryTotal: device.MemoryTotal,
+			NodeID:      device.NodeID,
+			PCIE:        device.PCIE,
+			BusID:       device.BusID,
+		})
 	}
 
 	return gpuDevices
@@ -179,7 +200,7 @@ func (g *gpuDeviceManager) getNodeGPUUsage() []metriccache.MetricSample {
 	return gpuMetrics
 }
 
-func (g *gpuDeviceManager) getPodOrContinerTotalGPUUsageOfPIDs(id string, isPodID bool, pids []uint32) []metriccache.MetricSample {
+func (g *gpuDeviceManager) getPodOrContainerTotalGPUUsageOfPIDs(id string, isPodID bool, pids []uint32) []metriccache.MetricSample {
 	if id == "" {
 		klog.Warning("id is empty")
 		return nil
@@ -261,7 +282,7 @@ func (g *gpuDeviceManager) getPodGPUUsage(uid, podParentDir string, cs []corev1.
 	if err != nil {
 		return nil, fmt.Errorf("failed to get pid, error: %v", err)
 	}
-	return g.getPodOrContinerTotalGPUUsageOfPIDs(uid, true, pids), nil
+	return g.getPodOrContainerTotalGPUUsageOfPIDs(uid, true, pids), nil
 }
 
 func (g *gpuDeviceManager) getContainerGPUUsage(containerID, podParentDir string, c *corev1.ContainerStatus) ([]metriccache.MetricSample, error) {
@@ -273,7 +294,7 @@ func (g *gpuDeviceManager) getContainerGPUUsage(containerID, podParentDir string
 	if err != nil {
 		return nil, fmt.Errorf("failed to get pid, error: %v", err)
 	}
-	return g.getPodOrContinerTotalGPUUsageOfPIDs(containerID, false, currentPIDs), nil
+	return g.getPodOrContainerTotalGPUUsageOfPIDs(containerID, false, currentPIDs), nil
 }
 
 func (g *gpuDeviceManager) collectGPUUsage() {
diff --git a/pkg/koordlet/metricsadvisor/devices/gpu/helper.go b/pkg/koordlet/metricsadvisor/devices/gpu/helper.go
new file mode 100644
index 00000000..7c85b989
--- /dev/null
+++ b/pkg/koordlet/metricsadvisor/devices/gpu/helper.go
@@ -0,0 +1,83 @@
+/*
+Copyright 2022 The Koordinator Authors.
+
+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 gpu
+
+import (
+	"bytes"
+	"fmt"
+	"os"
+	"path/filepath"
+	"regexp"
+	"strconv"
+	"strings"
+
+	"github.com/koordinator-sh/koordinator/pkg/koordlet/util/system"
+)
+
+var (
+	pcieRegexp = regexp.MustCompile(`pci\d{4}:[0-9a-fA-F]{2}`)
+)
+
+func parseGPUPCIInfo(busIdLegacy [16]int8) (int32, string, string, error) {
+	busIDBuilder := &strings.Builder{}
+	for _, v := range busIdLegacy {
+		if v != 0 {
+			busIDBuilder.WriteByte(byte(v))
+		}
+	}
+	busID := strings.ToLower(busIDBuilder.String())
+	nodeID, err := getNUMANodeID(busID)
+	if err != nil {
+		return 0, "", "", fmt.Errorf("failed to parse NUMA Node ID, err: %w", err)
+	}
+	pcie, err := getPCIERootComplexID(busID)
+	if err != nil {
+		return 0, "", "", fmt.Errorf("failed to parse PCIE ID, err: %w", err)
+	}
+	return nodeID, pcie, busID, nil
+}
+
+func getPCIERootComplexID(bdf string) (string, error) {
+	path, err := filepath.EvalSymlinks(filepath.Join(system.GetPCIDeviceDir(), bdf))
+	if err != nil {
+		return "", err
+	}
+	return parsePCIEID(path), err
+}
+
+func parsePCIEID(path string) string {
+	result := pcieRegexp.FindAllStringSubmatch(path, -1)
+	if len(result) == 0 || len(result[0]) == 0 {
+		return ""
+	}
+	return result[0][0]
+}
+
+func getNUMANodeID(bdf string) (int32, error) {
+	data, err := os.ReadFile(filepath.Join(system.GetPCIDeviceDir(), bdf, "numa_node"))
+	if err != nil {
+		return -1, err
+	}
+	nodeID, err := strconv.Atoi(string(bytes.TrimSpace(data)))
+	if err != nil {
+		return 0, err
+	}
+	if nodeID == -1 {
+		nodeID = 0
+	}
+	return int32(nodeID), nil
+}
diff --git a/pkg/koordlet/statesinformer/impl/states_device_linux.go b/pkg/koordlet/statesinformer/impl/states_device_linux.go
index 2e34bb9a..04c6e911 100644
--- a/pkg/koordlet/statesinformer/impl/states_device_linux.go
+++ b/pkg/koordlet/statesinformer/impl/states_device_linux.go
@@ -162,6 +162,17 @@ func (s *statesInformer) buildGPUDevice() []schedulingv1alpha1.DeviceInfo {
 			health = false
 		}
 		s.gpuMutex.RUnlock()
+
+		var topology *schedulingv1alpha1.DeviceTopology
+		if gpu.NodeID >= 0 && gpu.PCIE != "" && gpu.BusID != "" {
+			topology = &schedulingv1alpha1.DeviceTopology{
+				SocketID: -1,
+				NodeID:   gpu.NodeID,
+				PCIEID:   gpu.PCIE,
+				BusID:    gpu.BusID,
+			}
+		}
+
 		deviceInfos = append(deviceInfos, schedulingv1alpha1.DeviceInfo{
 			UUID:   gpu.UUID,
 			Minor:  &gpu.Minor,
@@ -172,6 +183,7 @@ func (s *statesInformer) buildGPUDevice() []schedulingv1alpha1.DeviceInfo {
 				extension.ResourceGPUMemory:      *resource.NewQuantity(int64(gpu.MemoryTotal), resource.BinarySI),
 				extension.ResourceGPUMemoryRatio: *resource.NewQuantity(100, resource.DecimalSI),
 			},
+			Topology: topology,
 		})
 	}
 	return deviceInfos
diff --git a/pkg/koordlet/statesinformer/impl/states_noderesourcetopology.go b/pkg/koordlet/statesinformer/impl/states_noderesourcetopology.go
index 6abeacc7..a79ae85c 100644
--- a/pkg/koordlet/statesinformer/impl/states_noderesourcetopology.go
+++ b/pkg/koordlet/statesinformer/impl/states_noderesourcetopology.go
@@ -766,7 +766,7 @@ func (s *nodeTopoInformer) calTopologyZoneList(nodeCPUInfo *metriccache.NodeCPUI
 	}
 	nodeNUMAInfo, ok := nodeNUMAInfoRaw.(*koordletutil.NodeNUMAInfo)
 	if !ok {
-		klog.Fatalf("type error, expect %T， but got %T", koordletutil.NodeNUMAInfo{}, nodeNUMAInfoRaw)
+		klog.Fatalf("type error, expect %T, but got %T", koordletutil.NodeNUMAInfo{}, nodeNUMAInfoRaw)
 	}
 	nodeNum := len(nodeNUMAInfo.NUMAInfos)
 
@@ -785,18 +785,42 @@ func (s *nodeTopoInformer) calTopologyZoneList(nodeCPUInfo *metriccache.NodeCPUI
 		} else {
 			cpuQuant = resource.MustParse("0")
 		}
+
+		hugepage2Mbyte := uint64(0)
+		hugepage1Gbyte := uint64(0)
+		hugepage2MQuant := *resource.NewQuantity(0, resource.BinarySI)
+		hugepage1GQuant := *resource.NewQuantity(0, resource.BinarySI)
+
+		if features.DefaultKoordletFeatureGate.Enabled(features.HugePageReport) {
+			hugepageInfos, ok := nodeNUMAInfo.HugePagesMap[int32(i)]
+			if ok {
+				if _, ok := hugepageInfos[koordletutil.Hugepage2Mkbyte]; ok {
+					hugepage2Mbyte = hugepageInfos[koordletutil.Hugepage2Mkbyte].MemTotalBytes()
+					hugepage2MQuant = *resource.NewQuantity(int64(hugepage2Mbyte), resource.BinarySI)
+				}
+				if _, ok := hugepageInfos[koordletutil.Hugepage1Gkbyte]; ok {
+					hugepage1Gbyte = hugepageInfos[koordletutil.Hugepage1Gkbyte].MemTotalBytes()
+					hugepage1GQuant = *resource.NewQuantity(int64(hugepage1Gbyte), resource.BinarySI)
+				}
+			}
+		}
+
 		var memQuant resource.Quantity
 		memInfo, ok := nodeNUMAInfo.MemInfoMap[int32(i)]
 		if ok {
-			memQuant = *resource.NewQuantity(int64(memInfo.MemTotalBytes()), resource.BinarySI)
+			memQuant = *resource.NewQuantity(int64(memInfo.MemTotalBytes()-hugepage2Mbyte-hugepage1Gbyte), resource.BinarySI)
 		} else {
-			memQuant = resource.MustParse("0")
+			memQuant = *resource.NewQuantity(0, resource.BinarySI)
 		}
+
 		zoneName := util.GenNodeZoneName(i)
 		zoneResourceList[zoneName] = corev1.ResourceList{
-			corev1.ResourceCPU:    cpuQuant,
-			corev1.ResourceMemory: memQuant,
+			corev1.ResourceCPU:                     cpuQuant,
+			corev1.ResourceMemory:                  memQuant,
+			corev1.ResourceHugePagesPrefix + "2Mi": hugepage2MQuant,
+			corev1.ResourceHugePagesPrefix + "1Gi": hugepage1GQuant,
 		}
+
 	}
 	zoneList := util.ZoneResourceListToZoneList(zoneResourceList)
 
diff --git a/pkg/koordlet/util/device.go b/pkg/koordlet/util/device.go
index 18d5d5cb..eac73a7d 100644
--- a/pkg/koordlet/util/device.go
+++ b/pkg/koordlet/util/device.go
@@ -38,4 +38,7 @@ type GPUDeviceInfo struct {
 	// Minor represents the Minor number of Devices, starting from 0
 	Minor       int32  `json:"minor,omitempty"`
 	MemoryTotal uint64 `json:"memory-total,omitempty"`
+	NodeID      int32  `json:"nodeID"`
+	PCIE        string `json:"pcie,omitempty"`
+	BusID       string `json:"busID,omitempty"`
 }
diff --git a/pkg/koordlet/util/meminfo.go b/pkg/koordlet/util/meminfo.go
index b4dd8eda..7bdcd3fb 100644
--- a/pkg/koordlet/util/meminfo.go
+++ b/pkg/koordlet/util/meminfo.go
@@ -29,6 +29,11 @@ import (
 	"github.com/koordinator-sh/koordinator/pkg/koordlet/util/system"
 )
 
+const (
+	Hugepage1Gkbyte = 1048576
+	Hugepage2Mkbyte = 2048
+)
+
 // MemInfo is the content of system /proc/meminfo.
 // NOTE: the unit of each field is KiB.
 type MemInfo struct {
@@ -78,6 +83,15 @@ type MemInfo struct {
 	DirectMap1G       uint64 `json:"direct_map_1G"`
 }
 
+type HugePagesInfo struct {
+	NumPages uint64 `json:"numPages,omitempty"`
+	PageSize uint64 `json:"pageSize,omitempty"`
+}
+
+func (i *HugePagesInfo) MemTotalBytes() uint64 {
+	return i.PageSize * i.NumPages * 1024
+}
+
 // MemTotalBytes returns the mem info's total bytes.
 func (i *MemInfo) MemTotalBytes() uint64 {
 	return i.MemTotal * 1024
@@ -148,6 +162,68 @@ func readMemInfo(path string, isNUMA bool) (*MemInfo, error) {
 	return &info, nil
 }
 
+func GetHugePagesInfo(nodeDir string) (map[uint64]*HugePagesInfo, error) {
+	hugePagesInfo := map[uint64]*HugePagesInfo{
+		Hugepage1Gkbyte: {
+			NumPages: 0,
+			PageSize: Hugepage1Gkbyte,
+		},
+		Hugepage2Mkbyte: {
+			NumPages: 0,
+			PageSize: Hugepage2Mkbyte,
+		},
+	}
+	hugepageDir := system.GetNUMAHugepagesDir(nodeDir)
+	hugeDirs, err := os.ReadDir(hugepageDir)
+	if err != nil {
+		klog.Warningf("failed to read hugepage dir %s, err: %w", hugepageDir, err)
+		return hugePagesInfo, err
+	}
+
+	for _, st := range hugeDirs {
+		nameArray := strings.Split(st.Name(), "-")
+		if len(nameArray) < 2 {
+			klog.Warningf("Split '-' failed, the directory name %s is invalid, it must be either hugepages-1048576kB or hugepages-2048kB, nameArray: %v, len(nameArray): %d", st.Name(), nameArray, len(nameArray))
+			continue
+		}
+		pageSizeArray := strings.Split(nameArray[1], "kB")
+		if len(pageSizeArray) < 1 {
+			klog.Warningf("Split 'kB' failed, the directory name %s is invalid, it must be either hugepages-1048576kB or hugepages-2048kB, pageSizeArray: %v, len(pageSizeArray): %d", st.Name(), pageSizeArray, len(pageSizeArray))
+			continue
+		}
+		pageSize, err := strconv.ParseUint(pageSizeArray[0], 10, 64)
+		if err != nil {
+			klog.Warningf("hugepage pageSize parse failed, it must be either hugepages-1048576kB or hugepages-2048kB, the dir: %s, origin data: %s, err: %v", st.Name(), pageSizeArray[0], err)
+			continue
+		}
+
+		if _, ok := hugePagesInfo[pageSize]; !ok {
+			klog.Warningf("An abnormal hugepage %d, the dir: %s", pageSize, st.Name())
+			continue
+		}
+
+		nrPath := system.GetNUMAHugepagesNrPath(nodeDir, st.Name())
+		val, err := os.ReadFile(nrPath)
+		if err != nil {
+			return hugePagesInfo, err
+		}
+
+		var numPages uint64
+		// we use sscanf as the file as a new-line that trips up ParseUint
+		// it returns the number of tokens successfully parsed, so if
+		// n != 1, it means we were unable to parse a number from the file
+		n, err := fmt.Sscanf(string(val), "%d", &numPages)
+		if err != nil || n != 1 {
+			klog.Warningf("could not parse file nr_hugepage for %s, contents %q", st.Name(), string(val))
+			continue
+		}
+
+		hugePagesInfo[pageSize].NumPages = numPages
+	}
+
+	return hugePagesInfo, nil
+}
+
 func GetMemInfo() (*MemInfo, error) {
 	memInfoPath := system.GetProcFilePath(system.ProcMemInfoName)
 	memInfo, err := readMemInfo(memInfoPath, false)
@@ -158,15 +234,17 @@ func GetMemInfo() (*MemInfo, error) {
 }
 
 type NUMAInfo struct {
-	NUMANodeID int32    `json:"numaNodeID,omitempty"`
-	MemInfo    *MemInfo `json:"memInfo,omitempty"`
+	NUMANodeID int32                     `json:"numaNodeID,omitempty"`
+	MemInfo    *MemInfo                  `json:"memInfo,omitempty"`
+	HugePages  map[uint64]*HugePagesInfo `json:"hugePages,omitempty"`
 }
 
 // NodeNUMAInfo represents the node NUMA information.
 // Currently, it just contains the meminfo for each NUMA node.
 type NodeNUMAInfo struct {
-	NUMAInfos  []NUMAInfo         `json:"numaInfos,omitempty"`
-	MemInfoMap map[int32]*MemInfo `json:"memInfoMap,omitempty"` // NUMANodeID -> MemInfo
+	NUMAInfos    []NUMAInfo                          `json:"numaInfos,omitempty"`
+	MemInfoMap   map[int32]*MemInfo                  `json:"memInfoMap,omitempty"` // NUMANodeID -> MemInfo
+	HugePagesMap map[int32]map[uint64]*HugePagesInfo `json:"hugePagesMap,omitempty"`
 }
 
 // GetNodeNUMAInfo gets the node NUMA information with the pre-configured sysfs path.
@@ -229,3 +307,73 @@ func GetNodeNUMAInfo() (*NodeNUMAInfo, error) {
 
 	return result, nil
 }
+
+// GetNodeHugePagesInfo gets the node NUMA hugepage information with pre-configured sysfs path.
+func GetNodeHugePagesInfo() (map[int32]map[uint64]*HugePagesInfo, error) {
+	numaNodeParentDir := system.GetSysNUMADir()
+	nodeDirs, err := os.ReadDir(numaNodeParentDir)
+	if err != nil {
+		return nil, fmt.Errorf("failed to read NUMA dir, err: %w", err)
+	}
+
+	hugePagesMap := make(map[int32]map[uint64]*HugePagesInfo)
+	for _, n := range nodeDirs {
+		dirName := n.Name() // assert string pattern `nodeX`
+		if len(dirName) < 4 || dirName[:4] != "node" {
+			klog.V(4).Infof("failed to get node NUMA info, err: invalid dir name %s", dirName)
+			return nil, fmt.Errorf("failed to get node NUMA info, err: invalid dir name %s", dirName)
+		}
+
+		nodeIDRaw, err := strconv.ParseInt(dirName[4:], 10, 32)
+		if err != nil {
+			klog.V(4).Infof("failed to parse NUMA ID, err: invalid dir name %s, err %v", dirName, err)
+			return nil, fmt.Errorf("failed to get node NUMA info, err: invalid dir name %s", dirName)
+		}
+		nodeID := int32(nodeIDRaw)
+		hugepageInfos, err := GetHugePagesInfo(dirName)
+		if err != nil {
+			klog.V(4).Infof("failed to read hugepage info, just set empty hugepage info, dir %s, err: %v", dirName, err)
+			return nil, fmt.Errorf("failed to read hugepage info, just set empty hugepage info, dir %s, err: %v", dirName, err)
+		}
+		hugePagesMap[nodeID] = hugepageInfos
+	}
+	return hugePagesMap, nil
+}
+
+func GetAndMergeHugepageToNumaInfo(numaInfo *NodeNUMAInfo) *NodeNUMAInfo {
+	var hugePagesMap map[int32]map[uint64]*HugePagesInfo
+	var err error
+	hugePagesMap, err = GetNodeHugePagesInfo()
+	if err != nil {
+		hugePagesMap = map[int32]map[uint64]*HugePagesInfo{
+			0: {
+				Hugepage1Gkbyte: {
+					NumPages: 0,
+					PageSize: Hugepage1Gkbyte,
+				},
+				Hugepage2Mkbyte: {
+					NumPages: 0,
+					PageSize: Hugepage2Mkbyte,
+				},
+			},
+			1: {
+				Hugepage1Gkbyte: {
+					NumPages: 0,
+					PageSize: Hugepage1Gkbyte,
+				},
+				Hugepage2Mkbyte: {
+					NumPages: 0,
+					PageSize: Hugepage2Mkbyte,
+				},
+			},
+		}
+	}
+	numaInfo.HugePagesMap = hugePagesMap
+	for i, info := range numaInfo.NUMAInfos {
+		if _, ok := hugePagesMap[info.NUMANodeID]; ok {
+			numaInfo.NUMAInfos[i].HugePages = hugePagesMap[info.NUMANodeID]
+		}
+	}
+
+	return numaInfo
+}
diff --git a/pkg/koordlet/util/system/system_file.go b/pkg/koordlet/util/system/system_file.go
index 8c781a27..3e47eab8 100644
--- a/pkg/koordlet/util/system/system_file.go
+++ b/pkg/koordlet/util/system/system_file.go
@@ -36,10 +36,13 @@ const (
 	SysctlSubDir          = "sys"
 	ProcCPUInfoName       = "cpuinfo"
 	KernelCmdlineFileName = "cmdline"
+	HugepageDir           = "hugepages"
+	nrPath                = "nr_hugepages"
 
 	KernelSchedGroupIdentityEnable = "kernel/sched_group_identity_enabled"
 
-	SysNUMASubDir = "bus/node/devices"
+	SysNUMASubDir   = "bus/node/devices"
+	SysPCIDeviceDir = "bus/pci/devices"
 
 	SysCPUSMTActiveSubPath       = "devices/system/cpu/smt/active"
 	SysIntelPStateNoTurboSubPath = "devices/system/cpu/intel_pstate/no_turbo"
@@ -102,6 +105,14 @@ func GetNUMAMemInfoPath(numaNodeSubDir string) string {
 	return filepath.Join(Conf.SysRootDir, SysNUMASubDir, numaNodeSubDir, ProcMemInfoName)
 }
 
+func GetNUMAHugepagesDir(numaNodeSubDir string) string {
+	return filepath.Join(Conf.SysRootDir, SysNUMASubDir, numaNodeSubDir, HugepageDir)
+}
+
+func GetNUMAHugepagesNrPath(numaNodeSubDir string, page string) string {
+	return filepath.Join(Conf.SysRootDir, SysNUMASubDir, numaNodeSubDir, HugepageDir, page, nrPath)
+}
+
 func GetCPUInfoPath() string {
 	return filepath.Join(Conf.ProcRootDir, ProcCPUInfoName)
 }
@@ -118,6 +129,8 @@ func GetProcSysFilePath(file string) string {
 	return filepath.Join(Conf.ProcRootDir, SysctlSubDir, file)
 }
 
+func GetPCIDeviceDir() string { return filepath.Join(Conf.SysRootDir, SysPCIDeviceDir) }
+
 var _ utilsysctl.Interface = &ProcSysctl{}
 
 // ProcSysctl implements Interface by reading and writing files under /proc/sys
diff --git a/pkg/scheduler/plugins/coscheduling/core/core.go b/pkg/scheduler/plugins/coscheduling/core/core.go
index 832393e4..642faa30 100644
--- a/pkg/scheduler/plugins/coscheduling/core/core.go
+++ b/pkg/scheduler/plugins/coscheduling/core/core.go
@@ -272,7 +272,7 @@ func (pgMgr *PodGroupManager) PreFilter(ctx context.Context, pod *corev1.Pod) er
 // ii. If non-strict mode, we will do nothing.
 func (pgMgr *PodGroupManager) PostFilter(ctx context.Context, pod *corev1.Pod, handle framework.Handle, pluginName string, filteredNodeStatusMap framework.NodeToStatusMap) (*framework.PostFilterResult, *framework.Status) {
 	if !util.IsPodNeedGang(pod) {
-		return &framework.PostFilterResult{}, framework.NewStatus(framework.Unschedulable, "")
+		return &framework.PostFilterResult{}, framework.NewStatus(framework.Unschedulable)
 	}
 	gang := pgMgr.GetGangByPod(pod)
 	if gang == nil {
diff --git a/pkg/scheduler/plugins/deviceshare/device_allocator.go b/pkg/scheduler/plugins/deviceshare/device_allocator.go
index cad7a6b9..4ea67070 100644
--- a/pkg/scheduler/plugins/deviceshare/device_allocator.go
+++ b/pkg/scheduler/plugins/deviceshare/device_allocator.go
@@ -68,6 +68,9 @@ type AutopilotAllocator struct {
 }
 
 func (a *AutopilotAllocator) Prepare() *framework.Status {
+	if a.requestsPerInstance != nil {
+		return nil
+	}
 	state := a.state
 	nodeDevice := a.nodeDevice
 	requestsPerInstance, desiredCountPerDeviceType, status := a.calcRequestsAndCountByDeviceType(state.podRequests, nodeDevice, state.hints)
@@ -88,31 +91,26 @@ func (a *AutopilotAllocator) Prepare() *framework.Status {
 
 func (a *AutopilotAllocator) Allocate(
 	required, preferred map[schedulingv1alpha1.DeviceType]sets.Int,
-	requiredDeviceResources, preemptibleFreeDevices map[schedulingv1alpha1.DeviceType]deviceResources,
+	requiredDeviceResources, preemptibleDeviceResources map[schedulingv1alpha1.DeviceType]deviceResources,
 ) (apiext.DeviceAllocations, *framework.Status) {
-	state := a.state
-	nodeDevice := a.nodeDevice
-	if a.requestsPerInstance == nil {
-		if status := a.Prepare(); !status.IsSuccess() {
-			return nil, status
-		}
+	if status := a.Prepare(); !status.IsSuccess() {
+		return nil, status
 	}
 
-	nodeDevice = a.filterNodeDevice(nodeDevice, state.hints, state.hintSelectors, requiredDeviceResources, preemptibleFreeDevices)
-
+	nodeDevice := a.filterNodeDevice(requiredDeviceResources, preemptibleDeviceResources)
 	requestCtx := &requestContext{
 		pod:                       a.pod,
 		node:                      a.node,
-		hints:                     state.hints,
-		hintSelectors:             state.hintSelectors,
-		required:                  required,
-		preferred:                 preferred,
+		hints:                     a.state.hints,
+		hintSelectors:             a.state.hintSelectors,
 		requestsPerInstance:       a.requestsPerInstance,
 		desiredCountPerDeviceType: a.desiredCountPerDeviceType,
 		allocationScorer:          a.scorer,
+		required:                  required,
+		preferred:                 preferred,
 	}
 
-	deviceAllocations, status := a.tryJointAllocate(requestCtx, state.jointAllocate, nodeDevice)
+	deviceAllocations, status := a.tryJointAllocate(requestCtx, a.state.jointAllocate, nodeDevice)
 	if !status.IsSuccess() {
 		return nil, status
 	}
@@ -131,16 +129,13 @@ func (a *AutopilotAllocator) Allocate(
 }
 
 func (a *AutopilotAllocator) filterNodeDevice(
-	nodeDevice *nodeDevice,
-	hints apiext.DeviceAllocateHints,
-	hintSelectors map[schedulingv1alpha1.DeviceType][2]labels.Selector,
-	requiredDeviceResources, preemptibleFreeDevices map[schedulingv1alpha1.DeviceType]deviceResources,
+	requiredDeviceResources, preemptibleDeviceResources map[schedulingv1alpha1.DeviceType]deviceResources,
 ) *nodeDevice {
 	devices := map[schedulingv1alpha1.DeviceType][]int{}
 	for deviceType := range a.requestsPerInstance {
-		deviceInfos := nodeDevice.deviceInfos[deviceType]
+		deviceInfos := a.nodeDevice.deviceInfos[deviceType]
 		minors := sets.NewInt()
-		selector := hintSelectors[deviceType][0]
+		selector := a.state.hintSelectors[deviceType][0]
 		for _, deviceInfo := range deviceInfos {
 			if a.numaNodes != nil {
 				if deviceInfo.Topology == nil || !a.numaNodes.IsSet(int(deviceInfo.Topology.NodeID)) {
@@ -155,7 +150,7 @@ func (a *AutopilotAllocator) filterNodeDevice(
 			devices[deviceType] = minors.UnsortedList()
 		}
 	}
-	nodeDevice = nodeDevice.filter(devices, hints, requiredDeviceResources, preemptibleFreeDevices)
+	nodeDevice := a.nodeDevice.filter(devices, a.state.hints, requiredDeviceResources, preemptibleDeviceResources)
 	return nodeDevice
 }
 
@@ -502,3 +497,28 @@ func newPreferredPCIes(nodeDevice *nodeDevice, deviceType schedulingv1alpha1.Dev
 	}
 	return pcies
 }
+
+func (a *AutopilotAllocator) score(
+	requiredDeviceResources, preemptibleDeviceResources map[schedulingv1alpha1.DeviceType]deviceResources,
+) (int64, *framework.Status) {
+	if status := a.Prepare(); !status.IsSuccess() {
+		return 0, status
+	}
+
+	nodeDevice := a.filterNodeDevice(requiredDeviceResources, preemptibleDeviceResources)
+
+	var finalScore int64
+	for deviceType, requests := range a.requestsPerInstance {
+		if quotav1.IsZero(requests) {
+			continue
+		}
+		deviceTotal := nodeDevice.deviceTotal[deviceType]
+		if len(deviceTotal) > 0 {
+			score := a.scorer.scoreNode(requests, deviceTotal, nodeDevice.deviceFree[deviceType])
+			// TODO(joseph): Maybe different device types have different weights, but that's not currently supported.
+			finalScore += score
+		}
+	}
+
+	return finalScore, nil
+}
diff --git a/pkg/scheduler/plugins/deviceshare/device_cache.go b/pkg/scheduler/plugins/deviceshare/device_cache.go
index 4e864a6a..04389ad1 100644
--- a/pkg/scheduler/plugins/deviceshare/device_cache.go
+++ b/pkg/scheduler/plugins/deviceshare/device_cache.go
@@ -311,62 +311,6 @@ func getVFAllocations(allocations []*apiext.DeviceAllocation) *VFAllocation {
 	return vfAllocation
 }
 
-func (n *nodeDevice) score(
-	podRequests map[schedulingv1alpha1.DeviceType]corev1.ResourceList,
-	requiredDeviceResources, preemptibleDeviceResources map[schedulingv1alpha1.DeviceType]deviceResources,
-	allocationScorer *resourceAllocationScorer,
-) (int64, error) {
-	var scores int64
-	for deviceType, requests := range podRequests {
-		if quotav1.IsZero(requests) {
-			continue
-		}
-		scoreByDeviceType, err := n.scoreByDeviceType(
-			requests,
-			deviceType,
-			requiredDeviceResources[deviceType],
-			preemptibleDeviceResources[deviceType],
-			allocationScorer,
-		)
-		if err != nil {
-			return 0, err
-		}
-		// TODO(joseph): Maybe different device types have different weights, but that's not currently supported.
-		scores += scoreByDeviceType
-	}
-
-	return scores, nil
-}
-
-func (n *nodeDevice) scoreByDeviceType(
-	podRequest corev1.ResourceList,
-	deviceType schedulingv1alpha1.DeviceType,
-	requiredDeviceResources deviceResources,
-	preemptibleDeviceResources deviceResources,
-	allocationScorer *resourceAllocationScorer,
-) (int64, error) {
-	nodeDeviceTotal := n.deviceTotal[deviceType]
-	if len(nodeDeviceTotal) == 0 {
-		return 0, nil
-	}
-
-	var freeDevices deviceResources
-	if len(requiredDeviceResources) > 0 {
-		freeDevices = requiredDeviceResources
-	} else {
-		freeDevices = n.calcFreeWithPreemptible(deviceType, preemptibleDeviceResources)
-	}
-
-	if deviceType == schedulingv1alpha1.GPU {
-		if err := fillGPUTotalMem(nodeDeviceTotal, podRequest); err != nil {
-			return 0, err
-		}
-	}
-
-	score := allocationScorer.scoreNode(podRequest, nodeDeviceTotal, freeDevices)
-	return score, nil
-}
-
 func (n *nodeDevice) calcFreeWithPreemptible(deviceType schedulingv1alpha1.DeviceType, preemptible deviceResources) deviceResources {
 	deviceFree := n.deviceFree[deviceType]
 	deviceUsed := n.deviceUsed[deviceType]
diff --git a/pkg/scheduler/plugins/deviceshare/plugin.go b/pkg/scheduler/plugins/deviceshare/plugin.go
index 49d93405..4bab311a 100644
--- a/pkg/scheduler/plugins/deviceshare/plugin.go
+++ b/pkg/scheduler/plugins/deviceshare/plugin.go
@@ -35,6 +35,7 @@ import (
 	schedulerconfig "github.com/koordinator-sh/koordinator/pkg/scheduler/apis/config"
 	"github.com/koordinator-sh/koordinator/pkg/scheduler/apis/config/validation"
 	"github.com/koordinator-sh/koordinator/pkg/scheduler/frameworkext"
+	"github.com/koordinator-sh/koordinator/pkg/scheduler/frameworkext/topologymanager"
 	reservationutil "github.com/koordinator-sh/koordinator/pkg/util/reservation"
 )
 
@@ -280,11 +281,15 @@ func (p *Plugin) Filter(ctx context.Context, cycleState *framework.CycleState, p
 	restoreState := reservationRestoreState.getNodeState(node.Name)
 	preemptible := appendAllocated(nil, restoreState.mergedUnmatchedUsed, state.preemptibleDevices[node.Name])
 
+	store := topologymanager.GetStore(cycleState)
+	affinity := store.GetAffinity(node.Name)
+
 	allocator := &AutopilotAllocator{
 		state:      state,
 		nodeDevice: nodeDeviceInfo,
 		node:       node,
 		pod:        pod,
+		numaNodes:  affinity.NUMANodeAffinity,
 	}
 
 	nodeDeviceInfo.lock.RLock()
@@ -338,11 +343,15 @@ func (p *Plugin) FilterReservation(ctx context.Context, cycleState *framework.Cy
 		return nil
 	}
 
+	store := topologymanager.GetStore(cycleState)
+	affinity := store.GetAffinity(nodeInfo.Node().Name)
+
 	allocator := &AutopilotAllocator{
 		state:      state,
 		nodeDevice: nodeDeviceInfo,
 		node:       nodeInfo.Node(),
 		pod:        pod,
+		numaNodes:  affinity.NUMANodeAffinity,
 	}
 
 	preemptible := appendAllocated(nil, restoreState.mergedUnmatchedUsed, state.preemptibleDevices[nodeName])
@@ -379,12 +388,16 @@ func (p *Plugin) Reserve(ctx context.Context, cycleState *framework.CycleState,
 		return nil
 	}
 
+	store := topologymanager.GetStore(cycleState)
+	affinity := store.GetAffinity(nodeInfo.Node().Name)
+
 	allocator := &AutopilotAllocator{
 		state:      state,
 		nodeDevice: nodeDeviceInfo,
 		node:       nodeInfo.Node(),
 		pod:        pod,
 		scorer:     p.scorer,
+		numaNodes:  affinity.NUMANodeAffinity,
 	}
 
 	reservationRestoreState := getReservationRestoreState(cycleState)
diff --git a/pkg/scheduler/plugins/deviceshare/reservation.go b/pkg/scheduler/plugins/deviceshare/reservation.go
index 3a2a4841..23f03469 100644
--- a/pkg/scheduler/plugins/deviceshare/reservation.go
+++ b/pkg/scheduler/plugins/deviceshare/reservation.go
@@ -281,10 +281,10 @@ func (p *Plugin) tryAllocateFromReservation(
 
 // scoreWithReservation combine the reservation with the node's resource usage to calculate the reservation score.
 func (p *Plugin) scoreWithReservation(
+	allocator *AutopilotAllocator,
 	state *preFilterState,
 	restoreState *nodeReservationRestoreStateData,
 	alloc *reservationAlloc,
-	nodeDeviceInfo *nodeDevice,
 	nodeName string,
 	basicPreemptible map[schedulingv1alpha1.DeviceType]deviceResources,
 ) (int64, *framework.Status) {
@@ -306,9 +306,9 @@ func (p *Plugin) scoreWithReservation(
 			preemptibleForDefault = appendAllocated(nil, basicPreemptible, restoreState.mergedMatchedAllocatable)
 		}
 		preemptible := appendAllocated(nil, preemptibleForDefault, preemptibleInRR)
-		score, err := nodeDeviceInfo.score(state.podRequests, nil, preemptible, p.scorer)
-		if err != nil {
-			return 0, framework.AsStatus(err)
+		score, status := allocator.score(nil, preemptible)
+		if !status.IsSuccess() {
+			return 0, status
 		}
 		return score, nil
 	}
@@ -318,18 +318,18 @@ func (p *Plugin) scoreWithReservation(
 	}
 	preemptible := appendAllocated(nil, preemptibleForAligned, alloc.remained, preemptibleInRR)
 	if allocatePolicy == schedulingv1alpha1.ReservationAllocatePolicyAligned {
-		score, err := nodeDeviceInfo.score(state.podRequests, nil, preemptible, p.scorer)
-		if err != nil {
-			return 0, framework.AsStatus(err)
+		score, status := allocator.score(nil, preemptible)
+		if !status.IsSuccess() {
+			return 0, status
 		}
 		return score, nil
 	}
 
 	if allocatePolicy == schedulingv1alpha1.ReservationAllocatePolicyRestricted {
 		requiredDeviceResources := calcRequiredDeviceResources(alloc, preemptibleInRR)
-		score, err := nodeDeviceInfo.score(state.podRequests, requiredDeviceResources, preemptible, p.scorer)
-		if err != nil {
-			return 0, framework.AsStatus(err)
+		score, status := allocator.score(requiredDeviceResources, preemptible)
+		if !status.IsSuccess() {
+			return 0, status
 		}
 		return score, nil
 	}
@@ -400,9 +400,9 @@ func (p *Plugin) allocateWithNominatedReservation(
 }
 
 func (p *Plugin) scoreWithNominatedReservation(
+	allocator *AutopilotAllocator,
 	state *preFilterState,
 	restoreState *nodeReservationRestoreStateData,
-	nodeDeviceInfo *nodeDevice,
 	nodeName string,
 	pod *corev1.Pod,
 	basicPreemptible map[schedulingv1alpha1.DeviceType]deviceResources,
@@ -424,10 +424,10 @@ func (p *Plugin) scoreWithNominatedReservation(
 	}
 
 	score, status := p.scoreWithReservation(
+		allocator,
 		state,
 		restoreState,
 		&restoreState.matched[allocIndex],
-		nodeDeviceInfo,
 		nodeName,
 		basicPreemptible,
 	)
diff --git a/pkg/scheduler/plugins/deviceshare/scoring.go b/pkg/scheduler/plugins/deviceshare/scoring.go
index 37f57c66..ec86ede9 100644
--- a/pkg/scheduler/plugins/deviceshare/scoring.go
+++ b/pkg/scheduler/plugins/deviceshare/scoring.go
@@ -28,6 +28,7 @@ import (
 
 	schedulerconfig "github.com/koordinator-sh/koordinator/pkg/scheduler/apis/config"
 	"github.com/koordinator-sh/koordinator/pkg/scheduler/frameworkext"
+	"github.com/koordinator-sh/koordinator/pkg/scheduler/frameworkext/topologymanager"
 )
 
 func (p *Plugin) Score(ctx context.Context, cycleState *framework.CycleState, pod *corev1.Pod, nodeName string) (int64, *framework.Status) {
@@ -39,11 +40,28 @@ func (p *Plugin) Score(ctx context.Context, cycleState *framework.CycleState, po
 		return 0, nil
 	}
 
+	nodeInfo, err := p.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
+	if err != nil {
+		return 0, framework.AsStatus(err)
+	}
+
 	nodeDeviceInfo := p.nodeDeviceCache.getNodeDevice(nodeName, false)
 	if nodeDeviceInfo == nil {
 		return 0, nil
 	}
 
+	store := topologymanager.GetStore(cycleState)
+	affinity := store.GetAffinity(nodeName)
+
+	allocator := &AutopilotAllocator{
+		state:      state,
+		nodeDevice: nodeDeviceInfo,
+		node:       nodeInfo.Node(),
+		pod:        pod,
+		scorer:     p.scorer,
+		numaNodes:  affinity.NUMANodeAffinity,
+	}
+
 	reservationRestoreState := getReservationRestoreState(cycleState)
 	restoreState := reservationRestoreState.getNodeState(nodeName)
 	preemptible := appendAllocated(nil, restoreState.mergedUnmatchedUsed, state.preemptibleDevices[nodeName])
@@ -53,7 +71,7 @@ func (p *Plugin) Score(ctx context.Context, cycleState *framework.CycleState, po
 
 	reservationInfo := frameworkext.GetNominatedReservation(cycleState, nodeName)
 	if reservationInfo != nil {
-		score, status := p.scoreWithNominatedReservation(state, restoreState, nodeDeviceInfo, nodeName, pod, preemptible, reservationInfo)
+		score, status := p.scoreWithNominatedReservation(allocator, state, restoreState, nodeName, pod, preemptible, reservationInfo)
 		if status.IsSuccess() {
 			return score, nil
 		}
@@ -62,11 +80,11 @@ func (p *Plugin) Score(ctx context.Context, cycleState *framework.CycleState, po
 	}
 
 	preemptible = appendAllocated(preemptible, restoreState.mergedMatchedAllocatable)
-	score, err := nodeDeviceInfo.score(state.podRequests, nil, preemptible, p.scorer)
-	if err != nil {
+	score, status := allocator.score(nil, preemptible)
+	if !status.IsSuccess() {
 		klog.ErrorS(status.AsError(), "Failed to score of DeviceShare",
 			"pod", klog.KObj(pod), "reservation", klog.KObj(reservationInfo), "node", nodeName)
-		return 0, framework.AsStatus(err)
+		return 0, status
 	}
 	return score, nil
 }
@@ -88,6 +106,11 @@ func (p *Plugin) ScoreReservation(ctx context.Context, cycleState *framework.Cyc
 		return 0, nil
 	}
 
+	nodeInfo, err := p.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
+	if err != nil {
+		return 0, framework.AsStatus(err)
+	}
+
 	reservationRestoreState := getReservationRestoreState(cycleState)
 	restoreState := reservationRestoreState.getNodeState(nodeName)
 
@@ -96,12 +119,24 @@ func (p *Plugin) ScoreReservation(ctx context.Context, cycleState *framework.Cyc
 		return 0, nil
 	}
 
+	store := topologymanager.GetStore(cycleState)
+	affinity := store.GetAffinity(nodeInfo.Node().Name)
+
+	allocator := &AutopilotAllocator{
+		state:      state,
+		nodeDevice: nodeDeviceInfo,
+		node:       nodeInfo.Node(),
+		pod:        pod,
+		scorer:     p.scorer,
+		numaNodes:  affinity.NUMANodeAffinity,
+	}
+
 	preemptible := appendAllocated(nil, restoreState.mergedUnmatchedUsed, state.preemptibleDevices[nodeName])
 
 	nodeDeviceInfo.lock.RLock()
 	defer nodeDeviceInfo.lock.RUnlock()
 
-	return p.scoreWithNominatedReservation(state, restoreState, nodeDeviceInfo, nodeName, pod, preemptible, reservationInfo)
+	return p.scoreWithNominatedReservation(allocator, state, restoreState, nodeName, pod, preemptible, reservationInfo)
 }
 
 func (p *Plugin) ReservationScoreExtensions() frameworkext.ReservationScoreExtensions {
diff --git a/pkg/scheduler/plugins/deviceshare/topology_hint.go b/pkg/scheduler/plugins/deviceshare/topology_hint.go
new file mode 100644
index 00000000..a884b880
--- /dev/null
+++ b/pkg/scheduler/plugins/deviceshare/topology_hint.go
@@ -0,0 +1,218 @@
+/*
+Copyright 2022 The Koordinator Authors.
+
+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 deviceshare
+
+import (
+	"context"
+	"sort"
+
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/util/sets"
+	quotav1 "k8s.io/apiserver/pkg/quota/v1"
+	"k8s.io/kubernetes/pkg/scheduler/framework"
+
+	schedulingv1alpha1 "github.com/koordinator-sh/koordinator/apis/scheduling/v1alpha1"
+	"github.com/koordinator-sh/koordinator/pkg/scheduler/frameworkext/topologymanager"
+	"github.com/koordinator-sh/koordinator/pkg/util/bitmask"
+)
+
+func (p *Plugin) GetPodTopologyHints(ctx context.Context, cycleState *framework.CycleState, pod *corev1.Pod, nodeName string) (map[string][]topologymanager.NUMATopologyHint, *framework.Status) {
+	state, status := getPreFilterState(cycleState)
+	if !status.IsSuccess() {
+		return nil, status
+	}
+	nodeInfo, err := p.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
+	if err != nil {
+		return nil, framework.AsStatus(err)
+	}
+	node := nodeInfo.Node()
+
+	nodeDeviceInfo := p.nodeDeviceCache.getNodeDevice(node.Name, false)
+	if nodeDeviceInfo == nil {
+		return nil, nil
+	}
+
+	return p.generateTopologyHints(cycleState, state, nodeDeviceInfo, node, pod)
+}
+
+func (p *Plugin) Allocate(ctx context.Context, cycleState *framework.CycleState, affinity topologymanager.NUMATopologyHint, pod *corev1.Pod, nodeName string) *framework.Status {
+	state, status := getPreFilterState(cycleState)
+	if !status.IsSuccess() {
+		return status
+	}
+
+	nodeInfo, err := p.handle.SnapshotSharedLister().NodeInfos().Get(nodeName)
+	if err != nil {
+		return framework.AsStatus(err)
+	}
+	node := nodeInfo.Node()
+
+	nodeDeviceInfo := p.nodeDeviceCache.getNodeDevice(node.Name, false)
+	if nodeDeviceInfo == nil {
+		return nil
+	}
+
+	reservationRestoreState := getReservationRestoreState(cycleState)
+	restoreState := reservationRestoreState.getNodeState(node.Name)
+	preemptible := appendAllocated(nil, restoreState.mergedUnmatchedUsed, state.preemptibleDevices[node.Name])
+
+	allocator := &AutopilotAllocator{
+		state:      state,
+		nodeDevice: nodeDeviceInfo,
+		node:       node,
+		pod:        pod,
+		numaNodes:  affinity.NUMANodeAffinity,
+	}
+
+	nodeDeviceInfo.lock.RLock()
+	defer nodeDeviceInfo.lock.RUnlock()
+	allocateResult, status := p.tryAllocateFromReservation(allocator, state, restoreState, restoreState.matched, node, preemptible, false)
+	if !status.IsSuccess() {
+		return status
+	}
+	if len(allocateResult) > 0 {
+		return nil
+	}
+
+	preemptible = appendAllocated(preemptible, restoreState.mergedMatchedAllocatable)
+	_, status = allocator.Allocate(nil, nil, nil, preemptible)
+	if status.IsSuccess() {
+		return nil
+	}
+	return status
+}
+
+func (p *Plugin) generateTopologyHints(cycleState *framework.CycleState, state *preFilterState, nodeDevice *nodeDevice, node *corev1.Node, pod *corev1.Pod) (map[string][]topologymanager.NUMATopologyHint, *framework.Status) {
+	reservationRestoreState := getReservationRestoreState(cycleState)
+	restoreState := reservationRestoreState.getNodeState(node.Name)
+	preemptible := appendAllocated(nil, restoreState.mergedUnmatchedUsed, state.preemptibleDevices[node.Name])
+
+	allocator := &AutopilotAllocator{
+		state:      state,
+		nodeDevice: nodeDevice,
+		node:       node,
+		pod:        pod,
+	}
+
+	nodeDevice.lock.RLock()
+	numaTopology := nodeDevice.numaTopology
+	nodeDevice.lock.RUnlock()
+	numaNodes := make([]int, 0, len(numaTopology.nodes))
+	for nodeID := range numaTopology.nodes {
+		numaNodes = append(numaNodes, nodeID)
+	}
+	sort.Ints(numaNodes)
+
+	var minAffinitySize map[corev1.ResourceName]int
+	hints := map[string][]topologymanager.NUMATopologyHint{}
+
+	bitmask.IterateBitMasks(numaNodes, func(mask bitmask.BitMask) {
+		nodeDevice.lock.RLock()
+		defer nodeDevice.lock.RUnlock()
+
+		allocator.numaNodes = mask
+		if status := allocator.Prepare(); !status.IsSuccess() {
+			return
+		}
+
+		maskNodes := mask.GetBits()
+		totalDevices := calcTotalDevicesByNUMA(nodeDevice, maskNodes)
+		for deviceType, wanted := range allocator.desiredCountPerDeviceType {
+			if totalDevices[deviceType] < wanted {
+				return
+			}
+		}
+
+		nodeCount := len(maskNodes)
+		if minAffinitySize == nil {
+			minAffinitySize = map[corev1.ResourceName]int{}
+			for _, requests := range allocator.requestsPerInstance {
+				resourceNames := quotav1.ResourceNames(requests)
+				for _, name := range resourceNames {
+					minAffinitySize[name] = len(numaNodes)
+				}
+			}
+		}
+
+		for resourceName, affinitySize := range minAffinitySize {
+			if nodeCount < affinitySize {
+				minAffinitySize[resourceName] = nodeCount
+			}
+		}
+
+		allocateResult, status := p.tryAllocateFromReservation(allocator, state, restoreState, restoreState.matched, node, preemptible, false)
+		if !status.IsSuccess() {
+			return
+		}
+		if len(allocateResult) == 0 {
+			preemptible := appendAllocated(preemptible, restoreState.mergedMatchedAllocatable)
+			_, status = allocator.Allocate(nil, nil, nil, preemptible)
+			if !status.IsSuccess() {
+				return
+			}
+		}
+
+		for resourceName := range minAffinitySize {
+			if _, ok := hints[string(resourceName)]; !ok {
+				hints[string(resourceName)] = []topologymanager.NUMATopologyHint{}
+			}
+			hints[string(resourceName)] = append(hints[string(resourceName)], topologymanager.NUMATopologyHint{
+				NUMANodeAffinity: mask,
+				Preferred:        false,
+				Score:            0,
+			})
+		}
+	})
+
+	totalResourceNames := sets.NewString()
+	for _, deviceInfos := range nodeDevice.deviceInfos {
+		if len(deviceInfos) > 0 {
+			for _, name := range quotav1.ResourceNames(deviceInfos[0].Resources) {
+				totalResourceNames.Insert(string(name))
+			}
+		}
+	}
+
+	// update hints preferred according to multiNUMAGroups, in case when it wasn't provided, the default
+	// behavior to prefer the minimal amount of NUMA nodes will be used
+	for resourceName, size := range minAffinitySize {
+		for i, hint := range hints[string(resourceName)] {
+			hints[string(resourceName)][i].Preferred = len(hint.NUMANodeAffinity.GetBits()) == size
+		}
+
+		h := hints[string(resourceName)]
+		if h == nil {
+			// no possible NUMA affinities for resource
+			hints[string(resourceName)] = []topologymanager.NUMATopologyHint{}
+		}
+	}
+
+	return hints, nil
+}
+
+func calcTotalDevicesByNUMA(nd *nodeDevice, numaNodes []int) map[schedulingv1alpha1.DeviceType]int {
+	m := map[schedulingv1alpha1.DeviceType]int{}
+	for _, node := range numaNodes {
+		pcies := nd.numaTopology.nodes[node]
+		for _, v := range pcies {
+			for deviceTypes, minors := range v.devices {
+				m[deviceTypes] += len(minors)
+			}
+		}
+	}
+	return m
+}
diff --git a/pkg/scheduler/plugins/deviceshare/utils.go b/pkg/scheduler/plugins/deviceshare/utils.go
index 1b343c18..a81bc988 100644
--- a/pkg/scheduler/plugins/deviceshare/utils.go
+++ b/pkg/scheduler/plugins/deviceshare/utils.go
@@ -251,28 +251,9 @@ func parsePodDeviceShareExtensions(pod *corev1.Pod, podRequests map[schedulingv1
 		return fmt.Errorf("invalid DeviceAllocateHint annotation, err: %s", err.Error())
 	}
 
-	var hintSelectors map[schedulingv1alpha1.DeviceType][2]labels.Selector
-	for deviceType, v := range hints {
-		var selector labels.Selector
-		var vfSelector labels.Selector
-		if v.Selector != nil {
-			var err error
-			selector, err = util.GetFastLabelSelector(v.Selector)
-			if err != nil {
-				return fmt.Errorf("invalid Selector of DeviceHint, deviceType: %s, err: %s", deviceType, err.Error())
-			}
-		}
-		if v.VFSelector != nil {
-			var err error
-			vfSelector, err = util.GetFastLabelSelector(v.VFSelector)
-			if err != nil {
-				return fmt.Errorf("invalid VFSelector of DeviceHint, deviceType: %s, err: %s", deviceType, err.Error())
-			}
-		}
-		if hintSelectors == nil {
-			hintSelectors = map[schedulingv1alpha1.DeviceType][2]labels.Selector{}
-		}
-		hintSelectors[deviceType] = [2]labels.Selector{selector, vfSelector}
+	hintSelectors, err := newHintSelectors(hints)
+	if err != nil {
+		return err
 	}
 
 	jointAllocate, err := apiext.GetDeviceJointAllocate(pod.Annotations)
@@ -299,3 +280,30 @@ func parsePodDeviceShareExtensions(pod *corev1.Pod, podRequests map[schedulingv1
 	state.jointAllocate = jointAllocate
 	return nil
 }
+
+func newHintSelectors(hints apiext.DeviceAllocateHints) (map[schedulingv1alpha1.DeviceType][2]labels.Selector, error) {
+	var hintSelectors map[schedulingv1alpha1.DeviceType][2]labels.Selector
+	for deviceType, v := range hints {
+		var selector labels.Selector
+		var vfSelector labels.Selector
+		if v.Selector != nil {
+			var err error
+			selector, err = util.GetFastLabelSelector(v.Selector)
+			if err != nil {
+				return nil, fmt.Errorf("invalid Selector of DeviceHint, deviceType: %s, err: %s", deviceType, err.Error())
+			}
+		}
+		if v.VFSelector != nil {
+			var err error
+			vfSelector, err = util.GetFastLabelSelector(v.VFSelector)
+			if err != nil {
+				return nil, fmt.Errorf("invalid VFSelector of DeviceHint, deviceType: %s, err: %s", deviceType, err.Error())
+			}
+		}
+		if hintSelectors == nil {
+			hintSelectors = map[schedulingv1alpha1.DeviceType][2]labels.Selector{}
+		}
+		hintSelectors[deviceType] = [2]labels.Selector{selector, vfSelector}
+	}
+	return hintSelectors, nil
+}



Test patch:
diff --git a/pkg/koordlet/metricsadvisor/collectors/nodeinfo/node_info_collector_test.go b/pkg/koordlet/metricsadvisor/collectors/nodeinfo/node_info_collector_test.go
index f8792111..363d343e 100644
--- a/pkg/koordlet/metricsadvisor/collectors/nodeinfo/node_info_collector_test.go
+++ b/pkg/koordlet/metricsadvisor/collectors/nodeinfo/node_info_collector_test.go
@@ -23,6 +23,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	"go.uber.org/atomic"
 
+	"github.com/koordinator-sh/koordinator/pkg/features"
 	"github.com/koordinator-sh/koordinator/pkg/koordlet/metriccache"
 	"github.com/koordinator-sh/koordinator/pkg/koordlet/metricsadvisor/framework"
 	koordletutil "github.com/koordinator-sh/koordinator/pkg/koordlet/util"
@@ -63,17 +64,6 @@ func TestNodeInfoCollector(t *testing.T) {
 }
 
 func Test_collectNodeNUMAInfo(t *testing.T) {
-	helper := system.NewFileTestUtil(t)
-	defer helper.Cleanup()
-	metricCache, err := metriccache.NewMetricCache(&metriccache.Config{
-		TSDBPath:              t.TempDir(),
-		TSDBEnablePromMetrics: false,
-	})
-	assert.NoError(t, err)
-	defer func() {
-		err = metricCache.Close()
-		assert.NoError(t, err)
-	}()
 
 	numaMemInfoContentStr0 := `Node 0 MemTotal:       263432804 kB
 Node 0 MemFree:        254391744 kB
@@ -147,10 +137,7 @@ Node 1 HugePages_Total:       0
 Node 1 HugePages_Free:        0
 Node 1 HugePages_Rsvd:        0
 Node 1 HugePages_Surp:        0`
-	numaMemInfoPath0 := system.GetNUMAMemInfoPath("node0")
-	helper.WriteFileContents(numaMemInfoPath0, numaMemInfoContentStr0)
-	numaMemInfoPath1 := system.GetNUMAMemInfoPath("node1")
-	helper.WriteFileContents(numaMemInfoPath1, numaMemInfoContentStr1)
+
 	testMemInfo0 := &koordletutil.MemInfo{
 		MemTotal: 263432804, MemFree: 254391744, MemAvailable: 256703236,
 		Buffers: 958096, Cached: 0, SwapCached: 0,
@@ -179,41 +166,336 @@ Node 1 HugePages_Surp:        0`
 		HugePages_Total: 0, HugePages_Free: 0, HugePages_Rsvd: 0,
 		HugePages_Surp: 0,
 	}
-	expected := &koordletutil.NodeNUMAInfo{
-		NUMAInfos: []koordletutil.NUMAInfo{
-			{
-				NUMANodeID: 0,
-				MemInfo:    testMemInfo0,
+
+	tts := []struct {
+		name           string
+		initfunc       func(*system.FileTestUtil)
+		enableHugePage bool
+		expected       *koordletutil.NodeNUMAInfo
+	}{
+		{
+			name: "only memory",
+			initfunc: func(helper *system.FileTestUtil) {
+				numaMemInfoPath0 := system.GetNUMAMemInfoPath("node0")
+				helper.WriteFileContents(numaMemInfoPath0, numaMemInfoContentStr0)
+				numaMemInfoPath1 := system.GetNUMAMemInfoPath("node1")
+				helper.WriteFileContents(numaMemInfoPath1, numaMemInfoContentStr1)
+			},
+			enableHugePage: false,
+			expected: &koordletutil.NodeNUMAInfo{
+				NUMAInfos: []koordletutil.NUMAInfo{
+					{
+						NUMANodeID: 0,
+						MemInfo:    testMemInfo0,
+					},
+					{
+						NUMANodeID: 1,
+						MemInfo:    testMemInfo1,
+					},
+				},
+				MemInfoMap: map[int32]*koordletutil.MemInfo{
+					0: testMemInfo0,
+					1: testMemInfo1,
+				},
+			},
+		},
+		{
+			name: "memory and hugepage, but featuregate not enable",
+			initfunc: func(helper *system.FileTestUtil) {
+				numaMemInfoPath0 := system.GetNUMAMemInfoPath("node0")
+				helper.WriteFileContents(numaMemInfoPath0, numaMemInfoContentStr0)
+				numaMemInfoPath1 := system.GetNUMAMemInfoPath("node1")
+				helper.WriteFileContents(numaMemInfoPath1, numaMemInfoContentStr1)
+				numaHugePage2MPath0 := system.GetNUMAHugepagesNrPath("node0", "hugepages-1048576kB")
+				helper.WriteFileContents(numaHugePage2MPath0, "20")
+				numaHugePage2MPath1 := system.GetNUMAHugepagesNrPath("node1", "hugepages-1048576kB")
+				helper.WriteFileContents(numaHugePage2MPath1, "40")
+			},
+			enableHugePage: false,
+			expected: &koordletutil.NodeNUMAInfo{
+				NUMAInfos: []koordletutil.NUMAInfo{
+					{
+						NUMANodeID: 0,
+						MemInfo:    testMemInfo0,
+					},
+					{
+						NUMANodeID: 1,
+						MemInfo:    testMemInfo1,
+					},
+				},
+				MemInfoMap: map[int32]*koordletutil.MemInfo{
+					0: testMemInfo0,
+					1: testMemInfo1,
+				},
+			},
+		},
+		{
+			name: "memory and 2M hugepage",
+			initfunc: func(helper *system.FileTestUtil) {
+				numaMemInfoPath0 := system.GetNUMAMemInfoPath("node0")
+				helper.WriteFileContents(numaMemInfoPath0, numaMemInfoContentStr0)
+				numaMemInfoPath1 := system.GetNUMAMemInfoPath("node1")
+				helper.WriteFileContents(numaMemInfoPath1, numaMemInfoContentStr1)
+				numaHugePage2MPath0 := system.GetNUMAHugepagesNrPath("node0", "hugepages-2048kB")
+				helper.WriteFileContents(numaHugePage2MPath0, "20")
+				numaHugePage2MPath1 := system.GetNUMAHugepagesNrPath("node1", "hugepages-2048kB")
+				helper.WriteFileContents(numaHugePage2MPath1, "40")
 			},
-			{
-				NUMANodeID: 1,
-				MemInfo:    testMemInfo1,
+			enableHugePage: true,
+			expected: &koordletutil.NodeNUMAInfo{
+				NUMAInfos: []koordletutil.NUMAInfo{
+					{
+						NUMANodeID: 0,
+						MemInfo:    testMemInfo0,
+						HugePages: map[uint64]*koordletutil.HugePagesInfo{
+							koordletutil.Hugepage2Mkbyte: {
+								PageSize: koordletutil.Hugepage2Mkbyte,
+								NumPages: 20,
+							},
+							koordletutil.Hugepage1Gkbyte: {
+								PageSize: koordletutil.Hugepage1Gkbyte,
+								NumPages: 0,
+							},
+						},
+					},
+					{
+						NUMANodeID: 1,
+						MemInfo:    testMemInfo1,
+						HugePages: map[uint64]*koordletutil.HugePagesInfo{
+							koordletutil.Hugepage2Mkbyte: {
+								PageSize: koordletutil.Hugepage2Mkbyte,
+								NumPages: 40,
+							},
+							koordletutil.Hugepage1Gkbyte: {
+								PageSize: koordletutil.Hugepage1Gkbyte,
+								NumPages: 0,
+							},
+						},
+					},
+				},
+				MemInfoMap: map[int32]*koordletutil.MemInfo{
+					0: testMemInfo0,
+					1: testMemInfo1,
+				},
+				HugePagesMap: map[int32]map[uint64]*koordletutil.HugePagesInfo{
+					0: {
+						koordletutil.Hugepage2Mkbyte: {
+							PageSize: koordletutil.Hugepage2Mkbyte,
+							NumPages: 20,
+						},
+						koordletutil.Hugepage1Gkbyte: {
+							PageSize: koordletutil.Hugepage1Gkbyte,
+							NumPages: 0,
+						},
+					},
+					1: {
+						koordletutil.Hugepage2Mkbyte: {
+							PageSize: koordletutil.Hugepage2Mkbyte,
+							NumPages: 40,
+						},
+						koordletutil.Hugepage1Gkbyte: {
+							PageSize: koordletutil.Hugepage1Gkbyte,
+							NumPages: 0,
+						},
+					},
+				},
 			},
 		},
-		MemInfoMap: map[int32]*koordletutil.MemInfo{
-			0: testMemInfo0,
-			1: testMemInfo1,
+		{
+			name: "memory and 1G hugepage",
+			initfunc: func(helper *system.FileTestUtil) {
+				numaMemInfoPath0 := system.GetNUMAMemInfoPath("node0")
+				helper.WriteFileContents(numaMemInfoPath0, numaMemInfoContentStr0)
+				numaMemInfoPath1 := system.GetNUMAMemInfoPath("node1")
+				helper.WriteFileContents(numaMemInfoPath1, numaMemInfoContentStr1)
+				numaHugePage1GPath0 := system.GetNUMAHugepagesNrPath("node0", "hugepages-1048576kB")
+				helper.WriteFileContents(numaHugePage1GPath0, "10")
+
+				numaHugePage1GPath1 := system.GetNUMAHugepagesNrPath("node1", "hugepages-1048576kB")
+				helper.WriteFileContents(numaHugePage1GPath1, "20")
+			},
+			enableHugePage: true,
+			expected: &koordletutil.NodeNUMAInfo{
+				NUMAInfos: []koordletutil.NUMAInfo{
+					{
+						NUMANodeID: 0,
+						MemInfo:    testMemInfo0,
+						HugePages: map[uint64]*koordletutil.HugePagesInfo{
+							koordletutil.Hugepage2Mkbyte: {
+								PageSize: koordletutil.Hugepage2Mkbyte,
+								NumPages: 0,
+							},
+							koordletutil.Hugepage1Gkbyte: {
+								PageSize: koordletutil.Hugepage1Gkbyte,
+								NumPages: 10,
+							},
+						},
+					},
+					{
+						NUMANodeID: 1,
+						MemInfo:    testMemInfo1,
+						HugePages: map[uint64]*koordletutil.HugePagesInfo{
+							koordletutil.Hugepage2Mkbyte: {
+								PageSize: koordletutil.Hugepage2Mkbyte,
+								NumPages: 0,
+							},
+							koordletutil.Hugepage1Gkbyte: {
+								PageSize: koordletutil.Hugepage1Gkbyte,
+								NumPages: 20,
+							},
+						},
+					},
+				},
+				MemInfoMap: map[int32]*koordletutil.MemInfo{
+					0: testMemInfo0,
+					1: testMemInfo1,
+				},
+				HugePagesMap: map[int32]map[uint64]*koordletutil.HugePagesInfo{
+					0: {
+						koordletutil.Hugepage2Mkbyte: {
+							PageSize: koordletutil.Hugepage2Mkbyte,
+							NumPages: 0,
+						},
+						koordletutil.Hugepage1Gkbyte: {
+							PageSize: koordletutil.Hugepage1Gkbyte,
+							NumPages: 10,
+						},
+					},
+					1: {
+						koordletutil.Hugepage2Mkbyte: {
+							PageSize: koordletutil.Hugepage2Mkbyte,
+							NumPages: 0,
+						},
+						koordletutil.Hugepage1Gkbyte: {
+							PageSize: koordletutil.Hugepage1Gkbyte,
+							NumPages: 20,
+						},
+					},
+				},
+			},
+		},
+		{
+			name: "memory and 2M & 1G hugepage",
+			initfunc: func(helper *system.FileTestUtil) {
+				numaMemInfoPath0 := system.GetNUMAMemInfoPath("node0")
+				helper.WriteFileContents(numaMemInfoPath0, numaMemInfoContentStr0)
+				numaMemInfoPath1 := system.GetNUMAMemInfoPath("node1")
+				helper.WriteFileContents(numaMemInfoPath1, numaMemInfoContentStr1)
+				numaHugePage1GPath0 := system.GetNUMAHugepagesNrPath("node0", "hugepages-1048576kB")
+				helper.WriteFileContents(numaHugePage1GPath0, "10")
+				numaHugePage2MPath0 := system.GetNUMAHugepagesNrPath("node0", "hugepages-2048kB")
+				helper.WriteFileContents(numaHugePage2MPath0, "20")
+
+				numaHugePage1GPath1 := system.GetNUMAHugepagesNrPath("node1", "hugepages-1048576kB")
+				helper.WriteFileContents(numaHugePage1GPath1, "10")
+				numaHugePage2MPath1 := system.GetNUMAHugepagesNrPath("node1", "hugepages-2048kB")
+				helper.WriteFileContents(numaHugePage2MPath1, "20")
+			},
+			enableHugePage: true,
+			expected: &koordletutil.NodeNUMAInfo{
+				NUMAInfos: []koordletutil.NUMAInfo{
+					{
+						NUMANodeID: 0,
+						MemInfo:    testMemInfo0,
+						HugePages: map[uint64]*koordletutil.HugePagesInfo{
+							koordletutil.Hugepage2Mkbyte: {
+								PageSize: koordletutil.Hugepage2Mkbyte,
+								NumPages: 20,
+							},
+							koordletutil.Hugepage1Gkbyte: {
+								PageSize: koordletutil.Hugepage1Gkbyte,
+								NumPages: 10,
+							},
+						},
+					},
+					{
+						NUMANodeID: 1,
+						MemInfo:    testMemInfo1,
+						HugePages: map[uint64]*koordletutil.HugePagesInfo{
+							koordletutil.Hugepage2Mkbyte: {
+								PageSize: koordletutil.Hugepage2Mkbyte,
+								NumPages: 20,
+							},
+							koordletutil.Hugepage1Gkbyte: {
+								PageSize: koordletutil.Hugepage1Gkbyte,
+								NumPages: 10,
+							},
+						},
+					},
+				},
+				MemInfoMap: map[int32]*koordletutil.MemInfo{
+					0: testMemInfo0,
+					1: testMemInfo1,
+				},
+				HugePagesMap: map[int32]map[uint64]*koordletutil.HugePagesInfo{
+					0: {
+						koordletutil.Hugepage2Mkbyte: {
+							PageSize: koordletutil.Hugepage2Mkbyte,
+							NumPages: 20,
+						},
+						koordletutil.Hugepage1Gkbyte: {
+							PageSize: koordletutil.Hugepage1Gkbyte,
+							NumPages: 10,
+						},
+					},
+					1: {
+						koordletutil.Hugepage2Mkbyte: {
+							PageSize: koordletutil.Hugepage2Mkbyte,
+							NumPages: 20,
+						},
+						koordletutil.Hugepage1Gkbyte: {
+							PageSize: koordletutil.Hugepage1Gkbyte,
+							NumPages: 10,
+						},
+					},
+				},
+			},
 		},
 	}
-	t.Run("test", func(t *testing.T) {
-		c := &nodeInfoCollector{
-			collectInterval: 60 * time.Second,
-			storage:         metricCache,
-			started:         atomic.NewBool(false),
-		}
+	for _, tt := range tts {
+		t.Run(tt.name, func(t *testing.T) {
+			helper := system.NewFileTestUtil(t)
+			defer helper.Cleanup()
 
-		// test collect successfully
-		err = c.collectNodeNUMAInfo()
-		assert.NoError(t, err)
-		nodeNUMAInfoRaw, ok := c.storage.Get(metriccache.NodeNUMAInfoKey)
-		assert.True(t, ok)
-		nodeNUMAInfo, ok := nodeNUMAInfoRaw.(*koordletutil.NodeNUMAInfo)
-		assert.True(t, ok)
-		assert.Equal(t, expected, nodeNUMAInfo)
-
-		// test collect failed when sys files are missing
-		helper.Cleanup()
-		err = c.collectNodeNUMAInfo()
-		assert.Error(t, err)
-	})
+			enabled := features.DefaultKoordletFeatureGate.Enabled(features.HugePageReport)
+			testFeatureGates := map[string]bool{string(features.HugePageReport): tt.enableHugePage}
+			err := features.DefaultMutableKoordletFeatureGate.SetFromMap(testFeatureGates)
+			assert.NoError(t, err)
+			defer func() {
+				testFeatureGates[string(features.HugePageReport)] = enabled
+				err = features.DefaultMutableKoordletFeatureGate.SetFromMap(testFeatureGates)
+				assert.NoError(t, err)
+			}()
+
+			metricCache, err := metriccache.NewMetricCache(&metriccache.Config{
+				TSDBPath:              t.TempDir(),
+				TSDBEnablePromMetrics: false,
+			})
+			assert.NoError(t, err)
+			defer func() {
+				err = metricCache.Close()
+				assert.NoError(t, err)
+			}()
+
+			tt.initfunc(helper)
+
+			c := &nodeInfoCollector{
+				collectInterval: 60 * time.Second,
+				storage:         metricCache,
+				started:         atomic.NewBool(false),
+			}
+			err = c.collectNodeNUMAInfo()
+			assert.NoError(t, err)
+			nodeNUMAInfoRaw, ok := c.storage.Get(metriccache.NodeNUMAInfoKey)
+			assert.True(t, ok)
+			nodeNUMAInfo, ok := nodeNUMAInfoRaw.(*koordletutil.NodeNUMAInfo)
+			assert.True(t, ok)
+			assert.Equal(t, tt.expected, nodeNUMAInfo)
+
+			// test collect failed when sys files are missing
+			helper.Cleanup()
+			err = c.collectNodeNUMAInfo()
+			assert.Error(t, err)
+		})
+	}
 }
diff --git a/pkg/koordlet/metricsadvisor/devices/gpu/collector_gpu_linux_test.go b/pkg/koordlet/metricsadvisor/devices/gpu/collector_gpu_linux_test.go
index 2ad46acb..8508b9a8 100644
--- a/pkg/koordlet/metricsadvisor/devices/gpu/collector_gpu_linux_test.go
+++ b/pkg/koordlet/metricsadvisor/devices/gpu/collector_gpu_linux_test.go
@@ -350,7 +350,7 @@ func Test_gpuUsageDetailRecord_getPodOrContinerTotalGPUUsageOfPIDs(t *testing.T)
 				devices:          tt.fields.devices,
 				processesMetrics: tt.fields.processesMetrics,
 			}
-			got := g.getPodOrContinerTotalGPUUsageOfPIDs(tt.args.id, tt.args.isPodID, tt.args.pids)
+			got := g.getPodOrContainerTotalGPUUsageOfPIDs(tt.args.id, tt.args.isPodID, tt.args.pids)
 			assert.Equal(t, tt.want, got)
 		})
 	}
diff --git a/pkg/koordlet/metricsadvisor/devices/gpu/helper_test.go b/pkg/koordlet/metricsadvisor/devices/gpu/helper_test.go
new file mode 100644
index 00000000..7d6b1cbc
--- /dev/null
+++ b/pkg/koordlet/metricsadvisor/devices/gpu/helper_test.go
@@ -0,0 +1,87 @@
+/*
+Copyright 2022 The Koordinator Authors.
+
+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 gpu
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/koordinator-sh/koordinator/pkg/koordlet/util/system"
+)
+
+func Test_parseGPUPCIInfo(t *testing.T) {
+	tests := []struct {
+		name      string
+		busID     string
+		nodeID    int32
+		pcie      string
+		wantNode  int32
+		wantPCIE  string
+		wantBusID string
+		wantErr   bool
+	}{
+		{
+			name:      "numa node -1",
+			busID:     "0000:00:07.0",
+			nodeID:    -1,
+			pcie:      "pci0000:00",
+			wantNode:  0,
+			wantPCIE:  "pci0000:00",
+			wantBusID: "0000:00:07.0",
+		},
+		{
+			name:      "numa node 1",
+			busID:     "0000:00:07.0",
+			nodeID:    1,
+			pcie:      "pci0000:00",
+			wantNode:  1,
+			wantPCIE:  "pci0000:00",
+			wantBusID: "0000:00:07.0",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			helper := system.NewFileTestUtil(t)
+			defer helper.Cleanup()
+
+			pciDeviceDir := system.GetPCIDeviceDir()
+			gpuDeviceDir := filepath.Join(pciDeviceDir, tt.pcie, tt.busID)
+			assert.NoError(t, os.MkdirAll(gpuDeviceDir, 0700))
+			assert.NoError(t, os.WriteFile(filepath.Join(gpuDeviceDir, "numa_node"), []byte(fmt.Sprintf("%d\n", tt.nodeID)), 0700))
+
+			symbolicLink := filepath.Join(pciDeviceDir, tt.busID)
+			assert.NoError(t, os.Symlink(gpuDeviceDir, symbolicLink))
+
+			var busIdLegacy [16]int8
+			for i, v := range tt.busID {
+				busIdLegacy[i] = int8(v)
+			}
+			nodeID, pcie, busID, err := parseGPUPCIInfo(busIdLegacy)
+			if (err != nil) && !tt.wantErr {
+				t.Errorf("expect wantErr=%v but got err=%v", tt.wantErr, err)
+				return
+			}
+			assert.Equal(t, tt.wantNode, nodeID)
+			assert.Equal(t, tt.wantPCIE, pcie)
+			assert.Equal(t, tt.wantBusID, busID)
+		})
+	}
+}
diff --git a/pkg/koordlet/statesinformer/impl/states_device_linux_test.go b/pkg/koordlet/statesinformer/impl/states_device_linux_test.go
index a55611fd..2150f238 100644
--- a/pkg/koordlet/statesinformer/impl/states_device_linux_test.go
+++ b/pkg/koordlet/statesinformer/impl/states_device_linux_test.go
@@ -48,6 +48,7 @@ func Test_reportGPUDevice(t *testing.T) {
 	gpuDeviceInfo = []koordletutil.GPUDeviceInfo{
 		{UUID: "1", Minor: 1, MemoryTotal: 8000},
 		{UUID: "2", Minor: 2, MemoryTotal: 10000},
+		{UUID: "3", Minor: 3, MemoryTotal: 8000, BusID: "0000:00:08.0", NodeID: 0, PCIE: "pci0000:00"},
 	}
 	mockMetricCache.EXPECT().Get(koordletutil.GPUDeviceType).Return(gpuDeviceInfo, true)
 	r := &statesInformer{
@@ -88,14 +89,31 @@ func Test_reportGPUDevice(t *testing.T) {
 				extension.ResourceGPUMemoryRatio: *resource.NewQuantity(100, resource.DecimalSI),
 			},
 		},
+		{
+			UUID:   "3",
+			Minor:  pointer.Int32(3),
+			Type:   schedulingv1alpha1.GPU,
+			Health: true,
+			Resources: map[corev1.ResourceName]resource.Quantity{
+				extension.ResourceGPUCore:        *resource.NewQuantity(100, resource.DecimalSI),
+				extension.ResourceGPUMemory:      *resource.NewQuantity(8000, resource.BinarySI),
+				extension.ResourceGPUMemoryRatio: *resource.NewQuantity(100, resource.DecimalSI),
+			},
+			Topology: &schedulingv1alpha1.DeviceTopology{
+				SocketID: -1,
+				NodeID:   0,
+				PCIEID:   "pci0000:00",
+				BusID:    "0000:00:08.0",
+			},
+		},
 	}
 	device, err := fakeClient.Get(context.TODO(), "test", metav1.GetOptions{})
 	assert.Equal(t, nil, err)
 	assert.Equal(t, device.Spec.Devices, expectedDevices)
 
 	gpuDeviceInfo = append(gpuDeviceInfo, koordletutil.GPUDeviceInfo{
-		UUID:        "3",
-		Minor:       3,
+		UUID:        "4",
+		Minor:       4,
 		MemoryTotal: 10000,
 	})
 
@@ -103,8 +121,8 @@ func Test_reportGPUDevice(t *testing.T) {
 	r.reportDevice()
 
 	expectedDevices = append(expectedDevices, schedulingv1alpha1.DeviceInfo{
-		UUID:   "3",
-		Minor:  pointer.Int32(3),
+		UUID:   "4",
+		Minor:  pointer.Int32(4),
 		Type:   schedulingv1alpha1.GPU,
 		Health: true,
 		Resources: map[corev1.ResourceName]resource.Quantity{
diff --git a/pkg/koordlet/statesinformer/impl/states_noderesourcetopology_test.go b/pkg/koordlet/statesinformer/impl/states_noderesourcetopology_test.go
index 329831c0..7da063db 100644
--- a/pkg/koordlet/statesinformer/impl/states_noderesourcetopology_test.go
+++ b/pkg/koordlet/statesinformer/impl/states_noderesourcetopology_test.go
@@ -491,6 +491,18 @@ func Test_reportNodeTopology(t *testing.T) {
 					Allocatable: *resource.NewQuantity(4, resource.DecimalSI),
 					Available:   *resource.NewQuantity(4, resource.DecimalSI),
 				},
+				{
+					Name:        "hugepages-1Gi",
+					Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+					Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+					Available:   *resource.NewQuantity(0, resource.BinarySI),
+				},
+				{
+					Name:        "hugepages-2Mi",
+					Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+					Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+					Available:   *resource.NewQuantity(0, resource.BinarySI),
+				},
 				{
 					Name:        "memory",
 					Capacity:    *resource.NewQuantity(269755191296, resource.BinarySI),
@@ -509,6 +521,18 @@ func Test_reportNodeTopology(t *testing.T) {
 					Allocatable: *resource.NewQuantity(4, resource.DecimalSI),
 					Available:   *resource.NewQuantity(4, resource.DecimalSI),
 				},
+				{
+					Name:        "hugepages-1Gi",
+					Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+					Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+					Available:   *resource.NewQuantity(0, resource.BinarySI),
+				},
+				{
+					Name:        "hugepages-2Mi",
+					Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+					Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+					Available:   *resource.NewQuantity(0, resource.BinarySI),
+				},
 				{
 					Name:        "memory",
 					Capacity:    *resource.NewQuantity(269754368000, resource.BinarySI),
@@ -585,6 +609,18 @@ func Test_reportNodeTopology(t *testing.T) {
 					Allocatable: *resource.NewQuantity(1, resource.DecimalSI),
 					Available:   *resource.NewQuantity(1, resource.DecimalSI),
 				},
+				{
+					Name:        "hugepages-1Gi",
+					Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+					Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+					Available:   *resource.NewQuantity(0, resource.BinarySI),
+				},
+				{
+					Name:        "hugepages-2Mi",
+					Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+					Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+					Available:   *resource.NewQuantity(0, resource.BinarySI),
+				},
 				{
 					Name:        "memory",
 					Capacity:    *resource.NewQuantity(269755191296, resource.BinarySI),
@@ -609,6 +645,18 @@ func Test_reportNodeTopology(t *testing.T) {
 					Allocatable: *resource.NewQuantity(1, resource.DecimalSI),
 					Available:   *resource.NewQuantity(1, resource.DecimalSI),
 				},
+				{
+					Name:        "hugepages-1Gi",
+					Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+					Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+					Available:   *resource.NewQuantity(0, resource.BinarySI),
+				},
+				{
+					Name:        "hugepages-2Mi",
+					Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+					Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+					Available:   *resource.NewQuantity(0, resource.BinarySI),
+				},
 				{
 					Name:        "memory",
 					Capacity:    *resource.NewQuantity(269754368000, resource.BinarySI),
@@ -639,6 +687,18 @@ func Test_reportNodeTopology(t *testing.T) {
 					Allocatable: *resource.NewQuantity(1, resource.DecimalSI),
 					Available:   *resource.NewQuantity(1, resource.DecimalSI),
 				},
+				{
+					Name:        "hugepages-1Gi",
+					Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+					Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+					Available:   *resource.NewQuantity(0, resource.BinarySI),
+				},
+				{
+					Name:        "hugepages-2Mi",
+					Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+					Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+					Available:   *resource.NewQuantity(0, resource.BinarySI),
+				},
 				{
 					Name:        "memory",
 					Capacity:    *resource.NewQuantity(269755191296, resource.BinarySI),
@@ -663,6 +723,18 @@ func Test_reportNodeTopology(t *testing.T) {
 					Allocatable: *resource.NewQuantity(1, resource.DecimalSI),
 					Available:   *resource.NewQuantity(1, resource.DecimalSI),
 				},
+				{
+					Name:        "hugepages-1Gi",
+					Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+					Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+					Available:   *resource.NewQuantity(0, resource.BinarySI),
+				},
+				{
+					Name:        "hugepages-2Mi",
+					Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+					Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+					Available:   *resource.NewQuantity(0, resource.BinarySI),
+				},
 				{
 					Name:        "memory",
 					Capacity:    *resource.NewQuantity(269754368000, resource.BinarySI),
@@ -1212,11 +1284,12 @@ func Test_calTopologyZoneList(t *testing.T) {
 		nodeCPUInfo *metriccache.NodeCPUInfo
 	}
 	tests := []struct {
-		name    string
-		fields  fields
-		args    args
-		want    topologyv1alpha1.ZoneList
-		wantErr bool
+		name           string
+		fields         fields
+		args           args
+		hugepageEnable bool
+		want           topologyv1alpha1.ZoneList
+		wantErr        bool
 	}{
 		{
 			name: "err when numa info not exist",
@@ -1284,13 +1357,114 @@ func Test_calTopologyZoneList(t *testing.T) {
 							{
 								NUMANodeID: 0,
 								MemInfo: &koordletutil.MemInfo{
-									MemTotal: 1024000,
+									MemTotal: 157286400, // 150G
+								},
+							},
+						},
+						MemInfoMap: map[int32]*koordletutil.MemInfo{
+							0: {
+								MemTotal: 157286400, // 150G
+							},
+						},
+					}, true).Times(1)
+					return mc
+				},
+			},
+			args: args{
+				nodeCPUInfo: &metriccache.NodeCPUInfo{
+					TotalInfo: koordletutil.CPUTotalInfo{
+						NodeToCPU: map[int32][]koordletutil.ProcessorInfo{
+							0: {
+								{
+									CPUID:    0,
+									CoreID:   0,
+									SocketID: 0,
+									NodeID:   0,
+								},
+								{
+									CPUID:    1,
+									CoreID:   1,
+									SocketID: 0,
+									NodeID:   0,
+								},
+							},
+						},
+					},
+				},
+			},
+			want: topologyv1alpha1.ZoneList{
+				{
+					Name: "node-0",
+					Type: util.NodeZoneType,
+					Resources: topologyv1alpha1.ResourceInfoList{
+						{
+							Name:        "cpu",
+							Capacity:    *resource.NewQuantity(2, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(2, resource.DecimalSI),
+							Available:   *resource.NewQuantity(2, resource.DecimalSI),
+						},
+						{
+							Name:        "hugepages-1Gi",
+							Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+							Available:   *resource.NewQuantity(0, resource.BinarySI),
+						},
+						{
+							Name:        "hugepages-2Mi",
+							Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+							Available:   *resource.NewQuantity(0, resource.BinarySI),
+						},
+						{
+							Name:        "memory",
+							Capacity:    *resource.NewQuantity(161061273600, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(161061273600, resource.BinarySI),
+							Available:   *resource.NewQuantity(161061273600, resource.BinarySI),
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "calculate single numa node with hugepage, but featuregate hugepage not enable",
+			fields: fields{
+				metricCache: func(ctrl *gomock.Controller) metriccache.MetricCache {
+					mc := mock_metriccache.NewMockMetricCache(ctrl)
+					mc.EXPECT().Get(metriccache.NodeNUMAInfoKey).Return(&koordletutil.NodeNUMAInfo{
+						NUMAInfos: []koordletutil.NUMAInfo{
+							{
+								NUMANodeID: 0,
+								MemInfo: &koordletutil.MemInfo{
+									MemTotal: 157286400, // 150G
+								},
+								HugePages: map[uint64]*koordletutil.HugePagesInfo{
+									koordletutil.Hugepage2Mkbyte: {
+										NumPages: 30,
+										PageSize: koordletutil.Hugepage2Mkbyte,
+									}, // 60M
+									koordletutil.Hugepage1Gkbyte: {
+										NumPages: 30,
+										PageSize: koordletutil.Hugepage1Gkbyte,
+									}, // 30G
 								},
 							},
 						},
 						MemInfoMap: map[int32]*koordletutil.MemInfo{
 							0: {
-								MemTotal: 1024000,
+								MemTotal: 157286400, // 150G
+							},
+						},
+						HugePagesMap: map[int32]map[uint64]*koordletutil.HugePagesInfo{
+							0: {
+								koordletutil.Hugepage2Mkbyte: {
+									NumPages: 30,
+									PageSize: koordletutil.Hugepage2Mkbyte,
+								}, // 60M
+								koordletutil.Hugepage1Gkbyte: {
+									NumPages: 30,
+									PageSize: koordletutil.Hugepage1Gkbyte,
+								}, // 30G
 							},
 						},
 					}, true).Times(1)
@@ -1330,11 +1504,125 @@ func Test_calTopologyZoneList(t *testing.T) {
 							Allocatable: *resource.NewQuantity(2, resource.DecimalSI),
 							Available:   *resource.NewQuantity(2, resource.DecimalSI),
 						},
+						{
+							Name:        "hugepages-1Gi",
+							Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+							Available:   *resource.NewQuantity(0, resource.BinarySI),
+						},
+						{
+							Name:        "hugepages-2Mi",
+							Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+							Available:   *resource.NewQuantity(0, resource.BinarySI),
+						},
 						{
 							Name:        "memory",
-							Capacity:    *resource.NewQuantity(1048576000, resource.BinarySI),
-							Allocatable: *resource.NewQuantity(1048576000, resource.BinarySI),
-							Available:   *resource.NewQuantity(1048576000, resource.BinarySI),
+							Capacity:    *resource.NewQuantity(161061273600, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(161061273600, resource.BinarySI),
+							Available:   *resource.NewQuantity(161061273600, resource.BinarySI),
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name:           "calculate single numa node with hugepage, featuregate hugepage enable",
+			hugepageEnable: true,
+			fields: fields{
+				metricCache: func(ctrl *gomock.Controller) metriccache.MetricCache {
+					mc := mock_metriccache.NewMockMetricCache(ctrl)
+					mc.EXPECT().Get(metriccache.NodeNUMAInfoKey).Return(&koordletutil.NodeNUMAInfo{
+						NUMAInfos: []koordletutil.NUMAInfo{
+							{
+								NUMANodeID: 0,
+								MemInfo: &koordletutil.MemInfo{
+									MemTotal: 157286400, // 150G
+								},
+								HugePages: map[uint64]*koordletutil.HugePagesInfo{
+									koordletutil.Hugepage2Mkbyte: {
+										NumPages: 30,
+										PageSize: koordletutil.Hugepage2Mkbyte,
+									}, // 60M
+									koordletutil.Hugepage1Gkbyte: {
+										NumPages: 30,
+										PageSize: koordletutil.Hugepage1Gkbyte,
+									}, // 30G
+								},
+							},
+						},
+						MemInfoMap: map[int32]*koordletutil.MemInfo{
+							0: {
+								MemTotal: 157286400, // 150G
+							},
+						},
+						HugePagesMap: map[int32]map[uint64]*koordletutil.HugePagesInfo{
+							0: {
+								koordletutil.Hugepage2Mkbyte: {
+									NumPages: 30,
+									PageSize: koordletutil.Hugepage2Mkbyte,
+								}, // 60M
+								koordletutil.Hugepage1Gkbyte: {
+									NumPages: 30,
+									PageSize: koordletutil.Hugepage1Gkbyte,
+								}, // 30G
+							},
+						},
+					}, true).Times(1)
+					return mc
+				},
+			},
+			args: args{
+				nodeCPUInfo: &metriccache.NodeCPUInfo{
+					TotalInfo: koordletutil.CPUTotalInfo{
+						NodeToCPU: map[int32][]koordletutil.ProcessorInfo{
+							0: {
+								{
+									CPUID:    0,
+									CoreID:   0,
+									SocketID: 0,
+									NodeID:   0,
+								},
+								{
+									CPUID:    1,
+									CoreID:   1,
+									SocketID: 0,
+									NodeID:   0,
+								},
+							},
+						},
+					},
+				},
+			},
+			want: topologyv1alpha1.ZoneList{
+				{
+					Name: "node-0",
+					Type: util.NodeZoneType,
+					Resources: topologyv1alpha1.ResourceInfoList{
+						{
+							Name:        "cpu",
+							Capacity:    *resource.NewQuantity(2, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(2, resource.DecimalSI),
+							Available:   *resource.NewQuantity(2, resource.DecimalSI),
+						},
+						{
+							Name:        "hugepages-1Gi",
+							Capacity:    *resource.NewQuantity(32212254720, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(32212254720, resource.BinarySI),
+							Available:   *resource.NewQuantity(32212254720, resource.BinarySI),
+						},
+						{
+							Name:        "hugepages-2Mi",
+							Capacity:    *resource.NewQuantity(62914560, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(62914560, resource.BinarySI),
+							Available:   *resource.NewQuantity(62914560, resource.BinarySI),
+						},
+						{
+							Name:        "memory",
+							Capacity:    *resource.NewQuantity(128786104320, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(128786104320, resource.BinarySI),
+							Available:   *resource.NewQuantity(128786104320, resource.BinarySI),
 						},
 					},
 				},
@@ -1351,22 +1639,220 @@ func Test_calTopologyZoneList(t *testing.T) {
 							{
 								NUMANodeID: 0,
 								MemInfo: &koordletutil.MemInfo{
-									MemTotal: 1024000,
+									MemTotal: 157286400, // 150G
+								},
+							},
+							{
+								NUMANodeID: 1,
+								MemInfo: &koordletutil.MemInfo{
+									MemTotal: 157286400, // 150G
+								},
+							},
+						},
+						MemInfoMap: map[int32]*koordletutil.MemInfo{
+							0: {
+								MemTotal: 157286400,
+							},
+							1: {
+								MemTotal: 157286400,
+							},
+						},
+					}, true).Times(1)
+					return mc
+				},
+			},
+			args: args{
+				nodeCPUInfo: &metriccache.NodeCPUInfo{
+					TotalInfo: koordletutil.CPUTotalInfo{
+						NodeToCPU: map[int32][]koordletutil.ProcessorInfo{
+							0: {
+								{
+									CPUID:    0,
+									CoreID:   0,
+									SocketID: 0,
+									NodeID:   0,
+								},
+								{
+									CPUID:    1,
+									CoreID:   1,
+									SocketID: 0,
+									NodeID:   0,
+								},
+								{
+									CPUID:    4,
+									CoreID:   0,
+									SocketID: 0,
+									NodeID:   0,
+								},
+								{
+									CPUID:    5,
+									CoreID:   1,
+									SocketID: 0,
+									NodeID:   0,
+								},
+							},
+							1: {
+								{
+									CPUID:    2,
+									CoreID:   2,
+									SocketID: 1,
+									NodeID:   1,
+								},
+								{
+									CPUID:    3,
+									CoreID:   3,
+									SocketID: 1,
+									NodeID:   1,
+								},
+								{
+									CPUID:    6,
+									CoreID:   2,
+									SocketID: 1,
+									NodeID:   1,
+								},
+								{
+									CPUID:    7,
+									CoreID:   3,
+									SocketID: 1,
+									NodeID:   1,
+								},
+							},
+						},
+					},
+				},
+			},
+			want: topologyv1alpha1.ZoneList{
+				{
+					Name: "node-0",
+					Type: util.NodeZoneType,
+					Resources: topologyv1alpha1.ResourceInfoList{
+						{
+							Name:        "cpu",
+							Capacity:    *resource.NewQuantity(4, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(4, resource.DecimalSI),
+							Available:   *resource.NewQuantity(4, resource.DecimalSI),
+						},
+						{
+							Name:        "hugepages-1Gi",
+							Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+							Available:   *resource.NewQuantity(0, resource.BinarySI),
+						},
+						{
+							Name:        "hugepages-2Mi",
+							Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+							Available:   *resource.NewQuantity(0, resource.BinarySI),
+						},
+						{
+							Name:        "memory",
+							Capacity:    *resource.NewQuantity(161061273600, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(161061273600, resource.BinarySI),
+							Available:   *resource.NewQuantity(161061273600, resource.BinarySI),
+						},
+					},
+				},
+				{
+					Name: "node-1",
+					Type: util.NodeZoneType,
+					Resources: topologyv1alpha1.ResourceInfoList{
+						{
+							Name:        "cpu",
+							Capacity:    *resource.NewQuantity(4, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(4, resource.DecimalSI),
+							Available:   *resource.NewQuantity(4, resource.DecimalSI),
+						},
+						{
+							Name:        "hugepages-1Gi",
+							Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+							Available:   *resource.NewQuantity(0, resource.BinarySI),
+						},
+						{
+							Name:        "hugepages-2Mi",
+							Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+							Available:   *resource.NewQuantity(0, resource.BinarySI),
+						},
+						{
+							Name:        "memory",
+							Capacity:    *resource.NewQuantity(161061273600, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(161061273600, resource.BinarySI),
+							Available:   *resource.NewQuantity(161061273600, resource.BinarySI),
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "calculate multiple numa nodes with hugepage, but featuregate hugepage not enable",
+			fields: fields{
+				metricCache: func(ctrl *gomock.Controller) metriccache.MetricCache {
+					mc := mock_metriccache.NewMockMetricCache(ctrl)
+					mc.EXPECT().Get(metriccache.NodeNUMAInfoKey).Return(&koordletutil.NodeNUMAInfo{
+						NUMAInfos: []koordletutil.NUMAInfo{
+							{
+								NUMANodeID: 0,
+								MemInfo: &koordletutil.MemInfo{
+									MemTotal: 157286400, // 150G
+								},
+								HugePages: map[uint64]*koordletutil.HugePagesInfo{
+									koordletutil.Hugepage2Mkbyte: {
+										NumPages: 30,
+										PageSize: koordletutil.Hugepage2Mkbyte,
+									}, // 60M
+									koordletutil.Hugepage1Gkbyte: {
+										NumPages: 30,
+										PageSize: koordletutil.Hugepage1Gkbyte,
+									}, // 30G
 								},
 							},
 							{
 								NUMANodeID: 1,
 								MemInfo: &koordletutil.MemInfo{
-									MemTotal: 1000000,
+									MemTotal: 157286400, // 150G
+								},
+								HugePages: map[uint64]*koordletutil.HugePagesInfo{
+									koordletutil.Hugepage2Mkbyte: {
+										NumPages: 30,
+										PageSize: koordletutil.Hugepage2Mkbyte,
+									}, // 60M
+									koordletutil.Hugepage1Gkbyte: {
+										NumPages: 30,
+										PageSize: koordletutil.Hugepage1Gkbyte,
+									}, // 30G
 								},
 							},
 						},
 						MemInfoMap: map[int32]*koordletutil.MemInfo{
 							0: {
-								MemTotal: 1024000,
+								MemTotal: 157286400,
 							},
 							1: {
-								MemTotal: 1000000,
+								MemTotal: 157286400,
+							},
+						},
+						HugePagesMap: map[int32]map[uint64]*koordletutil.HugePagesInfo{
+							0: {
+								koordletutil.Hugepage2Mkbyte: {
+									NumPages: 30,
+									PageSize: koordletutil.Hugepage2Mkbyte,
+								},
+								koordletutil.Hugepage1Gkbyte: {
+									NumPages: 30,
+									PageSize: koordletutil.Hugepage1Gkbyte,
+								},
+							},
+							1: {
+								koordletutil.Hugepage2Mkbyte: {
+									NumPages: 30,
+									PageSize: koordletutil.Hugepage2Mkbyte,
+								},
+								koordletutil.Hugepage1Gkbyte: {
+									NumPages: 30,
+									PageSize: koordletutil.Hugepage1Gkbyte,
+								},
 							},
 						},
 					}, true).Times(1)
@@ -1444,11 +1930,23 @@ func Test_calTopologyZoneList(t *testing.T) {
 							Allocatable: *resource.NewQuantity(4, resource.DecimalSI),
 							Available:   *resource.NewQuantity(4, resource.DecimalSI),
 						},
+						{
+							Name:        "hugepages-1Gi",
+							Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+							Available:   *resource.NewQuantity(0, resource.BinarySI),
+						},
+						{
+							Name:        "hugepages-2Mi",
+							Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+							Available:   *resource.NewQuantity(0, resource.BinarySI),
+						},
 						{
 							Name:        "memory",
-							Capacity:    *resource.NewQuantity(1048576000, resource.BinarySI),
-							Allocatable: *resource.NewQuantity(1048576000, resource.BinarySI),
-							Available:   *resource.NewQuantity(1048576000, resource.BinarySI),
+							Capacity:    *resource.NewQuantity(161061273600, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(161061273600, resource.BinarySI),
+							Available:   *resource.NewQuantity(161061273600, resource.BinarySI),
 						},
 					},
 				},
@@ -1462,11 +1960,222 @@ func Test_calTopologyZoneList(t *testing.T) {
 							Allocatable: *resource.NewQuantity(4, resource.DecimalSI),
 							Available:   *resource.NewQuantity(4, resource.DecimalSI),
 						},
+						{
+							Name:        "hugepages-1Gi",
+							Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+							Available:   *resource.NewQuantity(0, resource.BinarySI),
+						},
+						{
+							Name:        "hugepages-2Mi",
+							Capacity:    *resource.NewQuantity(0, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(0, resource.BinarySI),
+							Available:   *resource.NewQuantity(0, resource.BinarySI),
+						},
 						{
 							Name:        "memory",
-							Capacity:    *resource.NewQuantity(1024000000, resource.BinarySI),
-							Allocatable: *resource.NewQuantity(1024000000, resource.BinarySI),
-							Available:   *resource.NewQuantity(1024000000, resource.BinarySI),
+							Capacity:    *resource.NewQuantity(161061273600, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(161061273600, resource.BinarySI),
+							Available:   *resource.NewQuantity(161061273600, resource.BinarySI),
+						},
+					},
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name:           "calculate multiple numa nodes with hugepage, featuregate hugepage enable",
+			hugepageEnable: true,
+			fields: fields{
+				metricCache: func(ctrl *gomock.Controller) metriccache.MetricCache {
+					mc := mock_metriccache.NewMockMetricCache(ctrl)
+					mc.EXPECT().Get(metriccache.NodeNUMAInfoKey).Return(&koordletutil.NodeNUMAInfo{
+						NUMAInfos: []koordletutil.NUMAInfo{
+							{
+								NUMANodeID: 0,
+								MemInfo: &koordletutil.MemInfo{
+									MemTotal: 157286400, // 150G
+								},
+								HugePages: map[uint64]*koordletutil.HugePagesInfo{
+									koordletutil.Hugepage2Mkbyte: {
+										NumPages: 30,
+										PageSize: koordletutil.Hugepage2Mkbyte,
+									}, // 60M
+									koordletutil.Hugepage1Gkbyte: {
+										NumPages: 30,
+										PageSize: koordletutil.Hugepage1Gkbyte,
+									}, // 30G
+								},
+							},
+							{
+								NUMANodeID: 1,
+								MemInfo: &koordletutil.MemInfo{
+									MemTotal: 157286400, // 150G
+								},
+								HugePages: map[uint64]*koordletutil.HugePagesInfo{
+									koordletutil.Hugepage2Mkbyte: {
+										NumPages: 30,
+										PageSize: koordletutil.Hugepage2Mkbyte,
+									}, // 60M
+									koordletutil.Hugepage1Gkbyte: {
+										NumPages: 30,
+										PageSize: koordletutil.Hugepage1Gkbyte,
+									}, // 30G
+								},
+							},
+						},
+						MemInfoMap: map[int32]*koordletutil.MemInfo{
+							0: {
+								MemTotal: 157286400,
+							},
+							1: {
+								MemTotal: 157286400,
+							},
+						},
+						HugePagesMap: map[int32]map[uint64]*koordletutil.HugePagesInfo{
+							0: {
+								koordletutil.Hugepage2Mkbyte: {
+									NumPages: 30,
+									PageSize: koordletutil.Hugepage2Mkbyte,
+								},
+								koordletutil.Hugepage1Gkbyte: {
+									NumPages: 30,
+									PageSize: koordletutil.Hugepage1Gkbyte,
+								},
+							},
+							1: {
+								koordletutil.Hugepage2Mkbyte: {
+									NumPages: 30,
+									PageSize: koordletutil.Hugepage2Mkbyte,
+								},
+								koordletutil.Hugepage1Gkbyte: {
+									NumPages: 30,
+									PageSize: koordletutil.Hugepage1Gkbyte,
+								},
+							},
+						},
+					}, true).Times(1)
+					return mc
+				},
+			},
+			args: args{
+				nodeCPUInfo: &metriccache.NodeCPUInfo{
+					TotalInfo: koordletutil.CPUTotalInfo{
+						NodeToCPU: map[int32][]koordletutil.ProcessorInfo{
+							0: {
+								{
+									CPUID:    0,
+									CoreID:   0,
+									SocketID: 0,
+									NodeID:   0,
+								},
+								{
+									CPUID:    1,
+									CoreID:   1,
+									SocketID: 0,
+									NodeID:   0,
+								},
+								{
+									CPUID:    4,
+									CoreID:   0,
+									SocketID: 0,
+									NodeID:   0,
+								},
+								{
+									CPUID:    5,
+									CoreID:   1,
+									SocketID: 0,
+									NodeID:   0,
+								},
+							},
+							1: {
+								{
+									CPUID:    2,
+									CoreID:   2,
+									SocketID: 1,
+									NodeID:   1,
+								},
+								{
+									CPUID:    3,
+									CoreID:   3,
+									SocketID: 1,
+									NodeID:   1,
+								},
+								{
+									CPUID:    6,
+									CoreID:   2,
+									SocketID: 1,
+									NodeID:   1,
+								},
+								{
+									CPUID:    7,
+									CoreID:   3,
+									SocketID: 1,
+									NodeID:   1,
+								},
+							},
+						},
+					},
+				},
+			},
+			want: topologyv1alpha1.ZoneList{
+				{
+					Name: "node-0",
+					Type: util.NodeZoneType,
+					Resources: topologyv1alpha1.ResourceInfoList{
+						{
+							Name:        "cpu",
+							Capacity:    *resource.NewQuantity(4, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(4, resource.DecimalSI),
+							Available:   *resource.NewQuantity(4, resource.DecimalSI),
+						},
+						{
+							Name:        "hugepages-1Gi",
+							Capacity:    *resource.NewQuantity(32212254720, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(32212254720, resource.BinarySI),
+							Available:   *resource.NewQuantity(32212254720, resource.BinarySI),
+						},
+						{
+							Name:        "hugepages-2Mi",
+							Capacity:    *resource.NewQuantity(62914560, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(62914560, resource.BinarySI),
+							Available:   *resource.NewQuantity(62914560, resource.BinarySI),
+						},
+						{
+							Name:        "memory",
+							Capacity:    *resource.NewQuantity(128786104320, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(128786104320, resource.BinarySI),
+							Available:   *resource.NewQuantity(128786104320, resource.BinarySI),
+						},
+					},
+				},
+				{
+					Name: "node-1",
+					Type: util.NodeZoneType,
+					Resources: topologyv1alpha1.ResourceInfoList{
+						{
+							Name:        "cpu",
+							Capacity:    *resource.NewQuantity(4, resource.DecimalSI),
+							Allocatable: *resource.NewQuantity(4, resource.DecimalSI),
+							Available:   *resource.NewQuantity(4, resource.DecimalSI),
+						},
+						{
+							Name:        "hugepages-1Gi",
+							Capacity:    *resource.NewQuantity(32212254720, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(32212254720, resource.BinarySI),
+							Available:   *resource.NewQuantity(32212254720, resource.BinarySI),
+						},
+						{
+							Name:        "hugepages-2Mi",
+							Capacity:    *resource.NewQuantity(62914560, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(62914560, resource.BinarySI),
+							Available:   *resource.NewQuantity(62914560, resource.BinarySI),
+						},
+						{
+							Name:        "memory",
+							Capacity:    *resource.NewQuantity(128786104320, resource.BinarySI),
+							Allocatable: *resource.NewQuantity(128786104320, resource.BinarySI),
+							Available:   *resource.NewQuantity(128786104320, resource.BinarySI),
 						},
 					},
 				},
@@ -1479,6 +2188,16 @@ func Test_calTopologyZoneList(t *testing.T) {
 			ctrl := gomock.NewController(t)
 			defer ctrl.Finish()
 
+			enabled := features.DefaultKoordletFeatureGate.Enabled(features.HugePageReport)
+			testFeatureGates := map[string]bool{string(features.HugePageReport): tt.hugepageEnable}
+			err := features.DefaultMutableKoordletFeatureGate.SetFromMap(testFeatureGates)
+			assert.NoError(t, err)
+			defer func() {
+				testFeatureGates[string(features.HugePageReport)] = enabled
+				err = features.DefaultMutableKoordletFeatureGate.SetFromMap(testFeatureGates)
+				assert.NoError(t, err)
+			}()
+
 			s := &nodeTopoInformer{
 				metricCache: tt.fields.metricCache(ctrl),
 			}
diff --git a/pkg/koordlet/util/meminfo_test.go b/pkg/koordlet/util/meminfo_test.go
index b576e0e6..73c27819 100644
--- a/pkg/koordlet/util/meminfo_test.go
+++ b/pkg/koordlet/util/meminfo_test.go
@@ -17,6 +17,7 @@ limitations under the License.
 package util
 
 import (
+	"fmt"
 	"path/filepath"
 	"testing"
 
@@ -486,3 +487,338 @@ Node 1 HugePages_Surp:        0`
 	assert.Error(t, err)
 	assert.Nil(t, got)
 }
+
+func TestGetNodeHugePagesInfo(t *testing.T) {
+	helper := system.NewFileTestUtil(t)
+	defer helper.Cleanup()
+
+	numaHugePage1GPath0 := system.GetNUMAHugepagesNrPath("node0", "hugepages-1048576kB")
+	helper.WriteFileContents(numaHugePage1GPath0, "10")
+	numaHugePage2MPath0 := system.GetNUMAHugepagesNrPath("node0", "hugepages-2048kB")
+	helper.WriteFileContents(numaHugePage2MPath0, "20")
+
+	numaHugePage1GPath1 := system.GetNUMAHugepagesNrPath("node1", "hugepages-1048576kB")
+	helper.WriteFileContents(numaHugePage1GPath1, "10")
+	numaHugePage2MPath1 := system.GetNUMAHugepagesNrPath("node1", "hugepages-2048kB")
+	helper.WriteFileContents(numaHugePage2MPath1, "20")
+
+	expected := map[int32]map[uint64]*HugePagesInfo{
+		0: {
+			Hugepage1Gkbyte: {
+				NumPages: 10,
+				PageSize: Hugepage1Gkbyte,
+			},
+			Hugepage2Mkbyte: {
+				NumPages: 20,
+				PageSize: Hugepage2Mkbyte,
+			},
+		},
+		1: {
+			Hugepage1Gkbyte: {
+				NumPages: 10,
+				PageSize: Hugepage1Gkbyte,
+			},
+			Hugepage2Mkbyte: {
+				NumPages: 20,
+				PageSize: Hugepage2Mkbyte,
+			},
+		},
+	}
+
+	got, err := GetNodeHugePagesInfo()
+	assert.NoError(t, err)
+	assert.Equal(t, expected, got)
+
+	// test partial failure
+	numaMemInfoPath2 := system.GetNUMAMemInfoPath("node2")
+	helper.MkDirAll(filepath.Dir(numaMemInfoPath2))
+	got, err = GetNodeHugePagesInfo()
+	assert.Error(t, err)
+	assert.Nil(t, got)
+
+	// test path not exist
+	helper.Cleanup()
+	got, err = GetNodeHugePagesInfo()
+	assert.Error(t, err)
+	assert.Nil(t, got)
+}
+
+func TestGetAndMergeHugepageToNumaInfo(t *testing.T) {
+	helper := system.NewFileTestUtil(t)
+	defer helper.Cleanup()
+	numaMemInfoContentStr0 := `Node 0 MemTotal:       263432804 kB
+Node 0 MemFree:        254391744 kB
+Node 0 MemAvailable:   256703236 kB
+Node 0 Buffers:          958096 kB
+Node 0 Cached:                0 kB
+Node 0 SwapCached:            0 kB
+Node 0 Active:          2786012 kB
+Node 0 Inactive:        2223752 kB
+Node 0 Active(anon):     289488 kB
+Node 0 Inactive(anon):     1300 kB
+Node 0 Active(file):    2496524 kB
+Node 0 Inactive(file):  2222452 kB
+Node 0 Unevictable:           0 kB
+Node 0 Mlocked:               0 kB
+Node 0 SwapTotal:             0 kB
+Node 0 SwapFree:              0 kB
+Node 0 Dirty:               624 kB
+Node 0 Writeback:             0 kB
+Node 0 AnonPages:        281748 kB
+Node 0 Mapped:           495936 kB
+Node 0 Shmem:              2340 kB
+Node 0 Slab:            1097040 kB
+Node 0 SReclaimable:     445164 kB
+Node 0 SUnreclaim:       651876 kB
+Node 0 KernelStack:       20944 kB
+Node 0 PageTables:         7896 kB
+Node 0 NFS_Unstable:          0 kB
+Node 0 Bounce:                0 kB
+Node 0 WritebackTmp:          0 kB
+Node 0 AnonHugePages:     38912 kB
+Node 0 ShmemHugePages:        0 kB
+Node 0 ShmemPmdMapped:        0 kB
+Node 0 HugePages_Total:       0
+Node 0 HugePages_Free:        0
+Node 0 HugePages_Rsvd:        0
+Node 0 HugePages_Surp:        0`
+	numaMemInfoContentStr1 := `Node 1 MemTotal:       263432000 kB
+Node 1 MemFree:        254391744 kB
+Node 1 MemAvailable:   256703236 kB
+Node 1 Buffers:          958096 kB
+Node 1 Cached:                0 kB
+Node 1 SwapCached:            0 kB
+Node 1 Active:          2786012 kB
+Node 1 Inactive:        2223752 kB
+Node 1 Active(anon):     289488 kB
+Node 1 Inactive(anon):     1300 kB
+Node 1 Active(file):    2496524 kB
+Node 1 Inactive(file):  2222452 kB
+Node 1 Unevictable:           0 kB
+Node 1 Mlocked:               0 kB
+Node 1 SwapTotal:             0 kB
+Node 1 SwapFree:              0 kB
+Node 1 Dirty:               624 kB
+Node 1 Writeback:             0 kB
+Node 1 AnonPages:        281748 kB
+Node 1 Mapped:           495936 kB
+Node 1 Shmem:              2340 kB
+Node 1 Slab:            1097040 kB
+Node 1 SReclaimable:     445164 kB
+Node 1 SUnreclaim:       651876 kB
+Node 1 KernelStack:       20944 kB
+Node 1 PageTables:         7896 kB
+Node 1 NFS_Unstable:          0 kB
+Node 1 Bounce:                0 kB
+Node 1 WritebackTmp:          0 kB
+Node 1 AnonHugePages:     38912 kB
+Node 1 ShmemHugePages:        0 kB
+Node 1 ShmemPmdMapped:        0 kB
+Node 1 HugePages_Total:       0
+Node 1 HugePages_Free:        0
+Node 1 HugePages_Rsvd:        0
+Node 1 HugePages_Surp:        0`
+	numaMemInfoPath0 := system.GetNUMAMemInfoPath("node0")
+	helper.WriteFileContents(numaMemInfoPath0, numaMemInfoContentStr0)
+	numaMemInfoPath1 := system.GetNUMAMemInfoPath("node1")
+	helper.WriteFileContents(numaMemInfoPath1, numaMemInfoContentStr1)
+
+	numaHugePage1GPath0 := system.GetNUMAHugepagesNrPath("node0", "hugepages-1048576kB")
+	helper.WriteFileContents(numaHugePage1GPath0, "10")
+	numaHugePage2MPath0 := system.GetNUMAHugepagesNrPath("node0", "hugepages-2048kB")
+	helper.WriteFileContents(numaHugePage2MPath0, "20")
+
+	numaHugePage1GPath1 := system.GetNUMAHugepagesNrPath("node1", "hugepages-1048576kB")
+	helper.WriteFileContents(numaHugePage1GPath1, "10")
+	numaHugePage2MPath1 := system.GetNUMAHugepagesNrPath("node1", "hugepages-2048kB")
+	helper.WriteFileContents(numaHugePage2MPath1, "20")
+
+	testMemInfo0 := &MemInfo{
+		MemTotal: 263432804, MemFree: 254391744, MemAvailable: 256703236,
+		Buffers: 958096, Cached: 0, SwapCached: 0,
+		Active: 2786012, Inactive: 2223752, ActiveAnon: 289488,
+		InactiveAnon: 1300, ActiveFile: 2496524, InactiveFile: 2222452,
+		Unevictable: 0, Mlocked: 0, SwapTotal: 0,
+		SwapFree: 0, Dirty: 624, Writeback: 0,
+		AnonPages: 281748, Mapped: 495936, Shmem: 2340,
+		Slab: 1097040, SReclaimable: 445164, SUnreclaim: 651876,
+		KernelStack: 20944, PageTables: 7896, NFS_Unstable: 0,
+		Bounce: 0, WritebackTmp: 0, AnonHugePages: 38912,
+		HugePages_Total: 0, HugePages_Free: 0, HugePages_Rsvd: 0,
+		HugePages_Surp: 0,
+	}
+	testMemInfo1 := &MemInfo{
+		MemTotal: 263432000, MemFree: 254391744, MemAvailable: 256703236,
+		Buffers: 958096, Cached: 0, SwapCached: 0,
+		Active: 2786012, Inactive: 2223752, ActiveAnon: 289488,
+		InactiveAnon: 1300, ActiveFile: 2496524, InactiveFile: 2222452,
+		Unevictable: 0, Mlocked: 0, SwapTotal: 0,
+		SwapFree: 0, Dirty: 624, Writeback: 0,
+		AnonPages: 281748, Mapped: 495936, Shmem: 2340,
+		Slab: 1097040, SReclaimable: 445164, SUnreclaim: 651876,
+		KernelStack: 20944, PageTables: 7896, NFS_Unstable: 0,
+		Bounce: 0, WritebackTmp: 0, AnonHugePages: 38912,
+		HugePages_Total: 0, HugePages_Free: 0, HugePages_Rsvd: 0,
+		HugePages_Surp: 0,
+	}
+
+	testEmptyHugePagesMap0 := map[uint64]*HugePagesInfo{
+		Hugepage1Gkbyte: {
+			NumPages: 0,
+			PageSize: Hugepage1Gkbyte,
+		},
+		Hugepage2Mkbyte: {
+			NumPages: 0,
+			PageSize: Hugepage2Mkbyte,
+		},
+	}
+
+	testHugePagesMap0 := map[uint64]*HugePagesInfo{
+		Hugepage1Gkbyte: {
+			NumPages: 10,
+			PageSize: Hugepage1Gkbyte,
+		},
+		Hugepage2Mkbyte: {
+			NumPages: 20,
+			PageSize: Hugepage2Mkbyte,
+		},
+	}
+
+	testHugePagesMap1 := map[uint64]*HugePagesInfo{
+		Hugepage1Gkbyte: {
+			NumPages: 10,
+			PageSize: Hugepage1Gkbyte,
+		},
+		Hugepage2Mkbyte: {
+			NumPages: 20,
+			PageSize: Hugepage2Mkbyte,
+		},
+	}
+
+	expectedWithoutHugepage := &NodeNUMAInfo{
+		NUMAInfos: []NUMAInfo{
+			{
+				NUMANodeID: 0,
+				MemInfo:    testMemInfo0,
+			},
+			{
+				NUMANodeID: 1,
+				MemInfo:    testMemInfo1,
+			},
+		},
+		MemInfoMap: map[int32]*MemInfo{
+			0: testMemInfo0,
+			1: testMemInfo1,
+		},
+	}
+
+	expectedEmptyHugepage := &NodeNUMAInfo{
+		NUMAInfos: []NUMAInfo{
+			{
+				NUMANodeID: 0,
+				MemInfo:    testMemInfo0,
+				HugePages:  testEmptyHugePagesMap0,
+			},
+			{
+				NUMANodeID: 1,
+				MemInfo:    testMemInfo1,
+				HugePages:  testEmptyHugePagesMap0,
+			},
+		},
+		MemInfoMap: map[int32]*MemInfo{
+			0: testMemInfo0,
+			1: testMemInfo1,
+		},
+		HugePagesMap: map[int32]map[uint64]*HugePagesInfo{
+			0: {
+				Hugepage1Gkbyte: {
+					NumPages: 0,
+					PageSize: Hugepage1Gkbyte,
+				},
+				Hugepage2Mkbyte: {
+					NumPages: 0,
+					PageSize: Hugepage2Mkbyte,
+				},
+			},
+			1: {
+				Hugepage1Gkbyte: {
+					NumPages: 0,
+					PageSize: Hugepage1Gkbyte,
+				},
+				Hugepage2Mkbyte: {
+					NumPages: 0,
+					PageSize: Hugepage2Mkbyte,
+				},
+			},
+		},
+	}
+
+	expected := &NodeNUMAInfo{
+		NUMAInfos: []NUMAInfo{
+			{
+				NUMANodeID: 0,
+				MemInfo:    testMemInfo0,
+				HugePages:  testHugePagesMap0,
+			},
+			{
+				NUMANodeID: 1,
+				MemInfo:    testMemInfo1,
+				HugePages:  testHugePagesMap1,
+			},
+		},
+		MemInfoMap: map[int32]*MemInfo{
+			0: testMemInfo0,
+			1: testMemInfo1,
+		},
+		HugePagesMap: map[int32]map[uint64]*HugePagesInfo{
+			0: {
+				Hugepage1Gkbyte: {
+					NumPages: 10,
+					PageSize: Hugepage1Gkbyte,
+				},
+				Hugepage2Mkbyte: {
+					NumPages: 20,
+					PageSize: Hugepage2Mkbyte,
+				},
+			},
+			1: {
+				Hugepage1Gkbyte: {
+					NumPages: 10,
+					PageSize: Hugepage1Gkbyte,
+				},
+				Hugepage2Mkbyte: {
+					NumPages: 20,
+					PageSize: Hugepage2Mkbyte,
+				},
+			},
+		},
+	}
+
+	numaInfo, err := GetNodeNUMAInfo()
+	assert.NoError(t, err)
+	assert.Equal(t, expectedWithoutHugepage, numaInfo)
+
+	numaInfo1, err := GetNodeNUMAInfo()
+	assert.NoError(t, err)
+	assert.Equal(t, expectedWithoutHugepage, numaInfo1)
+
+	numaInfo2, err := GetNodeNUMAInfo()
+	assert.NoError(t, err)
+	assert.Equal(t, expectedWithoutHugepage, numaInfo2)
+
+	numaInfoWithHugePage := GetAndMergeHugepageToNumaInfo(numaInfo)
+	assert.Equal(t, expected, numaInfoWithHugePage)
+
+	// test partial failure
+
+	numaMemInfoPath2 := system.GetNUMAMemInfoPath("node2")
+	helper.MkDirAll(filepath.Dir(numaMemInfoPath2))
+	fmt.Printf("%v", numaInfo1)
+	numaInfoWithHugePageErr := GetAndMergeHugepageToNumaInfo(numaInfo1)
+	assert.Equal(t, expectedEmptyHugepage, numaInfoWithHugePageErr)
+
+	// test path not exist
+	helper.Cleanup()
+	numaInfoWithHugePageErr2 := GetAndMergeHugepageToNumaInfo(numaInfo2)
+	assert.Equal(t, expectedEmptyHugepage, numaInfoWithHugePageErr2)
+}
diff --git a/pkg/scheduler/plugins/deviceshare/device_allocator_test.go b/pkg/scheduler/plugins/deviceshare/device_allocator_test.go
index 6bd27461..629fea82 100644
--- a/pkg/scheduler/plugins/deviceshare/device_allocator_test.go
+++ b/pkg/scheduler/plugins/deviceshare/device_allocator_test.go
@@ -866,8 +866,10 @@ func TestAutopilotAllocator(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			deviceCR := tt.deviceCR.DeepCopy()
+			deviceCR.ResourceVersion = "1"
 			koordFakeClient := koordfake.NewSimpleClientset()
-			_, err := koordFakeClient.SchedulingV1alpha1().Devices().Create(context.TODO(), tt.deviceCR, metav1.CreateOptions{})
+			_, err := koordFakeClient.SchedulingV1alpha1().Devices().Create(context.TODO(), deviceCR, metav1.CreateOptions{})
 			assert.NoError(t, err)
 			koordShareInformerFactory := koordinatorinformers.NewSharedInformerFactory(koordFakeClient, 0)
 
@@ -924,9 +926,6 @@ func TestAutopilotAllocator(t *testing.T) {
 				podRequest[apiext.ResourceNvidiaGPU] = *resource.NewQuantity(int64(tt.gpuWanted), resource.DecimalSI)
 			}
 
-			nodeDevice.lock.Lock()
-			defer nodeDevice.lock.Unlock()
-
 			pod := &corev1.Pod{
 				Spec: corev1.PodSpec{
 					HostNetwork: tt.hostNetwork,
@@ -1704,8 +1703,9 @@ func TestAutopilotAllocatorWithExclusivePolicyAndRequiredScope(t *testing.T) {
 
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			deviceCR := tt.deviceCR.DeepCopy()
+			deviceCR.ResourceVersion = "1"
 			koordFakeClient := koordfake.NewSimpleClientset()
-			deviceCR := tt.deviceCR
 			_, err := koordFakeClient.SchedulingV1alpha1().Devices().Create(context.TODO(), deviceCR, metav1.CreateOptions{})
 			assert.NoError(t, err)
 			koordShareInformerFactory := koordinatorinformers.NewSharedInformerFactory(koordFakeClient, 0)
@@ -1763,9 +1763,6 @@ func TestAutopilotAllocatorWithExclusivePolicyAndRequiredScope(t *testing.T) {
 				podRequest[apiext.ResourceNvidiaGPU] = *resource.NewQuantity(int64(tt.gpuWanted), resource.DecimalSI)
 			}
 
-			nodeDevice.lock.Lock()
-			defer nodeDevice.lock.Unlock()
-
 			pod := &corev1.Pod{
 				Spec: corev1.PodSpec{
 					HostNetwork: tt.hostNetwork,
diff --git a/pkg/scheduler/plugins/deviceshare/plugin_test.go b/pkg/scheduler/plugins/deviceshare/plugin_test.go
index 2b66aca1..5f8db9f1 100644
--- a/pkg/scheduler/plugins/deviceshare/plugin_test.go
+++ b/pkg/scheduler/plugins/deviceshare/plugin_test.go
@@ -30,6 +30,7 @@ import (
 	"k8s.io/apimachinery/pkg/api/equality"
 	"k8s.io/apimachinery/pkg/api/resource"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	apiruntime "k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/apimachinery/pkg/util/uuid"
 	k8sfeature "k8s.io/apiserver/pkg/util/feature"
@@ -133,7 +134,11 @@ func newPluginTestSuit(t *testing.T, nodes []*corev1.Node) *pluginTestSuit {
 		schedulertesting.RegisterQueueSortPlugin(queuesort.Name, queuesort.New),
 	}
 
-	cs := kubefake.NewSimpleClientset()
+	var objects []apiruntime.Object
+	for _, node := range nodes {
+		objects = append(objects, node)
+	}
+	cs := kubefake.NewSimpleClientset(objects...)
 	informerFactory := informers.NewSharedInformerFactory(cs, 0)
 	snapshot := newTestSharedLister(nil, nodes)
 
diff --git a/pkg/scheduler/plugins/deviceshare/scoring_test.go b/pkg/scheduler/plugins/deviceshare/scoring_test.go
index dbc19779..ae2a608b 100644
--- a/pkg/scheduler/plugins/deviceshare/scoring_test.go
+++ b/pkg/scheduler/plugins/deviceshare/scoring_test.go
@@ -18,6 +18,7 @@ package deviceshare
 
 import (
 	"context"
+	"fmt"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -42,15 +43,29 @@ func TestScore(t *testing.T) {
 			Name: "test-node",
 		},
 	}
-	testNodeInfo := &framework.NodeInfo{}
-	testNodeInfo.SetNode(testNode)
+
+	gpuResources := corev1.ResourceList{
+		apiext.ResourceGPUCore:        resource.MustParse("100"),
+		apiext.ResourceGPUMemoryRatio: resource.MustParse("100"),
+		apiext.ResourceGPUMemory:      resource.MustParse("16Gi"),
+	}
+
+	deviceInfoGenerator := func(deviceType schedulingv1alpha1.DeviceType, minor int, resources corev1.ResourceList) *schedulingv1alpha1.DeviceInfo {
+		return &schedulingv1alpha1.DeviceInfo{
+			Type:      deviceType,
+			Health:    true,
+			UUID:      fmt.Sprintf("%s-123456-%d", deviceType, minor),
+			Minor:     pointer.Int32(int32(minor)),
+			Resources: resources,
+		}
+	}
+
 	tests := []struct {
 		name            string
 		strategy        schedulerconfig.ScoringStrategyType
 		state           *preFilterState
 		reserved        apiext.DeviceAllocations
 		nodeDeviceCache *nodeDeviceCache
-		nodeInfo        *framework.NodeInfo
 		wantScore       int64
 		wantStatus      *framework.Status
 	}{
@@ -67,7 +82,6 @@ func TestScore(t *testing.T) {
 			name:            "empty node info",
 			state:           &preFilterState{skip: false},
 			nodeDeviceCache: newNodeDeviceCache(),
-			nodeInfo:        framework.NewNodeInfo(),
 			wantScore:       0,
 			wantStatus:      nil,
 		},
@@ -75,7 +89,6 @@ func TestScore(t *testing.T) {
 			name:            "error missing nodecache",
 			state:           &preFilterState{skip: false},
 			nodeDeviceCache: newNodeDeviceCache(),
-			nodeInfo:        testNodeInfo,
 			wantScore:       0,
 			wantStatus:      nil,
 		},
@@ -95,9 +108,8 @@ func TestScore(t *testing.T) {
 					"test-node": newNodeDevice(),
 				},
 			},
-			nodeInfo:   testNodeInfo,
 			wantScore:  0,
-			wantStatus: nil,
+			wantStatus: framework.NewStatus(framework.UnschedulableAndUnresolvable, "Insufficient gpu devices"),
 		},
 		{
 			name: "completely idle node",
@@ -114,27 +126,18 @@ func TestScore(t *testing.T) {
 				nodeDeviceInfos: map[string]*nodeDevice{
 					"test-node": {
 						deviceFree: map[schedulingv1alpha1.DeviceType]deviceResources{
-							schedulingv1alpha1.GPU: {
-								0: corev1.ResourceList{
-									apiext.ResourceGPUCore:        resource.MustParse("100"),
-									apiext.ResourceGPUMemoryRatio: resource.MustParse("100"),
-									apiext.ResourceGPUMemory:      resource.MustParse("16Gi"),
-								},
-							},
+							schedulingv1alpha1.GPU: {0: gpuResources},
 						},
 						deviceTotal: map[schedulingv1alpha1.DeviceType]deviceResources{
-							schedulingv1alpha1.GPU: {
-								0: corev1.ResourceList{
-									apiext.ResourceGPUCore:        resource.MustParse("100"),
-									apiext.ResourceGPUMemoryRatio: resource.MustParse("100"),
-									apiext.ResourceGPUMemory:      resource.MustParse("16Gi"),
-								},
-							},
+							schedulingv1alpha1.GPU: {0: gpuResources},
+						},
+						numaTopology: &NUMATopology{},
+						deviceInfos: map[schedulingv1alpha1.DeviceType][]*schedulingv1alpha1.DeviceInfo{
+							schedulingv1alpha1.GPU: {deviceInfoGenerator(schedulingv1alpha1.GPU, 0, gpuResources)},
 						},
 					},
 				},
 			},
-			nodeInfo:   testNodeInfo,
 			wantScore:  0,
 			wantStatus: nil,
 		},
@@ -154,37 +157,27 @@ func TestScore(t *testing.T) {
 					"test-node": {
 						deviceFree: map[schedulingv1alpha1.DeviceType]deviceResources{
 							schedulingv1alpha1.GPU: {
-								0: corev1.ResourceList{
-									apiext.ResourceGPUCore:        resource.MustParse("400"),
-									apiext.ResourceGPUMemoryRatio: resource.MustParse("400"),
-									apiext.ResourceGPUMemory:      resource.MustParse("64Gi"),
-								},
-								1: corev1.ResourceList{
-									apiext.ResourceGPUCore:        resource.MustParse("400"),
-									apiext.ResourceGPUMemoryRatio: resource.MustParse("400"),
-									apiext.ResourceGPUMemory:      resource.MustParse("64Gi"),
-								},
+								0: gpuResources,
+								1: gpuResources,
 							},
 						},
 						deviceTotal: map[schedulingv1alpha1.DeviceType]deviceResources{
 							schedulingv1alpha1.GPU: {
-								0: corev1.ResourceList{
-									apiext.ResourceGPUCore:        resource.MustParse("400"),
-									apiext.ResourceGPUMemoryRatio: resource.MustParse("400"),
-									apiext.ResourceGPUMemory:      resource.MustParse("64Gi"),
-								},
-								1: corev1.ResourceList{
-									apiext.ResourceGPUCore:        resource.MustParse("400"),
-									apiext.ResourceGPUMemoryRatio: resource.MustParse("400"),
-									apiext.ResourceGPUMemory:      resource.MustParse("64Gi"),
-								},
+								0: gpuResources,
+								1: gpuResources,
+							},
+						},
+						numaTopology: &NUMATopology{},
+						deviceInfos: map[schedulingv1alpha1.DeviceType][]*schedulingv1alpha1.DeviceInfo{
+							schedulingv1alpha1.GPU: {
+								deviceInfoGenerator(schedulingv1alpha1.GPU, 0, gpuResources),
+								deviceInfoGenerator(schedulingv1alpha1.GPU, 1, gpuResources),
 							},
 						},
 					},
 				},
 			},
-			nodeInfo:   testNodeInfo,
-			wantScore:  93,
+			wantScore:  75,
 			wantStatus: nil,
 		},
 		{
@@ -211,13 +204,7 @@ func TestScore(t *testing.T) {
 							},
 						},
 						deviceTotal: map[schedulingv1alpha1.DeviceType]deviceResources{
-							schedulingv1alpha1.GPU: {
-								0: corev1.ResourceList{
-									apiext.ResourceGPUCore:        resource.MustParse("100"),
-									apiext.ResourceGPUMemoryRatio: resource.MustParse("100"),
-									apiext.ResourceGPUMemory:      resource.MustParse("16Gi"),
-								},
-							},
+							schedulingv1alpha1.GPU: {0: gpuResources},
 						},
 						deviceUsed: map[schedulingv1alpha1.DeviceType]deviceResources{
 							schedulingv1alpha1.GPU: {
@@ -228,10 +215,13 @@ func TestScore(t *testing.T) {
 								},
 							},
 						},
+						numaTopology: &NUMATopology{},
+						deviceInfos: map[schedulingv1alpha1.DeviceType][]*schedulingv1alpha1.DeviceInfo{
+							schedulingv1alpha1.GPU: {deviceInfoGenerator(schedulingv1alpha1.GPU, 0, gpuResources)},
+						},
 					},
 				},
 			},
-			nodeInfo:   testNodeInfo,
 			wantScore:  25,
 			wantStatus: nil,
 		},
@@ -260,13 +250,7 @@ func TestScore(t *testing.T) {
 							},
 						},
 						deviceTotal: map[schedulingv1alpha1.DeviceType]deviceResources{
-							schedulingv1alpha1.GPU: {
-								0: corev1.ResourceList{
-									apiext.ResourceGPUCore:        resource.MustParse("100"),
-									apiext.ResourceGPUMemoryRatio: resource.MustParse("100"),
-									apiext.ResourceGPUMemory:      resource.MustParse("16Gi"),
-								},
-							},
+							schedulingv1alpha1.GPU: {0: gpuResources},
 						},
 						deviceUsed: map[schedulingv1alpha1.DeviceType]deviceResources{
 							schedulingv1alpha1.GPU: {
@@ -277,10 +261,13 @@ func TestScore(t *testing.T) {
 								},
 							},
 						},
+						numaTopology: &NUMATopology{},
+						deviceInfos: map[schedulingv1alpha1.DeviceType][]*schedulingv1alpha1.DeviceInfo{
+							schedulingv1alpha1.GPU: {deviceInfoGenerator(schedulingv1alpha1.GPU, 0, gpuResources)},
+						},
 					},
 				},
 			},
-			nodeInfo:   testNodeInfo,
 			wantScore:  75,
 			wantStatus: nil,
 		},
@@ -343,10 +330,22 @@ func TestScore(t *testing.T) {
 								},
 							},
 						},
+						numaTopology: &NUMATopology{},
+						deviceInfos: map[schedulingv1alpha1.DeviceType][]*schedulingv1alpha1.DeviceInfo{
+							schedulingv1alpha1.GPU: {
+								deviceInfoGenerator(schedulingv1alpha1.GPU, 0, corev1.ResourceList{
+									apiext.ResourceGPUCore:        resource.MustParse("1000"),
+									apiext.ResourceGPUMemoryRatio: resource.MustParse("1000"),
+									apiext.ResourceGPUMemory:      resource.MustParse("160Gi"),
+								}),
+							},
+							schedulingv1alpha1.RDMA: {
+								deviceInfoGenerator(schedulingv1alpha1.RDMA, 0, corev1.ResourceList{apiext.ResourceRDMA: resource.MustParse("50")}),
+							},
+						},
 					},
 				},
 			},
-			nodeInfo:   testNodeInfo,
 			wantScore:  184,
 			wantStatus: nil,
 		},
@@ -385,27 +384,18 @@ func TestScore(t *testing.T) {
 							},
 						},
 						deviceTotal: map[schedulingv1alpha1.DeviceType]deviceResources{
-							schedulingv1alpha1.GPU: {
-								0: corev1.ResourceList{
-									apiext.ResourceGPUCore:        resource.MustParse("100"),
-									apiext.ResourceGPUMemoryRatio: resource.MustParse("100"),
-									apiext.ResourceGPUMemory:      resource.MustParse("16Gi"),
-								},
-							},
+							schedulingv1alpha1.GPU: {0: gpuResources},
 						},
 						deviceUsed: map[schedulingv1alpha1.DeviceType]deviceResources{
-							schedulingv1alpha1.GPU: {
-								0: corev1.ResourceList{
-									apiext.ResourceGPUCore:        resource.MustParse("100"),
-									apiext.ResourceGPUMemoryRatio: resource.MustParse("100"),
-									apiext.ResourceGPUMemory:      resource.MustParse("16Gi"),
-								},
-							},
+							schedulingv1alpha1.GPU: {0: gpuResources},
+						},
+						numaTopology: &NUMATopology{},
+						deviceInfos: map[schedulingv1alpha1.DeviceType][]*schedulingv1alpha1.DeviceInfo{
+							schedulingv1alpha1.GPU: {deviceInfoGenerator(schedulingv1alpha1.GPU, 0, gpuResources)},
 						},
 					},
 				},
 			},
-			nodeInfo:   testNodeInfo,
 			wantScore:  50,
 			wantStatus: nil,
 		},
@@ -422,14 +412,7 @@ func TestScore(t *testing.T) {
 			},
 			reserved: apiext.DeviceAllocations{
 				schedulingv1alpha1.GPU: []*apiext.DeviceAllocation{
-					{
-						Minor: 0,
-						Resources: corev1.ResourceList{
-							apiext.ResourceGPUCore:        resource.MustParse("100"),
-							apiext.ResourceGPUMemoryRatio: resource.MustParse("100"),
-							apiext.ResourceGPUMemory:      resource.MustParse("16Gi"),
-						},
-					},
+					{Minor: 0, Resources: gpuResources},
 				},
 			},
 			nodeDeviceCache: &nodeDeviceCache{
@@ -446,32 +429,33 @@ func TestScore(t *testing.T) {
 							},
 						},
 						deviceTotal: map[schedulingv1alpha1.DeviceType]deviceResources{
-							schedulingv1alpha1.GPU: {
-								0: corev1.ResourceList{
-									apiext.ResourceGPUCore:        resource.MustParse("100"),
-									apiext.ResourceGPUMemoryRatio: resource.MustParse("100"),
-									apiext.ResourceGPUMemory:      resource.MustParse("16Gi"),
-								},
-							},
+							schedulingv1alpha1.GPU: {0: gpuResources},
+						},
+						deviceUsed:   map[schedulingv1alpha1.DeviceType]deviceResources{},
+						numaTopology: &NUMATopology{},
+						deviceInfos: map[schedulingv1alpha1.DeviceType][]*schedulingv1alpha1.DeviceInfo{
+							schedulingv1alpha1.GPU: {deviceInfoGenerator(schedulingv1alpha1.GPU, 0, gpuResources)},
 						},
-						deviceUsed: map[schedulingv1alpha1.DeviceType]deviceResources{},
 					},
 				},
 			},
-			nodeInfo:   testNodeInfo,
 			wantScore:  50,
 			wantStatus: nil,
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			suit := newPluginTestSuit(t, []*corev1.Node{testNode})
 			args := getDefaultArgs()
 			if tt.strategy != "" {
 				args.ScoringStrategy.Type = tt.strategy
 			}
-			scorerPlugin := deviceResourceStrategyTypeMap[args.ScoringStrategy.Type]
-			scorer := scorerPlugin(args)
-			p := &Plugin{nodeDeviceCache: tt.nodeDeviceCache, scorer: scorer}
+			p, err := suit.proxyNew(args, suit)
+			assert.NoError(t, err)
+
+			pl := p.(*Plugin)
+			pl.nodeDeviceCache = tt.nodeDeviceCache
+
 			cycleState := framework.NewCycleState()
 			if tt.state != nil {
 				cycleState.Write(stateKey, tt.state)
@@ -514,7 +498,7 @@ func TestScore(t *testing.T) {
 				}
 				cycleState.Write(reservationRestoreStateKey, restoreState)
 			}
-			score, status := p.Score(context.TODO(), cycleState, &corev1.Pod{}, "test-node")
+			score, status := pl.Score(context.TODO(), cycleState, &corev1.Pod{}, "test-node")
 			assert.Equal(t, tt.wantScore, score)
 			assert.Equal(t, tt.wantStatus, status)
 		})
@@ -598,22 +582,19 @@ func TestScoreReservation(t *testing.T) {
 			Name: "test-node",
 		},
 	}
-	testNodeInfo := &framework.NodeInfo{}
-	testNodeInfo.SetNode(testNode)
+
+	gpuResources := corev1.ResourceList{
+		apiext.ResourceGPUCore:        resource.MustParse("100"),
+		apiext.ResourceGPUMemoryRatio: resource.MustParse("100"),
+		apiext.ResourceGPUMemory:      resource.MustParse("16Gi"),
+	}
+	rdmaResources := corev1.ResourceList{
+		apiext.ResourceRDMA: resource.MustParse("100"),
+	}
 
 	deviceTotal := map[schedulingv1alpha1.DeviceType]deviceResources{
-		schedulingv1alpha1.GPU: {
-			0: corev1.ResourceList{
-				apiext.ResourceGPUCore:        resource.MustParse("100"),
-				apiext.ResourceGPUMemoryRatio: resource.MustParse("100"),
-				apiext.ResourceGPUMemory:      resource.MustParse("16Gi"),
-			},
-		},
-		schedulingv1alpha1.RDMA: {
-			0: corev1.ResourceList{
-				apiext.ResourceRDMA: resource.MustParse("100"),
-			},
-		},
+		schedulingv1alpha1.GPU:  {0: gpuResources},
+		schedulingv1alpha1.RDMA: {0: rdmaResources},
 	}
 
 	deviceUsed := func() map[schedulingv1alpha1.DeviceType]deviceResources {
@@ -628,6 +609,27 @@ func TestScoreReservation(t *testing.T) {
 		}
 	}
 
+	deviceInfos := map[schedulingv1alpha1.DeviceType][]*schedulingv1alpha1.DeviceInfo{
+		schedulingv1alpha1.GPU: {
+			{
+				Type:      schedulingv1alpha1.GPU,
+				Health:    true,
+				UUID:      "gpu-123456-0",
+				Minor:     pointer.Int32(0),
+				Resources: gpuResources,
+			},
+		},
+		schedulingv1alpha1.RDMA: {
+			{
+				Type:      schedulingv1alpha1.RDMA,
+				Health:    true,
+				UUID:      "rdma-123456-0",
+				Minor:     pointer.Int32(0),
+				Resources: rdmaResources,
+			},
+		},
+	}
+
 	tests := []struct {
 		name               string
 		podRequests        corev1.ResourceList
@@ -637,7 +639,6 @@ func TestScoreReservation(t *testing.T) {
 		allocatePolicy     schedulingv1alpha1.ReservationAllocatePolicy
 		scoreStrategy      schedulerconfig.ScoringStrategyType
 		nodeDeviceCache    *nodeDeviceCache
-		nodeInfo           *framework.NodeInfo
 		wantScore          int64
 		wantNormalize      *int64
 		wantStatus         *framework.Status
@@ -664,14 +665,15 @@ func TestScoreReservation(t *testing.T) {
 			nodeDeviceCache: &nodeDeviceCache{
 				nodeDeviceInfos: map[string]*nodeDevice{
 					"test-node": {
-						deviceTotal: deviceTotal,
-						deviceUsed:  deviceUsed(),
-						deviceFree:  map[schedulingv1alpha1.DeviceType]deviceResources{},
-						allocateSet: map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						deviceTotal:  deviceTotal,
+						deviceUsed:   deviceUsed(),
+						deviceFree:   map[schedulingv1alpha1.DeviceType]deviceResources{},
+						allocateSet:  map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						numaTopology: &NUMATopology{},
+						deviceInfos:  deviceInfos,
 					},
 				},
 			},
-			nodeInfo:   testNodeInfo,
 			wantScore:  25,
 			wantStatus: nil,
 		},
@@ -698,14 +700,15 @@ func TestScoreReservation(t *testing.T) {
 			nodeDeviceCache: &nodeDeviceCache{
 				nodeDeviceInfos: map[string]*nodeDevice{
 					"test-node": {
-						deviceTotal: deviceTotal,
-						deviceUsed:  deviceUsed(),
-						deviceFree:  map[schedulingv1alpha1.DeviceType]deviceResources{},
-						allocateSet: map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						deviceTotal:  deviceTotal,
+						deviceUsed:   deviceUsed(),
+						deviceFree:   map[schedulingv1alpha1.DeviceType]deviceResources{},
+						allocateSet:  map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						numaTopology: &NUMATopology{},
+						deviceInfos:  deviceInfos,
 					},
 				},
 			},
-			nodeInfo:   testNodeInfo,
 			wantScore:  75,
 			wantStatus: nil,
 		},
@@ -732,14 +735,15 @@ func TestScoreReservation(t *testing.T) {
 			nodeDeviceCache: &nodeDeviceCache{
 				nodeDeviceInfos: map[string]*nodeDevice{
 					"test-node": {
-						deviceTotal: deviceTotal,
-						deviceFree:  map[schedulingv1alpha1.DeviceType]deviceResources{},
-						deviceUsed:  deviceUsed(),
-						allocateSet: map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						deviceTotal:  deviceTotal,
+						deviceFree:   map[schedulingv1alpha1.DeviceType]deviceResources{},
+						deviceUsed:   deviceUsed(),
+						allocateSet:  map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						numaTopology: &NUMATopology{},
+						deviceInfos:  deviceInfos,
 					},
 				},
 			},
-			nodeInfo:   testNodeInfo,
 			wantScore:  25,
 			wantStatus: nil,
 		},
@@ -766,14 +770,15 @@ func TestScoreReservation(t *testing.T) {
 			nodeDeviceCache: &nodeDeviceCache{
 				nodeDeviceInfos: map[string]*nodeDevice{
 					"test-node": {
-						deviceTotal: deviceTotal,
-						deviceFree:  map[schedulingv1alpha1.DeviceType]deviceResources{},
-						deviceUsed:  deviceUsed(),
-						allocateSet: map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						deviceTotal:  deviceTotal,
+						deviceFree:   map[schedulingv1alpha1.DeviceType]deviceResources{},
+						deviceUsed:   deviceUsed(),
+						allocateSet:  map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						numaTopology: &NUMATopology{},
+						deviceInfos:  deviceInfos,
 					},
 				},
 			},
-			nodeInfo:   testNodeInfo,
 			wantScore:  75,
 			wantStatus: nil,
 		},
@@ -800,14 +805,15 @@ func TestScoreReservation(t *testing.T) {
 			nodeDeviceCache: &nodeDeviceCache{
 				nodeDeviceInfos: map[string]*nodeDevice{
 					"test-node": {
-						deviceTotal: deviceTotal,
-						deviceFree:  map[schedulingv1alpha1.DeviceType]deviceResources{},
-						deviceUsed:  map[schedulingv1alpha1.DeviceType]deviceResources{},
-						allocateSet: map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						deviceTotal:  deviceTotal,
+						deviceFree:   map[schedulingv1alpha1.DeviceType]deviceResources{},
+						deviceUsed:   map[schedulingv1alpha1.DeviceType]deviceResources{},
+						allocateSet:  map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						numaTopology: &NUMATopology{},
+						deviceInfos:  deviceInfos,
 					},
 				},
 			},
-			nodeInfo:   testNodeInfo,
 			wantScore:  0,
 			wantStatus: nil,
 		},
@@ -834,14 +840,15 @@ func TestScoreReservation(t *testing.T) {
 			nodeDeviceCache: &nodeDeviceCache{
 				nodeDeviceInfos: map[string]*nodeDevice{
 					"test-node": {
-						deviceTotal: deviceTotal,
-						deviceFree:  map[schedulingv1alpha1.DeviceType]deviceResources{},
-						deviceUsed:  map[schedulingv1alpha1.DeviceType]deviceResources{},
-						allocateSet: map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						deviceTotal:  deviceTotal,
+						deviceFree:   map[schedulingv1alpha1.DeviceType]deviceResources{},
+						deviceUsed:   map[schedulingv1alpha1.DeviceType]deviceResources{},
+						allocateSet:  map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						numaTopology: &NUMATopology{},
+						deviceInfos:  deviceInfos,
 					},
 				},
 			},
-			nodeInfo:   testNodeInfo,
 			wantScore:  100,
 			wantStatus: nil,
 		},
@@ -878,14 +885,15 @@ func TestScoreReservation(t *testing.T) {
 			nodeDeviceCache: &nodeDeviceCache{
 				nodeDeviceInfos: map[string]*nodeDevice{
 					"test-node": {
-						deviceTotal: deviceTotal,
-						deviceFree:  map[schedulingv1alpha1.DeviceType]deviceResources{},
-						deviceUsed:  deviceUsed(),
-						allocateSet: map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						deviceTotal:  deviceTotal,
+						deviceFree:   map[schedulingv1alpha1.DeviceType]deviceResources{},
+						deviceUsed:   deviceUsed(),
+						allocateSet:  map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						numaTopology: &NUMATopology{},
+						deviceInfos:  deviceInfos,
 					},
 				},
 			},
-			nodeInfo:   testNodeInfo,
 			wantScore:  50,
 			wantStatus: nil,
 		},
@@ -933,11 +941,12 @@ func TestScoreReservation(t *testing.T) {
 								},
 							},
 						},
-						allocateSet: map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						allocateSet:  map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						numaTopology: &NUMATopology{},
+						deviceInfos:  deviceInfos,
 					},
 				},
 			},
-			nodeInfo:   testNodeInfo,
 			wantScore:  50,
 			wantStatus: nil,
 		},
@@ -966,28 +975,42 @@ func TestScoreReservation(t *testing.T) {
 				nodeDeviceInfos: map[string]*nodeDevice{
 					"test-node": {
 						deviceTotal: deviceTotal,
-						deviceFree:  map[schedulingv1alpha1.DeviceType]deviceResources{},
-						deviceUsed:  map[schedulingv1alpha1.DeviceType]deviceResources{},
-						allocateSet: map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						deviceFree: map[schedulingv1alpha1.DeviceType]deviceResources{
+							schedulingv1alpha1.RDMA: {
+								0: rdmaResources,
+							},
+						},
+						deviceUsed: map[schedulingv1alpha1.DeviceType]deviceResources{
+							schedulingv1alpha1.GPU: {
+								0: {
+									apiext.ResourceGPUCore:        resource.MustParse("50"),
+									apiext.ResourceGPUMemoryRatio: resource.MustParse("50"),
+									apiext.ResourceGPUMemory:      resource.MustParse("8Gi"),
+								},
+							},
+						},
+						allocateSet:  map[schedulingv1alpha1.DeviceType]map[types.NamespacedName]deviceResources{},
+						numaTopology: &NUMATopology{},
+						deviceInfos:  deviceInfos,
 					},
 				},
 			},
-			nodeInfo:      testNodeInfo,
-			wantScore:     200,
+			wantScore:     120,
 			wantNormalize: pointer.Int64(100),
 			wantStatus:    nil,
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
+			suit := newPluginTestSuit(t, []*corev1.Node{testNode})
 			args := getDefaultArgs()
 			if tt.scoreStrategy != "" {
 				args.ScoringStrategy.Type = tt.scoreStrategy
 			}
-			scorerPlugin := deviceResourceStrategyTypeMap[args.ScoringStrategy.Type]
-			scorer := scorerPlugin(args)
-			p := &Plugin{nodeDeviceCache: tt.nodeDeviceCache, scorer: scorer}
-			cycleState := framework.NewCycleState()
+			p, err := suit.proxyNew(args, suit)
+			assert.NoError(t, err)
+			pl := p.(*Plugin)
+			pl.nodeDeviceCache = tt.nodeDeviceCache
 
 			pod := &corev1.Pod{
 				ObjectMeta: metav1.ObjectMeta{
@@ -1007,7 +1030,8 @@ func TestScoreReservation(t *testing.T) {
 				},
 			}
 
-			_, status := p.PreFilter(context.TODO(), cycleState, pod)
+			cycleState := framework.NewCycleState()
+			_, status := pl.PreFilter(context.TODO(), cycleState, pod)
 			assert.True(t, status.IsSuccess())
 
 			state, status := getPreFilterState(cycleState)
@@ -1037,16 +1061,18 @@ func TestScoreReservation(t *testing.T) {
 				tt.nodeDeviceCache.updatePod(nil, reservationutil.NewReservePod(reservation))
 
 				rInfo = frameworkext.NewReservationInfo(reservation)
-				status := p.PreRestoreReservation(context.TODO(), cycleState, pod)
+				status := pl.PreRestoreReservation(context.TODO(), cycleState, pod)
 				assert.True(t, status.IsSuccess())
-				nodeToState, status := p.RestoreReservation(context.TODO(), cycleState, pod, []*frameworkext.ReservationInfo{rInfo}, nil, tt.nodeInfo)
+
+				nodeInfo, err := pl.handle.SnapshotSharedLister().NodeInfos().Get(testNode.Name)
+				assert.NoError(t, err)
+
+				nodeToState, status := pl.RestoreReservation(context.TODO(), cycleState, pod, []*frameworkext.ReservationInfo{rInfo}, nil, nodeInfo)
 				assert.True(t, status.IsSuccess())
-				status = p.FinalRestoreReservation(context.TODO(), cycleState, pod, frameworkext.NodeReservationRestoreStates{
-					"test-node": nodeToState,
-				})
+				status = pl.FinalRestoreReservation(context.TODO(), cycleState, pod, frameworkext.NodeReservationRestoreStates{testNode.Name: nodeToState})
 				assert.True(t, status.IsSuccess())
 			}
-			score, status := p.ScoreReservation(context.TODO(), cycleState, pod, rInfo, "test-node")
+			score, status := pl.ScoreReservation(context.TODO(), cycleState, pod, rInfo, "test-node")
 			assert.Equal(t, tt.wantStatus, status)
 			assert.Equal(t, tt.wantScore, score)
 			if tt.wantNormalize != nil {
@@ -1055,7 +1081,7 @@ func TestScoreReservation(t *testing.T) {
 						Score: score,
 					},
 				}
-				status = p.ReservationScoreExtensions().NormalizeReservationScore(context.TODO(), cycleState, pod, scoreList)
+				status = pl.ReservationScoreExtensions().NormalizeReservationScore(context.TODO(), cycleState, pod, scoreList)
 				assert.True(t, status.IsSuccess())
 				assert.Equal(t, *tt.wantNormalize, scoreList[0].Score)
 			}
diff --git a/pkg/scheduler/plugins/deviceshare/topology_hint_test.go b/pkg/scheduler/plugins/deviceshare/topology_hint_test.go
new file mode 100644
index 00000000..ff5f3d61
--- /dev/null
+++ b/pkg/scheduler/plugins/deviceshare/topology_hint_test.go
@@ -0,0 +1,430 @@
+/*
+Copyright 2022 The Koordinator Authors.
+
+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 deviceshare
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	corev1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/kubernetes/pkg/scheduler/framework"
+
+	apiext "github.com/koordinator-sh/koordinator/apis/extension"
+	schedulingv1alpha1 "github.com/koordinator-sh/koordinator/apis/scheduling/v1alpha1"
+	"github.com/koordinator-sh/koordinator/pkg/scheduler/frameworkext/topologymanager"
+	"github.com/koordinator-sh/koordinator/pkg/util/bitmask"
+)
+
+func newBitMask(bits ...int) bitmask.BitMask {
+	mask, _ := bitmask.NewBitMask(bits...)
+	return mask
+}
+
+func TestPlugin_GetPodTopologyHints(t *testing.T) {
+	node := &corev1.Node{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "node-1",
+		},
+	}
+
+	gpuRequests := corev1.ResourceList{
+		apiext.ResourceGPUCore:        resource.MustParse("100"),
+		apiext.ResourceGPUMemoryRatio: resource.MustParse("100"),
+	}
+	rdmaRequests := corev1.ResourceList{
+		apiext.ResourceRDMA: resource.MustParse("2"),
+	}
+	tests := []struct {
+		name            string
+		podRequests     map[schedulingv1alpha1.DeviceType]corev1.ResourceList
+		hints           apiext.DeviceAllocateHints
+		jointAllocate   *apiext.DeviceJointAllocate
+		assignedDevices apiext.DeviceAllocations
+		want            map[string][]topologymanager.NUMATopologyHint
+		wantErr         bool
+	}{
+		{
+			name: "generate gpu&rdma hints",
+			podRequests: map[schedulingv1alpha1.DeviceType]corev1.ResourceList{
+				schedulingv1alpha1.GPU:  gpuRequests,
+				schedulingv1alpha1.RDMA: rdmaRequests,
+			},
+			want: map[string][]topologymanager.NUMATopologyHint{
+				string(apiext.ResourceGPUCore): {
+					{NUMANodeAffinity: newBitMask(0), Preferred: true},
+					{NUMANodeAffinity: newBitMask(1), Preferred: true},
+					{NUMANodeAffinity: newBitMask(0, 1), Preferred: false},
+				},
+				string(apiext.ResourceGPUMemory): {
+					{NUMANodeAffinity: newBitMask(0), Preferred: true},
+					{NUMANodeAffinity: newBitMask(1), Preferred: true},
+					{NUMANodeAffinity: newBitMask(0, 1), Preferred: false},
+				},
+				string(apiext.ResourceGPUMemoryRatio): {
+					{NUMANodeAffinity: newBitMask(0), Preferred: true},
+					{NUMANodeAffinity: newBitMask(1), Preferred: true},
+					{NUMANodeAffinity: newBitMask(0, 1), Preferred: false},
+				},
+				string(apiext.ResourceRDMA): {
+					{NUMANodeAffinity: newBitMask(0), Preferred: true},
+					{NUMANodeAffinity: newBitMask(1), Preferred: true},
+					{NUMANodeAffinity: newBitMask(0, 1), Preferred: false},
+				},
+			},
+		},
+		{
+			name: "generate gpu hints with assigned devices",
+			podRequests: map[schedulingv1alpha1.DeviceType]corev1.ResourceList{
+				schedulingv1alpha1.GPU: {
+					apiext.ResourceGPUCore:        resource.MustParse("400"),
+					apiext.ResourceGPUMemoryRatio: resource.MustParse("400"),
+				},
+			},
+			assignedDevices: map[schedulingv1alpha1.DeviceType][]*apiext.DeviceAllocation{
+				schedulingv1alpha1.GPU: {
+					{
+						Minor:     0,
+						Resources: gpuRequests,
+					},
+				},
+			},
+			want: map[string][]topologymanager.NUMATopologyHint{
+				string(apiext.ResourceGPUCore): {
+					{NUMANodeAffinity: newBitMask(1), Preferred: true},
+					{NUMANodeAffinity: newBitMask(0, 1), Preferred: false},
+				},
+				string(apiext.ResourceGPUMemory): {
+					{NUMANodeAffinity: newBitMask(1), Preferred: true},
+					{NUMANodeAffinity: newBitMask(0, 1), Preferred: false},
+				},
+				string(apiext.ResourceGPUMemoryRatio): {
+					{NUMANodeAffinity: newBitMask(1), Preferred: true},
+					{NUMANodeAffinity: newBitMask(0, 1), Preferred: false},
+				},
+			},
+		},
+		{
+			name: "generate fpga empty hints",
+			podRequests: map[schedulingv1alpha1.DeviceType]corev1.ResourceList{
+				schedulingv1alpha1.FPGA: {
+					apiext.ResourceFPGA: resource.MustParse("100"),
+				},
+			},
+			want: map[string][]topologymanager.NUMATopologyHint{},
+		},
+		{
+			name: "generate 2 rdma hints",
+			podRequests: map[schedulingv1alpha1.DeviceType]corev1.ResourceList{
+				schedulingv1alpha1.RDMA: rdmaRequests,
+			},
+			hints: apiext.DeviceAllocateHints{
+				schedulingv1alpha1.RDMA: {
+					AllocateStrategy: apiext.RequestsAsCountAllocateStrategy,
+				},
+			},
+			want: map[string][]topologymanager.NUMATopologyHint{
+				string(apiext.ResourceRDMA): {
+					{NUMANodeAffinity: newBitMask(0), Preferred: true},
+					{NUMANodeAffinity: newBitMask(1), Preferred: true},
+					{NUMANodeAffinity: newBitMask(0, 1), Preferred: false},
+				},
+			},
+		},
+		{
+			name: "generate rdma 2 vf hints",
+			podRequests: map[schedulingv1alpha1.DeviceType]corev1.ResourceList{
+				schedulingv1alpha1.RDMA: rdmaRequests,
+			},
+			hints: apiext.DeviceAllocateHints{
+				schedulingv1alpha1.RDMA: {
+					VFSelector:       &metav1.LabelSelector{},
+					AllocateStrategy: apiext.RequestsAsCountAllocateStrategy,
+				},
+			},
+			want: map[string][]topologymanager.NUMATopologyHint{
+				string(apiext.ResourceRDMA): {
+					{NUMANodeAffinity: newBitMask(0), Preferred: true},
+					{NUMANodeAffinity: newBitMask(1), Preferred: true},
+					{NUMANodeAffinity: newBitMask(0, 1), Preferred: false},
+				},
+			},
+		},
+		{
+			name: "generate rdma 4 vf hints",
+			podRequests: map[schedulingv1alpha1.DeviceType]corev1.ResourceList{
+				schedulingv1alpha1.RDMA: {
+					apiext.ResourceRDMA: resource.MustParse("4"),
+				},
+			},
+			hints: apiext.DeviceAllocateHints{
+				schedulingv1alpha1.RDMA: {
+					VFSelector:       &metav1.LabelSelector{},
+					AllocateStrategy: apiext.RequestsAsCountAllocateStrategy,
+				},
+			},
+			want: map[string][]topologymanager.NUMATopologyHint{
+				string(apiext.ResourceRDMA): {
+					{NUMANodeAffinity: newBitMask(0, 1), Preferred: true},
+				},
+			},
+		},
+		{
+			name: "generate joint-allocate gpu&rdma hints",
+			podRequests: map[schedulingv1alpha1.DeviceType]corev1.ResourceList{
+				schedulingv1alpha1.RDMA: rdmaRequests,
+				schedulingv1alpha1.GPU:  gpuRequests,
+			},
+			hints: apiext.DeviceAllocateHints{
+				schedulingv1alpha1.RDMA: {
+					VFSelector: &metav1.LabelSelector{},
+				},
+			},
+			jointAllocate: &apiext.DeviceJointAllocate{
+				DeviceTypes: []schedulingv1alpha1.DeviceType{schedulingv1alpha1.GPU, schedulingv1alpha1.RDMA},
+			},
+			want: map[string][]topologymanager.NUMATopologyHint{
+				string(apiext.ResourceGPUCore): {
+					{NUMANodeAffinity: newBitMask(0), Preferred: true},
+					{NUMANodeAffinity: newBitMask(1), Preferred: true},
+					{NUMANodeAffinity: newBitMask(0, 1), Preferred: false},
+				},
+				string(apiext.ResourceGPUMemory): {
+					{NUMANodeAffinity: newBitMask(0), Preferred: true},
+					{NUMANodeAffinity: newBitMask(1), Preferred: true},
+					{NUMANodeAffinity: newBitMask(0, 1), Preferred: false},
+				},
+				string(apiext.ResourceGPUMemoryRatio): {
+					{NUMANodeAffinity: newBitMask(0), Preferred: true},
+					{NUMANodeAffinity: newBitMask(1), Preferred: true},
+					{NUMANodeAffinity: newBitMask(0, 1), Preferred: false},
+				},
+				string(apiext.ResourceRDMA): {
+					{NUMANodeAffinity: newBitMask(0), Preferred: true},
+					{NUMANodeAffinity: newBitMask(1), Preferred: true},
+					{NUMANodeAffinity: newBitMask(0, 1), Preferred: false},
+				},
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			suit := newPluginTestSuit(t, []*corev1.Node{node})
+			deviceCR := fakeDeviceCR.DeepCopy()
+			deviceCR.Name = node.Name
+			deviceCR.ResourceVersion = "1"
+			_, err := suit.koordClientSet.SchedulingV1alpha1().Devices().Create(context.TODO(), deviceCR, metav1.CreateOptions{})
+			assert.NoError(t, err)
+
+			p, err := suit.proxyNew(getDefaultArgs(), suit.Framework)
+			assert.NoError(t, err)
+
+			suit.koordinatorSharedInformerFactory.Start(nil)
+			suit.SharedInformerFactory().Start(nil)
+			suit.koordinatorSharedInformerFactory.WaitForCacheSync(nil)
+			suit.SharedInformerFactory().WaitForCacheSync(nil)
+
+			pl := p.(*Plugin)
+			if tt.assignedDevices != nil {
+				pod := &corev1.Pod{
+					ObjectMeta: metav1.ObjectMeta{
+						Namespace: "default",
+						Name:      "assigned-pod",
+						UID:       "123456",
+					},
+					Spec: corev1.PodSpec{
+						NodeName: node.Name,
+					},
+				}
+				assert.NoError(t, apiext.SetDeviceAllocations(pod, tt.assignedDevices))
+				pl.nodeDeviceCache.updatePod(nil, pod)
+			}
+
+			hintSelectors, err := newHintSelectors(tt.hints)
+			assert.NoError(t, err)
+
+			state := &preFilterState{
+				skip:          false,
+				podRequests:   tt.podRequests,
+				hints:         tt.hints,
+				hintSelectors: hintSelectors,
+				jointAllocate: tt.jointAllocate,
+			}
+			cycleState := framework.NewCycleState()
+			cycleState.Write(stateKey, state)
+			pod := &corev1.Pod{}
+
+			got, status := pl.GetPodTopologyHints(context.TODO(), cycleState, pod, node.Name)
+			assert.Equal(t, tt.want, got)
+			if !tt.wantErr != status.IsSuccess() {
+				t.Errorf("expect tt.wantErr=%v, but got %v", tt.wantErr, status)
+				return
+			}
+		})
+	}
+}
+
+func TestPlugin_Allocate(t *testing.T) {
+	node := &corev1.Node{
+		ObjectMeta: metav1.ObjectMeta{
+			Name: "node-1",
+		},
+	}
+
+	gpuRequests := corev1.ResourceList{
+		apiext.ResourceGPUCore:   resource.MustParse("100"),
+		apiext.ResourceGPUMemory: resource.MustParse("8Gi"),
+	}
+	rdmaRequests := corev1.ResourceList{
+		apiext.ResourceRDMA: resource.MustParse("2"),
+	}
+	tests := []struct {
+		name          string
+		podRequests   map[schedulingv1alpha1.DeviceType]corev1.ResourceList
+		hints         apiext.DeviceAllocateHints
+		affinity      topologymanager.NUMATopologyHint
+		jointAllocate *apiext.DeviceJointAllocate
+		wantErr       bool
+	}{
+		{
+			name: "allocate gpu&rdma by affinity",
+			podRequests: map[schedulingv1alpha1.DeviceType]corev1.ResourceList{
+				schedulingv1alpha1.GPU:  gpuRequests,
+				schedulingv1alpha1.RDMA: rdmaRequests,
+			},
+			affinity: topologymanager.NUMATopologyHint{
+				NUMANodeAffinity: newBitMask(0),
+			},
+		},
+		{
+			name: "generate fpga empty hints",
+			podRequests: map[schedulingv1alpha1.DeviceType]corev1.ResourceList{
+				schedulingv1alpha1.FPGA: {
+					apiext.ResourceFPGA: resource.MustParse("100"),
+				},
+			},
+			affinity: topologymanager.NUMATopologyHint{},
+			wantErr:  true,
+		},
+		{
+			name: "generate 2 rdma hints",
+			podRequests: map[schedulingv1alpha1.DeviceType]corev1.ResourceList{
+				schedulingv1alpha1.RDMA: rdmaRequests,
+			},
+			hints: apiext.DeviceAllocateHints{
+				schedulingv1alpha1.RDMA: {
+					AllocateStrategy: apiext.RequestsAsCountAllocateStrategy,
+				},
+			},
+			affinity: topologymanager.NUMATopologyHint{
+				NUMANodeAffinity: newBitMask(0),
+			},
+		},
+		{
+			name: "generate rdma 2 vf hints",
+			podRequests: map[schedulingv1alpha1.DeviceType]corev1.ResourceList{
+				schedulingv1alpha1.RDMA: rdmaRequests,
+			},
+			hints: apiext.DeviceAllocateHints{
+				schedulingv1alpha1.RDMA: {
+					VFSelector:       &metav1.LabelSelector{},
+					AllocateStrategy: apiext.RequestsAsCountAllocateStrategy,
+				},
+			},
+			affinity: topologymanager.NUMATopologyHint{
+				NUMANodeAffinity: newBitMask(0),
+			},
+		},
+		{
+			name: "generate rdma 4 vf hints",
+			podRequests: map[schedulingv1alpha1.DeviceType]corev1.ResourceList{
+				schedulingv1alpha1.RDMA: {
+					apiext.ResourceRDMA: resource.MustParse("4"),
+				},
+			},
+			hints: apiext.DeviceAllocateHints{
+				schedulingv1alpha1.RDMA: {
+					VFSelector:       &metav1.LabelSelector{},
+					AllocateStrategy: apiext.RequestsAsCountAllocateStrategy,
+				},
+			},
+			affinity: topologymanager.NUMATopologyHint{
+				NUMANodeAffinity: newBitMask(0),
+			},
+			wantErr: true,
+		},
+		{
+			name: "generate joint-allocate gpu&rdma hints",
+			podRequests: map[schedulingv1alpha1.DeviceType]corev1.ResourceList{
+				schedulingv1alpha1.RDMA: rdmaRequests,
+				schedulingv1alpha1.GPU:  gpuRequests,
+			},
+			hints: apiext.DeviceAllocateHints{
+				schedulingv1alpha1.RDMA: {
+					VFSelector: &metav1.LabelSelector{},
+				},
+			},
+			jointAllocate: &apiext.DeviceJointAllocate{
+				DeviceTypes: []schedulingv1alpha1.DeviceType{schedulingv1alpha1.GPU, schedulingv1alpha1.RDMA},
+			},
+			affinity: topologymanager.NUMATopologyHint{
+				NUMANodeAffinity: newBitMask(0),
+			},
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			suit := newPluginTestSuit(t, []*corev1.Node{node})
+			deviceCR := fakeDeviceCR.DeepCopy()
+			deviceCR.Name = node.Name
+			deviceCR.ResourceVersion = "1"
+			_, err := suit.koordClientSet.SchedulingV1alpha1().Devices().Create(context.TODO(), deviceCR, metav1.CreateOptions{})
+			assert.NoError(t, err)
+			p, err := suit.proxyNew(getDefaultArgs(), suit.Framework)
+			assert.NoError(t, err)
+
+			suit.koordinatorSharedInformerFactory.Start(nil)
+			suit.SharedInformerFactory().Start(nil)
+			suit.koordinatorSharedInformerFactory.WaitForCacheSync(nil)
+			suit.SharedInformerFactory().WaitForCacheSync(nil)
+
+			hintSelectors, err := newHintSelectors(tt.hints)
+			assert.NoError(t, err)
+
+			state := &preFilterState{
+				skip:          false,
+				podRequests:   tt.podRequests,
+				hints:         tt.hints,
+				hintSelectors: hintSelectors,
+				jointAllocate: tt.jointAllocate,
+			}
+			cycleState := framework.NewCycleState()
+			cycleState.Write(stateKey, state)
+			pod := &corev1.Pod{}
+			pl := p.(*Plugin)
+			status := pl.Allocate(context.TODO(), cycleState, tt.affinity, pod, node.Name)
+			if !tt.wantErr != status.IsSuccess() {
+				t.Errorf("expect tt.wantErr=%v, but got %v", tt.wantErr, status)
+				return
+			}
+		})
+	}
+}