[ML] Hide inference stats for PyTorch models (#160599)

## Summary

Resolves https://github.com/elastic/kibana/issues/157385

Hides inference stats for the PyTorch models. 

- The salient information (`inference_count`, `timestamp`) is a repeat
of what is already displayed in the Deployment Stats section.
- `missing_all_fields_count` is confusing as the PyTorch models take a
single input field rather than multiple fields as DFA models do, hence
omitted.
- The deployment stats have an
[error_count](https://www.elastic.co/guide/en/elasticsearch/reference/current/get-trained-models-stats.html)
field, hence it has been added to the Deployment Stats and
`failure_count` has been removed.
- Displays the stats tab by default for expanded rows if the model has
started deployments
This commit is contained in:
Dima Arnautov 2023-06-28 14:55:28 +02:00 committed by GitHub
parent 6db22e3e2a
commit 4064e2b7d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 260 additions and 235 deletions

View file

@ -61,12 +61,8 @@ export const ELASTIC_MODEL_DEFINITIONS = {
export const MODEL_STATE = {
...DEPLOYMENT_STATE,
DOWNLOADING: i18n.translate('xpack.ml.trainedModels.modelsList.downloadingStateLabel', {
defaultMessage: 'downloading',
}),
DOWNLOADED: i18n.translate('xpack.ml.trainedModels.modelsList.downloadedStateLabel', {
defaultMessage: 'downloaded',
}),
DOWNLOADING: 'downloading',
DOWNLOADED: 'downloaded',
} as const;
export type ModelState = typeof MODEL_STATE[keyof typeof MODEL_STATE];
export type ModelState = typeof MODEL_STATE[keyof typeof MODEL_STATE] | null;

View file

@ -202,6 +202,7 @@ export interface AllocatedModel {
throughput_last_minute: number;
number_of_allocations: number;
threads_per_allocation: number;
error_count?: number;
};
}

View file

@ -220,6 +220,16 @@ export const AllocatedModels: FC<AllocatedModelsProps> = ({
return v.node.number_of_pending_requests;
},
},
{
name: i18n.translate('xpack.ml.trainedModels.nodesList.modelsList.errorCountHeader', {
defaultMessage: 'Errors',
}),
width: '60px',
'data-test-subj': 'mlAllocatedModelsTableErrorCount',
render: (v: AllocatedModel) => {
return v.node.error_count ?? 0;
},
},
].filter((v) => !hideColumns.includes(v.id!));
return (

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import React, { FC, useMemo, useCallback } from 'react';
import React, { type FC, useCallback, useMemo } from 'react';
import { omit, pick } from 'lodash';
import {
EuiBadge,
@ -27,7 +27,6 @@ import { FIELD_FORMAT_IDS } from '@kbn/field-formats-plugin/common';
import { isPopulatedObject } from '@kbn/ml-is-populated-object';
import { isDefined } from '@kbn/ml-is-defined';
import { TRAINED_MODEL_TYPE } from '@kbn/ml-trained-models-utils';
import type { PartialBy } from '../../../common/types/common';
import type { ModelItemFull } from './models_list';
import { ModelPipelines } from './pipelines';
import { AllocatedModels } from '../memory_usage/nodes_overview/allocated_models';
@ -132,30 +131,34 @@ export const ExpandedRow: FC<ExpandedRowProps> = ({ item }) => {
description,
} = item;
const inferenceStats = useMemo(() => {
if (!isPopulatedObject(stats.inference_stats)) return;
const inferenceStats = useMemo<TrainedModelStat['inference_stats']>(() => {
if (!isPopulatedObject(stats.inference_stats) || item.model_type === TRAINED_MODEL_TYPE.PYTORCH)
return;
const result = { ...stats.inference_stats } as PartialBy<
Exclude<TrainedModelStat['inference_stats'], undefined>,
'cache_miss_count'
>;
if (item.model_type === TRAINED_MODEL_TYPE.PYTORCH) {
delete result.cache_miss_count;
}
return result;
return stats.inference_stats;
}, [stats.inference_stats, item.model_type]);
const { analytics_config: analyticsConfig, ...restMetaData } = metadata ?? {};
const details = {
const details = useMemo(() => {
return {
description,
tags,
version,
estimated_operations,
estimated_heap_memory_usage_bytes,
default_field_map,
license_level,
};
}, [
default_field_map,
description,
estimated_heap_memory_usage_bytes,
estimated_operations,
license_level,
tags,
version,
estimated_operations,
estimated_heap_memory_usage_bytes,
default_field_map,
license_level,
};
]);
const deploymentStatItems: AllocatedModel[] = useMemo<AllocatedModel[]>(() => {
const deploymentStats = stats.deployment_stats;
@ -181,6 +184,7 @@ export const ExpandedRow: FC<ExpandedRowProps> = ({ item }) => {
'throughput_last_minute',
'number_of_allocations',
'threads_per_allocation',
'error_count',
]),
name: nodeName,
} as AllocatedModel['node'],
@ -191,46 +195,28 @@ export const ExpandedRow: FC<ExpandedRowProps> = ({ item }) => {
return items;
}, [stats]);
const tabs: EuiTabbedContentTab[] = [
{
id: 'details',
'data-test-subj': 'mlTrainedModelDetails',
name: (
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.detailsTabLabel"
defaultMessage="Details"
/>
),
content: (
<div data-test-subj={'mlTrainedModelDetailsContent'}>
<EuiSpacer size={'s'} />
<EuiFlexGrid columns={2} gutterSize={'m'}>
<EuiFlexItem>
<EuiPanel>
<EuiTitle size={'xs'}>
<h5>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.detailsTitle"
defaultMessage="Details"
/>
</h5>
</EuiTitle>
<EuiSpacer size={'m'} />
<EuiDescriptionList
compressed={true}
type="column"
listItems={formatToListItems(details)}
/>
</EuiPanel>
</EuiFlexItem>
{isPopulatedObject(restMetaData) ? (
const tabs = useMemo<EuiTabbedContentTab[]>(() => {
return [
{
id: 'details',
'data-test-subj': 'mlTrainedModelDetails',
name: (
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.detailsTabLabel"
defaultMessage="Details"
/>
),
content: (
<div data-test-subj={'mlTrainedModelDetailsContent'}>
<EuiSpacer size={'s'} />
<EuiFlexGrid columns={2} gutterSize={'m'}>
<EuiFlexItem>
<EuiPanel>
<EuiTitle size={'xs'}>
<h5>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.metadataTitle"
defaultMessage="Metadata"
id="xpack.ml.trainedModels.modelsList.expandedRow.detailsTitle"
defaultMessage="Details"
/>
</h5>
</EuiTitle>
@ -238,192 +224,224 @@ export const ExpandedRow: FC<ExpandedRowProps> = ({ item }) => {
<EuiDescriptionList
compressed={true}
type="column"
listItems={formatToListItems(restMetaData)}
listItems={formatToListItems(details)}
/>
</EuiPanel>
</EuiFlexItem>
) : null}
</EuiFlexGrid>
</div>
),
},
...(inferenceConfig
? [
{
id: 'config',
'data-test-subj': 'mlTrainedModelInferenceConfig',
name: (
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.configTabLabel"
defaultMessage="Config"
/>
),
content: (
<div data-test-subj={'mlTrainedModelInferenceConfigContent'}>
<EuiSpacer size={'s'} />
<EuiFlexGrid columns={2} gutterSize={'m'}>
<EuiFlexItem>
<EuiPanel>
<EuiTitle size={'xs'}>
<h5>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.inferenceConfigTitle"
defaultMessage="Inference configuration"
/>
</h5>
</EuiTitle>
<EuiSpacer size={'m'} />
<EuiDescriptionList
compressed={true}
type="column"
listItems={formatToListItems(
inferenceConfig[Object.keys(inferenceConfig)[0]]
)}
/>
</EuiPanel>
</EuiFlexItem>
{analyticsConfig && (
<EuiFlexItem>
<EuiPanel>
<EuiTitle size={'xs'}>
<h5>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.analyticsConfigTitle"
defaultMessage="Analytics configuration"
/>
</h5>
</EuiTitle>
<EuiSpacer size={'m'} />
<EuiDescriptionList
compressed={true}
type="column"
listItems={formatToListItems(analyticsConfig)}
{isPopulatedObject(restMetaData) ? (
<EuiFlexItem>
<EuiPanel>
<EuiTitle size={'xs'}>
<h5>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.metadataTitle"
defaultMessage="Metadata"
/>
</EuiPanel>
</EuiFlexItem>
)}
</EuiFlexGrid>
</div>
),
},
]
: []),
...(isPopulatedObject(omit(stats, ['pipeline_count', 'ingest']))
? [
{
id: 'stats',
'data-test-subj': 'mlTrainedModelStats',
name: (
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.statsTabLabel"
defaultMessage="Stats"
/>
),
content: (
<div data-test-subj={'mlTrainedModelStatsContent'}>
<EuiSpacer size={'s'} />
{!!deploymentStatItems?.length ? (
<>
<EuiPanel>
<EuiTitle size={'xs'}>
<h5>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.deploymentStatsTitle"
defaultMessage="Deployment stats"
/>
</h5>
</EuiTitle>
<EuiSpacer size={'m'} />
<AllocatedModels models={deploymentStatItems} hideColumns={['model_id']} />
</EuiPanel>
<EuiSpacer size={'s'} />
</>
) : null}
<EuiFlexGrid columns={2} gutterSize={'m'}>
{inferenceStats ? (
<EuiFlexItem>
<EuiPanel>
<EuiTitle size={'xs'}>
<h5>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.inferenceStatsTitle"
defaultMessage="Inference stats"
/>
</h5>
</EuiTitle>
<EuiSpacer size={'m'} />
<EuiDescriptionList
compressed={true}
type="column"
listItems={formatToListItems(inferenceStats)}
/>
</EuiPanel>
</EuiFlexItem>
) : null}
{isPopulatedObject(stats.model_size_stats) &&
!isPopulatedObject(inferenceStats) ? (
<EuiFlexItem>
<EuiPanel>
<EuiTitle size={'xs'}>
<h5>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.modelSizeStatsTitle"
defaultMessage="Model size stats"
/>
</h5>
</EuiTitle>
<EuiSpacer size={'m'} />
<EuiDescriptionList
compressed={true}
type="column"
listItems={formatToListItems(stats.model_size_stats)}
/>
</EuiPanel>
</EuiFlexItem>
) : null}
</EuiFlexGrid>
</div>
),
},
]
: []),
...((isPopulatedObject(pipelines) && Object.keys(pipelines).length > 0) || stats.ingest
? [
{
id: 'pipelines',
'data-test-subj': 'mlTrainedModelPipelines',
name: (
<>
</h5>
</EuiTitle>
<EuiSpacer size={'m'} />
<EuiDescriptionList
compressed={true}
type="column"
listItems={formatToListItems(restMetaData)}
/>
</EuiPanel>
</EuiFlexItem>
) : null}
</EuiFlexGrid>
</div>
),
},
...(inferenceConfig
? [
{
id: 'config',
'data-test-subj': 'mlTrainedModelInferenceConfig',
name: (
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.pipelinesTabLabel"
defaultMessage="Pipelines"
id="xpack.ml.trainedModels.modelsList.expandedRow.configTabLabel"
defaultMessage="Config"
/>
{isPopulatedObject(pipelines) ? (
<EuiNotificationBadge>{Object.keys(pipelines).length}</EuiNotificationBadge>
) : null}
</>
),
content: (
<div data-test-subj={'mlTrainedModelPipelinesContent'}>
<EuiSpacer size={'s'} />
<ModelPipelines pipelines={pipelines!} ingestStats={stats.ingest} />
</div>
),
},
]
: []),
];
),
content: (
<div data-test-subj={'mlTrainedModelInferenceConfigContent'}>
<EuiSpacer size={'s'} />
<EuiFlexGrid columns={2} gutterSize={'m'}>
<EuiFlexItem>
<EuiPanel>
<EuiTitle size={'xs'}>
<h5>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.inferenceConfigTitle"
defaultMessage="Inference configuration"
/>
</h5>
</EuiTitle>
<EuiSpacer size={'m'} />
<EuiDescriptionList
compressed={true}
type="column"
listItems={formatToListItems(
inferenceConfig[Object.keys(inferenceConfig)[0]]
)}
/>
</EuiPanel>
</EuiFlexItem>
{analyticsConfig && (
<EuiFlexItem>
<EuiPanel>
<EuiTitle size={'xs'}>
<h5>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.analyticsConfigTitle"
defaultMessage="Analytics configuration"
/>
</h5>
</EuiTitle>
<EuiSpacer size={'m'} />
<EuiDescriptionList
compressed={true}
type="column"
listItems={formatToListItems(analyticsConfig)}
/>
</EuiPanel>
</EuiFlexItem>
)}
</EuiFlexGrid>
</div>
),
},
]
: []),
...(isPopulatedObject(omit(stats, ['pipeline_count', 'ingest']))
? [
{
id: 'stats',
'data-test-subj': 'mlTrainedModelStats',
name: (
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.statsTabLabel"
defaultMessage="Stats"
/>
),
content: (
<div data-test-subj={'mlTrainedModelStatsContent'}>
<EuiSpacer size={'s'} />
{!!deploymentStatItems?.length ? (
<>
<EuiPanel>
<EuiTitle size={'xs'}>
<h5>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.deploymentStatsTitle"
defaultMessage="Deployment stats"
/>
</h5>
</EuiTitle>
<EuiSpacer size={'m'} />
<AllocatedModels models={deploymentStatItems} hideColumns={['model_id']} />
</EuiPanel>
<EuiSpacer size={'s'} />
</>
) : null}
<EuiFlexGrid columns={2} gutterSize={'m'}>
{inferenceStats ? (
<EuiFlexItem>
<EuiPanel>
<EuiTitle size={'xs'}>
<h5>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.inferenceStatsTitle"
defaultMessage="Inference stats"
/>
</h5>
</EuiTitle>
<EuiSpacer size={'m'} />
<EuiDescriptionList
compressed={true}
type="column"
listItems={formatToListItems(inferenceStats)}
/>
</EuiPanel>
</EuiFlexItem>
) : null}
{isPopulatedObject(stats.model_size_stats) &&
!isPopulatedObject(inferenceStats) ? (
<EuiFlexItem>
<EuiPanel>
<EuiTitle size={'xs'}>
<h5>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.modelSizeStatsTitle"
defaultMessage="Model size stats"
/>
</h5>
</EuiTitle>
<EuiSpacer size={'m'} />
<EuiDescriptionList
compressed={true}
type="column"
listItems={formatToListItems(stats.model_size_stats)}
/>
</EuiPanel>
</EuiFlexItem>
) : null}
</EuiFlexGrid>
</div>
),
},
]
: []),
...((isPopulatedObject(pipelines) && Object.keys(pipelines).length > 0) || stats.ingest
? [
{
id: 'pipelines',
'data-test-subj': 'mlTrainedModelPipelines',
name: (
<>
<FormattedMessage
id="xpack.ml.trainedModels.modelsList.expandedRow.pipelinesTabLabel"
defaultMessage="Pipelines"
/>
{isPopulatedObject(pipelines) ? (
<EuiNotificationBadge>{Object.keys(pipelines).length}</EuiNotificationBadge>
) : null}
</>
),
content: (
<div data-test-subj={'mlTrainedModelPipelinesContent'}>
<EuiSpacer size={'s'} />
<ModelPipelines pipelines={pipelines!} ingestStats={stats.ingest} />
</div>
),
},
]
: []),
];
}, [
analyticsConfig,
deploymentStatItems,
details,
formatToListItems,
inferenceConfig,
inferenceStats,
pipelines,
restMetaData,
stats,
]);
const initialSelectedTab =
item.state === 'started' ? tabs.find((t) => t.id === 'stats') : tabs[0];
return (
<EuiTabbedContent
size="s"
style={{ width: '100%' }}
css={{ width: '100%' }}
tabs={tabs}
initialSelectedTab={tabs[0]}
initialSelectedTab={initialSelectedTab}
autoFocus="selected"
onTabClick={(tab) => {}}
data-test-subj={'mlTrainedModelRowDetails'}
/>
);

View file

@ -283,7 +283,7 @@ export const ModelsList: FC<Props> = ({
(v) => v.state === DEPLOYMENT_STATE.STARTED
)
? DEPLOYMENT_STATE.STARTED
: '';
: null;
});
const elasticModels = models.filter((model) =>