fix: default benchmark form values

This commit is contained in:
jialin
2026-02-02 14:10:46 +08:00
parent fc599a0637
commit f651ab7fd9
15 changed files with 188 additions and 53 deletions
+15
View File
@@ -0,0 +1,15 @@
import { atom } from 'jotai';
export const benchmarkTargetInstanceAtom = atom<{
cluster_id: number | null;
model_name: string;
model_id: number | null;
model_instance_name: string;
model_instance: string[];
}>({
cluster_id: null,
model_name: '',
model_id: null,
model_instance_name: '',
model_instance: []
});
+2 -1
View File
@@ -275,5 +275,6 @@ export default {
'models.form.readyWorkers': 'workers ready', 'models.form.readyWorkers': 'workers ready',
'models.form.maxContextLength': 'Maximum Context Length', 'models.form.maxContextLength': 'Maximum Context Length',
'models.form.backend.helperText': 'models.form.backend.helperText':
'Not enabled yet. Will be enabled after deployment. ' 'Not enabled yet. Will be enabled after deployment. ',
'models.table.instance.benchmark': 'Run Benchmark'
}; };
+3 -1
View File
@@ -275,7 +275,8 @@ export default {
'models.form.readyWorkers': 'workers ready', 'models.form.readyWorkers': 'workers ready',
'models.form.maxContextLength': 'Maximum Context Length', 'models.form.maxContextLength': 'Maximum Context Length',
'models.form.backend.helperText': 'models.form.backend.helperText':
'Not enabled yet. Will be enabled after deployment. ' 'Not enabled yet. Will be enabled after deployment. ',
'models.table.instance.benchmark': 'Run Benchmark'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -373,4 +374,5 @@ export default {
// 75. 'models.form.readyWorkers': 'workers ready', // 75. 'models.form.readyWorkers': 'workers ready',
// 76. 'models.form.maxContextLength': 'Maximum Context Length', // 76. 'models.form.maxContextLength': 'Maximum Context Length',
// 77. 'models.form.backend.helperText': 'Not enabled yet. Will be enabled after deployment. ', // 77. 'models.form.backend.helperText': 'Not enabled yet. Will be enabled after deployment. ',
// 78. 'models.table.instance.benchmark': 'Run Benchmark'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+3 -1
View File
@@ -279,10 +279,12 @@ export default {
'models.form.readyWorkers': 'воркеров готово', 'models.form.readyWorkers': 'воркеров готово',
'models.form.maxContextLength': 'Maximum Context Length', 'models.form.maxContextLength': 'Maximum Context Length',
'models.form.backend.helperText': 'models.form.backend.helperText':
'Not enabled yet. Will be enabled after deployment. ' 'Not enabled yet. Will be enabled after deployment. ',
'models.table.instance.benchmark': 'Run Benchmark'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'models.form.maxContextLength': 'Maximum Context Length', // 1. 'models.form.maxContextLength': 'Maximum Context Length',
// 2. 'models.form.backend.helperText': 'Not enabled yet. Will be enabled after deployment. ', // 2. 'models.form.backend.helperText': 'Not enabled yet. Will be enabled after deployment. ',
// 3. 'models.table.instance.benchmark': 'Run Benchmark'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+3 -2
View File
@@ -205,7 +205,7 @@ export default {
'models.form.gpusAllocationType.custom.tips': 'models.form.gpusAllocationType.custom.tips':
'您可以指定每个副本的 GPU 数量。', '您可以指定每个副本的 GPU 数量。',
'models.mymodels.status.inactive': '已停止', 'models.mymodels.status.inactive': '已停止',
'models.mymodels.status.degrade': '异常', 'models.mymodels.status.degrade': '未就绪',
'models.mymodels.status.active': '可用', 'models.mymodels.status.active': '可用',
'models.form.kvCache.tips': 'models.form.kvCache.tips':
'扩展 KV 缓存和推测解码仅在内置后端(vLLM / SGLang)可用,请切换后端以启用。', '扩展 KV 缓存和推测解码仅在内置后端(vLLM / SGLang)可用,请切换后端以启用。',
@@ -258,5 +258,6 @@ export default {
'你指定的模型文件路径在 GPUStack 服务器上不存在。建议在 GPUStack 服务器和 GPUStack 节点上使用相同的模型文件路径,这有助于 GPUStack 做出更优的调度与决策。', '你指定的模型文件路径在 GPUStack 服务器上不存在。建议在 GPUStack 服务器和 GPUStack 节点上使用相同的模型文件路径,这有助于 GPUStack 做出更优的调度与决策。',
'models.form.readyWorkers': '节点就绪', 'models.form.readyWorkers': '节点就绪',
'models.form.maxContextLength': '最大上下文长度', 'models.form.maxContextLength': '最大上下文长度',
'models.form.backend.helperText': '该社区后端暂未启用,部署后将自动启用' 'models.form.backend.helperText': '该社区后端暂未启用,部署后将自动启用',
'models.table.instance.benchmark': '运行基准测试'
}; };
+7 -2
View File
@@ -3,6 +3,7 @@ import SealSelect from '@/components/seal-form/seal-select';
import { PageAction } from '@/config'; 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 { useBenchmarkTargetInstance } from '@/pages/llmodels/hooks/use-run-benchmark';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Form } from 'antd'; import { Form } from 'antd';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
@@ -15,6 +16,7 @@ const BasicForm: React.FC = () => {
const form = Form.useFormInstance(); const form = Form.useFormInstance();
const { getRuleMessage } = useAppUtils(); const { getRuleMessage } = useAppUtils();
const { action, open, clusterList } = useFormContext(); const { action, open, clusterList } = useFormContext();
const { benchmarkTargetInstance } = useBenchmarkTargetInstance();
useEffect(() => { useEffect(() => {
const initClusterId = (list: any[]) => { const initClusterId = (list: any[]) => {
@@ -35,9 +37,12 @@ const BasicForm: React.FC = () => {
clusterList?.length > 0 && clusterList?.length > 0 &&
action === PageAction.CREATE action === PageAction.CREATE
) { ) {
form.setFieldValue('cluster_id', initClusterId(clusterList)); form.setFieldValue(
'cluster_id',
benchmarkTargetInstance.cluster_id || initClusterId(clusterList)
);
} }
}, [form, action, clusterList]); }, [form, action, clusterList, benchmarkTargetInstance]);
return ( return (
<> <>
+40 -3
View File
@@ -43,14 +43,46 @@ const DatasetForm: React.FC = () => {
} }
}; };
// Initialize profile when open form
const initProfile = (
value: string,
option: any,
datasetList: Global.BaseOption<number | string>[]
) => {
if (value !== ProfileValueMap.Custom) {
const dataset_id = datasetList.find(
(item) => item.label === option.config?.dataset_name
)?.value;
form.setFieldsValue({
profile: value,
dataset_id: dataset_id,
..._.omit(option?.config, ['description', 'dataset_source'])
});
}
};
useEffect(() => { useEffect(() => {
if (!open) { if (!open) {
cancelDatasetRequest(); cancelDatasetRequest();
cancelProfilesRequest(); cancelProfilesRequest();
} }
if (open) { if (open) {
fetchProfilesData(); const init = async () => {
fetchDatasetData(); const profiles = await fetchProfilesData();
const datasets = await fetchDatasetData();
// set default profile
if (profiles?.length > 0) {
const throughputProfile = profiles.find(
(item) => item.value === ProfileValueMap.ThroughputMedium
);
if (throughputProfile) {
initProfile(throughputProfile.value, throughputProfile, datasets);
}
}
};
init();
} }
}, [open]); }, [open]);
@@ -73,7 +105,12 @@ const DatasetForm: React.FC = () => {
required required
> >
{profilesOptions?.map((item: any) => ( {profilesOptions?.map((item: any) => (
<Select.Option key={item.value} value={item.value}> <Select.Option
key={item.value}
value={item.value}
label={item.label}
config={item.config}
>
<AutoTooltip <AutoTooltip
ghost ghost
showTitle={!!item.tips} showTitle={!!item.tips}
-9
View File
@@ -37,12 +37,9 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
const { action, currentData, onFinish, open, clusterList } = props; const { action, currentData, onFinish, open, clusterList } = props;
const intl = useIntl(); const intl = useIntl();
const [form] = Form.useForm(); const [form] = Form.useForm();
const profile = Form.useWatch('profile', form);
const { getScrollElementScrollableHeight } = useWrapperContext(); const { getScrollElementScrollableHeight } = useWrapperContext();
const [activeKey, setActiveKey] = useState<string[]>([TABKeysMap.PROFILE]); const [activeKey, setActiveKey] = useState<string[]>([TABKeysMap.PROFILE]);
const scrollTabsRef = useRef<any>(null); const scrollTabsRef = useRef<any>(null);
const showAdvanced = profile !== 'Custom' && Boolean(profile);
const segmentOptions = [ const segmentOptions = [
{ {
@@ -88,12 +85,6 @@ const ProviderForm: React.FC<ProviderFormProps> = forwardRef((props, ref) => {
} }
}, [form, currentData, action]); }, [form, currentData, action]);
useEffect(() => {
if (!showAdvanced && activeKey?.includes(TABKeysMap.ADVANCED)) {
setActiveKey([TABKeysMap.PROFILE]);
}
}, [showAdvanced, activeKey]);
return ( return (
<ScrollSpyTabs <ScrollSpyTabs
ref={scrollTabsRef} ref={scrollTabsRef}
+49 -29
View File
@@ -5,6 +5,7 @@ import {
InstanceStatusMap, InstanceStatusMap,
InstanceStatusMapValue InstanceStatusMapValue
} from '@/pages/llmodels/config'; } from '@/pages/llmodels/config';
import { useBenchmarkTargetInstance } from '@/pages/llmodels/hooks/use-run-benchmark';
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';
@@ -43,12 +44,15 @@ const ModelInstanceForm: React.FC = () => {
fetchInstanceList, fetchInstanceList,
cancelRequest: cancelInstanceRequest cancelRequest: cancelInstanceRequest
} = useQueryModelInstancesList(); } = useQueryModelInstancesList();
const { benchmarkTargetInstance, clearBenchmarkTargetInstance } =
useBenchmarkTargetInstance();
const handleOnChange = async (value: any, selectedOptions: any) => { const handleOnChange = async (value: any, selectedOptions: any) => {
form.setFieldsValue({ form.setFieldsValue({
model_name: value[0], model_name: value[0],
model_id: selectedOptions[0]?.id, model_id: selectedOptions[0]?.id,
model_instance_name: value[1] model_instance_name: value[1],
model_instance: value
}); });
}; };
@@ -87,43 +91,60 @@ const ModelInstanceForm: React.FC = () => {
} }
}; };
const handleOnOpenChange = async (open: boolean) => { const initModelInstance = async () => {
// fetch model list when dropdown is opened // fetch model list when dropdown is opened
if (open && modelList.length === 0) { const list = await fetchModelList({ page: -1 });
const list = await fetchModelList({ page: -1 }); const modelOptions = list
const modelOptions = list .filter((model: any) => model.replicas > 0)
.filter((model: any) => model.replicas > 0) .map((model: any) => ({
.map((model: any) => ({ label: model.name,
label: model.name, value: model.name,
value: model.name, id: model.id,
id: model.id, isLeaf: false,
isLeaf: false, children: []
children: [] }));
}));
if (modelOptions.length === 0) { if (modelOptions.length === 0) {
return; return;
}
// preload instances for the first model
const instanceList = await fetchInstanceList({ id: modelOptions[0]?.id });
const instanceOptions = instanceList.map((instance: any) =>
renderInstance(instance)
);
if (modelOptions[0]) {
modelOptions[0].children = [...instanceOptions] as never[];
}
setModelList(modelOptions);
} }
// preload instances for the first model
const instanceList = await fetchInstanceList({ id: modelOptions[0]?.id });
const instanceOptions = instanceList.map((instance: any) =>
renderInstance(instance)
);
if (modelOptions[0]) {
modelOptions[0].children = [...instanceOptions] as never[];
}
// init form value for model instance
if (
benchmarkTargetInstance.model_name &&
benchmarkTargetInstance.model_instance_name
) {
form.setFieldsValue({
...benchmarkTargetInstance
});
} else {
handleOnChange(
[modelOptions[0].value, instanceOptions[0]?.value],
[modelOptions[0], instanceOptions[0]]
);
}
setModelList(modelOptions);
}; };
useEffect(() => { useEffect(() => {
if (open) {
initModelInstance();
}
if (!open) { if (!open) {
cancelModelRequest(); cancelModelRequest();
cancelInstanceRequest(); cancelInstanceRequest();
clearBenchmarkTargetInstance();
} }
}, [open]); }, [open, benchmarkTargetInstance]);
return ( return (
<Form.Item<FormData> <Form.Item<FormData>
@@ -154,7 +175,6 @@ const ModelInstanceForm: React.FC = () => {
getPopupContainer={(triggerNode) => triggerNode.parentNode} getPopupContainer={(triggerNode) => triggerNode.parentNode}
optionNode={InstanceNode} optionNode={InstanceNode}
loadData={loadInstances} loadData={loadInstances}
onOpenChange={handleOnOpenChange}
onChange={handleOnChange} onChange={handleOnChange}
></SealCascader> ></SealCascader>
</Form.Item> </Form.Item>
+10 -1
View File
@@ -4,6 +4,7 @@ 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 { useBenchmarkTargetInstance } from '@/pages/llmodels/hooks/use-run-benchmark';
import { useQueryModelList } from '@/pages/llmodels/services/use-query-model-list'; 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';
@@ -76,11 +77,19 @@ const Benchmark: React.FC = () => {
contentHeight: 320, contentHeight: 320,
clusterList clusterList
}); });
const { benchmarkTargetInstance } = useBenchmarkTargetInstance();
useEffect(() => { useEffect(() => {
fetchModelList({ page: -1 }); fetchModelList({ page: -1 });
fetchClusterList({ page: -1 });
fetchDatasetData(); fetchDatasetData();
fetchClusterList({ page: -1 }).then(() => {
if (benchmarkTargetInstance.model_name) {
openBenchmarkModal(
PageAction.CREATE,
intl.formatMessage({ id: 'benchmark.button.add' })
);
}
});
return () => { return () => {
cancelClusterRequest(); cancelClusterRequest();
}; };
@@ -21,6 +21,7 @@ const useQueryDataset = () => {
// TODO: may be fetch data from server in the future. // TODO: may be fetch data from server in the future.
setDatasetList([...datasetOptions]); setDatasetList([...datasetOptions]);
return datasetOptions;
}; };
return { return {
@@ -41,7 +41,7 @@ export default function useQueryProfiles() {
}; };
}) || []; }) || [];
setProfilesOptions([ const options = [
...list, ...list,
{ {
label: intl.formatMessage({ id: 'backend.custom' }), label: intl.formatMessage({ id: 'backend.custom' }),
@@ -55,7 +55,9 @@ export default function useQueryProfiles() {
total_requests: null total_requests: null
} }
} }
]); ];
setProfilesOptions(options);
return options;
}; };
return { return {
@@ -9,6 +9,7 @@ import StatusTag from '@/components/status-tag';
import ThemeTag from '@/components/tags-wrapper/theme-tag'; import ThemeTag from '@/components/tags-wrapper/theme-tag';
import { HandlerOptions } from '@/hooks/use-chunk-fetch'; import { HandlerOptions } from '@/hooks/use-chunk-fetch';
import useDownloadStream from '@/hooks/use-download-stream'; import useDownloadStream from '@/hooks/use-download-stream';
import { useBenchmarkTargetInstance } from '@/pages/llmodels/hooks/use-run-benchmark';
import { ListItem as WorkerListItem } from '@/pages/resources/config/types'; import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
import { convertFileSize } from '@/utils'; import { convertFileSize } from '@/utils';
import { import {
@@ -333,6 +334,12 @@ const childActionList = [
], ],
icon: <DownloadOutlined /> icon: <DownloadOutlined />
}, },
{
label: 'models.table.instance.benchmark',
key: 'benchmark',
status: [InstanceStatusMap.Running],
icon: <IconFont type="icon-speed" />
},
{ {
label: 'common.button.delrecreate', label: 'common.button.delrecreate',
key: 'delete', key: 'delete',
@@ -416,6 +423,7 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
defaultOpenId, defaultOpenId,
handleChildSelect handleChildSelect
}) => { }) => {
const { runBenchmarkOnInstance } = useBenchmarkTargetInstance();
const [api, contextHolder] = notification.useNotification({ const [api, contextHolder] = notification.useNotification({
stack: { threshold: 1 } stack: { threshold: 1 }
}); });
@@ -423,7 +431,7 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
const intl = useIntl(); const intl = useIntl();
const actionItems = useMemo(() => { const actionItems = useMemo(() => {
return _.filter(childActionList, (action: any) => { return _.filter(childActionList, (action: any) => {
if (action.key === 'viewlog' || action.key === 'download') { if (action.status && action.status.length > 0) {
return action.status.includes(instanceData.state); return action.status.includes(instanceData.state);
} }
return true; return true;
@@ -662,7 +670,9 @@ const InstanceItem: React.FC<InstanceItemProps> = ({
]); ]);
const handleOnSelect = (val: string) => { const handleOnSelect = (val: string) => {
if (val === 'download') { if (val === 'benchmark') {
runBenchmarkOnInstance(instanceData);
} else if (val === 'download') {
downloadStream({ downloadStream({
url: `${MODEL_INSTANCE_API}/${instanceData.id}/logs`, url: `${MODEL_INSTANCE_API}/${instanceData.id}/logs`,
filename: createFileName(instanceData.name), filename: createFileName(instanceData.name),
+1
View File
@@ -114,6 +114,7 @@ export interface DistributedServers {
} }
export interface ModelInstanceListItem { export interface ModelInstanceListItem {
backend?: string; backend?: string;
cluster_id: number;
backend_version?: string; backend_version?: string;
source: string; source: string;
huggingface_repo_id: string; huggingface_repo_id: string;
@@ -0,0 +1,38 @@
import { benchmarkTargetInstanceAtom } from '@/atoms/benchmark';
import { useNavigate } from '@umijs/max';
import { useAtom } from 'jotai';
import { ModelInstanceListItem } from '../config/types';
export const useBenchmarkTargetInstance = () => {
const [benchmarkTargetInstance, setBenchmarkTargetInstance] = useAtom(
benchmarkTargetInstanceAtom
);
const navigate = useNavigate();
const runBenchmarkOnInstance = (instance: ModelInstanceListItem) => {
setBenchmarkTargetInstance({
cluster_id: instance.cluster_id,
model_name: instance.model_name,
model_id: instance.model_id,
model_instance_name: instance.name,
model_instance: [instance.model_name, instance.name]
});
navigate('/models/benchmark');
};
const clearBenchmarkTargetInstance = () => {
setBenchmarkTargetInstance({
cluster_id: null,
model_name: '',
model_id: null,
model_instance_name: '',
model_instance: []
});
};
return {
benchmarkTargetInstance,
clearBenchmarkTargetInstance,
runBenchmarkOnInstance
};
};