chore: profile config

This commit is contained in:
jialin
2026-01-30 18:48:55 +08:00
parent 8f2eea70f6
commit 1a2e82f079
27 changed files with 730 additions and 231 deletions
+1 -1
View File
@@ -56,7 +56,7 @@ declare namespace Global {
currentUser?: UserInfo; currentUser?: UserInfo;
} }
type SearchParams = Pagination & { search?: string }; type SearchParams = Pagination & { search?: string; [key: string]: any };
type MessageType = 'transition' | 'warning' | 'danger' | 'success' | 'info'; type MessageType = 'transition' | 'warning' | 'danger' | 'success' | 'info';
+13 -8
View File
@@ -1,10 +1,15 @@
import { request } from '@umijs/max'; import { request } from '@umijs/max';
import { CancelToken } from 'axios'; import { CancelToken } from 'axios';
import { BenchmarkMetricsFormData } from '../config/detail-types'; import {
import { BenchmarkListItem, DatasetListItem, FormData } from '../config/types'; BenchmarkListItem,
DatasetListItem,
FormData,
ProfileOption
} from '../config/types';
export const BENCHMARKS_API = '/benchmarks'; export const BENCHMARKS_API = '/benchmarks';
export const DATASETS_API = '/datasets'; export const DATASETS_API = '/datasets';
export const PROFILES_CONFIG_API = '/benchmark-profiles/default-config';
export async function queryBenchmarkList( export async function queryBenchmarkList(
params: Global.SearchParams, params: Global.SearchParams,
@@ -83,18 +88,18 @@ export async function queryDatasetList(
}); });
} }
export async function queryBenchmarkMetrics( export async function queryProfiles(
params: { params: {
id: number; id?: number | string;
data: BenchmarkMetricsFormData;
}, },
options?: { options?: {
token?: CancelToken; token?: CancelToken;
} }
) { ) {
return request<any>(`${BENCHMARKS_API}/${params.id}/metrics`, { return request<{
method: 'POST', profiles: ProfileOption[];
data: params.data, }>(`${PROFILES_CONFIG_API}`, {
method: 'get',
cancelToken: options?.token cancelToken: options?.token
}); });
} }
@@ -0,0 +1,64 @@
import { Descriptions, DescriptionsProps } from 'antd';
import React from 'react';
import { useDetailContext } from '../../config/detail-context';
import Section from '../summary/section';
const Benchmark: React.FC = () => {
const { detailData } = useDetailContext();
const items: DescriptionsProps['items'] = [
{
key: '1',
label: 'Profile',
children: detailData?.profile || '-'
},
{
key: '2',
label: 'Dataset',
children: detailData?.dataset_name || '-'
},
{
key: '3',
label: 'Token Length (In/Out)',
children: (
<span>
{detailData?.dataset_input_tokens || '-'} /{' '}
{detailData?.dataset_output_tokens || '-'}
</span>
)
},
{
key: '5',
label: 'Seed',
children: detailData?.seed || '-'
},
{
key: '6',
label: 'Request Rate',
children: detailData?.request_rate || '-'
},
{
key: '7',
label: 'Total Requests',
children: detailData?.total_requests || '-'
}
];
return (
<Section title="Benchmark Parameters">
<Descriptions
items={items}
colon={false}
column={3}
layout="vertical"
styles={{
content: {
justifyContent: 'flex-start'
}
}}
></Descriptions>
</Section>
);
};
export default Benchmark;
@@ -1,9 +1,23 @@
import React from 'react'; import React from 'react';
import styled from 'styled-components';
import { useDetailContext } from '../../config/detail-context'; import { useDetailContext } from '../../config/detail-context';
import Benchmark from './benchmark';
import Instance from './instance';
const Container = styled.div`
display: flex;
flex-direction: column;
gap: 16px;
`;
const Configure: React.FC = () => { const Configure: React.FC = () => {
const { detailData } = useDetailContext(); const { detailData } = useDetailContext();
return <div>Configure Content</div>; return (
<Container>
<Instance />
<Benchmark />
</Container>
);
}; };
export default Configure; export default Configure;
@@ -0,0 +1,101 @@
import StatusTag from '@/components/status-tag';
import { InstanceStatusMapValue, status } from '@/pages/llmodels/config';
import { convertFileSize } from '@/utils';
import { Descriptions } from 'antd';
import _ from 'lodash';
import React, { useMemo } from 'react';
import { useDetailContext } from '../../config/detail-context';
import Section from '../summary/section';
const calcTotalVram = (vram: Record<string, number>) => {
return _.sum(_.values(vram));
};
const Instance: React.FC = () => {
const { detailData } = useDetailContext();
const items = useMemo(() => {
const { snapshot } = detailData;
const [instanceName, instanceData] =
Object.entries(snapshot.instances || {})[0] || [];
return [
{
key: '1',
label: 'Instance Name',
children: (
<div className="flex-center gap-8">
<span>{instanceName}</span>
<StatusTag
statusValue={{
status: status[instanceData.state],
text: InstanceStatusMapValue[instanceData.state],
message: detailData.state_message || undefined
}}
/>
</div>
)
},
{
key: '2',
label: 'Worker',
children: instanceData?.worker_name || '-'
// children:
// instanceData?.ports?.length > 0
// ? `${instanceData.worker_ip}:${instanceData.ports[0]}`
// : instanceData.worker_ip || '-'
},
{
key: '3',
label: 'GPU Indexes',
children:
_.join(
instanceData.gpu_indexes?.sort?.((a, b) => a - b),
','
) || '-'
},
{
key: '6',
label: 'GPU Type',
children: instanceData?.gpu_type || '-'
},
{
key: '4',
label: 'Allocated VRAM',
children:
convertFileSize(
instanceData.computed_resource_claim?.vram
? calcTotalVram(instanceData.computed_resource_claim?.vram)
: 0,
1
) || '-'
},
{
key: '5',
label: 'Backend',
children: `${instanceData?.backend || '-'} ${
instanceData.backend_version
? `(${instanceData.backend_version})`
: ''
}`
}
];
}, [detailData]);
return (
<Section title="Model Instance">
<Descriptions
items={items}
colon={false}
column={3}
layout="vertical"
styles={{
content: {
justifyContent: 'flex-start'
}
}}
></Descriptions>
</Section>
);
};
export default Instance;
@@ -1,16 +1,28 @@
import AutoTooltip from '@/components/auto-tooltip';
import { convertFileSize } from '@/utils'; import { convertFileSize } from '@/utils';
import { Descriptions, DescriptionsProps } from 'antd'; import { Descriptions, DescriptionsProps } from 'antd';
import React from 'react'; import React from 'react';
import styled from 'styled-components';
import { GPUData } from '../../config/detail-types'; import { GPUData } from '../../config/detail-types';
import Section from '../summary/section'; import Section from '../summary/section';
const Content = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
align-items: flex-start;
.name {
font-weight: 500;
}
`;
const Environment: React.FC<GPUData> = (props) => { const Environment: React.FC<GPUData> = (props) => {
const items: DescriptionsProps['items'] = [ const items: DescriptionsProps['items'] = [
{ // {
key: '4', // key: '4',
label: 'Name', // label: 'Name',
children: props.name // children: <AutoTooltip ghost>{props.name}</AutoTooltip>
}, // },
{ {
key: '1', key: '1',
label: 'VRAM', label: 'VRAM',
@@ -18,12 +30,12 @@ const Environment: React.FC<GPUData> = (props) => {
}, },
{ {
key: '3', key: '3',
label: 'Driver Version', label: 'Driver',
children: props.driver_version children: props.driver_version
}, },
{ {
key: '2', key: '2',
label: 'Runtime Version', label: 'Runtime',
children: props.runtime_version children: props.runtime_version
}, },
{ {
@@ -35,21 +47,36 @@ const Environment: React.FC<GPUData> = (props) => {
key: '5', key: '5',
label: 'Vendor', label: 'Vendor',
children: props.vendor children: props.vendor
},
{
key: '7',
label: 'GPU Type',
children: props.type
} }
]; ];
return ( return (
<Section title={`GPU ${props.index}`}> <Section
<Descriptions title={
items={items} <span style={{ color: 'var(--ant-color-text-tertiary)' }}>
colon={false} GPU {props.index}
column={3} </span>
layout="vertical" }
styles={{ >
content: { <Content>
justifyContent: 'flex-start' <div className="name">
} <AutoTooltip ghost>{props.name}</AutoTooltip>
}} </div>
></Descriptions> <Descriptions
items={items}
colon={true}
column={3}
styles={{
content: {
justifyContent: 'flex-start'
}
}}
></Descriptions>
</Content>
</Section> </Section>
); );
}; };
@@ -63,7 +63,14 @@ const Environment: React.FC = () => {
gpuData={mainWorker.gpuData} gpuData={mainWorker.gpuData}
title={ title={
<div className="flex-center gap-8"> <div className="flex-center gap-8">
<Tag color="geekblue">Sub</Tag> <Tag
style={{
backgroundColor: 'var(--ant-color-fill-secondary)',
color: 'var(--ant-color-text-tertiary)'
}}
>
Sub
</Tag>
<span>{mainWorker?.workerData?.name}</span> <span>{mainWorker?.workerData?.name}</span>
</div> </div>
} }
@@ -31,6 +31,10 @@ const Environment: React.FC<{
<Section <Section
title={title} title={title}
styles={{ styles={{
wraper: {
border: 'none',
overflow: 'unset'
},
container: { container: {
border: 'none', border: 'none',
paddingInline: 16, paddingInline: 16,
@@ -1,3 +1,4 @@
import BaseSelect from '@/components/seal-form/base/select';
import { SearchOutlined, SyncOutlined } from '@ant-design/icons'; import { SearchOutlined, SyncOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Button, Input, Space } from 'antd'; import { Button, Input, Space } from 'antd';
@@ -6,11 +7,19 @@ import React from 'react';
export interface RightActionsProps { export interface RightActionsProps {
handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void; handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
handleSearch: () => void; handleSearch: () => void;
handleQueryChange: (value: any, option?: any) => void;
modelList?: Global.BaseOption<number>[];
datasetList?: Global.BaseOption<string>[];
gpuVendorList?: Global.BaseOption<string>[];
} }
const RightActions: React.FC<RightActionsProps> = ({ const RightActions: React.FC<RightActionsProps> = ({
handleInputChange, handleInputChange,
handleSearch handleSearch,
handleQueryChange,
modelList,
datasetList,
gpuVendorList
}) => { }) => {
const intl = useIntl(); const intl = useIntl();
@@ -29,7 +38,42 @@ const RightActions: React.FC<RightActionsProps> = ({
allowClear allowClear
onChange={handleInputChange} onChange={handleInputChange}
></Input> ></Input>
{/* <CompareConditions></CompareConditions> */} <BaseSelect
allowClear
placeholder="Filter by model"
style={{ width: 150 }}
options={modelList}
onChange={(value, option) =>
handleQueryChange({
model_name: value,
page: 1
})
}
></BaseSelect>
<BaseSelect
allowClear
placeholder="Filter by dataset"
style={{ width: 150 }}
options={datasetList}
onChange={(value, option) =>
handleQueryChange({
dataset_name: value,
page: 1
})
}
></BaseSelect>
<BaseSelect
allowClear
placeholder="Filter by GPU vendor"
onChange={(value, option) =>
handleQueryChange({
gpu_summary: value,
page: 1
})
}
style={{ width: 180 }}
options={gpuVendorList}
></BaseSelect>
<Button <Button
type="text" type="text"
style={{ color: 'var(--ant-color-text-tertiary)' }} style={{ color: 'var(--ant-color-text-tertiary)' }}
@@ -12,6 +12,7 @@ export interface RightActionsProps {
handleDeleteByBatch: () => void; handleDeleteByBatch: () => void;
handleClickPrimary?: () => void; handleClickPrimary?: () => void;
handleSettingFields?: () => void; handleSettingFields?: () => void;
handleExport?: () => void;
settingButton?: React.ReactNode; settingButton?: React.ReactNode;
buttonText?: string; buttonText?: string;
rowSelection: { rowSelection: {
@@ -23,6 +24,7 @@ const RightActions: React.FC<RightActionsProps> = ({
handleDeleteByBatch, handleDeleteByBatch,
handleClickPrimary, handleClickPrimary,
handleSettingFields, handleSettingFields,
handleExport,
settingButton, settingButton,
buttonText, buttonText,
rowSelection rowSelection
@@ -36,7 +38,7 @@ const RightActions: React.FC<RightActionsProps> = ({
}, },
{ {
label: 'common.button.delete', label: 'common.button.delete',
key: 'start', key: 'delete',
props: { props: {
danger: true danger: true
}, },
@@ -44,7 +46,13 @@ const RightActions: React.FC<RightActionsProps> = ({
} }
]; ];
const handleActionSelect = (val: string) => {}; const handleActionSelect = (val: string) => {
if (val === 'delete') {
handleDeleteByBatch();
} else if (val === 'export') {
handleExport?.();
}
};
return ( return (
<Space size={16}> <Space size={16}>
@@ -1,12 +1,10 @@
import AutoTooltip from '@/components/auto-tooltip'; import AutoTooltip from '@/components/auto-tooltip';
import StatusTag from '@/components/status-tag';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Col, Descriptions, DescriptionsProps, Row, Statistic } from 'antd'; import { Col, Descriptions, DescriptionsProps, Row, Statistic } from 'antd';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { round } from 'lodash'; import { round } from 'lodash';
import React from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { BenchmarkStatus, BenchmarkStatusLabelMap } from '../../config';
import { useDetailContext } from '../../config/detail-context'; import { useDetailContext } from '../../config/detail-context';
import PercentileResult from './percentile-result'; import PercentileResult from './percentile-result';
import Section from './section'; import Section from './section';
@@ -158,7 +156,7 @@ const Summary: React.FC = () => {
<Container> <Container>
<Row gutter={16}> <Row gutter={16}>
{cardFields.map((field) => ( {cardFields.map((field) => (
<Col span={8} key={field.key} style={{ padding: '8px' }}> <Col span={4} key={field.key} style={{ padding: '8px' }}>
<Card> <Card>
<Statistic <Statistic
title={field.label} title={field.label}
@@ -180,17 +178,7 @@ const Summary: React.FC = () => {
</Col> </Col>
))} ))}
</Row> </Row>
<Section> <Section title="Metadata">
<div className="flex-center section-title gap-8">
<span>{detailData.model_instance_name}</span>
<StatusTag
statusValue={{
status: BenchmarkStatus[detailData.state],
text: BenchmarkStatusLabelMap[detailData.state],
message: detailData.state_message || undefined
}}
/>
</div>
<Descriptions <Descriptions
items={items} items={items}
colon={false} colon={false}
@@ -205,10 +193,7 @@ const Summary: React.FC = () => {
</Section> </Section>
<Box> <Box>
<Section> <Section title="Throughput">
<div className="flex-center section-title gap-8">
<span>Throughput</span>
</div>
<Descriptions <Descriptions
items={throughputFields} items={throughputFields}
colon={true} colon={true}
@@ -220,10 +205,7 @@ const Summary: React.FC = () => {
}} }}
></Descriptions> ></Descriptions>
</Section> </Section>
<Section> <Section title="Latency">
<div className="flex-center section-title gap-8">
<span>Latency</span>
</div>
<Descriptions <Descriptions
items={latencyFields} items={latencyFields}
colon={true} colon={true}
@@ -1,7 +1,5 @@
import _ from 'lodash'; import React from 'react';
import React, { useEffect } from 'react';
import { useDetailContext } from '../../config/detail-context'; import { useDetailContext } from '../../config/detail-context';
import useQueryMtrics from '../../services/use-query-metrics';
const fields = [ const fields = [
'requests_per_second_mean', 'requests_per_second_mean',
@@ -15,25 +13,7 @@ const fields = [
]; ];
const Summary: React.FC = () => { const Summary: React.FC = () => {
const { detailData, id } = useDetailContext(); const { detailData } = useDetailContext();
const {
detailData: metricsData,
fetchData,
cancelRequest
} = useQueryMtrics();
useEffect(() => {
if (id) {
fetchData({
id: id,
data: {
..._.pick(detailData, fields)
}
});
} else {
cancelRequest();
}
}, [id]);
return <div></div>; return <div></div>;
}; };
@@ -1,8 +1,14 @@
import styled from 'styled-components'; import styled from 'styled-components';
const Wrapper = styled.div`
border: 1px solid var(--ant-color-border);
border-radius: var(--ant-border-radius);
overflow: hidden;
`;
const Container = styled.div` const Container = styled.div`
padding: 16px; padding: 16px;
border: 1px solid var(--ant-color-border); // border: 1px solid var(--ant-color-border);
border-radius: 4px; border-radius: 4px;
.section-title { .section-title {
font-weight: 500; font-weight: 500;
@@ -17,25 +23,29 @@ const Container = styled.div`
const Title = styled.div` const Title = styled.div`
display: flex; display: flex;
font-weight: 500; font-weight: 500;
margin-bottom: 12px; // margin-bottom: 12px;
font-size: 14px; font-size: 14px;
gap: 8px; gap: 8px;
padding: 12px 16px;
align-items: center; align-items: center;
background-color: var(--ant-color-fill-tertiary);
border-radius: 4px 4px 0 0;
`; `;
const DetailSection: React.FC<{ const DetailSection: React.FC<{
children: React.ReactNode; children: React.ReactNode;
title?: React.ReactNode; title?: React.ReactNode;
styles?: { styles?: {
wraper?: React.CSSProperties;
container?: React.CSSProperties; container?: React.CSSProperties;
title?: React.CSSProperties; title?: React.CSSProperties;
}; };
}> = ({ children, title, styles }) => { }> = ({ children, title, styles }) => {
return ( return (
<Container style={styles?.container}> <Wrapper style={styles?.wraper}>
{title && <Title style={styles?.title}>{title}</Title>} {title && <Title style={styles?.title}>{title}</Title>}
{children} <Container style={styles?.container}>{children}</Container>
</Container> </Wrapper>
); );
}; };
+25 -5
View File
@@ -28,26 +28,46 @@ export const BenchmarkStatus: Record<string, StatusType> = {
[BenchmarkStatusValueMap.Unreachable]: StatusMaps.error [BenchmarkStatusValueMap.Unreachable]: StatusMaps.error
}; };
export const ProfileValueMap = {
LatencyShort: 'latency_short',
ThroughputMedium: 'throughput_medium',
LongContextStress: 'long_context_stress',
GenerationHeavy: 'generation_heavy',
Custom: 'Custom'
};
export const ProfileLabelMap = {
[ProfileValueMap.LatencyShort]: 'benchmark.form.profile.latency',
[ProfileValueMap.ThroughputMedium]: 'benchmark.form.profile.throughput',
[ProfileValueMap.LongContextStress]: 'benchmark.form.profile.longContext',
[ProfileValueMap.GenerationHeavy]: 'benchmark.form.profile.heavy',
[ProfileValueMap.Custom]: 'benchmark.form.profile.custom'
};
export const profileOptions = [ export const profileOptions = [
{ {
label: 'benchmark.form.profile.latency', label: 'benchmark.form.profile.latency',
value: 'latency_short', value: ProfileValueMap.LatencyShort,
locale: true locale: true
}, },
{ {
label: 'benchmark.form.profile.throughput', label: 'benchmark.form.profile.throughput',
value: 'throughput_medium', value: ProfileValueMap.ThroughputMedium,
locale: true locale: true
}, },
{ {
label: 'benchmark.form.profile.longContext', label: 'benchmark.form.profile.longContext',
value: 'long_context_stress', value: ProfileValueMap.LongContextStress,
locale: true locale: true
}, },
{ {
label: 'benchmark.form.profile.heavy', label: 'benchmark.form.profile.heavy',
value: 'generation_heavy', value: ProfileValueMap.GenerationHeavy,
locale: true locale: true
}, },
{ label: 'benchmark.form.profile.custom', value: 'custom', locale: true } {
label: 'benchmark.form.profile.custom',
value: ProfileValueMap.Custom,
locale: true
}
]; ];
+13
View File
@@ -87,6 +87,7 @@ export interface FormData {
state_message: string; state_message: string;
worker_id: number; worker_id: number;
gpu_summary: string; gpu_summary: string;
seed: number;
gpu_vendor_summary: string; gpu_vendor_summary: string;
} }
@@ -101,4 +102,16 @@ export interface DatasetListItem {
source: string; source: string;
prompt_tokens: number; prompt_tokens: number;
output_tokens: number; output_tokens: number;
id: number;
}
export interface ProfileOption {
name: string;
description: string;
dataset_name: string;
dataset_source: string;
dataset_prompt_tokens: number;
dataset_output_tokens: number;
request_rate: number;
total_requests: number;
} }
+6 -1
View File
@@ -1,5 +1,5 @@
import { useIntl, useNavigate, useSearchParams } from '@umijs/max'; import { useIntl, useNavigate, useSearchParams } from '@umijs/max';
import React from 'react'; import React, { useEffect } from 'react';
import { PageContainerInner } from '../_components/page-box'; import { PageContainerInner } from '../_components/page-box';
import PageBreadcrumb from '../_components/page-breadcrumb'; import PageBreadcrumb from '../_components/page-breadcrumb';
import DetailContent from './components/detail-content'; import DetailContent from './components/detail-content';
@@ -19,6 +19,11 @@ const Details: React.FC = () => {
title: name title: name
} }
]; ];
useEffect(() => {
document.title = `${intl.formatMessage({ id: 'benchmark.title' })} - ${name}`;
}, [name, intl]);
return ( return (
<PageContainerInner <PageContainerInner
header={{ header={{
+20 -3
View File
@@ -5,6 +5,10 @@ import { PageAction } from '@/config';
import useAppUtils from '@/hooks/use-app-utils'; import useAppUtils from '@/hooks/use-app-utils';
import { ClusterStatusValueMap } from '@/pages/cluster-management/config'; import { ClusterStatusValueMap } from '@/pages/cluster-management/config';
import { useQueryClusterList } from '@/pages/cluster-management/services/use-query-cluster-list'; import { useQueryClusterList } from '@/pages/cluster-management/services/use-query-cluster-list';
import {
InstanceStatusMap,
InstanceStatusMapValue
} from '@/pages/llmodels/config';
import { useQueryModelInstancesList } from '@/pages/llmodels/services/use-query-model-instances'; import { useQueryModelInstancesList } from '@/pages/llmodels/services/use-query-model-instances';
import { useQueryModelList } from '@/pages/llmodels/services/use-query-model-list'; import { useQueryModelList } from '@/pages/llmodels/services/use-query-model-list';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
@@ -28,9 +32,9 @@ const BasicForm: React.FC = () => {
} = useQueryClusterList(); } = useQueryClusterList();
const { const {
loading: modelLoading, loading: modelLoading,
fetchModelList, fetchData: fetchModelList,
cancelRequest: cancelModelRequest, cancelRequest: cancelModelRequest,
modelList dataList: modelList
} = useQueryModelList(); } = useQueryModelList();
const { const {
loading: instanceLoading, loading: instanceLoading,
@@ -65,6 +69,15 @@ const BasicForm: React.FC = () => {
form.setFieldValue('model_id', option?.id); form.setFieldValue('model_id', option?.id);
}; };
const optionRender = (option: any) => {
return (
<span className="flex-center">
{option.label}
<span className="text-tertiary m-l-4">{`[${InstanceStatusMapValue[option.data?.state]}]`}</span>
</span>
);
};
useEffect(() => { useEffect(() => {
const initClusterId = (list: any[]) => { const initClusterId = (list: any[]) => {
// Find default cluster // Find default cluster
@@ -163,7 +176,11 @@ const BasicForm: React.FC = () => {
> >
<SealSelect <SealSelect
loading={instanceLoading} loading={instanceLoading}
options={instanceList} options={instanceList.map((item) => ({
...item,
disabled: item.state !== InstanceStatusMap.Running
}))}
optionRender={optionRender}
onOpenChange={onInstanceOpenChange} onOpenChange={onInstanceOpenChange}
label={intl.formatMessage({ id: 'benchmark.table.instance' })} label={intl.formatMessage({ id: 'benchmark.table.instance' })}
required required
+116 -22
View File
@@ -1,40 +1,129 @@
import AutoComplete from '@/components/seal-form/auto-complete';
import SealInputNumber from '@/components/seal-form/input-number';
import SealInput from '@/components/seal-form/seal-input'; import SealInput from '@/components/seal-form/seal-input';
import SealSelect from '@/components/seal-form/seal-select'; import SealSelect from '@/components/seal-form/seal-select';
import useAppUtils from '@/hooks/use-app-utils'; import useAppUtils from '@/hooks/use-app-utils';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
import _ from 'lodash';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { profileOptions } from '../config'; import { ProfileValueMap } from '../config';
import { useFormContext } from '../config/form-context'; import { useFormContext } from '../config/form-context';
import { FormData } from '../config/types'; import { FormData } from '../config/types';
import useQueryDataset from '../services/use-query-dataset'; import useQueryDataset from '../services/use-query-dataset';
import useQueryProfiles from '../services/use-query-profiles';
import RandomSettingsForm from './random-settings'; import RandomSettingsForm from './random-settings';
const profileFields = [
'dataset_prompt_tokens',
'dataset_output_tokens',
'request_rate',
'total_requests'
];
const DatasetForm: React.FC = () => { const DatasetForm: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const { getRuleMessage } = useAppUtils(); const { getRuleMessage } = useAppUtils();
const { action, open } = useFormContext(); const { action, open } = useFormContext();
const { const {
dataList: datasetList, datasetList,
loading: datasetLoading, loading: datasetLoading,
fetchData, fetchDatasetData,
cancelRequest: cancelDatasetRequest cancelRequest: cancelDatasetRequest
} = useQueryDataset(); } = useQueryDataset();
const {
profilesOptions,
fetchProfilesData,
cancelRequest: cancelProfilesRequest
} = useQueryProfiles();
const profileConfigCache = React.useRef<{ [key: string]: any }>({});
const handleOnDataSetChange = (value: any, option: any) => { const handleOnDataSetChange = (value: any, option: any) => {
form.setFieldValue('dataset_id', option?.data?.id); if (value === 'Custom') {
form.setFieldsValue({
profile: ProfileValueMap.Custom,
dataset_id: null,
dataset_prompt_tokens: null,
dataset_output_tokens: null,
request_rate: null,
total_requests: null
});
} else {
form.setFieldsValue({
profile: ProfileValueMap.Custom,
dataset_id: option?.data?.id,
dataset_prompt_tokens: option?.prompt_tokens,
dataset_output_tokens: option?.output_tokens,
request_rate: null,
total_requests: null
});
}
}; };
const handleOnDatasetOpenChange = async (open: boolean) => { const setCustomProfileValues = () => {
if (!datasetList.length && open) { form.setFieldsValue({
await fetchData({ page: -1 }); dataset_name: 'Custom',
dataset_id: null,
dataset_prompt_tokens: null,
dataset_output_tokens: null,
request_rate: null,
total_requests: null
});
};
const handleProfileChange = (value: string, option: any) => {
if (value === ProfileValueMap.Custom) {
setCustomProfileValues();
profileConfigCache.current = {};
} else {
const dataset_id = datasetList.find(
(item) => item.label === option.config?.dataset_name
)?.value;
form.setFieldsValue({
dataset_id: dataset_id,
..._.omit(option?.config, ['description', 'dataset_source'])
});
profileConfigCache.current = {
profile: value,
config: {
dataset_id: dataset_id,
..._.omit(option?.config, ['description', 'dataset_source'])
}
};
} }
}; };
const handleOnProfileConfigChange = async () => {
const values = form.getFieldsValue(profileFields);
if (
_.isEqual(values, {
..._.pick(profileConfigCache.current.config, profileFields)
})
) {
form.setFieldsValue({
profile: profileConfigCache.current.profile,
...profileConfigCache.current.config
});
return;
}
form.setFieldsValue({
profile: 'Custom',
dataset_name: 'Custom'
});
};
useEffect(() => { useEffect(() => {
if (!open) { if (!open) {
cancelDatasetRequest(); cancelDatasetRequest();
cancelProfilesRequest();
}
if (open) {
fetchProfilesData();
fetchDatasetData();
} }
}, [open]); }, [open]);
@@ -51,7 +140,8 @@ const DatasetForm: React.FC = () => {
]} ]}
> >
<SealSelect <SealSelect
options={profileOptions} onChange={handleProfileChange}
options={profilesOptions}
label={intl.formatMessage({ id: 'benchmark.form.profile' })} label={intl.formatMessage({ id: 'benchmark.form.profile' })}
required required
></SealSelect> ></SealSelect>
@@ -65,51 +155,55 @@ const DatasetForm: React.FC = () => {
} }
]} ]}
> >
<SealSelect <AutoComplete
options={datasetList.map((item) => ({ options={datasetList.map((item) => ({
label: item.name, ...item,
value: item.name label: item.label,
value: item.label
}))} }))}
loading={datasetLoading} loading={datasetLoading}
onChange={handleOnDataSetChange} onChange={handleOnDataSetChange}
onOpenChange={handleOnDatasetOpenChange}
label={intl.formatMessage({ id: 'benchmark.table.dataset' })} label={intl.formatMessage({ id: 'benchmark.table.dataset' })}
required required
></SealSelect> ></AutoComplete>
</Form.Item> </Form.Item>
<Form.Item<FormData> hidden name="dataset_id"> <Form.Item<FormData> hidden name="dataset_id">
<SealInput.Input></SealInput.Input> <SealInput.Input></SealInput.Input>
</Form.Item> </Form.Item>
<RandomSettingsForm></RandomSettingsForm> <RandomSettingsForm
onValueChange={handleOnProfileConfigChange}
></RandomSettingsForm>
<Form.Item<FormData> <Form.Item<FormData>
name="request_rate" name="request_rate"
rules={[ rules={[
{ {
required: true, required: true,
message: getRuleMessage('select', 'benchmark.table.requestRate') message: getRuleMessage('input', 'benchmark.table.requestRate')
} }
]} ]}
> >
<SealSelect <SealInputNumber
options={[]} min={0}
onChange={handleOnProfileConfigChange}
label={intl.formatMessage({ id: 'benchmark.table.requestRate' })} label={intl.formatMessage({ id: 'benchmark.table.requestRate' })}
required required
></SealSelect> ></SealInputNumber>
</Form.Item> </Form.Item>
<Form.Item<FormData> <Form.Item<FormData>
name="total_requests" name="total_requests"
rules={[ rules={[
{ {
required: true, required: true,
message: getRuleMessage('select', 'benchmark.form.totalRequests') message: getRuleMessage('input', 'benchmark.form.totalRequests')
} }
]} ]}
> >
<SealSelect <SealInputNumber
options={[]} min={0}
onChange={handleOnProfileConfigChange}
label={intl.formatMessage({ id: 'benchmark.form.totalRequests' })} label={intl.formatMessage({ id: 'benchmark.form.totalRequests' })}
required required
></SealSelect> ></SealInputNumber>
</Form.Item> </Form.Item>
</> </>
); );
+11 -1
View File
@@ -99,7 +99,17 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
open open
}} }}
> >
<Form form={form} onFinish={onFinish} initialValues={{}}> <Form
form={form}
onFinish={onFinish}
initialValues={{
dataset_prompt_tokens: null,
dataset_output_tokens: null,
total_requests: null,
request_rate: null,
seed: null
}}
>
<Basic /> <Basic />
<CollapsePanel <CollapsePanel
activeKey={activeKey} activeKey={activeKey}
+18 -33
View File
@@ -1,12 +1,13 @@
import SealInputNumber from '@/components/seal-form/input-number'; import SealInputNumber from '@/components/seal-form/input-number';
import SealSelect from '@/components/seal-form/seal-select';
import useAppUtils from '@/hooks/use-app-utils'; import useAppUtils from '@/hooks/use-app-utils';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
import React from 'react'; import React from 'react';
import { FormData } from '../config/types'; import { FormData } from '../config/types';
const DatasetForm: React.FC = () => { const DatasetForm: React.FC<{
onValueChange?: (value: any) => void;
}> = ({ onValueChange }) => {
const intl = useIntl(); const intl = useIntl();
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const { getRuleMessage } = useAppUtils(); const { getRuleMessage } = useAppUtils();
@@ -18,27 +19,16 @@ const DatasetForm: React.FC = () => {
rules={[ rules={[
{ {
required: true, required: true,
message: getRuleMessage( message: getRuleMessage('input', 'benchmark.table.inputTokenLength')
'select',
'benchmark.table.inputTokenLength'
)
} }
]} ]}
> >
<SealSelect <SealInputNumber
options={[ min={0}
{
label: '100',
value: 100
},
{
label: '128',
value: 128
}
]}
label={intl.formatMessage({ id: 'benchmark.table.inputTokenLength' })} label={intl.formatMessage({ id: 'benchmark.table.inputTokenLength' })}
required required
></SealSelect> onChange={onValueChange}
></SealInputNumber>
</Form.Item> </Form.Item>
<Form.Item<FormData> <Form.Item<FormData>
name="dataset_output_tokens" name="dataset_output_tokens"
@@ -46,33 +36,28 @@ const DatasetForm: React.FC = () => {
{ {
required: true, required: true,
message: getRuleMessage( message: getRuleMessage(
'select', 'input',
'benchmark.table.outputTokenLength' 'benchmark.table.outputTokenLength'
) )
} }
]} ]}
> >
<SealSelect <SealInputNumber
options={[ min={0}
{
label: '4',
value: 4
},
{
label: '8',
value: 8
}
]}
label={intl.formatMessage({ label={intl.formatMessage({
id: 'benchmark.table.outputTokenLength' id: 'benchmark.table.outputTokenLength'
})} })}
required required
></SealSelect> onChange={onValueChange}
></SealInputNumber>
</Form.Item> </Form.Item>
<Form.Item<FormData> name="seed"> <Form.Item<FormData>
name="seed"
getValueProps={(value) => ({ value: value || null })}
>
<SealInputNumber <SealInputNumber
min={0}
label={intl.formatMessage({ id: 'playground.image.params.seed' })} label={intl.formatMessage({ id: 'playground.image.params.seed' })}
required
></SealInputNumber> ></SealInputNumber>
</Form.Item> </Form.Item>
</> </>
@@ -0,0 +1,28 @@
import { exportJsonToExcel } from '@/utils/excel-reader';
const useExportData = (params: { columns: any[] }) => {
const { columns } = params;
const colIndexMap = columns.reduce(
(map, col) => {
if (col.dataIndex !== 'operations') {
map[col.dataIndex] = col.title;
}
return map;
},
{} as Record<string, any>
);
const exportData = (dataList: any[]) => {
const fileName = `benchmark.xlsx`;
exportJsonToExcel({
jsonData: dataList || [],
fileName: fileName,
fields: Object.keys(colIndexMap),
fieldLabels: colIndexMap,
formatMap: {}
});
};
return { exportData };
};
export default useExportData;
+38
View File
@@ -4,12 +4,15 @@ import { FilterBar } from '@/components/page-tools';
import { PageAction } from '@/config'; import { PageAction } from '@/config';
import { TABLE_SORT_DIRECTIONS } from '@/config/settings'; import { TABLE_SORT_DIRECTIONS } from '@/config/settings';
import useTableFetch from '@/hooks/use-table-fetch'; import useTableFetch from '@/hooks/use-table-fetch';
import { useQueryModelList } from '@/pages/llmodels/services/use-query-model-list';
import { useIntl, useNavigate } from '@umijs/max'; import { useIntl, useNavigate } from '@umijs/max';
import { useMemoizedFn } from 'ahooks'; import { useMemoizedFn } from 'ahooks';
import { ConfigProvider, Table, message } from 'antd'; import { ConfigProvider, Table, message } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import { useEffect, useState } from 'react';
import NoResult from '../_components/no-result'; import NoResult from '../_components/no-result';
import PageBox from '../_components/page-box'; import PageBox from '../_components/page-box';
import useQueryGPUs from '../resources/services/use-query-gpus';
import { import {
createBenchmark, createBenchmark,
deleteBenchmark, deleteBenchmark,
@@ -24,6 +27,7 @@ import { FormData, BenchmarkListItem as ListItem } from './config/types';
import useBenchmarkColumns from './hooks/use-benchmark-columns'; import useBenchmarkColumns from './hooks/use-benchmark-columns';
import useColumnSettings from './hooks/use-column-settings'; import useColumnSettings from './hooks/use-column-settings';
import useCreateBenchmark from './hooks/use-create-benchmark'; import useCreateBenchmark from './hooks/use-create-benchmark';
import useExportData from './hooks/use-export-data';
import useViewDetail from './hooks/use-view-detail'; import useViewDetail from './hooks/use-view-detail';
const Benchmark: React.FC = () => { const Benchmark: React.FC = () => {
@@ -38,6 +42,7 @@ const Benchmark: React.FC = () => {
fetchData, fetchData,
handlePageChange, handlePageChange,
handleTableChange, handleTableChange,
handleQueryChange,
handleSearch, handleSearch,
handleNameChange handleNameChange
} = useTableFetch<ListItem>({ } = useTableFetch<ListItem>({
@@ -54,7 +59,26 @@ const Benchmark: React.FC = () => {
closeViewDetailModal, closeViewDetailModal,
openViewDetailModalStatus openViewDetailModalStatus
} = useViewDetail(); } = useViewDetail();
const { dataList: modelList, fetchData: fetchModelList } = useQueryModelList({
getValue: (item: any) => item.name
});
const { fetchData: fetchGpuList } = useQueryGPUs();
const { SettingsButton, selectedColumns } = useColumnSettings(); const { SettingsButton, selectedColumns } = useColumnSettings();
const [gpuVendorList, setGpuVendorList] = useState<
Global.BaseOption<string>[]
>([]);
useEffect(() => {
fetchModelList({ page: -1 });
fetchGpuList({ page: -1 }).then((list) => {
const vendors = _.uniq(list.map((gpu) => gpu.vendor).filter((v) => !!v));
const vendorOptions = vendors.map((vendor: string) => ({
label: vendor,
value: vendor
}));
setGpuVendorList(vendorOptions);
});
}, []);
const handleAddBenchmark = () => { const handleAddBenchmark = () => {
openBenchmarkModal(PageAction.CREATE, 'Add Benchmark'); openBenchmarkModal(PageAction.CREATE, 'Add Benchmark');
@@ -137,6 +161,15 @@ const Benchmark: React.FC = () => {
handleOnCellClick handleOnCellClick
); );
const { exportData } = useExportData({ columns: columns });
const handleExportData = () => {
const list = dataSource.dataList.filter((item) =>
rowSelection.selectedRowKeys.includes(item.id)
);
exportData(list);
};
return ( return (
<> <>
<PageBox> <PageBox>
@@ -150,7 +183,10 @@ const Benchmark: React.FC = () => {
widths={{ input: 300 }} widths={{ input: 300 }}
left={ left={
<LeftActions <LeftActions
modelList={modelList}
gpuVendorList={gpuVendorList}
handleSearch={handleSearch} handleSearch={handleSearch}
handleQueryChange={handleQueryChange}
handleInputChange={handleNameChange} handleInputChange={handleNameChange}
></LeftActions> ></LeftActions>
} }
@@ -159,6 +195,7 @@ const Benchmark: React.FC = () => {
settingButton={SettingsButton} settingButton={SettingsButton}
handleDeleteByBatch={handleDeleteBatch} handleDeleteByBatch={handleDeleteBatch}
handleClickPrimary={handleAddBenchmark} handleClickPrimary={handleAddBenchmark}
handleExport={handleExportData}
buttonText={intl.formatMessage({ buttonText={intl.formatMessage({
id: 'benchmark.button.add' id: 'benchmark.button.add'
})} })}
@@ -176,6 +213,7 @@ const Benchmark: React.FC = () => {
sortDirections={TABLE_SORT_DIRECTIONS} sortDirections={TABLE_SORT_DIRECTIONS}
showSorterTooltip={false} showSorterTooltip={false}
rowKey="id" rowKey="id"
scroll={{ x: 1200 }}
onChange={handleTableChange} onChange={handleTableChange}
pagination={{ pagination={{
showSizeChanger: true, showSizeChanger: true,
@@ -1,4 +1,5 @@
import { useQueryDataList } from '@/hooks/use-query-data-list'; import { useQueryDataList } from '@/hooks/use-query-data-list';
import { useState } from 'react';
import { queryDatasetList } from '../apis'; import { queryDatasetList } from '../apis';
import { DatasetListItem } from '../config/types'; import { DatasetListItem } from '../config/types';
@@ -11,10 +12,34 @@ const useQueryDataset = () => {
fetchList: queryDatasetList fetchList: queryDatasetList
}); });
const [datasetList, setDatasetList] = useState<
Global.BaseOption<number | string>[]
>([]);
const fetchDatasetData = async () => {
const items = await fetchData({
page: -1
});
const list =
items?.map((item) => ({
...item,
label: item.name,
value: item.id
})) || [];
setDatasetList([
...list,
{
label: 'Custom',
value: 'Custom'
}
]);
};
return { return {
dataList, datasetList,
loading, loading,
fetchData, fetchDatasetData,
cancelRequest cancelRequest
}; };
}; };
@@ -1,22 +0,0 @@
import { useQueryData } from '@/hooks/use-query-data-list';
import { queryBenchmarkMetrics } from '../apis';
import { BenchmarkMetricsFormData } from '../config/detail-types';
export default function useQueryBenchmarkMetrics() {
const { detailData, loading, cancelRequest, fetchData } = useQueryData<
any,
{
id: number;
data: BenchmarkMetricsFormData;
}
>({
fetchDetail: queryBenchmarkMetrics,
key: 'benchmarkMetrics'
});
return {
detailData,
loading,
cancelRequest,
fetchData
};
}
@@ -0,0 +1,57 @@
import { useQueryData } from '@/hooks/use-query-data-list';
import _ from 'lodash';
import { useState } from 'react';
import { queryProfiles } from '../apis';
import { ProfileOption } from '../config/types';
export default function useQueryProfiles() {
const { detailData, loading, cancelRequest, fetchData } = useQueryData<{
profiles: ProfileOption[];
}>({
fetchDetail: queryProfiles,
key: 'profiles'
});
const [profilesOptions, setProfilesOptions] = useState<
{
label: string;
value: string;
config: Partial<ProfileOption>;
}[]
>([]);
const fetchProfilesData = async () => {
const res = await fetchData({});
const list =
res?.profiles?.map((item) => {
return {
label: item.name,
value: item.name,
config: {
..._.omit(item, 'name')
}
};
}) || [];
setProfilesOptions([
...list,
{
label: 'Custom',
value: 'Custom',
config: {
dataset_name: '',
dataset_prompt_tokens: null,
dataset_output_tokens: null,
request_rate: null,
total_requests: null
}
}
]);
};
return {
profilesOptions,
loading,
cancelRequest,
fetchProfilesData
};
}
@@ -1,67 +1,25 @@
import { createAxiosToken } from '@/hooks/use-chunk-request'; import { useQueryDataList } from '@/hooks/use-query-data-list';
import { useRequest } from 'ahooks';
import { message } from 'antd';
import { CancelTokenSource } from 'axios';
import { useEffect, useRef, useState } from 'react';
import { queryModelsList } from '../apis'; import { queryModelsList } from '../apis';
import { ListItem } from '../config/types'; import { ListItem } from '../config/types';
/** export const useQueryModelList = (optons?: {
* getLabel?: (item: ListItem) => string;
* @returns loading, fetch, dataList getValue?: (item: ListItem) => any;
*/ }) => {
export const useQueryModelList = () => { const { dataList, loading, fetchData, cancelRequest } = useQueryDataList<
const axiosTokenRef = useRef<CancelTokenSource | null>(null); ListItem,
const [dataList, setDataList] = useState< Global.SearchParams
Array<Partial<ListItem> & { label: string; value: number }> >({
>([]); key: 'modelList',
fetchList: queryModelsList
const { });
runAsync: fetchData,
loading,
cancel
} = useRequest(
async (params: { page: number; perPage?: number; cluster_id?: number }) => {
axiosTokenRef.current?.cancel();
axiosTokenRef.current = createAxiosToken();
const res = await queryModelsList(params, {
token: axiosTokenRef.current.token
});
setDataList(
res.items?.map((item: ListItem) => ({
...item,
label: item.name,
value: item.id
})) || []
);
return res.items || [];
},
{
manual: true,
onSuccess: (response) => {},
onError: (error) => {
message.error(error?.message || 'Failed to fetch model list');
setDataList([]);
}
}
);
const cancelRequest = () => {
cancel();
axiosTokenRef.current?.cancel();
};
useEffect(() => {
return () => {
cancel();
axiosTokenRef.current?.cancel();
};
}, []);
return { return {
dataList,
loading, loading,
modelList: dataList, fetchData,
cancelRequest, cancelRequest
fetchModelList: fetchData
}; };
}; };
export default useQueryModelList;
@@ -0,0 +1,25 @@
import { useQueryDataList } from '@/hooks/use-query-data-list';
import { queryGpuDevicesList } from '../apis';
import { GPUDeviceItem as ListItem } from '../config/types';
export const useQueryGPUs = (optons?: {
getLabel?: (item: ListItem) => string;
getValue?: (item: ListItem) => any;
}) => {
const { dataList, loading, fetchData, cancelRequest } = useQueryDataList<
ListItem,
Global.SearchParams
>({
key: 'gpuList',
fetchList: queryGpuDevicesList
});
return {
dataList,
loading,
fetchData,
cancelRequest
};
};
export default useQueryGPUs;