style: detail layout
This commit is contained in:
@@ -21,7 +21,7 @@ export default {
|
||||
'benchmark.form.profile.longContext': 'Long Context',
|
||||
'benchmark.form.profile.heavy': 'Generation Heavy',
|
||||
'benchmark.form.profile.custom': 'Custom',
|
||||
'benchmark.table.inputTokenLength': 'Input Token Length',
|
||||
'benchmark.table.inputTokenLength': 'Prompt Token Length',
|
||||
'benchmark.table.outputTokenLength': 'Output Token Length',
|
||||
'benchmark.detail.summary.title': 'Summary',
|
||||
'benchmark.detail.configure.title': 'Configure',
|
||||
|
||||
@@ -21,7 +21,7 @@ export default {
|
||||
'benchmark.form.profile.longContext': 'Long Context',
|
||||
'benchmark.form.profile.heavy': 'Generation Heavy',
|
||||
'benchmark.form.profile.custom': 'Custom',
|
||||
'benchmark.table.inputTokenLength': 'Input Token Length',
|
||||
'benchmark.table.inputTokenLength': 'Prompt Token Length',
|
||||
'benchmark.table.outputTokenLength': 'Output Token Length',
|
||||
'benchmark.detail.summary.title': 'Summary',
|
||||
'benchmark.detail.configure.title': 'Configure',
|
||||
|
||||
@@ -21,7 +21,7 @@ export default {
|
||||
'benchmark.form.profile.longContext': 'Long Context',
|
||||
'benchmark.form.profile.heavy': 'Generation Heavy',
|
||||
'benchmark.form.profile.custom': 'Custom',
|
||||
'benchmark.table.inputTokenLength': 'Input Token Length',
|
||||
'benchmark.table.inputTokenLength': 'Prompt Token Length',
|
||||
'benchmark.table.outputTokenLength': 'Output Token Length',
|
||||
'benchmark.detail.summary.title': 'Summary',
|
||||
'benchmark.detail.configure.title': 'Configure',
|
||||
|
||||
@@ -21,7 +21,7 @@ export default {
|
||||
'benchmark.form.profile.longContext': '长上下文',
|
||||
'benchmark.form.profile.heavy': '高生成量',
|
||||
'benchmark.form.profile.custom': '自定义',
|
||||
'benchmark.table.inputTokenLength': '输入 Token 长度',
|
||||
'benchmark.table.inputTokenLength': '提示词 Token 长度',
|
||||
'benchmark.table.outputTokenLength': '输出 Token 长度',
|
||||
'benchmark.detail.summary.title': '摘要',
|
||||
'benchmark.detail.configure.title': '配置',
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
export const BENCHMARKS_API = '/benchmarks';
|
||||
export const DATASETS_API = '/datasets';
|
||||
export const PROFILES_CONFIG_API = '/benchmark-profiles/default-config';
|
||||
export const EXPORT_BENCHMARK_LIST = '/benchmarks/export';
|
||||
|
||||
export async function queryBenchmarkList(
|
||||
params: Global.SearchParams,
|
||||
@@ -103,3 +104,19 @@ export async function queryProfiles(
|
||||
cancelToken: options?.token
|
||||
});
|
||||
}
|
||||
|
||||
export async function exportBenchmarkList(
|
||||
params: {
|
||||
ids?: number[];
|
||||
},
|
||||
options?: {
|
||||
token?: CancelToken;
|
||||
}
|
||||
) {
|
||||
return request(`${BENCHMARKS_API}/export`, {
|
||||
method: 'POST',
|
||||
data: params.ids,
|
||||
responseType: 'blob',
|
||||
cancelToken: options?.token
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@ import SealTable from '@/components/seal-table/index';
|
||||
import useExpandedRowKeys from '@/hooks/use-expanded-row-keys';
|
||||
import { convertFileSize } from '@/utils';
|
||||
import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
|
||||
import { Col, Row } from 'antd';
|
||||
import { Col, Row, Tag } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useDetailContext } from '../../config/detail-context';
|
||||
@@ -32,30 +33,31 @@ const Environment: React.FC = () => {
|
||||
|
||||
return {
|
||||
...workerInfo,
|
||||
..._.pick(gpuData?.[0], ['driver_version', 'runtime_version']),
|
||||
isMain: true,
|
||||
children: gpuData
|
||||
};
|
||||
}, [snapshot]);
|
||||
|
||||
// const subWorkers = useMemo(() => {
|
||||
// const [[mainWorkerName, mainWorkerInfo]] = Object.entries(snapshot.workers);
|
||||
const subWorkers = useMemo(() => {
|
||||
const [[mainWorkerName, mainWorkerInfo]] = Object.entries(snapshot.workers);
|
||||
|
||||
// const subOrdinaryWorkers = Object.values(snapshot.instances).filter(
|
||||
// (instance) => instance.worker_name === mainWorkerName
|
||||
// );
|
||||
const subOrdinaryWorkers = Object.values(snapshot.instances).filter(
|
||||
(instance) => instance.worker_name === mainWorkerName
|
||||
);
|
||||
|
||||
// return subOrdinaryWorkers.map((worker) => {
|
||||
// const gpuData = Object.values(snapshot.gpus).filter(
|
||||
// (gpu) => gpu.worker_name === worker.worker_name
|
||||
// );
|
||||
return subOrdinaryWorkers.map((worker) => {
|
||||
const gpuData = Object.values(snapshot.gpus).filter(
|
||||
(gpu) => gpu.worker_name === worker.worker_name
|
||||
);
|
||||
|
||||
// return {
|
||||
// ...worker,
|
||||
// isMain: false,
|
||||
// children: gpuData
|
||||
// };
|
||||
// });
|
||||
// }, [snapshot, mainWorker]);
|
||||
return {
|
||||
...worker,
|
||||
isMain: false,
|
||||
children: gpuData
|
||||
};
|
||||
});
|
||||
}, [snapshot, mainWorker]);
|
||||
|
||||
const GPUColumns = [
|
||||
{
|
||||
@@ -68,19 +70,26 @@ const Environment: React.FC = () => {
|
||||
<AutoTooltip ghost>{value}</AutoTooltip>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: 'Index',
|
||||
dataIndex: 'index',
|
||||
key: 'index',
|
||||
span: 4,
|
||||
colStyle: { paddingLeft: 48 }
|
||||
},
|
||||
{
|
||||
title: 'Vendor',
|
||||
dataIndex: 'vendor',
|
||||
key: 'vendor',
|
||||
span: 4,
|
||||
colStyle: { paddingLeft: 46 }
|
||||
span: 6,
|
||||
colStyle: { paddingLeft: 110 }
|
||||
},
|
||||
{
|
||||
title: 'VRAM',
|
||||
dataIndex: 'memory_total',
|
||||
key: 'memory_total',
|
||||
label: 'VRAM',
|
||||
span: 3,
|
||||
span: 4,
|
||||
render: (value: number, record: any) => convertFileSize(value)
|
||||
},
|
||||
{
|
||||
@@ -88,20 +97,8 @@ const Environment: React.FC = () => {
|
||||
dataIndex: 'core_total',
|
||||
key: 'core_total',
|
||||
label: 'Cores',
|
||||
span: 3
|
||||
},
|
||||
{
|
||||
title: 'Runtime Version',
|
||||
dataIndex: 'runtime_version',
|
||||
key: 'runtime_version',
|
||||
span: 4
|
||||
},
|
||||
{
|
||||
title: 'Driver Version',
|
||||
dataIndex: 'driver_version',
|
||||
key: 'driver_version',
|
||||
span: 4,
|
||||
colStyle: { paddingLeft: 35 }
|
||||
colStyle: { paddingLeft: 36 }
|
||||
}
|
||||
];
|
||||
|
||||
@@ -110,31 +107,56 @@ const Environment: React.FC = () => {
|
||||
title: 'Worker Name',
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
span: 6
|
||||
span: 6,
|
||||
render: (value: string, record: any) => {
|
||||
return (
|
||||
<>
|
||||
<AutoTooltip ghost>{value}</AutoTooltip>
|
||||
{record.isMain && (
|
||||
<Tag color="geekblue" style={{ marginLeft: 8 }}>
|
||||
Main
|
||||
</Tag>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'System',
|
||||
dataIndex: 'os',
|
||||
key: 'system',
|
||||
span: 4,
|
||||
span: 5,
|
||||
render: (os: { name: string; version: string }, record: any) => {
|
||||
return `${record.os.name}`;
|
||||
return (
|
||||
<AutoTooltip
|
||||
ghost
|
||||
>{`${record.os.name} (${record.os.version})`}</AutoTooltip>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Version',
|
||||
dataIndex: 'version',
|
||||
key: 'version',
|
||||
span: 6,
|
||||
title: 'Runtime Version',
|
||||
dataIndex: 'runtime_version',
|
||||
key: 'runtime_version',
|
||||
span: 3,
|
||||
render: (val: any, record: any) => {
|
||||
return <AutoTooltip ghost>{record.os.version}</AutoTooltip>;
|
||||
return <AutoTooltip ghost>{record.runtime_version}</AutoTooltip>;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Driver Version',
|
||||
dataIndex: 'driver_version',
|
||||
key: 'driver_version',
|
||||
span: 3,
|
||||
render: (val: any, record: any) => {
|
||||
return <AutoTooltip ghost>{record.driver_version}</AutoTooltip>;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'CPU Count',
|
||||
dataIndex: 'cpu_total',
|
||||
key: 'cpu_total',
|
||||
span: 4
|
||||
span: 3
|
||||
},
|
||||
{
|
||||
title: 'Memory',
|
||||
@@ -146,8 +168,8 @@ const Environment: React.FC = () => {
|
||||
];
|
||||
|
||||
const dataList = useMemo(() => {
|
||||
return [mainWorker];
|
||||
}, [mainWorker]);
|
||||
return [mainWorker, ...subWorkers];
|
||||
}, [mainWorker, subWorkers]);
|
||||
|
||||
const handleToggleExpandAll = useMemoizedFn((expanded: boolean) => {
|
||||
const keys = dataList?.map((item) => item.id);
|
||||
@@ -161,6 +183,29 @@ const Environment: React.FC = () => {
|
||||
const renderChildren = useMemoizedFn((list: any[]) => {
|
||||
return (
|
||||
<div style={{ borderRadius: 'var(--ant-table-header-border-radius)' }}>
|
||||
<Row style={{ width: '100%' }} align="middle">
|
||||
{GPUColumns.map((col) => (
|
||||
<Col key={col.key} span={col.span} style={{ ...col.colStyle }}>
|
||||
<span
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'flex-start',
|
||||
gap: 4,
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: 'var(--ant-color-text-tertiary)',
|
||||
fontSize: 12
|
||||
}}
|
||||
>
|
||||
{col.title}
|
||||
</span>
|
||||
</span>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
<RowChildren>
|
||||
{list.map((gpu) => (
|
||||
<Row key={gpu.id} style={{ width: '100%' }} align="middle">
|
||||
@@ -174,14 +219,6 @@ const Environment: React.FC = () => {
|
||||
flexDirection: 'column'
|
||||
}}
|
||||
>
|
||||
<span
|
||||
style={{
|
||||
color: 'var(--ant-color-text-tertiary)',
|
||||
fontSize: 12
|
||||
}}
|
||||
>
|
||||
{col.title}
|
||||
</span>
|
||||
{col.render
|
||||
? col.render((gpu as any)[col.dataIndex], gpu)
|
||||
: (gpu as any)[col.dataIndex]}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Descriptions, DescriptionsProps } from 'antd';
|
||||
import React from 'react';
|
||||
import { useDetailContext } from '../../config/detail-context';
|
||||
import Section from './section';
|
||||
import Title from './title';
|
||||
|
||||
const Benchmark: React.FC = () => {
|
||||
const { detailData } = useDetailContext();
|
||||
@@ -19,7 +19,7 @@ const Benchmark: React.FC = () => {
|
||||
},
|
||||
{
|
||||
key: '3',
|
||||
label: 'Token Length (Prompt/Out)',
|
||||
label: 'Token Length (Prompt/Output)',
|
||||
children: (
|
||||
<span>
|
||||
{detailData?.dataset_prompt_tokens || '-'} /{' '}
|
||||
@@ -45,19 +45,19 @@ const Benchmark: React.FC = () => {
|
||||
];
|
||||
|
||||
return (
|
||||
<Section title="Benchmark Parameters" minHeight={132}>
|
||||
<div>
|
||||
<Title>Parameters</Title>
|
||||
<Descriptions
|
||||
items={items}
|
||||
colon={false}
|
||||
column={3}
|
||||
layout="vertical"
|
||||
styles={{
|
||||
content: {
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
}}
|
||||
></Descriptions>
|
||||
</Section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { Divider } from 'antd';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import BenchMark from './benchmark';
|
||||
import Instance from './instance';
|
||||
import MetricsResult from './metrics-result';
|
||||
import PercentileResult from './percentile-result';
|
||||
import Section from './section';
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
@@ -14,10 +16,16 @@ const Container = styled.div`
|
||||
const Summary: React.FC = () => {
|
||||
return (
|
||||
<Container>
|
||||
<Instance />
|
||||
<BenchMark />
|
||||
<MetricsResult />
|
||||
<PercentileResult />
|
||||
<Section title="Results">
|
||||
<MetricsResult />
|
||||
<Divider />
|
||||
<PercentileResult />
|
||||
</Section>
|
||||
<Section title="Benchmark Details">
|
||||
<Instance />
|
||||
<Divider />
|
||||
<BenchMark />
|
||||
</Section>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import AutoTooltip from '@/components/auto-tooltip';
|
||||
import { Descriptions, Flex, Tag } from 'antd';
|
||||
import _ from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useDetailContext } from '../../config/detail-context';
|
||||
import Section from './section';
|
||||
import Title from './title';
|
||||
|
||||
const calcTotalVram = (vram: Record<string, number>) => {
|
||||
return _.sum(_.values(vram));
|
||||
@@ -10,6 +11,8 @@ const calcTotalVram = (vram: Record<string, number>) => {
|
||||
|
||||
const Instance: React.FC = () => {
|
||||
const { detailData } = useDetailContext();
|
||||
const [, instanceData] =
|
||||
Object.entries(detailData?.snapshot?.instances || {})[0] || [];
|
||||
|
||||
const items = useMemo(() => {
|
||||
const { snapshot } = detailData;
|
||||
@@ -18,25 +21,21 @@ const Instance: React.FC = () => {
|
||||
return [
|
||||
{
|
||||
key: '1',
|
||||
label: 'Instance Name',
|
||||
label: 'Model Name',
|
||||
children: (
|
||||
<div className="flex-center gap-8">
|
||||
<span>
|
||||
{detailData?.model_name}/{detailData?.model_instance_name}
|
||||
</span>
|
||||
</div>
|
||||
<AutoTooltip ghost>{detailData?.model_name || '-'}</AutoTooltip>
|
||||
)
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
label: 'Worker',
|
||||
children: instanceData?.worker_name || '-'
|
||||
},
|
||||
{
|
||||
key: '6',
|
||||
label: 'GPU Type',
|
||||
children: instanceData?.gpu_type || '-'
|
||||
label: 'Instance Name',
|
||||
children: (
|
||||
<AutoTooltip ghost>
|
||||
{detailData?.model_instance_name || '-'}
|
||||
</AutoTooltip>
|
||||
)
|
||||
},
|
||||
|
||||
{
|
||||
key: '5',
|
||||
label: 'Backend',
|
||||
@@ -45,6 +44,13 @@ const Instance: React.FC = () => {
|
||||
? `(${instanceData?.backend_version})`
|
||||
: ''
|
||||
}`
|
||||
},
|
||||
{
|
||||
key: '6',
|
||||
label: 'Model File',
|
||||
children: (
|
||||
<AutoTooltip ghost>{instanceData?.resolved_path || '-'}</AutoTooltip>
|
||||
)
|
||||
}
|
||||
];
|
||||
}, [detailData]);
|
||||
@@ -144,7 +150,8 @@ const Instance: React.FC = () => {
|
||||
}, [detailData]);
|
||||
|
||||
return (
|
||||
<Section title="Model Instance" minHeight={220}>
|
||||
<div>
|
||||
<Title>Instance</Title>
|
||||
<Descriptions
|
||||
items={items}
|
||||
colon={false}
|
||||
@@ -168,7 +175,7 @@ const Instance: React.FC = () => {
|
||||
}
|
||||
}}
|
||||
></Descriptions>
|
||||
</Section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,25 +1,46 @@
|
||||
import { Descriptions } from 'antd';
|
||||
import _, { round } from 'lodash';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { useDetailContext } from '../../config/detail-context';
|
||||
import Section from './section';
|
||||
import Title from './title';
|
||||
|
||||
const Card = styled.div`
|
||||
height: 78px;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--ant-color-border);
|
||||
border-radius: var(--ant-border-radius);
|
||||
background-color: var(--ant-color-bg-container);
|
||||
`;
|
||||
|
||||
const Box = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 16px;
|
||||
`;
|
||||
|
||||
const DescWrapper = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
gap: 24px;
|
||||
`;
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: 'Duration (s)',
|
||||
dataIndex: 'duration',
|
||||
path: ['raw_metrics', 'benchmarks', '0', 'duration'],
|
||||
unit: 's',
|
||||
render: (value: number) => round(value, 2)
|
||||
},
|
||||
// {
|
||||
// title: 'Duration',
|
||||
// dataIndex: 'duration',
|
||||
// path: ['raw_metrics', 'benchmarks', '0', 'duration'],
|
||||
// unit: 's',
|
||||
// render: (value: number) => round(value, 2)
|
||||
// },
|
||||
|
||||
{
|
||||
title: 'Total Requests',
|
||||
dataIndex: 'total_requests',
|
||||
path: 'total_requests',
|
||||
unit: '',
|
||||
render: (value: number) => round(value, 0)
|
||||
},
|
||||
// {
|
||||
// title: 'Total Requests',
|
||||
// dataIndex: 'total_requests',
|
||||
// path: 'total_requests',
|
||||
// unit: '',
|
||||
// render: (value: number) => round(value, 0)
|
||||
// },
|
||||
{
|
||||
title: 'Total token throughput',
|
||||
dataIndex: 'tokens_per_second_mean',
|
||||
@@ -28,18 +49,25 @@ const columns = [
|
||||
render: (value: number) => round(value, 2)
|
||||
},
|
||||
{
|
||||
title: 'Success Requests',
|
||||
dataIndex: 'successful_requests',
|
||||
path: ['raw_metrics', 'benchmarks', '0'],
|
||||
unit: '',
|
||||
render: (value: number) => {
|
||||
return (
|
||||
<span style={{ color: 'var(--ant-color-success)' }}>
|
||||
{_.get(value, 'metrics.request_totals.successful')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
title: 'Prompt token throughput ',
|
||||
dataIndex: 'prompt_tokens_per_second_mean',
|
||||
path: 'prompt_tokens_per_second_mean',
|
||||
unit: 'Tokens/s',
|
||||
render: (value: number) => round(value, 2)
|
||||
},
|
||||
// {
|
||||
// title: 'Success Requests',
|
||||
// dataIndex: 'successful_requests',
|
||||
// path: ['raw_metrics', 'benchmarks', '0'],
|
||||
// unit: '',
|
||||
// render: (value: number) => {
|
||||
// return (
|
||||
// <span style={{ color: 'var(--ant-color-success)' }}>
|
||||
// {_.get(value, 'metrics.request_totals.successful')}
|
||||
// </span>
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
|
||||
{
|
||||
title: 'Output token throughput',
|
||||
@@ -51,65 +79,103 @@ const columns = [
|
||||
];
|
||||
|
||||
const columnsSub = [
|
||||
// {
|
||||
// title: 'Failed Requests',
|
||||
// dataIndex: 'failed_requests',
|
||||
// path: ['raw_metrics', 'benchmarks', '0'],
|
||||
// unit: '',
|
||||
// render: (value: number) => {
|
||||
// return (
|
||||
// <span style={{ color: 'var(--ant-color-error)' }}>
|
||||
// {_.get(value, 'metrics.request_totals.errored')}
|
||||
// </span>
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// title: 'Concurrency',
|
||||
// dataIndex: 'request_concurrency',
|
||||
// path: ['raw_metrics', 'benchmarks', '0'],
|
||||
// unit: '',
|
||||
// render: (value: number) => {
|
||||
// return round(
|
||||
// _.get(value, 'metrics.request_concurrency.successful.mean'),
|
||||
// 0
|
||||
// );
|
||||
// }
|
||||
// },
|
||||
|
||||
{
|
||||
title: 'Failed Requests',
|
||||
dataIndex: 'failed_requests',
|
||||
path: ['raw_metrics', 'benchmarks', '0'],
|
||||
unit: '',
|
||||
render: (value: number) => {
|
||||
return (
|
||||
<span style={{ color: 'var(--ant-color-error)' }}>
|
||||
{_.get(value, 'metrics.request_totals.errored')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Concurrency',
|
||||
dataIndex: 'request_concurrency',
|
||||
path: ['raw_metrics', 'benchmarks', '0'],
|
||||
unit: '',
|
||||
render: (value: number) => {
|
||||
return round(
|
||||
_.get(value, 'metrics.request_concurrency.successful.mean'),
|
||||
0
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Request token throughput ',
|
||||
dataIndex: 'prompt_tokens_per_second_mean',
|
||||
path: 'prompt_tokens_per_second_mean',
|
||||
unit: 'Tokens/s',
|
||||
render: (value: number) => round(value, 2)
|
||||
},
|
||||
{
|
||||
title: 'Average Request Latency (ms)',
|
||||
title: 'Average Request Latency',
|
||||
dataIndex: 'request_latency_mean',
|
||||
path: 'request_latency_mean',
|
||||
unit: 'ms',
|
||||
render: (value: number) => round(value, 2)
|
||||
},
|
||||
{
|
||||
title: 'Average Time To First Token (ms)',
|
||||
title: 'Average Time To First Token',
|
||||
dataIndex: 'time_to_first_token_mean',
|
||||
path: 'time_to_first_token_mean',
|
||||
unit: 'ms',
|
||||
render: (value: number) => round(value, 2)
|
||||
},
|
||||
{
|
||||
title: 'Average Time Per Output Token (ms)',
|
||||
title: 'Average Time Per Output Token',
|
||||
dataIndex: 'time_per_output_token_mean',
|
||||
path: 'time_per_output_token_mean',
|
||||
unit: 'ms',
|
||||
render: (value: number) => round(value, 2)
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: '',
|
||||
path: '',
|
||||
unit: '',
|
||||
render: (_: number) => ''
|
||||
title: 'Average Inter Token Latency',
|
||||
dataIndex: 'inter_token_latency_mean',
|
||||
path: 'inter_token_latency_mean',
|
||||
unit: 'ms',
|
||||
render: (value: number) => round(value, 2)
|
||||
}
|
||||
];
|
||||
|
||||
const requestFields = [
|
||||
{
|
||||
label: 'Total Requests',
|
||||
key: 'total_requests',
|
||||
dataIndex: 'total_requests',
|
||||
path: 'total_requests',
|
||||
precision: 0,
|
||||
render: (value: number) => round(value, 0),
|
||||
unit: ''
|
||||
},
|
||||
{
|
||||
label: 'Success Requests',
|
||||
key: 'total_requests',
|
||||
dataIndex: 'successful_requests',
|
||||
path: ['raw_metrics', 'benchmarks', '0'],
|
||||
render: (value: number) =>
|
||||
round(_.get(value, ['metrics', 'request_totals', 'successful']), 0),
|
||||
precision: 0,
|
||||
color: 'var(--ant-color-success)',
|
||||
unit: ''
|
||||
},
|
||||
{
|
||||
label: 'Failed Requests',
|
||||
key: 'total_requests',
|
||||
dataIndex: 'failed_requests',
|
||||
path: ['raw_metrics', 'benchmarks', '0'],
|
||||
render: (value: number) =>
|
||||
round(_.get(value, ['metrics', 'request_totals', 'errored']), 0),
|
||||
precision: 0,
|
||||
color: 'var(--ant-color-error)',
|
||||
unit: ''
|
||||
},
|
||||
{
|
||||
label: 'Concurrency',
|
||||
key: 'request_concurrency',
|
||||
dataIndex: 'request_concurrency',
|
||||
path: ['raw_metrics', 'benchmarks', '0'],
|
||||
render: (value: number) =>
|
||||
round(_.get(value, 'metrics.request_concurrency.successful.mean'), 0),
|
||||
precision: 0,
|
||||
unit: ''
|
||||
}
|
||||
];
|
||||
|
||||
@@ -148,39 +214,94 @@ const PercentileResult: React.FC = () => {
|
||||
})
|
||||
);
|
||||
|
||||
const throughputItems = columns.map(
|
||||
({ title, dataIndex, path, render, unit }) => ({
|
||||
key: dataIndex,
|
||||
label: title,
|
||||
children: unit ? (
|
||||
<span className="flex-center">
|
||||
{render(_.get(detailData, path) ?? 0)}{' '}
|
||||
<span className="m-l-4">({unit})</span>
|
||||
</span>
|
||||
) : (
|
||||
render(_.get(detailData, path) ?? 0)
|
||||
)
|
||||
})
|
||||
);
|
||||
|
||||
const latencyItems = columnsSub.map(
|
||||
({ title, dataIndex, path, render, unit }) => ({
|
||||
key: dataIndex,
|
||||
label: title,
|
||||
children: unit ? (
|
||||
<span className="flex-center">
|
||||
{render(_.get(detailData, path) ?? 0)}{' '}
|
||||
<span className="m-l-4">({unit})</span>
|
||||
</span>
|
||||
) : (
|
||||
render(_.get(detailData, path) ?? 0)
|
||||
)
|
||||
})
|
||||
);
|
||||
|
||||
const requestItems = requestFields.map(
|
||||
({ label, dataIndex, path, render, unit, color }) => ({
|
||||
key: dataIndex,
|
||||
label: label,
|
||||
children: unit ? (
|
||||
<span className="flex-center" style={{ color: color }}>
|
||||
{render(_.get(detailData, path) ?? 0)}{' '}
|
||||
<span className="m-l-4">({unit})</span>
|
||||
</span>
|
||||
) : (
|
||||
<span style={{ color: color }}>
|
||||
{render(_.get(detailData, path) ?? 0)}
|
||||
</span>
|
||||
)
|
||||
})
|
||||
);
|
||||
|
||||
const descriptionStyles = {
|
||||
header: {
|
||||
marginBottom: 8
|
||||
},
|
||||
title: {
|
||||
fontWeight: 500,
|
||||
color: 'var(--ant-color-text-secondary)',
|
||||
fontSize: 14
|
||||
},
|
||||
content: {
|
||||
justifyContent: 'flex-start'
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Section title="Metrics Result" minHeight={210}>
|
||||
{/* <Table
|
||||
size="small"
|
||||
columns={[
|
||||
{
|
||||
title: 'Metrics',
|
||||
dataIndex: 'metrics'
|
||||
},
|
||||
...columns
|
||||
]}
|
||||
dataSource={buildPercentileTable(metrics)}
|
||||
rowKey="percentile"
|
||||
pagination={false}
|
||||
styles={{
|
||||
body: {
|
||||
cell: {
|
||||
height: 54
|
||||
}
|
||||
}
|
||||
}}
|
||||
></Table> */}
|
||||
<Descriptions
|
||||
items={items}
|
||||
colon={false}
|
||||
column={2}
|
||||
styles={{
|
||||
content: {
|
||||
justifyContent: 'flex-end'
|
||||
}
|
||||
}}
|
||||
></Descriptions>
|
||||
</Section>
|
||||
<div>
|
||||
<Title>Metrics</Title>
|
||||
<Box>
|
||||
<Descriptions
|
||||
styles={descriptionStyles}
|
||||
title="Throughput"
|
||||
items={throughputItems}
|
||||
colon={false}
|
||||
column={1}
|
||||
></Descriptions>
|
||||
<Descriptions
|
||||
styles={descriptionStyles}
|
||||
title="Latency"
|
||||
items={latencyItems}
|
||||
colon={false}
|
||||
column={1}
|
||||
></Descriptions>
|
||||
<Descriptions
|
||||
styles={descriptionStyles}
|
||||
title="Requests"
|
||||
items={requestItems}
|
||||
colon={false}
|
||||
column={1}
|
||||
></Descriptions>
|
||||
</Box>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Table } from 'antd';
|
||||
import { round } from 'lodash';
|
||||
import React from 'react';
|
||||
import { useDetailContext } from '../../config/detail-context';
|
||||
import Section from './section';
|
||||
import Title from './title';
|
||||
|
||||
const columns = [
|
||||
{
|
||||
@@ -76,13 +76,17 @@ const PercentileResult: React.FC = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<Section title="Percentiles Result" minHeight={240}>
|
||||
<div>
|
||||
<Title>Percentile</Title>
|
||||
<Table
|
||||
size="small"
|
||||
columns={[
|
||||
{
|
||||
title: 'Percentile',
|
||||
dataIndex: 'percentile'
|
||||
dataIndex: 'percentile',
|
||||
render: (value: string) => (
|
||||
<span style={{ fontWeight: 500 }}>{value}</span>
|
||||
)
|
||||
},
|
||||
...columns
|
||||
]}
|
||||
@@ -90,17 +94,15 @@ const PercentileResult: React.FC = () => {
|
||||
rowKey="percentile"
|
||||
pagination={false}
|
||||
styles={{
|
||||
// header: {
|
||||
// row: {
|
||||
// background: 'none',
|
||||
// borderBottom: '1px solid var(--ant-color-split)'
|
||||
// },
|
||||
// cell: {
|
||||
// background: 'none',
|
||||
// paddingBottom: 0,
|
||||
// borderBottom: '1px solid var(--ant-color-split)'
|
||||
// }
|
||||
// },
|
||||
header: {
|
||||
row: {
|
||||
backgroundColor: 'transparent'
|
||||
},
|
||||
cell: {
|
||||
fontWeight: 400,
|
||||
borderBottom: '1px solid var(--ant-color-split)'
|
||||
}
|
||||
},
|
||||
body: {
|
||||
cell: {
|
||||
height: 54
|
||||
@@ -108,7 +110,7 @@ const PercentileResult: React.FC = () => {
|
||||
}
|
||||
}}
|
||||
></Table>
|
||||
</Section>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import { BulbOutlined } from '@ant-design/icons';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Content = styled.div`
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
margin-bottom: 16px;
|
||||
`;
|
||||
|
||||
const Title: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
return (
|
||||
<Content>
|
||||
<BulbOutlined
|
||||
style={{ marginRight: 8, color: 'var(--ant-color-text-tertiary)' }}
|
||||
/>
|
||||
{children}
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
|
||||
export default Title;
|
||||
@@ -7,6 +7,7 @@ export const BenchmarkStatusValueMap = {
|
||||
Running: 'running',
|
||||
Completed: 'completed',
|
||||
Error: 'error',
|
||||
Stopped: 'stopped',
|
||||
Unreachable: 'unreachable'
|
||||
};
|
||||
|
||||
@@ -16,6 +17,7 @@ export const BenchmarkStatusLabelMap = {
|
||||
[BenchmarkStatusValueMap.Running]: 'Running',
|
||||
[BenchmarkStatusValueMap.Completed]: 'Completed',
|
||||
[BenchmarkStatusValueMap.Error]: 'Error',
|
||||
[BenchmarkStatusValueMap.Stopped]: 'Stopped',
|
||||
[BenchmarkStatusValueMap.Unreachable]: 'Unreachable'
|
||||
};
|
||||
|
||||
@@ -25,7 +27,8 @@ export const BenchmarkStatus: Record<string, StatusType> = {
|
||||
[BenchmarkStatusValueMap.Running]: StatusMaps.success,
|
||||
[BenchmarkStatusValueMap.Completed]: StatusMaps.success,
|
||||
[BenchmarkStatusValueMap.Error]: StatusMaps.error,
|
||||
[BenchmarkStatusValueMap.Unreachable]: StatusMaps.error
|
||||
[BenchmarkStatusValueMap.Unreachable]: StatusMaps.error,
|
||||
[BenchmarkStatusValueMap.Stopped]: StatusMaps.warning
|
||||
};
|
||||
|
||||
export const ProfileValueMap = {
|
||||
|
||||
@@ -91,6 +91,7 @@ export interface BenchmarkListItem extends FormData {
|
||||
updated_at: string;
|
||||
state: string;
|
||||
state_message: string;
|
||||
progress: number;
|
||||
instance_snapshot: InstanceSnapshot;
|
||||
gpu_snapshot: GPUSnapshot[];
|
||||
}
|
||||
|
||||
@@ -77,6 +77,7 @@ const BasicForm: React.FC = () => {
|
||||
]}
|
||||
>
|
||||
<SealSelect
|
||||
disabled={action === PageAction.EDIT}
|
||||
loading={clusterLoading}
|
||||
options={clusterList}
|
||||
label={intl.formatMessage({ id: 'clusters.title' })}
|
||||
|
||||
@@ -87,13 +87,11 @@ const DatasetForm: React.FC = () => {
|
||||
<Form.Item<FormData> hidden name="dataset_id">
|
||||
<SealInput.Input></SealInput.Input>
|
||||
</Form.Item>
|
||||
{profile === 'Custom' && (
|
||||
<RandomSettingsForm
|
||||
datasetList={datasetList}
|
||||
datasetLoading={datasetLoading}
|
||||
handleOnDataSetChange={handleOnDataSetChange}
|
||||
></RandomSettingsForm>
|
||||
)}
|
||||
<RandomSettingsForm
|
||||
datasetList={datasetList}
|
||||
datasetLoading={datasetLoading}
|
||||
handleOnDataSetChange={handleOnDataSetChange}
|
||||
></RandomSettingsForm>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,7 +17,6 @@ import FormContext from '../config/form-context';
|
||||
import { FormData, BenchmarkListItem as ListItem } from '../config/types';
|
||||
import Basic from './basic';
|
||||
import DatasetForm from './dataset';
|
||||
import RandomSettingsForm from './random-settings';
|
||||
|
||||
interface ProviderFormProps {
|
||||
ref?: any;
|
||||
@@ -90,16 +89,16 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
|
||||
}
|
||||
}, [showAdvanced, activeKey]);
|
||||
|
||||
const advancedItems = showAdvanced
|
||||
? [
|
||||
{
|
||||
key: TABKeysMap.ADVANCED,
|
||||
label: intl.formatMessage({ id: 'resources.form.advanced' }),
|
||||
forceRender: true,
|
||||
children: <RandomSettingsForm />
|
||||
}
|
||||
]
|
||||
: [];
|
||||
// const advancedItems = showAdvanced
|
||||
// ? [
|
||||
// {
|
||||
// key: TABKeysMap.ADVANCED,
|
||||
// label: intl.formatMessage({ id: 'resources.form.advanced' }),
|
||||
// forceRender: true,
|
||||
// children: <RandomSettingsForm />
|
||||
// }
|
||||
// ]
|
||||
// : [];
|
||||
|
||||
console.log('render form with profile:', showAdvanced);
|
||||
|
||||
@@ -144,8 +143,7 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
|
||||
label: intl.formatMessage({ id: 'common.title.config' }),
|
||||
forceRender: true,
|
||||
children: <DatasetForm />
|
||||
},
|
||||
...advancedItems
|
||||
}
|
||||
]}
|
||||
></CollapsePanel>
|
||||
</Form>
|
||||
|
||||
@@ -3,7 +3,7 @@ import AutoTooltip from '@/components/auto-tooltip';
|
||||
import StatusTag from '@/components/status-tag';
|
||||
import { tableSorter } from '@/config/settings';
|
||||
import { useIntl } from '@umijs/max';
|
||||
import { Typography } from 'antd';
|
||||
import { Progress, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/es/table';
|
||||
import _ from 'lodash';
|
||||
import { useMemo } from 'react';
|
||||
@@ -58,15 +58,24 @@ const useBenchmarkColumns = (
|
||||
ellipsis: {
|
||||
showTitle: false
|
||||
},
|
||||
width: 100,
|
||||
width: 120,
|
||||
render: (value: number, record: ListItem) => (
|
||||
<StatusTag
|
||||
statusValue={{
|
||||
status: BenchmarkStatus[value],
|
||||
text: BenchmarkStatusLabelMap[value],
|
||||
message: record.state_message || undefined
|
||||
}}
|
||||
/>
|
||||
<span className="flex-center gap-8">
|
||||
<StatusTag
|
||||
statusValue={{
|
||||
status: BenchmarkStatus[value],
|
||||
text: BenchmarkStatusLabelMap[value],
|
||||
message: record.state_message || undefined
|
||||
}}
|
||||
/>
|
||||
{record.progress !== undefined && record.progress < 100 && (
|
||||
<Progress
|
||||
type="circle"
|
||||
percent={_.round(record.progress, 0)}
|
||||
size={20}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
)
|
||||
},
|
||||
// {
|
||||
|
||||
@@ -27,8 +27,8 @@ import { FormData, BenchmarkListItem as ListItem } from './config/types';
|
||||
import useBenchmarkColumns from './hooks/use-benchmark-columns';
|
||||
import useColumnSettings from './hooks/use-column-settings';
|
||||
import useCreateBenchmark from './hooks/use-create-benchmark';
|
||||
import useExportData from './hooks/use-export-data';
|
||||
import useViewLogs from './hooks/use-view-logs';
|
||||
import { useExportBenchmark } from './services/use-export-benchmark';
|
||||
import useQueryDataset from './services/use-query-dataset';
|
||||
|
||||
const Benchmark: React.FC = () => {
|
||||
@@ -65,6 +65,7 @@ const Benchmark: React.FC = () => {
|
||||
const { SettingsButton, selectedColumns } = useColumnSettings();
|
||||
|
||||
const { datasetList, fetchDatasetData } = useQueryDataset();
|
||||
const { exportData } = useExportBenchmark();
|
||||
|
||||
useEffect(() => {
|
||||
fetchModelList({ page: -1 });
|
||||
@@ -154,13 +155,8 @@ const Benchmark: React.FC = () => {
|
||||
handleOnCellClick
|
||||
);
|
||||
|
||||
const { exportData } = useExportData({ columns: columns });
|
||||
|
||||
const handleExportData = () => {
|
||||
const list = dataSource.dataList.filter((item) =>
|
||||
rowSelection.selectedRowKeys.includes(item.id)
|
||||
);
|
||||
exportData(list);
|
||||
exportData(rowSelection.selectedRowKeys);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import { GPUSTACK_API_BASE_URL } from '@/config/settings';
|
||||
import { downloadFile } from '@/utils/download-stream';
|
||||
import { message } from 'antd';
|
||||
import { EXPORT_BENCHMARK_LIST } from '../apis';
|
||||
|
||||
const matchFilename = (disposition: string | null): string | undefined => {
|
||||
if (!disposition) return '';
|
||||
|
||||
const match = disposition.match(/filename="?([^"]+)"?/);
|
||||
const filename = match ? match[1] : '';
|
||||
return filename;
|
||||
};
|
||||
|
||||
export function useExportBenchmark() {
|
||||
const exportData = async (data: any[]) => {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${GPUSTACK_API_BASE_URL}${EXPORT_BENCHMARK_LIST}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
}
|
||||
);
|
||||
// header
|
||||
const contentDispostion = res.headers.get('content-Disposition');
|
||||
const filename =
|
||||
matchFilename(contentDispostion) || `benchmark-export.yml`;
|
||||
if (res.ok) {
|
||||
const blob = await res.blob();
|
||||
downloadFile(blob, filename);
|
||||
} else {
|
||||
message.error('Download failed');
|
||||
}
|
||||
} catch (error) {
|
||||
message.error('Download failed');
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
exportData
|
||||
};
|
||||
}
|
||||
@@ -36,6 +36,7 @@ const useStyles = createStyles(({ token, css }) => {
|
||||
interface LocalUserFormProps {
|
||||
handleLogin: (values: any) => void;
|
||||
form: FormInstance;
|
||||
loading?: boolean;
|
||||
loginOption: {
|
||||
saml: boolean;
|
||||
oidc: boolean;
|
||||
@@ -144,6 +145,7 @@ const LocalUserForm: React.FC<LocalUserFormProps> = (props) => {
|
||||
htmlType="submit"
|
||||
type="primary"
|
||||
block
|
||||
loading={props.loading}
|
||||
style={{ height: '48px', fontSize: '14px' }}
|
||||
>
|
||||
{intl.formatMessage({ id: 'common.button.login' })}
|
||||
|
||||
@@ -148,7 +148,7 @@ const LoginForm = () => {
|
||||
};
|
||||
|
||||
// local user authentication
|
||||
const { handleLogin } = useLocalAuth({
|
||||
const { handleLogin, submitLoading } = useLocalAuth({
|
||||
fetchUserInfo,
|
||||
form,
|
||||
onSuccess: async (userInfo) => {
|
||||
@@ -254,6 +254,7 @@ const LoginForm = () => {
|
||||
<LocalUserForm
|
||||
handleLogin={handleLogin}
|
||||
form={form}
|
||||
loading={submitLoading}
|
||||
loginOption={SSOAuth.options}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import { FormInstance } from 'antd';
|
||||
import CryptoJS from 'crypto-js';
|
||||
import { useAtom } from 'jotai';
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { login } from '../apis';
|
||||
|
||||
interface UseLocalAuthOptions {
|
||||
@@ -27,6 +27,7 @@ export const useLocalAuth = ({
|
||||
form
|
||||
}: UseLocalAuthOptions) => {
|
||||
const [initialPassword, setInitialPassword] = useAtom(initialPasswordAtom);
|
||||
const [submitLoading, setSubmitLoading] = useState<boolean>(false);
|
||||
|
||||
// Encrypt password before storing
|
||||
const encryptPassword = (password: string) => {
|
||||
@@ -66,6 +67,7 @@ export const useLocalAuth = ({
|
||||
|
||||
// click login button
|
||||
const handleLogin = async (values: any) => {
|
||||
setSubmitLoading(true);
|
||||
try {
|
||||
await login({
|
||||
username: values.username,
|
||||
@@ -86,6 +88,8 @@ export const useLocalAuth = ({
|
||||
onSuccess?.(userInfo);
|
||||
} catch (error: any) {
|
||||
onError?.(error);
|
||||
} finally {
|
||||
setSubmitLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -94,6 +98,7 @@ export const useLocalAuth = ({
|
||||
}, []);
|
||||
|
||||
return {
|
||||
handleLogin
|
||||
handleLogin,
|
||||
submitLoading
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user