From 9b843151fe9d45ef6ad6839a46a66ee2fc082335 Mon Sep 17 00:00:00 2001 From: jialin Date: Mon, 20 Oct 2025 14:43:44 +0800 Subject: [PATCH] feat: add gpus_per_replicas --- src/locales/en-US/models.ts | 10 ++- src/locales/ja-JP/models.ts | 18 +++- src/locales/ru-RU/models.ts | 18 +++- src/locales/zh-CN/models.ts | 9 +- src/pages/llmodels/config/index.ts | 6 ++ src/pages/llmodels/config/types.ts | 5 ++ src/pages/llmodels/config/utils.ts | 17 +++- src/pages/llmodels/forms/index.tsx | 5 +- src/pages/llmodels/forms/schedule-type.tsx | 86 +++++++++++++++++-- .../llmodels/hooks/use-form-initial-values.ts | 13 ++- 10 files changed, 170 insertions(+), 17 deletions(-) diff --git a/src/locales/en-US/models.ts b/src/locales/en-US/models.ts index 348a84aa..0060a024 100644 --- a/src/locales/en-US/models.ts +++ b/src/locales/en-US/models.ts @@ -195,5 +195,13 @@ export default { 'models.accessSettings.public.tips': 'When set to public, anyone can access this model without authentication, which may lead to data exposure risks.', 'models.table.button.deploy': 'Deploy Now', - 'models.form.backendVersion.holder': 'Enter or select a version' + 'models.form.backendVersion.holder': 'Enter or select a version', + 'models.form.gpusperreplica': 'GPUs per Replica', + 'models.form.gpusAllocationType': 'GPU Allocation Type', + 'models.form.gpusAllocationType.auto': 'Auto', + 'models.form.gpusAllocationType.custom': 'Custom', + 'models.form.gpusAllocationType.auto.tips': + 'System calculates GPUs per replica automatically.', + 'models.form.gpusAllocationType.custom.tips': + 'You can specify the exact number of GPUs per replica.' }; diff --git a/src/locales/ja-JP/models.ts b/src/locales/ja-JP/models.ts index c76a7ef3..5448bbd1 100644 --- a/src/locales/ja-JP/models.ts +++ b/src/locales/ja-JP/models.ts @@ -192,7 +192,15 @@ export default { 'models.accessSettings.public.tips': 'When set to public, anyone can access this model without authentication, which may lead to data exposure risks.', 'models.table.button.deploy': 'Deploy Now', - 'models.form.backendVersion.holder': 'Enter or select a version' + 'models.form.backendVersion.holder': 'Enter or select a version', + 'models.form.gpusperreplica': 'GPUs per Replica', + 'models.form.gpusAllocationType': 'GPU Allocation Type', + 'models.form.gpusAllocationType.auto': 'Auto', + 'models.form.gpusAllocationType.custom': 'Custom', + 'models.form.gpusAllocationType.auto.tips': + 'System calculates GPUs per replica automatically.', + 'models.form.gpusAllocationType.custom.tips': + 'You can specify the exact number of GPUs per replica.' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== @@ -226,6 +234,12 @@ export default { // 29. 'models.accessSettings.allowedUsers': 'Allowed users', // 30. 'models.accessSettings.public.tips': 'When set to public, anyone can access this model without authentication, which may lead to data exposure risks.', // 31. 'models.table.button.deploy': 'Deploy Now', -// 32. 'models.form.backendVersion.holder': 'Enter or select a version' +// 32. 'models.form.backendVersion.holder': 'Enter or select a version', +// 33. 'models.form.gpusperreplica': 'GPUs per Replica', +// 34. 'models.form.gpusAllocationType': 'GPU Allocation Type', +// 35. 'models.form.gpusAllocationType.auto': 'Auto', +// 36. 'models.form.gpusAllocationType.custom': 'Custom', +// 37. 'models.form.gpusAllocationType.auto.tips': 'System calculates GPUs per replica automatically.', +// 38. 'models.form.gpusAllocationType.custom.tips': 'You can specify the exact number of GPUs per replica.' // ========== End of To-Do List ========== diff --git a/src/locales/ru-RU/models.ts b/src/locales/ru-RU/models.ts index e687a004..535c9b89 100644 --- a/src/locales/ru-RU/models.ts +++ b/src/locales/ru-RU/models.ts @@ -195,7 +195,15 @@ export default { 'models.accessSettings.public.tips': 'When set to public, anyone can access this model without authentication, which may lead to data exposure risks.', 'models.table.button.deploy': 'Deploy Now', - 'models.form.backendVersion.holder': 'Enter or select a version' + 'models.form.backendVersion.holder': 'Enter or select a version', + 'models.form.gpusperreplica': 'GPUs per Replica', + 'models.form.gpusAllocationType': 'GPU Allocation Type', + 'models.form.gpusAllocationType.auto': 'Auto', + 'models.form.gpusAllocationType.custom': 'Custom', + 'models.form.gpusAllocationType.auto.tips': + 'System calculates GPUs per replica automatically.', + 'models.form.gpusAllocationType.custom.tips': + 'You can specify the exact number of GPUs per replica.' }; // ========== To-Do: Translate Keys (Remove After Translation) ========== @@ -221,5 +229,11 @@ export default { // 21. 'models.accessSettings.allowedUsers': 'Allowed users', // 22. 'models.accessSettings.public.tips': 'When set to public, anyone can access this model without authentication, which may lead to data exposure risks.', // 23. 'models.table.button.deploy': 'Deploy Now', -// 24. 'models.form.backendVersion.holder': 'Enter or select a version' +// 24. 'models.form.backendVersion.holder': 'Enter or select a version', +// 25. 'models.form.gpusperreplica': 'GPUs per Replica', +// 26. 'models.form.gpusAllocationType': 'GPU Allocation Type', +// 27. 'models.form.gpusAllocationType.auto': 'Auto', +// 28. 'models.form.gpusAllocationType.custom': 'Custom', +// 29. 'models.form.gpusAllocationType.auto.tips': 'System calculates GPUs per replica automatically.', +// 30. 'models.form.gpusAllocationType.custom.tips': 'You can specify the exact number of GPUs per replica.' // ========== End of To-Do List ========== diff --git a/src/locales/zh-CN/models.ts b/src/locales/zh-CN/models.ts index a442d33a..96a3c086 100644 --- a/src/locales/zh-CN/models.ts +++ b/src/locales/zh-CN/models.ts @@ -184,5 +184,12 @@ export default { 'models.accessSettings.public.tips': '公开后,任何人无需认证即可访问,可能存在数据泄露风险。', 'models.table.button.deploy': '立即部署', - 'models.form.backendVersion.holder': '输入或选择一个版本' + 'models.form.backendVersion.holder': '输入或选择一个版本', + 'models.form.gpusperreplica': '每副本 GPU 数量', + 'models.form.gpusAllocationType': 'GPU 分配方式', + 'models.form.gpusAllocationType.auto': '自动', + 'models.form.gpusAllocationType.custom': '自定义', + 'models.form.gpusAllocationType.auto.tips': + '系统自动计算每个副本的 GPU 数量。', + 'models.form.gpusAllocationType.custom.tips': '您可以指定每个副本的 GPU 数量' }; diff --git a/src/pages/llmodels/config/index.ts b/src/pages/llmodels/config/index.ts index 232d6dab..a53d4763 100644 --- a/src/pages/llmodels/config/index.ts +++ b/src/pages/llmodels/config/index.ts @@ -194,6 +194,11 @@ export const ScheduleValueMap = { SpecificGPUType: 'specific_gpu_type' }; +export const gpusCountTypeMap = { + Auto: 'auto', + Custom: 'custom' +}; + export const scheduleList = [ { label: 'models.form.scheduletype.auto', @@ -324,6 +329,7 @@ export const excludeFields = [ 'backend_version', 'ollama_library_model_name', 'scheduleType', + 'gpusCountType', 'placement_strategy', 'backend', 'gpu_selector', diff --git a/src/pages/llmodels/config/types.ts b/src/pages/llmodels/config/types.ts index ea3b6c1e..a3255217 100644 --- a/src/pages/llmodels/config/types.ts +++ b/src/pages/llmodels/config/types.ts @@ -27,6 +27,7 @@ export interface ListItem { access_policy: 'public' | 'authed' | 'allowed_users'; gpu_selector?: { gpu_ids: string[]; + gpus_per_replica?: number; }; worker_selector?: object; } @@ -63,11 +64,13 @@ export interface FormData { gpu_ids?: string[]; gpu_type?: string; gpu_count?: number; + gpus_per_replica?: number; }; placement_strategy?: string; cpu_offloading?: boolean; worker_selector?: object; scheduleType?: string; + gpusCountType?: string; name: string; replicas: number; description: string; @@ -191,6 +194,7 @@ export interface CatalogSpec { worker_selector: Record; gpu_selector: { gpu_ids: string[]; + gpus_per_replica: number; }; backend: string; backend_version: string; @@ -220,6 +224,7 @@ export interface EvaluateSpec { worker_selector?: Record; gpu_selector?: { gpu_ids: string[]; + gpus_per_replica: number; }; backend?: string; backend_version?: string; diff --git a/src/pages/llmodels/config/utils.ts b/src/pages/llmodels/config/utils.ts index a2b01f5b..f99f2e5e 100644 --- a/src/pages/llmodels/config/utils.ts +++ b/src/pages/llmodels/config/utils.ts @@ -1,4 +1,5 @@ import _ from 'lodash'; +import { gpusCountTypeMap } from '.'; import { backendOptionsMap } from '../config/backend-parameters'; import { FormData } from './types'; @@ -33,6 +34,11 @@ export const generateGPUSelector = (data: any, gpuOptions: any[]) => { }; }; +/** + * before submit the form, generate the gpu_selector field + * @param data + * @returns + */ export const generateGPUIds = (data: FormData) => { const gpu_ids = _.get(data, 'gpu_selector.gpu_ids', []); console.log('generateGPUIds', gpu_ids); @@ -54,10 +60,17 @@ export const generateGPUIds = (data: FormData) => { }, [] ); - + if (gpusCountTypeMap.Auto === data.gpusCountType) { + return { + gpu_selector: { + gpu_ids: result || [] + } + }; + } return { gpu_selector: { - gpu_ids: result || [] + gpu_ids: result || [], + gpus_per_replica: data.gpu_selector?.gpus_per_replica || null } }; }; diff --git a/src/pages/llmodels/forms/index.tsx b/src/pages/llmodels/forms/index.tsx index 4ea65b11..b25d61a2 100644 --- a/src/pages/llmodels/forms/index.tsx +++ b/src/pages/llmodels/forms/index.tsx @@ -5,7 +5,7 @@ import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import _ from 'lodash'; import React, { forwardRef, useImperativeHandle } from 'react'; -import { excludeFields, ScheduleValueMap } from '../config'; +import { excludeFields, gpusCountTypeMap, ScheduleValueMap } from '../config'; import { backendOptionsMap } from '../config/backend-parameters'; import { FormContext } from '../config/form-context'; import { @@ -120,7 +120,7 @@ const DataForm: React.FC = forwardRef((props, ref) => { data.categories = data.categories ? [data.categories] : []; const gpuSelector = generateGPUIds(data); const allValues = { - ..._.omit(data, ['scheduleType']), + ..._.omit(data, ['scheduleType', 'gpusCountType']), ...gpuSelector }; @@ -250,6 +250,7 @@ const DataForm: React.FC = forwardRef((props, ref) => { placement_strategy: 'spread', cpu_offloading: true, scheduleType: ScheduleValueMap.Auto, + gpusCountType: gpusCountTypeMap.Auto, categories: null, restart_on_error: true, distributed_inference_across_workers: true, diff --git a/src/pages/llmodels/forms/schedule-type.tsx b/src/pages/llmodels/forms/schedule-type.tsx index 9d36238b..7f4b5c89 100644 --- a/src/pages/llmodels/forms/schedule-type.tsx +++ b/src/pages/llmodels/forms/schedule-type.tsx @@ -1,3 +1,4 @@ +import SealInputNumber from '@/components/seal-form/input-number'; import SealCascader from '@/components/seal-form/seal-cascader'; import SealSelect from '@/components/seal-form/seal-select'; import TooltipList from '@/components/tooltip-list'; @@ -6,9 +7,9 @@ import { useIntl } from '@umijs/max'; import { Form } from 'antd'; import React from 'react'; import GPUCard from '../components/gpu-card'; -import { scheduleList, ScheduleValueMap } from '../config'; +import { gpusCountTypeMap, scheduleList, ScheduleValueMap } from '../config'; import { backendOptionsMap } from '../config/backend-parameters'; -import { useCatalogFormContext, useFormContext } from '../config/form-context'; +import { useFormContext } from '../config/form-context'; const scheduleTypeTips = [ { @@ -27,13 +28,31 @@ const scheduleTypeTips = [ } ]; +const gpuAllocateTypeTips = [ + { + title: { + text: 'models.form.gpusAllocationType.auto', + locale: true + }, + tips: 'models.form.gpusAllocationType.auto.tips' + }, + { + title: { + text: 'models.form.gpusAllocationType.custom', + locale: true + }, + tips: 'models.form.gpusAllocationType.custom.tips' + } +]; + const ScheduleTypeForm: React.FC = () => { const intl = useIntl(); const { onValuesChange, gpuOptions } = useFormContext(); - const { onQuantizationChange } = useCatalogFormContext(); const { getRuleMessage } = useAppUtils(); const form = Form.useFormInstance(); const scheduleType = Form.useWatch('scheduleType', form); + const gpusCountType = Form.useWatch('gpusCountType', form); + const gpuSelectorIds = Form.useWatch(['gpu_selector', 'gpu_ids'], form); const handleScheduleTypeChange = (value: string) => { if (value === ScheduleValueMap.Auto) { @@ -41,8 +60,11 @@ const ScheduleTypeForm: React.FC = () => { } }; - const handleOnQuantizationChange = (val: any) => { - onQuantizationChange?.(val); + const handleGpusCountTypeChange = (val: string) => { + if (val === 'custom') { + form.setFieldValue(['gpu_selector', 'gpus_per_replica'], 2); + } + onValuesChange?.({}, form.getFieldsValue()); }; const handleBeforeGpuSelectorChange = (gpuIds: any[]) => {}; @@ -52,6 +74,23 @@ const ScheduleTypeForm: React.FC = () => { onValuesChange?.({}, form.getFieldsValue()); }; + const handleOnStepReplicaStep = ( + value: number, + info: { offset: number; type: 'up' | 'down' } + ) => { + let newValue = value; + const isPowerOfTwo = (n: number) => (n & (n - 1)) === 0 && n !== 0; // check power of two + if (!isPowerOfTwo(value)) { + if (info.type === 'up') { + newValue = Math.pow(2, Math.ceil(Math.log2(value))); + } else { + newValue = Math.pow(2, Math.floor(Math.log2(value))); + } + } + form.setFieldValue(['gpu_selector', 'gpus_per_replica'], newValue); + onValuesChange?.({}, form.getFieldsValue()); + }; + return ( <> @@ -115,6 +154,43 @@ const ScheduleTypeForm: React.FC = () => { onChange={handleGpuSelectorChange} > + + + } + options={[ + { + label: intl.formatMessage({ + id: 'models.form.gpusAllocationType.auto' + }), + value: gpusCountTypeMap.Auto + }, + { + label: intl.formatMessage({ + id: 'models.form.gpusAllocationType.custom' + }), + value: gpusCountTypeMap.Custom + } + ]} + > + + {gpusCountType === gpusCountTypeMap.Custom && ( + + + + )} )} diff --git a/src/pages/llmodels/hooks/use-form-initial-values.ts b/src/pages/llmodels/hooks/use-form-initial-values.ts index 68e1d978..8c530a79 100644 --- a/src/pages/llmodels/hooks/use-form-initial-values.ts +++ b/src/pages/llmodels/hooks/use-form-initial-values.ts @@ -10,7 +10,7 @@ import { ListItem as WorkerListItem } from '@/pages/resources/config/types'; import { useAtom } from 'jotai'; import { useState } from 'react'; import { queryGPUList } from '../apis'; -import { ScheduleValueMap } from '../config'; +import { gpusCountTypeMap, ScheduleValueMap } from '../config'; import { GPUListItem, ListItem } from '../config/types'; type EmptyObject = Record; @@ -226,13 +226,22 @@ export default function useFormInitialValues() { } }; + /** + * before set the form initial values, generate the form values + * @param data + * @param gpuOptions + * @returns + */ const generateFormValues = (data: ListItem, gpuOptions: any[]) => { const formData = { ...data, categories: data?.categories?.length ? data.categories[0] : null, scheduleType: data?.gpu_selector ? ScheduleValueMap.Manual - : ScheduleValueMap.Auto + : ScheduleValueMap.Auto, + gpusCountType: data?.gpu_selector?.gpus_per_replica + ? gpusCountTypeMap.Custom + : gpusCountTypeMap.Auto }; return formData; };