From 88f16b4108730be00863cbe273993dc205e01acf Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Thu, 20 Apr 2023 11:36:33 +0200 Subject: [PATCH 01/26] add deployment id to the start deployment modal --- .../model_management/deployment_setup.tsx | 43 ++++++++++++++++++- .../model_management/model_actions.tsx | 22 +++++----- .../services/ml_api_service/trained_models.ts | 1 + .../server/routes/schemas/inference_schema.ts | 1 + 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx index 9068f679cf261..cbfe9f43f99f0 100644 --- a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx +++ b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx @@ -24,6 +24,7 @@ import { EuiSpacer, EuiDescribedFormGroup, EuiLink, + EuiFieldText, } from '@elastic/eui'; import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; import type { Observable } from 'rxjs'; @@ -42,6 +43,7 @@ export interface ThreadingParams { numOfAllocations: number; threadsPerAllocations?: number; priority?: 'low' | 'normal'; + deploymentId?: string; } const THREADS_MAX_EXPONENT = 4; @@ -72,6 +74,44 @@ export const DeploymentSetup: FC = ({ config, onConfigChan return ( + + + + } + description={ + + } + > + + } + hasChildLabel={false} + > + { + onConfigChange({ ...config, deploymentId: e.target.value }); + }} + aria-label="Use aria labels when no actual label is in use" + data-test-subj={'mlModelsStartDeploymentModalDeploymentId'} + /> + + + {config.priority !== undefined ? ( = ({ numOfAllocations: 1, threadsPerAllocations: 1, priority: isCloudTrial() ? 'low' : 'normal', + deploymentId: modelId, } ); @@ -373,7 +414,7 @@ export const StartUpdateDeploymentModal: FC = ({ * @param overlays * @param theme$ */ -export const getUserInputThreadingParamsProvider = +export const getUserInputModelDeploymentParamsProvider = (overlays: OverlayStart, theme$: Observable, startModelDeploymentDocUrl: string) => (modelId: string, initialParams?: ThreadingParams): Promise => { return new Promise(async (resolve) => { diff --git a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx index 7d4b6866f7a16..0105af0082294 100644 --- a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx +++ b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx @@ -18,7 +18,7 @@ import { import { useTrainedModelsApiService } from '../services/ml_api_service/trained_models'; import { getUserConfirmationProvider } from './force_stop_dialog'; import { useToastNotificationService } from '../services/toast_notification_service'; -import { getUserInputThreadingParamsProvider } from './deployment_setup'; +import { getUserInputModelDeploymentParamsProvider } from './deployment_setup'; import { useMlKibana, useMlLocator, useNavigateToPath } from '../contexts/kibana'; import { getAnalysisType } from '../../../common/util/analytics_utils'; import { DataFrameAnalysisConfigType } from '../../../common/types/data_frame_analytics'; @@ -67,8 +67,9 @@ export function useModelActions({ [overlays, theme] ); - const getUserInputThreadingParams = useMemo( - () => getUserInputThreadingParamsProvider(overlays, theme.theme$, startModelDeploymentDocUrl), + const getUserInputModelDeploymentParams = useMemo( + () => + getUserInputModelDeploymentParamsProvider(overlays, theme.theme$, startModelDeploymentDocUrl), [overlays, theme.theme$, startModelDeploymentDocUrl] ); @@ -161,16 +162,17 @@ export function useModelActions({ }, available: (item) => item.model_type === TRAINED_MODEL_TYPE.PYTORCH, onClick: async (item) => { - const threadingParams = await getUserInputThreadingParams(item.model_id); + const modelDeploymentParams = await getUserInputModelDeploymentParams(item.model_id); - if (!threadingParams) return; + if (!modelDeploymentParams) return; try { onLoading(true); await trainedModelsApiService.startModelAllocation(item.model_id, { - number_of_allocations: threadingParams.numOfAllocations, - threads_per_allocation: threadingParams.threadsPerAllocations!, - priority: threadingParams.priority!, + number_of_allocations: modelDeploymentParams.numOfAllocations, + threads_per_allocation: modelDeploymentParams.threadsPerAllocations!, + priority: modelDeploymentParams.priority!, + deployment_id: modelDeploymentParams.deploymentId, }); displaySuccessToast( i18n.translate('xpack.ml.trainedModels.modelsList.startSuccess', { @@ -215,7 +217,7 @@ export function useModelActions({ !isLoading && item.stats?.deployment_stats?.state === DEPLOYMENT_STATE.STARTED, onClick: async (item) => { - const threadingParams = await getUserInputThreadingParams(item.model_id, { + const threadingParams = await getUserInputModelDeploymentParams(item.model_id, { numOfAllocations: item.stats?.deployment_stats?.number_of_allocations!, }); @@ -374,7 +376,7 @@ export function useModelActions({ displayErrorToast, displaySuccessToast, getUserConfirmation, - getUserInputThreadingParams, + getUserInputModelDeploymentParams, isBuiltInModel, navigateToPath, navigateToUrl, diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts index 1548a298acd18..d57e81f4c05c1 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts @@ -132,6 +132,7 @@ export function trainedModelsApiProvider(httpService: HttpService) { number_of_allocations: number; threads_per_allocation: number; priority: 'low' | 'normal'; + deployment_id?: string; } ) { return httpService.http<{ acknowledge: boolean }>({ diff --git a/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts b/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts index dd0a15fa20f83..034ad761f2863 100644 --- a/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/inference_schema.ts @@ -19,6 +19,7 @@ export const threadingParamsSchema = schema.maybe( number_of_allocations: schema.number(), threads_per_allocation: schema.number(), priority: schema.oneOf([schema.literal('low'), schema.literal('normal')]), + deployment_id: schema.maybe(schema.string()), }) ); From e52358c1fd4d6c5116a31528078c1f3b8c02025b Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Thu, 20 Apr 2023 15:11:49 +0200 Subject: [PATCH 02/26] add deployment_ids --- x-pack/plugins/ml/common/types/trained_models.ts | 1 + .../application/model_management/deployment_setup.tsx | 2 -- .../application/model_management/force_stop_dialog.tsx | 4 ++-- .../public/application/model_management/model_actions.tsx | 8 ++------ .../public/application/model_management/models_list.tsx | 8 ++++++++ 5 files changed, 13 insertions(+), 10 deletions(-) diff --git a/x-pack/plugins/ml/common/types/trained_models.ts b/x-pack/plugins/ml/common/types/trained_models.ts index b4767db5d5344..e786297f4c36f 100644 --- a/x-pack/plugins/ml/common/types/trained_models.ts +++ b/x-pack/plugins/ml/common/types/trained_models.ts @@ -128,6 +128,7 @@ export interface InferenceConfigResponse { export interface TrainedModelDeploymentStatsResponse { model_id: string; + deployment_id: string; inference_threads: number; model_threads: number; state: DeploymentState; diff --git a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx index cbfe9f43f99f0..932636c8b0a12 100644 --- a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx +++ b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx @@ -101,12 +101,10 @@ export const DeploymentSetup: FC = ({ config, onConfigChan hasChildLabel={false} > { onConfigChange({ ...config, deploymentId: e.target.value }); }} - aria-label="Use aria labels when no actual label is in use" data-test-subj={'mlModelsStartDeploymentModalDeploymentId'} /> diff --git a/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx b/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx index 1800f9b13db03..ee6bac7ebf6e6 100644 --- a/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx +++ b/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx @@ -19,7 +19,7 @@ interface ForceStopModelConfirmDialogProps { onConfirm: () => void; } -export const ForceStopModelConfirmDialog: FC = ({ +export const StopModelDeploymentsConfirmDialog: FC = ({ model, onConfirm, onCancel, @@ -64,7 +64,7 @@ export const getUserConfirmationProvider = const modalSession = overlays.openModal( toMountPoint( wrapWithTheme( - { modalSession.close(); diff --git a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx index 0105af0082294..6927122ece3c8 100644 --- a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx +++ b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx @@ -273,13 +273,9 @@ export function useModelActions({ item.stats?.deployment_stats?.state !== DEPLOYMENT_STATE.STOPPING, onClick: async (item) => { const requireForceStop = isPopulatedObject(item.pipelines); + const hasMultipleDeployments = item.deployment_ids.length > 1; - if (requireForceStop) { - const hasUserApproved = await getUserConfirmation(item); - if (!hasUserApproved) return; - } - - if (requireForceStop) { + if (requireForceStop || hasMultipleDeployments) { const hasUserApproved = await getUserConfirmation(item); if (!hasUserApproved) return; } diff --git a/x-pack/plugins/ml/public/application/model_management/models_list.tsx b/x-pack/plugins/ml/public/application/model_management/models_list.tsx index 48a1e6266b235..108869b8cc7fe 100644 --- a/x-pack/plugins/ml/public/application/model_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/model_management/models_list.tsx @@ -55,6 +55,7 @@ export type ModelItem = TrainedModelConfigResponse & { type?: string[]; stats?: Stats; pipelines?: ModelPipelines['pipelines'] | null; + deployment_ids: string[]; }; export type ModelItemFull = Required; @@ -226,6 +227,13 @@ export const ModelsList: FC = ({ ...(model.stats ?? {}), ...stats, }; + + if (stats.deployment_stats) { + if (!Array.isArray(model.deployment_ids)) { + model.deployment_ids = []; + } + model.deployment_ids.push(stats.deployment_stats.deployment_id); + } } } } From 9893f1db8de2209e6de2954a319ada7c8032a402 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Thu, 20 Apr 2023 17:19:00 +0200 Subject: [PATCH 03/26] stop multiple deployments --- .../model_management/force_stop_dialog.tsx | 97 ++++++++++++++----- .../model_management/model_actions.tsx | 10 +- .../services/ml_api_service/trained_models.ts | 4 +- 3 files changed, 84 insertions(+), 27 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx b/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx index ee6bac7ebf6e6..07b69cb07fd18 100644 --- a/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx +++ b/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx @@ -5,33 +5,63 @@ * 2.0. */ -import React, { FC } from 'react'; -import { EuiConfirmModal } from '@elastic/eui'; +import React, { type FC, useState } from 'react'; +import { EuiCheckboxGroup, EuiCheckboxGroupOption, EuiConfirmModal } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import type { OverlayStart, ThemeServiceStart } from '@kbn/core/public'; import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; +import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import type { ModelItem } from './models_list'; interface ForceStopModelConfirmDialogProps { model: ModelItem; onCancel: () => void; - onConfirm: () => void; + onConfirm: (deploymentIds: string[]) => void; } +/** + * Confirmation is required when there are multiple model deployments + * or associated pipelines. + */ export const StopModelDeploymentsConfirmDialog: FC = ({ model, onConfirm, onCancel, }) => { + const [checkboxIdToSelectedMap, setCheckboxIdToSelectedMap] = useState>( + {} + ); + + const options: EuiCheckboxGroupOption[] = model.deployment_ids.map((deploymentId) => { + return { + id: deploymentId, + label: deploymentId, + }; + }); + + const onChange = (id: string) => { + setCheckboxIdToSelectedMap((prev) => { + return { + ...prev, + [id]: !prev[id], + }; + }); + }; + + const selectedDeploymentIds = Object.keys(checkboxIdToSelectedMap).filter( + (id) => checkboxIdToSelectedMap[id] + ); + return ( 1 && selectedDeploymentIds.length === 0} > - -
    - {Object.keys(model.pipelines!) - .sort() - .map((pipelineName) => { - return
  • {pipelineName}
  • ; - })} -
+ {model.deployment_ids.length > 1 ? ( + + ), + }} + options={options} + idToSelectedMap={checkboxIdToSelectedMap} + onChange={onChange} + /> + ) : null} + + {isPopulatedObject(model.pipelines) ? ( + <> + +
    + {Object.keys(model.pipelines!) + .sort() + .map((pipelineName) => { + return
  • {pipelineName}
  • ; + })} +
+ + ) : null}
); }; export const getUserConfirmationProvider = - (overlays: OverlayStart, theme: ThemeServiceStart) => async (forceStopModel: ModelItem) => { + (overlays: OverlayStart, theme: ThemeServiceStart) => + async (forceStopModel: ModelItem): Promise => { return new Promise(async (resolve, reject) => { try { const modalSession = overlays.openModal( @@ -68,11 +121,11 @@ export const getUserConfirmationProvider = model={forceStopModel} onCancel={() => { modalSession.close(); - resolve(false); + reject(); }} - onConfirm={() => { + onConfirm={(deploymentIds: string[]) => { modalSession.close(); - resolve(true); + resolve(deploymentIds); }} />, theme.theme$ @@ -80,7 +133,7 @@ export const getUserConfirmationProvider = ) ); } catch (e) { - resolve(false); + reject(); } }); }; diff --git a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx index 6927122ece3c8..1b249224cd9de 100644 --- a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx +++ b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx @@ -275,14 +275,18 @@ export function useModelActions({ const requireForceStop = isPopulatedObject(item.pipelines); const hasMultipleDeployments = item.deployment_ids.length > 1; + let deploymentIds: string[] = [item.model_id]; if (requireForceStop || hasMultipleDeployments) { - const hasUserApproved = await getUserConfirmation(item); - if (!hasUserApproved) return; + try { + deploymentIds = await getUserConfirmation(item); + } catch (error) { + return; + } } try { onLoading(true); - await trainedModelsApiService.stopModelAllocation(item.model_id, { + await trainedModelsApiService.stopModelAllocation(deploymentIds, { force: requireForceStop, }); displaySuccessToast( diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts index d57e81f4c05c1..2975982eccad0 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/trained_models.ts @@ -142,11 +142,11 @@ export function trainedModelsApiProvider(httpService: HttpService) { }); }, - stopModelAllocation(modelId: string, options: { force: boolean } = { force: false }) { + stopModelAllocation(deploymentsIds: string[], options: { force: boolean } = { force: false }) { const force = options?.force; return httpService.http<{ acknowledge: boolean }>({ - path: `${apiBasePath}/trained_models/${modelId}/deployment/_stop`, + path: `${apiBasePath}/trained_models/${deploymentsIds.join(',')}/deployment/_stop`, method: 'POST', query: { force }, }); From 2a3343e2452b24106bc2c89a403864a78423ae9f Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 24 Apr 2023 14:23:41 +0200 Subject: [PATCH 04/26] render deployment stats --- .../plugins/ml/common/types/trained_models.ts | 4 +- .../nodes_overview/allocated_models.tsx | 45 +++++++----- .../model_management/expanded_row.tsx | 71 +++++++++---------- .../model_management/model_actions.tsx | 8 +-- .../model_management/models_list.tsx | 68 +++++++++--------- 5 files changed, 99 insertions(+), 97 deletions(-) diff --git a/x-pack/plugins/ml/common/types/trained_models.ts b/x-pack/plugins/ml/common/types/trained_models.ts index e786297f4c36f..1de77b523266d 100644 --- a/x-pack/plugins/ml/common/types/trained_models.ts +++ b/x-pack/plugins/ml/common/types/trained_models.ts @@ -51,7 +51,7 @@ export interface TrainedModelStat { } >; }; - deployment_stats?: Omit; + deployment_stats?: TrainedModelDeploymentStatsResponse; model_size_stats?: TrainedModelModelSizeStats; } @@ -164,6 +164,8 @@ export interface TrainedModelDeploymentStatsResponse { } export interface AllocatedModel { + key: string; + deployment_id: string; inference_threads: number; allocation_status: { target_allocation_count: number; diff --git a/x-pack/plugins/ml/public/application/memory_usage/nodes_overview/allocated_models.tsx b/x-pack/plugins/ml/public/application/memory_usage/nodes_overview/allocated_models.tsx index 198c01806e06b..6b86287b6faa1 100644 --- a/x-pack/plugins/ml/public/application/memory_usage/nodes_overview/allocated_models.tsx +++ b/x-pack/plugins/ml/public/application/memory_usage/nodes_overview/allocated_models.tsx @@ -39,6 +39,33 @@ export const AllocatedModels: FC = ({ const euiTheme = useEuiTheme(); const columns: Array> = [ + { + id: 'deployment_id', + field: 'deployment_id', + name: i18n.translate('xpack.ml.trainedModels.nodesList.modelsList.deploymentIdHeader', { + defaultMessage: 'ID', + }), + width: '150px', + sortable: true, + truncateText: false, + 'data-test-subj': 'mlAllocatedModelsTableDeploymentId', + }, + { + name: i18n.translate('xpack.ml.trainedModels.nodesList.modelsList.modelRoutingStateHeader', { + defaultMessage: 'Routing state', + }), + width: '100px', + 'data-test-subj': 'mlAllocatedModelsTableRoutingState', + render: (v: AllocatedModel) => { + const { routing_state: routingState, reason } = v.node.routing_state; + + return ( + + {routingState} + + ); + }, + }, { id: 'node_name', field: 'node.name', @@ -193,22 +220,6 @@ export const AllocatedModels: FC = ({ return v.node.number_of_pending_requests; }, }, - { - name: i18n.translate('xpack.ml.trainedModels.nodesList.modelsList.modelRoutingStateHeader', { - defaultMessage: 'Routing state', - }), - width: '100px', - 'data-test-subj': 'mlAllocatedModelsTableRoutingState', - render: (v: AllocatedModel) => { - const { routing_state: routingState, reason } = v.node.routing_state; - - return ( - - {routingState} - - ); - }, - }, ].filter((v) => !hideColumns.includes(v.id!)); return ( @@ -219,7 +230,7 @@ export const AllocatedModels: FC = ({ isExpandable={false} isSelectable={false} items={models} - itemId={'model_id'} + itemId={'key'} rowProps={(item) => ({ 'data-test-subj': `mlAllocatedModelTableRow row-${item.model_id}`, })} diff --git a/x-pack/plugins/ml/public/application/model_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/model_management/expanded_row.tsx index 4d6a3c744408a..7c52e441306f7 100644 --- a/x-pack/plugins/ml/public/application/model_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/model_management/expanded_row.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { FC, useEffect, useState, useMemo, useCallback } from 'react'; +import React, { FC, useMemo, useCallback } from 'react'; import { omit, pick } from 'lodash'; import { EuiBadge, @@ -110,8 +110,6 @@ export function useListItemsFormatter() { } export const ExpandedRow: FC = ({ item }) => { - const [modelItems, setModelItems] = useState([]); - const formatToListItems = useListItemsFormatter(); const { @@ -144,42 +142,39 @@ export const ExpandedRow: FC = ({ item }) => { license_level, }; - useEffect( - function updateModelItems() { - (async function () { - const deploymentStats = stats.deployment_stats; - const modelSizeStats = stats.model_size_stats; + const deploymentStatItems: AllocatedModel[] = useMemo(() => { + const deploymentStats = stats.deployment_stats; + const modelSizeStats = stats.model_size_stats; - if (!deploymentStats || !modelSizeStats) return; + if (!deploymentStats || !modelSizeStats) return []; - const items: AllocatedModel[] = deploymentStats.nodes.map((n) => { - const nodeName = Object.values(n.node)[0].name; - return { - ...deploymentStats, - ...modelSizeStats, - node: { - ...pick(n, [ - 'average_inference_time_ms', - 'inference_count', - 'routing_state', - 'last_access', - 'number_of_pending_requests', - 'start_time', - 'throughput_last_minute', - 'number_of_allocations', - 'threads_per_allocation', - ]), - name: nodeName, - } as AllocatedModel['node'], - }; - }); + const items: AllocatedModel[] = deploymentStats.flatMap((perDeploymentStat) => { + return perDeploymentStat.nodes.map((n) => { + const nodeName = Object.values(n.node)[0].name; + return { + key: `${perDeploymentStat.deployment_id}_${nodeName}`, + ...perDeploymentStat, + ...modelSizeStats, + node: { + ...pick(n, [ + 'average_inference_time_ms', + 'inference_count', + 'routing_state', + 'last_access', + 'number_of_pending_requests', + 'start_time', + 'throughput_last_minute', + 'number_of_allocations', + 'threads_per_allocation', + ]), + name: nodeName, + } as AllocatedModel['node'], + }; + }); + }); - setModelItems(items); - })(); - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [stats.deployment_stats] - ); + return items; + }, [stats]); const tabs: EuiTabbedContentTab[] = [ { @@ -313,7 +308,7 @@ export const ExpandedRow: FC = ({ item }) => {
- {!!modelItems?.length ? ( + {!!deploymentStatItems?.length ? ( <> @@ -325,7 +320,7 @@ export const ExpandedRow: FC = ({ item }) => { - + diff --git a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx index 1b249224cd9de..83ad99f993d74 100644 --- a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx +++ b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx @@ -152,13 +152,7 @@ export function useModelActions({ type: 'icon', isPrimary: true, enabled: (item) => { - const { state } = item.stats?.deployment_stats ?? {}; - return ( - canStartStopTrainedModels && - !isLoading && - state !== DEPLOYMENT_STATE.STARTED && - state !== DEPLOYMENT_STATE.STARTING - ); + return canStartStopTrainedModels && !isLoading; }, available: (item) => item.model_type === TRAINED_MODEL_TYPE.PYTORCH, onClick: async (item) => { diff --git a/x-pack/plugins/ml/public/application/model_management/models_list.tsx b/x-pack/plugins/ml/public/application/model_management/models_list.tsx index 108869b8cc7fe..3b4adb239368d 100644 --- a/x-pack/plugins/ml/public/application/model_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/model_management/models_list.tsx @@ -18,7 +18,7 @@ import { EuiTitle, SearchFilterConfig, } from '@elastic/eui'; - +import { groupBy } from 'lodash'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiBasicTableColumn } from '@elastic/eui/src/components/basic_table/basic_table'; @@ -28,14 +28,16 @@ import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { usePageUrlState } from '@kbn/ml-url-state'; import { useTimefilter } from '@kbn/ml-date-picker'; import { BUILT_IN_MODEL_TYPE, BUILT_IN_MODEL_TAG } from '@kbn/ml-trained-models-utils'; +import { isDefined } from '@kbn/ml-is-defined'; import { useModelActions } from './model_actions'; import { ModelsTableToConfigMapping } from '.'; import { ModelsBarStats, StatsBar } from '../components/stats_bar'; import { useMlKibana } from '../contexts/kibana'; import { useTrainedModelsApiService } from '../services/ml_api_service/trained_models'; -import { +import type { ModelPipelines, TrainedModelConfigResponse, + TrainedModelDeploymentStatsResponse, TrainedModelStat, } from '../../../common/types/trained_models'; import { DeleteModelsModal } from './delete_models_modal'; @@ -49,13 +51,13 @@ import { useRefresh } from '../routing/use_refresh'; import { SavedObjectsWarning } from '../components/saved_objects_warning'; import { TestTrainedModelFlyout } from './test_models'; -type Stats = Omit; +type Stats = Omit; export type ModelItem = TrainedModelConfigResponse & { type?: string[]; - stats?: Stats; + stats?: Stats & { deployment_stats: TrainedModelDeploymentStatsResponse[] }; pipelines?: ModelPipelines['pipelines'] | null; - deployment_ids: string[]; + deployment_ids?: string[]; }; export type ModelItemFull = Required; @@ -220,22 +222,19 @@ export const ModelsList: FC = ({ const { trained_model_stats: modelsStatsResponse } = await trainedModelsApiService.getTrainedModelStats(models.map((m) => m.model_id)); - for (const { model_id: id, ...stats } of modelsStatsResponse) { - const model = models.find((m) => m.model_id === id); - if (model) { - model.stats = { - ...(model.stats ?? {}), - ...stats, - }; - - if (stats.deployment_stats) { - if (!Array.isArray(model.deployment_ids)) { - model.deployment_ids = []; - } - model.deployment_ids.push(stats.deployment_stats.deployment_id); - } - } - } + const groupByModelId = groupBy(modelsStatsResponse, 'model_id'); + + models.forEach((model) => { + const modelStats = groupByModelId[model.model_id]; + model.stats = { + ...(model.stats ?? {}), + ...modelStats[0], + deployment_stats: modelStats.map((d) => d.deployment_stats).filter(isDefined), + }; + model.deployment_ids = modelStats + .map((v) => v.deployment_stats?.deployment_id) + .filter(isDefined); + }); } return true; @@ -355,19 +354,20 @@ export const ModelsList: FC = ({ ), 'data-test-subj': 'mlModelsTableColumnType', }, - { - name: i18n.translate('xpack.ml.trainedModels.modelsList.stateHeader', { - defaultMessage: 'State', - }), - sortable: (item) => item.stats?.deployment_stats?.state, - align: 'left', - truncateText: true, - render: (model: ModelItem) => { - const state = model.stats?.deployment_stats?.state; - return state ? {state} : null; - }, - 'data-test-subj': 'mlModelsTableColumnDeploymentState', - }, + // FIXME show combined deploymnetss state + // { + // name: i18n.translate('xpack.ml.trainedModels.modelsList.stateHeader', { + // defaultMessage: 'State', + // }), + // sortable: (item) => item.stats?.deployment_stats?.state, + // align: 'left', + // truncateText: true, + // render: (model: ModelItem) => { + // const state = model.stats?.deployment_stats?.state; + // return state ? {state} : null; + // }, + // 'data-test-subj': 'mlModelsTableColumnDeploymentState', + // }, { field: ModelsTableToConfigMapping.createdAt, name: i18n.translate('xpack.ml.trainedModels.modelsList.createdAtHeader', { From f94246a67d8e1bd404431f815d1bdea33036ebef Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 24 Apr 2023 14:47:53 +0200 Subject: [PATCH 05/26] stop action enabled condition, update deployment ids param --- .../public/application/model_management/model_actions.tsx | 7 ++----- .../ml/public/application/model_management/models_list.tsx | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx index 83ad99f993d74..6764addc09d7f 100644 --- a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx +++ b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx @@ -261,15 +261,12 @@ export function useModelActions({ isPrimary: true, available: (item) => item.model_type === TRAINED_MODEL_TYPE.PYTORCH, enabled: (item) => - canStartStopTrainedModels && - !isLoading && - isPopulatedObject(item.stats?.deployment_stats) && - item.stats?.deployment_stats?.state !== DEPLOYMENT_STATE.STOPPING, + canStartStopTrainedModels && !isLoading && item.deployment_ids.length > 0, onClick: async (item) => { const requireForceStop = isPopulatedObject(item.pipelines); const hasMultipleDeployments = item.deployment_ids.length > 1; - let deploymentIds: string[] = [item.model_id]; + let deploymentIds: string[] = item.deployment_ids; if (requireForceStop || hasMultipleDeployments) { try { deploymentIds = await getUserConfirmation(item); diff --git a/x-pack/plugins/ml/public/application/model_management/models_list.tsx b/x-pack/plugins/ml/public/application/model_management/models_list.tsx index 3b4adb239368d..df005d1e0fc83 100644 --- a/x-pack/plugins/ml/public/application/model_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/model_management/models_list.tsx @@ -57,7 +57,7 @@ export type ModelItem = TrainedModelConfigResponse & { type?: string[]; stats?: Stats & { deployment_stats: TrainedModelDeploymentStatsResponse[] }; pipelines?: ModelPipelines['pipelines'] | null; - deployment_ids?: string[]; + deployment_ids: string[]; }; export type ModelItemFull = Required; From ed57c0473d07f97a3a2f3e001e7199b090d2681f Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 24 Apr 2023 14:57:52 +0200 Subject: [PATCH 06/26] remove model id check for stop deployment --- x-pack/plugins/ml/server/lib/ml_client/ml_client.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts index 691279977740d..deb64bbfefac5 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts @@ -500,7 +500,6 @@ export function getMlClient( }); }, async stopTrainedModelDeployment(...p: Parameters) { - await modelIdsCheck(p); return mlClient.stopTrainedModelDeployment(...p); }, async inferTrainedModel(...p: Parameters) { From c5ab587e2e1a814225cfdba69c06e8ce63d44142 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 24 Apr 2023 15:55:31 +0200 Subject: [PATCH 07/26] deployment id validation --- x-pack/plugins/ml/common/util/validators.ts | 10 ++++ .../model_management/deployment_setup.tsx | 49 ++++++++++++++----- .../model_management/model_actions.tsx | 6 ++- 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/x-pack/plugins/ml/common/util/validators.ts b/x-pack/plugins/ml/common/util/validators.ts index 4cbef8470cfc0..e890db5893ad6 100644 --- a/x-pack/plugins/ml/common/util/validators.ts +++ b/x-pack/plugins/ml/common/util/validators.ts @@ -99,3 +99,13 @@ export function timeIntervalInputValidator() { return null; }; } + +export function dictionaryValidator(dict: string[], shouldInclude: boolean = false) { + const dictSet = new Set(dict); + return (value: string) => { + if (dictSet.has(value) !== shouldInclude) { + return { matchDict: value }; + } + return null; + }; +} diff --git a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx index 932636c8b0a12..1ea42ae55ca51 100644 --- a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx +++ b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx @@ -32,11 +32,17 @@ import type { CoreTheme, OverlayStart } from '@kbn/core/public'; import { css } from '@emotion/react'; import { numberValidator } from '@kbn/ml-agg-utils'; import { isCloudTrial } from '../services/ml_server_info'; -import { composeValidators, requiredValidator } from '../../../common/util/validators'; +import { + composeValidators, + dictionaryValidator, + requiredValidator, +} from '../../../common/util/validators'; +import { ModelItem } from './models_list'; interface DeploymentSetupProps { config: ThreadingParams; onConfigChange: (config: ThreadingParams) => void; + errors: Partial>; } export interface ThreadingParams { @@ -51,7 +57,7 @@ const THREADS_MAX_EXPONENT = 4; /** * Form for setting threading params. */ -export const DeploymentSetup: FC = ({ config, onConfigChange }) => { +export const DeploymentSetup: FC = ({ config, onConfigChange, errors }) => { const numOfAllocation = config.numOfAllocations; const threadsPerAllocations = config.threadsPerAllocations; @@ -99,8 +105,16 @@ export const DeploymentSetup: FC = ({ config, onConfigChan /> } hasChildLabel={false} + isInvalid={!!errors.deploymentId} + error={ + + } > { onConfigChange({ ...config, deploymentId: e.target.value }); @@ -278,7 +292,7 @@ export const DeploymentSetup: FC = ({ config, onConfigChan }; interface StartDeploymentModalProps { - modelId: string; + model: ModelItem; startModelDeploymentDocUrl: string; onConfigChange: (config: ThreadingParams) => void; onClose: () => void; @@ -289,7 +303,7 @@ interface StartDeploymentModalProps { * Modal window wrapper for {@link DeploymentSetup} */ export const StartUpdateDeploymentModal: FC = ({ - modelId, + model, onConfigChange, onClose, startModelDeploymentDocUrl, @@ -300,18 +314,29 @@ export const StartUpdateDeploymentModal: FC = ({ numOfAllocations: 1, threadsPerAllocations: 1, priority: isCloudTrial() ? 'low' : 'normal', - deploymentId: modelId, + deploymentId: model.model_id, } ); const isUpdate = initialParams !== undefined; + const deploymentIdValidator = useMemo( + () => dictionaryValidator(model.deployment_ids), + [model.deployment_ids] + ); + const numOfAllocationsValidator = composeValidators( requiredValidator(), numberValidator({ min: 1, integerOnly: true }) ); - const errors = numOfAllocationsValidator(config.numOfAllocations); + const numOfAllocationsErrors = numOfAllocationsValidator(config.numOfAllocations); + const deploymentIdErrors = deploymentIdValidator(config.deploymentId ?? ''); + + const errors = { + ...(numOfAllocationsErrors ? { numOfAllocations: numOfAllocationsErrors } : {}), + ...(deploymentIdErrors ? { deploymentId: deploymentIdErrors } : {}), + }; return ( = ({ ) : ( )} @@ -352,7 +377,7 @@ export const StartUpdateDeploymentModal: FC = ({ /> - + @@ -385,7 +410,7 @@ export const StartUpdateDeploymentModal: FC = ({ form={'startDeploymentForm'} onClick={onConfigChange.bind(null, config)} fill - disabled={!!errors} + disabled={Object.keys(errors).length > 0} data-test-subj={'mlModelsStartDeploymentModalStartButton'} > {isUpdate ? ( @@ -414,7 +439,7 @@ export const StartUpdateDeploymentModal: FC = ({ */ export const getUserInputModelDeploymentParamsProvider = (overlays: OverlayStart, theme$: Observable, startModelDeploymentDocUrl: string) => - (modelId: string, initialParams?: ThreadingParams): Promise => { + (model: ModelItem, initialParams?: ThreadingParams): Promise => { return new Promise(async (resolve) => { try { const modalSession = overlays.openModal( @@ -423,7 +448,7 @@ export const getUserInputModelDeploymentParamsProvider = { modalSession.close(); diff --git a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx index 6764addc09d7f..94e9005e91405 100644 --- a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx +++ b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx @@ -156,7 +156,7 @@ export function useModelActions({ }, available: (item) => item.model_type === TRAINED_MODEL_TYPE.PYTORCH, onClick: async (item) => { - const modelDeploymentParams = await getUserInputModelDeploymentParams(item.model_id); + const modelDeploymentParams = await getUserInputModelDeploymentParams(item); if (!modelDeploymentParams) return; @@ -166,7 +166,9 @@ export function useModelActions({ number_of_allocations: modelDeploymentParams.numOfAllocations, threads_per_allocation: modelDeploymentParams.threadsPerAllocations!, priority: modelDeploymentParams.priority!, - deployment_id: modelDeploymentParams.deploymentId, + deployment_id: !!modelDeploymentParams.deploymentId + ? modelDeploymentParams.deploymentId + : item.model_id, }); displaySuccessToast( i18n.translate('xpack.ml.trainedModels.modelsList.startSuccess', { From 50b23c22d82fee7d4e047924be19e5bc12c2b47a Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 24 Apr 2023 16:00:15 +0200 Subject: [PATCH 08/26] default deployment id check --- .../application/model_management/deployment_setup.tsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx index 1ea42ae55ca51..e2a5ee0be1d59 100644 --- a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx +++ b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx @@ -320,10 +320,13 @@ export const StartUpdateDeploymentModal: FC = ({ const isUpdate = initialParams !== undefined; - const deploymentIdValidator = useMemo( - () => dictionaryValidator(model.deployment_ids), - [model.deployment_ids] - ); + const deploymentIdValidator = useMemo(() => { + return dictionaryValidator([ + ...model.deployment_ids, + // check for deployment with the default ID + ...(model.deployment_ids.includes(model.model_id) ? [''] : []), + ]); + }, [model.deployment_ids, model.model_id]); const numOfAllocationsValidator = composeValidators( requiredValidator(), From 797f66f40e4827bc4d3290f3330a8e0c93e8b435 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 24 Apr 2023 18:39:02 +0200 Subject: [PATCH 09/26] test model action --- .../model_management/model_actions.tsx | 8 +-- .../model_management/models_list.tsx | 11 ++-- .../test_models/models/inference_base.ts | 17 ++++- .../test_models/selected_model.tsx | 8 ++- .../test_models/test_flyout.tsx | 65 ++++++++++++------- .../model_management/test_models/utils.ts | 2 +- .../ml/server/lib/ml_client/ml_client.ts | 2 - 7 files changed, 71 insertions(+), 42 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx index 94e9005e91405..4683b3ed12c1d 100644 --- a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx +++ b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx @@ -34,7 +34,7 @@ export function useModelActions({ fetchModels, }: { isLoading: boolean; - onTestAction: (model: string) => void; + onTestAction: (model: ModelItem) => void; onModelsDeleteRequest: (modelsIds: string[]) => void; onLoading: (isLoading: boolean) => void; fetchModels: () => void; @@ -211,9 +211,9 @@ export function useModelActions({ item.model_type === TRAINED_MODEL_TYPE.PYTORCH && canStartStopTrainedModels && !isLoading && - item.stats?.deployment_stats?.state === DEPLOYMENT_STATE.STARTED, + !!item.stats?.deployment_stats?.some((v) => v.state === DEPLOYMENT_STATE.STARTED), onClick: async (item) => { - const threadingParams = await getUserInputModelDeploymentParams(item.model_id, { + const threadingParams = await getUserInputModelDeploymentParams(item, { numOfAllocations: item.stats?.deployment_stats?.number_of_allocations!, }); @@ -358,7 +358,7 @@ export function useModelActions({ type: 'icon', isPrimary: true, available: isTestable, - onClick: (item) => onTestAction(item.model_id), + onClick: (item) => onTestAction(item), enabled: (item) => canTestTrainedModels && isTestable(item, true), }, ], diff --git a/x-pack/plugins/ml/public/application/model_management/models_list.tsx b/x-pack/plugins/ml/public/application/model_management/models_list.tsx index df005d1e0fc83..beb655b44e4d4 100644 --- a/x-pack/plugins/ml/public/application/model_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/model_management/models_list.tsx @@ -123,7 +123,7 @@ export const ModelsList: FC = ({ const [itemIdToExpandedRowMap, setItemIdToExpandedRowMap] = useState>( {} ); - const [showTestFlyout, setShowTestFlyout] = useState(null); + const [modelToTest, setModelToTest] = useState(null); const isBuiltInModel = useCallback( (item: ModelItem) => item.tags.includes(BUILT_IN_MODEL_TAG), @@ -276,7 +276,7 @@ export const ModelsList: FC = ({ const actions = useModelActions({ isLoading, fetchModels: fetchModelsData, - onTestAction: setShowTestFlyout, + onTestAction: setModelToTest, onModelsDeleteRequest: setModelIdsToDelete, onLoading: setIsLoading, }); @@ -541,11 +541,8 @@ export const ModelsList: FC = ({ modelIds={modelIdsToDelete} /> )} - {showTestFlyout === null ? null : ( - + {modelToTest === null ? null : ( + )} ); diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/models/inference_base.ts b/x-pack/plugins/ml/public/application/model_management/test_models/models/inference_base.ts index 1e3e3ecd5dfd7..6e5d47c2be29c 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/models/inference_base.ts +++ b/x-pack/plugins/ml/public/application/model_management/test_models/models/inference_base.ts @@ -61,6 +61,8 @@ export abstract class InferenceBase { protected abstract readonly inferenceTypeLabel: string; protected readonly modelInputField: string; + protected _deploymentId: string | null = null; + protected inputText$ = new BehaviorSubject([]); private inputField$ = new BehaviorSubject(''); private inferenceResult$ = new BehaviorSubject(null); @@ -86,6 +88,17 @@ export abstract class InferenceBase { this.subscriptions$.unsubscribe(); } + public set deploymentId(deploymentId: string) { + this._deploymentId = deploymentId; + } + + public get deploymentId() { + if (!this._deploymentId) { + throw new Error('Deployment ID is required'); + } + return this._deploymentId; + } + protected initialize( additionalValidators?: Array>, additionalChanges?: Array> @@ -243,7 +256,7 @@ export abstract class InferenceBase { ): estypes.IngestProcessorContainer[] { const processor: estypes.IngestProcessorContainer = { inference: { - model_id: this.model.model_id, + model_id: this.deploymentId, target_field: this.inferenceType, field_map: { [this.inputField$.getValue()]: this.modelInputField, @@ -277,7 +290,7 @@ export abstract class InferenceBase { const inferenceConfig = getInferenceConfig(); const resp = (await this.trainedModelsApi.inferTrainedModel( - this.model.model_id, + this.deploymentId, { docs: this.getInferDocs(), ...(inferenceConfig ? { inference_config: inferenceConfig } : {}), diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/selected_model.tsx b/x-pack/plugins/ml/public/application/model_management/test_models/selected_model.tsx index 3749620b47b13..84a6260ca7405 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/selected_model.tsx +++ b/x-pack/plugins/ml/public/application/model_management/test_models/selected_model.tsx @@ -29,9 +29,10 @@ import { INPUT_TYPE } from './models/inference_base'; interface Props { model: estypes.MlTrainedModelConfig; inputType: INPUT_TYPE; + deploymentId: string; } -export const SelectedModel: FC = ({ model, inputType }) => { +export const SelectedModel: FC = ({ model, inputType, deploymentId }) => { const { trainedModels } = useMlApiContext(); const inferrer: InferrerType | undefined = useMemo(() => { @@ -66,6 +67,11 @@ export const SelectedModel: FC = ({ model, inputType }) => { } }, [inputType, model, trainedModels]); + useEffect(() => { + if (!inferrer) return; + inferrer.deploymentId = deploymentId; + }, [deploymentId, inferrer]); + useEffect(() => { return () => { inferrer?.destroy(); diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/test_flyout.tsx b/x-pack/plugins/ml/public/application/model_management/test_models/test_flyout.tsx index 1ee8a853bb477..d3fa13b233f5f 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/test_flyout.tsx +++ b/x-pack/plugins/ml/public/application/model_management/test_models/test_flyout.tsx @@ -5,50 +5,35 @@ * 2.0. */ -import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey'; -import React, { FC, useState, useEffect } from 'react'; +import React, { FC, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n-react'; import { EuiFlyout, - EuiFlyoutHeader, - EuiTitle, EuiFlyoutBody, + EuiFlyoutHeader, + EuiFormRow, + EuiSelect, EuiSpacer, EuiTab, EuiTabs, + EuiTitle, useEuiPaddingSize, } from '@elastic/eui'; import { SelectedModel } from './selected_model'; import { INPUT_TYPE } from './models/inference_base'; -import { useTrainedModelsApiService } from '../../services/ml_api_service/trained_models'; +import { type ModelItem } from '../models_list'; interface Props { - modelId: string; + model: ModelItem; onClose: () => void; } -export const TestTrainedModelFlyout: FC = ({ modelId, onClose }) => { +export const TestTrainedModelFlyout: FC = ({ model, onClose }) => { + const [deploymentId, setDeploymentId] = useState(model.deployment_ids[0]); const mediumPadding = useEuiPaddingSize('m'); - const trainedModelsApiService = useTrainedModelsApiService(); const [inputType, setInputType] = useState(INPUT_TYPE.TEXT); - const [model, setModel] = useState(null); - - useEffect( - function fetchModel() { - trainedModelsApiService.getTrainedModels(modelId).then((resp) => { - if (resp.length) { - setModel(resp[0]); - } - }); - }, - [modelId, trainedModelsApiService] - ); - - if (model === null) { - return null; - } return ( <> @@ -68,6 +53,32 @@ export const TestTrainedModelFlyout: FC = ({ modelId, onClose }) => { + {model.deployment_ids.length > 1 ? ( + <> + + } + > + { + return { text: v, value: v }; + })} + value={deploymentId} + onChange={(e) => { + setDeploymentId(e.target.value); + }} + /> + + + + ) : null} + = ({ modelId, onClose }) => { - + diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/utils.ts b/x-pack/plugins/ml/public/application/model_management/test_models/utils.ts index 2048af3e31173..bb0dc6e9973e8 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/utils.ts +++ b/x-pack/plugins/ml/public/application/model_management/test_models/utils.ts @@ -22,7 +22,7 @@ export function isTestable(modelItem: ModelItem, checkForState = false) { Object.keys(modelItem.inference_config)[0] as SupportedPytorchTasksType ) && (checkForState === false || - modelItem.stats?.deployment_stats?.state === DEPLOYMENT_STATE.STARTED) + modelItem.stats?.deployment_stats?.some((v) => v.state === DEPLOYMENT_STATE.STARTED)) ) { return true; } diff --git a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts index deb64bbfefac5..d7904c182d09e 100644 --- a/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts +++ b/x-pack/plugins/ml/server/lib/ml_client/ml_client.ts @@ -491,7 +491,6 @@ export function getMlClient( return mlClient.startTrainedModelDeployment(...p); }, async updateTrainedModelDeployment(...p: Parameters) { - await modelIdsCheck(p); const { model_id: modelId, number_of_allocations: numberOfAllocations } = p[0]; return client.asInternalUser.transport.request({ method: 'POST', @@ -503,7 +502,6 @@ export function getMlClient( return mlClient.stopTrainedModelDeployment(...p); }, async inferTrainedModel(...p: Parameters) { - await modelIdsCheck(p); // Temporary workaround for the incorrect inferTrainedModelDeployment function in the esclient if ( // @ts-expect-error TS complains it's always false From e10db18521cca833b96b808bbc97eb71d7ea1909 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 24 Apr 2023 19:36:28 +0200 Subject: [PATCH 10/26] update deployment --- .../model_management/deployment_setup.tsx | 66 +++++++++++++++---- .../model_management/model_actions.tsx | 15 +++-- 2 files changed, 63 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx index e2a5ee0be1d59..a74ea8ce1abbe 100644 --- a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx +++ b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx @@ -25,6 +25,7 @@ import { EuiDescribedFormGroup, EuiLink, EuiFieldText, + EuiSelect, } from '@elastic/eui'; import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; import type { Observable } from 'rxjs'; @@ -43,6 +44,8 @@ interface DeploymentSetupProps { config: ThreadingParams; onConfigChange: (config: ThreadingParams) => void; errors: Partial>; + isUpdate?: boolean; + deploymentsParams?: Record; } export interface ThreadingParams { @@ -57,7 +60,13 @@ const THREADS_MAX_EXPONENT = 4; /** * Form for setting threading params. */ -export const DeploymentSetup: FC = ({ config, onConfigChange, errors }) => { +export const DeploymentSetup: FC = ({ + config, + onConfigChange, + errors, + isUpdate, + deploymentsParams, +}) => { const numOfAllocation = config.numOfAllocations; const threadsPerAllocations = config.threadsPerAllocations; @@ -113,14 +122,33 @@ export const DeploymentSetup: FC = ({ config, onConfigChan /> } > - { - onConfigChange({ ...config, deploymentId: e.target.value }); - }} - data-test-subj={'mlModelsStartDeploymentModalDeploymentId'} - /> + {!isUpdate ? ( + { + onConfigChange({ ...config, deploymentId: e.target.value }); + }} + data-test-subj={'mlModelsStartDeploymentModalDeploymentId'} + /> + ) : ( + { + return { text: v, value: v }; + })} + value={config.deploymentId} + onChange={(e) => { + const update = e.target.value; + onConfigChange({ + ...config, + deploymentId: update, + numOfAllocations: deploymentsParams![update].numOfAllocations, + }); + }} + data-test-subj={'mlModelsStartDeploymentModalDeploymentSelectId'} + /> + )} @@ -309,6 +337,8 @@ export const StartUpdateDeploymentModal: FC = ({ startModelDeploymentDocUrl, initialParams, }) => { + const isUpdate = !!initialParams; + const [config, setConfig] = useState( initialParams ?? { numOfAllocations: 1, @@ -318,15 +348,16 @@ export const StartUpdateDeploymentModal: FC = ({ } ); - const isUpdate = initialParams !== undefined; - const deploymentIdValidator = useMemo(() => { + if (isUpdate) { + return () => null; + } return dictionaryValidator([ ...model.deployment_ids, // check for deployment with the default ID ...(model.deployment_ids.includes(model.model_id) ? [''] : []), ]); - }, [model.deployment_ids, model.model_id]); + }, [model.deployment_ids, model.model_id, isUpdate]); const numOfAllocationsValidator = composeValidators( requiredValidator(), @@ -380,7 +411,16 @@ export const StartUpdateDeploymentModal: FC = ({ /> - + { + acc[curr.deployment_id] = { numOfAllocations: curr.number_of_allocations }; + return acc; + }, {} as Record)} + /> diff --git a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx index 4683b3ed12c1d..e35a9118d38b4 100644 --- a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx +++ b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx @@ -213,16 +213,21 @@ export function useModelActions({ !isLoading && !!item.stats?.deployment_stats?.some((v) => v.state === DEPLOYMENT_STATE.STARTED), onClick: async (item) => { - const threadingParams = await getUserInputModelDeploymentParams(item, { - numOfAllocations: item.stats?.deployment_stats?.number_of_allocations!, + const deploymentToUpdate = item.deployment_ids[0]; + + const deploymentParams = await getUserInputModelDeploymentParams(item, { + deploymentId: deploymentToUpdate, + numOfAllocations: item.stats!.deployment_stats.find( + (v) => v.deployment_id === deploymentToUpdate + )!.number_of_allocations, }); - if (!threadingParams) return; + if (!deploymentParams) return; try { onLoading(true); - await trainedModelsApiService.updateModelDeployment(item.model_id, { - number_of_allocations: threadingParams.numOfAllocations, + await trainedModelsApiService.updateModelDeployment(deploymentParams.deploymentId!, { + number_of_allocations: deploymentParams.numOfAllocations, }); displaySuccessToast( i18n.translate('xpack.ml.trainedModels.modelsList.updateSuccess', { From ec7d12dc6878c2617d1d3ba7b1b32ab2b5e9e1b6 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Mon, 24 Apr 2023 20:24:46 +0200 Subject: [PATCH 11/26] fix TS --- .../ml/public/application/model_management/models_list.tsx | 7 ++++--- .../ml/server/models/model_management/memory_usage.ts | 2 ++ 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/models_list.tsx b/x-pack/plugins/ml/public/application/model_management/models_list.tsx index beb655b44e4d4..b2aa9caa0236c 100644 --- a/x-pack/plugins/ml/public/application/model_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/model_management/models_list.tsx @@ -153,11 +153,11 @@ export const ModelsList: FC = ({ type: [ model.model_type, ...Object.keys(model.inference_config), - ...(isBuiltInModel(model) ? [BUILT_IN_MODEL_TYPE] : []), + ...(isBuiltInModel(model as ModelItem) ? [BUILT_IN_MODEL_TYPE] : []), ], } : {}), - }; + } as ModelItem; newItems.push(tableItem); if (itemIdToExpandedRowMap[model.model_id]) { @@ -165,7 +165,8 @@ export const ModelsList: FC = ({ } } - // Need to fetch state for all models to enable/disable actions + // Need to fetch stats for all models to enable/disable actions + // TODO combine fetching models definitions and stats into a single function await fetchModelsStats(newItems); setItems(newItems); diff --git a/x-pack/plugins/ml/server/models/model_management/memory_usage.ts b/x-pack/plugins/ml/server/models/model_management/memory_usage.ts index 29c51055efe5b..f14cf69c0c9e0 100644 --- a/x-pack/plugins/ml/server/models/model_management/memory_usage.ts +++ b/x-pack/plugins/ml/server/models/model_management/memory_usage.ts @@ -199,6 +199,8 @@ export class MemoryUsageService { ...rest, ...modelSizeState, node: nodeRest, + // @ts-ignore + key: `${rest.deployment_id}_${nodeId}`, }; }); From c076dfef65ea1d39272f407e2711252094882683 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 25 Apr 2023 10:56:30 +0200 Subject: [PATCH 12/26] update test tests --- .../__mocks__/mock_deployment_response.json | 4 + .../model_management/memory_usage.test.ts | 82 ++++++++++++------- .../models/model_management/memory_usage.ts | 3 +- 3 files changed, 58 insertions(+), 31 deletions(-) diff --git a/x-pack/plugins/ml/server/models/model_management/__mocks__/mock_deployment_response.json b/x-pack/plugins/ml/server/models/model_management/__mocks__/mock_deployment_response.json index cc36165debe5d..cbeda1e304a16 100644 --- a/x-pack/plugins/ml/server/models/model_management/__mocks__/mock_deployment_response.json +++ b/x-pack/plugins/ml/server/models/model_management/__mocks__/mock_deployment_response.json @@ -7,6 +7,7 @@ }, "pipeline_count" : 0, "deployment_stats": { + "deployment_id": "distilbert-base-uncased-finetuned-sst-2-english", "model_id": "distilbert-base-uncased-finetuned-sst-2-english", "inference_threads": 1, "model_threads": 1, @@ -102,6 +103,7 @@ }, "pipeline_count" : 0, "deployment_stats": { + "deployment_id": "elastic__distilbert-base-cased-finetuned-conll03-english", "model_id": "elastic__distilbert-base-cased-finetuned-conll03-english", "inference_threads": 1, "model_threads": 1, @@ -197,6 +199,7 @@ }, "pipeline_count" : 0, "deployment_stats": { + "deployment_id": "sentence-transformers__msmarco-minilm-l-12-v3", "model_id": "sentence-transformers__msmarco-minilm-l-12-v3", "inference_threads": 1, "model_threads": 1, @@ -292,6 +295,7 @@ }, "pipeline_count" : 0, "deployment_stats": { + "deployment_id": "typeform__mobilebert-uncased-mnli", "model_id": "typeform__mobilebert-uncased-mnli", "inference_threads": 1, "model_threads": 1, diff --git a/x-pack/plugins/ml/server/models/model_management/memory_usage.test.ts b/x-pack/plugins/ml/server/models/model_management/memory_usage.test.ts index e08a94e4d951c..3f85487a4cbf3 100644 --- a/x-pack/plugins/ml/server/models/model_management/memory_usage.test.ts +++ b/x-pack/plugins/ml/server/models/model_management/memory_usage.test.ts @@ -150,7 +150,6 @@ describe('Model service', () => { }, nodes: [ { - name: 'node3', allocated_models: [ { allocation_status: { @@ -158,12 +157,12 @@ describe('Model service', () => { state: 'started', target_allocation_count: 3, }, + deployment_id: 'distilbert-base-uncased-finetuned-sst-2-english', inference_threads: 1, + key: 'distilbert-base-uncased-finetuned-sst-2-english_node3', model_id: 'distilbert-base-uncased-finetuned-sst-2-english', model_size_bytes: 267386880, - required_native_memory_bytes: 534773760, model_threads: 1, - state: 'started', node: { average_inference_time_ms: 0, inference_count: 0, @@ -171,6 +170,8 @@ describe('Model service', () => { routing_state: 'started', }, }, + required_native_memory_bytes: 534773760, + state: 'started', }, { allocation_status: { @@ -178,12 +179,12 @@ describe('Model service', () => { state: 'started', target_allocation_count: 3, }, + deployment_id: 'elastic__distilbert-base-cased-finetuned-conll03-english', inference_threads: 1, + key: 'elastic__distilbert-base-cased-finetuned-conll03-english_node3', model_id: 'elastic__distilbert-base-cased-finetuned-conll03-english', model_size_bytes: 260947500, - required_native_memory_bytes: 521895000, model_threads: 1, - state: 'started', node: { average_inference_time_ms: 0, inference_count: 0, @@ -191,6 +192,8 @@ describe('Model service', () => { routing_state: 'started', }, }, + required_native_memory_bytes: 521895000, + state: 'started', }, { allocation_status: { @@ -198,12 +201,12 @@ describe('Model service', () => { state: 'started', target_allocation_count: 3, }, + deployment_id: 'sentence-transformers__msmarco-minilm-l-12-v3', inference_threads: 1, + key: 'sentence-transformers__msmarco-minilm-l-12-v3_node3', model_id: 'sentence-transformers__msmarco-minilm-l-12-v3', model_size_bytes: 133378867, - required_native_memory_bytes: 266757734, model_threads: 1, - state: 'started', node: { average_inference_time_ms: 0, inference_count: 0, @@ -211,6 +214,8 @@ describe('Model service', () => { routing_state: 'started', }, }, + required_native_memory_bytes: 266757734, + state: 'started', }, { allocation_status: { @@ -218,12 +223,12 @@ describe('Model service', () => { state: 'started', target_allocation_count: 3, }, + deployment_id: 'typeform__mobilebert-uncased-mnli', inference_threads: 1, + key: 'typeform__mobilebert-uncased-mnli_node3', model_id: 'typeform__mobilebert-uncased-mnli', model_size_bytes: 100139008, - required_native_memory_bytes: 200278016, model_threads: 1, - state: 'started', node: { average_inference_time_ms: 0, inference_count: 0, @@ -231,6 +236,8 @@ describe('Model service', () => { routing_state: 'started', }, }, + required_native_memory_bytes: 200278016, + state: 'started', }, ], attributes: { @@ -239,7 +246,6 @@ describe('Model service', () => { }, id: '3qIoLFnbSi-DwVrYioUCdw', memory_overview: { - ml_max_in_bytes: 1073741824, anomaly_detection: { total: 0, }, @@ -250,6 +256,7 @@ describe('Model service', () => { jvm: 1073741824, total: 15599742976, }, + ml_max_in_bytes: 1073741824, trained_models: { by_model: [ { @@ -272,10 +279,10 @@ describe('Model service', () => { total: 1555161790, }, }, + name: 'node3', roles: ['data', 'ingest', 'master', 'ml', 'transform'], }, { - name: 'node2', allocated_models: [ { allocation_status: { @@ -283,18 +290,20 @@ describe('Model service', () => { state: 'started', target_allocation_count: 3, }, + deployment_id: 'distilbert-base-uncased-finetuned-sst-2-english', inference_threads: 1, + key: 'distilbert-base-uncased-finetuned-sst-2-english_node2', model_id: 'distilbert-base-uncased-finetuned-sst-2-english', model_size_bytes: 267386880, - required_native_memory_bytes: 534773760, model_threads: 1, - state: 'started', node: { routing_state: { reason: 'The object cannot be set twice!', routing_state: 'failed', }, }, + required_native_memory_bytes: 534773760, + state: 'started', }, { allocation_status: { @@ -302,18 +311,20 @@ describe('Model service', () => { state: 'started', target_allocation_count: 3, }, + deployment_id: 'elastic__distilbert-base-cased-finetuned-conll03-english', inference_threads: 1, + key: 'elastic__distilbert-base-cased-finetuned-conll03-english_node2', model_id: 'elastic__distilbert-base-cased-finetuned-conll03-english', model_size_bytes: 260947500, - required_native_memory_bytes: 521895000, model_threads: 1, - state: 'started', node: { routing_state: { reason: 'The object cannot be set twice!', routing_state: 'failed', }, }, + required_native_memory_bytes: 521895000, + state: 'started', }, { allocation_status: { @@ -321,18 +332,20 @@ describe('Model service', () => { state: 'started', target_allocation_count: 3, }, + deployment_id: 'sentence-transformers__msmarco-minilm-l-12-v3', inference_threads: 1, + key: 'sentence-transformers__msmarco-minilm-l-12-v3_node2', model_id: 'sentence-transformers__msmarco-minilm-l-12-v3', model_size_bytes: 133378867, - required_native_memory_bytes: 266757734, model_threads: 1, - state: 'started', node: { routing_state: { reason: 'The object cannot be set twice!', routing_state: 'failed', }, }, + required_native_memory_bytes: 266757734, + state: 'started', }, { allocation_status: { @@ -340,18 +353,20 @@ describe('Model service', () => { state: 'started', target_allocation_count: 3, }, + deployment_id: 'typeform__mobilebert-uncased-mnli', inference_threads: 1, + key: 'typeform__mobilebert-uncased-mnli_node2', model_id: 'typeform__mobilebert-uncased-mnli', model_size_bytes: 100139008, - required_native_memory_bytes: 200278016, model_threads: 1, - state: 'started', node: { routing_state: { reason: 'The object cannot be set twice!', routing_state: 'failed', }, }, + required_native_memory_bytes: 200278016, + state: 'started', }, ], attributes: { @@ -360,7 +375,6 @@ describe('Model service', () => { }, id: 'DpCy7SOBQla3pu0Dq-tnYw', memory_overview: { - ml_max_in_bytes: 1073741824, anomaly_detection: { total: 0, }, @@ -371,6 +385,7 @@ describe('Model service', () => { jvm: 1073741824, total: 15599742976, }, + ml_max_in_bytes: 1073741824, trained_models: { by_model: [ { @@ -393,6 +408,7 @@ describe('Model service', () => { total: 1555161790, }, }, + name: 'node2', roles: ['data', 'master', 'ml', 'transform'], }, { @@ -403,12 +419,12 @@ describe('Model service', () => { state: 'started', target_allocation_count: 3, }, + deployment_id: 'distilbert-base-uncased-finetuned-sst-2-english', inference_threads: 1, + key: 'distilbert-base-uncased-finetuned-sst-2-english_node1', model_id: 'distilbert-base-uncased-finetuned-sst-2-english', model_size_bytes: 267386880, - required_native_memory_bytes: 534773760, model_threads: 1, - state: 'started', node: { average_inference_time_ms: 0, inference_count: 0, @@ -416,6 +432,8 @@ describe('Model service', () => { routing_state: 'started', }, }, + required_native_memory_bytes: 534773760, + state: 'started', }, { allocation_status: { @@ -423,12 +441,12 @@ describe('Model service', () => { state: 'started', target_allocation_count: 3, }, + deployment_id: 'elastic__distilbert-base-cased-finetuned-conll03-english', inference_threads: 1, + key: 'elastic__distilbert-base-cased-finetuned-conll03-english_node1', model_id: 'elastic__distilbert-base-cased-finetuned-conll03-english', model_size_bytes: 260947500, - required_native_memory_bytes: 521895000, model_threads: 1, - state: 'started', node: { average_inference_time_ms: 0, inference_count: 0, @@ -436,6 +454,8 @@ describe('Model service', () => { routing_state: 'started', }, }, + required_native_memory_bytes: 521895000, + state: 'started', }, { allocation_status: { @@ -443,12 +463,12 @@ describe('Model service', () => { state: 'started', target_allocation_count: 3, }, + deployment_id: 'sentence-transformers__msmarco-minilm-l-12-v3', inference_threads: 1, + key: 'sentence-transformers__msmarco-minilm-l-12-v3_node1', model_id: 'sentence-transformers__msmarco-minilm-l-12-v3', model_size_bytes: 133378867, - required_native_memory_bytes: 266757734, model_threads: 1, - state: 'started', node: { average_inference_time_ms: 0, inference_count: 0, @@ -456,6 +476,8 @@ describe('Model service', () => { routing_state: 'started', }, }, + required_native_memory_bytes: 266757734, + state: 'started', }, { allocation_status: { @@ -463,12 +485,12 @@ describe('Model service', () => { state: 'started', target_allocation_count: 3, }, + deployment_id: 'typeform__mobilebert-uncased-mnli', inference_threads: 1, + key: 'typeform__mobilebert-uncased-mnli_node1', model_id: 'typeform__mobilebert-uncased-mnli', model_size_bytes: 100139008, - required_native_memory_bytes: 200278016, model_threads: 1, - state: 'started', node: { average_inference_time_ms: 0, inference_count: 0, @@ -476,6 +498,8 @@ describe('Model service', () => { routing_state: 'started', }, }, + required_native_memory_bytes: 200278016, + state: 'started', }, ], attributes: { @@ -484,7 +508,6 @@ describe('Model service', () => { }, id: 'pt7s6lKHQJaP4QHKtU-Q0Q', memory_overview: { - ml_max_in_bytes: 1073741824, anomaly_detection: { total: 0, }, @@ -495,6 +518,7 @@ describe('Model service', () => { jvm: 1073741824, total: 15599742976, }, + ml_max_in_bytes: 1073741824, trained_models: { by_model: [ { diff --git a/x-pack/plugins/ml/server/models/model_management/memory_usage.ts b/x-pack/plugins/ml/server/models/model_management/memory_usage.ts index f14cf69c0c9e0..541b396b0a6e5 100644 --- a/x-pack/plugins/ml/server/models/model_management/memory_usage.ts +++ b/x-pack/plugins/ml/server/models/model_management/memory_usage.ts @@ -199,8 +199,7 @@ export class MemoryUsageService { ...rest, ...modelSizeState, node: nodeRest, - // @ts-ignore - key: `${rest.deployment_id}_${nodeId}`, + key: `${rest.deployment_id}_${node.name}`, }; }); From f3b46b3ba27d4551ace90c28f4e45479f3c3a57a Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 25 Apr 2023 10:57:24 +0200 Subject: [PATCH 13/26] state column --- .../model_management/models_list.tsx | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/models_list.tsx b/x-pack/plugins/ml/public/application/model_management/models_list.tsx index b2aa9caa0236c..b22997f1d7512 100644 --- a/x-pack/plugins/ml/public/application/model_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/model_management/models_list.tsx @@ -27,7 +27,11 @@ import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { usePageUrlState } from '@kbn/ml-url-state'; import { useTimefilter } from '@kbn/ml-date-picker'; -import { BUILT_IN_MODEL_TYPE, BUILT_IN_MODEL_TAG } from '@kbn/ml-trained-models-utils'; +import { + BUILT_IN_MODEL_TYPE, + BUILT_IN_MODEL_TAG, + DEPLOYMENT_STATE, +} from '@kbn/ml-trained-models-utils'; import { isDefined } from '@kbn/ml-is-defined'; import { useModelActions } from './model_actions'; import { ModelsTableToConfigMapping } from '.'; @@ -355,20 +359,22 @@ export const ModelsList: FC = ({ ), 'data-test-subj': 'mlModelsTableColumnType', }, - // FIXME show combined deploymnetss state - // { - // name: i18n.translate('xpack.ml.trainedModels.modelsList.stateHeader', { - // defaultMessage: 'State', - // }), - // sortable: (item) => item.stats?.deployment_stats?.state, - // align: 'left', - // truncateText: true, - // render: (model: ModelItem) => { - // const state = model.stats?.deployment_stats?.state; - // return state ? {state} : null; - // }, - // 'data-test-subj': 'mlModelsTableColumnDeploymentState', - // }, + { + name: i18n.translate('xpack.ml.trainedModels.modelsList.stateHeader', { + defaultMessage: 'State', + }), + align: 'left', + truncateText: false, + render: (model: ModelItem) => { + const state = model.stats?.deployment_stats?.some( + (v) => v.state === DEPLOYMENT_STATE.STARTED + ) + ? DEPLOYMENT_STATE.STARTED + : ''; + return state ? {state} : null; + }, + 'data-test-subj': 'mlModelsTableColumnDeploymentState', + }, { field: ModelsTableToConfigMapping.createdAt, name: i18n.translate('xpack.ml.trainedModels.modelsList.createdAtHeader', { From 9a38d07b794df2a7451f30e70b9233840590bda1 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 25 Apr 2023 11:22:40 +0200 Subject: [PATCH 14/26] fix i18n --- x-pack/plugins/translations/translations/fr-FR.json | 1 - x-pack/plugins/translations/translations/ja-JP.json | 1 - x-pack/plugins/translations/translations/zh-CN.json | 1 - 3 files changed, 3 deletions(-) diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json index 290368a66b4ed..b34b8e7aa8b97 100644 --- a/x-pack/plugins/translations/translations/fr-FR.json +++ b/x-pack/plugins/translations/translations/fr-FR.json @@ -21399,7 +21399,6 @@ "xpack.ml.timeSeriesExplorer.timeSeriesChart.zoomAggregationIntervalLabel": "(intervalle d'agrégation : {focusAggInt}, étendue du compartiment : {bucketSpan})", "xpack.ml.trainedModels.modelsList.deleteModal.header": "Supprimer {modelsCount, plural, one {{modelId}} other {# modèles}} ?", "xpack.ml.trainedModels.modelsList.fetchDeletionErrorMessage": "La suppression {modelsCount, plural, one {du modèle} other {des modèles}} a échoué", - "xpack.ml.trainedModels.modelsList.forceStopDialog.title": "Arrêter le modèle {modelId} ?", "xpack.ml.trainedModels.modelsList.selectedModelsMessage": "{modelsCount, plural, one {# modèle sélectionné} other {# modèles sélectionnés}}", "xpack.ml.trainedModels.modelsList.startDeployment.modalTitle": "Démarrer le déploiement de {modelId}", "xpack.ml.trainedModels.modelsList.startFailed": "Impossible de démarrer \"{modelId}\"", diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index c1c8d3f14fe4e..5f43761f62498 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -21386,7 +21386,6 @@ "xpack.ml.timeSeriesExplorer.timeSeriesChart.updatedAnnotationNotificationMessage": "ID {jobId}のジョブの注釈が更新されました。", "xpack.ml.timeSeriesExplorer.timeSeriesChart.zoomAggregationIntervalLabel": "(アグリゲーション間隔:{focusAggInt}、バケットスパン:{bucketSpan})", "xpack.ml.trainedModels.modelsList.fetchDeletionErrorMessage": "{modelsCount, plural, other {モデル}}の削除が失敗しました", - "xpack.ml.trainedModels.modelsList.forceStopDialog.title": "モデル{modelId}を停止しますか?", "xpack.ml.trainedModels.modelsList.selectedModelsMessage": "{modelsCount, plural, other {#個のモデル}}を選択済み", "xpack.ml.trainedModels.modelsList.startDeployment.modalTitle": "{modelId}デプロイを開始", "xpack.ml.trainedModels.modelsList.startFailed": "\"{modelId}\"の開始に失敗しました", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 40989bc638cc5..8f839a942b766 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -21397,7 +21397,6 @@ "xpack.ml.timeSeriesExplorer.timeSeriesChart.zoomAggregationIntervalLabel": "(聚合时间间隔:{focusAggInt},存储桶跨度:{bucketSpan})", "xpack.ml.trainedModels.modelsList.deleteModal.header": "删除 {modelsCount, plural, one {{modelId}} other {# 个模型}}?", "xpack.ml.trainedModels.modelsList.fetchDeletionErrorMessage": "{modelsCount, plural, other {模型}}删除失败", - "xpack.ml.trainedModels.modelsList.forceStopDialog.title": "停止模型 {modelId}", "xpack.ml.trainedModels.modelsList.selectedModelsMessage": "{modelsCount, plural, other {# 个模型}}已选择", "xpack.ml.trainedModels.modelsList.startDeployment.modalTitle": "启动 {modelId} 部署", "xpack.ml.trainedModels.modelsList.startFailed": "无法启动“{modelId}”", From 580b3f0a2b20a8a1844d5108c84233b088430b41 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 25 Apr 2023 12:20:48 +0200 Subject: [PATCH 15/26] Update x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: István Zoltán Szabó --- .../ml/public/application/model_management/deployment_setup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx index a74ea8ce1abbe..75a002f2fc426 100644 --- a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx +++ b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx @@ -118,7 +118,7 @@ export const DeploymentSetup: FC = ({ error={ } > From d87fa7a832252a813b662e5ce24215caf5b08152 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 25 Apr 2023 12:20:56 +0200 Subject: [PATCH 16/26] Update x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: István Zoltán Szabó --- .../ml/public/application/model_management/deployment_setup.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx index 75a002f2fc426..2c15df0068086 100644 --- a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx +++ b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx @@ -102,7 +102,7 @@ export const DeploymentSetup: FC = ({ description={ } > From 27fb400bf94c4897d834b3df9a3ff7bc90ce45d1 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 25 Apr 2023 13:53:15 +0200 Subject: [PATCH 17/26] pipelines for deployments --- .../model_management/expanded_row.tsx | 2 +- .../model_management/pipelines/pipelines.tsx | 5 +-- .../ml/server/routes/trained_models.ts | 39 +++++++++++++++---- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/model_management/expanded_row.tsx index 7c52e441306f7..0561ca742568a 100644 --- a/x-pack/plugins/ml/public/application/model_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/model_management/expanded_row.tsx @@ -385,7 +385,7 @@ export const ExpandedRow: FC = ({ item }) => { id="xpack.ml.trainedModels.modelsList.expandedRow.pipelinesTabLabel" defaultMessage="Pipelines" />{' '} - {stats.pipeline_count} + {Object.keys(pipelines!).length} ), content: ( diff --git a/x-pack/plugins/ml/public/application/model_management/pipelines/pipelines.tsx b/x-pack/plugins/ml/public/application/model_management/pipelines/pipelines.tsx index ac8156ae0053b..bd80da5e5e6e3 100644 --- a/x-pack/plugins/ml/public/application/model_management/pipelines/pipelines.tsx +++ b/x-pack/plugins/ml/public/application/model_management/pipelines/pipelines.tsx @@ -45,9 +45,8 @@ export const ModelPipelines: FC = ({ pipelines, ingestStats const pipelineDefinition = pipelines?.[pipelineName]; return ( - <> + @@ -123,7 +122,7 @@ export const ModelPipelines: FC = ({ pipelines, ingestStats ) : null} - + ); })} diff --git a/x-pack/plugins/ml/server/routes/trained_models.ts b/x-pack/plugins/ml/server/routes/trained_models.ts index c09b46f7c23cb..06a0849488a9c 100644 --- a/x-pack/plugins/ml/server/routes/trained_models.ts +++ b/x-pack/plugins/ml/server/routes/trained_models.ts @@ -10,13 +10,13 @@ import { RouteInitialization } from '../types'; import { wrapError } from '../client/error_wrapper'; import { getInferenceQuerySchema, + inferTrainedModelBody, + inferTrainedModelQuery, modelIdSchema, optionalModelIdSchema, + pipelineSimulateBody, putTrainedModelQuerySchema, - inferTrainedModelQuery, - inferTrainedModelBody, threadingParamsSchema, - pipelineSimulateBody, updateDeploymentParamsSchema, } from './schemas/inference_schema'; @@ -59,14 +59,33 @@ export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) const result = body.trained_model_configs as TrainedModelConfigResponse[]; try { if (withPipelines) { + // Also need to retrieve the list of deployment IDs from stats + const stats = await mlClient.getTrainedModelsStats({ + ...(modelId ? { model_id: modelId } : {}), + size: 10000, + }); + + const modelDeploymentsMap = stats.trained_model_stats.reduce((acc, curr) => { + if (!curr.deployment_stats) return acc; + // @ts-ignore elasticsearch-js client is missing deployment_id + const deploymentId = curr.deployment_stats.deployment_id; + if (acc[curr.model_id]) { + acc[curr.model_id].push(deploymentId); + } else { + acc[curr.model_id] = [deploymentId]; + } + return acc; + }, {} as Record); + const modelIdsAndAliases: string[] = Array.from( - new Set( - result + new Set([ + ...result .map(({ model_id: id, metadata }) => { return [id, ...(metadata?.model_aliases ?? [])]; }) - .flat() - ) + .flat(), + ...Object.values(modelDeploymentsMap).flat(), + ]) ); const pipelinesResponse = await modelsProvider(client).getModelsPipelines( @@ -81,6 +100,12 @@ export function trainedModelsRoutes({ router, routeGuard }: RouteInitialization) ...(pipelinesResponse.get(alias) ?? {}), }; }, {}), + ...(modelDeploymentsMap[model.model_id] ?? []).reduce((acc, deploymentId) => { + return { + ...acc, + ...(pipelinesResponse.get(deploymentId) ?? {}), + }; + }, {}), }; } } From 26c18f3ea144fd1845799902eb8df900969fa654 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 25 Apr 2023 14:02:34 +0200 Subject: [PATCH 18/26] show placeholder --- .../model_management/deployment_setup.tsx | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx index 2c15df0068086..99f229d963011 100644 --- a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx +++ b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx @@ -5,27 +5,27 @@ * 2.0. */ -import React, { FC, useState, useMemo } from 'react'; +import React, { FC, useMemo, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n-react'; import { - EuiForm, + EuiButton, + EuiButtonEmpty, EuiButtonGroup, - EuiFormRow, + EuiCallOut, + EuiDescribedFormGroup, EuiFieldNumber, + EuiFieldText, + EuiForm, + EuiFormRow, + EuiLink, EuiModal, - EuiModalHeader, - EuiModalHeaderTitle, EuiModalBody, EuiModalFooter, - EuiButtonEmpty, - EuiButton, - EuiCallOut, - EuiSpacer, - EuiDescribedFormGroup, - EuiLink, - EuiFieldText, + EuiModalHeader, + EuiModalHeaderTitle, EuiSelect, + EuiSpacer, } from '@elastic/eui'; import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; import type { Observable } from 'rxjs'; @@ -70,6 +70,11 @@ export const DeploymentSetup: FC = ({ const numOfAllocation = config.numOfAllocations; const threadsPerAllocations = config.threadsPerAllocations; + const defaultDeploymentId = useMemo(() => { + return config.deploymentId; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + const threadsPerAllocationsOptions = useMemo( () => new Array(THREADS_MAX_EXPONENT).fill(null).map((v, i) => { @@ -124,6 +129,7 @@ export const DeploymentSetup: FC = ({ > {!isUpdate ? ( { From 99a84e8370e549a79ce1b3b9413fe3ebd0b59d5f Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 25 Apr 2023 14:33:58 +0200 Subject: [PATCH 19/26] validate deployment id against all existing --- .../model_management/deployment_setup.tsx | 16 +++++++-- .../model_management/model_actions.tsx | 35 +++++++++++-------- .../model_management/models_list.tsx | 8 +++++ 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx index 99f229d963011..8f251f8ee5827 100644 --- a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx +++ b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx @@ -331,6 +331,7 @@ interface StartDeploymentModalProps { onConfigChange: (config: ThreadingParams) => void; onClose: () => void; initialParams?: ThreadingParams; + modelAndDeploymentIds?: string[]; } /** @@ -342,6 +343,7 @@ export const StartUpdateDeploymentModal: FC = ({ onClose, startModelDeploymentDocUrl, initialParams, + modelAndDeploymentIds, }) => { const isUpdate = !!initialParams; @@ -358,12 +360,17 @@ export const StartUpdateDeploymentModal: FC = ({ if (isUpdate) { return () => null; } + + const otherModelAndDeploymentIds = [...(modelAndDeploymentIds ?? [])]; + otherModelAndDeploymentIds.splice(otherModelAndDeploymentIds?.indexOf(model.model_id), 1); + return dictionaryValidator([ ...model.deployment_ids, + ...otherModelAndDeploymentIds, // check for deployment with the default ID ...(model.deployment_ids.includes(model.model_id) ? [''] : []), ]); - }, [model.deployment_ids, model.model_id, isUpdate]); + }, [modelAndDeploymentIds, model.deployment_ids, model.model_id, isUpdate]); const numOfAllocationsValidator = composeValidators( requiredValidator(), @@ -488,7 +495,11 @@ export const StartUpdateDeploymentModal: FC = ({ */ export const getUserInputModelDeploymentParamsProvider = (overlays: OverlayStart, theme$: Observable, startModelDeploymentDocUrl: string) => - (model: ModelItem, initialParams?: ThreadingParams): Promise => { + ( + model: ModelItem, + initialParams?: ThreadingParams, + deploymentIds?: string[] + ): Promise => { return new Promise(async (resolve) => { try { const modalSession = overlays.openModal( @@ -497,6 +508,7 @@ export const getUserInputModelDeploymentParamsProvider = { modalSession.close(); diff --git a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx index e35a9118d38b4..60162a731181e 100644 --- a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx +++ b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx @@ -11,9 +11,9 @@ import { isPopulatedObject } from '@kbn/ml-is-populated-object'; import { EuiToolTip } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import { + BUILT_IN_MODEL_TAG, DEPLOYMENT_STATE, TRAINED_MODEL_TYPE, - BUILT_IN_MODEL_TAG, } from '@kbn/ml-trained-models-utils'; import { useTrainedModelsApiService } from '../services/ml_api_service/trained_models'; import { getUserConfirmationProvider } from './force_stop_dialog'; @@ -32,12 +32,14 @@ export function useModelActions({ onLoading, isLoading, fetchModels, + modelAndDeploymentIds, }: { isLoading: boolean; onTestAction: (model: ModelItem) => void; onModelsDeleteRequest: (modelsIds: string[]) => void; onLoading: (isLoading: boolean) => void; fetchModels: () => void; + modelAndDeploymentIds: string[]; }): Array> { const { services: { @@ -156,7 +158,11 @@ export function useModelActions({ }, available: (item) => item.model_type === TRAINED_MODEL_TYPE.PYTORCH, onClick: async (item) => { - const modelDeploymentParams = await getUserInputModelDeploymentParams(item); + const modelDeploymentParams = await getUserInputModelDeploymentParams( + item, + undefined, + modelAndDeploymentIds + ); if (!modelDeploymentParams) return; @@ -368,23 +374,24 @@ export function useModelActions({ }, ], [ - canDeleteTrainedModels, + urlLocator, + navigateToUrl, + navigateToPath, canStartStopTrainedModels, - canTestTrainedModels, - displayErrorToast, + isLoading, + getUserInputModelDeploymentParams, + modelAndDeploymentIds, + onLoading, + trainedModelsApiService, displaySuccessToast, + fetchModels, + displayErrorToast, getUserConfirmation, - getUserInputModelDeploymentParams, + onModelsDeleteRequest, + canDeleteTrainedModels, isBuiltInModel, - navigateToPath, - navigateToUrl, onTestAction, - trainedModelsApiService, - urlLocator, - onModelsDeleteRequest, - onLoading, - fetchModels, - isLoading, + canTestTrainedModels, ] ); } diff --git a/x-pack/plugins/ml/public/application/model_management/models_list.tsx b/x-pack/plugins/ml/public/application/model_management/models_list.tsx index b22997f1d7512..5887dc05c5369 100644 --- a/x-pack/plugins/ml/public/application/model_management/models_list.tsx +++ b/x-pack/plugins/ml/public/application/model_management/models_list.tsx @@ -275,6 +275,13 @@ export const ModelsList: FC = ({ })); }, [items]); + const modelAndDeploymentIds = useMemo( + () => [ + ...new Set([...items.flatMap((v) => v.deployment_ids), ...items.map((i) => i.model_id)]), + ], + [items] + ); + /** * Table actions */ @@ -284,6 +291,7 @@ export const ModelsList: FC = ({ onTestAction: setModelToTest, onModelsDeleteRequest: setModelIdsToDelete, onLoading: setIsLoading, + modelAndDeploymentIds, }); const toggleDetails = async (item: ModelItem) => { From 47c791cf47768024aadc3f17e34cbe9e1f164da7 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 25 Apr 2023 15:22:34 +0200 Subject: [PATCH 20/26] warning for stopping selected deployments with pipelines --- .../model_management/force_stop_dialog.tsx | 100 +++++++++++++----- .../model_management/pipelines/pipelines.tsx | 4 +- 2 files changed, 74 insertions(+), 30 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx b/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx index 07b69cb07fd18..8950206359da4 100644 --- a/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx +++ b/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx @@ -5,13 +5,20 @@ * 2.0. */ -import React, { type FC, useState } from 'react'; -import { EuiCheckboxGroup, EuiCheckboxGroupOption, EuiConfirmModal } from '@elastic/eui'; +import React, { type FC, useState, useMemo } from 'react'; +import { + EuiCallOut, + EuiCheckboxGroup, + EuiCheckboxGroupOption, + EuiConfirmModal, + EuiSpacer, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n-react'; import { i18n } from '@kbn/i18n'; import type { OverlayStart, ThemeServiceStart } from '@kbn/core/public'; import { toMountPoint, wrapWithTheme } from '@kbn/kibana-react-plugin/public'; import { isPopulatedObject } from '@kbn/ml-is-populated-object'; +import { isDefined } from '@kbn/ml-is-defined'; import type { ModelItem } from './models_list'; interface ForceStopModelConfirmDialogProps { @@ -53,6 +60,33 @@ export const StopModelDeploymentsConfirmDialog: FC checkboxIdToSelectedMap[id] ); + const deploymentPipelinesMap = useMemo(() => { + if (!isPopulatedObject(model.pipelines)) return {}; + return Object.entries(model.pipelines).reduce((acc, [pipelineId, pipelineDef]) => { + const deploymentIds: string[] = (pipelineDef?.processors ?? []) + .map((v) => v?.inference?.model_id) + .filter(isDefined); + deploymentIds.forEach((dId) => { + if (acc[dId]) { + acc[dId].push(pipelineId); + } else { + acc[dId] = [pipelineId]; + } + }); + return acc; + }, {} as Record); + }, [model.pipelines]); + + const pipelineWarning = useMemo(() => { + return [ + ...new Set( + Object.entries(deploymentPipelinesMap) + .filter(([deploymentId]) => selectedDeploymentIds.includes(deploymentId)) + .flatMap(([, pipelineNames]) => pipelineNames) + ), + ].sort(); + }, [deploymentPipelinesMap, selectedDeploymentIds]); + return ( 1 && selectedDeploymentIds.length === 0} > {model.deployment_ids.length > 1 ? ( - - ), - }} - options={options} - idToSelectedMap={checkboxIdToSelectedMap} - onChange={onChange} - /> + <> + + ), + }} + options={options} + idToSelectedMap={checkboxIdToSelectedMap} + onChange={onChange} + /> + + ) : null} - {isPopulatedObject(model.pipelines) ? ( + {pipelineWarning.length > 0 ? ( <> - -
    - {Object.keys(model.pipelines!) - .sort() - .map((pipelineName) => { - return
  • {pipelineName}
  • ; - })} -
+ + } + color="warning" + iconType="warning" + > +

+

    + {pipelineWarning.map((pipelineName) => { + return
  • {pipelineName}
  • ; + })} +
+

+
) : null}
diff --git a/x-pack/plugins/ml/public/application/model_management/pipelines/pipelines.tsx b/x-pack/plugins/ml/public/application/model_management/pipelines/pipelines.tsx index bd80da5e5e6e3..7eb20d77ec8d1 100644 --- a/x-pack/plugins/ml/public/application/model_management/pipelines/pipelines.tsx +++ b/x-pack/plugins/ml/public/application/model_management/pipelines/pipelines.tsx @@ -80,7 +80,7 @@ export const ModelPipelines: FC = ({ pipelines, ingestStats initialIsOpen={initialIsOpen} > - {ingestStats?.pipelines ? ( + {ingestStats!.pipelines[pipelineName]?.processors ? ( @@ -92,7 +92,7 @@ export const ModelPipelines: FC = ({ pipelines, ingestStats - + ) : null} From 43df748f912fc13702895f5b903b3d11f3595b1f Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 25 Apr 2023 15:25:16 +0200 Subject: [PATCH 21/26] update message --- .../public/application/model_management/force_stop_dialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx b/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx index 8950206359da4..f276bf8b617f7 100644 --- a/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx +++ b/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx @@ -133,7 +133,7 @@ export const StopModelDeploymentsConfirmDialog: FC } color="warning" From e292b7d92591312bf633a3c95142f6498192ed44 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 25 Apr 2023 16:17:00 +0200 Subject: [PATCH 22/26] fix force stopping single deployment --- .../model_management/force_stop_dialog.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx b/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx index f276bf8b617f7..303d3bafb0398 100644 --- a/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx +++ b/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx @@ -56,9 +56,10 @@ export const StopModelDeploymentsConfirmDialog: FC checkboxIdToSelectedMap[id] - ); + const selectedDeploymentIds = + model.deployment_ids.length > 1 + ? Object.keys(checkboxIdToSelectedMap).filter((id) => checkboxIdToSelectedMap[id]) + : model.deployment_ids; const deploymentPipelinesMap = useMemo(() => { if (!isPopulatedObject(model.pipelines)) return {}; @@ -78,6 +79,9 @@ export const StopModelDeploymentsConfirmDialog: FC(() => { + if (model.deployment_ids.length === 1 && isPopulatedObject(model.pipelines)) { + return Object.keys(model.pipelines); + } return [ ...new Set( Object.entries(deploymentPipelinesMap) @@ -85,7 +89,7 @@ export const StopModelDeploymentsConfirmDialog: FC pipelineNames) ), ].sort(); - }, [deploymentPipelinesMap, selectedDeploymentIds]); + }, [model, deploymentPipelinesMap, selectedDeploymentIds]); return ( Date: Tue, 25 Apr 2023 18:57:52 +0200 Subject: [PATCH 23/26] fix pipelines counter --- .../public/application/model_management/expanded_row.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/expanded_row.tsx b/x-pack/plugins/ml/public/application/model_management/expanded_row.tsx index 0561ca742568a..0fd2068d8bcf3 100644 --- a/x-pack/plugins/ml/public/application/model_management/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/model_management/expanded_row.tsx @@ -374,7 +374,7 @@ export const ExpandedRow: FC = ({ item }) => { }, ] : []), - ...((pipelines && Object.keys(pipelines).length > 0) || stats.ingest + ...((isPopulatedObject(pipelines) && Object.keys(pipelines).length > 0) || stats.ingest ? [ { id: 'pipelines', @@ -384,8 +384,10 @@ export const ExpandedRow: FC = ({ item }) => { {' '} - {Object.keys(pipelines!).length} + /> + + {isPopulatedObject(pipelines) ? Object.keys(pipelines!).length : 0} + ), content: ( From 4212247bf5988e997837b367e26a2f49fe616545 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 25 Apr 2023 19:02:56 +0200 Subject: [PATCH 24/26] minor refactoring with hooks --- .../model_management/deployment_setup.tsx | 11 ++++--- .../model_management/force_stop_dialog.tsx | 33 +++++++++++-------- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx index 8f251f8ee5827..622fb0a961183 100644 --- a/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx +++ b/x-pack/plugins/ml/public/application/model_management/deployment_setup.tsx @@ -429,10 +429,13 @@ export const StartUpdateDeploymentModal: FC = ({ onConfigChange={setConfig} errors={errors} isUpdate={isUpdate} - deploymentsParams={model.stats?.deployment_stats.reduce((acc, curr) => { - acc[curr.deployment_id] = { numOfAllocations: curr.number_of_allocations }; - return acc; - }, {} as Record)} + deploymentsParams={model.stats?.deployment_stats.reduce>( + (acc, curr) => { + acc[curr.deployment_id] = { numOfAllocations: curr.number_of_allocations }; + return acc; + }, + {} + )} /> diff --git a/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx b/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx index 303d3bafb0398..39b108127b127 100644 --- a/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx +++ b/x-pack/plugins/ml/public/application/model_management/force_stop_dialog.tsx @@ -5,7 +5,7 @@ * 2.0. */ -import React, { type FC, useState, useMemo } from 'react'; +import React, { type FC, useState, useMemo, useCallback } from 'react'; import { EuiCallOut, EuiCheckboxGroup, @@ -40,26 +40,33 @@ export const StopModelDeploymentsConfirmDialog: FC { - return { - id: deploymentId, - label: deploymentId, - }; - }); + const options: EuiCheckboxGroupOption[] = useMemo( + () => + model.deployment_ids.map((deploymentId) => { + return { + id: deploymentId, + label: deploymentId, + }; + }), + [model.deployment_ids] + ); - const onChange = (id: string) => { + const onChange = useCallback((id: string) => { setCheckboxIdToSelectedMap((prev) => { return { ...prev, [id]: !prev[id], }; }); - }; + }, []); - const selectedDeploymentIds = - model.deployment_ids.length > 1 - ? Object.keys(checkboxIdToSelectedMap).filter((id) => checkboxIdToSelectedMap[id]) - : model.deployment_ids; + const selectedDeploymentIds = useMemo( + () => + model.deployment_ids.length > 1 + ? Object.keys(checkboxIdToSelectedMap).filter((id) => checkboxIdToSelectedMap[id]) + : model.deployment_ids, + [model.deployment_ids, checkboxIdToSelectedMap] + ); const deploymentPipelinesMap = useMemo(() => { if (!isPopulatedObject(model.pipelines)) return {}; From c8ccef8e19b22e75073e3f39b514c2d675263f08 Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 25 Apr 2023 19:26:35 +0200 Subject: [PATCH 25/26] fallback to model id for test model pipeline generation --- .../model_management/model_actions.tsx | 2 +- .../test_models/models/inference_base.ts | 11 +++---- .../test_models/selected_model.tsx | 30 ++++++++++--------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx index 60162a731181e..8fdb50f7b40f3 100644 --- a/x-pack/plugins/ml/public/application/model_management/model_actions.tsx +++ b/x-pack/plugins/ml/public/application/model_management/model_actions.tsx @@ -370,7 +370,7 @@ export function useModelActions({ isPrimary: true, available: isTestable, onClick: (item) => onTestAction(item), - enabled: (item) => canTestTrainedModels && isTestable(item, true), + enabled: (item) => canTestTrainedModels && isTestable(item, true) && !isLoading, }, ], [ diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/models/inference_base.ts b/x-pack/plugins/ml/public/application/model_management/test_models/models/inference_base.ts index 6e5d47c2be29c..ba7850fb9a8cb 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/models/inference_base.ts +++ b/x-pack/plugins/ml/public/application/model_management/test_models/models/inference_base.ts @@ -88,14 +88,11 @@ export abstract class InferenceBase { this.subscriptions$.unsubscribe(); } - public set deploymentId(deploymentId: string) { + public set deploymentId(deploymentId: string | null) { this._deploymentId = deploymentId; } - public get deploymentId() { - if (!this._deploymentId) { - throw new Error('Deployment ID is required'); - } + public get deploymentId(): string | null { return this._deploymentId; } @@ -256,7 +253,7 @@ export abstract class InferenceBase { ): estypes.IngestProcessorContainer[] { const processor: estypes.IngestProcessorContainer = { inference: { - model_id: this.deploymentId, + model_id: this.deploymentId ?? this.model.model_id, target_field: this.inferenceType, field_map: { [this.inputField$.getValue()]: this.modelInputField, @@ -290,7 +287,7 @@ export abstract class InferenceBase { const inferenceConfig = getInferenceConfig(); const resp = (await this.trainedModelsApi.inferTrainedModel( - this.deploymentId, + this.deploymentId ?? this.model.model_id, { docs: this.getInferDocs(), ...(inferenceConfig ? { inference_config: inferenceConfig } : {}), diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/selected_model.tsx b/x-pack/plugins/ml/public/application/model_management/test_models/selected_model.tsx index 84a6260ca7405..ec79f3aa61a27 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/selected_model.tsx +++ b/x-pack/plugins/ml/public/application/model_management/test_models/selected_model.tsx @@ -35,42 +35,44 @@ interface Props { export const SelectedModel: FC = ({ model, inputType, deploymentId }) => { const { trainedModels } = useMlApiContext(); - const inferrer: InferrerType | undefined = useMemo(() => { + const inferrer = useMemo(() => { + let newInferrer: InferrerType | undefined; + if (model.model_type === TRAINED_MODEL_TYPE.PYTORCH) { const taskType = Object.keys(model.inference_config)[0]; switch (taskType) { case SUPPORTED_PYTORCH_TASKS.NER: - return new NerInference(trainedModels, model, inputType); + newInferrer = new NerInference(trainedModels, model, inputType); break; case SUPPORTED_PYTORCH_TASKS.TEXT_CLASSIFICATION: - return new TextClassificationInference(trainedModels, model, inputType); + newInferrer = new TextClassificationInference(trainedModels, model, inputType); break; case SUPPORTED_PYTORCH_TASKS.ZERO_SHOT_CLASSIFICATION: - return new ZeroShotClassificationInference(trainedModels, model, inputType); + newInferrer = new ZeroShotClassificationInference(trainedModels, model, inputType); break; case SUPPORTED_PYTORCH_TASKS.TEXT_EMBEDDING: - return new TextEmbeddingInference(trainedModels, model, inputType); + newInferrer = new TextEmbeddingInference(trainedModels, model, inputType); break; case SUPPORTED_PYTORCH_TASKS.FILL_MASK: - return new FillMaskInference(trainedModels, model, inputType); + newInferrer = new FillMaskInference(trainedModels, model, inputType); break; case SUPPORTED_PYTORCH_TASKS.QUESTION_ANSWERING: - return new QuestionAnsweringInference(trainedModels, model, inputType); + newInferrer = new QuestionAnsweringInference(trainedModels, model, inputType); break; - default: break; } } else if (model.model_type === TRAINED_MODEL_TYPE.LANG_IDENT) { - return new LangIdentInference(trainedModels, model, inputType); + newInferrer = new LangIdentInference(trainedModels, model, inputType); } - }, [inputType, model, trainedModels]); - useEffect(() => { - if (!inferrer) return; - inferrer.deploymentId = deploymentId; - }, [deploymentId, inferrer]); + if (newInferrer) { + newInferrer.deploymentId = deploymentId; + } + + return newInferrer; + }, [inputType, model, trainedModels, deploymentId]); useEffect(() => { return () => { From 9326a23d25c4b231e92f6ac20cd0e4b39893241d Mon Sep 17 00:00:00 2001 From: Dima Arnautov Date: Tue, 25 Apr 2023 19:39:47 +0200 Subject: [PATCH 26/26] pass deployment id to constructor --- .../test_models/models/inference_base.ts | 11 ++-------- .../test_models/models/ner/ner_inference.ts | 5 +++-- .../question_answering_inference.ts | 5 +++-- .../fill_mask_inference.ts | 5 +++-- .../lang_ident_inference.ts | 5 +++-- .../text_classification_inference.ts | 5 +++-- .../zero_shot_classification_inference.ts | 5 +++-- .../text_embedding_inference.ts | 5 +++-- .../test_models/selected_model.tsx | 22 ++++++------------- 9 files changed, 30 insertions(+), 38 deletions(-) diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/models/inference_base.ts b/x-pack/plugins/ml/public/application/model_management/test_models/models/inference_base.ts index ba7850fb9a8cb..2eff762343077 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/models/inference_base.ts +++ b/x-pack/plugins/ml/public/application/model_management/test_models/models/inference_base.ts @@ -78,7 +78,8 @@ export abstract class InferenceBase { constructor( protected readonly trainedModelsApi: ReturnType, protected readonly model: estypes.MlTrainedModelConfig, - protected readonly inputType: INPUT_TYPE + protected readonly inputType: INPUT_TYPE, + protected readonly deploymentId: string ) { this.modelInputField = model.input?.field_names[0] ?? DEFAULT_INPUT_FIELD; this.inputField$.next(this.modelInputField); @@ -88,14 +89,6 @@ export abstract class InferenceBase { this.subscriptions$.unsubscribe(); } - public set deploymentId(deploymentId: string | null) { - this._deploymentId = deploymentId; - } - - public get deploymentId(): string | null { - return this._deploymentId; - } - protected initialize( additionalValidators?: Array>, additionalChanges?: Array> diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/models/ner/ner_inference.ts b/x-pack/plugins/ml/public/application/model_management/test_models/models/ner/ner_inference.ts index f01127e94cda6..15cdec114aad4 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/models/ner/ner_inference.ts +++ b/x-pack/plugins/ml/public/application/model_management/test_models/models/ner/ner_inference.ts @@ -36,9 +36,10 @@ export class NerInference extends InferenceBase { constructor( trainedModelsApi: ReturnType, model: estypes.MlTrainedModelConfig, - inputType: INPUT_TYPE + inputType: INPUT_TYPE, + deploymentId: string ) { - super(trainedModelsApi, model, inputType); + super(trainedModelsApi, model, inputType, deploymentId); this.initialize(); } diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/models/question_answering/question_answering_inference.ts b/x-pack/plugins/ml/public/application/model_management/test_models/models/question_answering/question_answering_inference.ts index 6c80703340d1d..b428442f8908d 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/models/question_answering/question_answering_inference.ts +++ b/x-pack/plugins/ml/public/application/model_management/test_models/models/question_answering/question_answering_inference.ts @@ -62,9 +62,10 @@ export class QuestionAnsweringInference extends InferenceBase, model: estypes.MlTrainedModelConfig, - inputType: INPUT_TYPE + inputType: INPUT_TYPE, + deploymentId: string ) { - super(trainedModelsApi, model, inputType); + super(trainedModelsApi, model, inputType, deploymentId); this.initialize( [this.questionText$.pipe(map((questionText) => questionText !== ''))], diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/fill_mask_inference.ts b/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/fill_mask_inference.ts index 2ec63d4453288..0c109292f16f1 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/fill_mask_inference.ts +++ b/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/fill_mask_inference.ts @@ -34,9 +34,10 @@ export class FillMaskInference extends InferenceBase constructor( trainedModelsApi: ReturnType, model: estypes.MlTrainedModelConfig, - inputType: INPUT_TYPE + inputType: INPUT_TYPE, + deploymentId: string ) { - super(trainedModelsApi, model, inputType); + super(trainedModelsApi, model, inputType, deploymentId); this.initialize([ this.inputText$.pipe(map((inputText) => inputText.every((t) => t.includes(MASK)))), diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/lang_ident_inference.ts b/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/lang_ident_inference.ts index ff6565de32b19..198d30f42b677 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/lang_ident_inference.ts +++ b/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/lang_ident_inference.ts @@ -30,9 +30,10 @@ export class LangIdentInference extends InferenceBase, model: estypes.MlTrainedModelConfig, - inputType: INPUT_TYPE + inputType: INPUT_TYPE, + deploymentId: string ) { - super(trainedModelsApi, model, inputType); + super(trainedModelsApi, model, inputType, deploymentId); this.initialize(); } diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/text_classification_inference.ts b/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/text_classification_inference.ts index 362e162c24b5e..3ee9588162544 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/text_classification_inference.ts +++ b/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/text_classification_inference.ts @@ -30,9 +30,10 @@ export class TextClassificationInference extends InferenceBase, model: estypes.MlTrainedModelConfig, - inputType: INPUT_TYPE + inputType: INPUT_TYPE, + deploymentId: string ) { - super(trainedModelsApi, model, inputType); + super(trainedModelsApi, model, inputType, deploymentId); this.initialize(); } diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/zero_shot_classification_inference.ts b/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/zero_shot_classification_inference.ts index 62af6f2dd55ae..19cc970826821 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/zero_shot_classification_inference.ts +++ b/x-pack/plugins/ml/public/application/model_management/test_models/models/text_classification/zero_shot_classification_inference.ts @@ -37,9 +37,10 @@ export class ZeroShotClassificationInference extends InferenceBase, model: estypes.MlTrainedModelConfig, - inputType: INPUT_TYPE + inputType: INPUT_TYPE, + deploymentId: string ) { - super(trainedModelsApi, model, inputType); + super(trainedModelsApi, model, inputType, deploymentId); this.initialize( [this.labelsText$.pipe(map((labelsText) => labelsText !== ''))], diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/models/text_embedding/text_embedding_inference.ts b/x-pack/plugins/ml/public/application/model_management/test_models/models/text_embedding/text_embedding_inference.ts index 1200690d665aa..258e4161f626b 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/models/text_embedding/text_embedding_inference.ts +++ b/x-pack/plugins/ml/public/application/model_management/test_models/models/text_embedding/text_embedding_inference.ts @@ -42,9 +42,10 @@ export class TextEmbeddingInference extends InferenceBase constructor( trainedModelsApi: ReturnType, model: estypes.MlTrainedModelConfig, - inputType: INPUT_TYPE + inputType: INPUT_TYPE, + deploymentId: string ) { - super(trainedModelsApi, model, inputType); + super(trainedModelsApi, model, inputType, deploymentId); this.initialize(); } diff --git a/x-pack/plugins/ml/public/application/model_management/test_models/selected_model.tsx b/x-pack/plugins/ml/public/application/model_management/test_models/selected_model.tsx index ec79f3aa61a27..c719aa7368cfa 100644 --- a/x-pack/plugins/ml/public/application/model_management/test_models/selected_model.tsx +++ b/x-pack/plugins/ml/public/application/model_management/test_models/selected_model.tsx @@ -36,42 +36,34 @@ export const SelectedModel: FC = ({ model, inputType, deploymentId }) => const { trainedModels } = useMlApiContext(); const inferrer = useMemo(() => { - let newInferrer: InferrerType | undefined; - if (model.model_type === TRAINED_MODEL_TYPE.PYTORCH) { const taskType = Object.keys(model.inference_config)[0]; switch (taskType) { case SUPPORTED_PYTORCH_TASKS.NER: - newInferrer = new NerInference(trainedModels, model, inputType); + return new NerInference(trainedModels, model, inputType, deploymentId); break; case SUPPORTED_PYTORCH_TASKS.TEXT_CLASSIFICATION: - newInferrer = new TextClassificationInference(trainedModels, model, inputType); + return new TextClassificationInference(trainedModels, model, inputType, deploymentId); break; case SUPPORTED_PYTORCH_TASKS.ZERO_SHOT_CLASSIFICATION: - newInferrer = new ZeroShotClassificationInference(trainedModels, model, inputType); + return new ZeroShotClassificationInference(trainedModels, model, inputType, deploymentId); break; case SUPPORTED_PYTORCH_TASKS.TEXT_EMBEDDING: - newInferrer = new TextEmbeddingInference(trainedModels, model, inputType); + return new TextEmbeddingInference(trainedModels, model, inputType, deploymentId); break; case SUPPORTED_PYTORCH_TASKS.FILL_MASK: - newInferrer = new FillMaskInference(trainedModels, model, inputType); + return new FillMaskInference(trainedModels, model, inputType, deploymentId); break; case SUPPORTED_PYTORCH_TASKS.QUESTION_ANSWERING: - newInferrer = new QuestionAnsweringInference(trainedModels, model, inputType); + return new QuestionAnsweringInference(trainedModels, model, inputType, deploymentId); break; default: break; } } else if (model.model_type === TRAINED_MODEL_TYPE.LANG_IDENT) { - newInferrer = new LangIdentInference(trainedModels, model, inputType); - } - - if (newInferrer) { - newInferrer.deploymentId = deploymentId; + return new LangIdentInference(trainedModels, model, inputType, deploymentId); } - - return newInferrer; }, [inputType, model, trainedModels, deploymentId]); useEffect(() => {