Skip to content

Commit

Permalink
feat: integrating NIM model UI
Browse files Browse the repository at this point in the history
Signed-off-by: Olga Lavtar <[email protected]>
  • Loading branch information
olavtar committed Sep 13, 2024
1 parent d0b5ce9 commit 5bdae2a
Show file tree
Hide file tree
Showing 14 changed files with 815 additions and 24 deletions.
12 changes: 10 additions & 2 deletions frontend/src/api/k8s/__tests__/inferenceServices.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import {
K8sStatus,
k8sCreateResource,
k8sDeleteResource,
k8sGetResource,
k8sListResource,
K8sStatus,
k8sUpdateResource,
} from '@openshift/dynamic-plugin-sdk-utils';
import { mockAcceleratorProfile } from '~/__mocks__/mockAcceleratorProfile';
Expand All @@ -28,7 +28,9 @@ import { InferenceServiceKind, ProjectKind } from '~/k8sTypes';
import { translateDisplayNameForK8s } from '~/concepts/k8s/utils';
import { ModelServingSize } from '~/pages/modelServing/screens/types';
import { AcceleratorProfileState } from '~/utilities/useAcceleratorProfileState';
import { AcceleratorProfileSelectFieldState } from '~/pages/notebookController/screens/server/AcceleratorProfileSelectField';
import {
AcceleratorProfileSelectFieldState,
} from '~/pages/notebookController/screens/server/AcceleratorProfileSelectField';

