diff --git a/src/locales/en-US/clusters.ts b/src/locales/en-US/clusters.ts index 6cf54728..70fb83ed 100644 --- a/src/locales/en-US/clusters.ts +++ b/src/locales/en-US/clusters.ts @@ -73,6 +73,9 @@ Same applies to the /opt/dtk directory.`, 'clusters.addworker.enterWorkerIP': 'Enter worker IP', 'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.', 'clusters.addworker.extraVolume': 'Additional Volume Mount', + 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount', + 'clusters.addworker.cacheVolume.tips': + 'If you want to custom the model cache directory, you can specify the path to mount it.', 'clusters.addworker.configSummary': 'Configuration Summary', 'clusters.addworker.gpuVendor': 'GPU Vendor', 'clusters.addworker.workerIP': 'Worker IP', @@ -80,6 +83,8 @@ Same applies to the /opt/dtk directory.`, 'clusters.addworker.autoDetect': 'Auto-detect', 'clusters.addworker.extraVolume.holder': 'e.g. /data/models (path must start with /)', + 'clusters.addworker.cacheVolume.holder': + 'e.g. /data/cache (path must start with /)', 'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device', 'clusters.button.genToken': 'Need to create a new token? Click here.', diff --git a/src/locales/ja-JP/clusters.ts b/src/locales/ja-JP/clusters.ts index 27c4fa51..0a695984 100644 --- a/src/locales/ja-JP/clusters.ts +++ b/src/locales/ja-JP/clusters.ts @@ -73,6 +73,9 @@ Same applies to the /opt/dtk directory.`, 'clusters.addworker.enterWorkerIP': 'Enter worker IP', 'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.', 'clusters.addworker.extraVolume': 'Additional Volume Mount', + 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount', + 'clusters.addworker.cacheVolume.tips': + 'If you want to custom the model cache directory, you can specify the path to mount it.', 'clusters.addworker.configSummary': 'Configuration Summary', 'clusters.addworker.gpuVendor': 'GPU Vendor', 'clusters.addworker.workerIP': 'Worker IP', @@ -80,6 +83,8 @@ Same applies to the /opt/dtk directory.`, 'clusters.addworker.autoDetect': 'Auto-detect', 'clusters.addworker.extraVolume.holder': 'e.g. /data/models (path must start with /)', + 'clusters.addworker.cacheVolume.holder': + 'e.g. /data/cache (path must start with /)', 'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device', 'clusters.button.genToken': 'Need to create a new token? Click here.', @@ -156,5 +161,8 @@ Same applies to the /opt/dtk directory.`, // 67. 'clusters.addworker.extraVolume.holder': 'e.g. /data/models (path must start with /)' // 68. 'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device', // 69. 'clusters.button.genToken': 'Need to create a new token? Click here.', -// 70. 'clusters.addworker.amdNotes-01': `If the /opt/rocm directory does not exist, please create a symbolic link pointing to the ROCm installed path: ln -s /path/to/rocm /opt/rocm.` +// 70. 'clusters.addworker.amdNotes-01': `If the /opt/rocm directory does not exist, please create a symbolic link pointing to the ROCm installed path: ln -s /path/to/rocm /opt/rocm.`, +// 71. 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount', +// 72. 'clusters.addworker.cacheVolume.tips': 'If you want to custom the model cache directory, you can specify the path to mount it.', +// 73. 'clusters.addworker.cacheVolume.holder': 'e.g. /data/cache (path must start with /)', // ========== End of To-Do List ========== diff --git a/src/locales/ru-RU/clusters.ts b/src/locales/ru-RU/clusters.ts index 354a5798..7d859247 100644 --- a/src/locales/ru-RU/clusters.ts +++ b/src/locales/ru-RU/clusters.ts @@ -73,6 +73,9 @@ export default { 'clusters.addworker.enterWorkerIP': 'Введите IP воркера', 'clusters.addworker.enterWorkerIP.error': 'Пожалуйста, введите IP воркера.', 'clusters.addworker.extraVolume': 'Дополнительное монтирование тома', + 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount', + 'clusters.addworker.cacheVolume.tips': + 'If you want to custom the model cache directory, you can specify the path to mount it.', 'clusters.addworker.configSummary': 'Сводка конфигурации', 'clusters.addworker.gpuVendor': 'Производитель GPU', 'clusters.addworker.workerIP': 'IP воркера', @@ -80,6 +83,8 @@ export default { 'clusters.addworker.autoDetect': 'Автоопределение', 'clusters.addworker.extraVolume.holder': 'напр. /data/models (путь должен начинаться с /)', + 'clusters.addworker.cacheVolume.holder': + 'напр. /data/cache (путь должен начинаться с /)', 'clusters.addworker.vendorNotes.title': 'Примечания для устройств {vendor}', 'clusters.button.genToken': 'Need to create a new token? Click here.', @@ -89,4 +94,6 @@ export default { // ========== To-Do: Translate Keys (Remove After Translation) ========== // 1. 'clusters.addworker.amdNotes-01': `If the /opt/rocm directory does not exist, please create a symbolic link pointing to the ROCm installed path: ln -s /path/to/rocm /opt/rocm.`, // 2. 'clusters.button.genToken': 'Need to create a new token? Click here.', +// 3. 'clusters.addworker.cacheVolume': 'Model Cache Volume Mount', +// 4. 'clusters.addworker.cacheVolume.tips': 'If you want to custom the model cache directory, you can specify the path to mount it.', // ================================================================ diff --git a/src/locales/zh-CN/clusters.ts b/src/locales/zh-CN/clusters.ts index 9e8638e0..d360a947 100644 --- a/src/locales/zh-CN/clusters.ts +++ b/src/locales/zh-CN/clusters.ts @@ -48,7 +48,7 @@ export default { 'clusters.addworker.nvidiaNotes-01': '如果节点有多个出站 IP 地址,请填写 WORKER_IP,以确保使用指定的 IP。可通过命令 hostname -I | xargs -n1 进行确认。', 'clusters.addworker.nvidiaNotes-02': - '如果模型目录已存在于节点上,请添加 --volume 参数进行挂载。', + '如果 worker 上已经存在模型目录,你可以指定该路径进行挂载。', 'clusters.addworker.hygonNotes': '如果 /opt/hyhal 目录不存在,请创建指向 Hygon 安装路径的符号链接:/opt/hyhal。与 /opt/dtk 目录相同。', 'clusters.addworker.corexNotes': @@ -71,6 +71,9 @@ export default { 'clusters.addworker.enterWorkerIP': '输入节点 IP', 'clusters.addworker.enterWorkerIP.error': '请输入节点 IP', 'clusters.addworker.extraVolume': '额外卷挂载', + 'clusters.addworker.cacheVolume': '缓存卷挂载', + 'clusters.addworker.cacheVolume.tips': + '如果你想自定义模型缓存目录,可以指定路径进行挂载。', 'clusters.addworker.configSummary': '配置摘要', 'clusters.addworker.gpuVendor': 'GPU 厂商', 'clusters.addworker.workerIP': '节点 IP', @@ -78,6 +81,8 @@ export default { 'clusters.addworker.autoDetect': '自动检测', 'clusters.addworker.extraVolume.holder': '例如:/data/models(路径需以 / 开头)', + 'clusters.addworker.cacheVolume.holder': + '例如:/data/cache(路径需以 / 开头)', 'clusters.addworker.vendorNotes.title': '{vendor}设备注意事项', 'clusters.button.genToken': '需要创建令牌?点击这里。', diff --git a/src/pages/backends/forms/versions-config.tsx b/src/pages/backends/forms/versions-config.tsx index 65d97f0e..5817a263 100644 --- a/src/pages/backends/forms/versions-config.tsx +++ b/src/pages/backends/forms/versions-config.tsx @@ -316,8 +316,12 @@ const VersionsForm: React.FC = ({ > diff --git a/src/pages/cluster-management/components/add-worker-command.tsx b/src/pages/cluster-management/components/add-worker-command.tsx index eb04bff6..01661a79 100644 --- a/src/pages/cluster-management/components/add-worker-command.tsx +++ b/src/pages/cluster-management/components/add-worker-command.tsx @@ -6,6 +6,7 @@ type ViewModalProps = { currentGPU?: string; workerIP?: string; modelDir?: string; + cacheDir?: string; registrationInfo: { token: string; image: string; @@ -17,6 +18,7 @@ const AddWorkerCommand: React.FC = ({ registrationInfo, workerIP, modelDir, + cacheDir, currentGPU }) => { const code = React.useMemo(() => { @@ -28,13 +30,14 @@ const AddWorkerCommand: React.FC = ({ tag: '', workerIP: workerIP, modelDir: modelDir, + cacheDir: cacheDir, image: registrationInfo.image, token: registrationInfo.token || '${token}' }) ?.trim() .replace(/\s+$/gm, '') .replace(/\\+$/, ''); - }, [registrationInfo, currentGPU, workerIP, modelDir]); + }, [registrationInfo, currentGPU, workerIP, modelDir, cacheDir]); return ( { path: '', required: false }; + const cacheDirConfig = summary.get('cacheDirConfig') || { + enable: false, + path: '' + }; const currentGPU = summary.get('currentGPU') || ''; const stepIndex = stepList.indexOf(StepNamesMap.RunCommand) + 1; @@ -51,6 +55,7 @@ const DockerRunCommand = () => { registrationInfo={registrationInfo} workerIP={workerIPConfig.enable ? workerIPConfig.ip : ''} modelDir={modelDirConfig.enable ? modelDirConfig.path : ''} + cacheDir={cacheDirConfig.enable ? cacheDirConfig.path : ''} currentGPU={currentGPU} /> diff --git a/src/pages/cluster-management/components/add-worker/specify-arguments.tsx b/src/pages/cluster-management/components/add-worker/specify-arguments.tsx index c59ba176..dff8fbda 100644 --- a/src/pages/cluster-management/components/add-worker/specify-arguments.tsx +++ b/src/pages/cluster-management/components/add-worker/specify-arguments.tsx @@ -2,12 +2,79 @@ import AlertInfoBlock from '@/components/alert-info/block'; import { ExclamationCircleFilled } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; import { Input, Switch } from 'antd'; -import { useEffect } from 'react'; +import React, { useEffect } from 'react'; +import styled from 'styled-components'; import { useAddWorkerContext } from './add-worker-context'; import { StepNamesMap } from './config'; import { NotesWrapper, SwitchWrapper, Tips, Title } from './constainers'; import StepCollapse from './step-collapse'; +const ButtonWrapper = styled.div` + display: flex; + align-items: center; + justify-content: space-between; +`; + +const SwitchSetting: React.FC<{ + label: string; + checked: boolean; + value?: string; + placeholder?: string; + tips?: React.ReactNode; + errorMessage?: React.ReactNode; + extra?: React.ReactNode; + onInputChange?: (value: string) => void; + onChange: (checked: boolean) => void; +}> = ({ + label, + checked, + onChange, + value, + placeholder, + tips, + extra, + errorMessage, + onInputChange +}) => { + return ( + + + + {label} + + + + {tips && ( + + )} + {checked && ( + <> + onInputChange?.(e.target.value)} + /> + {errorMessage && ( + + {errorMessage} + + )} + + )} + {extra} + + ); +}; + const SpecifyArguments = () => { const intl = useIntl(); const { stepList, summary, updateField, registerField } = @@ -24,6 +91,11 @@ const SpecifyArguments = () => { path: '' }; + const cacheDirConfig = summary.get('cacheDirConfig') || { + enable: false, + path: '' + }; + const setWorkerIPConfig = (config: { enable: boolean; ip?: string; @@ -42,6 +114,13 @@ const SpecifyArguments = () => { }); }; + const setCacheDirConfig = (config: { enable: boolean; path?: string }) => { + updateField('cacheDirConfig', { + ...cacheDirConfig, + ...config + }); + }; + const beforeNext = async () => { if (workerIPConfig.enable && !workerIPConfig.ip) { setWorkerIPConfig({ @@ -56,16 +135,13 @@ const SpecifyArguments = () => { }; useEffect(() => { - const unregister = registerField('workerIPConfig'); + const unregisterWorkerIP = registerField('workerIPConfig'); + const unregisterModelDir = registerField('modelDirConfig'); + const unregisterCacheDir = registerField('cacheDirConfig'); return () => { - unregister(); - }; - }, []); - - useEffect(() => { - const unregister = registerField('modelDirConfig'); - return () => { - unregister(); + unregisterWorkerIP(); + unregisterModelDir(); + unregisterCacheDir(); }; }, []); @@ -80,6 +156,11 @@ const SpecifyArguments = () => { enable: false, path: '' }); + + updateField('cacheDirConfig', { + enable: false, + path: '' + }); }, []); return ( @@ -102,123 +183,111 @@ const SpecifyArguments = () => { }} > {/* worker IP config */} - -
- - {workerIPConfig.enable - ? intl.formatMessage({ - id: 'clusters.addworker.specifyWorkerIP' - }) - : intl.formatMessage({ - id: 'clusters.addworker.detectWorkerIP' - })} - - - setWorkerIPConfig({ - ...workerIPConfig, - enable: checked, - required: false + -
- {workerIPConfig.enable && ( - <> - - setWorkerIPConfig({ - ...workerIPConfig, - ip: e.target.value - }) + : intl.formatMessage({ + id: 'clusters.addworker.detectWorkerIP' + }) + } + placeholder={intl.formatMessage({ + id: 'clusters.addworker.enterWorkerIP' + })} + value={workerIPConfig.ip} + checked={workerIPConfig.enable} + errorMessage={ + workerIPConfig.required && + !workerIPConfig.ip && + intl.formatMessage({ + id: 'clusters.addworker.enterWorkerIP.error' + }) + } + onChange={(checked) => + setWorkerIPConfig({ + ...workerIPConfig, + enable: checked, + required: false + }) + } + onInputChange={(value) => + setWorkerIPConfig({ + ...workerIPConfig, + ip: value + }) + } + extra={ + !workerIPConfig.enable && ( + } + message={ + +
  • +
    } - /> - {workerIPConfig.required && !workerIPConfig.ip && ( - - {intl.formatMessage({ - id: 'clusters.addworker.enterWorkerIP.error' - })} - - )} - - )} - {!workerIPConfig.enable && ( - } - message={ - -
  • -
    - } - >
    - )} -
    + > + ) + } + > {/* model directory config */} - -
    - - {/* optional */} - - {intl.formatMessage({ id: 'clusters.addworker.extraVolume' })} - - - - - setModelDirConfig({ ...modelDirConfig, enable: checked }) - } - > -
    - - {modelDirConfig.enable && ( - - setModelDirConfig({ - ...modelDirConfig, - path: e.target.value - }) - } - /> - )} -
    + + setModelDirConfig({ ...modelDirConfig, enable: checked }) + } + onInputChange={(value) => + setModelDirConfig({ + ...modelDirConfig, + path: value + }) + } + > + {/* cache directory config */} + + setCacheDirConfig({ ...cacheDirConfig, enable: checked }) + } + onInputChange={(value) => + setCacheDirConfig({ + ...cacheDirConfig, + path: value + }) + } + > ); diff --git a/src/pages/cluster-management/components/add-worker/summary-data.tsx b/src/pages/cluster-management/components/add-worker/summary-data.tsx index cb25b1c3..7ecd028d 100644 --- a/src/pages/cluster-management/components/add-worker/summary-data.tsx +++ b/src/pages/cluster-management/components/add-worker/summary-data.tsx @@ -27,6 +27,11 @@ const SummaryData: React.FC = () => { path: '' }; + const cacheDirConfig = summary.get('cacheDirConfig') || { + enable: false, + path: '' + }; + return ( @@ -129,6 +134,32 @@ const SummaryData: React.FC = () => { )} </span> </div> + <div className="item"> + <span className="label"> + {intl.formatMessage({ id: 'clusters.addworker.cacheVolume' })}: + </span> + <span className="value"> + {cacheDirConfig.enable && cacheDirConfig.path + ? cacheDirConfig.path + : ''} + {(!cacheDirConfig.path || !cacheDirConfig.enable) && ( + <StopOutlined + style={{ + color: 'var(--ant-color-text-tertiary)' + }} + /> + )} + + {cacheDirConfig.enable && cacheDirConfig.path && ( + <CheckCircleOutlined + style={{ + color: 'var(--ant-color-success)', + marginLeft: 4 + }} + /> + )} + </span> + </div> </div> </ConfigWrapper> ); diff --git a/src/pages/llmodels/components/access-control-modal/index.tsx b/src/pages/llmodels/components/access-control-modal/index.tsx index 66a9abb8..0f281dd8 100644 --- a/src/pages/llmodels/components/access-control-modal/index.tsx +++ b/src/pages/llmodels/components/access-control-modal/index.tsx @@ -36,7 +36,7 @@ const AccessControlModal: React.FC< message.success(intl.formatMessage({ id: 'common.message.success' })); onCancel?.(); } catch (error) { - message.error(intl.formatMessage({ id: 'common.message.failed' })); + // do nothing, error message handled in request layer } }; diff --git a/src/pages/llmodels/forms/index.tsx b/src/pages/llmodels/forms/index.tsx index 4d19229c..16f4bf99 100644 --- a/src/pages/llmodels/forms/index.tsx +++ b/src/pages/llmodels/forms/index.tsx @@ -6,7 +6,7 @@ import CollapsePanel from '@/pages/_components/collapse-panel'; import { useWrapperContext } from '@/pages/_components/column-wrapper/use-wrapper-context'; import { useIntl } from '@umijs/max'; import useMemoizedFn from 'ahooks/lib/useMemoizedFn'; -import { Form, Segmented } from 'antd'; +import { Form } from 'antd'; import _ from 'lodash'; import React, { forwardRef, useImperativeHandle, useMemo } from 'react'; import styled from 'styled-components'; @@ -42,14 +42,6 @@ const scheduleRequiredFields = ['gpu_selector']; const performanceRequiredFields = ['speculative_config']; -const SegmentedInner = styled(Segmented)` - width: 100%; - border-radius: 0; - .ant-segmented-item { - flex: 1; - } -`; - const SegmentedHeader = styled.div<{ $top?: number }>` position: sticky; top: ${(props) => props.$top || 0}px; @@ -260,9 +252,10 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => { await onClusterChange?.(value); getGPUOptionList({ clusterId: value }); getBackendOptions({ cluster_id: value }); - if (scheduleType === ScheduleValueMap.Manual) { - form.setFieldValue(['gpu_selector', 'gpu_ids'], []); - } + form.setFieldsValue({ + scheduleType: ScheduleValueMap.Auto, + gpu_selector: null + }); await new Promise((resolve) => { setTimeout(resolve, 150); }); diff --git a/src/pages/llmodels/hooks/index.ts b/src/pages/llmodels/hooks/index.ts index 14633641..f5962342 100644 --- a/src/pages/llmodels/hooks/index.ts +++ b/src/pages/llmodels/hooks/index.ts @@ -174,6 +174,7 @@ export const useCheckCompatibility = () => { const handleEvaluate = async (data: any) => { try { // when no cluster selected, show warning and prompt user to add cluster first + console.log('handleEvaluate', data); if (!data.cluster_id) { setWarningStatus({ show: true, @@ -388,7 +389,7 @@ export const useCheckCompatibility = () => { const handleDoEvalute = async (formData: FormData) => { const currentRequestId = updateRequestId(); const evalutionData = await handleEvaluate(formData); - if (currentRequestId === requestIdRef.current) { + if (currentRequestId === requestIdRef.current && evalutionData) { handleShowCompatibleAlert?.(evalutionData); return evalutionData; } diff --git a/src/pages/resources/config/gpu-driver.ts b/src/pages/resources/config/gpu-driver.ts index d8e4aeed..d542e757 100644 --- a/src/pages/resources/config/gpu-driver.ts +++ b/src/pages/resources/config/gpu-driver.ts @@ -127,13 +127,15 @@ export const dockerEnvCommandMap = { ) }; const setNormalArgs = (params: any) => { + console.log('params++++++++++++++', params); return `sudo docker run -d --name gpustack-worker \\ --restart=unless-stopped \\ --privileged \\ --network=host \\ --volume /var/run/docker.sock:/var/run/docker.sock \\ --volume gpustack-data:/var/lib/gpustack \\ - ${params.modelDir ? `--volume ${params.modelDir}:${params.modelDir} \\` : ''}`; + ${params.modelDir ? `--volume ${params.modelDir}:${params.modelDir} \\` : ''} + ${params.cacheDir ? `--volume ${params.cacheDir}:/var/lib/gpustack/cache \\` : ''}`; }; const setImageArgs = (params: any) => {