@@ -38,6 +38,7 @@ import (
3838 fwkrh "github.com/llm-d/llm-d-router/pkg/epp/framework/interface/requesthandling"
3939 "github.com/llm-d/llm-d-router/pkg/epp/framework/interface/scheduling"
4040 attrmm "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/datalayer/attribute/multimodal"
41+ tokenproducer "github.com/llm-d/llm-d-router/pkg/epp/framework/plugins/requestcontrol/dataproducer/tokenizer"
4142 k8stypes "k8s.io/apimachinery/pkg/types"
4243)
4344
@@ -47,6 +48,11 @@ const (
4748
4849 defaultCacheSize = 10000
4950 podCleanupInterval = 2 * time .Minute
51+
52+ // WeightModeUnweighted counts each unique multimodal hash once with unit size.
53+ WeightModeUnweighted = "unweighted"
54+ // WeightModeWeighted uses token-producer multimodal placeholder lengths as item sizes.
55+ WeightModeWeighted = "weighted"
5056)
5157
5258var (
@@ -56,12 +62,16 @@ var (
5662 _ requestcontrol.DataProducer = & Producer {}
5763 _ requestcontrol.PreRequest = & Producer {}
5864 _ fwkdl.EndpointExtractor = & Producer {}
65+ _ plugin.ConsumerPlugin = & Producer {}
5966)
6067
6168// Parameters configures the multimodal encoder-cache data producer.
6269type Parameters struct {
6370 // CacheSize defines the maximum number of mm_hash -> pod-set entries to track.
6471 CacheSize int `json:"cacheSize"`
72+ // WeightMode selects unweighted unit sizes (default) or weighted tokenized
73+ // placeholder lengths from token-producer metadata.
74+ WeightMode string `json:"weightMode,omitempty"`
6575}
6676
6777// Factory creates a multimodal encoder-cache data producer.
@@ -72,6 +82,12 @@ func Factory(name string, rawParameters *json.Decoder, handle plugin.Handle) (pl
7282 return nil , fmt .Errorf ("failed to parse the parameters of the '%s' plugin - %w" , ProducerType , err )
7383 }
7484 }
85+ if parameters .WeightMode == "" {
86+ parameters .WeightMode = WeightModeUnweighted
87+ }
88+ if parameters .WeightMode != WeightModeUnweighted && parameters .WeightMode != WeightModeWeighted {
89+ return nil , fmt .Errorf ("failed to parse the parameters of the '%s' plugin - invalid weightMode %q" , ProducerType , parameters .WeightMode )
90+ }
7591
7692 return New (handle .Context (), name , & parameters , handle .PodList )
7793}
@@ -81,6 +97,7 @@ func Factory(name string, rawParameters *json.Decoder, handle plugin.Handle) (pl
8197type Producer struct {
8298 typedName plugin.TypedName
8399 dk plugin.DataKey
100+ useWeighted bool
84101 cache * lru.Cache [string , map [string ]struct {}]
85102 pluginState * plugin.PluginState
86103 podList func () []k8stypes.NamespacedName
@@ -111,9 +128,15 @@ func New(ctx context.Context, name string, params *Parameters, podList func() []
111128 return nil , fmt .Errorf ("failed to create multimodal encoder-cache LRU with size %d: %w" , cacheSize , err )
112129 }
113130
131+ useWeighted := false
132+ if params != nil && params .WeightMode == WeightModeWeighted {
133+ useWeighted = true
134+ }
135+
114136 p := & Producer {
115137 typedName : plugin.TypedName {Type : ProducerType , Name : name },
116138 dk : attrmm .EncoderCacheMatchInfoKey .WithNonEmptyProducerName (name ),
139+ useWeighted : useWeighted ,
117140 cache : cache ,
118141 pluginState : plugin .NewPluginState (ctx ),
119142 podList : podList ,
@@ -147,6 +170,15 @@ func (p *Producer) Produces() map[plugin.DataKey]any {
147170 return map [plugin.DataKey ]any {p .dk : attrmm.EncoderCacheMatchInfo {}}
148171}
149172
173+ // Consumes declares the token-producer dependency only for weighted mode so
174+ // unweighted configs are not forced to pull in tokenization.
175+ func (p * Producer ) Consumes () map [plugin.DataKey ]any {
176+ if ! p .useWeighted {
177+ return nil
178+ }
179+ return map [plugin.DataKey ]any {tokenproducer .TokenizedPromptDataKey : scheduling.TokenizedPrompt {}}
180+ }
181+
150182// PluginState returns request-scoped state shared between producer extension points.
151183func (p * Producer ) PluginState () * plugin.PluginState {
152184 return p .pluginState
@@ -155,7 +187,7 @@ func (p *Producer) PluginState() *plugin.PluginState {
155187// Produce attaches multimodal encoder-cache match data to endpoints.
156188func (p * Producer ) Produce (ctx context.Context , request * scheduling.InferenceRequest , endpoints []scheduling.Endpoint ) error {
157189 logger := log .FromContext (ctx ).V (logging .DEBUG )
158- requestItems := ExtractMMItems (request )
190+ requestItems := p . extractMMItems (request )
159191 if len (requestItems ) == 0 {
160192 logger .Info ("No multimodal content found, skipping encoder-cache match data" )
161193 return nil
@@ -179,16 +211,25 @@ func (p *Producer) Produce(ctx context.Context, request *scheduling.InferenceReq
179211 return nil
180212}
181213
214+ func (p * Producer ) extractMMItems (request * scheduling.InferenceRequest ) []attrmm.MatchItem {
215+ return extractMMItems (request , p .useWeighted )
216+ }
217+
182218// ExtractMMItems returns deterministic, unique multimodal encoder-cache items
183- // for a request. Parser-provided multimodal features are preferred; if
184- // unavailable, typed structured media blocks are hashed from stable identifiers.
219+ // for a request using the unweighted path. Parser-provided multimodal features
220+ // are preferred; if unavailable, typed structured media blocks are hashed from
221+ // stable identifiers.
185222func ExtractMMItems (request * scheduling.InferenceRequest ) []attrmm.MatchItem {
223+ return extractMMItems (request , false )
224+ }
225+
226+ func extractMMItems (request * scheduling.InferenceRequest , useWeighted bool ) []attrmm.MatchItem {
186227 if request == nil || request .Body == nil {
187228 return nil
188229 }
189230
190231 if request .Body .TokenizedPrompt != nil && len (request .Body .TokenizedPrompt .MultiModalFeatures ) > 0 {
191- return itemsFromTokenizedPrompt (request .Body .TokenizedPrompt .MultiModalFeatures )
232+ return itemsFromTokenizedPrompt (request .Body .TokenizedPrompt .MultiModalFeatures , useWeighted )
192233 }
193234
194235 if g := request .Body .Generate ; g != nil && g .Features != nil && len (g .Features .MMHashes ) > 0 {
@@ -215,17 +256,28 @@ func itemsFromGenerateFeatures(mmHashes map[string][]string) []attrmm.MatchItem
215256 return itemSlice (itemsByHash )
216257}
217258
218- func itemsFromTokenizedPrompt (features []fwkrh.MultiModalFeature ) []attrmm.MatchItem {
259+ func itemsFromTokenizedPrompt (features []fwkrh.MultiModalFeature , useWeighted bool ) []attrmm.MatchItem {
219260 itemsByHash := map [string ]attrmm.MatchItem {}
220261 for _ , feature := range features {
221- if feature .Hash == "" {
222- continue
223- }
224- addItem (itemsByHash , feature .Hash )
262+ addTokenizedItem (itemsByHash , feature , useWeighted )
225263 }
226264 return itemSlice (itemsByHash )
227265}
228266
267+ func addTokenizedItem (itemsByHash map [string ]attrmm.MatchItem , feature fwkrh.MultiModalFeature , useWeighted bool ) {
268+ if feature .Hash == "" {
269+ return
270+ }
271+ if _ , exists := itemsByHash [feature .Hash ]; exists {
272+ return
273+ }
274+ size := 1
275+ if useWeighted && feature .Length > 0 {
276+ size = feature .Length
277+ }
278+ itemsByHash [feature .Hash ] = attrmm.MatchItem {Hash : feature .Hash , Size : size }
279+ }
280+
229281func itemsFromChat (request * fwkrh.ChatCompletionsRequest ) []attrmm.MatchItem {
230282 itemsByHash := map [string ]attrmm.MatchItem {}
231283 for _ , message := range request .Messages {
0 commit comments