Skip to content

Commit cc8eca3

Browse files
committed
feat: add ownedBy and createdAt for OpenModel
Signed-off-by: googs1025 <[email protected]>
1 parent 5e00ed3 commit cc8eca3

File tree

9 files changed

+128
-2
lines changed

9 files changed

+128
-2
lines changed

api/core/v1alpha1/model_types.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ const (
3737

3838
HUGGING_FACE = "Huggingface"
3939
MODEL_SCOPE = "ModelScope"
40+
41+
DefaultOwnedBy = "llmaz"
4042
)
4143

4244
// ModelHub represents the model registry for model downloads.
@@ -195,6 +197,18 @@ type ModelSpec struct {
195197
Source ModelSource `json:"source"`
196198
// InferenceConfig represents the inference configurations for the model.
197199
InferenceConfig *InferenceConfig `json:"inferenceConfig,omitempty"`
200+
// OwnedBy represents the owner of the running models serving by the backends,
201+
// which will be exported as the field of "OwnedBy" in openai-compatible API "/models".
202+
// Default to "llmaz" if not set.
203+
// +optional
204+
// +kubebuilder:default="llmaz"
205+
OwnedBy *string `json:"ownedBy,omitempty"`
206+
// CreatedAt represents the creation timestamp of the running models serving by the backends,
207+
// which will be exported as the field of "Created" in openai-compatible API "/models".
208+
// It follows the format of RFC 3339, for example "2024-05-21T10:00:00Z".
209+
// +optional
210+
// +kubebuilder:validation:Format=date-time
211+
CreatedAt *metav1.Time `json:"createdAt,omitempty"`
198212
}
199213

200214
const (

api/core/v1alpha1/zz_generated.deepcopy.go

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client-go/applyconfiguration/core/v1alpha1/modelspec.go

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/llmaz.io_openmodels.yaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ spec:
4141
spec:
4242
description: ModelSpec defines the desired state of Model
4343
properties:
44+
createdAt:
45+
description: |-
46+
CreatedAt represents the creation timestamp of the running models serving by the backends,
47+
which will be exported as the field of "Created" in openai-compatible API "/models".
48+
It follows the format of RFC 3339, for example "2024-05-21T10:00:00Z".
49+
format: date-time
50+
type: string
4451
familyName:
4552
description: |-
4653
FamilyName represents the model type, like llama2, which will be auto injected
@@ -106,6 +113,13 @@ spec:
106113
maxItems: 8
107114
type: array
108115
type: object
116+
ownedBy:
117+
default: llmaz
118+
description: |-
119+
OwnedBy represents the owner of the running models serving by the backends,
120+
which will be exported as the field of "OwnedBy" in openai-compatible API "/models".
121+
Default to "llmaz" if not set.
122+
type: string
109123
source:
110124
description: |-
111125
Source represents the source of the model, there're several ways to load

pkg/controller/core/model_controller.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package controller
1818

1919
import (
2020
"context"
21+
"time"
2122

2223
"k8s.io/apimachinery/pkg/runtime"
2324
"k8s.io/apimachinery/pkg/types"
@@ -64,6 +65,17 @@ func (r *ModelReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl
6465

6566
logger.V(10).Info("reconcile Model", "Model", klog.KObj(model))
6667

68+
shouldUpdate, newModel := needsUpdated(model)
69+
if shouldUpdate {
70+
if err := r.Update(ctx, newModel); err != nil {
71+
logger.Error(err, "failed to update OpenModel with default values")
72+
return ctrl.Result{}, err
73+
}
74+
logger.Info("Updated OpenModel with default spec fields",
75+
"OwnedBy", *newModel.Spec.OwnedBy,
76+
"CreatedAt", newModel.Spec.CreatedAt.Time.UTC().Format(time.RFC3339))
77+
}
78+
6779
return ctrl.Result{}, nil
6880
}
6981

@@ -73,3 +85,22 @@ func (r *ModelReconciler) SetupWithManager(mgr ctrl.Manager) error {
7385
For(&coreapi.OpenModel{}).
7486
Complete(r)
7587
}
88+
89+
// needsUpdated checks if the model needs to be updated.
90+
func needsUpdated(model *coreapi.OpenModel) (bool, *coreapi.OpenModel) {
91+
needsUpdate := false
92+
newModel := model.DeepCopy()
93+
94+
if newModel.Spec.OwnedBy == nil {
95+
defaultOwner := coreapi.DefaultOwnedBy
96+
newModel.Spec.OwnedBy = &defaultOwner
97+
needsUpdate = true
98+
}
99+
100+
if newModel.Spec.CreatedAt == nil {
101+
newModel.Spec.CreatedAt = &model.CreationTimestamp
102+
needsUpdate = true
103+
}
104+
105+
return needsUpdate, newModel
106+
}

site/content/en/docs/reference/core.v1alpha1.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,24 @@ the model such as loading from huggingface, OCI registry, s3, host path and so o
361361
<p>InferenceConfig represents the inference configurations for the model.</p>
362362
</td>
363363
</tr>
364+
<tr><td><code>ownedBy</code><br/>
365+
<code>string</code>
366+
</td>
367+
<td>
368+
<p>OwnedBy represents the owner of the running models serving by the backends,
369+
which will be exported as the field of &quot;OwnedBy&quot; in openai-compatible API &quot;/models&quot;.
370+
Default to &quot;llmaz&quot; if not set.</p>
371+
</td>
372+
</tr>
373+
<tr><td><code>createdAt</code><br/>
374+
<a href="https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#time-v1-meta"><code>k8s.io/apimachinery/pkg/apis/meta/v1.Time</code></a>
375+
</td>
376+
<td>
377+
<p>CreatedAt represents the creation timestamp of the running models serving by the backends,
378+
which will be exported as the field of &quot;Created&quot; in openai-compatible API &quot;/models&quot;.
379+
It follows the format of RFC 3339, for example &quot;2024-05-21T10:00:00Z&quot;.</p>
380+
</td>
381+
</tr>
364382
</tbody>
365383
</table>
366384

test/integration/webhook/model_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,15 @@ var _ = ginkgo.Describe("model default and validation", func() {
5555
return wrapper.MakeModel("llama3-8b").ModelSourceWithModelID("meta-llama/Meta-Llama-3-8B", "", "", nil, nil).FamilyName("llama3").Obj()
5656
},
5757
wantModel: func() *coreapi.OpenModel {
58-
return wrapper.MakeModel("llama3-8b").ModelSourceWithModelID("meta-llama/Meta-Llama-3-8B", "", "main", nil, nil).ModelSourceWithModelHub("Huggingface").FamilyName("llama3").Label(coreapi.ModelFamilyNameLabelKey, "llama3").Obj()
58+
return wrapper.MakeModel("llama3-8b").ModelSourceWithModelID("meta-llama/Meta-Llama-3-8B", "", "main", nil, nil).ModelSourceWithModelHub("Huggingface").FamilyName("llama3").Label(coreapi.ModelFamilyNameLabelKey, "llama3").OwnedBy(coreapi.DefaultOwnedBy).Obj()
5959
},
6060
}),
6161
ginkgo.Entry("apply modelscope model hub name", &testDefaultingCase{
6262
model: func() *coreapi.OpenModel {
6363
return wrapper.MakeModel("llama3-8b").FamilyName("llama3").ModelSourceWithModelHub("ModelScope").ModelSourceWithModelID("LLM-Research/Meta-Llama-3-8B", "", "", nil, nil).Obj()
6464
},
6565
wantModel: func() *coreapi.OpenModel {
66-
return wrapper.MakeModel("llama3-8b").ModelSourceWithModelID("LLM-Research/Meta-Llama-3-8B", "", "main", nil, nil).ModelSourceWithModelHub("ModelScope").FamilyName("llama3").Label(coreapi.ModelFamilyNameLabelKey, "llama3").Obj()
66+
return wrapper.MakeModel("llama3-8b").ModelSourceWithModelID("LLM-Research/Meta-Llama-3-8B", "", "main", nil, nil).ModelSourceWithModelHub("ModelScope").FamilyName("llama3").Label(coreapi.ModelFamilyNameLabelKey, "llama3").OwnedBy(coreapi.DefaultOwnedBy).Obj()
6767
},
6868
}),
6969
)