jest.mock('@openshift/dynamic-plugin-sdk-utils', () => ({
k8sListResource: jest.fn(),
Expand Down Expand Up @@ -186,6 +188,7 @@ describe('assembleInferenceService', () => {
undefined,
false,
undefined,
undefined,
acceleratorProfileState,
selectedAcceleratorProfile,
);
Expand Down Expand Up @@ -218,6 +221,7 @@ describe('assembleInferenceService', () => {
undefined,
true,
undefined,
undefined,
acceleratorProfileState,
selectedAcceleratorProfile,
);
Expand Down Expand Up @@ -251,6 +255,7 @@ describe('assembleInferenceService', () => {
undefined,
false,
undefined,
undefined,
acceleratorProfileState,
selectedAcceleratorProfile,
);
Expand Down Expand Up @@ -279,6 +284,7 @@ describe('assembleInferenceService', () => {
undefined,
true,
undefined,
undefined,
acceleratorProfileState,
selectedAcceleratorProfile,
);
Expand Down Expand Up @@ -321,6 +327,7 @@ describe('assembleInferenceService', () => {
undefined,
false,
undefined,
undefined,
acceleratorProfileState,
selectedAcceleratorProfile,
);
Expand Down Expand Up @@ -373,6 +380,7 @@ describe('assembleInferenceService', () => {
undefined,
true,
undefined,
undefined,
acceleratorProfileState,
selectedAcceleratorProfile,
);
Expand Down
16 changes: 14 additions & 2 deletions frontend/src/api/k8s/inferenceServices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import {
k8sDeleteResource,
k8sGetResource,
k8sListResource,
k8sUpdateResource,
K8sStatus,
k8sUpdateResource,
} from '@openshift/dynamic-plugin-sdk-utils';
import { InferenceServiceModel } from '~/api/models';
import { InferenceServiceKind, K8sAPIOptions, KnownLabels } from '~/k8sTypes';
Expand All @@ -14,7 +14,9 @@ import { translateDisplayNameForK8s } from '~/concepts/k8s/utils';
import { applyK8sAPIOptions } from '~/api/apiMergeUtils';
import { AcceleratorProfileState } from '~/utilities/useAcceleratorProfileState';
import { ContainerResources } from '~/types';
import { AcceleratorProfileSelectFieldState } from '~/pages/notebookController/screens/server/AcceleratorProfileSelectField';
import {
AcceleratorProfileSelectFieldState,
} from '~/pages/notebookController/screens/server/AcceleratorProfileSelectField';
import { getModelServingProjects } from './projects';
import { assemblePodSpecOptions } from './utils';

Expand All @@ -24,6 +26,7 @@ export const assembleInferenceService = (
editName?: string,
isModelMesh?: boolean,
inferenceService?: InferenceServiceKind,
isStorageNeeded?: boolean,
initialAcceleratorProfile?: AcceleratorProfileState,
selectedAcceleratorProfile?: AcceleratorProfileSelectFieldState,
): InferenceServiceKind => {
Expand Down Expand Up @@ -162,6 +165,11 @@ export const assembleInferenceService = (
};
}

// If storage is not needed, remove storage from the inference service
if (isStorageNeeded !== undefined && !isStorageNeeded) {
delete updateInferenceService.spec.predictor.model?.storage;
}

return updateInferenceService;
};

Expand Down Expand Up @@ -234,13 +242,15 @@ export const createInferenceService = (
initialAcceleratorProfile?: AcceleratorProfileState,
selectedAcceleratorProfile?: AcceleratorProfileSelectFieldState,
dryRun = false,
isStorageNeeded?: boolean,
): Promise<InferenceServiceKind> => {
const inferenceService = assembleInferenceService(
data,
secretKey,
undefined,
isModelMesh,
undefined,
isStorageNeeded,
initialAcceleratorProfile,
selectedAcceleratorProfile,
);
Expand All @@ -263,13 +273,15 @@ export const updateInferenceService = (
initialAcceleratorProfile?: AcceleratorProfileState,
selectedAcceleratorProfile?: AcceleratorProfileSelectFieldState,
dryRun = false,
isStorageNeeded?: boolean,
): Promise<InferenceServiceKind> => {
const inferenceService = assembleInferenceService(
data,
secretKey,
existingData.metadata.name,
isModelMesh,
existingData,
isStorageNeeded,
initialAcceleratorProfile,
selectedAcceleratorProfile,
);
Expand Down
41 changes: 31 additions & 10 deletions frontend/src/api/k8s/servingRuntimes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,16 @@ import {
k8sUpdateResource,
} from '@openshift/dynamic-plugin-sdk-utils';
import { ServingRuntimeModel } from '~/api/models';
import {
K8sAPIOptions,
ServingContainer,
ServingRuntimeAnnotations,
ServingRuntimeKind,
} from '~/k8sTypes';
import { CreatingServingRuntimeObject } from '~/pages/modelServing/screens/types';
import { K8sAPIOptions, ServingContainer, ServingRuntimeAnnotations, ServingRuntimeKind } from '~/k8sTypes';
import { CreatingServingRuntimeObject, SupportedModelFormatsInfo } from '~/pages/modelServing/screens/types';
import { ContainerResources } from '~/types';
import { getModelServingRuntimeName } from '~/pages/modelServing/utils';
import { getDisplayNameFromK8sResource, translateDisplayNameForK8s } from '~/concepts/k8s/utils';
import { applyK8sAPIOptions } from '~/api/apiMergeUtils';
import { AcceleratorProfileState } from '~/utilities/useAcceleratorProfileState';
import { AcceleratorProfileSelectFieldState } from '~/pages/notebookController/screens/server/AcceleratorProfileSelectField';
import {
AcceleratorProfileSelectFieldState,
} from '~/pages/notebookController/screens/server/AcceleratorProfileSelectField';
import { getModelServingProjects } from './projects';
import { assemblePodSpecOptions, getshmVolume, getshmVolumeMount } from './utils';

Expand All @@ -33,7 +30,15 @@ export const assembleServingRuntime = (
selectedAcceleratorProfile?: AcceleratorProfileSelectFieldState,
isModelMesh?: boolean,
): ServingRuntimeKind => {
const { name: displayName, numReplicas, modelSize, externalRoute, tokenAuth } = data;
const {
name: displayName,
numReplicas,
modelSize,
externalRoute,
tokenAuth,
imageName,
supportedModelFormatsInfo,
} = data;
const createName = isCustomServingRuntimesEnabled
? translateDisplayNameForK8s(displayName)
: getModelServingRuntimeName(namespace);
Expand Down Expand Up @@ -123,7 +128,12 @@ export const assembleServingRuntime = (
volumeMounts.push(getshmVolumeMount());
}

const containerWithoutResources = _.omit(container, 'resources');
const updatedContainer = {
...container,
...(imageName && { image: imageName }),
};

const containerWithoutResources = _.omit(updatedContainer, 'resources');

return {
...containerWithoutResources,
Expand All @@ -134,6 +144,17 @@ export const assembleServingRuntime = (
},
);

if (supportedModelFormatsInfo) {
const supportedModelFormatsObj: SupportedModelFormatsInfo = {
name: supportedModelFormatsInfo.name,
version: supportedModelFormatsInfo.version,
autoSelect: true,
priority: 1,
};

updatedServingRuntime.spec.supportedModelFormats = [supportedModelFormatsObj];
}

if (isModelMesh) {
updatedServingRuntime.spec.tolerations = tolerations;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import * as React from 'react';
import {
Bullseye,
Card,
CardBody,
CardFooter,
CardTitle,
Text,
TextContent,
TextVariants,
} from '@patternfly/react-core';
import { ProjectDetailsContext } from '~/pages/projects/ProjectDetailsContext';
import { ServingRuntimePlatform } from '~/types';
import {
getSortedTemplates,
getTemplateEnabled,
getTemplateEnabledForPlatform,
} from '~/pages/modelServing/customServingRuntimes/utils';
import ModelServingPlatformButtonAction from '~/pages/modelServing/screens/projects/ModelServingPlatformButtonAction';
import DeployNIMServiceModal from './NIMServiceModal/DeployNIMServiceModal';

const EmptyNIMModelServingCard: React.FC = () => {
const {
dataConnections: { data: dataConnections },
} = React.useContext(ProjectDetailsContext);
const [open, setOpen] = React.useState(false);

const {
servingRuntimes: { refresh: refreshServingRuntime },
servingRuntimeTemplates: [templates],
servingRuntimeTemplateOrder: { data: templateOrder },
servingRuntimeTemplateDisablement: { data: templateDisablement },
serverSecrets: { refresh: refreshTokens },
inferenceServices: { refresh: refreshInferenceServices },
currentProject,
} = React.useContext(ProjectDetailsContext);

const onSubmit = (submit: boolean) => {
if (submit) {
refreshServingRuntime();
refreshInferenceServices();
setTimeout(refreshTokens, 500); // need a timeout to wait for tokens creation
}
};

const templatesSorted = getSortedTemplates(templates, templateOrder);
const templatesEnabled = templatesSorted.filter((template) =>
getTemplateEnabled(template, templateDisablement),
);
const emptyTemplates = templatesEnabled.length === 0;

return (
<>
<Card
style={{
height: '100%',
border: '1px solid var(--pf-v5-global--BorderColor--100)',
borderRadius: 16,
}}
data-testid="nvidia-nim-model-serving-platform-card"
>
<CardTitle>
<TextContent>
<Text component={TextVariants.h2}>NVIDIA NIM model serving platform</Text>
</TextContent>
</CardTitle>
<CardBody>
Models are deployed using NVIDIA NIM microservices. Choose this option when you want to
deploy your model within a NIM container. Please provide the API key to authenticate with
the NIM service.
</CardBody>
<CardFooter>
<Bullseye>
<ModelServingPlatformButtonAction
isProjectModelMesh={false}
emptyTemplates={emptyTemplates}
onClick={() => setOpen(true)}
variant="secondary"
testId="nim-serving-deploy-button"
/>
</Bullseye>
</CardFooter>
</Card>
<DeployNIMServiceModal
isOpen={open}
projectContext={{
currentProject,
dataConnections,
}}
servingRuntimeTemplates={templatesEnabled.filter((template) =>
getTemplateEnabledForPlatform(template, ServingRuntimePlatform.SINGLE),
)}
onClose={(submit) => {
onSubmit(submit);
setOpen(false);
}}
/>
</>
);
};

export default EmptyNIMModelServingCard;
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import {
} from '~/pages/modelServing/customServingRuntimes/utils';
import { ServingRuntimePlatform } from '~/types';
import { getProjectModelServingPlatform } from '~/pages/modelServing/screens/projects/utils';
import KServeInferenceServiceTable from '~/pages/modelServing/screens/projects/KServeSection/KServeInferenceServiceTable';
import KServeInferenceServiceTable
from '~/pages/modelServing/screens/projects/KServeSection/KServeInferenceServiceTable';
import useServingPlatformStatuses from '~/pages/modelServing/useServingPlatformStatuses';
import DashboardPopupIconButton from '~/concepts/dashboard/DashboardPopupIconButton';
import DetailsSection from '~/pages/projects/screens/detail/DetailsSection';
Expand All @@ -32,6 +33,7 @@ import EmptySingleModelServingCard from '~/pages/modelServing/screens/projects/E
import EmptyMultiModelServingCard from '~/pages/modelServing/screens/projects/EmptyMultiModelServingCard';
import { ProjectObjectType, typedEmptyImage } from '~/concepts/design/utils';
import EmptyModelServingPlatform from '~/pages/modelServing/screens/projects/EmptyModelServingPlatform';
import EmptyNIMModelServingCard from '~/pages/modelServing/screens/projects/EmptyNIMModelServingCard';
import ManageServingRuntimeModal from './ServingRuntimeModal/ManageServingRuntimeModal';
import ModelMeshServingRuntimeTable from './ModelMeshSection/ServingRuntimeTable';
import ModelServingPlatformButtonAction from './ModelServingPlatformButtonAction';
Expand Down Expand Up @@ -197,6 +199,9 @@ const ModelServingPlatform: React.FC = () => {
<GalleryItem>
<EmptyMultiModelServingCard />
</GalleryItem>
<GalleryItem>
<EmptyNIMModelServingCard />
</GalleryItem>
</Gallery>
</StackItem>
<StackItem>
Expand Down
Loading

0 comments on commit 5bdae2a

Please sign in to comment.