Unverified Commit a91e6348 authored by Julien Mancuso's avatar Julien Mancuso Committed by GitHub
Browse files

feat: add dynamoModel CRD (#4166)


Signed-off-by: default avatarJulien Mancuso <jmancuso@nvidia.com>
parent b2f1defe
...@@ -10007,6 +10007,20 @@ spec: ...@@ -10007,6 +10007,20 @@ spec:
format: int32 format: int32
type: integer type: integer
type: object type: object
modelRef:
description: |-
ModelRef references a model that this component serves
When specified, a headless service will be created for endpoint discovery
properties:
name:
description: Name is the base model identifier (e.g., "llama-3-70b-instruct-v1")
type: string
revision:
description: Revision is the model revision/version (optional)
type: string
required:
- name
type: object
multinode: multinode:
description: Multinode is the configuration for multinode components. description: Multinode is the configuration for multinode components.
properties: properties:
......
...@@ -10142,6 +10142,20 @@ spec: ...@@ -10142,6 +10142,20 @@ spec:
format: int32 format: int32
type: integer type: integer
type: object type: object
modelRef:
description: |-
ModelRef references a model that this component serves
When specified, a headless service will be created for endpoint discovery
properties:
name:
description: Name is the base model identifier (e.g., "llama-3-70b-instruct-v1")
type: string
revision:
description: Revision is the model revision/version (optional)
type: string
required:
- name
type: object
multinode: multinode:
description: Multinode is the configuration for multinode components. description: Multinode is the configuration for multinode components.
properties: properties:
......
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.16.4
helm.sh/resource-policy: keep
name: dynamomodels.nvidia.com
spec:
group: nvidia.com
names:
kind: DynamoModel
listKind: DynamoModelList
plural: dynamomodels
shortNames:
- dm
singular: dynamomodel
scope: Namespaced
versions:
- additionalPrinterColumns:
- description: Base model name
jsonPath: .spec.baseModelName
name: BaseModel
type: string
- description: Model type
jsonPath: .spec.modelType
name: Type
type: string
- description: Ready endpoints
jsonPath: .status.readyEndpoints
name: Ready
type: integer
- description: Total endpoints
jsonPath: .status.totalEndpoints
name: Total
type: integer
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: DynamoModel is the Schema for the dynamo models API
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: DynamoModelSpec defines the desired state of DynamoModel
properties:
baseModelName:
description: |-
BaseModelName is the base model identifier that matches the service label
This is used to discover endpoints via headless services
type: string
modelName:
description: ModelName is the full model identifier (e.g., "meta-llama/Llama-3.3-70B-Instruct-lora")
type: string
modelType:
default: base
description: ModelType specifies the type of model (e.g., "base", "lora", "adapter")
enum:
- base
- lora
- adapter
type: string
source:
description: Source specifies the model source location (only applicable for lora model type)
properties:
uri:
description: |-
URI is the model source URI
Supported formats:
- S3: s3://bucket/path/to/model
- HuggingFace: hf://org/model@revision_sha
type: string
required:
- uri
type: object
required:
- baseModelName
- modelName
type: object
status:
description: DynamoModelStatus defines the observed state of DynamoModel
properties:
conditions:
description: Conditions represents the latest available observations of the model's state
items:
description: Condition contains details for one aspect of the current state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
endpoints:
description: Endpoints is the current list of all endpoints for this model
items:
description: EndpointInfo represents a single endpoint (pod) serving the model
properties:
address:
description: Address is the full address of the endpoint (e.g., "http://10.0.1.5:9090")
type: string
podName:
description: PodName is the name of the pod serving this endpoint
type: string
ready:
description: |-
Ready indicates whether the endpoint is ready to serve traffic
For LoRA models: true if the POST /loras request succeeded with a 2xx status code
For base models: always false (no probing performed)
type: boolean
required:
- address
- ready
type: object
type: array
readyEndpoints:
description: ReadyEndpoints is the count of endpoints that are ready
type: integer
totalEndpoints:
description: TotalEndpoints is the total count of endpoints
type: integer
required:
- readyEndpoints
- totalEndpoints
type: object
type: object
served: true
storage: true
subresources:
status: {}
...@@ -30,6 +30,8 @@ metadata: ...@@ -30,6 +30,8 @@ metadata:
{{- include "dynamo-operator.labels" . | nindent 4 }} {{- include "dynamo-operator.labels" . | nindent 4 }}
spec: spec:
replicas: {{ .Values.controllerManager.replicas }} replicas: {{ .Values.controllerManager.replicas }}
strategy:
type: Recreate
selector: selector:
matchLabels: matchLabels:
control-plane: controller-manager control-plane: controller-manager
...@@ -76,12 +78,13 @@ spec: ...@@ -76,12 +78,13 @@ spec:
{{- range .Values.controllerManager.manager.args }} {{- range .Values.controllerManager.manager.args }}
- {{ . }} - {{ . }}
{{- end }} {{- end }}
{{- if .Values.namespaceRestriction.enabled }}
- --restrictedNamespace={{ default .Release.Namespace .Values.namespaceRestriction.targetNamespace }}
- --leader-elect=false
{{- else }}
- --leader-elect - --leader-elect
- --leader-election-id={{ default "dynamo.nvidia.com" .Values.controllerManager.leaderElection.id }} - --leader-election-id={{ default "dynamo.nvidia.com" .Values.controllerManager.leaderElection.id }}
{{- if .Values.namespaceRestriction.enabled }}
{{- $restrictedNs := default .Release.Namespace .Values.namespaceRestriction.targetNamespace }}
- --restrictedNamespace={{ $restrictedNs }}
- --leader-election-namespace={{ $restrictedNs }}
{{- else }}
- --leader-election-namespace={{ default "kube-system" .Values.controllerManager.leaderElection.namespace }} - --leader-election-namespace={{ default "kube-system" .Values.controllerManager.leaderElection.namespace }}
{{- end }} {{- end }}
{{- if .Values.natsAddr }} {{- if .Values.natsAddr }}
...@@ -165,4 +168,4 @@ spec: ...@@ -165,4 +168,4 @@ spec:
securityContext: securityContext:
runAsNonRoot: true runAsNonRoot: true
serviceAccountName: {{ include "dynamo-operator.fullname" . }}-controller-manager serviceAccountName: {{ include "dynamo-operator.fullname" . }}-controller-manager
terminationGracePeriodSeconds: 10 terminationGracePeriodSeconds: 30
...@@ -12,16 +12,17 @@ ...@@ -12,16 +12,17 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
{{/*
Only create leader election RBAC when leader election is enabled.
When namespaceRestriction.enabled=true, leader election is disabled (--leader-elect=false),
so these permissions are not needed.
*/}}
{{- if not .Values.namespaceRestriction.enabled }}
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.namespaceRestriction.enabled }}
kind: Role
{{- else }}
kind: ClusterRole kind: ClusterRole
{{- end }}
metadata: metadata:
name: {{ include "dynamo-operator.fullname" . }}-leader-election-role name: {{ include "dynamo-operator.fullname" . }}-leader-election-role
{{- if .Values.namespaceRestriction.enabled }}
namespace: {{ default .Release.Namespace .Values.namespaceRestriction.targetNamespace }}
{{- end }}
labels: labels:
app.kubernetes.io/component: rbac app.kubernetes.io/component: rbac
app.kubernetes.io/created-by: dynamo-operator app.kubernetes.io/created-by: dynamo-operator
...@@ -61,9 +62,16 @@ rules: ...@@ -61,9 +62,16 @@ rules:
- patch - patch
--- ---
apiVersion: rbac.authorization.k8s.io/v1 apiVersion: rbac.authorization.k8s.io/v1
{{- if .Values.namespaceRestriction.enabled }}
kind: RoleBinding
{{- else }}
kind: ClusterRoleBinding kind: ClusterRoleBinding
{{- end }}
metadata: metadata:
name: {{ include "dynamo-operator.fullname" . }}-leader-election-rolebinding name: {{ include "dynamo-operator.fullname" . }}-leader-election-rolebinding
{{- if .Values.namespaceRestriction.enabled }}
namespace: {{ default .Release.Namespace .Values.namespaceRestriction.targetNamespace }}
{{- end }}
labels: labels:
app.kubernetes.io/component: rbac app.kubernetes.io/component: rbac
app.kubernetes.io/created-by: dynamo-operator app.kubernetes.io/created-by: dynamo-operator
...@@ -71,10 +79,13 @@ metadata: ...@@ -71,10 +79,13 @@ metadata:
{{- include "dynamo-operator.labels" . | nindent 4 }} {{- include "dynamo-operator.labels" . | nindent 4 }}
roleRef: roleRef:
apiGroup: rbac.authorization.k8s.io apiGroup: rbac.authorization.k8s.io
{{- if .Values.namespaceRestriction.enabled }}
kind: Role
{{- else }}
kind: ClusterRole kind: ClusterRole
{{- end }}
name: '{{ include "dynamo-operator.fullname" . }}-leader-election-role' name: '{{ include "dynamo-operator.fullname" . }}-leader-election-role'
subjects: subjects:
- kind: ServiceAccount - kind: ServiceAccount
name: '{{ include "dynamo-operator.fullname" . }}-controller-manager' name: '{{ include "dynamo-operator.fullname" . }}-controller-manager'
namespace: '{{ .Release.Namespace }}' namespace: '{{ .Release.Namespace }}'
{{- end }} \ No newline at end of file
\ No newline at end of file
...@@ -62,6 +62,14 @@ rules: ...@@ -62,6 +62,14 @@ rules:
- patch - patch
- update - update
- watch - watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- get
- list
- watch
- apiGroups: - apiGroups:
- "" - ""
resources: resources:
...@@ -361,6 +369,7 @@ rules: ...@@ -361,6 +369,7 @@ rules:
- dynamocomponentdeployments - dynamocomponentdeployments
- dynamographdeploymentrequests - dynamographdeploymentrequests
- dynamographdeployments - dynamographdeployments
- dynamomodels
verbs: verbs:
- create - create
- delete - delete
...@@ -375,6 +384,7 @@ rules: ...@@ -375,6 +384,7 @@ rules:
- dynamocomponentdeployments/finalizers - dynamocomponentdeployments/finalizers
- dynamographdeploymentrequests/finalizers - dynamographdeploymentrequests/finalizers
- dynamographdeployments/finalizers - dynamographdeployments/finalizers
- dynamomodels/finalizers
verbs: verbs:
- update - update
- apiGroups: - apiGroups:
...@@ -383,6 +393,7 @@ rules: ...@@ -383,6 +393,7 @@ rules:
- dynamocomponentdeployments/status - dynamocomponentdeployments/status
- dynamographdeploymentrequests/status - dynamographdeploymentrequests/status
- dynamographdeployments/status - dynamographdeployments/status
- dynamomodels/status
verbs: verbs:
- get - get
- patch - patch
......
...@@ -25,6 +25,12 @@ namespaceRestriction: ...@@ -25,6 +25,12 @@ namespaceRestriction:
enabled: false enabled: false
# The target namespace to restrict to. If empty, defaults to the release namespace # The target namespace to restrict to. If empty, defaults to the release namespace
targetNamespace: "" targetNamespace: ""
# Namespace scope marker lease configuration (used to prevent conflicts when running both cluster-wide and namespace-restricted operators)
lease:
# Duration before the namespace scope marker lease expires if not renewed (namespace-restricted mode only). When a namespace-restricted operator is running, it creates a lease in its namespace. The cluster-wide operator detects this lease and excludes that namespace from processing. If the namespace operator stops renewing the lease (e.g., crashes), the lease expires and the cluster-wide operator automatically resumes processing that namespace.
duration: 30s
# Interval for renewing the namespace scope marker lease (namespace-restricted mode only). The namespace-restricted operator renews its lease at this interval to signal it's still running.
renewInterval: 10s
controllerManager: controllerManager:
tolerations: [] tolerations: []
...@@ -66,7 +72,7 @@ controllerManager: ...@@ -66,7 +72,7 @@ controllerManager:
- --health-probe-bind-address=:8081 - --health-probe-bind-address=:8081
- --metrics-bind-address=127.0.0.1:8080 - --metrics-bind-address=127.0.0.1:8080
- --leader-elect - --leader-elect
- --leader-election-id=dynamo.nko.nvidia.com - --leader-election-id=dynamo.nvidia.com
containerSecurityContext: containerSecurityContext:
allowPrivilegeEscalation: false allowPrivilegeEscalation: false
capabilities: capabilities:
......
...@@ -24,4 +24,12 @@ resources: ...@@ -24,4 +24,12 @@ resources:
kind: DynamoGraphDeployment kind: DynamoGraphDeployment
path: github.com/ai-dynamo/dynamo/deploy/cloud/operator/api/v1alpha1 path: github.com/ai-dynamo/dynamo/deploy/cloud/operator/api/v1alpha1
version: v1alpha1 version: v1alpha1
- api:
crdVersion: v1
namespaced: true
controller: true
domain: nvidia.com
kind: DynamoModel
path: github.com/ai-dynamo/dynamo/deploy/cloud/operator/api/v1alpha1
version: v1alpha1
version: "3" version: "3"
/*
* SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package v1alpha1
import (
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// DynamoModelSpec defines the desired state of DynamoModel
type DynamoModelSpec struct {
// ModelName is the full model identifier (e.g., "meta-llama/Llama-3.3-70B-Instruct-lora")
// +kubebuilder:validation:Required
ModelName string `json:"modelName"`
// BaseModelName is the base model identifier that matches the service label
// This is used to discover endpoints via headless services
// +kubebuilder:validation:Required
BaseModelName string `json:"baseModelName"`
// ModelType specifies the type of model (e.g., "base", "lora", "adapter")
// +kubebuilder:validation:Enum=base;lora;adapter
// +kubebuilder:default=base
// +optional
ModelType string `json:"modelType,omitempty"`
// Source specifies the model source location (only applicable for lora model type)
// +optional
Source *ModelSource `json:"source,omitempty"`
}
// ModelSource defines the source location of a model
type ModelSource struct {
// URI is the model source URI
// Supported formats:
// - S3: s3://bucket/path/to/model
// - HuggingFace: hf://org/model@revision_sha
// +kubebuilder:validation:Required
URI string `json:"uri"`
}
// EndpointInfo represents a single endpoint (pod) serving the model
type EndpointInfo struct {
// Address is the full address of the endpoint (e.g., "http://10.0.1.5:9090")
Address string `json:"address"`
// PodName is the name of the pod serving this endpoint
// +optional
PodName string `json:"podName,omitempty"`
// Ready indicates whether the endpoint is ready to serve traffic
// For LoRA models: true if the POST /loras request succeeded with a 2xx status code
// For base models: always false (no probing performed)
Ready bool `json:"ready"`
}
// DynamoModelStatus defines the observed state of DynamoModel
type DynamoModelStatus struct {
// Endpoints is the current list of all endpoints for this model
// +optional
Endpoints []EndpointInfo `json:"endpoints,omitempty"`
// ReadyEndpoints is the count of endpoints that are ready
ReadyEndpoints int `json:"readyEndpoints"`
// TotalEndpoints is the total count of endpoints
TotalEndpoints int `json:"totalEndpoints"`
// Conditions represents the latest available observations of the model's state
// +optional
Conditions []metav1.Condition `json:"conditions,omitempty"`
}
// +genclient
// +kubebuilder:object:root=true
// +kubebuilder:subresource:status
// +kubebuilder:storageversion
// +kubebuilder:printcolumn:name="BaseModel",type="string",JSONPath=".spec.baseModelName",description="Base model name"
// +kubebuilder:printcolumn:name="Type",type="string",JSONPath=".spec.modelType",description="Model type"
// +kubebuilder:printcolumn:name="Ready",type="integer",JSONPath=".status.readyEndpoints",description="Ready endpoints"
// +kubebuilder:printcolumn:name="Total",type="integer",JSONPath=".status.totalEndpoints",description="Total endpoints"
// +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:resource:shortName=dm
// DynamoModel is the Schema for the dynamo models API
type DynamoModel struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec DynamoModelSpec `json:"spec,omitempty"`
Status DynamoModelStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// DynamoModelList contains a list of DynamoModel
type DynamoModelList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []DynamoModel `json:"items"`
}
func init() {
SchemeBuilder.Register(&DynamoModel{}, &DynamoModelList{})
}
// IsLoRA returns true if this is a LoRA model (case-insensitive)
func (m *DynamoModel) IsLoRA() bool {
return strings.EqualFold(m.Spec.ModelType, "lora")
}
// GetReadyEndpoints returns only the endpoints that are ready
func (m *DynamoModel) GetReadyEndpoints() []EndpointInfo {
var ready []EndpointInfo
for _, ep := range m.Status.Endpoints {
if ep.Ready {
ready = append(ready, ep)
}
}
return ready
}
// HasEndpoints returns true if the model has any endpoints
func (m *DynamoModel) HasEndpoints() bool {
return len(m.Status.Endpoints) > 0
}
// HasReadyEndpoints returns true if the model has any ready endpoints
func (m *DynamoModel) HasReadyEndpoints() bool {
return m.Status.ReadyEndpoints > 0
}
...@@ -88,6 +88,11 @@ type DynamoComponentDeploymentSharedSpec struct { ...@@ -88,6 +88,11 @@ type DynamoComponentDeploymentSharedSpec struct {
// Ingress config to expose the component outside the cluster (or through a service mesh). // Ingress config to expose the component outside the cluster (or through a service mesh).
Ingress *IngressSpec `json:"ingress,omitempty"` Ingress *IngressSpec `json:"ingress,omitempty"`
// ModelRef references a model that this component serves
// When specified, a headless service will be created for endpoint discovery
// +optional
ModelRef *ModelReference `json:"modelRef,omitempty"`
// SharedMemory controls the tmpfs mounted at /dev/shm (enable/disable and size). // SharedMemory controls the tmpfs mounted at /dev/shm (enable/disable and size).
SharedMemory *SharedMemorySpec `json:"sharedMemory,omitempty"` SharedMemory *SharedMemorySpec `json:"sharedMemory,omitempty"`
...@@ -274,3 +279,14 @@ func (s *DynamoComponentDeployment) GetParentGraphDeploymentName() string { ...@@ -274,3 +279,14 @@ func (s *DynamoComponentDeployment) GetParentGraphDeploymentName() string {
func (s *DynamoComponentDeployment) GetParentGraphDeploymentNamespace() string { func (s *DynamoComponentDeployment) GetParentGraphDeploymentNamespace() string {
return s.GetNamespace() return s.GetNamespace()
} }
// ModelReference identifies a model served by this component
type ModelReference struct {
// Name is the base model identifier (e.g., "llama-3-70b-instruct-v1")
// +kubebuilder:validation:Required
Name string `json:"name"`
// Revision is the model revision/version (optional)
// +optional
Revision string `json:"revision,omitempty"`
}
...@@ -40,9 +40,9 @@ package v1alpha1 ...@@ -40,9 +40,9 @@ package v1alpha1
import ( import (
"github.com/ai-dynamo/dynamo/deploy/cloud/operator/api/dynamo/common" "github.com/ai-dynamo/dynamo/deploy/cloud/operator/api/dynamo/common"
"k8s.io/api/autoscaling/v2" "k8s.io/api/autoscaling/v2"
"k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
) )
...@@ -98,7 +98,7 @@ func (in *BaseStatus) DeepCopyInto(out *BaseStatus) { ...@@ -98,7 +98,7 @@ func (in *BaseStatus) DeepCopyInto(out *BaseStatus) {
*out = *in *out = *in
if in.Conditions != nil { if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in)) *out = make([]v1.Condition, len(*in))
for i := range *in { for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
...@@ -267,7 +267,7 @@ func (in *DynamoComponentDeploymentSharedSpec) DeepCopyInto(out *DynamoComponent ...@@ -267,7 +267,7 @@ func (in *DynamoComponentDeploymentSharedSpec) DeepCopyInto(out *DynamoComponent
} }
if in.Envs != nil { if in.Envs != nil {
in, out := &in.Envs, &out.Envs in, out := &in.Envs, &out.Envs
*out = make([]v1.EnvVar, len(*in)) *out = make([]corev1.EnvVar, len(*in))
for i := range *in { for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
...@@ -287,6 +287,11 @@ func (in *DynamoComponentDeploymentSharedSpec) DeepCopyInto(out *DynamoComponent ...@@ -287,6 +287,11 @@ func (in *DynamoComponentDeploymentSharedSpec) DeepCopyInto(out *DynamoComponent
*out = new(IngressSpec) *out = new(IngressSpec)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
if in.ModelRef != nil {
in, out := &in.ModelRef, &out.ModelRef
*out = new(ModelReference)
**out = **in
}
if in.SharedMemory != nil { if in.SharedMemory != nil {
in, out := &in.SharedMemory, &out.SharedMemory in, out := &in.SharedMemory, &out.SharedMemory
*out = new(SharedMemorySpec) *out = new(SharedMemorySpec)
...@@ -304,12 +309,12 @@ func (in *DynamoComponentDeploymentSharedSpec) DeepCopyInto(out *DynamoComponent ...@@ -304,12 +309,12 @@ func (in *DynamoComponentDeploymentSharedSpec) DeepCopyInto(out *DynamoComponent
} }
if in.LivenessProbe != nil { if in.LivenessProbe != nil {
in, out := &in.LivenessProbe, &out.LivenessProbe in, out := &in.LivenessProbe, &out.LivenessProbe
*out = new(v1.Probe) *out = new(corev1.Probe)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
if in.ReadinessProbe != nil { if in.ReadinessProbe != nil {
in, out := &in.ReadinessProbe, &out.ReadinessProbe in, out := &in.ReadinessProbe, &out.ReadinessProbe
*out = new(v1.Probe) *out = new(corev1.Probe)
(*in).DeepCopyInto(*out) (*in).DeepCopyInto(*out)
} }
if in.Replicas != nil { if in.Replicas != nil {
...@@ -355,7 +360,7 @@ func (in *DynamoComponentDeploymentStatus) DeepCopyInto(out *DynamoComponentDepl ...@@ -355,7 +360,7 @@ func (in *DynamoComponentDeploymentStatus) DeepCopyInto(out *DynamoComponentDepl
*out = *in *out = *in
if in.Conditions != nil { if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in)) *out = make([]v1.Condition, len(*in))
for i := range *in { for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
...@@ -523,7 +528,7 @@ func (in *DynamoGraphDeploymentRequestStatus) DeepCopyInto(out *DynamoGraphDeplo ...@@ -523,7 +528,7 @@ func (in *DynamoGraphDeploymentRequestStatus) DeepCopyInto(out *DynamoGraphDeplo
*out = *in *out = *in
if in.Conditions != nil { if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in)) *out = make([]v1.Condition, len(*in))
for i := range *in { for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
...@@ -578,7 +583,7 @@ func (in *DynamoGraphDeploymentSpec) DeepCopyInto(out *DynamoGraphDeploymentSpec ...@@ -578,7 +583,7 @@ func (in *DynamoGraphDeploymentSpec) DeepCopyInto(out *DynamoGraphDeploymentSpec
} }
if in.Envs != nil { if in.Envs != nil {
in, out := &in.Envs, &out.Envs in, out := &in.Envs, &out.Envs
*out = make([]v1.EnvVar, len(*in)) *out = make([]corev1.EnvVar, len(*in))
for i := range *in { for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
...@@ -600,7 +605,7 @@ func (in *DynamoGraphDeploymentStatus) DeepCopyInto(out *DynamoGraphDeploymentSt ...@@ -600,7 +605,7 @@ func (in *DynamoGraphDeploymentStatus) DeepCopyInto(out *DynamoGraphDeploymentSt
*out = *in *out = *in
if in.Conditions != nil { if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions in, out := &in.Conditions, &out.Conditions
*out = make([]metav1.Condition, len(*in)) *out = make([]v1.Condition, len(*in))
for i := range *in { for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
...@@ -617,6 +622,127 @@ func (in *DynamoGraphDeploymentStatus) DeepCopy() *DynamoGraphDeploymentStatus { ...@@ -617,6 +622,127 @@ func (in *DynamoGraphDeploymentStatus) DeepCopy() *DynamoGraphDeploymentStatus {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DynamoModel) DeepCopyInto(out *DynamoModel) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamoModel.
func (in *DynamoModel) DeepCopy() *DynamoModel {
if in == nil {
return nil
}
out := new(DynamoModel)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *DynamoModel) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DynamoModelList) DeepCopyInto(out *DynamoModelList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]DynamoModel, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamoModelList.
func (in *DynamoModelList) DeepCopy() *DynamoModelList {
if in == nil {
return nil
}
out := new(DynamoModelList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *DynamoModelList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DynamoModelSpec) DeepCopyInto(out *DynamoModelSpec) {
*out = *in
if in.Source != nil {
in, out := &in.Source, &out.Source
*out = new(ModelSource)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamoModelSpec.
func (in *DynamoModelSpec) DeepCopy() *DynamoModelSpec {
if in == nil {
return nil
}
out := new(DynamoModelSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *DynamoModelStatus) DeepCopyInto(out *DynamoModelStatus) {
*out = *in
if in.Endpoints != nil {
in, out := &in.Endpoints, &out.Endpoints
*out = make([]EndpointInfo, len(*in))
copy(*out, *in)
}
if in.Conditions != nil {
in, out := &in.Conditions, &out.Conditions
*out = make([]v1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DynamoModelStatus.
func (in *DynamoModelStatus) DeepCopy() *DynamoModelStatus {
if in == nil {
return nil
}
out := new(DynamoModelStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *EndpointInfo) DeepCopyInto(out *EndpointInfo) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EndpointInfo.
func (in *EndpointInfo) DeepCopy() *EndpointInfo {
if in == nil {
return nil
}
out := new(EndpointInfo)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *IngressSpec) DeepCopyInto(out *IngressSpec) { func (in *IngressSpec) DeepCopyInto(out *IngressSpec) {
*out = *in *out = *in
...@@ -686,6 +812,36 @@ func (in *IngressTLSSpec) DeepCopy() *IngressTLSSpec { ...@@ -686,6 +812,36 @@ func (in *IngressTLSSpec) DeepCopy() *IngressTLSSpec {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ModelReference) DeepCopyInto(out *ModelReference) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModelReference.
func (in *ModelReference) DeepCopy() *ModelReference {
if in == nil {
return nil
}
out := new(ModelReference)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ModelSource) DeepCopyInto(out *ModelSource) {
*out = *in
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ModelSource.
func (in *ModelSource) DeepCopy() *ModelSource {
if in == nil {
return nil
}
out := new(ModelSource)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *MultinodeSpec) DeepCopyInto(out *MultinodeSpec) { func (in *MultinodeSpec) DeepCopyInto(out *MultinodeSpec) {
*out = *in *out = *in
......
...@@ -60,6 +60,7 @@ import ( ...@@ -60,6 +60,7 @@ import (
"github.com/ai-dynamo/dynamo/deploy/cloud/operator/internal/controller" "github.com/ai-dynamo/dynamo/deploy/cloud/operator/internal/controller"
commonController "github.com/ai-dynamo/dynamo/deploy/cloud/operator/internal/controller_common" commonController "github.com/ai-dynamo/dynamo/deploy/cloud/operator/internal/controller_common"
"github.com/ai-dynamo/dynamo/deploy/cloud/operator/internal/etcd" "github.com/ai-dynamo/dynamo/deploy/cloud/operator/internal/etcd"
"github.com/ai-dynamo/dynamo/deploy/cloud/operator/internal/modelendpoint"
"github.com/ai-dynamo/dynamo/deploy/cloud/operator/internal/namespace_scope" "github.com/ai-dynamo/dynamo/deploy/cloud/operator/internal/namespace_scope"
"github.com/ai-dynamo/dynamo/deploy/cloud/operator/internal/rbac" "github.com/ai-dynamo/dynamo/deploy/cloud/operator/internal/rbac"
"github.com/ai-dynamo/dynamo/deploy/cloud/operator/internal/secret" "github.com/ai-dynamo/dynamo/deploy/cloud/operator/internal/secret"
...@@ -559,6 +560,15 @@ func main() { ...@@ -559,6 +560,15 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "DynamoGraphDeploymentRequest") setupLog.Error(err, "unable to create controller", "controller", "DynamoGraphDeploymentRequest")
os.Exit(1) os.Exit(1)
} }
if err = (&controller.DynamoModelReconciler{
Client: mgr.GetClient(),
Recorder: mgr.GetEventRecorderFor("dynamomodel"),
EndpointClient: modelendpoint.NewClient(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "DynamoModel")
os.Exit(1)
}
//+kubebuilder:scaffold:builder //+kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
......
...@@ -10007,6 +10007,20 @@ spec: ...@@ -10007,6 +10007,20 @@ spec:
format: int32 format: int32
type: integer type: integer
type: object type: object
modelRef:
description: |-
ModelRef references a model that this component serves
When specified, a headless service will be created for endpoint discovery
properties:
name:
description: Name is the base model identifier (e.g., "llama-3-70b-instruct-v1")
type: string
revision:
description: Revision is the model revision/version (optional)
type: string
required:
- name
type: object
multinode: multinode:
description: Multinode is the configuration for multinode components. description: Multinode is the configuration for multinode components.
properties: properties:
......
...@@ -10142,6 +10142,20 @@ spec: ...@@ -10142,6 +10142,20 @@ spec:
format: int32 format: int32
type: integer type: integer
type: object type: object
modelRef:
description: |-
ModelRef references a model that this component serves
When specified, a headless service will be created for endpoint discovery
properties:
name:
description: Name is the base model identifier (e.g., "llama-3-70b-instruct-v1")
type: string
revision:
description: Revision is the model revision/version (optional)
type: string
required:
- name
type: object
multinode: multinode:
description: Multinode is the configuration for multinode components. description: Multinode is the configuration for multinode components.
properties: properties:
......
# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
---
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
annotations:
controller-gen.kubebuilder.io/version: v0.16.4
helm.sh/resource-policy: keep
name: dynamomodels.nvidia.com
spec:
group: nvidia.com
names:
kind: DynamoModel
listKind: DynamoModelList
plural: dynamomodels
shortNames:
- dm
singular: dynamomodel
scope: Namespaced
versions:
- additionalPrinterColumns:
- description: Base model name
jsonPath: .spec.baseModelName
name: BaseModel
type: string
- description: Model type
jsonPath: .spec.modelType
name: Type
type: string
- description: Ready endpoints
jsonPath: .status.readyEndpoints
name: Ready
type: integer
- description: Total endpoints
jsonPath: .status.totalEndpoints
name: Total
type: integer
- jsonPath: .metadata.creationTimestamp
name: Age
type: date
name: v1alpha1
schema:
openAPIV3Schema:
description: DynamoModel is the Schema for the dynamo models API
properties:
apiVersion:
description: |-
APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
type: string
kind:
description: |-
Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
type: string
metadata:
type: object
spec:
description: DynamoModelSpec defines the desired state of DynamoModel
properties:
baseModelName:
description: |-
BaseModelName is the base model identifier that matches the service label
This is used to discover endpoints via headless services
type: string
modelName:
description: ModelName is the full model identifier (e.g., "meta-llama/Llama-3.3-70B-Instruct-lora")
type: string
modelType:
default: base
description: ModelType specifies the type of model (e.g., "base", "lora", "adapter")
enum:
- base
- lora
- adapter
type: string
source:
description: Source specifies the model source location (only applicable for lora model type)
properties:
uri:
description: |-
URI is the model source URI
Supported formats:
- S3: s3://bucket/path/to/model
- HuggingFace: hf://org/model@revision_sha
type: string
required:
- uri
type: object
required:
- baseModelName
- modelName
type: object
status:
description: DynamoModelStatus defines the observed state of DynamoModel
properties:
conditions:
description: Conditions represents the latest available observations of the model's state
items:
description: Condition contains details for one aspect of the current state of this API Resource.
properties:
lastTransitionTime:
description: |-
lastTransitionTime is the last time the condition transitioned from one status to another.
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
format: date-time
type: string
message:
description: |-
message is a human readable message indicating details about the transition.
This may be an empty string.
maxLength: 32768
type: string
observedGeneration:
description: |-
observedGeneration represents the .metadata.generation that the condition was set based upon.
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
with respect to the current state of the instance.
format: int64
minimum: 0
type: integer
reason:
description: |-
reason contains a programmatic identifier indicating the reason for the condition's last transition.
Producers of specific condition types may define expected values and meanings for this field,
and whether the values are considered a guaranteed API.
The value should be a CamelCase string.
This field may not be empty.
maxLength: 1024
minLength: 1
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
type: string
status:
description: status of the condition, one of True, False, Unknown.
enum:
- "True"
- "False"
- Unknown
type: string
type:
description: type of condition in CamelCase or in foo.example.com/CamelCase.
maxLength: 316
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
type: string
required:
- lastTransitionTime
- message
- reason
- status
- type
type: object
type: array
endpoints:
description: Endpoints is the current list of all endpoints for this model
items:
description: EndpointInfo represents a single endpoint (pod) serving the model
properties:
address:
description: Address is the full address of the endpoint (e.g., "http://10.0.1.5:9090")
type: string
podName:
description: PodName is the name of the pod serving this endpoint
type: string
ready:
description: |-
Ready indicates whether the endpoint is ready to serve traffic
For LoRA models: true if the POST /loras request succeeded with a 2xx status code
For base models: always false (no probing performed)
type: boolean
required:
- address
- ready
type: object
type: array
readyEndpoints:
description: ReadyEndpoints is the count of endpoints that are ready
type: integer
totalEndpoints:
description: TotalEndpoints is the total count of endpoints
type: integer
required:
- readyEndpoints
- totalEndpoints
type: object
type: object
served: true
storage: true
subresources:
status: {}
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
resources: resources:
- bases/nvidia.com_dynamocomponentdeployments.yaml - bases/nvidia.com_dynamocomponentdeployments.yaml
- bases/nvidia.com_dynamographdeployments.yaml - bases/nvidia.com_dynamographdeployments.yaml
- bases/nvidia.com_dynamomodels.yaml
#+kubebuilder:scaffold:crdkustomizeresource #+kubebuilder:scaffold:crdkustomizeresource
patches: [] patches: []
......
...@@ -103,4 +103,4 @@ spec: ...@@ -103,4 +103,4 @@ spec:
cpu: 1024m cpu: 1024m
memory: 1Gi memory: 1Gi
serviceAccountName: controller-manager serviceAccountName: controller-manager
terminationGracePeriodSeconds: 10 terminationGracePeriodSeconds: 30
...@@ -98,6 +98,14 @@ rules: ...@@ -98,6 +98,14 @@ rules:
- patch - patch
- update - update
- watch - watch
- apiGroups:
- discovery.k8s.io
resources:
- endpointslices
verbs:
- get
- list
- watch
- apiGroups: - apiGroups:
- events.k8s.io - events.k8s.io
resources: resources:
...@@ -174,6 +182,7 @@ rules: ...@@ -174,6 +182,7 @@ rules:
- dynamocomponentdeployments - dynamocomponentdeployments
- dynamographdeploymentrequests - dynamographdeploymentrequests
- dynamographdeployments - dynamographdeployments
- dynamomodels
verbs: verbs:
- create - create
- delete - delete
...@@ -188,6 +197,7 @@ rules: ...@@ -188,6 +197,7 @@ rules:
- dynamocomponentdeployments/finalizers - dynamocomponentdeployments/finalizers
- dynamographdeploymentrequests/finalizers - dynamographdeploymentrequests/finalizers
- dynamographdeployments/finalizers - dynamographdeployments/finalizers
- dynamomodels/finalizers
verbs: verbs:
- update - update
- apiGroups: - apiGroups:
...@@ -196,6 +206,7 @@ rules: ...@@ -196,6 +206,7 @@ rules:
- dynamocomponentdeployments/status - dynamocomponentdeployments/status
- dynamographdeploymentrequests/status - dynamographdeploymentrequests/status
- dynamographdeployments/status - dynamographdeployments/status
- dynamomodels/status
verbs: verbs:
- get - get
- patch - patch
......
...@@ -19,4 +19,5 @@ resources: ...@@ -19,4 +19,5 @@ resources:
- nvidia.com_v1alpha1_dynamocomponent.yaml - nvidia.com_v1alpha1_dynamocomponent.yaml
- nvidia.com_v1alpha1_dynamographdeployment.yaml - nvidia.com_v1alpha1_dynamographdeployment.yaml
- nvidia.com_v1alpha1_dynamographdeploymentrequest.yaml - nvidia.com_v1alpha1_dynamographdeploymentrequest.yaml
- nvidia.com_v1alpha1_dynamomodel.yaml
#+kubebuilder:scaffold:manifestskustomizesamples #+kubebuilder:scaffold:manifestskustomizesamples
...@@ -38,6 +38,9 @@ const ( ...@@ -38,6 +38,9 @@ const (
KubeLabelDynamoDeploymentTargetType = "nvidia.com/dynamo-deployment-target-type" KubeLabelDynamoDeploymentTargetType = "nvidia.com/dynamo-deployment-target-type"
KubeLabelDynamoComponentType = "nvidia.com/dynamo-component-type" KubeLabelDynamoComponentType = "nvidia.com/dynamo-component-type"
KubeLabelDynamoSubComponentType = "nvidia.com/dynamo-sub-component-type" KubeLabelDynamoSubComponentType = "nvidia.com/dynamo-sub-component-type"
KubeLabelDynamoBaseModel = "nvidia.com/dynamo-base-model"
KubeLabelDynamoBaseModelHash = "nvidia.com/dynamo-base-model-hash"
KubeAnnotationDynamoBaseModel = "nvidia.com/dynamo-base-model"
KubeLabelValueFalse = "false" KubeLabelValueFalse = "false"
KubeLabelValueTrue = "true" KubeLabelValueTrue = "true"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment