Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions api/core/v1alpha1/model_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ const (

HUGGING_FACE = "Huggingface"
MODEL_SCOPE = "ModelScope"

DefaultOwnedBy = "llmaz"
)

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

const (
Expand Down
9 changes: 9 additions & 0 deletions api/core/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions client-go/applyconfiguration/core/v1alpha1/modelspec.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions config/crd/bases/llmaz.io_openmodels.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ spec:
spec:
description: ModelSpec defines the desired state of Model
properties:
createdAt:
description: |-
CreatedAt represents the creation timestamp of the running models serving by the backends,
which will be exported as the field of "Created" in openai-compatible API "/models".
It follows the format of RFC 3339, for example "2024-05-21T10:00:00Z".
format: date-time
type: string
familyName:
description: |-
FamilyName represents the model type, like llama2, which will be auto injected
Expand Down Expand Up @@ -106,6 +113,13 @@ spec:
maxItems: 8
type: array
type: object
ownedBy:
default: llmaz
description: |-
OwnedBy represents the owner of the running models serving by the backends,
which will be exported as the field of "OwnedBy" in openai-compatible API "/models".
Default to "llmaz" if not set.
type: string
source:
description: |-
Source represents the source of the model, there're several ways to load
Expand Down
4 changes: 4 additions & 0 deletions pkg/webhook/openmodel_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ package webhook
import (
"context"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/validation"
"k8s.io/apimachinery/pkg/util/validation/field"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/webhook"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
Expand Down Expand Up @@ -61,6 +63,8 @@ func (w *OpenModelWebhook) Default(ctx context.Context, obj runtime.Object) erro
model.Labels = map[string]string{}
}
model.Labels[coreapi.ModelFamilyNameLabelKey] = string(model.Spec.FamilyName)
createdAt := ptr.Deref[metav1.Time](model.Spec.CreatedAt, metav1.Now().Rfc3339Copy())
model.Spec.CreatedAt = &createdAt
return nil
}

Expand Down
18 changes: 18 additions & 0 deletions site/content/en/docs/reference/core.v1alpha1.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,24 @@ the model such as loading from huggingface, OCI registry, s3, host path and so o
<p>InferenceConfig represents the inference configurations for the model.</p>
</td>
</tr>
<tr><td><code>ownedBy</code><br/>
<code>string</code>
</td>
<td>
<p>OwnedBy represents the owner of the running models serving by the backends,
which will be exported as the field of &quot;OwnedBy&quot; in openai-compatible API &quot;/models&quot;.
Default to &quot;llmaz&quot; if not set.</p>
</td>
</tr>
<tr><td><code>createdAt</code><br/>
<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>
</td>
<td>
<p>CreatedAt represents the creation timestamp of the running models serving by the backends,
which will be exported as the field of &quot;Created&quot; in openai-compatible API &quot;/models&quot;.
It follows the format of RFC 3339, for example &quot;2024-05-21T10:00:00Z&quot;.</p>
</td>
</tr>
</tbody>
</table>

Expand Down
13 changes: 11 additions & 2 deletions test/integration/webhook/model_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,31 @@ var _ = ginkgo.Describe("model default and validation", func() {
gomega.Expect(k8sClient.Create(ctx, model)).To(gomega.Succeed())
gomega.Expect(model).To(gomega.BeComparableTo(tc.wantModel(),
cmpopts.IgnoreTypes(coreapi.ModelStatus{}),
cmpopts.IgnoreFields(coreapi.ModelSpec{}, "CreatedAt"),
Copy link
Member Author

Choose a reason for hiding this comment

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

Regarding the time field, in order to avoid fake tests, we should avoid checking

cmpopts.IgnoreFields(metav1.ObjectMeta{}, "UID", "ResourceVersion", "Generation", "CreationTimestamp", "ManagedFields")))
},
ginkgo.Entry("apply model family name", &testDefaultingCase{
model: func() *coreapi.OpenModel {
return wrapper.MakeModel("llama3-8b").ModelSourceWithModelID("meta-llama/Meta-Llama-3-8B", "", "", nil, nil).FamilyName("llama3").Obj()
},
wantModel: func() *coreapi.OpenModel {
return wrapper.MakeModel("llama3-8b").ModelSourceWithModelID("meta-llama/Meta-Llama-3-8B", "", "main", nil, nil).ModelSourceWithModelHub("Huggingface").FamilyName("llama3").Label(coreapi.ModelFamilyNameLabelKey, "llama3").Obj()
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()
},
}),
ginkgo.Entry("apply modelscope model hub name", &testDefaultingCase{
model: func() *coreapi.OpenModel {
return wrapper.MakeModel("llama3-8b").FamilyName("llama3").ModelSourceWithModelHub("ModelScope").ModelSourceWithModelID("LLM-Research/Meta-Llama-3-8B", "", "", nil, nil).Obj()
},
wantModel: func() *coreapi.OpenModel {
return wrapper.MakeModel("llama3-8b").ModelSourceWithModelID("LLM-Research/Meta-Llama-3-8B", "", "main", nil, nil).ModelSourceWithModelHub("ModelScope").FamilyName("llama3").Label(coreapi.ModelFamilyNameLabelKey, "llama3").Obj()
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()
},
}),
ginkgo.Entry("custom ownedBy should not be overwritten", &testDefaultingCase{
model: func() *coreapi.OpenModel {
return wrapper.MakeModel("llama3-8b").FamilyName("llama3").ModelSourceWithModelHub("ModelScope").ModelSourceWithModelID("LLM-Research/Meta-Llama-3-8B", "", "", nil, nil).OwnedBy("custom-owner").Obj()
},
wantModel: func() *coreapi.OpenModel {
return wrapper.MakeModel("llama3-8b").ModelSourceWithModelID("LLM-Research/Meta-Llama-3-8B", "", "main", nil, nil).ModelSourceWithModelHub("ModelScope").FamilyName("llama3").Label(coreapi.ModelFamilyNameLabelKey, "llama3").OwnedBy("custom-owner").Obj()
},
}),
)
Expand Down
7 changes: 7 additions & 0 deletions test/util/validation/validate_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package validation
import (
"context"
"errors"
"fmt"

"github.com/onsi/gomega"
"k8s.io/apimachinery/pkg/types"
Expand All @@ -37,6 +38,12 @@ func ValidateModel(ctx context.Context, k8sClient client.Client, model *coreapi.
if model.Labels[coreapi.ModelFamilyNameLabelKey] != string(model.Spec.FamilyName) {
return errors.New("family name not right")
}
if model.Spec.OwnedBy == nil {
return fmt.Errorf("ownedBy is nil")
}
if model.Spec.CreatedAt == nil {
return fmt.Errorf("createdAt is nil")
}

return nil
}, util.IntegrationTimeout, util.Interval).Should(gomega.Succeed())
Expand Down
5 changes: 5 additions & 0 deletions test/util/wrapper/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,11 @@ func (w *ModelWrapper) Label(k, v string) *ModelWrapper {
return w
}

func (w *ModelWrapper) OwnedBy(ownedBy string) *ModelWrapper {
w.Spec.OwnedBy = &ownedBy
return w
}

func MakeFlavor(name string) *FlavorWrapper {
return &FlavorWrapper{
coreapi.Flavor{
Expand Down
Loading