Skip to content

Commit 8873786

Browse files
authored
feat(job): enable static sub-slicing for v6e and v5p(#5774)
1 parent 66693f6 commit 8873786

10 files changed

Lines changed: 520 additions & 132 deletions

pkg/config/hardware.go

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -436,11 +436,11 @@ func isValid3DDimension(x int) bool {
436436
}
437437

438438
// Validate3DTopology validates the 3D topology dimensions for a given machine type.
439-
// If superSlicingCheck is true, it skips the common 3D shapes check and strictly validates
440-
// that each dimension is a multiple of 4 and >= 4.
441-
func Validate3DTopology(topology string, machineType string, superSlicingCheck bool) error {
439+
// If dynamicSlicingCheck is true, it validates that the topology is either a valid subslice
440+
// (2x2x1, 2x2x2, 2x2x4, 2x4x4) or a valid superslice (dimensions are multiples of 4 and >= 4).
441+
func Validate3DTopology(topology string, machineType string, dynamicSlicingCheck bool) error {
442442

443-
if !superSlicingCheck && valid3DShapes[topology] {
443+
if !dynamicSlicingCheck && valid3DShapes[topology] {
444444
return nil
445445
}
446446

@@ -452,23 +452,13 @@ func Validate3DTopology(topology string, machineType string, superSlicingCheck b
452452
b, _ := strconv.Atoi(dims[1])
453453
c, _ := strconv.Atoi(dims[2])
454454

455-
if !isValid3DDimension(a) || !isValid3DDimension(b) || !isValid3DDimension(c) {
456-
return fmt.Errorf("topology %s dimensions must be >= 4 and multiples of 4 (unless it is a common base topology)", topology)
457-
}
458-
459-
maxCubes := 0
460-
found := false
461-
462-
for prefix, constraints := range tpu3DConstraintsMap {
463-
if strings.HasPrefix(machineType, prefix) {
464-
maxCubes = constraints.maxCubes
465-
found = true
466-
break
467-
}
455+
if err := validateDimensions(topology, a, b, c, dynamicSlicingCheck); err != nil {
456+
return err
468457
}
469458

470-
if !found {
471-
return fmt.Errorf("unknown 3D TPU family for machine type %s", machineType)
459+
maxCubes, err := getMaxCubes(machineType)
460+
if err != nil {
461+
return err
472462
}
473463

474464
cubes := (a / 4) * (b / 4) * (c / 4)
@@ -477,12 +467,38 @@ func Validate3DTopology(topology string, machineType string, superSlicingCheck b
477467
}
478468

479469
if !(a <= b && b <= c) {
480-
return fmt.Errorf("topology %s dimensions must be in non-decreasing order (A <= B <= C)", topology)
470+
if topology != "2x2x1" {
471+
return fmt.Errorf("topology %s dimensions must be in non-decreasing order (A <= B <= C)", topology)
472+
}
481473
}
482474

483475
return nil
484476
}
485477

478+
func validateDimensions(topology string, a, b, c int, dynamicSlicingCheck bool) error {
479+
if dynamicSlicingCheck {
480+
isSubSlice := (topology == "2x2x1" || topology == "2x2x2" || topology == "2x2x4" || topology == "2x4x4")
481+
isSuperSlice := (isValid3DDimension(a) && isValid3DDimension(b) && isValid3DDimension(c))
482+
if !isSubSlice && !isSuperSlice {
483+
return fmt.Errorf("topology %s is not valid for dynamic slicing. It must be a valid subslice (2x2x1, 2x2x2, 2x2x4, 2x4x4) or superslice (dimensions >= 4 and multiples of 4)", topology)
484+
}
485+
} else {
486+
if !isValid3DDimension(a) || !isValid3DDimension(b) || !isValid3DDimension(c) {
487+
return fmt.Errorf("topology %s dimensions must be >= 4 and multiples of 4 (unless it is a common base topology)", topology)
488+
}
489+
}
490+
return nil
491+
}
492+
493+
func getMaxCubes(machineType string) (int, error) {
494+
for prefix, constraints := range tpu3DConstraintsMap {
495+
if strings.HasPrefix(machineType, prefix) {
496+
return constraints.maxCubes, nil
497+
}
498+
}
499+
return 0, fmt.Errorf("unknown 3D TPU family for machine type %s", machineType)
500+
}
501+
486502
// CheckTopologyContainment returns true if the requested topology fits within the container topology.
487503
func CheckTopologyContainment(requested, container string, accelType string) (bool, error) {
488504
reqDims := strings.Split(requested, "x")
@@ -506,3 +522,19 @@ func CheckTopologyContainment(requested, container string, accelType string) (bo
506522

507523
return true, nil
508524
}
525+
526+
// Is3DTorusTPU returns true if the machine type belongs to a 3D Torus TPU family (v4 or v5p)
527+
// where static sub-slicing coordinate labeling is unsupported by GKE.
528+
func Is3DTorusTPU(machineType string) bool {
529+
lower := strings.ToLower(machineType)
530+
// Filter out tpu7x (v7) which uses its own dynamic coordinates/partition scheme.
531+
if strings.Contains(lower, "tpu7") {
532+
return false
533+
}
534+
for _, family := range []string{"v4", "v5p", "ct4p", "ct5p"} {
535+
if strings.Contains(lower, family) {
536+
return true
537+
}
538+
}
539+
return false
540+
}

pkg/config/hardware_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,3 +575,61 @@ func TestCheckTopologyContainment(t *testing.T) {
575575
})
576576
}
577577
}
578+
579+
func TestIs3DTorusTPU(t *testing.T) {
580+
tests := []struct {
581+
name string
582+
machineType string
583+
wantResult bool
584+
}{
585+
{
586+
name: "v4 chip name (Torus)",
587+
machineType: "ct4p-hightpu-4t",
588+
wantResult: true,
589+
},
590+
{
591+
name: "v5p machine name (Torus)",
592+
machineType: "ct5p-hightpu-4t",
593+
wantResult: true,
594+
},
595+
{
596+
name: "v5e machine name (2D Mesh)",
597+
machineType: "ct5l-hightpu-4t",
598+
wantResult: false,
599+
},
600+
{
601+
name: "v6e machine name (2D Mesh)",
602+
machineType: "ct6e-standard-8t",
603+
wantResult: false,
604+
},
605+
{
606+
name: "tpu7x machine name (Exclude)",
607+
machineType: "tpu7x-standard-16t",
608+
wantResult: false,
609+
},
610+
{
611+
name: "Shorthand v4-8",
612+
machineType: "v4-8",
613+
wantResult: true,
614+
},
615+
{
616+
name: "Shorthand v5p-8",
617+
machineType: "v5p-8",
618+
wantResult: true,
619+
},
620+
{
621+
name: "Non-TPU machine type",
622+
machineType: "a2-highgpu-1g",
623+
wantResult: false,
624+
},
625+
}
626+
627+
for _, tt := range tests {
628+
t.Run(tt.name, func(t *testing.T) {
629+
got := Is3DTorusTPU(tt.machineType)
630+
if got != tt.wantResult {
631+
t.Errorf("Is3DTorusTPU(%q) = %v, want %v", tt.machineType, got, tt.wantResult)
632+
}
633+
})
634+
}
635+
}

pkg/orchestrator/gke/gke_job_orchestrator.go

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ func NewGKEOrchestrator() *GKEOrchestrator {
6060
machineCapCache: make(map[string]MachineTypeCap),
6161
topologyCache: make(map[string]string),
6262
dynamicSlicingCache: make(map[string]bool),
63+
staticSlicingCache: make(map[string]bool),
6364
}
6465
}
6566

@@ -102,7 +103,7 @@ func (g *GKEOrchestrator) SubmitJob(job orchestrator.JobDefinition) error {
102103
return err
103104
}
104105

105-
profile, isDynamicSlicing, err := g.resolveHardwareRequirements(&job)
106+
profile, isDynamicSlicing, isStaticSlicing, err := g.resolveHardwareRequirements(&job)
106107
if err != nil {
107108
return err
108109
}
@@ -115,7 +116,7 @@ func (g *GKEOrchestrator) SubmitJob(job orchestrator.JobDefinition) error {
115116
return err
116117
}
117118

118-
if err := g.generateAndSubmitManifests(job, fullImageName, profile, isDynamicSlicing); err != nil {
119+
if err := g.generateAndSubmitManifests(job, fullImageName, profile, isDynamicSlicing, isStaticSlicing); err != nil {
119120
return err
120121
}
121122

@@ -250,16 +251,16 @@ func (g *GKEOrchestrator) GetJobLogs(name string, opts orchestrator.LogsOptions)
250251
return res.Stdout, nil
251252
}
252253

253-
func (g *GKEOrchestrator) generateAndSubmitManifests(job orchestrator.JobDefinition, fullImageName string, profile JobProfile, isDynamicSlicing bool) error {
254+
func (g *GKEOrchestrator) generateAndSubmitManifests(job orchestrator.JobDefinition, fullImageName string, profile JobProfile, isDynamicSlicing bool, isStaticSlicing bool) error {
254255
if job.IsPathwaysJob {
255-
manifestContent, err := g.GeneratePathwaysManifest(job, fullImageName, profile, isDynamicSlicing)
256+
manifestContent, err := g.GeneratePathwaysManifest(job, fullImageName, profile, isDynamicSlicing, isStaticSlicing)
256257
if err != nil {
257258
return err
258259
}
259260
return g.ApplyManifest(manifestContent, job.DryRunManifest, job.WorkloadName)
260261
}
261262

262-
manifestOpts, err := g.PrepareManifestOptions(job, fullImageName, profile, isDynamicSlicing)
263+
manifestOpts, err := g.PrepareManifestOptions(job, fullImageName, profile, isDynamicSlicing, isStaticSlicing)
263264
if err != nil {
264265
return err
265266
}
@@ -302,7 +303,7 @@ func (g *GKEOrchestrator) validateJobConflicts(workloadName string, clusterName
302303
return nil
303304
}
304305

305-
func (g *GKEOrchestrator) GeneratePathwaysManifest(job orchestrator.JobDefinition, fullImageName string, profile JobProfile, isDynamicSlicing bool) (string, error) {
306+
func (g *GKEOrchestrator) GeneratePathwaysManifest(job orchestrator.JobDefinition, fullImageName string, profile JobProfile, isDynamicSlicing bool, isStaticSlicing bool) (string, error) {
306307
// Set default values for Pathways-specific fields if not provided
307308
if job.Pathways.ProxyServerImage == "" {
308309
job.Pathways.ProxyServerImage = defaultPathwaysProxyImage
@@ -320,7 +321,7 @@ func (g *GKEOrchestrator) GeneratePathwaysManifest(job orchestrator.JobDefinitio
320321
return "", fmt.Errorf("failed to parse pathways jobset template: %w", err)
321322
}
322323

323-
opts, err := g.PrepareManifestOptions(job, fullImageName, profile, isDynamicSlicing)
324+
opts, err := g.PrepareManifestOptions(job, fullImageName, profile, isDynamicSlicing, isStaticSlicing)
324325
if err != nil {
325326
return "", err
326327
}
@@ -1797,12 +1798,12 @@ func (g *GKEOrchestrator) addAcceleratorLabel(nodeSelector map[string]string, ac
17971798
}
17981799
}
17991800

1800-
func (g *GKEOrchestrator) addTopologyLabel(nodeSelector map[string]string, schedOpts SchedulingOptions, isGPU bool, isCPUMachine bool, isDynamicSlicing bool) error {
1801+
func (g *GKEOrchestrator) addTopologyLabel(nodeSelector map[string]string, schedOpts SchedulingOptions, isGPU bool, isCPUMachine bool) error {
18011802
if schedOpts.Topology != "" {
18021803
if isGPU || isCPUMachine {
18031804
return fmt.Errorf("topology is not allowed for GPU and CPU jobs")
18041805
}
1805-
if !isDynamicSlicing {
1806+
if !schedOpts.IsDynamicSlicing && !schedOpts.IsStaticSlicing {
18061807
_, hasFallback := schedOpts.NodeAffinityLabels[tpuTopologyLabel]
18071808
if !hasFallback {
18081809
nodeSelector[tpuTopologyLabel] = schedOpts.Topology
@@ -1812,7 +1813,7 @@ func (g *GKEOrchestrator) addTopologyLabel(nodeSelector map[string]string, sched
18121813
return nil
18131814
}
18141815

1815-
func (g *GKEOrchestrator) buildNodeSelector(schedOpts SchedulingOptions, job orchestrator.JobDefinition, isDynamicSlicing bool, isCPUMachine bool) (string, error) {
1816+
func (g *GKEOrchestrator) buildNodeSelector(schedOpts SchedulingOptions, job orchestrator.JobDefinition, isCPUMachine bool) (string, error) {
18161817
nodeSelector := GetNodeSelector(schedOpts)
18171818
if nodeSelector == nil {
18181819
nodeSelector = make(map[string]string)
@@ -1831,7 +1832,7 @@ func (g *GKEOrchestrator) buildNodeSelector(schedOpts SchedulingOptions, job orc
18311832

18321833
g.addAcceleratorLabel(nodeSelector, accelLabel, isCPUMachine, job.MachineType)
18331834

1834-
if err := g.addTopologyLabel(nodeSelector, schedOpts, isGPU, isCPUMachine, isDynamicSlicing); err != nil {
1835+
if err := g.addTopologyLabel(nodeSelector, schedOpts, isGPU, isCPUMachine); err != nil {
18351836
return "", err
18361837
}
18371838

@@ -1860,10 +1861,10 @@ func (g *GKEOrchestrator) buildAffinity(schedOpts SchedulingOptions) (string, er
18601861
return "", nil
18611862
}
18621863

1863-
func (g *GKEOrchestrator) buildTopologyAnnotation(topology string, numSlices int, isDynamicSlicing bool) string {
1864+
func (g *GKEOrchestrator) buildTopologyAnnotation(topology string, machineType string, numSlices int, nodesPerSlice int, isSubSlicing bool) string {
18641865
var topologyAnnotation map[string]string
1865-
if isDynamicSlicing {
1866-
topologyAnnotation = GetTopologyAnnotation(topology, numSlices)
1866+
if isSubSlicing {
1867+
topologyAnnotation = GetTopologyAnnotation(topology, machineType, numSlices, nodesPerSlice)
18671868
} else if topology != "" {
18681869
topologyAnnotation = map[string]string{
18691870
"cloud.google.com/gke-tpu-slice-topology": topology,

0 commit comments

Comments
 (0)