diff --git a/frontend/src/concepts/areas/const.ts b/frontend/src/concepts/areas/const.ts index 55765f87fe..8790cc76c7 100644 --- a/frontend/src/concepts/areas/const.ts +++ b/frontend/src/concepts/areas/const.ts @@ -122,5 +122,6 @@ export const SupportedAreasStateMap: SupportedAreasState = { }, [SupportedArea.NIM_MODEL]: { featureFlags: ['disableNIMModelServing'], + reliantAreas: [SupportedArea.K_SERVE], }, }; diff --git a/frontend/src/pages/modelServing/screens/global/EmptyModelServing.tsx b/frontend/src/pages/modelServing/screens/global/EmptyModelServing.tsx index 510b20d254..89af0d014f 100644 --- a/frontend/src/pages/modelServing/screens/global/EmptyModelServing.tsx +++ b/frontend/src/pages/modelServing/screens/global/EmptyModelServing.tsx @@ -2,15 +2,12 @@ import * as React from 'react'; import { Button } from '@patternfly/react-core'; import { useNavigate } from 'react-router-dom'; import { ModelServingContext } from '~/pages/modelServing/ModelServingContext'; -import { getProjectModelServingPlatform } from '~/pages/modelServing/screens/projects/utils'; -import { ServingRuntimePlatform } from '~/types'; import useServingPlatformStatuses from '~/pages/modelServing/useServingPlatformStatuses'; import EmptyDetailsView from '~/components/EmptyDetailsView'; import { ProjectObjectType, typedEmptyImage } from '~/concepts/design/utils'; import { ProjectSectionID } from '~/pages/projects/screens/detail/types'; import ServeModelButton from '~/pages/modelServing/screens/global/ServeModelButton'; import { getDisplayNameFromK8sResource } from '~/concepts/k8s/utils'; -import { isProjectNIMSupported } from '~/pages/modelServing/screens/projects/nimUtils'; const EmptyModelServing: React.FC = () => { const navigate = useNavigate(); @@ -19,14 +16,9 @@ const EmptyModelServing: React.FC = () => { project, } = React.useContext(ModelServingContext); const servingPlatformStatuses = useServingPlatformStatuses(); - const isKServeNIMEnabled = project ? isProjectNIMSupported(project) : false; - if ( - (getProjectModelServingPlatform(project, servingPlatformStatuses).platform !== - ServingRuntimePlatform.SINGLE || - isKServeNIMEnabled) && - servingRuntimes.length === 0 - ) { + if (servingPlatformStatuses.modelMesh.enabled && servingRuntimes.length === 0) { + // Server needed -- must deploy from the project return ( { const deployingFromRegistry = !!(modelRegistryName && registeredModelId && modelVersionId); const servingPlatformStatuses = useServingPlatformStatuses(); - const { - kServe: { enabled: kServeEnabled }, - modelMesh: { enabled: modelMeshEnabled }, - nim: { available: isNIMAvailable }, - numServingPlatformsAvailable, - } = servingPlatformStatuses; + const kServeEnabled = servingPlatformStatuses.kServe.enabled; + const isNIMAvailable = servingPlatformStatuses.kServeNIM.enabled; + const modelMeshEnabled = servingPlatformStatuses.modelMesh.enabled; const { servingRuntimes: { @@ -100,8 +97,7 @@ const ModelServingPlatform: React.FC = () => { getProjectModelServingPlatform(currentProject, servingPlatformStatuses); const shouldShowPlatformSelection = - ((kServeEnabled && modelMeshEnabled) || (!kServeEnabled && !modelMeshEnabled)) && - !currentProjectServingPlatform; + servingPlatformStatuses.platformEnabledCount !== 1 && !currentProjectServingPlatform; const isProjectModelMesh = currentProjectServingPlatform === ServingRuntimePlatform.MULTI; @@ -259,7 +255,7 @@ const ModelServingPlatform: React.FC = () => { isEmpty={shouldShowPlatformSelection} loadError={platformError || servingRuntimeError || templateError} emptyState={ - kServeEnabled && modelMeshEnabled ? ( + servingPlatformStatuses.platformEnabledCount > 1 ? ( { - - - - - - + {kServeEnabled && ( + + + + )} + {modelMeshEnabled && ( + + + + )} {isNIMAvailable && ( { ? 'Multi-model serving enabled' : 'Single-model serving enabled'} - {emptyModelServer && numServingPlatformsAvailable > 1 && ( + {emptyModelServer && servingPlatformStatuses.platformEnabledCount > 1 && ( ({ kServe: { enabled: kServeEnabled, installed: kServeInstalled, }, + kServeNIM: { + enabled: nimEnabled, + installed: nimInstalled, + }, modelMesh: { enabled: modelMeshEnabled, installed: modelMeshInstalled, }, - nim: { - available: nimAvailable, - }, - numServingPlatformsAvailable: [kServeEnabled, modelMeshEnabled, nimAvailable].filter(Boolean) - .length, + platformEnabledCount: [kServeEnabled, nimEnabled, modelMeshEnabled].filter(Boolean).length, }); describe('getProjectModelServingPlatform', () => { diff --git a/frontend/src/pages/modelServing/screens/projects/useIsNIMAvailable.ts b/frontend/src/pages/modelServing/screens/projects/useIsNIMAvailable.ts index 244e55b466..bceb64b93f 100644 --- a/frontend/src/pages/modelServing/screens/projects/useIsNIMAvailable.ts +++ b/frontend/src/pages/modelServing/screens/projects/useIsNIMAvailable.ts @@ -1,8 +1,10 @@ import { useEffect, useState } from 'react'; import { SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; import { isNIMServingRuntimeTemplateAvailable } from '~/pages/modelServing/screens/projects/nimUtils'; +import { useDashboardNamespace } from '~/redux/selectors'; -export const useIsNIMAvailable = (dashboardNamespace: string): boolean => { +export const useIsNIMAvailable = (): boolean => { + const { dashboardNamespace } = useDashboardNamespace(); const [isNIMAvailable, setIsNIMAvailable] = useState(false); const isNIMModelServingAvailable = useIsAreaAvailable(SupportedArea.NIM_MODEL).status; diff --git a/frontend/src/pages/modelServing/screens/projects/utils.ts b/frontend/src/pages/modelServing/screens/projects/utils.ts index f9fab1dc14..ee7088ecc5 100644 --- a/frontend/src/pages/modelServing/screens/projects/utils.ts +++ b/frontend/src/pages/modelServing/screens/projects/utils.ts @@ -298,13 +298,19 @@ export const getProjectModelServingPlatform = ( ): { platform?: ServingRuntimePlatform; error?: Error } => { const { kServe: { enabled: kServeEnabled, installed: kServeInstalled }, + kServeNIM: { enabled: nimEnabled }, modelMesh: { enabled: modelMeshEnabled, installed: modelMeshInstalled }, + platformEnabledCount, } = platformStatuses; + if (!project) { + // Likely temporary or a bad usage of the hook return {}; } + if (project.metadata.labels?.[KnownLabels.MODEL_SERVING_PROJECT] === undefined) { - if ((kServeEnabled && modelMeshEnabled) || (!kServeEnabled && !modelMeshEnabled)) { + // Auto-select logic + if (platformEnabledCount !== 1) { return {}; } if (modelMeshEnabled) { @@ -313,13 +319,21 @@ export const getProjectModelServingPlatform = ( if (kServeEnabled) { return { platform: ServingRuntimePlatform.SINGLE }; } - } - if (project.metadata.labels?.[KnownLabels.MODEL_SERVING_PROJECT] === 'true') { + if (nimEnabled) { + // TODO: this is weird, it relies on KServe today... so it's never "only installed" + return { platform: ServingRuntimePlatform.SINGLE }; + } + + // TODO: unreachable code unless adding a new platform? probably should throw an error + } else if (project.metadata.labels[KnownLabels.MODEL_SERVING_PROJECT] === 'true') { + // Model mesh logic return { platform: ServingRuntimePlatform.MULTI, error: modelMeshInstalled ? undefined : new Error('Multi-model platform is not installed'), }; } + + // KServe logic return { platform: ServingRuntimePlatform.SINGLE, error: kServeInstalled ? undefined : new Error('Single-model platform is not installed'), diff --git a/frontend/src/pages/modelServing/screens/types.ts b/frontend/src/pages/modelServing/screens/types.ts index f22d2b6088..d309e69790 100644 --- a/frontend/src/pages/modelServing/screens/types.ts +++ b/frontend/src/pages/modelServing/screens/types.ts @@ -108,19 +108,15 @@ export type ServingRuntimeEditInfo = { secrets: SecretKind[]; }; +type PlatformStatus = { + enabled: boolean; + installed: boolean; +}; export type ServingPlatformStatuses = { - kServe: { - enabled: boolean; - installed: boolean; - }; - modelMesh: { - enabled: boolean; - installed: boolean; - }; - nim: { - available: boolean; - }; - numServingPlatformsAvailable: number; + kServe: PlatformStatus; + kServeNIM: PlatformStatus; + modelMesh: PlatformStatus; + platformEnabledCount: number; }; export type LabeledDataConnection = { diff --git a/frontend/src/pages/modelServing/useServingPlatformStatuses.ts b/frontend/src/pages/modelServing/useServingPlatformStatuses.ts index e7c8c421a3..369034c71b 100644 --- a/frontend/src/pages/modelServing/useServingPlatformStatuses.ts +++ b/frontend/src/pages/modelServing/useServingPlatformStatuses.ts @@ -1,33 +1,31 @@ import { StackComponent, SupportedArea, useIsAreaAvailable } from '~/concepts/areas'; import { ServingPlatformStatuses } from '~/pages/modelServing/screens/types'; -import { useDashboardNamespace } from '~/redux/selectors'; import { useIsNIMAvailable } from '~/pages/modelServing/screens/projects/useIsNIMAvailable'; const useServingPlatformStatuses = (): ServingPlatformStatuses => { - const { dashboardNamespace } = useDashboardNamespace(); - const kServeStatus = useIsAreaAvailable(SupportedArea.K_SERVE); const modelMeshStatus = useIsAreaAvailable(SupportedArea.MODEL_MESH); const kServeEnabled = kServeStatus.status; const modelMeshEnabled = modelMeshStatus.status; const kServeInstalled = !!kServeStatus.requiredComponents?.[StackComponent.K_SERVE]; const modelMeshInstalled = !!modelMeshStatus.requiredComponents?.[StackComponent.MODEL_MESH]; - const isNIMAvailable = useIsNIMAvailable(dashboardNamespace); + + const isNIMAvailable = useIsNIMAvailable(); return { kServe: { enabled: kServeEnabled, installed: kServeInstalled, }, + kServeNIM: { + enabled: isNIMAvailable, + installed: kServeInstalled, + }, modelMesh: { enabled: modelMeshEnabled, installed: modelMeshInstalled, }, - nim: { - available: isNIMAvailable, - }, - numServingPlatformsAvailable: [kServeEnabled, modelMeshEnabled, isNIMAvailable].filter(Boolean) - .length, + platformEnabledCount: [kServeEnabled, isNIMAvailable, modelMeshEnabled].filter(Boolean).length, }; }; diff --git a/frontend/src/pages/projects/screens/detail/overview/serverModels/PlatformSelectSection.tsx b/frontend/src/pages/projects/screens/detail/overview/serverModels/PlatformSelectSection.tsx index b985c5417d..5750042f0c 100644 --- a/frontend/src/pages/projects/screens/detail/overview/serverModels/PlatformSelectSection.tsx +++ b/frontend/src/pages/projects/screens/detail/overview/serverModels/PlatformSelectSection.tsx @@ -11,11 +11,12 @@ const PlatformSelectSection: React.FC = () => { const [errorSelectingPlatform, setErrorSelectingPlatform] = React.useState(); const servingPlatformStatuses = useServingPlatformStatuses(); - const { - nim: { available: isNIMAvailable }, - } = servingPlatformStatuses; + const kServeEnabled = servingPlatformStatuses.kServe.enabled; + const isNIMAvailable = servingPlatformStatuses.kServeNIM.enabled; + const modelMeshEnabled = servingPlatformStatuses.modelMesh.enabled; - const galleryWidths = isNIMAvailable + const threeEnabled = [kServeEnabled, modelMeshEnabled, isNIMAvailable].every((v) => v); + const galleryWidths = threeEnabled ? { minWidths: { default: '100%', lg: 'calc(33.33% - 1rem / 3 * 2)' }, maxWidths: { default: '100%', lg: 'calc(33.33% - 1rem / 3 * 2)' }, @@ -38,8 +39,12 @@ const PlatformSelectSection: React.FC = () => { - - + {kServeEnabled && ( + + )} + {modelMeshEnabled && ( + + )} {isNIMAvailable && ( )} diff --git a/frontend/src/pages/projects/screens/detail/overview/serverModels/ServeModelsSection.tsx b/frontend/src/pages/projects/screens/detail/overview/serverModels/ServeModelsSection.tsx index d38dce18ab..29a1e41515 100644 --- a/frontend/src/pages/projects/screens/detail/overview/serverModels/ServeModelsSection.tsx +++ b/frontend/src/pages/projects/screens/detail/overview/serverModels/ServeModelsSection.tsx @@ -10,8 +10,8 @@ import DeployedModelsSection from './deployedModels/DeployedModelsSection'; const ServeModelsSection: React.FC = () => { const servingPlatformStatuses = useServingPlatformStatuses(); const { - numServingPlatformsAvailable, modelMesh: { enabled: modelMeshEnabled }, + platformEnabledCount, } = servingPlatformStatuses; const { currentProject } = React.useContext(ProjectDetailsContext); @@ -21,11 +21,11 @@ const ServeModelsSection: React.FC = () => { servingPlatformStatuses, ); - if (numServingPlatformsAvailable > 1 && !currentProjectServingPlatform) { + if (platformEnabledCount > 1 && !currentProjectServingPlatform) { return ; } - if (numServingPlatformsAvailable === 0) { + if (platformEnabledCount === 0) { return ; } diff --git a/frontend/src/pages/projects/screens/detail/overview/serverModels/deployedModels/DeployedModelsSection.tsx b/frontend/src/pages/projects/screens/detail/overview/serverModels/deployedModels/DeployedModelsSection.tsx index 08f5bc3fea..f5e7d9768a 100644 --- a/frontend/src/pages/projects/screens/detail/overview/serverModels/deployedModels/DeployedModelsSection.tsx +++ b/frontend/src/pages/projects/screens/detail/overview/serverModels/deployedModels/DeployedModelsSection.tsx @@ -52,7 +52,6 @@ const DeployedModelsSection: React.FC = ({ isMultiPl } = React.useContext(ProjectDetailsContext); const servingPlatformStatuses = useServingPlatformStatuses(); - const { numServingPlatformsAvailable } = servingPlatformStatuses; const { error: platformError } = getProjectModelServingPlatform( currentProject, servingPlatformStatuses, @@ -130,7 +129,7 @@ const DeployedModelsSection: React.FC = ({ isMultiPl headerInfo={ - {numServingPlatformsAvailable > 1 && ( + {servingPlatformStatuses.platformEnabledCount > 1 && ( = ({ isMultiPl - {numServingPlatformsAvailable > 1 && ( + {servingPlatformStatuses.platformEnabledCount > 1 && (