Skip to content

Commit a4e5588

Browse files
committed
fix: skip TPOT recording for non-streaming requests
A non-streaming response arrives as a single body chunk, so the first-token and completion timestamps coincide and RecordRequestTPOT's validity check logged an error-level 'Request latency values are invalid for TPOT calculation' line on every non-streaming request. TPOT has no meaning without inter-token timing, so thread the existing modelServerStreaming flag through (mirroring RecordRequestTTFT) and skip recording silently for non-streaming responses. Fixes #2166
1 parent 3a31761 commit a4e5588

3 files changed

Lines changed: 24 additions & 9 deletions

File tree

pkg/epp/handlers/response.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ func (s *StreamingServer) HandleResponseBody(ctx context.Context, reqCtx *Reques
9090
metrics.RecordRequestLatencies(ctx, reqCtx.IncomingModelName, reqCtx.TargetModelName, fairnessID, priority, reqCtx.RequestReceivedTimestamp, reqCtx.ResponseCompleteTimestamp)
9191
metrics.RecordResponseSizes(reqCtx.IncomingModelName, reqCtx.TargetModelName, fairnessID, priority, reqCtx.ResponseSize)
9292
metrics.RecordRequestTTFT(ctx, reqCtx.IncomingModelName, reqCtx.TargetModelName, fairnessID, priority, reqCtx.modelServerStreaming, reqCtx.RequestReceivedTimestamp, reqCtx.FirstTokenTimestamp)
93-
metrics.RecordRequestTPOT(ctx, reqCtx.IncomingModelName, reqCtx.TargetModelName, fairnessID, priority, reqCtx.RequestReceivedTimestamp, reqCtx.FirstTokenTimestamp, reqCtx.ResponseCompleteTimestamp, reqCtx.Usage.CompletionTokens)
93+
metrics.RecordRequestTPOT(ctx, reqCtx.IncomingModelName, reqCtx.TargetModelName, fairnessID, priority, reqCtx.modelServerStreaming, reqCtx.RequestReceivedTimestamp, reqCtx.FirstTokenTimestamp, reqCtx.ResponseCompleteTimestamp, reqCtx.Usage.CompletionTokens)
9494
}
9595
return s.director.HandleResponseBody(ctx, reqCtx, endOfStream)
9696
}

pkg/epp/metrics/metrics.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -677,8 +677,16 @@ func RecordRequestTTFT(ctx context.Context, modelName, targetModelName, fairness
677677
return true
678678
}
679679

