feat: model cache volume mount, clear gpu selector

This commit is contained in:
jialin
2025-12-09 19:07:40 +08:00
parent daa10617c0
commit 087675889f
14 changed files with 280 additions and 143 deletions
+5
View File
@@ -73,6 +73,9 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
'clusters.addworker.enterWorkerIP': 'Enter worker IP', 'clusters.addworker.enterWorkerIP': 'Enter worker IP',
'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.', 'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.',
'clusters.addworker.extraVolume': 'Additional Volume Mount', '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.configSummary': 'Configuration Summary',
'clusters.addworker.gpuVendor': 'GPU Vendor', 'clusters.addworker.gpuVendor': 'GPU Vendor',
'clusters.addworker.workerIP': 'Worker IP', 'clusters.addworker.workerIP': 'Worker IP',
@@ -80,6 +83,8 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
'clusters.addworker.autoDetect': 'Auto-detect', 'clusters.addworker.autoDetect': 'Auto-detect',
'clusters.addworker.extraVolume.holder': 'clusters.addworker.extraVolume.holder':
'e.g. /data/models (path must start with /)', '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.addworker.vendorNotes.title': 'Notes for {vendor} Device',
'clusters.button.genToken': 'clusters.button.genToken':
'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.', 'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
+9 -1
View File
@@ -73,6 +73,9 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
'clusters.addworker.enterWorkerIP': 'Enter worker IP', 'clusters.addworker.enterWorkerIP': 'Enter worker IP',
'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.', 'clusters.addworker.enterWorkerIP.error': 'Please enter the worker IP.',
'clusters.addworker.extraVolume': 'Additional Volume Mount', '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.configSummary': 'Configuration Summary',
'clusters.addworker.gpuVendor': 'GPU Vendor', 'clusters.addworker.gpuVendor': 'GPU Vendor',
'clusters.addworker.workerIP': 'Worker IP', 'clusters.addworker.workerIP': 'Worker IP',
@@ -80,6 +83,8 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
'clusters.addworker.autoDetect': 'Auto-detect', 'clusters.addworker.autoDetect': 'Auto-detect',
'clusters.addworker.extraVolume.holder': 'clusters.addworker.extraVolume.holder':
'e.g. /data/models (path must start with /)', '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.addworker.vendorNotes.title': 'Notes for {vendor} Device',
'clusters.button.genToken': 'clusters.button.genToken':
'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.', 'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
@@ -156,5 +161,8 @@ Same applies to the <span class="bold-text">/opt/dtk</span> directory.`,
// 67. 'clusters.addworker.extraVolume.holder': 'e.g. /data/models (path must start with /)' // 67. 'clusters.addworker.extraVolume.holder': 'e.g. /data/models (path must start with /)'
// 68. 'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device', // 68. 'clusters.addworker.vendorNotes.title': 'Notes for {vendor} Device',
// 69. 'clusters.button.genToken': 'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.', // 69. 'clusters.button.genToken': 'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
// 70. 'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.` // 70. 'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`,
// 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 ========== // ========== End of To-Do List ==========
+7
View File
@@ -73,6 +73,9 @@ export default {
'clusters.addworker.enterWorkerIP': 'Введите IP воркера', 'clusters.addworker.enterWorkerIP': 'Введите IP воркера',
'clusters.addworker.enterWorkerIP.error': 'Пожалуйста, введите IP воркера.', 'clusters.addworker.enterWorkerIP.error': 'Пожалуйста, введите IP воркера.',
'clusters.addworker.extraVolume': 'Дополнительное монтирование тома', '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.configSummary': 'Сводка конфигурации',
'clusters.addworker.gpuVendor': 'Производитель GPU', 'clusters.addworker.gpuVendor': 'Производитель GPU',
'clusters.addworker.workerIP': 'IP воркера', 'clusters.addworker.workerIP': 'IP воркера',
@@ -80,6 +83,8 @@ export default {
'clusters.addworker.autoDetect': 'Автоопределение', 'clusters.addworker.autoDetect': 'Автоопределение',
'clusters.addworker.extraVolume.holder': 'clusters.addworker.extraVolume.holder':
'напр. /data/models (путь должен начинаться с /)', 'напр. /data/models (путь должен начинаться с /)',
'clusters.addworker.cacheVolume.holder':
'напр. /data/cache (путь должен начинаться с /)',
'clusters.addworker.vendorNotes.title': 'Примечания для устройств {vendor}', 'clusters.addworker.vendorNotes.title': 'Примечания для устройств {vendor}',
'clusters.button.genToken': 'clusters.button.genToken':
'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.', 'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
@@ -89,4 +94,6 @@ export default {
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`, // 1. 'clusters.addworker.amdNotes-01': `If the <span class="bold-text">/opt/rocm</span> directory does not exist, please create a symbolic link pointing to the ROCm installed path: <span class="bold-text">ln -s /path/to/rocm /opt/rocm</span>.`,
// 2. 'clusters.button.genToken': 'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.', // 2. 'clusters.button.genToken': 'Need to create a new token? Click <a href="{link}" target="_blank">here</a>.',
// 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.',
// ================================================================ // ================================================================
+6 -1
View File
@@ -48,7 +48,7 @@ export default {
'clusters.addworker.nvidiaNotes-01': 'clusters.addworker.nvidiaNotes-01':
'如果节点有多个出站 IP 地址,请填写 <span class="bold-text">WORKER_IP</span>,以确保使用指定的 IP。可通过命令 <span class="bold-text">hostname -I | xargs -n1</span> 进行确认。', '如果节点有多个出站 IP 地址,请填写 <span class="bold-text">WORKER_IP</span>,以确保使用指定的 IP。可通过命令 <span class="bold-text">hostname -I | xargs -n1</span> 进行确认。',
'clusters.addworker.nvidiaNotes-02': 'clusters.addworker.nvidiaNotes-02':
'如果模型目录已存在于节点上,请添加 <span class="bold-text">--volume</span> 参数进行挂载。', '如果 worker 上已经存在模型目录,你可以指定该路径进行挂载。',
'clusters.addworker.hygonNotes': 'clusters.addworker.hygonNotes':
'如果 <span class="bold-text">/opt/hyhal</span> 目录不存在,请创建指向 Hygon 安装路径的符号链接:<span class="bold-text">/opt/hyhal</span>。与 <span class="bold-text">/opt/dtk</span> 目录相同。', '如果 <span class="bold-text">/opt/hyhal</span> 目录不存在,请创建指向 Hygon 安装路径的符号链接:<span class="bold-text">/opt/hyhal</span>。与 <span class="bold-text">/opt/dtk</span> 目录相同。',
'clusters.addworker.corexNotes': 'clusters.addworker.corexNotes':
@@ -71,6 +71,9 @@ export default {
'clusters.addworker.enterWorkerIP': '输入节点 IP', 'clusters.addworker.enterWorkerIP': '输入节点 IP',
'clusters.addworker.enterWorkerIP.error': '请输入节点 IP', 'clusters.addworker.enterWorkerIP.error': '请输入节点 IP',
'clusters.addworker.extraVolume': '额外卷挂载', 'clusters.addworker.extraVolume': '额外卷挂载',
'clusters.addworker.cacheVolume': '缓存卷挂载',
'clusters.addworker.cacheVolume.tips':
'如果你想自定义模型缓存目录,可以指定路径进行挂载。',
'clusters.addworker.configSummary': '配置摘要', 'clusters.addworker.configSummary': '配置摘要',
'clusters.addworker.gpuVendor': 'GPU 厂商', 'clusters.addworker.gpuVendor': 'GPU 厂商',
'clusters.addworker.workerIP': '节点 IP', 'clusters.addworker.workerIP': '节点 IP',
@@ -78,6 +81,8 @@ export default {
'clusters.addworker.autoDetect': '自动检测', 'clusters.addworker.autoDetect': '自动检测',
'clusters.addworker.extraVolume.holder': 'clusters.addworker.extraVolume.holder':
'例如:/data/models(路径需以 / 开头)', '例如:/data/models(路径需以 / 开头)',
'clusters.addworker.cacheVolume.holder':
'例如:/data/cache(路径需以 / 开头)',
'clusters.addworker.vendorNotes.title': '{vendor}设备注意事项', 'clusters.addworker.vendorNotes.title': '{vendor}设备注意事项',
'clusters.button.genToken': 'clusters.button.genToken':
'需要创建令牌?点击<a href="{link}" target="_blank">这里</a>。', '需要创建令牌?点击<a href="{link}" target="_blank">这里</a>。',
+5 -1
View File
@@ -316,8 +316,12 @@ const VersionsForm: React.FC<AddModalProps> = ({
> >
<SealInput.Input <SealInput.Input
trim trim
label={intl.formatMessage({ id: 'backend.imageName' })}
required required
placeholder={intl.formatMessage(
{ id: 'common.help.eg' },
{ content: 'vllm/vllm-openai:v0.12.0' }
)}
label={intl.formatMessage({ id: 'backend.imageName' })}
></SealInput.Input> ></SealInput.Input>
</Form.Item> </Form.Item>
</Box> </Box>
@@ -6,6 +6,7 @@ type ViewModalProps = {
currentGPU?: string; currentGPU?: string;
workerIP?: string; workerIP?: string;
modelDir?: string; modelDir?: string;
cacheDir?: string;
registrationInfo: { registrationInfo: {
token: string; token: string;
image: string; image: string;
@@ -17,6 +18,7 @@ const AddWorkerCommand: React.FC<ViewModalProps> = ({
registrationInfo, registrationInfo,
workerIP, workerIP,
modelDir, modelDir,
cacheDir,
currentGPU currentGPU
}) => { }) => {
const code = React.useMemo(() => { const code = React.useMemo(() => {
@@ -28,13 +30,14 @@ const AddWorkerCommand: React.FC<ViewModalProps> = ({
tag: '', tag: '',
workerIP: workerIP, workerIP: workerIP,
modelDir: modelDir, modelDir: modelDir,
cacheDir: cacheDir,
image: registrationInfo.image, image: registrationInfo.image,
token: registrationInfo.token || '${token}' token: registrationInfo.token || '${token}'
}) })
?.trim() ?.trim()
.replace(/\s+$/gm, '') .replace(/\s+$/gm, '')
.replace(/\\+$/, ''); .replace(/\\+$/, '');
}, [registrationInfo, currentGPU, workerIP, modelDir]); }, [registrationInfo, currentGPU, workerIP, modelDir, cacheDir]);
return ( return (
<HighlightCode <HighlightCode
@@ -42,6 +42,10 @@ export interface SummaryDataKeys {
enabled: boolean; enabled: boolean;
path: string; path: string;
}; };
cacheDirConfig: {
enabled: boolean;
path: string;
};
workerIPConfig: { workerIPConfig: {
enabled: boolean; enabled: boolean;
ip: string; ip: string;
@@ -21,6 +21,10 @@ const DockerRunCommand = () => {
path: '', path: '',
required: false required: false
}; };
const cacheDirConfig = summary.get('cacheDirConfig') || {
enable: false,
path: ''
};
const currentGPU = summary.get('currentGPU') || ''; const currentGPU = summary.get('currentGPU') || '';
const stepIndex = stepList.indexOf(StepNamesMap.RunCommand) + 1; const stepIndex = stepList.indexOf(StepNamesMap.RunCommand) + 1;
@@ -51,6 +55,7 @@ const DockerRunCommand = () => {
registrationInfo={registrationInfo} registrationInfo={registrationInfo}
workerIP={workerIPConfig.enable ? workerIPConfig.ip : ''} workerIP={workerIPConfig.enable ? workerIPConfig.ip : ''}
modelDir={modelDirConfig.enable ? modelDirConfig.path : ''} modelDir={modelDirConfig.enable ? modelDirConfig.path : ''}
cacheDir={cacheDirConfig.enable ? cacheDirConfig.path : ''}
currentGPU={currentGPU} currentGPU={currentGPU}
/> />
</StepCollapse> </StepCollapse>
@@ -2,12 +2,79 @@ import AlertInfoBlock from '@/components/alert-info/block';
import { ExclamationCircleFilled } from '@ant-design/icons'; import { ExclamationCircleFilled } from '@ant-design/icons';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import { Input, Switch } from 'antd'; 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 { useAddWorkerContext } from './add-worker-context';
import { StepNamesMap } from './config'; import { StepNamesMap } from './config';
import { NotesWrapper, SwitchWrapper, Tips, Title } from './constainers'; import { NotesWrapper, SwitchWrapper, Tips, Title } from './constainers';
import StepCollapse from './step-collapse'; 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 (
<SwitchWrapper>
<ButtonWrapper>
<span style={{ color: 'var(--ant-color-text)', fontWeight: 500 }}>
<span>{label}</span>
</span>
<Switch checked={checked} onChange={onChange}></Switch>
</ButtonWrapper>
{tips && (
<Tips
dangerouslySetInnerHTML={{
__html: tips
}}
></Tips>
)}
{checked && (
<>
<Input
style={{ width: '100%' }}
value={value}
placeholder={placeholder}
onChange={(e) => onInputChange?.(e.target.value)}
/>
{errorMessage && (
<Tips
style={{
color: 'var(--ant-color-error)'
}}
>
{errorMessage}
</Tips>
)}
</>
)}
{extra}
</SwitchWrapper>
);
};
const SpecifyArguments = () => { const SpecifyArguments = () => {
const intl = useIntl(); const intl = useIntl();
const { stepList, summary, updateField, registerField } = const { stepList, summary, updateField, registerField } =
@@ -24,6 +91,11 @@ const SpecifyArguments = () => {
path: '' path: ''
}; };
const cacheDirConfig = summary.get('cacheDirConfig') || {
enable: false,
path: ''
};
const setWorkerIPConfig = (config: { const setWorkerIPConfig = (config: {
enable: boolean; enable: boolean;
ip?: string; ip?: string;
@@ -42,6 +114,13 @@ const SpecifyArguments = () => {
}); });
}; };
const setCacheDirConfig = (config: { enable: boolean; path?: string }) => {
updateField('cacheDirConfig', {
...cacheDirConfig,
...config
});
};
const beforeNext = async () => { const beforeNext = async () => {
if (workerIPConfig.enable && !workerIPConfig.ip) { if (workerIPConfig.enable && !workerIPConfig.ip) {
setWorkerIPConfig({ setWorkerIPConfig({
@@ -56,16 +135,13 @@ const SpecifyArguments = () => {
}; };
useEffect(() => { useEffect(() => {
const unregister = registerField('workerIPConfig'); const unregisterWorkerIP = registerField('workerIPConfig');
const unregisterModelDir = registerField('modelDirConfig');
const unregisterCacheDir = registerField('cacheDirConfig');
return () => { return () => {
unregister(); unregisterWorkerIP();
}; unregisterModelDir();
}, []); unregisterCacheDir();
useEffect(() => {
const unregister = registerField('modelDirConfig');
return () => {
unregister();
}; };
}, []); }, []);
@@ -80,6 +156,11 @@ const SpecifyArguments = () => {
enable: false, enable: false,
path: '' path: ''
}); });
updateField('cacheDirConfig', {
enable: false,
path: ''
});
}, []); }, []);
return ( return (
@@ -102,123 +183,111 @@ const SpecifyArguments = () => {
}} }}
> >
{/* worker IP config */} {/* worker IP config */}
<SwitchWrapper> <SwitchSetting
<div className="button"> label={
<span style={{ color: 'var(--ant-color-text)', fontWeight: 500 }}> workerIPConfig.enable
{workerIPConfig.enable ? intl.formatMessage({
? intl.formatMessage({ id: 'clusters.addworker.specifyWorkerIP'
id: 'clusters.addworker.specifyWorkerIP'
})
: intl.formatMessage({
id: 'clusters.addworker.detectWorkerIP'
})}
</span>
<Switch
checked={workerIPConfig.enable}
onChange={(checked) =>
setWorkerIPConfig({
...workerIPConfig,
enable: checked,
required: false
}) })
} : intl.formatMessage({
></Switch> id: 'clusters.addworker.detectWorkerIP'
</div> })
{workerIPConfig.enable && ( }
<> placeholder={intl.formatMessage({
<Input id: 'clusters.addworker.enterWorkerIP'
style={{ width: '100%' }} })}
placeholder={intl.formatMessage({ value={workerIPConfig.ip}
id: 'clusters.addworker.enterWorkerIP' checked={workerIPConfig.enable}
})} errorMessage={
value={workerIPConfig.ip} workerIPConfig.required &&
onChange={(e) => !workerIPConfig.ip &&
setWorkerIPConfig({ intl.formatMessage({
...workerIPConfig, id: 'clusters.addworker.enterWorkerIP.error'
ip: e.target.value })
}) }
onChange={(checked) =>
setWorkerIPConfig({
...workerIPConfig,
enable: checked,
required: false
})
}
onInputChange={(value) =>
setWorkerIPConfig({
...workerIPConfig,
ip: value
})
}
extra={
!workerIPConfig.enable && (
<AlertInfoBlock
maxHeight={200}
contentStyle={{
paddingLeft: 0
}}
type="warning"
icon={<ExclamationCircleFilled />}
message={
<NotesWrapper>
<li
style={{
marginLeft: '0 !important',
listStyleType: 'none'
}}
dangerouslySetInnerHTML={{
__html: intl.formatMessage({
id: 'clusters.addworker.nvidiaNotes-01'
})
}}
></li>
</NotesWrapper>
} }
/> ></AlertInfoBlock>
{workerIPConfig.required && !workerIPConfig.ip && ( )
<Tips }
style={{ ></SwitchSetting>
color: 'var(--ant-color-error)'
}}
>
{intl.formatMessage({
id: 'clusters.addworker.enterWorkerIP.error'
})}
</Tips>
)}
</>
)}
{!workerIPConfig.enable && (
<AlertInfoBlock
maxHeight={200}
contentStyle={{
paddingLeft: 0
}}
style={{ marginBottom: 8 }}
type="warning"
icon={<ExclamationCircleFilled />}
message={
<NotesWrapper>
<li
style={{
marginLeft: '0 !important',
listStyleType: 'none'
}}
dangerouslySetInnerHTML={{
__html: intl.formatMessage({
id: 'clusters.addworker.nvidiaNotes-01'
})
}}
></li>
</NotesWrapper>
}
></AlertInfoBlock>
)}
</SwitchWrapper>
{/* model directory config */} {/* model directory config */}
<SwitchWrapper> <SwitchSetting
<div className="button"> label={intl.formatMessage({ id: 'clusters.addworker.extraVolume' })}
<span style={{ color: 'var(--ant-color-text)', fontWeight: 500 }}> tips={intl.formatMessage({
{/* optional */} id: 'clusters.addworker.nvidiaNotes-02'
<span> })}
{intl.formatMessage({ id: 'clusters.addworker.extraVolume' })} placeholder={intl.formatMessage({
</span> id: 'clusters.addworker.extraVolume.holder'
</span> })}
checked={modelDirConfig.enable}
<Switch value={modelDirConfig.path}
checked={modelDirConfig.enable} onChange={(checked) =>
onChange={(checked) => setModelDirConfig({ ...modelDirConfig, enable: checked })
setModelDirConfig({ ...modelDirConfig, enable: checked }) }
} onInputChange={(value) =>
></Switch> setModelDirConfig({
</div> ...modelDirConfig,
<Tips path: value
dangerouslySetInnerHTML={{ })
__html: intl.formatMessage({ }
id: 'clusters.addworker.nvidiaNotes-02' ></SwitchSetting>
}) {/* cache directory config */}
}} <SwitchSetting
></Tips> label={intl.formatMessage({ id: 'clusters.addworker.cacheVolume' })}
{modelDirConfig.enable && ( tips={intl.formatMessage({
<Input id: 'clusters.addworker.cacheVolume.tips'
style={{ width: '100%' }} })}
value={modelDirConfig.path} placeholder={intl.formatMessage({
placeholder={intl.formatMessage({ id: 'clusters.addworker.cacheVolume.holder'
id: 'clusters.addworker.extraVolume.holder' })}
})} checked={cacheDirConfig.enable}
onChange={(e) => value={cacheDirConfig.path}
setModelDirConfig({ onChange={(checked) =>
...modelDirConfig, setCacheDirConfig({ ...cacheDirConfig, enable: checked })
path: e.target.value }
}) onInputChange={(value) =>
} setCacheDirConfig({
/> ...cacheDirConfig,
)} path: value
</SwitchWrapper> })
}
></SwitchSetting>
</div> </div>
</StepCollapse> </StepCollapse>
); );
@@ -27,6 +27,11 @@ const SummaryData: React.FC = () => {
path: '' path: ''
}; };
const cacheDirConfig = summary.get('cacheDirConfig') || {
enable: false,
path: ''
};
return ( return (
<ConfigWrapper> <ConfigWrapper>
<Title> <Title>
@@ -129,6 +134,32 @@ const SummaryData: React.FC = () => {
)} )}
</span> </span>
</div> </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> </div>
</ConfigWrapper> </ConfigWrapper>
); );
@@ -36,7 +36,7 @@ const AccessControlModal: React.FC<
message.success(intl.formatMessage({ id: 'common.message.success' })); message.success(intl.formatMessage({ id: 'common.message.success' }));
onCancel?.(); onCancel?.();
} catch (error) { } catch (error) {
message.error(intl.formatMessage({ id: 'common.message.failed' })); // do nothing, error message handled in request layer
} }
}; };
+5 -12
View File
@@ -6,7 +6,7 @@ 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 { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import useMemoizedFn from 'ahooks/lib/useMemoizedFn'; import useMemoizedFn from 'ahooks/lib/useMemoizedFn';
import { Form, Segmented } from 'antd'; import { Form } from 'antd';
import _ from 'lodash'; import _ from 'lodash';
import React, { forwardRef, useImperativeHandle, useMemo } from 'react'; import React, { forwardRef, useImperativeHandle, useMemo } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
@@ -42,14 +42,6 @@ const scheduleRequiredFields = ['gpu_selector'];
const performanceRequiredFields = ['speculative_config']; const performanceRequiredFields = ['speculative_config'];
const SegmentedInner = styled(Segmented)`
width: 100%;
border-radius: 0;
.ant-segmented-item {
flex: 1;
}
`;
const SegmentedHeader = styled.div<{ $top?: number }>` const SegmentedHeader = styled.div<{ $top?: number }>`
position: sticky; position: sticky;
top: ${(props) => props.$top || 0}px; top: ${(props) => props.$top || 0}px;
@@ -260,9 +252,10 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
await onClusterChange?.(value); await onClusterChange?.(value);
getGPUOptionList({ clusterId: value }); getGPUOptionList({ clusterId: value });
getBackendOptions({ cluster_id: value }); getBackendOptions({ cluster_id: value });
if (scheduleType === ScheduleValueMap.Manual) { form.setFieldsValue({
form.setFieldValue(['gpu_selector', 'gpu_ids'], []); scheduleType: ScheduleValueMap.Auto,
} gpu_selector: null
});
await new Promise((resolve) => { await new Promise((resolve) => {
setTimeout(resolve, 150); setTimeout(resolve, 150);
}); });
+2 -1
View File
@@ -174,6 +174,7 @@ export const useCheckCompatibility = () => {
const handleEvaluate = async (data: any) => { const handleEvaluate = async (data: any) => {
try { try {
// when no cluster selected, show warning and prompt user to add cluster first // when no cluster selected, show warning and prompt user to add cluster first
console.log('handleEvaluate', data);
if (!data.cluster_id) { if (!data.cluster_id) {
setWarningStatus({ setWarningStatus({
show: true, show: true,
@@ -388,7 +389,7 @@ export const useCheckCompatibility = () => {
const handleDoEvalute = async (formData: FormData) => { const handleDoEvalute = async (formData: FormData) => {
const currentRequestId = updateRequestId(); const currentRequestId = updateRequestId();
const evalutionData = await handleEvaluate(formData); const evalutionData = await handleEvaluate(formData);
if (currentRequestId === requestIdRef.current) { if (currentRequestId === requestIdRef.current && evalutionData) {
handleShowCompatibleAlert?.(evalutionData); handleShowCompatibleAlert?.(evalutionData);
return evalutionData; return evalutionData;
} }
+3 -1
View File
@@ -127,13 +127,15 @@ export const dockerEnvCommandMap = {
) )
}; };
const setNormalArgs = (params: any) => { const setNormalArgs = (params: any) => {
console.log('params++++++++++++++', params);
return `sudo docker run -d --name gpustack-worker \\ return `sudo docker run -d --name gpustack-worker \\
--restart=unless-stopped \\ --restart=unless-stopped \\
--privileged \\ --privileged \\
--network=host \\ --network=host \\
--volume /var/run/docker.sock:/var/run/docker.sock \\ --volume /var/run/docker.sock:/var/run/docker.sock \\
--volume gpustack-data:/var/lib/gpustack \\ --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) => { const setImageArgs = (params: any) => {