Skip to content

Commit 888b060

Browse files
committed
Add GPU fallback support via unified compute field
compute now accepts either a string or array in the TOML: compute = "HOPPER_H100" compute = ["HOPPER_H100", "HOPPER_H200", "AMPERE_A100_80GB"] First element is primary, rest are fallbacks. CLI normalizes both forms and sends compute as array (or string) to the backend API. Changes: - config.go: Compute is interface{}, normalized into ComputePrimary + ComputeFallbacks - loader.go: normalizeCompute() handles string/array type switch - validator.go: validates primary and fallbacks against compute enum - deploy.go/run.go: use ComputePrimary instead of Compute - deploy_test.go: updated to use ComputePrimary Companion backend PR: CerebriumAI/dashboard-backend#3432 Made-with: Cursor
1 parent fba766e commit 888b060

6 files changed

Lines changed: 68 additions & 19 deletions

File tree

internal/ui/commands/deploy.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1395,16 +1395,16 @@ func (m *DeployView) renderDeploymentSummary() string {
13951395

13961396
// HARDWARE PARAMETERS
13971397
var hardwareItems []string
1398-
if m.conf.Config.Hardware.Compute != nil {
1399-
hardwareItems = append(hardwareItems, fmt.Sprintf("Compute: %s", *m.conf.Config.Hardware.Compute))
1398+
if m.conf.Config.Hardware.ComputePrimary != nil {
1399+
hardwareItems = append(hardwareItems, fmt.Sprintf("Compute: %s", *m.conf.Config.Hardware.ComputePrimary))
14001400
}
14011401
if m.conf.Config.Hardware.CPU != nil {
14021402
hardwareItems = append(hardwareItems, fmt.Sprintf("CPU: %.1f", *m.conf.Config.Hardware.CPU))
14031403
}
14041404
if m.conf.Config.Hardware.Memory != nil {
14051405
hardwareItems = append(hardwareItems, fmt.Sprintf("Memory: %.0f GB", *m.conf.Config.Hardware.Memory))
14061406
}
1407-
if m.conf.Config.Hardware.GPUCount != nil && m.conf.Config.Hardware.Compute != nil && *m.conf.Config.Hardware.Compute != "CPU" {
1407+
if m.conf.Config.Hardware.GPUCount != nil && m.conf.Config.Hardware.ComputePrimary != nil && *m.conf.Config.Hardware.ComputePrimary != "CPU" {
14081408
hardwareItems = append(hardwareItems, fmt.Sprintf("GPU Count: %d", *m.conf.Config.Hardware.GPUCount))
14091409
}
14101410
if m.conf.Config.Hardware.Region != nil {
@@ -1466,7 +1466,7 @@ func (m *DeployView) renderDeploymentSummary() string {
14661466
concurrency := fmt.Sprintf("Replica Concurrency: %d", *m.conf.Config.Scaling.ReplicaConcurrency)
14671467

14681468
// Add GPU warning if applicable
1469-
if m.conf.Config.Hardware.Compute != nil && *m.conf.Config.Hardware.Compute != "CPU" && *m.conf.Config.Scaling.ReplicaConcurrency > 1 {
1469+
if m.conf.Config.Hardware.ComputePrimary != nil && *m.conf.Config.Hardware.ComputePrimary != "CPU" && *m.conf.Config.Scaling.ReplicaConcurrency > 1 {
14701470
concurrency += " ⚠️ (Multiple concurrent requests on GPU)"
14711471
}
14721472
scalingItems = append(scalingItems, concurrency)
@@ -1546,16 +1546,16 @@ func (m *DeployView) renderDeploymentSummary() string {
15461546

15471547
// HARDWARE PARAMETERS
15481548
var hardwareRows []ui.TableRow
1549-
if m.conf.Config.Hardware.Compute != nil {
1550-
hardwareRows = append(hardwareRows, ui.TableRow{Label: "Compute:", Value: *m.conf.Config.Hardware.Compute})
1549+
if m.conf.Config.Hardware.ComputePrimary != nil {
1550+
hardwareRows = append(hardwareRows, ui.TableRow{Label: "Compute:", Value: *m.conf.Config.Hardware.ComputePrimary})
15511551
}
15521552
if m.conf.Config.Hardware.CPU != nil {
15531553
hardwareRows = append(hardwareRows, ui.TableRow{Label: "CPU:", Value: fmt.Sprintf("%.1f", *m.conf.Config.Hardware.CPU)})
15541554
}
15551555
if m.conf.Config.Hardware.Memory != nil {
15561556
hardwareRows = append(hardwareRows, ui.TableRow{Label: "Memory:", Value: fmt.Sprintf("%.0f GB", *m.conf.Config.Hardware.Memory)})
15571557
}
1558-
if m.conf.Config.Hardware.GPUCount != nil && m.conf.Config.Hardware.Compute != nil && *m.conf.Config.Hardware.Compute != "CPU" {
1558+
if m.conf.Config.Hardware.GPUCount != nil && m.conf.Config.Hardware.ComputePrimary != nil && *m.conf.Config.Hardware.ComputePrimary != "CPU" {
15591559
hardwareRows = append(hardwareRows, ui.TableRow{Label: "GPU Count:", Value: fmt.Sprintf("%d", *m.conf.Config.Hardware.GPUCount)})
15601560
}
15611561
if m.conf.Config.Hardware.Region != nil {
@@ -1636,7 +1636,7 @@ func (m *DeployView) renderDeploymentSummary() string {
16361636
concurrency := fmt.Sprintf("%d", *m.conf.Config.Scaling.ReplicaConcurrency)
16371637

16381638
// Add GPU warning if applicable
1639-
if m.conf.Config.Hardware.Compute != nil && *m.conf.Config.Hardware.Compute != "CPU" && *m.conf.Config.Scaling.ReplicaConcurrency > 1 {
1639+
if m.conf.Config.Hardware.ComputePrimary != nil && *m.conf.Config.Hardware.ComputePrimary != "CPU" && *m.conf.Config.Scaling.ReplicaConcurrency > 1 {
16401640
concurrency += " ⚠️ (Multiple concurrent requests on GPU)"
16411641
}
16421642
scalingRows = append(scalingRows, ui.TableRow{Label: "Replica Concurrency:", Value: concurrency})

internal/ui/commands/deploy_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ func TestDeployView(t *testing.T) {
147147
Exclude: []string{"__pycache__", "*.pyc"},
148148
},
149149
Hardware: projectconfig.HardwareConfig{
150-
Compute: &compute,
150+
ComputePrimary: &compute,
151151
CPU: &cpu,
152152
Memory: &memory,
153153
GPUCount: &gpuCount,

internal/ui/commands/run.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -617,8 +617,8 @@ func (m *RunView) prepareRun() tea.Msg {
617617
hardwareInfo := ""
618618
if m.conf.Config != nil {
619619
var info []string
620-
if m.conf.Config.Hardware.Compute != nil && *m.conf.Config.Hardware.Compute != "" {
621-
info = append(info, fmt.Sprintf("Compute: %s", *m.conf.Config.Hardware.Compute))
620+
if m.conf.Config.Hardware.ComputePrimary != nil && *m.conf.Config.Hardware.ComputePrimary != "" {
621+
info = append(info, fmt.Sprintf("Compute: %s", *m.conf.Config.Hardware.ComputePrimary))
622622
}
623623
if m.conf.Config.Hardware.GPUCount != nil && *m.conf.Config.Hardware.GPUCount > 0 {
624624
info = append(info, fmt.Sprintf("GPU: %d", *m.conf.Config.Hardware.GPUCount))
@@ -848,8 +848,8 @@ func (m *RunView) uploadRun() tea.Msg {
848848
// Build hardware config
849849
hardwareConfig := make(map[string]any)
850850
if m.conf.Config != nil {
851-
if m.conf.Config.Hardware.Compute != nil && *m.conf.Config.Hardware.Compute != "" {
852-
hardwareConfig["computeType"] = *m.conf.Config.Hardware.Compute
851+
if m.conf.Config.Hardware.ComputePrimary != nil && *m.conf.Config.Hardware.ComputePrimary != "" {
852+
hardwareConfig["computeType"] = *m.conf.Config.Hardware.ComputePrimary
853853
}
854854
if m.conf.Config.Hardware.GPUCount != nil && *m.conf.Config.Hardware.GPUCount > 0 {
855855
hardwareConfig["gpuCount"] = *m.conf.Config.Hardware.GPUCount

pkg/projectconfig/config.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,14 @@ type DeploymentConfig struct {
2828
type HardwareConfig struct {
2929
CPU *float64 `mapstructure:"cpu" toml:"cpu,omitempty"`
3030
Memory *float64 `mapstructure:"memory" toml:"memory,omitempty"`
31-
Compute *string `mapstructure:"compute" toml:"compute,omitempty"`
31+
Compute interface{} `mapstructure:"compute" toml:"compute,omitempty"`
3232
GPUCount *int `mapstructure:"gpu_count" toml:"gpu_count,omitempty"`
3333
Provider *string `mapstructure:"provider" toml:"provider,omitempty"`
3434
Region *string `mapstructure:"region" toml:"region,omitempty"`
35-
}
35+
36+
// Normalized from Compute after loading. Not set directly by TOML.
37+
ComputePrimary *string `mapstructure:"-" toml:"-"`
38+
ComputeFallbacks []string `mapstructure:"-" toml:"-"`}
3639

3740
// ScalingConfig represents the [cerebrium.scaling] section
3841
type ScalingConfig struct {
@@ -113,10 +116,10 @@ func (pc *ProjectConfig) ToPayload() map[string]any {
113116
if pc.Hardware.Memory != nil {
114117
payload["memory"] = *pc.Hardware.Memory
115118
}
116-
if pc.Hardware.Compute != nil {
117-
payload["compute"] = *pc.Hardware.Compute
119+
if pc.Hardware.ComputePrimary != nil {
120+
if len(pc.Hardware.ComputeFallbacks) > 0 { all := append([]string{*pc.Hardware.ComputePrimary}, pc.Hardware.ComputeFallbacks...); payload["compute"] = all } else { payload["compute"] = *pc.Hardware.ComputePrimary }
118121
}
119-
if pc.Hardware.GPUCount != nil && pc.Hardware.Compute != nil && *pc.Hardware.Compute != "CPU" {
122+
if pc.Hardware.GPUCount != nil && pc.Hardware.ComputePrimary != nil && *pc.Hardware.ComputePrimary != "CPU" {
120123
payload["gpuCount"] = *pc.Hardware.GPUCount
121124
}
122125
if pc.Hardware.Provider != nil {

pkg/projectconfig/loader.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,10 @@ func Load(configPath string) (*ProjectConfig, error) {
113113
}
114114
}
115115

116+
// Normalize compute field: accepts string or array
117+
if err := normalizeCompute(&config.Hardware); err != nil {
118+
return nil, err
119+
}
116120
// Validate compute_tier before applying defaults
117121
if config.Scaling.ComputeTier != nil {
118122
tier := *config.Scaling.ComputeTier
@@ -127,6 +131,35 @@ func Load(configPath string) (*ProjectConfig, error) {
127131
return &config, nil
128132
}
129133

134+
// normalizeCompute handles the compute field accepting either a string or an array.
135+
func normalizeCompute(hw *HardwareConfig) error {
136+
if hw.Compute == nil {
137+
return nil
138+
}
139+
switch v := hw.Compute.(type) {
140+
case string:
141+
hw.ComputePrimary = &v
142+
case []interface{}:
143+
if len(v) == 0 {
144+
return fmt.Errorf("compute array must not be empty")
145+
}
146+
first, ok := v[0].(string)
147+
if !ok {
148+
return fmt.Errorf("compute values must be strings")
149+
}
150+
hw.ComputePrimary = &first
151+
for _, item := range v[1:] {
152+
s, ok := item.(string)
153+
if !ok {
154+
return fmt.Errorf("compute values must be strings")
155+
}
156+
hw.ComputeFallbacks = append(hw.ComputeFallbacks, s)
157+
}
158+
default:
159+
return fmt.Errorf("compute must be a string or array of strings")
160+
}
161+
return nil
162+
}
130163
// applyDefaults sets default values for fields that weren't specified in the config
131164
func applyDefaults(config *ProjectConfig) {
132165
// Apply deployment defaults

pkg/projectconfig/validator.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,21 @@ func Validate(config *ProjectConfig) error {
3939
}
4040
}
4141

42+
validComputeTypes := map[string]bool{
43+
"ADA_L4": true, "ADA_L40": true, "AMPERE_A10": true, "AMPERE_A100_40GB": true,
44+
"AMPERE_A100_80GB": true, "HOPPER_H100": true, "HOPPER_H200": true,
45+
"BLACKWELL_B200": true, "CPU": true, "INF2": true, "TRN1": true, "TURING_T4": true,
46+
}
47+
if config.Hardware.ComputePrimary != nil && !validComputeTypes[*config.Hardware.ComputePrimary] {
48+
return fmt.Errorf("invalid compute value %q", *config.Hardware.ComputePrimary)
49+
}
50+
for _, fb := range config.Hardware.ComputeFallbacks {
51+
if !validComputeTypes[fb] {
52+
return fmt.Errorf("invalid compute fallback value %q", fb)
53+
}
54+
}
4255
// Default gpu_count to 1 if compute is set but gpu_count is not
43-
if config.Hardware.Compute != nil && *config.Hardware.Compute != "CPU" {
56+
if config.Hardware.ComputePrimary != nil && *config.Hardware.ComputePrimary != "CPU" {
4457
if config.Hardware.GPUCount == nil {
4558
defaultGPUCount := 1
4659
config.Hardware.GPUCount = &defaultGPUCount

0 commit comments

Comments
 (0)