Skip to content

Commit a86c4dc

Browse files
author
照微
committed
gemini report
1 parent c8b7d18 commit a86c4dc

12 files changed

Lines changed: 278 additions & 74 deletions

File tree

cmd/llmctl/config/config.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Examples:
4242
llmctl config view
4343
4444
# Add a new storage configuration
45-
llmctl config add-storage my-pvc --type pvc --config claimName=model-pvc
45+
llmctl config add-storage my-pvc --type pvc --config pvcName=model-pvc
4646
4747
# Add a new model source
4848
llmctl config add-source huggingface --type huggingface --config token=hf_xxx

cmd/llmctl/svc/run.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ func resolveEngine(engineType string, cfg *cliconfig.Config) (*cliconfig.EngineC
6666
}
6767

6868
// 3. Use default (empty config) - plugin will use its built-in defaults
69-
fmt.Printf("INFO: Using default configuration for engine '%s'. Run 'llmctl config add-engine %s' to customize.\n", engineType, engineType)
69+
fmt.Printf("INFO: Using default configuration for engine '%s'. Run 'llmctl config set-engine %s' to customize.\n", engineType, engineType)
7070
return &cliconfig.EngineConfig{
7171
Type: engineType,
7272
Config: map[string]interface{}{},

pkg/autobenchmark/config/parse.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ func validateScenario(cfg *AutoBenchmarkConfig) []string {
144144
} else if err := ValidateWorkload(cfg.Scenario.Workload); err != nil {
145145
errs = append(errs, fmt.Sprintf("scenario.workload: %v", err))
146146
}
147-
if cfg.Scenario.MaxRequests <= 0 {
148-
errs = append(errs, "scenario.maxRequests: must be positive")
147+
if cfg.Scenario.MaxRequests < 0 {
148+
errs = append(errs, "scenario.maxRequests: must not be negative (0 means use tool default)")
149149
}
150150
if cfg.Scenario.Concurrency <= 0 {
151151
errs = append(errs, "scenario.concurrency: must be positive")

pkg/autobenchmark/controller/controller.go

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -195,11 +195,10 @@ func (ctrl *Controller) Run(ctx context.Context) error {
195195
// template should be resumable on the next run.
196196
if ctx.Err() != nil {
197197
logger.Info("Experiment timeout during template, not marking completed", "template", tmplRef.Name)
198-
ts.BestTrial = SelectBest(ts.Trials)
199198
} else {
200199
ts.Completed = true
201-
ts.BestTrial = SelectBest(ts.Trials)
202200
}
201+
ts.BestTrial = SelectBest(ts.Trials)
203202

204203
// Update global best
205204
if ts.BestTrial != nil {
@@ -357,11 +356,8 @@ func (ctrl *Controller) executeTrial(
357356
return
358357
}
359358
cleanupCtx, cleanupCancel := context.WithTimeout(context.Background(), 60*time.Second)
360-
err := wait.ExponentialBackoffWithContext(cleanupCtx, wait.Backoff{
361-
Duration: 5 * time.Second,
362-
Factor: 1,
363-
Steps: 3,
364-
}, func(ctx context.Context) (bool, error) {
359+
defer cleanupCancel()
360+
err := wait.PollUntilContextTimeout(cleanupCtx, 5*time.Second, 20*time.Second, true, func(ctx context.Context) (bool, error) {
365361
if delErr := ctrl.manager.Delete(ctx, trialName); delErr != nil {
366362
if apierrors.IsNotFound(delErr) {
367363
return true, nil
@@ -374,7 +370,6 @@ func (ctrl *Controller) executeTrial(
374370
if err != nil {
375371
logger.Error(err, "Failed to cleanup trial RBG after retries", "rbgName", trialName)
376372
}
377-
cleanupCancel()
378373
}()
379374

380375
// Create trial RBG
@@ -400,7 +395,7 @@ func (ctrl *Controller) executeTrial(
400395
ctrl.collectFailureLogs(logger, trialName, resultDir, &result)
401396
return result
402397
}
403-
modelName := extractServedModelName(baseRBG, ctrl.cfg.Backend)
398+
modelName := extractServedModelName(baseRBG)
404399

405400
// Result directory: {reportDir}/{scenario}/{templateName}/trial-{idx}
406401
scenario := ctrl.cfg.Scenario

pkg/autobenchmark/controller/early_termination.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func CheckEarlyTermination(trials []abtypes.TrialResult, spec config.EarlyTermin
7474
consecutive := 0
7575
for i := len(trials) - 1; i >= 0; i-- {
7676
if IsExecutionError(&trials[i]) {
77-
break
77+
continue
7878
}
7979
if !trials[i].IsSLAFeasible() {
8080
consecutive++

pkg/autobenchmark/controller/endpoint.go

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ func (ctrl *Controller) resolveEndpoint(trialRBG *v1alpha2.RoleBasedGroup) strin
4747
}
4848
return lifecycle.GetServiceEndpoint(trialRBG, &role, ctrl.namespace, port)
4949
}
50-
// Last resort fallback: assume a default worker role at port 8000.
51-
return lifecycle.GetServiceEndpoint(trialRBG, &v1alpha2.RoleSpec{Name: "worker"}, ctrl.namespace, 8000)
50+
// Last resort fallback: assume a default worker role at the engine's default port.
51+
return lifecycle.GetServiceEndpoint(trialRBG, &v1alpha2.RoleSpec{Name: "worker"}, ctrl.namespace, defaultEnginePort(ctrl.cfg.Backend))
5252
}
5353

5454
// resolveRolePort extracts the inference port for a role.
@@ -94,20 +94,20 @@ func defaultEnginePort(backend string) int {
9494
}
9595
}
9696

97-
// extractServedModelName reads --served-model-name from the base template's container args.
97+
// extractServedModelName reads the served-model-name flag from the base template's container args.
9898
// Falls back to the RBG metadata name if the flag is not found.
99-
func extractServedModelName(rbg *v1alpha2.RoleBasedGroup, backend string) string {
100-
flag := "--served-model-name"
101-
if backend == "vllm" {
102-
flag = "--served-model-name"
103-
}
99+
func extractServedModelName(rbg *v1alpha2.RoleBasedGroup) string {
100+
// Both vllm and sglang use --served-model-name.
101+
const flag = "--served-model-name"
104102
for _, role := range rbg.Spec.Roles {
105103
podSpec := getRolePodSpec(&role)
106104
if podSpec == nil {
107105
continue
108106
}
109107
for _, c := range podSpec.Containers {
110-
allArgs := append(c.Command, c.Args...)
108+
allArgs := make([]string, 0, len(c.Command)+len(c.Args))
109+
allArgs = append(allArgs, c.Command...)
110+
allArgs = append(allArgs, c.Args...)
111111
for i, arg := range allArgs {
112112
if arg == flag && i+1 < len(allArgs) {
113113
return allArgs[i+1]
@@ -147,6 +147,9 @@ func (ctrl *Controller) waitRBGFullyReady(
147147
rbgReady := false
148148
httpClient := &http.Client{Timeout: 5 * time.Second}
149149

150+
// Resolve endpoint once — trialRBG is immutable for the duration of the wait.
151+
endpoint = ctrl.resolveEndpoint(trialRBG)
152+
150153
err = wait.PollUntilContextTimeout(ctx, 10*time.Second, timeout, true, func(ctx context.Context) (bool, error) {
151154
if !rbgReady {
152155
rbg, err := ctrl.manager.Get(ctx, trialName)
@@ -161,9 +164,12 @@ func (ctrl *Controller) waitRBGFullyReady(
161164
rbgReady = true
162165
}
163166

164-
endpoint = ctrl.resolveEndpoint(trialRBG)
165167
healthURL := endpoint + "/health"
166-
resp, err := httpClient.Get(healthURL)
168+
req, reqErr := http.NewRequestWithContext(ctx, http.MethodGet, healthURL, nil)
169+
if reqErr != nil {
170+
return false, nil
171+
}
172+
resp, err := httpClient.Do(req)
167173
if err != nil {
168174
logger.V(2).Info("Endpoint not ready yet", "error", err.Error())
169175
return false, nil // retry
@@ -200,7 +206,7 @@ func sanitizeLabelValue(name string) string {
200206
// Replace invalid characters with '-'
201207
invalidChars := regexp.MustCompile(`[^a-zA-Z0-9._-]`)
202208
name = invalidChars.ReplaceAllString(name, "-")
203-
name = strings.Trim(name, "-")
209+
name = strings.Trim(name, "-._")
204210
if name == "" {
205211
name = "default"
206212
}

pkg/autobenchmark/controller/endpoint_test.go

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ func TestSanitizeLabelValue(t *testing.T) {
6565
{"special chars replaced", "exp name@v1!", "exp-name-v1"},
6666
{"empty after sanitize", "@@@@", "default"},
6767
{"leading and trailing dashes trimmed", "-my-exp-", "my-exp"},
68+
{"trailing dot trimmed", "my-exp.", "my-exp"},
69+
{"trailing underscore trimmed", "my-exp_", "my-exp"},
6870
{"all dashes becomes default", "---", "default"},
6971
}
7072

@@ -198,10 +200,9 @@ func TestIsRBGReady(t *testing.T) {
198200

199201
func TestExtractServedModelName(t *testing.T) {
200202
tests := []struct {
201-
name string
202-
rbg *v1alpha2.RoleBasedGroup
203-
backend string
204-
want string
203+
name string
204+
rbg *v1alpha2.RoleBasedGroup
205+
want string
205206
}{
206207
{
207208
name: "flag in args",
@@ -217,8 +218,7 @@ func TestExtractServedModelName(t *testing.T) {
217218
},
218219
},
219220
},
220-
backend: "vllm",
221-
want: "llama-3",
221+
want: "llama-3",
222222
},
223223
{
224224
name: "flag in command",
@@ -237,8 +237,7 @@ func TestExtractServedModelName(t *testing.T) {
237237
},
238238
},
239239
},
240-
backend: "vllm",
241-
want: "gpt-custom",
240+
want: "gpt-custom",
242241
},
243242
{
244243
name: "flag not found - fallback to rbg name",
@@ -254,22 +253,20 @@ func TestExtractServedModelName(t *testing.T) {
254253
},
255254
},
256255
},
257-
backend: "vllm",
258-
want: "my-rbg",
256+
want: "my-rbg",
259257
},
260258
{
261259
name: "no roles",
262260
rbg: &v1alpha2.RoleBasedGroup{
263261
ObjectMeta: metav1.ObjectMeta{Name: "empty-rbg"},
264262
},
265-
backend: "sglang",
266-
want: "empty-rbg",
263+
want: "empty-rbg",
267264
},
268265
}
269266

270267
for _, tt := range tests {
271268
t.Run(tt.name, func(t *testing.T) {
272-
assert.Equal(t, tt.want, extractServedModelName(tt.rbg, tt.backend))
269+
assert.Equal(t, tt.want, extractServedModelName(tt.rbg))
273270
})
274271
}
275272
}

pkg/autobenchmark/controller/failure_logs.go

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,30 @@ func (ctrl *Controller) snapshotRestartCounts(logger logr.Logger, trialName stri
6666
return snap
6767
}
6868

69+
// identifyFailedPods filters pods that actually crashed during the benchmark:
70+
// - Pod in Failed phase → indicates pod crash
71+
// - Any container RestartCount increased vs. preRunRestarts snapshot → OOM or similar crash
72+
// - Neither → benchmark tool's own problem, not a pod crash
73+
func identifyFailedPods(pods []corev1.Pod, preRunRestarts podRestartSnapshot) []*corev1.Pod {
74+
var failedPods []*corev1.Pod
75+
for i := range pods {
76+
pod := &pods[i]
77+
if pod.Status.Phase == corev1.PodFailed {
78+
failedPods = append(failedPods, pod)
79+
continue
80+
}
81+
if preRunRestarts != nil {
82+
for _, cs := range append(pod.Status.InitContainerStatuses, pod.Status.ContainerStatuses...) {
83+
if cs.RestartCount > preRunRestarts[pod.Name+"/"+cs.Name] {
84+
failedPods = append(failedPods, pod)
85+
break
86+
}
87+
}
88+
}
89+
}
90+
return failedPods
91+
}
92+
6993
// collectBenchmarkFailureLogs checks pod state after eval.Run failure and
7094
// collects logs only when pods actually crashed during the benchmark:
7195
// - Pod in Failed phase → collect current logs (before RBG controller deletes it)
@@ -84,27 +108,9 @@ func (ctrl *Controller) collectBenchmarkFailureLogs(logger logr.Logger, trialNam
84108
return
85109
}
86110

87-
var needLogs bool
88-
for i := range podList.Items {
89-
pod := &podList.Items[i]
90-
if pod.Status.Phase == corev1.PodFailed {
91-
needLogs = true
92-
break
93-
}
94-
if preRunRestarts != nil {
95-
for _, cs := range append(pod.Status.InitContainerStatuses, pod.Status.ContainerStatuses...) {
96-
if cs.RestartCount > preRunRestarts[pod.Name+"/"+cs.Name] {
97-
needLogs = true
98-
break
99-
}
100-
}
101-
}
102-
if needLogs {
103-
break
104-
}
105-
}
111+
failedPods := identifyFailedPods(podList.Items, preRunRestarts)
106112

107-
if !needLogs {
113+
if len(failedPods) == 0 {
108114
return
109115
}
110116

@@ -114,13 +120,12 @@ func (ctrl *Controller) collectBenchmarkFailureLogs(logger logr.Logger, trialNam
114120
return
115121
}
116122

117-
for i := range podList.Items {
118-
pod := &podList.Items[i]
123+
for _, pod := range failedPods {
119124
logPath := filepath.Join(logDir, pod.Name+".log")
120125
ctrl.writePodLogs(ctx, logPath, pod)
121126
}
122127

123-
logger.Info("Collected benchmark failure logs", "logDir", logDir)
128+
logger.Info("Collected benchmark failure logs", "logDir", logDir, "pods", len(failedPods))
124129
}
125130

126131
// collectFailureLogs fetches failure context from all pods belonging to the
@@ -172,8 +177,12 @@ func (ctrl *Controller) collectFailureLogs(logger logr.Logger, trialName string,
172177
}
173178

174179
if len(pendingSummaries) > 0 {
175-
result.Error = fmt.Sprintf("%s; pending pods: %s",
176-
result.Error, strings.Join(pendingSummaries, "; "))
180+
pendingMsg := "pending pods: " + strings.Join(pendingSummaries, "; ")
181+
if result.Error != "" {
182+
result.Error += "; " + pendingMsg
183+
} else {
184+
result.Error = pendingMsg
185+
}
177186
}
178187

179188
if logFileCount > 0 {
@@ -254,8 +263,10 @@ func (ctrl *Controller) fetchContainerLogs(ctx context.Context, sb *strings.Buil
254263
fmt.Fprintf(sb, "(failed to get logs: %v)\n\n", err)
255264
return
256265
}
266+
defer func() {
267+
_ = stream.Close()
268+
}()
257269
logBytes, err := io.ReadAll(stream)
258-
_ = stream.Close()
259270
if err != nil {
260271
fmt.Fprintf(sb, "(failed to read logs: %v)\n\n", err)
261272
return

0 commit comments

Comments
 (0)