Skip to content

Commit b3b8127

Browse files
committed
fix(epp): use exact cached blocks for in-flight load
The in-flight load producer incorrectly used the tier-weighted MatchBlocks for literal token load tracking, inflating perceived load when serving from CPU cache. This updates the accounting logic to use the exact CachedBlockCount. Resolves issue #1292. Signed-off-by: prasanna <prasannajaga9@gmail.com>
1 parent 4ed07d6 commit b3b8127

2 files changed

Lines changed: 134 additions & 30 deletions

File tree

pkg/epp/framework/plugins/requestcontrol/dataproducer/inflightload/producer.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -510,12 +510,12 @@ func addedTokensKey(endpointID, profileName string) string {
510510
// excluding any prefix already cached on it.
511511
//
512512
// When the configured prefix producer (approximate or precise) has populated
513-
// PrefixCacheMatchInfo on the endpoint under prefixMatchInfoKey, the matched and
514-
// total block counts are in real (tokenized) units, so we use them directly:
515-
// uncached = (TotalBlocks - MatchBlocks) * BlockSizeTokens. For very long prompts
516-
// where the prefix index is capped (MaxPrefixTokensToMatch), any tail beyond the
517-
// cap is added back from the (estimated) inputTokens so the full prompt cost is
518-
// still reflected.
513+
// PrefixCacheMatchInfo on the endpoint under prefixMatchInfoKey, the literal
514+
// cached and total block counts are in real (tokenized) units, so we use them
515+
// directly: uncached = (TotalBlocks - CachedBlockCount) * BlockSizeTokens. For
516+
// very long prompts where the prefix index is capped (MaxPrefixTokensToMatch),
517+
// any tail beyond the cap is added back from the (estimated) inputTokens so the
518+
// full prompt cost is still reflected.
519519
//
520520
// When the attribute is missing, we fall back to the estimated inputTokens.
521521
func uncachedInputTokens(endpoint fwksched.Endpoint, inputTokens int64, prefixMatchInfoKey string) int64 {
@@ -532,10 +532,10 @@ func uncachedInputTokens(endpoint fwksched.Endpoint, inputTokens int64, prefixMa
532532
}
533533

534534
blockSize := int64(info.BlockSizeTokens())
535-
matched := int64(info.MatchBlocks()) * blockSize
535+
cached := int64(info.CachedBlockCount()) * blockSize
536536
indexed := int64(info.TotalBlocks()) * blockSize
537537

538-
uncachedIndexed := indexed - matched
538+
uncachedIndexed := indexed - cached
539539
if uncachedIndexed < 0 {
540540
uncachedIndexed = 0
541541
}

pkg/epp/framework/plugins/requestcontrol/dataproducer/inflightload/producer_test.go

Lines changed: 126 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,33 @@ func TestInFlightLoadProducer_Produce(t *testing.T) {
133133
require.False(t, ok, "InFlightLoad should not be populated by Produce")
134134
}
135135

136+
func TestInFlightLoadProducer_ProduceUsesLiteralCachedBlocks(t *testing.T) {
137+
t.Parallel()
138+
139+
// Initialize our load producer.
140+
producer := newTestProducer(t)
141+
// Disable output token estimation so we can test input tokens cleanly (output = 0).
142+
producer.addEstimatedOutputTokens = false
143+
144+
// Create a stub endpoint representing our model-serving pod.
145+
endpoint := newStubSchedulingEndpoint("precise-cache-endpoint")
146+
// Inject mock prefix cache information onto this endpoint.
147+
// matchBlocks = 1 (weighted), cachedBlockCount = 2 (literal), totalBlocks = 2, blockSizeTokens = 4.
148+
endpoint.Put(
149+
attrprefix.PrefixCacheMatchInfoDataKey.String(),
150+
attrprefix.NewPrefixCacheMatchInfo(1, 2, 4).WithCachedBlockCount(2),
151+
)
152+
153+
// Trigger the Produce logic with an 8-token request.
154+
err := producer.Produce(context.Background(), makeTokenRequest("req-precise", 8), []fwksched.Endpoint{endpoint})
155+
require.NoError(t, err)
156+
157+
// Assert the produced output is exactly 0 uncached tokens.
158+
value, ok := endpoint.Get(producer.uncachedRequestTokensDk.String())
159+
require.True(t, ok)
160+
require.Equal(t, int64(0), value.(*attrconcurrency.UncachedRequestTokens).Tokens)
161+
}
162+
136163
func TestInFlightLoadProducer_Extract(t *testing.T) {
137164
t.Parallel()
138165

@@ -648,23 +675,27 @@ func TestInFlightLoadProducer_ExcludeOutputTokens_SingleChunk(t *testing.T) {
648675
}
649676

650677
// TestInFlightLoadProducer_PrefixCacheDiscount verifies that when PrefixCacheMatchInfo
651-
// is published on the endpoint, the matched prefix is excluded from the tracked input
652-
// tokens, and that release subtracts the same (discounted) amount.
678+
// is published on the endpoint, the literal cached prefix is excluded from the tracked
679+
// input tokens, and that release subtracts the same (discounted) amount.
653680
func TestInFlightLoadProducer_PrefixCacheDiscount(t *testing.T) {
654681
t.Parallel()
655682

683+
// Setup the tracker database and context.
656684
producer := newTestProducer(t)
657685
ctx := context.Background()
658686
endpointName := "prefix-cache-endpoint"
659687
endpointID := fullEndpointName(endpointName)
660688

661689
// 8 input tokens. Output = 8 * 1.5 = 12.
662-
// With block_size=4, total=2 blocks, matched=1 block (4 tokens cached):
663-
// uncached_input = (2-1)*4 + max(0, 8-2*4) = 4
664-
// total tokens = 4 + 12 = 16
690+
// With block_size=4 and total=2 blocks, the tier-weighted match is 1 block,
691+
// but both blocks are cached. The tracked total therefore contains only output.
665692
endpoint := newStubSchedulingEndpoint(endpointName)
666-
endpoint.Put(attrprefix.PrefixCacheMatchInfoDataKey.String(), attrprefix.NewPrefixCacheMatchInfo(1, 2, 4))
693+
endpoint.Put(
694+
attrprefix.PrefixCacheMatchInfoDataKey.String(),
695+
attrprefix.NewPrefixCacheMatchInfo(1, 2, 4).WithCachedBlockCount(2),
696+
)
667697

698+
// Create an 8-token input request (with 12 estimated output tokens).
668699
req := makeTokenRequest("req-prefix", 8)
669700
res := &fwksched.SchedulingResult{
670701
PrimaryProfileName: "default",
@@ -673,10 +704,11 @@ func TestInFlightLoadProducer_PrefixCacheDiscount(t *testing.T) {
673704
},
674705
}
675706

707+
// Simulate dispatch (PreRequest Phase) adding request weight to trackers.
676708
producer.PreRequest(ctx, req, res)
677709
require.Equal(t, int64(1), producer.requestTracker.get(endpointID))
678-
require.Equal(t, int64(16), producer.tokenTracker.get(endpointID),
679-
"only uncached input (4) plus output (12) should be tracked")
710+
require.Equal(t, int64(12), producer.tokenTracker.get(endpointID),
711+
"only output tokens should be tracked for a fully cached input")
680712

681713
// Release uses the exact stored value, returning to zero.
682714
req.SchedulingResult = res
@@ -915,23 +947,95 @@ func TestInFlightLoadProducer_AtomicTokenRelease_Concurrent(t *testing.T) {
915947
require.Equal(t, int64(0), producer.requestTracker.get(endpointID))
916948
}
917949

918-
func TestUncachedInputTokens_Overestimate(t *testing.T) {
919-
// Setup:
920-
// inputTokens (estimated) = 5
921-
// PrefixCacheMatchInfo: matchBlocks=1, totalBlocks=2, blockSizeTokens=4
922-
// indexedTokens = 2 * 4 = 8
923-
// matchedTokens = 1 * 4 = 4
924-
925-
endpoint := newStubSchedulingEndpoint("test-ep")
926-
endpoint.Put(attrprefix.PrefixCacheMatchInfoDataKey.String(), attrprefix.NewPrefixCacheMatchInfo(1, 2, 4))
950+
func TestUncachedInputTokens(t *testing.T) {
951+
t.Parallel()
927952

928-
inputTokens := int64(5)
953+
const key = "prefix-match-info"
954+
tests := []struct {
955+
name string
956+
inputTokens int64
957+
info datalayer.Cloneable
958+
noEndpoint bool
959+
want int64
960+
}{
961+
// Case: Missing/nil components should default back to treating the entire prompt as uncached.
962+
{name: "nil endpoint", inputTokens: 8, noEndpoint: true, want: 8},
963+
{name: "negative input without endpoint", inputTokens: -1, noEndpoint: true, want: 0},
964+
{name: "missing prefix information", inputTokens: 8, want: 8},
965+
{name: "wrong prefix information type", inputTokens: 8, info: &attrconcurrency.InFlightLoad{}, want: 8},
966+
// Case: Literal count differs from weighted match (our big fix for offloaded caches).
967+
{
968+
name: "literal count differs from weighted match",
969+
inputTokens: 4096,
970+
info: attrprefix.NewPrefixCacheMatchInfo(204, 256, 16).
971+
WithCachedBlockCount(256),
972+
want: 0,
973+
},
974+
// Case: Partially cached precise prefix.
975+
{
976+
name: "partially cached precise prefix",
977+
inputTokens: 4096,
978+
info: attrprefix.NewPrefixCacheMatchInfo(128, 256, 16).
979+
WithCachedBlockCount(192),
980+
want: 1024,
981+
},
982+
// Case: Unindexed tail (prompt length exceeds maximum indexed range).
983+
{
984+
name: "unindexed tail",
985+
inputTokens: 4096,
986+
info: attrprefix.NewPrefixCacheMatchInfo(128, 128, 16).
987+
WithCachedBlockCount(128),
988+
want: 2048,
989+
},
990+
{
991+
name: "partial final block",
992+
inputTokens: 10,
993+
info: attrprefix.NewPrefixCacheMatchInfo(1, 2, 4),
994+
want: 6,
995+
},
996+
// Case: Backward compatibility test (defaults cached count to match blocks).
997+
{
998+
name: "approximate producer compatibility",
999+
inputTokens: 4096,
1000+
info: attrprefix.NewPrefixCacheMatchInfo(128, 256, 16),
1001+
want: 2048,
1002+
},
1003+
{
1004+
name: "invalid block size",
1005+
inputTokens: 8,
1006+
info: attrprefix.NewPrefixCacheMatchInfo(1, 2, 0),
1007+
want: 8,
1008+
},
1009+
// Case: Clamped bounds safety check.
1010+
{
1011+
name: "cached count above total blocks",
1012+
inputTokens: 8,
1013+
info: attrprefix.NewPrefixCacheMatchInfo(2, 2, 4).
1014+
WithCachedBlockCount(3),
1015+
want: 0,
1016+
},
1017+
{
1018+
name: "prefix count trusted over smaller estimate",
1019+
inputTokens: 5,
1020+
info: attrprefix.NewPrefixCacheMatchInfo(1, 2, 4),
1021+
want: 4,
1022+
},
1023+
}
9291024

930-
uncached := uncachedInputTokens(endpoint, inputTokens, attrprefix.PrefixCacheMatchInfoDataKey.String())
1025+
for _, tt := range tests {
1026+
t.Run(tt.name, func(t *testing.T) {
1027+
var endpoint fwksched.Endpoint
1028+
if !tt.noEndpoint {
1029+
stub := newStubSchedulingEndpoint("test-endpoint")
1030+
if tt.info != nil {
1031+
stub.Put(key, tt.info)
1032+
}
1033+
endpoint = stub
1034+
}
9311035

932-
// When the prefix cache says 4 tokens are definitely uncached in the indexed portion (8-4),
933-
// we trust that over the smaller (approximate) estimate of 5.
934-
require.Equal(t, int64(4), uncached, "should trust the prefix cache's uncached count (indexed-matched) over the smaller estimate")
1036+
require.Equal(t, tt.want, uncachedInputTokens(endpoint, tt.inputTokens, key))
1037+
})
1038+
}
9351039
}
9361040

9371041
func TestInFlightLoadProducer_PanicSafety(t *testing.T) {

0 commit comments

Comments
 (0)