Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changes/unreleased/added-20260810-112224.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
kind: Added
body: |-
Enforce fractional GPU compute modes
99 changes: 99 additions & 0 deletions pkg/scheduler/actions/allocate/allocateFractionalGpu_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,20 @@ import (
. "go.uber.org/mock/gomock"
"gopkg.in/h2non/gock.v1"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"

kaiv1common "github.com/kai-scheduler/KAI-scheduler/pkg/apis/kai/v1/common"
schedulingv1alpha2 "github.com/kai-scheduler/KAI-scheduler/pkg/apis/scheduling/v1alpha2"
commonconstants "github.com/kai-scheduler/KAI-scheduler/pkg/common/constants"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/actions/allocate"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/actions/integration_tests/integration_tests_utils"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/api/common_info"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/api/node_info"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/api/pod_info"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/api/pod_status"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/api/resource_info"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/conf"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/constants"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/test_utils"
Expand All @@ -42,6 +49,69 @@ func TestHandleFractionalGPUAllocation(t *testing.T) {
}
}

func TestFractionalGPUAllocationDoesNotUseGpuGroupWithDifferentComputeMode(t *testing.T) {
test_utils.InitTestingInfrastructure()
controller := NewController(t)
defer controller.Finish()
defer gock.Off()

const gpuGroup = "time-slicing-group"
topology := test_utils.TestTopologyBasic{
Name: "sm-sharing pod does not allocate on time-slicing gpu group",
Jobs: []*jobs_fake.TestJobBasic{
{
Name: "running_job0",
RequiredGPUsPerTask: 0.5,
Priority: constants.PriorityTrainNumber,
QueueName: "queue0",
Tasks: []*tasks_fake.TestTaskBasic{
{
NodeName: "node0",
GPUGroups: []string{gpuGroup},
State: pod_status.Running,
},
},
},
{
Name: "pending_job0",
RequiredGPUsPerTask: 0.5,
Priority: constants.PriorityTrainNumber,
QueueName: "queue0",
Tasks: []*tasks_fake.TestTaskBasic{
{
State: pod_status.Pending,
Annotations: map[string]string{
commonconstants.GpuComputeSharingMode: string(schedulingv1alpha2.GPUComputeSharingModeSMSharing),
},
},
},
},
},
Nodes: map[string]nodes_fake.TestNodeBasic{
"node0": {
GPUs: 1,
},
},
Queues: []test_utils.TestQueueBasic{
{
Name: "queue0",
DeservedGPUs: 1,
},
},
}

ssn := test_utils.BuildSession(topology, controller)
addReservationPodToNodeForTest(ssn.ClusterInfo.Nodes["node0"], gpuGroup, schedulingv1alpha2.GPUComputeSharingModeTimeSlicing)

allocate.New().Execute(ssn)

pendingTask := ssn.ClusterInfo.PodGroupInfos["pending_job0"].GetAllPodsMap()[common_info.PodID("pending_job0-0")]
if pendingTask.Status != pod_status.Pending {
t.Fatalf("expected sm-sharing task to stay pending, got status %s on node %s with groups %v",
pendingTask.Status, pendingTask.NodeName, pendingTask.GPUGroupIDs())
}
}

func TestFractionalGPUAllocationUsesNodeConditionOverride(t *testing.T) {
test_utils.InitTestingInfrastructure()
controller := NewController(t)
Expand Down Expand Up @@ -111,6 +181,35 @@ func TestFractionalGPUAllocationUsesNodeConditionOverride(t *testing.T) {
test_utils.MatchExpectedAndRealTasks(t, 0, topology, ssn)
}

func addReservationPodToNodeForTest(
node *node_info.NodeInfo, gpuGroup string, mode schedulingv1alpha2.GPUComputeSharingMode,
) {
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: types.UID("reservation-" + gpuGroup),
Name: commonconstants.GPUReservationPodPrefix + "-node0-test",
Namespace: commonconstants.DefaultResourceReservationName,
Labels: map[string]string{
commonconstants.GPUGroup: gpuGroup,
},
Annotations: map[string]string{
commonconstants.PodGroupAnnotationForPod: "reservation",
commonconstants.GpuComputeSharingMode: string(mode),
},
},
Spec: v1.PodSpec{
NodeName: node.Name,
Containers: []v1.Container{
{Name: "reservation"},
},
},
Status: v1.PodStatus{Phase: v1.PodRunning},
}
task := pod_info.NewTaskInfo(pod, resource_info.NewResourceVectorMap())
task.Status = pod_status.Running
node.PodInfos[task.UID] = task
}