680-
// RecordRequestTPOT records the average time per output token.
681-
func RecordRequestTPOT(ctx context.Context, modelName, targetModelName, fairnessID, priority string, received time.Time, firstToken time.Time, complete time.Time, outputTokenCount int) bool {
680+
// RecordRequestTPOT records the average time per output token. TPOT is only
681+
// derivable for streaming responses: a non-streaming response arrives as a
682+
// single body chunk, so the first-token and completion timestamps coincide and
683+
// no inter-token timing exists. Such requests are skipped silently instead of
684+
// being logged as invalid (they would otherwise emit an error-level line per
685+
// request on non-streaming workloads).
686+
func RecordRequestTPOT(ctx context.Context, modelName, targetModelName, fairnessID, priority string, streaming bool, received time.Time, firstToken time.Time, complete time.Time, outputTokenCount int) bool {
687+
if !streaming {
688+
return false
689+
}
682690
modelName, targetModelName = boundModels(modelName, targetModelName)
683691
fairnessID = boundFairnessID(fairnessID)
684692
if firstToken.IsZero() || outputTokenCount <= 1 {

pkg/epp/metrics/metrics_test.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1442,32 +1442,39 @@ func TestRecordRequestTPOT(t *testing.T) {
14421442
received := timeBaseline
14431443
firstToken := timeBaseline.Add(500 * time.Millisecond)
14441444
complete := timeBaseline.Add(2000 * time.Millisecond)
1445-
require.True(t, RecordRequestTPOT(ctx, "m10", "t10", "tenant-a", "3", received, firstToken, complete, 11))
1445+
require.True(t, RecordRequestTPOT(ctx, "m10", "t10", "tenant-a", "3", true, received, firstToken, complete, 11))
14461446

14471447
h, err := getHistogramVecLabelValues(t, llmdRequestTPOT, "m10", "t10", "tenant-a", "3")
14481448
require.NoError(t, err)
14491449
require.Equal(t, uint64(1), h.GetSampleCount())
14501450
require.InDelta(t, 0.15, h.GetSampleSum(), 0.001)
14511451
})
14521452

1453+
t.Run("non-streaming skipped without error log", func(t *testing.T) {
1454+
received := timeBaseline
1455+
firstToken := timeBaseline.Add(500 * time.Millisecond)
1456+
// Non-streaming: the whole body arrives at once, so complete == firstToken.
1457+
require.False(t, RecordRequestTPOT(ctx, "m10", "t10", "tenant-a", "3", false, received, firstToken, firstToken, 11))
1458+
})
1459+
14531460
t.Run("single token skipped", func(t *testing.T) {
1454-
require.False(t, RecordRequestTPOT(ctx, "m10", "t10", "tenant-a", "3", timeBaseline, timeBaseline.Add(100*time.Millisecond), timeBaseline.Add(200*time.Millisecond), 1))
1461+
require.False(t, RecordRequestTPOT(ctx, "m10", "t10", "tenant-a", "3", true, timeBaseline, timeBaseline.Add(100*time.Millisecond), timeBaseline.Add(200*time.Millisecond), 1))
14551462
})
14561463

14571464
t.Run("zero tokens skipped", func(t *testing.T) {
1458-
require.False(t, RecordRequestTPOT(ctx, "m10", "t10", "tenant-a", "3", timeBaseline, timeBaseline.Add(100*time.Millisecond), timeBaseline.Add(200*time.Millisecond), 0))
1465+
require.False(t, RecordRequestTPOT(ctx, "m10", "t10", "tenant-a", "3", true, timeBaseline, timeBaseline.Add(100*time.Millisecond), timeBaseline.Add(200*time.Millisecond), 0))
14591466
})
14601467

14611468
t.Run("zero first token timestamp", func(t *testing.T) {
1462-
require.False(t, RecordRequestTPOT(ctx, "m10", "t10", "tenant-a", "3", timeBaseline, time.Time{}, timeBaseline.Add(200*time.Millisecond), 10))
1469+
require.False(t, RecordRequestTPOT(ctx, "m10", "t10", "tenant-a", "3", true, timeBaseline, time.Time{}, timeBaseline.Add(200*time.Millisecond), 10))
14631470
})
14641471

14651472
t.Run("first token before received", func(t *testing.T) {
1466-
require.False(t, RecordRequestTPOT(ctx, "m10", "t10", "tenant-a", "3", timeBaseline.Add(100*time.Millisecond), timeBaseline, timeBaseline.Add(200*time.Millisecond), 10))
1473+
require.False(t, RecordRequestTPOT(ctx, "m10", "t10", "tenant-a", "3", true, timeBaseline.Add(100*time.Millisecond), timeBaseline, timeBaseline.Add(200*time.Millisecond), 10))
14671474
})
14681475

14691476
t.Run("complete before first token", func(t *testing.T) {
1470-
require.False(t, RecordRequestTPOT(ctx, "m10", "t10", "tenant-a", "3", timeBaseline, timeBaseline.Add(200*time.Millisecond), timeBaseline.Add(100*time.Millisecond), 10))
1477+
require.False(t, RecordRequestTPOT(ctx, "m10", "t10", "tenant-a", "3", true, timeBaseline, timeBaseline.Add(200*time.Millisecond), timeBaseline.Add(100*time.Millisecond), 10))
14711478
})
14721479
}
14731480

0 commit comments

Comments
 (0)