Skip to content

fix(epp): record encode-stage endpoint in multimodal encoder-cache - #2627

Open
acardace wants to merge 3 commits into
llm-d:mainfrom
acardace:fix/mm-cache-prerequest-records-encode-endpoints
Open

fix(epp): record encode-stage endpoint in multimodal encoder-cache#2627
acardace wants to merge 3 commits into
llm-d:mainfrom
acardace:fix/mm-cache-prerequest-records-encode-endpoints

Conversation

@acardace

Copy link
Copy Markdown
Contributor

What type of PR is this?
/kind bug

What this PR does / why we need it:

In encode-disaggregated (E/PD, E/P/D) serving, mm-embeddings-cache-scorer always scored 0 for every encode pod, so encoder-cache affinity never routed repeated media to the pod that already held the embedding, and encoder_cache_hit_ratio stayed at 0.

The mm-embeddings-cache-producer reads the per-pod LRU in Produce() (keyed by endpoint.ID, scoring encode pods) and writes it in PreRequest(). PreRequest recorded the endpoints from ProfileResults[PrimaryProfileName], which disagg-profile-handler sets to the decode profile. So the producer stored decode-pod IDs while scoring queried encode-pod IDs: the keys never matched and affinity was always 0. Recording the decode pod also inflated encoder_cache_hits_total, which counts a hit for every pod in the LRU holding the hash even though decode pods do not hold the encoder cache.

This PR records the encode profile target endpoints instead, falling back to the primary profile when no encode profile ran (aggregated serving). The encode profile name is configurable via a new encodeProfile parameter (default encode) to match disagg-profile-handler.

Validated on a 1 encode-per-pod and 2 encode-pod E/PD deployment (Qwen2.5-VL-7B): with the fix, the first request scores 0 (cold), subsequent same-image requests score 1 on the encoding pod and 0 on the other, and all repeated requests route to that pod. encoder_cache_hits_total records only the encode pod.

Which issue(s) this PR fixes:
Fixes #2626

Release note (write NONE if no user-facing change):

Fixed multimodal encoder-cache affinity in encode-disaggregated (E/PD, E/P/D) serving. The mm-embeddings-cache-producer now records the encode-stage endpoint that holds the embeddings, rather than the decode endpoint, so repeated media routes to the pod that already encoded it. Adds an optional encodeProfile parameter (default "encode") to match the profiles.encode value on disagg-profile-handler.

Add an optional encodeProfile parameter (default "encode") to the
multimodal encoder-cache producer, matching the profiles.encode value on
disagg-profile-handler. The value is resolved and stored on the producer
for use when recording encoder-cache placements.

Signed-off-by: Antonio Cardace <acardace@redhat.com>
The mm-embeddings-cache-producer recorded encoder-cache placements against
the primary (decode) profile's endpoint. In disaggregated E/PD serving the
encode stage runs as a separate profile and selects a different pod, so the
producer stored decode-pod IDs while scoring queried encode-pod IDs. The
keys never matched: the affinity score was always zero and repeated media
never stuck to the pod that already held the embeddings.

Record the encode profile's target endpoints instead, falling back to the
primary profile when no encode profile ran (aggregated serving).

Recording only the encode endpoint also keeps encoder_cache_hits_total from
counting decode pods, which do not hold the encoder cache.

Signed-off-by: Antonio Cardace <acardace@redhat.com>
@acardace
acardace requested a review from a team as a code owner August 31, 2026 12:43
@acardace
acardace requested review from elevran and hexfusion August 31, 2026 12:43
@github-actions github-actions Bot added area/epp area/scheduling size/L Denotes a PR that changes 100-499 lines, ignoring generated files. kind/bug Categorizes issue or PR as related to a bug. labels Aug 31, 2026
@acardace
acardace requested a review from zetxqx as a code owner September 1, 2026 07:51
@yankay
yankay requested a lite review from Copilot September 1, 2026 11:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes multimodal encoder-cache affinity for encode-disaggregated serving by ensuring mm-embeddings-cache-producer records the encode-stage endpoint(s) (rather than the decode endpoint) when updating the per-pod LRU used for affinity scoring.

Changes:

  • Add an encodeProfile parameter (default "encode") and store it on the producer instance.
  • Update PreRequest to select endpoints from the encode profile’s TargetEndpoints, with fallback to the primary profile for aggregated serving.
  • Add unit tests covering encode-vs-decode endpoint recording and the primary-profile fallback behavior.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
pkg/epp/framework/plugins/requestcontrol/dataproducer/multimodal/producer.go Introduces configurable encodeProfile and wires it into the producer initialization.
pkg/epp/framework/plugins/requestcontrol/dataproducer/multimodal/prerequest.go Records cache placement against encode-profile endpoints (fallback to primary profile).
pkg/epp/framework/plugins/requestcontrol/dataproducer/multimodal/producer_test.go Adds tests validating encode endpoint recording and aggregated fallback.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +149 to +173
func TestPreRequestRecordsEncodeEndpointInDisaggregatedMode(t *testing.T) {
producer := newTestProducer(t, nil, nil)
encodePod := k8stypes.NamespacedName{Namespace: "default", Name: "encode-pod"}
decodePod := k8stypes.NamespacedName{Namespace: "default", Name: "decode-pod"}

request := requestWithHashes("req-1", map[string]int{"hash-a": 1})

require.NoError(t, producer.Produce(context.Background(), request,
[]scheduling.Endpoint{newEndpoint(encodePod), newEndpoint(decodePod)}))

result := &scheduling.SchedulingResult{
PrimaryProfileName: "decode",
ProfileResults: map[string]*scheduling.ProfileRunResult{
"decode": {TargetEndpoints: []scheduling.Endpoint{newEndpoint(decodePod)}},
"encode": {TargetEndpoints: []scheduling.Endpoint{newEndpoint(encodePod)}},
},
}

_ = producer.PreRequest(context.Background(), request, result)
producer.wg.Wait()

cache := producer.cacheSnapshot()
assert.Contains(t, cache["hash-a"], encodePod.String())
assert.NotContains(t, cache["hash-a"], decodePod.String())
}
…producer

Signed-off-by: Antonio Cardace <acardace@redhat.com>
@acardace
acardace force-pushed the fix/mm-cache-prerequest-records-encode-endpoints branch from 8b0f630 to a9a8cc4 Compare September 1, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/epp area/scheduling kind/bug Categorizes issue or PR as related to a bug. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

mm-embeddings-cache-producer records decode pod instead of encode pod in E/PD disaggregation

2 participants