test/util/validation/validate_model.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package validation
1919
import (
2020
"context"
2121
"errors"
22+
"fmt"
2223

2324
"github.com/onsi/gomega"
2425
"k8s.io/apimachinery/pkg/types"
@@ -37,6 +38,21 @@ func ValidateModel(ctx context.Context, k8sClient client.Client, model *coreapi.
3738
if model.Labels[coreapi.ModelFamilyNameLabelKey] != string(model.Spec.FamilyName) {
3839
return errors.New("family name not right")
3940
}
41+
if model.Spec.OwnedBy == nil {
42+
return fmt.Errorf("ownedBy is nil")
43+
}
44+
if *model.Spec.OwnedBy != coreapi.DefaultOwnedBy {
45+
return fmt.Errorf("ownedBy value not right: expected %q, got %q", coreapi.DefaultOwnedBy, *model.Spec.OwnedBy)
46+
}
47+
if *model.Spec.CreatedAt != model.CreationTimestamp {
48+
return errors.New("created at not right")
49+
}
50+
if model.Spec.CreatedAt != nil {
51+
if !model.Spec.CreatedAt.Time.Equal(model.CreationTimestamp.Time) {
52+
return fmt.Errorf("createdAt not equal to creationTimestamp: expected %v, got %v",
53+
model.CreationTimestamp.Time, model.Spec.CreatedAt.Time)
54+
}
55+
}
4056

4157
return nil
4258
}, util.IntegrationTimeout, util.Interval).Should(gomega.Succeed())

test/util/wrapper/model.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,11 @@ func (w *ModelWrapper) Label(k, v string) *ModelWrapper {
107107
return w
108108
}
109109

110+
func (w *ModelWrapper) OwnedBy(ownedBy string) *ModelWrapper {
111+
w.Spec.OwnedBy = &ownedBy
112+
return w
113+
}
114+
110115
func MakeFlavor(name string) *FlavorWrapper {
111116
return &FlavorWrapper{
112117
coreapi.Flavor{

0 commit comments

Comments
 (0)