From f651ab7fd95d7cba88b724688bb7a4d2eb837742 Mon Sep 17 00:00:00 2001 From: jialin Date: Mon, 2 Feb 2026 14:09:10 +0800 Subject: [PATCH] fix: default benchmark form values --- src/atoms/benchmark.ts | 15 ++++ src/locales/en-US/models.ts | 3 +- src/locales/ja-JP/models.ts | 4 +- src/locales/ru-RU/models.ts | 4 +- src/locales/zh-CN/models.ts | 5 +- src/pages/benchmark/forms/basic.tsx | 9 ++- src/pages/benchmark/forms/dataset.tsx | 43 +++++++++- src/pages/benchmark/forms/index.tsx | 9 --- src/pages/benchmark/forms/model-instance.tsx | 78 ++++++++++++------- src/pages/benchmark/index.tsx | 11 ++- .../benchmark/services/use-query-dataset.ts | 1 + .../benchmark/services/use-query-profiles.ts | 6 +- .../llmodels/components/instance-item.tsx | 14 +++- src/pages/llmodels/config/types.ts | 1 + src/pages/llmodels/hooks/use-run-benchmark.ts | 38 +++++++++ 15 files changed, 188 insertions(+), 53 deletions(-) create mode 100644 src/atoms/benchmark.ts create mode 100644 src/pages/llmodels/hooks/use-run-benchmark.ts diff --git a/src/atoms/benchmark.ts b/src/atoms/benchmark.ts new file mode 100644 index 00000000..fe9b75e1 --- /dev/null +++ b/src/atoms/benchmark.ts @@ -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: [] +}); diff --git a/src/locales/en-US/models.ts b/src/locales/en-US/models.ts index aa5e4372..b7002f7e 100644 --- a/src/locales/en-US/models.ts +++ b/src/locales/en-US/models.ts @@ -275,5 +275,6 @@ export default { 'models.form.readyWorkers': 'workers ready', 'models.form.maxContextLength': 'Maximum Context Length', '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' }; diff --git a/src/locales/ja-JP/models.ts b/src/locales/ja-JP/models.ts index a799282d..e1aab431 100644 --- a/src/locales/ja-JP/models.ts +++ b/src/locales/ja-JP/models.ts @@ -275,7 +275,8 @@ export default { 'models.form.readyWorkers': 'workers ready', 'models.form.maxContextLength': 'Maximum Context Length', '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) ========== @@ -373,4 +374,5 @@ export default { // 75. 'models.form.readyWorkers': 'workers ready', // 76. 'models.form.maxContextLength': 'Maximum Context Length', // 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 ========== diff --git a/src/locales/ru-RU/models.ts b/src/locales/ru-RU/models.ts index 39ddf2d1..458c9fb4 100644 --- a/src/locales/ru-RU/models.ts +++ b/src/locales/ru-RU/models.ts @@ -279,10 +279,12 @@ export default { 'models.form.readyWorkers': 'воркеров готово', 'models.form.maxContextLength': 'Maximum Context Length', '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) ========== // 1. 'models.form.maxContextLength': 'Maximum Context Length', // 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 ========== diff --git a/src/locales/zh-CN/models.ts b/src/locales/zh-CN/models.ts index ced774a5..1bd75379 100644 --- a/src/locales/zh-CN/models.ts +++ b/src/locales/zh-CN/models.ts @@ -205,7 +205,7 @@ export default { 'models.form.gpusAllocationType.custom.tips': '您可以指定每个副本的 GPU 数量。', 'models.mymodels.status.inactive': '已停止', - 'models.mymodels.status.degrade': '异常', + 'models.mymodels.status.degrade': '未就绪', 'models.mymodels.status.active': '可用', 'models.form.kvCache.tips': '扩展 KV 缓存和推测解码仅在内置后端(vLLM / SGLang)可用,请切换后端以启用。', @@ -258,5 +258,6 @@ export default { '你指定的模型文件路径在 GPUStack 服务器上不存在。建议在 GPUStack 服务器和 GPUStack 节点上使用相同的模型文件路径,这有助于 GPUStack 做出更优的调度与决策。', 'models.form.readyWorkers': '节点就绪', 'models.form.maxContextLength': '最大上下文长度', - 'models.form.backend.helperText': '该社区后端暂未启用,部署后将自动启用' + 'models.form.backend.helperText': '该社区后端暂未启用,部署后将自动启用', + 'models.table.instance.benchmark': '运行基准测试' }; diff --git a/src/pages/benchmark/forms/basic.tsx b/src/pages/benchmark/forms/basic.tsx index d97fca48..3c077512 100644 --- a/src/pages/benchmark/forms/basic.tsx +++ b/src/pages/benchmark/forms/basic.tsx @@ -3,6 +3,7 @@ import SealSelect from '@/components/seal-form/seal-select'; import { PageAction } from '@/config'; import useAppUtils from '@/hooks/use-app-utils'; import { ClusterStatusValueMap } from '@/pages/cluster-management/config'; +import { useBenchmarkTargetInstance } from '@/pages/llmodels/hooks/use-run-benchmark'; import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React, { useEffect } from 'react'; @@ -15,6 +16,7 @@ const BasicForm: React.FC = () => { const form = Form.useFormInstance(); const { getRuleMessage } = useAppUtils(); const { action, open, clusterList } = useFormContext(); + const { benchmarkTargetInstance } = useBenchmarkTargetInstance(); useEffect(() => { const initClusterId = (list: any[]) => { @@ -35,9 +37,12 @@ const BasicForm: React.FC = () => { clusterList?.length > 0 && 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 ( <> diff --git a/src/pages/benchmark/forms/dataset.tsx b/src/pages/benchmark/forms/dataset.tsx index 78bb4d09..8d1b366c 100644 --- a/src/pages/benchmark/forms/dataset.tsx +++ b/src/pages/benchmark/forms/dataset.tsx @@ -43,14 +43,46 @@ const DatasetForm: React.FC = () => { } }; + // Initialize profile when open form + const initProfile = ( + value: string, + option: any, + datasetList: Global.BaseOption[] + ) => { + 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(() => { if (!open) { cancelDatasetRequest(); cancelProfilesRequest(); } + if (open) { - fetchProfilesData(); - fetchDatasetData(); + const init = async () => { + 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]); @@ -73,7 +105,12 @@ const DatasetForm: React.FC = () => { required > {profilesOptions?.map((item: any) => ( - + = forwardRef((props, ref) => { const { action, currentData, onFinish, open, clusterList } = props; const intl = useIntl(); const [form] = Form.useForm(); - const profile = Form.useWatch('profile', form); - const { getScrollElementScrollableHeight } = useWrapperContext(); const [activeKey, setActiveKey] = useState([TABKeysMap.PROFILE]); const scrollTabsRef = useRef(null); - const showAdvanced = profile !== 'Custom' && Boolean(profile); const segmentOptions = [ { @@ -88,12 +85,6 @@ const ProviderForm: React.FC = forwardRef((props, ref) => { } }, [form, currentData, action]); - useEffect(() => { - if (!showAdvanced && activeKey?.includes(TABKeysMap.ADVANCED)) { - setActiveKey([TABKeysMap.PROFILE]); - } - }, [showAdvanced, activeKey]); - return ( { fetchInstanceList, cancelRequest: cancelInstanceRequest } = useQueryModelInstancesList(); + const { benchmarkTargetInstance, clearBenchmarkTargetInstance } = + useBenchmarkTargetInstance(); const handleOnChange = async (value: any, selectedOptions: any) => { form.setFieldsValue({ model_name: value[0], 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 - if (open && modelList.length === 0) { - const list = await fetchModelList({ page: -1 }); - const modelOptions = list - .filter((model: any) => model.replicas > 0) - .map((model: any) => ({ - label: model.name, - value: model.name, - id: model.id, - isLeaf: false, - children: [] - })); + const list = await fetchModelList({ page: -1 }); + const modelOptions = list + .filter((model: any) => model.replicas > 0) + .map((model: any) => ({ + label: model.name, + value: model.name, + id: model.id, + isLeaf: false, + children: [] + })); - if (modelOptions.length === 0) { - 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); + if (modelOptions.length === 0) { + 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[]; + } + + // 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(() => { + if (open) { + initModelInstance(); + } if (!open) { cancelModelRequest(); cancelInstanceRequest(); + clearBenchmarkTargetInstance(); } - }, [open]); + }, [open, benchmarkTargetInstance]); return ( @@ -154,7 +175,6 @@ const ModelInstanceForm: React.FC = () => { getPopupContainer={(triggerNode) => triggerNode.parentNode} optionNode={InstanceNode} loadData={loadInstances} - onOpenChange={handleOnOpenChange} onChange={handleOnChange} > diff --git a/src/pages/benchmark/index.tsx b/src/pages/benchmark/index.tsx index cb6d45be..e2c1693d 100644 --- a/src/pages/benchmark/index.tsx +++ b/src/pages/benchmark/index.tsx @@ -4,6 +4,7 @@ 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 { useBenchmarkTargetInstance } from '@/pages/llmodels/hooks/use-run-benchmark'; import { useQueryModelList } from '@/pages/llmodels/services/use-query-model-list'; import { useIntl, useNavigate } from '@umijs/max'; import { useMemoizedFn } from 'ahooks'; @@ -76,11 +77,19 @@ const Benchmark: React.FC = () => { contentHeight: 320, clusterList }); + const { benchmarkTargetInstance } = useBenchmarkTargetInstance(); useEffect(() => { fetchModelList({ page: -1 }); - fetchClusterList({ page: -1 }); fetchDatasetData(); + fetchClusterList({ page: -1 }).then(() => { + if (benchmarkTargetInstance.model_name) { + openBenchmarkModal( + PageAction.CREATE, + intl.formatMessage({ id: 'benchmark.button.add' }) + ); + } + }); return () => { cancelClusterRequest(); }; diff --git a/src/pages/benchmark/services/use-query-dataset.ts b/src/pages/benchmark/services/use-query-dataset.ts index 3216f275..e06faf20 100644 --- a/src/pages/benchmark/services/use-query-dataset.ts +++ b/src/pages/benchmark/services/use-query-dataset.ts @@ -21,6 +21,7 @@ const useQueryDataset = () => { // TODO: may be fetch data from server in the future. setDatasetList([...datasetOptions]); + return datasetOptions; }; return { diff --git a/src/pages/benchmark/services/use-query-profiles.ts b/src/pages/benchmark/services/use-query-profiles.ts index 442c410a..fa4ad160 100644 --- a/src/pages/benchmark/services/use-query-profiles.ts +++ b/src/pages/benchmark/services/use-query-profiles.ts @@ -41,7 +41,7 @@ export default function useQueryProfiles() { }; }) || []; - setProfilesOptions([ + const options = [ ...list, { label: intl.formatMessage({ id: 'backend.custom' }), @@ -55,7 +55,9 @@ export default function useQueryProfiles() { total_requests: null } } - ]); + ]; + setProfilesOptions(options); + return options; }; return { diff --git a/src/pages/llmodels/components/instance-item.tsx b/src/pages/llmodels/components/instance-item.tsx index 40cb61bd..db3a7419 100644 --- a/src/pages/llmodels/components/instance-item.tsx +++ b/src/pages/llmodels/components/instance-item.tsx @@ -9,6 +9,7 @@ import StatusTag from '@/components/status-tag'; import ThemeTag from '@/components/tags-wrapper/theme-tag'; import { HandlerOptions } from '@/hooks/use-chunk-fetch'; 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 { convertFileSize } from '@/utils'; import { @@ -333,6 +334,12 @@ const childActionList = [ ], icon: }, + { + label: 'models.table.instance.benchmark', + key: 'benchmark', + status: [InstanceStatusMap.Running], + icon: + }, { label: 'common.button.delrecreate', key: 'delete', @@ -416,6 +423,7 @@ const InstanceItem: React.FC = ({ defaultOpenId, handleChildSelect }) => { + const { runBenchmarkOnInstance } = useBenchmarkTargetInstance(); const [api, contextHolder] = notification.useNotification({ stack: { threshold: 1 } }); @@ -423,7 +431,7 @@ const InstanceItem: React.FC = ({ const intl = useIntl(); const actionItems = useMemo(() => { 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 true; @@ -662,7 +670,9 @@ const InstanceItem: React.FC = ({ ]); const handleOnSelect = (val: string) => { - if (val === 'download') { + if (val === 'benchmark') { + runBenchmarkOnInstance(instanceData); + } else if (val === 'download') { downloadStream({ url: `${MODEL_INSTANCE_API}/${instanceData.id}/logs`, filename: createFileName(instanceData.name), diff --git a/src/pages/llmodels/config/types.ts b/src/pages/llmodels/config/types.ts index b05a8ef8..3e102a34 100644 --- a/src/pages/llmodels/config/types.ts +++ b/src/pages/llmodels/config/types.ts @@ -114,6 +114,7 @@ export interface DistributedServers { } export interface ModelInstanceListItem { backend?: string; + cluster_id: number; backend_version?: string; source: string; huggingface_repo_id: string; diff --git a/src/pages/llmodels/hooks/use-run-benchmark.ts b/src/pages/llmodels/hooks/use-run-benchmark.ts new file mode 100644 index 00000000..5013b64a --- /dev/null +++ b/src/pages/llmodels/hooks/use-run-benchmark.ts @@ -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 + }; +};