Skip to content

Commit e6267d7

Browse files
authored
Move group configuration Modelconfig collector group conf (#241)
* move config to modelconfigcollector Signed-off-by: ronenkat <16743404+ronenkat@users.noreply.github.com> * updated tests Signed-off-by: ronenkat <16743404+ronenkat@users.noreply.github.com> * address review Signed-off-by: ronenkat <16743404+ronenkat@users.noreply.github.com> * revert changes to requestcostmetadata/plugin_test.go Signed-off-by: ronenkat <16743404+ronenkat@users.noreply.github.com> --------- Signed-off-by: ronenkat <16743404+ronenkat@users.noreply.github.com>
1 parent 4da7f0e commit e6267d7

6 files changed

Lines changed: 441 additions & 257 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
Copyright 2026 The llm-d Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
// Package modelgroups defines the shared attribute key and Cloneable type used
18+
// to attach group membership to a Model in the datalayer. Producers (the
19+
// model-config-datasource plugin) publish a model's group names here; consumers
20+
// (the model-group-name-filter) read the same attribute to resolve "auto/<group>"
21+
// selectors, so the storage contract has a single source of truth.
22+
package modelgroups
23+
24+
import (
25+
"slices"
26+
27+
"github.com/llm-d/llm-d-inference-payload-processor/pkg/framework/interface/datalayer"
28+
)
29+
30+
// GroupsAttributeKey is the AttributeMap key under which a model's Groups is
31+
// stored. A model with no group memberships does not have this attribute set.
32+
const GroupsAttributeKey = "model_groups"
33+
34+
// Groups is a Cloneable list of group names a model belongs to. It is stored
35+
// in the Model's AttributeMap under GroupsAttributeKey.
36+
type Groups []string
37+
38+
// Clone implements datalayer.Cloneable.
39+
func (g Groups) Clone() datalayer.Cloneable {
40+
c := make(Groups, len(g))
41+
copy(c, g)
42+
return c
43+
}
44+
45+
// Contains reports whether name appears in g.
46+
func (g Groups) Contains(name string) bool {
47+
return slices.Contains(g, name)
48+
}

pkg/framework/plugins/datalayer/modelconfigcollector/plugin.go

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929

3030
"github.com/llm-d/llm-d-inference-payload-processor/pkg/framework/interface/datalayer"
3131
dlsrc "github.com/llm-d/llm-d-inference-payload-processor/pkg/framework/interface/datalayer/datasource"
32+
"github.com/llm-d/llm-d-inference-payload-processor/pkg/framework/interface/datalayer/modelgroups"
3233
"github.com/llm-d/llm-d-inference-payload-processor/pkg/framework/interface/datalayer/pricing"
3334
"github.com/llm-d/llm-d-inference-payload-processor/pkg/framework/interface/plugin"
3435
)
@@ -54,9 +55,17 @@ type ModelConfiguration struct {
5455
Pricing pricing.ModelPriceShape `json:"pricing"`
5556
}
5657

58+
// ModelGroupConfig is a named group of model names in the config file. It is
59+
// used to populate each listed model's modelgroups.GroupsAttributeKey attribute.
60+
type ModelGroupConfig struct {
61+
Name string `json:"name"`
62+
Models []string `json:"models"`
63+
}
64+
5765
// ModelsConfig is the schema of the JSON config file.
5866
type ModelsConfig struct {
5967
Models []ModelConfiguration `json:"models"`
68+
Groups []ModelGroupConfig `json:"groups,omitempty"`
6069
}
6170

6271
// ModelConfigDataSource watches a JSON file listing model names and keeps the
@@ -176,7 +185,8 @@ func (c *ModelConfigDataSource) Stop() {
176185
}
177186

178187
// syncModels reads the config file, registers every valid listed model in the datastore,
179-
// and removes any datastore model that no longer appears in the file.
188+
// removes any datastore model that no longer appears in the file, and (re)populates each
189+
// model's group membership from the config's group-centric "groups" list.
180190
func (c *ModelConfigDataSource) syncModels(ctx context.Context) error {
181191
logger := log.FromContext(ctx).WithName("model-config-datasource")
182192

@@ -191,7 +201,32 @@ func (c *ModelConfigDataSource) syncModels(ctx context.Context) error {
191201
return err
192202
}
193203

204+
// membership maps a model name to the group names it belongs to, inverting the
205+
// config's group-centric "groups" list ({name, models[]}) so it can be looked up
206+
// per model below. Membership is only resolved against the "models" list once
207+
// that loop below has run, so a group can reference a model before its validity
208+
// as a model entry is known.
209+
// A group with an empty name or an empty model list is invalid as the filter's
210+
// "auto/<group-name>" syntax requires non-empty group-name.
211+
// An individual empty model name within an otherwise valid group is
212+
// skipped on its own.
213+
membership := make(map[string]modelgroups.Groups)
214+
for _, g := range cfg.Groups {
215+
if g.Name == "" || len(g.Models) == 0 {
216+
logger.Info("skipping invalid group configuration", "group", g.Name, "models", g.Models)
217+
continue
218+
}
219+
for _, modelName := range g.Models {
220+
if modelName == "" {
221+
logger.Info("skipping empty model name in group configuration", "group", g.Name)
222+
continue
223+
}
224+
membership[modelName] = append(membership[modelName], g.Name)
225+
}
226+
}
227+
194228
desired := make(map[string]struct{}, len(cfg.Models))
229+
invalid := make(map[string]struct{}, len(cfg.Models))
195230
for _, m := range cfg.Models {
196231
if m.Name == "" {
197232
logger.Info("skipping model entry with empty name")
@@ -202,11 +237,35 @@ func (c *ModelConfigDataSource) syncModels(ctx context.Context) error {
202237
"model", m.Name,
203238
"input_per_million", m.Pricing.InputPerMillion,
204239
"output_per_million", m.Pricing.OutputPerMillion)
240+
invalid[m.Name] = struct{}{}
205241
continue
206242
}
207243
desired[m.Name] = struct{}{}
208244
mdl := c.ds.GetOrCreateModel(m.Name)
209245
mdl.GetAttributes().Put(pricing.TokenPricesAttributeKey, pricing.ToTokenPrices(m.Pricing))
246+
// Refresh group membership alongside pricing so a model that lost all its
247+
// group memberships in this reload doesn't keep a stale attribute.
248+
if groups, ok := membership[m.Name]; ok {
249+
mdl.GetAttributes().Put(modelgroups.GroupsAttributeKey, groups)
250+
} else {
251+
mdl.GetAttributes().Delete(modelgroups.GroupsAttributeKey)
252+
}
253+
}
254+
255+
// A group may reference a model name that isn't a valid "models" entry: either
256+
// missing from "models" entirely, or present but itself skipped (empty name,
257+
// negative price). Group membership never auto-creates a model, so just warn,
258+
// distinguishing the two cases so the "invalid model" warning already logged
259+
// above isn't mistaken for an unrelated "unknown model" one.
260+
for modelName := range membership {
261+
if _, ok := desired[modelName]; ok {
262+
continue
263+
}
264+
if _, ok := invalid[modelName]; ok {
265+
logger.Info("skipping invalid model referenced in group configuration", "model", modelName)
266+
} else {
267+
logger.Info("skipping unknown model in group configuration", "model", modelName)
268+
}
210269
}
211270

212271
for _, model := range c.ds.GetModels(datalayer.AllModelsPredicate) {

0 commit comments

Comments
 (0)