func getFractionalGPUTestsMetadata() []integration_tests_utils.TestTopologyMetadata {
return []integration_tests_utils.TestTopologyMetadata{
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ func runSchedulerOneRound(testMetadata *TestTopologyMetadata, controller *Contro
case pod_status.Releasing:
if jobMetadata.DeleteJobInTest {
taskMetadata.NodeName = task.NodeName
taskMetadata.GPUGroups = task.GPUGroups
taskMetadata.GPUGroups = task.GPUGroupIDs()
taskMetadata.State = pod_status.Releasing
} else {
taskMetadata.NodeName = ""
Expand All @@ -124,12 +124,12 @@ func runSchedulerOneRound(testMetadata *TestTopologyMetadata, controller *Contro
case pod_status.Binding:
taskMetadata.State = pod_status.Running
taskMetadata.NodeName = task.NodeName
taskMetadata.GPUGroups = task.GPUGroups
taskMetadata.GPUGroups = task.GPUGroupIDs()

default:
taskMetadata.State = task.Status
taskMetadata.NodeName = task.NodeName
taskMetadata.GPUGroups = task.GPUGroups
taskMetadata.GPUGroups = task.GPUGroupIDs()
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,20 @@ package reclaim_test
import (
"testing"

. "go.uber.org/mock/gomock"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"

schedulingv1alpha2 "github.com/kai-scheduler/KAI-scheduler/pkg/apis/scheduling/v1alpha2"
commonconstants "github.com/kai-scheduler/KAI-scheduler/pkg/common/constants"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/actions/integration_tests/integration_tests_utils"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/actions/reclaim"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/api/common_info"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/api/node_info"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/api/pod_info"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/api/pod_status"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/api/resource_info"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/constants"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/test_utils"
"github.com/kai-scheduler/KAI-scheduler/pkg/scheduler/test_utils/jobs_fake"
Expand All @@ -19,6 +31,128 @@ func TestReclaimFractionalIntegrationTest(t *testing.T) {
integration_tests_utils.RunTests(t, getReclaimFractionalTestsMetadata())
}

func TestReclaimCanUseFullyEvictedFractionalGpuForDifferentComputeMode(t *testing.T) {
test_utils.InitTestingInfrastructure()
controller := NewController(t)
defer controller.Finish()

const gpuGroup = "time-slicing-group"
topology := test_utils.TestTopologyBasic{
Name: "reclaim all time-slicing fractions before using GPU for sm-sharing",
Jobs: []*jobs_fake.TestJobBasic{
{
Name: "running_job0",
RequiredGPUsPerTask: 0.5,
Priority: constants.PriorityTrainNumber,
QueueName: "queue0",
Tasks: []*tasks_fake.TestTaskBasic{
{
NodeName: "node0",
GPUGroups: []string{gpuGroup},
State: pod_status.Running,
},
{
NodeName: "node0",
GPUGroups: []string{gpuGroup},
State: pod_status.Running,
},
},
},
{
Name: "pending_job0",
RequiredGPUsPerTask: 0.5,
Priority: constants.PriorityTrainNumber,
QueueName: "queue1",
Tasks: []*tasks_fake.TestTaskBasic{
{
State: pod_status.Pending,
Annotations: map[string]string{
commonconstants.GpuComputeSharingMode: string(schedulingv1alpha2.GPUComputeSharingModeSMSharing),
},
},
},
},
},
Nodes: map[string]nodes_fake.TestNodeBasic{
"node0": {
GPUs: 1,
},
},
Queues: []test_utils.TestQueueBasic{
{
Name: "queue0",
DeservedGPUs: 0,
},
{
Name: "queue1",
DeservedGPUs: 1,
},
},
Mocks: &test_utils.TestMock{
CacheRequirements: &test_utils.CacheMocking{
NumberOfCacheEvictions: 2,
NumberOfPipelineActions: 1,
},
},
}

ssn := test_utils.BuildSession(topology, controller)
addReservationPodToNodeForReclaimTest(ssn.ClusterInfo.Nodes["node0"], gpuGroup, schedulingv1alpha2.GPUComputeSharingModeTimeSlicing)

reclaim.New().Execute(ssn)

runningTasks := ssn.ClusterInfo.PodGroupInfos["running_job0"].GetAllPodsMap()
for taskID, task := range runningTasks {
if task.Status != pod_status.Releasing {
t.Fatalf("expected time-slicing task %s to be releasing, got %s", taskID, task.Status)
}
}

pendingTask := ssn.ClusterInfo.PodGroupInfos["pending_job0"].GetAllPodsMap()[common_info.PodID("pending_job0-0")]
if pendingTask.Status != pod_status.Pipelined {
t.Fatalf("expected sm-sharing task to be pipelined, got status %s", pendingTask.Status)
}
if pendingTask.NodeName != "node0" {
t.Fatalf("expected sm-sharing task on node0, got %s", pendingTask.NodeName)
}
gpuGroups := pendingTask.GPUGroupIDs()
if len(gpuGroups) != 1 || gpuGroups[0] == gpuGroup {
t.Fatalf("expected sm-sharing task to use a new gpu group, got %v", gpuGroups)
}
if pendingTask.FractionalGpuGroups[0].ComputeSharingMode != schedulingv1alpha2.GPUComputeSharingModeSMSharing {
t.Fatalf("expected sm-sharing mode, got %s", pendingTask.FractionalGpuGroups[0].ComputeSharingMode)
}
}

func addReservationPodToNodeForReclaimTest(
node *node_info.NodeInfo, gpuGroup string, mode schedulingv1alpha2.GPUComputeSharingMode,
) {
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
UID: types.UID("reservation-" + gpuGroup),
Name: commonconstants.GPUReservationPodPrefix + "-node0-test",
Namespace: commonconstants.DefaultResourceReservationName,
Labels: map[string]string{
commonconstants.GPUGroup: gpuGroup,
},
Annotations: map[string]string{
commonconstants.PodGroupAnnotationForPod: "reservation",
commonconstants.GpuComputeSharingMode: string(mode),
},
},
Spec: v1.PodSpec{
NodeName: node.Name,
Containers: []v1.Container{
{Name: "reservation"},
},
},
Status: v1.PodStatus{Phase: v1.PodRunning},
}
task := pod_info.NewTaskInfo(pod, resource_info.NewResourceVectorMap())
task.Status = pod_status.Running
node.PodInfos[task.UID] = task
}

func getReclaimFractionalTestsMetadata() []integration_tests_utils.TestTopologyMetadata {
return []integration_tests_utils.TestTopologyMetadata{
{
Expand Down
Loading
Loading