feat: benchmark form

This commit is contained in:
jialin
2026-01-30 18:48:55 +08:00
parent 37b814b70a
commit fb0e45ebed
10 changed files with 356 additions and 37 deletions
+11 -1
View File
@@ -3,6 +3,7 @@ export default {
'benchmark.button.add': 'Add Benchmark', 'benchmark.button.add': 'Add Benchmark',
'benchmark.button.compare': 'Compare', 'benchmark.button.compare': 'Compare',
'benchmark.table.model': 'Model', 'benchmark.table.model': 'Model',
'benchmark.table.instance': 'Endpoint',
'benchmark.table.dataset': 'Dataset', 'benchmark.table.dataset': 'Dataset',
'benchmark.table.requestRate': 'Request Rate', 'benchmark.table.requestRate': 'Request Rate',
'benchmark.table.gpu': 'GPU ', 'benchmark.table.gpu': 'GPU ',
@@ -12,5 +13,14 @@ export default {
'benchmark.table.rps': 'Requests/s', 'benchmark.table.rps': 'Requests/s',
'benchmark.table.tps': 'Tokens/s', 'benchmark.table.tps': 'Tokens/s',
'benchmark.table.inputLen': 'Input Length', 'benchmark.table.inputLen': 'Input Length',
'benchmark.table.outputLen': 'Output Length' 'benchmark.table.outputLen': 'Output Length',
'benchmark.form.profile': 'Profile',
'benchmark.form.totalRequests': 'Total Requests',
'benchmark.form.profile.latency': 'Latency',
'benchmark.form.profile.throughput': 'Throughput',
'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.outputTokenLength': 'Output Token Length'
}; };
+11 -1
View File
@@ -3,6 +3,7 @@ export default {
'benchmark.button.add': 'Add Benchmark', 'benchmark.button.add': 'Add Benchmark',
'benchmark.button.compare': 'Compare', 'benchmark.button.compare': 'Compare',
'benchmark.table.model': 'Model', 'benchmark.table.model': 'Model',
'benchmark.table.instance': 'Endpoint',
'benchmark.table.dataset': 'Dataset', 'benchmark.table.dataset': 'Dataset',
'benchmark.table.requestRate': 'Request Rate', 'benchmark.table.requestRate': 'Request Rate',
'benchmark.table.gpu': 'GPU ', 'benchmark.table.gpu': 'GPU ',
@@ -12,5 +13,14 @@ export default {
'benchmark.table.rps': 'Requests/s', 'benchmark.table.rps': 'Requests/s',
'benchmark.table.tps': 'Tokens/s', 'benchmark.table.tps': 'Tokens/s',
'benchmark.table.inputLen': 'Input Length', 'benchmark.table.inputLen': 'Input Length',
'benchmark.table.outputLen': 'Output Length' 'benchmark.table.outputLen': 'Output Length',
'benchmark.form.profile': 'Profile',
'benchmark.form.totalRequests': 'Total Requests',
'benchmark.form.profile.latency': 'Latency',
'benchmark.form.profile.throughput': 'Throughput',
'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.outputTokenLength': 'Output Token Length'
}; };
+11 -1
View File
@@ -3,6 +3,7 @@ export default {
'benchmark.button.add': 'Add Benchmark', 'benchmark.button.add': 'Add Benchmark',
'benchmark.button.compare': 'Compare', 'benchmark.button.compare': 'Compare',
'benchmark.table.model': 'Model', 'benchmark.table.model': 'Model',
'benchmark.table.instance': 'Endpoint',
'benchmark.table.dataset': 'Dataset', 'benchmark.table.dataset': 'Dataset',
'benchmark.table.requestRate': 'Request Rate', 'benchmark.table.requestRate': 'Request Rate',
'benchmark.table.gpu': 'GPU ', 'benchmark.table.gpu': 'GPU ',
@@ -12,5 +13,14 @@ export default {
'benchmark.table.rps': 'Requests/s', 'benchmark.table.rps': 'Requests/s',
'benchmark.table.tps': 'Tokens/s', 'benchmark.table.tps': 'Tokens/s',
'benchmark.table.inputLen': 'Input Length', 'benchmark.table.inputLen': 'Input Length',
'benchmark.table.outputLen': 'Output Length' 'benchmark.table.outputLen': 'Output Length',
'benchmark.form.profile': 'Profile',
'benchmark.form.totalRequests': 'Total Requests',
'benchmark.form.profile.latency': 'Latency',
'benchmark.form.profile.throughput': 'Throughput',
'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.outputTokenLength': 'Output Token Length'
}; };
+11 -1
View File
@@ -3,6 +3,7 @@ export default {
'benchmark.button.add': '添加基准测试', 'benchmark.button.add': '添加基准测试',
'benchmark.button.compare': '对比', 'benchmark.button.compare': '对比',
'benchmark.table.model': '模型', 'benchmark.table.model': '模型',
'benchmark.table.instance': '目标实例',
'benchmark.table.dataset': '数据集', 'benchmark.table.dataset': '数据集',
'benchmark.table.requestRate': '请求率', 'benchmark.table.requestRate': '请求率',
'benchmark.table.gpu': 'GPU ', 'benchmark.table.gpu': 'GPU ',
@@ -12,5 +13,14 @@ export default {
'benchmark.table.rps': '请求数/秒', 'benchmark.table.rps': '请求数/秒',
'benchmark.table.tps': 'Tokens/秒', 'benchmark.table.tps': 'Tokens/秒',
'benchmark.table.inputLen': '输入长度', 'benchmark.table.inputLen': '输入长度',
'benchmark.table.outputLen': '输出长度' 'benchmark.table.outputLen': '输出长度',
'benchmark.form.profile': '模式',
'benchmark.form.totalRequests': '总请求数',
'benchmark.form.profile.latency': '延迟',
'benchmark.form.profile.throughput': '吞吐',
'benchmark.form.profile.longContext': '长上下文',
'benchmark.form.profile.heavy': '高生成量',
'benchmark.form.profile.custom': '自定义',
'benchmark.table.inputTokenLength': '输入 Token 长度',
'benchmark.table.outputTokenLength': '输出 Token 长度'
}; };
@@ -3,6 +3,9 @@ import { createContext, useContext } from 'react';
interface FormContextProps { interface FormContextProps {
action: PageActionType; action: PageActionType;
clusterList: Global.BaseOption<number>[];
modelList: Global.BaseOption<number>[];
modelInstanceList: Global.BaseOption<number>[];
} }
const FormContext = createContext<FormContextProps>({} as FormContextProps); const FormContext = createContext<FormContextProps>({} as FormContextProps);
+24
View File
@@ -27,3 +27,27 @@ export const BenchmarkStatus: Record<string, StatusType> = {
[BenchmarkStatusValueMap.Error]: StatusMaps.error, [BenchmarkStatusValueMap.Error]: StatusMaps.error,
[BenchmarkStatusValueMap.Unreachable]: StatusMaps.error [BenchmarkStatusValueMap.Unreachable]: StatusMaps.error
}; };
export const profileOptions = [
{
label: 'benchmark.form.profile.latency',
value: 'latency_short',
locale: true
},
{
label: 'benchmark.form.profile.throughput',
value: 'throughput_medium',
locale: true
},
{
label: 'benchmark.form.profile.longContext',
value: 'long_context_stress',
locale: true
},
{
label: 'benchmark.form.profile.heavy',
value: 'generation_heavy',
locale: true
},
{ label: 'benchmark.form.profile.custom', value: 'custom', locale: true }
];
+116 -18
View File
@@ -1,33 +1,131 @@
import LabelSelector from '@/components/label-selector';
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 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 from 'react'; import React from 'react';
import { useFormContext } from '../config/form-context';
import { FormData } from '../config/types'; import { FormData } from '../config/types';
const BasicForm: React.FC = () => { const BasicForm: React.FC = () => {
const intl = useIntl(); const intl = useIntl();
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const labels = Form.useWatch('labels', form);
const { getRuleMessage } = useAppUtils();
const { modelList, modelInstanceList, clusterList } = useFormContext();
const handleLabelsChange = (labels: object) => {
form.setFieldValue('labels', labels);
};
return ( return (
<Form.Item<FormData> <>
name="name" <Form.Item<FormData>
rules={[ data-field="name"
{ name="name"
required: true, rules={[
message: intl.formatMessage( {
{ id: 'common.form.rule.input' }, required: true,
{ message: getRuleMessage('input', 'common.table.name')
name: intl.formatMessage({ id: 'common.table.name' }) }
]}
>
<SealInput.Input
label={intl.formatMessage({ id: 'common.table.name' })}
required
></SealInput.Input>
</Form.Item>
<Form.Item<FormData>
name="cluster_id"
rules={[
{
required: true,
message: getRuleMessage('select', 'clusters.title')
}
]}
>
<SealSelect
options={clusterList}
label={intl.formatMessage({ id: 'clusters.title' })}
required
></SealSelect>
</Form.Item>
<Form.Item<FormData>
name="model_id"
rules={[
{
required: true,
message: getRuleMessage('select', 'benchmark.table.model')
}
]}
>
<SealSelect
options={modelList}
label={intl.formatMessage({ id: 'benchmark.table.model' })}
required
></SealSelect>
</Form.Item>
<Form.Item<FormData>
name="model_instance_name"
rules={[
{
required: true,
message: getRuleMessage('select', 'benchmark.table.instance')
}
]}
>
<SealSelect
options={modelInstanceList}
label={intl.formatMessage({ id: 'benchmark.table.instance' })}
required
></SealSelect>
</Form.Item>
<Form.Item<FormData>
name="labels"
rules={[
() => ({
validator(rule, value) {
if (_.keys(value).length > 0) {
if (_.some(_.keys(value), (k: string) => !value[k])) {
return Promise.reject(
intl.formatMessage(
{
id: 'common.validate.value'
},
{
name: intl.formatMessage({
id: 'resources.form.label'
})
}
)
);
}
}
return Promise.resolve();
} }
) })
} ]}
]} >
> <LabelSelector
<SealInput.Input label={intl.formatMessage({
label={intl.formatMessage({ id: 'common.table.name' })} id: 'resources.table.labels'
required })}
></SealInput.Input> labels={labels}
</Form.Item> btnText={intl.formatMessage({ id: 'common.button.addLabel' })}
onChange={handleLabelsChange}
></LabelSelector>
</Form.Item>
<Form.Item<FormData> name="description">
<SealInput.TextArea
scaleSize={true}
label={intl.formatMessage({
id: 'common.table.description'
})}
></SealInput.TextArea>
</Form.Item>
</>
); );
}; };
+83
View File
@@ -0,0 +1,83 @@
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 { profileOptions } from '../config';
import { FormData } from '../config/types';
import RandomSettingsForm from './random-settings';
const DatasetForm: React.FC = () => {
const intl = useIntl();
const form = Form.useFormInstance();
const { getRuleMessage } = useAppUtils();
return (
<>
<Form.Item<FormData>
data-field="profile"
name="profile"
rules={[
{
required: true,
message: getRuleMessage('select', 'benchmark.form.profile')
}
]}
>
<SealSelect
options={profileOptions}
label={intl.formatMessage({ id: 'benchmark.form.profile' })}
required
></SealSelect>
</Form.Item>
<Form.Item<FormData>
name="dataset_name"
rules={[
{
required: true,
message: getRuleMessage('select', 'benchmark.table.dataset')
}
]}
>
<SealSelect
options={[]}
label={intl.formatMessage({ id: 'benchmark.table.dataset' })}
required
></SealSelect>
</Form.Item>
<RandomSettingsForm></RandomSettingsForm>
<Form.Item<FormData>
name="request_rate"
rules={[
{
required: true,
message: getRuleMessage('select', 'benchmark.table.requestRate')
}
]}
>
<SealSelect
options={[]}
label={intl.formatMessage({ id: 'benchmark.table.requestRate' })}
required
></SealSelect>
</Form.Item>
<Form.Item<FormData>
name="total_requests"
rules={[
{
required: true,
message: getRuleMessage('select', 'benchmark.form.totalRequests')
}
]}
>
<SealSelect
options={[]}
label={intl.formatMessage({ id: 'benchmark.form.totalRequests' })}
required
></SealSelect>
</Form.Item>
</>
);
};
export default DatasetForm;
+22 -15
View File
@@ -1,6 +1,7 @@
import IconFont from '@/components/icon-font'; import IconFont from '@/components/icon-font';
import { PageAction } from '@/config'; import { PageAction } from '@/config';
import { PageActionType } from '@/config/types'; import { PageActionType } from '@/config/types';
import CollapsePanel from '@/pages/_components/collapse-panel';
import { useWrapperContext } from '@/pages/_components/column-wrapper/use-wrapper-context'; import { useWrapperContext } from '@/pages/_components/column-wrapper/use-wrapper-context';
import ScrollSpyTabs from '@/pages/_components/scroll-spy-tabs'; import ScrollSpyTabs from '@/pages/_components/scroll-spy-tabs';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
@@ -15,6 +16,7 @@ import {
import FormContext from '../config/form-context'; import FormContext from '../config/form-context';
import { FormData, BenchmarkListItem as ListItem } from '../config/types'; import { FormData, BenchmarkListItem as ListItem } from '../config/types';
import Basic from './basic'; import Basic from './basic';
import DatasetForm from './dataset';
interface ProviderFormProps { interface ProviderFormProps {
ref?: any; ref?: any;
@@ -25,9 +27,7 @@ interface ProviderFormProps {
const TABKeysMap = { const TABKeysMap = {
BASIC: 'basic', BASIC: 'basic',
SUPPORTEDMODELS: 'supportedModels', PROFILE: 'profile'
CUSTOMCONFIG: 'customConfig',
ADVANCED: 'advanced'
}; };
const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => { const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
@@ -35,27 +35,21 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
const intl = useIntl(); const intl = useIntl();
const [form] = Form.useForm(); const [form] = Form.useForm();
const { getScrollElementScrollableHeight } = useWrapperContext(); const { getScrollElementScrollableHeight } = useWrapperContext();
const [activeKey, setActiveKey] = useState<string[]>([TABKeysMap.BASIC]); const [activeKey, setActiveKey] = useState<string[]>([TABKeysMap.PROFILE]);
const scrollTabsRef = useRef<any>(null); const scrollTabsRef = useRef<any>(null);
const segmentOptions = [ const segmentOptions = [
{ {
value: TABKeysMap.BASIC, value: TABKeysMap.BASIC,
label: 'Basic', label: intl.formatMessage({ id: 'common.title.basicInfo' }),
icon: <IconFont type="icon-basic" />, icon: <IconFont type="icon-basic" />,
field: 'name' field: 'name'
}, },
{ {
value: TABKeysMap.SUPPORTEDMODELS, value: TABKeysMap.PROFILE,
label: 'Supported Models', label: intl.formatMessage({ id: 'common.title.config' }),
icon: <IconFont type="icon-speed" />,
field: 'supportedModels'
},
{
value: TABKeysMap.ADVANCED,
label: intl.formatMessage({ id: 'resources.form.advanced' }),
icon: <IconFont type="icon-settings" />, icon: <IconFont type="icon-settings" />,
field: 'advanceConfig' field: 'profile'
} }
]; ];
@@ -88,7 +82,7 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
<ScrollSpyTabs <ScrollSpyTabs
ref={scrollTabsRef} ref={scrollTabsRef}
defaultTarget="basic" defaultTarget="basic"
segmentOptions={[]} segmentOptions={segmentOptions}
activeKey={activeKey} activeKey={activeKey}
setActiveKey={handleActiveChange} setActiveKey={handleActiveChange}
segmentedTop={{ segmentedTop={{
@@ -100,6 +94,19 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
<FormContext.Provider value={{ action }}> <FormContext.Provider value={{ action }}>
<Form form={form} onFinish={onFinish} initialValues={{}}> <Form form={form} onFinish={onFinish} initialValues={{}}>
<Basic /> <Basic />
<CollapsePanel
activeKey={activeKey}
accordion={false}
onChange={handleOnCollapseChange}
items={[
{
key: TABKeysMap.PROFILE,
label: intl.formatMessage({ id: 'common.title.config' }),
forceRender: true,
children: <DatasetForm />
}
]}
></CollapsePanel>
</Form> </Form>
</FormContext.Provider> </FormContext.Provider>
</ScrollSpyTabs> </ScrollSpyTabs>
@@ -0,0 +1,64 @@
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 intl = useIntl();
const form = Form.useFormInstance();
const { getRuleMessage } = useAppUtils();
return (
<>
<Form.Item<FormData>
name="input_length"
rules={[
{
required: true,
message: getRuleMessage(
'select',
'benchmark.table.inputTokenLength'
)
}
]}
>
<SealSelect
options={[]}
label={intl.formatMessage({ id: 'benchmark.table.inputTokenLength' })}
required
></SealSelect>
</Form.Item>
<Form.Item<FormData>
name="output_length"
rules={[
{
required: true,
message: getRuleMessage(
'select',
'benchmark.table.outputTokenLength'
)
}
]}
>
<SealSelect
options={[]}
label={intl.formatMessage({
id: 'benchmark.table.outputTokenLength'
})}
required
></SealSelect>
</Form.Item>
<Form.Item<FormData> name="seed">
<SealInputNumber
label={intl.formatMessage({ id: 'playground.image.params.seed' })}
required
></SealInputNumber>
</Form.Item>
</>
);
};
export default DatasetForm;