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;
}
type SearchParams = Pagination & { search?: string };
type SearchParams = Pagination & { search?: string; [key: string]: any };
type MessageType = 'transition' | 'warning' | 'danger' | 'success' | 'info';
+13 -8
View File
@@ -1,10 +1,15 @@
import { request } from '@umijs/max';
import { CancelToken } from 'axios';
import { BenchmarkMetricsFormData } from '../config/detail-types';
import { BenchmarkListItem, DatasetListItem, FormData } from '../config/types';
import {
BenchmarkListItem,
DatasetListItem,
FormData,
ProfileOption
} from '../config/types';
export const BENCHMARKS_API = '/benchmarks';
export const DATASETS_API = '/datasets';
export const PROFILES_CONFIG_API = '/benchmark-profiles/default-config';
export async function queryBenchmarkList(
params: Global.SearchParams,
@@ -83,18 +88,18 @@ export async function queryDatasetList(
});
}
export async function queryBenchmarkMetrics(
export async function queryProfiles(
params: {
id: number;
data: BenchmarkMetricsFormData;
id?: number | string;
},
options?: {
token?: CancelToken;
}
) {
return request<any>(`${BENCHMARKS_API}/${params.id}/metrics`, {
method: 'POST',
data: params.data,
return request<{
profiles: ProfileOption[];
}>(`${PROFILES_CONFIG_API}`, {
method: 'get',
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 styled from 'styled-components';
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 { detailData } = useDetailContext();
return <div>Configure Content</div>;
return (
<Container>
<Instance />
<Benchmark />
</Container>
);
};
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 { Descriptions, DescriptionsProps } from 'antd';
import React from 'react';
import styled from 'styled-components';
import { GPUData } from '../../config/detail-types';
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 items: DescriptionsProps['items'] = [
{
key: '4',
label: 'Name',
children: props.name
},
// {
// key: '4',
// label: 'Name',
// children: <AutoTooltip ghost>{props.name}</AutoTooltip>
// },
{
key: '1',
label: 'VRAM',
@@ -18,12 +30,12 @@ const Environment: React.FC<GPUData> = (props) => {
},
{
key: '3',
label: 'Driver Version',
label: 'Driver',
children: props.driver_version
},
{
key: '2',
label: 'Runtime Version',
label: 'Runtime',
children: props.runtime_version
},
{
@@ -35,21 +47,36 @@ const Environment: React.FC<GPUData> = (props) => {
key: '5',
label: 'Vendor',
children: props.vendor
},
{
key: '7',
label: 'GPU Type',
children: props.type
}
];
return (
<Section title={`GPU ${props.index}`}>
<Descriptions
items={items}
colon={false}
column={3}
layout="vertical"
styles={{
content: {
justifyContent: 'flex-start'
}
}}
></Descriptions>
<Section
title={
<span style={{ color: 'var(--ant-color-text-tertiary)' }}>
GPU {props.index}
</span>
}
>
<Content>
<div className="name">
<AutoTooltip ghost>{props.name}</AutoTooltip>
</div>
<Descriptions
items={items}
colon={true}
column={3}
styles={{
content: {
justifyContent: 'flex-start'
}
}}
></Descriptions>
</Content>
</Section>
);
};
@@ -63,7 +63,14 @@ const Environment: React.FC = () => {
gpuData={mainWorker.gpuData}
title={
<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>
</div>
}
@@ -31,6 +31,10 @@ const Environment: React.FC<{
<Section
title={title}
styles={{
wraper: {
border: 'none',
overflow: 'unset'
},
container: {
border: 'none',
paddingInline: 16,
@@ -1,3 +1,4 @@
import BaseSelect from '@/components/seal-form/base/select';
import { SearchOutlined, SyncOutlined } from '@ant-design/icons';
import { useIntl } from '@umijs/max';
import { Button, Input, Space } from 'antd';
@@ -6,11 +7,19 @@ import React from 'react';
export interface RightActionsProps {
handleInputChange: (e: React.ChangeEvent<HTMLInputElement>) => 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> = ({
handleInputChange,
handleSearch
handleSearch,
handleQueryChange,
modelList,
datasetList,
gpuVendorList
}) => {
const intl = useIntl();
@@ -29,7 +38,42 @@ const RightActions: React.FC<RightActionsProps> = ({
allowClear
onChange={handleInputChange}
></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
type="text"
style={{ color: 'var(--ant-color-text-tertiary)' }}
@@ -12,6 +12,7 @@ export interface RightActionsProps {
handleDeleteByBatch: () => void;
handleClickPrimary?: () => void;
handleSettingFields?: () => void;
handleExport?: () => void;
settingButton?: React.ReactNode;
buttonText?: string;
rowSelection: {
@@ -23,6 +24,7 @@ const RightActions: React.FC<RightActionsProps> = ({
handleDeleteByBatch,
handleClickPrimary,
handleSettingFields,
handleExport,
settingButton,
buttonText,
rowSelection
@@ -36,7 +38,7 @@ const RightActions: React.FC<RightActionsProps> = ({
},
{
label: 'common.button.delete',
key: 'start',
key: 'delete',
props: {
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 (
<Space size={16}>
@@ -1,12 +1,10 @@
import AutoTooltip from '@/components/auto-tooltip';
import StatusTag from '@/components/status-tag';
import { useIntl } from '@umijs/max';
import { Col, Descriptions, DescriptionsProps, Row, Statistic } from 'antd';
import dayjs from 'dayjs';
import { round } from 'lodash';
import React from 'react';
import styled from 'styled-components';
import { BenchmarkStatus, BenchmarkStatusLabelMap } from '../../config';
import { useDetailContext } from '../../config/detail-context';
import PercentileResult from './percentile-result';
import Section from './section';
@@ -158,7 +156,7 @@ const Summary: React.FC = () => {
<Container>
<Row gutter={16}>
{cardFields.map((field) => (
<Col span={8} key={field.key} style={{ padding: '8px' }}>
<Col span={4} key={field.key} style={{ padding: '8px' }}>
<Card>
<Statistic
title={field.label}
@@ -180,17 +178,7 @@ const Summary: React.FC = () => {
</Col>
))}
</Row>
<Section>
<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>
<Section title="Metadata">
<Descriptions
items={items}
colon={false}
@@ -205,10 +193,7 @@ const Summary: React.FC = () => {
</Section>
<Box>
<Section>
<div className="flex-center section-title gap-8">
<span>Throughput</span>
</div>
<Section title="Throughput">
<Descriptions
items={throughputFields}
colon={true}
@@ -220,10 +205,7 @@ const Summary: React.FC = () => {
}}
></Descriptions>
</Section>
<Section>
<div className="flex-center section-title gap-8">
<span>Latency</span>
</div>
<Section title="Latency">
<Descriptions
items={latencyFields}
colon={true}
@@ -1,7 +1,5 @@
import _ from 'lodash';
import React, { useEffect } from 'react';
import React from 'react';
import { useDetailContext } from '../../config/detail-context';
import useQueryMtrics from '../../services/use-query-metrics';
const fields = [
'requests_per_second_mean',
@@ -15,25 +13,7 @@ const fields = [
];
const Summary: React.FC = () => {
const { detailData, id } = useDetailContext();
const {
detailData: metricsData,
fetchData,
cancelRequest
} = useQueryMtrics();
useEffect(() => {
if (id) {
fetchData({
id: id,
data: {
..._.pick(detailData, fields)
}
});
} else {
cancelRequest();
}
}, [id]);
const { detailData } = useDetailContext();
return <div></div>;
};
@@ -1,8 +1,14 @@
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`
padding: 16px;
border: 1px solid var(--ant-color-border);
// border: 1px solid var(--ant-color-border);
border-radius: 4px;
.section-title {
font-weight: 500;
@@ -17,25 +23,29 @@ const Container = styled.div`
const Title = styled.div`
display: flex;
font-weight: 500;
margin-bottom: 12px;
// margin-bottom: 12px;
font-size: 14px;
gap: 8px;
padding: 12px 16px;
align-items: center;
background-color: var(--ant-color-fill-tertiary);
border-radius: 4px 4px 0 0;
`;
const DetailSection: React.FC<{
children: React.ReactNode;
title?: React.ReactNode;
styles?: {
wraper?: React.CSSProperties;
container?: React.CSSProperties;
title?: React.CSSProperties;
};
}> = ({ children, title, styles }) => {
return (
<Container style={styles?.container}>
<Wrapper style={styles?.wraper}>
{title && <Title style={styles?.title}>{title}</Title>}
{children}
</Container>
<Container style={styles?.container}>{children}</Container>
</Wrapper>
);
};
+25 -5
View File
@@ -28,26 +28,46 @@ export const BenchmarkStatus: Record<string, StatusType> = {
[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 = [
{
label: 'benchmark.form.profile.latency',
value: 'latency_short',
value: ProfileValueMap.LatencyShort,
locale: true
},
{
label: 'benchmark.form.profile.throughput',
value: 'throughput_medium',
value: ProfileValueMap.ThroughputMedium,
locale: true
},
{
label: 'benchmark.form.profile.longContext',
value: 'long_context_stress',
value: ProfileValueMap.LongContextStress,
locale: true
},
{
label: 'benchmark.form.profile.heavy',
value: 'generation_heavy',
value: ProfileValueMap.GenerationHeavy,
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;
worker_id: number;
gpu_summary: string;
seed: number;
gpu_vendor_summary: string;
}
@@ -101,4 +102,16 @@ export interface DatasetListItem {
source: string;
prompt_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 React from 'react';
import React, { useEffect } from 'react';
import { PageContainerInner } from '../_components/page-box';
import PageBreadcrumb from '../_components/page-breadcrumb';
import DetailContent from './components/detail-content';
@@ -19,6 +19,11 @@ const Details: React.FC = () => {
title: name
}
];
useEffect(() => {
document.title = `${intl.formatMessage({ id: 'benchmark.title' })} - ${name}`;
}, [name, intl]);
return (
<PageContainerInner
header={{
+20 -3
View File
@@ -5,6 +5,10 @@ import { PageAction } from '@/config';
import useAppUtils from '@/hooks/use-app-utils';
import { ClusterStatusValueMap } from '@/pages/cluster-management/config';
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 { useQueryModelList } from '@/pages/llmodels/services/use-query-model-list';
import { useIntl } from '@umijs/max';
@@ -28,9 +32,9 @@ const BasicForm: React.FC = () => {
} = useQueryClusterList();
const {
loading: modelLoading,
fetchModelList,
fetchData: fetchModelList,
cancelRequest: cancelModelRequest,
modelList
dataList: modelList
} = useQueryModelList();
const {
loading: instanceLoading,
@@ -65,6 +69,15 @@ const BasicForm: React.FC = () => {
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(() => {
const initClusterId = (list: any[]) => {
// Find default cluster
@@ -163,7 +176,11 @@ const BasicForm: React.FC = () => {
>
<SealSelect
loading={instanceLoading}
options={instanceList}
options={instanceList.map((item) => ({
...item,
disabled: item.state !== InstanceStatusMap.Running
}))}
optionRender={optionRender}
onOpenChange={onInstanceOpenChange}
label={intl.formatMessage({ id: 'benchmark.table.instance' })}
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 SealSelect from '@/components/seal-form/seal-select';
import useAppUtils from '@/hooks/use-app-utils';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import _ from 'lodash';
import React, { useEffect } from 'react';
import { profileOptions } from '../config';
import { ProfileValueMap } from '../config';
import { useFormContext } from '../config/form-context';
import { FormData } from '../config/types';
import useQueryDataset from '../services/use-query-dataset';
import useQueryProfiles from '../services/use-query-profiles';
import RandomSettingsForm from './random-settings';
const profileFields = [
'dataset_prompt_tokens',
'dataset_output_tokens',
'request_rate',
'total_requests'
];
const DatasetForm: React.FC = () => {
const intl = useIntl();
const form = Form.useFormInstance();
const { getRuleMessage } = useAppUtils();
const { action, open } = useFormContext();
const {
dataList: datasetList,
datasetList,
loading: datasetLoading,
fetchData,
fetchDatasetData,
cancelRequest: cancelDatasetRequest
} = useQueryDataset();
const {
profilesOptions,
fetchProfilesData,
cancelRequest: cancelProfilesRequest
} = useQueryProfiles();
const profileConfigCache = React.useRef<{ [key: string]: 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) => {
if (!datasetList.length && open) {
await fetchData({ page: -1 });
const setCustomProfileValues = () => {
form.setFieldsValue({
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(() => {
if (!open) {
cancelDatasetRequest();
cancelProfilesRequest();
}
if (open) {
fetchProfilesData();
fetchDatasetData();
}
}, [open]);
@@ -51,7 +140,8 @@ const DatasetForm: React.FC = () => {
]}
>
<SealSelect
options={profileOptions}
onChange={handleProfileChange}
options={profilesOptions}
label={intl.formatMessage({ id: 'benchmark.form.profile' })}
required
></SealSelect>
@@ -65,51 +155,55 @@ const DatasetForm: React.FC = () => {
}
]}
>
<SealSelect
<AutoComplete
options={datasetList.map((item) => ({
label: item.name,
value: item.name
...item,
label: item.label,
value: item.label
}))}
loading={datasetLoading}
onChange={handleOnDataSetChange}
onOpenChange={handleOnDatasetOpenChange}
label={intl.formatMessage({ id: 'benchmark.table.dataset' })}
required
></SealSelect>
></AutoComplete>
</Form.Item>
<Form.Item<FormData> hidden name="dataset_id">
<SealInput.Input></SealInput.Input>
</Form.Item>
<RandomSettingsForm></RandomSettingsForm>
<RandomSettingsForm
onValueChange={handleOnProfileConfigChange}
></RandomSettingsForm>
<Form.Item<FormData>
name="request_rate"
rules={[
{
required: true,
message: getRuleMessage('select', 'benchmark.table.requestRate')
message: getRuleMessage('input', 'benchmark.table.requestRate')
}
]}
>
<SealSelect
options={[]}
<SealInputNumber
min={0}
onChange={handleOnProfileConfigChange}
label={intl.formatMessage({ id: 'benchmark.table.requestRate' })}
required
></SealSelect>
></SealInputNumber>
</Form.Item>
<Form.Item<FormData>
name="total_requests"
rules={[
{
required: true,
message: getRuleMessage('select', 'benchmark.form.totalRequests')
message: getRuleMessage('input', 'benchmark.form.totalRequests')
}
]}
>
<SealSelect
options={[]}
<SealInputNumber
min={0}
onChange={handleOnProfileConfigChange}
label={intl.formatMessage({ id: 'benchmark.form.totalRequests' })}
required
></SealSelect>
></SealInputNumber>
</Form.Item>
</>
);
+11 -1
View File
@@ -99,7 +99,17 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
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 />
<CollapsePanel
activeKey={activeKey}
+18 -33
View File
@@ -1,12 +1,13 @@
import SealInputNumber from '@/components/seal-form/input-number';
import SealSelect from '@/components/seal-form/seal-select';
import useAppUtils from '@/hooks/use-app-utils';
import { useIntl } from '@umijs/max';
import { Form } from 'antd';
import React from 'react';
import { FormData } from '../config/types';
const DatasetForm: React.FC = () => {
const DatasetForm: React.FC<{
onValueChange?: (value: any) => void;
}> = ({ onValueChange }) => {
const intl = useIntl();
const form = Form.useFormInstance();
const { getRuleMessage } = useAppUtils();
@@ -18,27 +19,16 @@ const DatasetForm: React.FC = () => {
rules={[
{
required: true,
message: getRuleMessage(
'select',
'benchmark.table.inputTokenLength'
)
message: getRuleMessage('input', 'benchmark.table.inputTokenLength')
}
]}
>
<SealSelect
options={[
{
label: '100',
value: 100
},
{
label: '128',
value: 128
}
]}
<SealInputNumber
min={0}
label={intl.formatMessage({ id: 'benchmark.table.inputTokenLength' })}
required
></SealSelect>
onChange={onValueChange}
></SealInputNumber>
</Form.Item>
<Form.Item<FormData>
name="dataset_output_tokens"
@@ -46,33 +36,28 @@ const DatasetForm: React.FC = () => {
{
required: true,
message: getRuleMessage(
'select',
'input',
'benchmark.table.outputTokenLength'
)
}
]}
>
<SealSelect
options={[
{
label: '4',
value: 4
},
{
label: '8',
value: 8
}
]}
<SealInputNumber
min={0}
label={intl.formatMessage({
id: 'benchmark.table.outputTokenLength'
})}
required
></SealSelect>
onChange={onValueChange}
></SealInputNumber>
</Form.Item>
<Form.Item<FormData> name="seed">
<Form.Item<FormData>
name="seed"
getValueProps={(value) => ({ value: value || null })}
>
<SealInputNumber
min={0}
label={intl.formatMessage({ id: 'playground.image.params.seed' })}
required
></SealInputNumber>
</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 { TABLE_SORT_DIRECTIONS } from '@/config/settings';
import useTableFetch from '@/hooks/use-table-fetch';
import { useQueryModelList } from '@/pages/llmodels/services/use-query-model-list';
import { useIntl, useNavigate } from '@umijs/max';
import { useMemoizedFn } from 'ahooks';
import { ConfigProvider, Table, message } from 'antd';
import _ from 'lodash';
import { useEffect, useState } from 'react';
import NoResult from '../_components/no-result';
import PageBox from '../_components/page-box';
import useQueryGPUs from '../resources/services/use-query-gpus';
import {
createBenchmark,
deleteBenchmark,
@@ -24,6 +27,7 @@ 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 useViewDetail from './hooks/use-view-detail';
const Benchmark: React.FC = () => {
@@ -38,6 +42,7 @@ const Benchmark: React.FC = () => {
fetchData,
handlePageChange,
handleTableChange,
handleQueryChange,
handleSearch,
handleNameChange
} = useTableFetch<ListItem>({
@@ -54,7 +59,26 @@ const Benchmark: React.FC = () => {
closeViewDetailModal,
openViewDetailModalStatus
} = useViewDetail();
const { dataList: modelList, fetchData: fetchModelList } = useQueryModelList({
getValue: (item: any) => item.name
});
const { fetchData: fetchGpuList } = useQueryGPUs();
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 = () => {
openBenchmarkModal(PageAction.CREATE, 'Add Benchmark');
@@ -137,6 +161,15 @@ 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);
};
return (
<>
<PageBox>
@@ -150,7 +183,10 @@ const Benchmark: React.FC = () => {
widths={{ input: 300 }}
left={
<LeftActions
modelList={modelList}
gpuVendorList={gpuVendorList}
handleSearch={handleSearch}
handleQueryChange={handleQueryChange}
handleInputChange={handleNameChange}
></LeftActions>
}
@@ -159,6 +195,7 @@ const Benchmark: React.FC = () => {
settingButton={SettingsButton}
handleDeleteByBatch={handleDeleteBatch}
handleClickPrimary={handleAddBenchmark}
handleExport={handleExportData}
buttonText={intl.formatMessage({
id: 'benchmark.button.add'
})}
@@ -176,6 +213,7 @@ const Benchmark: React.FC = () => {
sortDirections={TABLE_SORT_DIRECTIONS}
showSorterTooltip={false}
rowKey="id"
scroll={{ x: 1200 }}
onChange={handleTableChange}
pagination={{
showSizeChanger: true,
@@ -1,4 +1,5 @@
import { useQueryDataList } from '@/hooks/use-query-data-list';
import { useState } from 'react';
import { queryDatasetList } from '../apis';
import { DatasetListItem } from '../config/types';
@@ -11,10 +12,34 @@ const useQueryDataset = () => {
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 {
dataList,
datasetList,
loading,
fetchData,
fetchDatasetData,
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 { useRequest } from 'ahooks';
import { message } from 'antd';
import { CancelTokenSource } from 'axios';
import { useEffect, useRef, useState } from 'react';
import { useQueryDataList } from '@/hooks/use-query-data-list';
import { queryModelsList } from '../apis';
import { ListItem } from '../config/types';
/**
*
* @returns loading, fetch, dataList
*/
export const useQueryModelList = () => {
const axiosTokenRef = useRef<CancelTokenSource | null>(null);
const [dataList, setDataList] = useState<
Array<Partial<ListItem> & { label: string; value: number }>
>([]);
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();
};
}, []);
export const useQueryModelList = (optons?: {
getLabel?: (item: ListItem) => string;
getValue?: (item: ListItem) => any;
}) => {
const { dataList, loading, fetchData, cancelRequest } = useQueryDataList<
ListItem,
Global.SearchParams
>({
key: 'modelList',
fetchList: queryModelsList
});
return {
dataList,
loading,
modelList: dataList,
cancelRequest,
fetchModelList: fetchData
fetchData,
cancelRequest
};
};
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;