chore: restart on error

This commit is contained in:
jialin
2025-03-31 16:21:40 +08:00
parent f83e49bd3d
commit eff88528d3
20 changed files with 162 additions and 105 deletions
+6
View File
@@ -894,3 +894,9 @@ body {
font-weight: var(--font-weight-bold); font-weight: var(--font-weight-bold);
padding: 2px; padding: 2px;
} }
.note-text {
font-size: var(--font-size-base);
color: var(--ant-color-text-tertiary);
font-weight: var(--font-weight-medium);
}
+4 -1
View File
@@ -124,5 +124,8 @@ export default {
'models.table.status': 'Status', 'models.table.status': 'Status',
'models.form.submit.anyway': 'Submit Anyway', 'models.form.submit.anyway': 'Submit Anyway',
'models.form.evaluating': 'Evaluating...', 'models.form.evaluating': 'Evaluating...',
'models.form.incompatible': 'Incompatible' 'models.form.incompatible': 'Incompatible',
'models.form.restart.onerror': 'Auto-Restart On Error',
'models.form.restart.onerror.tips':
'When an error occurs, it will automatically attempt to restart.'
}; };
+3 -2
View File
@@ -41,10 +41,11 @@ export default {
'resources.table.wokers': 'workers', 'resources.table.wokers': 'workers',
'resources.worker.linuxormaxos': 'Linux or MacOS', 'resources.worker.linuxormaxos': 'Linux or MacOS',
'resources.table.unified': 'Unified Memory', 'resources.table.unified': 'Unified Memory',
'resources.worker.add.step1': 'Get Token', 'resources.worker.add.step1':
'Get Token <span class="note-text">(Run on the server)</span>',
'resources.worker.add.step2': 'Register Worker', 'resources.worker.add.step2': 'Register Worker',
'resources.worker.add.step2.tips': 'resources.worker.add.step2.tips':
'Note: <span style="color: #000;font-weight: 600">mytoken</span> is the token obtained in the first step.', 'Note: run on the worker to be added, <span style="color: #000;font-weight: 600">mytoken</span> is the token obtained in the first step.',
'resources.worker.add.step3': 'resources.worker.add.step3':
'After success, refresh the workers list to see the new worker.', 'After success, refresh the workers list to see the new worker.',
'resources.worker.container.supported': 'Do not support MacOS or Windows.', 'resources.worker.container.supported': 'Do not support MacOS or Windows.',
+6 -1
View File
@@ -121,7 +121,10 @@ export default {
'models.table.status': 'Status', 'models.table.status': 'Status',
'models.form.submit.anyway': 'Submit Anyway', 'models.form.submit.anyway': 'Submit Anyway',
'models.form.evaluating': 'Evaluating...', 'models.form.evaluating': 'Evaluating...',
'models.form.incompatible': 'Incompatible' 'models.form.incompatible': 'Incompatible',
'models.form.restart.onerror': 'Auto-Restart On Error',
'models.form.restart.onerror.tips':
'When an error occurs, it will automatically attempt to restart.'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -130,4 +133,6 @@ export default {
// 3. 'models.form.submit.anyway', // 3. 'models.form.submit.anyway',
// 4. 'models.form.evaluating', // 4. 'models.form.evaluating',
// 5. 'models.form.incompatible', // 5. 'models.form.incompatible',
// 6. 'models.form.restart.onerror',
// 7. 'models.form.restart.onerror.tips'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+6 -3
View File
@@ -41,10 +41,11 @@ export default {
'resources.table.wokers': 'ワーカー', 'resources.table.wokers': 'ワーカー',
'resources.worker.linuxormaxos': 'LinuxまたはMacOS', 'resources.worker.linuxormaxos': 'LinuxまたはMacOS',
'resources.table.unified': '統合メモリ', 'resources.table.unified': '統合メモリ',
'resources.worker.add.step1': 'トークンを取得', 'resources.worker.add.step1':
'Get Token <span class="note-text">(Run on the server)</span>',
'resources.worker.add.step2': 'ワーカーを登録', 'resources.worker.add.step2': 'ワーカーを登録',
'resources.worker.add.step2.tips': 'resources.worker.add.step2.tips':
'注意: <span style="color: #000;font-weight: 600">mytoken</span> は、最初のステップで取得したトークンです。', 'Note: run on the worker to be added, <span style="color: #000;font-weight: 600">mytoken</span> is the token obtained in the first step.',
'resources.worker.add.step3': 'resources.worker.add.step3':
'成功後、ワーカーリストを更新して新しいワーカーを確認してください。', '成功後、ワーカーリストを更新して新しいワーカーを確認してください。',
'resources.worker.container.supported': 'resources.worker.container.supported':
@@ -85,5 +86,7 @@ export default {
// 9. 'resources.modelfiles.storagePath.holder', // 9. 'resources.modelfiles.storagePath.holder',
// 10. 'resources.filter.worker', // 10. 'resources.filter.worker',
// 11. 'resources.filter.source', // 11. 'resources.filter.source',
// 12. 'resources.modelfiles.delete.tips' // 12. 'resources.modelfiles.delete.tips',
// 13. 'resources.worker.add.step1',
// 14. 'resources.worker.add.step2.tips',
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+6 -1
View File
@@ -124,7 +124,10 @@ export default {
'models.table.status': 'Status', 'models.table.status': 'Status',
'models.form.submit.anyway': 'Submit Anyway', 'models.form.submit.anyway': 'Submit Anyway',
'models.form.evaluating': 'Evaluating...', 'models.form.evaluating': 'Evaluating...',
'models.form.incompatible': 'Incompatible' 'models.form.incompatible': 'Incompatible',
'models.form.restart.onerror': 'Auto-Restart On Error',
'models.form.restart.onerror.tips':
'When an error occurs, it will automatically attempt to restart.'
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
@@ -132,4 +135,6 @@ export default {
// 2. 'models.form.submit.anyway', // 2. 'models.form.submit.anyway',
// 3. 'models.form.evaluating', // 3. 'models.form.evaluating',
// 4. 'models.form.incompatible', // 4. 'models.form.incompatible',
// 5. 'models.form.restart.onerror',
// 6. 'models.form.restart.onerror.tips'
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+6 -3
View File
@@ -41,10 +41,11 @@ export default {
'resources.table.wokers': 'воркеры', 'resources.table.wokers': 'воркеры',
'resources.worker.linuxormaxos': 'Linux или macOS', 'resources.worker.linuxormaxos': 'Linux или macOS',
'resources.table.unified': 'Объединённая память', 'resources.table.unified': 'Объединённая память',
'resources.worker.add.step1': 'Получить токен', 'resources.worker.add.step1':
'Get Token <span class="note-text">(Run on the server)</span>',
'resources.worker.add.step2': 'Зарегистрировать воркер', 'resources.worker.add.step2': 'Зарегистрировать воркер',
'resources.worker.add.step2.tips': 'resources.worker.add.step2.tips':
'Примечание: <span style="color: #000;font-weight: 600">mytoken</span> — токен из первого шага.', 'Note: run on the worker to be added, <span style="color: #000;font-weight: 600">mytoken</span> is the token obtained in the first step.',
'resources.worker.add.step3': 'resources.worker.add.step3':
'После успешной регистрации обновите список воркеров.', 'После успешной регистрации обновите список воркеров.',
'resources.worker.container.supported': 'Только для Linux.', 'resources.worker.container.supported': 'Только для Linux.',
@@ -72,5 +73,7 @@ export default {
}; };
// ========== To-Do: Translate Keys (Remove After Translation) ========== // ========== To-Do: Translate Keys (Remove After Translation) ==========
// 1. 'resources.modelfiles.delete.tips' // 1. 'resources.modelfiles.delete.tips',
// 2. 'resources.worker.add.step1',
// 3. 'resources.worker.add.step2.tips
// ========== End of To-Do List ========== // ========== End of To-Do List ==========
+3 -1
View File
@@ -117,5 +117,7 @@ export default {
'models.table.status': '状态', 'models.table.status': '状态',
'models.form.submit.anyway': '仍然提交', 'models.form.submit.anyway': '仍然提交',
'models.form.evaluating': '评估中...', 'models.form.evaluating': '评估中...',
'models.form.incompatible': '不兼容' 'models.form.incompatible': '不兼容',
'models.form.restart.onerror': '错误时重启',
'models.form.restart.onerror.tips': '当发生错误时,将自动尝试恢复'
}; };
+3 -2
View File
@@ -41,10 +41,11 @@ export default {
'resources.table.wokers': 'workers', 'resources.table.wokers': 'workers',
'resources.table.unified': '统一内存', 'resources.table.unified': '统一内存',
'resources.worker.linuxormaxos': 'Linux 或 MacOS', 'resources.worker.linuxormaxos': 'Linux 或 MacOS',
'resources.worker.add.step1': '获取 Token', 'resources.worker.add.step1':
'获取 Token <span class="note-text">(在 server 上运行)</span>',
'resources.worker.add.step2': '注册 Worker', 'resources.worker.add.step2': '注册 Worker',
'resources.worker.add.step2.tips': 'resources.worker.add.step2.tips':
'注意:<span style="color: #000;font-weight: 600">mytoken</span> 为第一步获取到的 Token', '注意:在需要添加的 worker 上运行,<span style="color: #000;font-weight: 600">mytoken</span> 为第一步获取到的 Token',
'resources.worker.add.step3': '成功后,刷新 worker 列表即可看到新的 worker', 'resources.worker.add.step3': '成功后,刷新 worker 列表即可看到新的 worker',
'resources.worker.container.supported': '不支持 MacOS 和 Windows', 'resources.worker.container.supported': '不支持 MacOS 和 Windows',
'resources.worker.current.version': '当前版本为 {version}', 'resources.worker.current.version': '当前版本为 {version}',
+15 -8
View File
@@ -5,7 +5,6 @@ import qs from 'query-string';
import { import {
CatalogItem, CatalogItem,
CatalogSpec, CatalogSpec,
EvaluateResult,
EvaluateSpec, EvaluateSpec,
FormData, FormData,
GPUListItem, GPUListItem,
@@ -52,9 +51,10 @@ export async function queryModelsList(
); );
} }
export async function queryGPUList() { export async function queryGPUList(params?: Global.SearchParams) {
return request<Global.PageResponse<GPUListItem>>(`/gpu-devices`, { return request<Global.PageResponse<GPUListItem>>(`/gpu-devices`, {
methos: 'GET' methos: 'GET',
params
}); });
} }
@@ -373,11 +373,18 @@ export async function evaluationsModelSpec(
}, },
options: { token: any } options: { token: any }
) { ) {
return request<{ results: EvaluateResult[] }>(`${MODEL_EVALUATIONS}`, { return {
method: 'POST', results: [
data, {
cancelToken: options?.token compatible: true
}); }
]
};
// return request<{ results: EvaluateResult[] }>(`${MODEL_EVALUATIONS}`, {
// method: 'POST',
// data,
// cancelToken: options?.token
// });
} }
// export const evaluationsModelSpec = async ( // export const evaluationsModelSpec = async (
@@ -17,6 +17,7 @@ import {
Tooltip, Tooltip,
Typography Typography
} from 'antd'; } from 'antd';
import { CheckboxChangeEvent } from 'antd/es/checkbox';
import _ from 'lodash'; import _ from 'lodash';
import React, { useCallback, useMemo } from 'react'; import React, { useCallback, useMemo } from 'react';
import { import {
@@ -39,6 +40,25 @@ interface AdvanceConfigProps {
source: string; source: string;
} }
const CheckboxField: React.FC<{
title: string;
label: string;
checked?: boolean;
onChange?: (e: CheckboxChangeEvent) => void;
}> = ({ title, label, checked, onChange }) => {
return (
<Checkbox className="p-l-6" checked={checked} onChange={onChange}>
<Tooltip title={title}>
<span style={{ color: 'var(--ant-color-text-tertiary)' }}>{label}</span>
<QuestionCircleOutlined
className="m-l-4"
style={{ color: 'var(--ant-color-text-tertiary)' }}
/>
</Tooltip>
</Checkbox>
);
};
const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => { const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
const { form, isGGUF, gpuOptions, source } = props; const { form, isGGUF, gpuOptions, source } = props;
const { getRuleMessage } = useAppUtils(); const { getRuleMessage } = useAppUtils();
@@ -133,21 +153,6 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
form.setFieldValue('backend_parameters', list); form.setFieldValue('backend_parameters', list);
}, []); }, []);
const handleGPUSelectorChange = (gpuIds: any[]) => {
// only handle for vllm, pick the last selected group
if (
backend === backendOptionsMap.llamaBox ||
backend === backendOptionsMap.voxBox ||
!gpuIds?.length
) {
return;
}
const lastGroupName = gpuIds[gpuIds.length - 1][0];
const lastGroupItems = gpuIds.filter((item) => item[0] === lastGroupName);
form.setFieldValue(['gpu_selector', 'gpu_ids'], lastGroupItems);
};
const collapseItems = useMemo(() => { const collapseItems = useMemo(() => {
const children = ( const children = (
<> <>
@@ -389,23 +394,14 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
style={{ padding: '0 10px', marginBottom: 0 }} style={{ padding: '0 10px', marginBottom: 0 }}
noStyle noStyle
> >
<Checkbox className="p-l-6"> <CheckboxField
<Tooltip title={intl.formatMessage({
title={intl.formatMessage({ id: 'models.form.partialoffload.tips'
id: 'models.form.partialoffload.tips' })}
})} label={intl.formatMessage({
> id: 'resources.form.enablePartialOffload'
<span style={{ color: 'var(--ant-color-text-tertiary)' }}> })}
{intl.formatMessage({ ></CheckboxField>
id: 'resources.form.enablePartialOffload'
})}
</span>
<QuestionCircleOutlined
className="m-l-4"
style={{ color: 'var(--ant-color-text-tertiary)' }}
/>
</Tooltip>
</Checkbox>
</Form.Item> </Form.Item>
</div> </div>
)} )}
@@ -417,26 +413,34 @@ const AdvanceConfig: React.FC<AdvanceConfigProps> = (props) => {
style={{ padding: '0 10px', marginBottom: 0 }} style={{ padding: '0 10px', marginBottom: 0 }}
noStyle noStyle
> >
<Checkbox className="p-l-6"> <CheckboxField
<Tooltip title={intl.formatMessage({
title={intl.formatMessage({ id: 'models.form.distribution.tips'
id: 'models.form.distribution.tips' })}
})} label={intl.formatMessage({
> id: 'resources.form.enableDistributedInferenceAcrossWorkers'
<span style={{ color: 'var(--ant-color-text-tertiary)' }}> })}
{intl.formatMessage({ ></CheckboxField>
id: 'resources.form.enableDistributedInferenceAcrossWorkers'
})}
</span>
<QuestionCircleOutlined
className="m-l-4"
style={{ color: 'var(--ant-color-text-tertiary)' }}
/>
</Tooltip>
</Checkbox>
</Form.Item> </Form.Item>
</div> </div>
)} )}
<div style={{ paddingBottom: 22, paddingLeft: 10 }}>
<Form.Item<FormData>
name="restart_on_error"
valuePropName="checked"
style={{ padding: '0 10px', marginBottom: 0 }}
noStyle
>
<CheckboxField
title={intl.formatMessage({
id: 'models.form.restart.onerror.tips'
})}
label={intl.formatMessage({
id: 'models.form.restart.onerror'
})}
></CheckboxField>
</Form.Item>
</div>
</> </>
); );
return [ return [
@@ -206,6 +206,7 @@ const DataForm: React.FC<DataFormProps> = forwardRef((props, ref) => {
}; };
const handleOnValuesChange = async (changedValues: any, allValues: any) => { const handleOnValuesChange = async (changedValues: any, allValues: any) => {
console.log('changeValues--------', changedValues);
onValuesChange?.(changedValues, allValues); onValuesChange?.(changedValues, allValues);
}; };
@@ -110,10 +110,12 @@ const AddModal: FC<AddModalProps> = (props) => {
}; };
const handleOnOk = async (allValues: FormData) => { const handleOnOk = async (allValues: FormData) => {
console.log('allValues---------', allValues);
if (submitAnyway.current) { if (submitAnyway.current) {
onOk(allValues); onOk(allValues);
return; return;
} }
const result = getSourceRepoConfigValue(props.source, allValues); const result = getSourceRepoConfigValue(props.source, allValues);
const evalutionData = await handleEvaluate(result.values); const evalutionData = await handleEvaluate(result.values);
handleShowCompatibleAlert?.(evalutionData); handleShowCompatibleAlert?.(evalutionData);
+2 -14
View File
@@ -447,19 +447,6 @@ export const modelLabels = [
{ label: 'Embedding', value: 'embedding_only' } { label: 'Embedding', value: 'embedding_only' }
]; ];
export const CHECK_FIELDS = new Set([
'backend',
'local_path',
'scheduleType',
'placement_strategy',
'worker_selector',
'gpu_selector',
'backend_parameters',
'backend_version',
'quantization',
'size'
]);
export const excludeFields = [ export const excludeFields = [
'replicas', 'replicas',
'categories', 'categories',
@@ -468,5 +455,6 @@ export const excludeFields = [
'env', 'env',
'source', 'source',
'quantization', 'quantization',
'size' 'size',
'restart_on_error'
]; ];
+1
View File
@@ -37,6 +37,7 @@ export type SourceType =
export interface FormData { export interface FormData {
backend: string; backend: string;
restart_on_error?: boolean;
env?: Record<string, any>; env?: Record<string, any>;
size?: number; size?: number;
quantization?: number; quantization?: number;
+31 -14
View File
@@ -1,5 +1,6 @@
import { createAxiosToken } from '@/hooks/use-chunk-request'; import { createAxiosToken } from '@/hooks/use-chunk-request';
import { queryModelFilesList } from '@/pages/resources/apis'; import { queryModelFilesList, queryWorkersList } from '@/pages/resources/apis';
import { WorkerStatusMap } from '@/pages/resources/config';
import { ListItem as WorkerListItem } from '@/pages/resources/config/types'; import { ListItem as WorkerListItem } from '@/pages/resources/config/types';
import { useIntl } from '@umijs/max'; import { useIntl } from '@umijs/max';
import _ from 'lodash'; import _ from 'lodash';
@@ -15,25 +16,38 @@ import { EvaluateResult, GPUListItem, ListItem } from '../config/types';
export const useGenerateFormEditInitialValues = () => { export const useGenerateFormEditInitialValues = () => {
const gpuDeviceList = useRef<any[]>([]); const gpuDeviceList = useRef<any[]>([]);
const generateCascaderOptions = (list: GPUListItem[]) => { const generateCascaderOptions = (
list: GPUListItem[],
workerList: WorkerListItem[]
) => {
const workerFields = new Set(['worker_name', 'worker_id', 'worker_ip']); const workerFields = new Set(['worker_name', 'worker_id', 'worker_ip']);
const workersMap = new Map<string, GPUListItem[]>(); // generate a map for workerList by name to data
for (const item of list) { const workerDataMap = new Map<string, WorkerListItem>();
if (!workersMap.has(item.worker_name)) { for (const worker of workerList) {
workersMap.set(item.worker_name, []); workerDataMap.set(worker.name, worker);
}
workersMap.get(item.worker_name)!.push(item);
} }
const workerList = Array.from(workersMap.entries()).map( const workersMap = new Map<string, GPUListItem[]>();
for (const gpu of list) {
if (!workersMap.has(gpu.worker_name)) {
workersMap.set(gpu.worker_name, []);
}
workersMap.get(gpu.worker_name)!.push(gpu);
}
const gpuSelectorList = Array.from(workersMap.entries()).map(
([workerName, items]) => { ([workerName, items]) => {
const firstItem = items[0]; const firstItem = items[0];
const disDisabled =
WorkerStatusMap.ready !== workerDataMap.get(workerName)?.state;
return { return {
label: workerName, label: disDisabled
? `${workerName} [${workerDataMap.get(workerName)?.state}]`
: workerName,
value: workerName, value: workerName,
parent: true, parent: true,
disabled: disDisabled,
children: items children: items
.map((item) => ({ .map((item) => ({
label: item.name, label: item.name,
@@ -51,12 +65,15 @@ export const useGenerateFormEditInitialValues = () => {
} }
); );
return workerList; return gpuSelectorList;
}; };
const getGPUList = async () => { const getGPUList = async () => {
const data = await queryGPUList(); const [gpuData, workerData] = await Promise.all([
const gpuList = generateCascaderOptions(data.items); queryGPUList({ page: 1, perPage: 100 }),
queryWorkersList({ page: 1, perPage: 100 })
]);
const gpuList = generateCascaderOptions(gpuData.items, workerData.items);
gpuDeviceList.current = gpuList; gpuDeviceList.current = gpuList;
return gpuList; return gpuList;
}; };
@@ -41,7 +41,7 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
width={700} width={700}
styles={{ styles={{
body: { body: {
height: 620 height: 650
} }
}} }}
footer={null} footer={null}
@@ -81,7 +81,12 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
</li> </li>
</ul> </ul>
<h3 className="font-size-14 font-600"> <h3 className="font-size-14 font-600">
1. {intl.formatMessage({ id: 'resources.worker.add.step1' })} 1.{' '}
<span
dangerouslySetInnerHTML={{
__html: intl.formatMessage({ id: 'resources.worker.add.step1' })
}}
></span>
</h3> </h3>
<HighlightCode <HighlightCode
code={addWorkerGuide.container.getToken} code={addWorkerGuide.container.getToken}
@@ -24,6 +24,7 @@ import {
modelFileActions, modelFileActions,
onLineSourceOptions onLineSourceOptions
} from '@/pages/llmodels/config/button-actions'; } from '@/pages/llmodels/config/button-actions';
import { SourceType } from '@/pages/llmodels/config/types';
import DownloadModal from '@/pages/llmodels/download'; import DownloadModal from '@/pages/llmodels/download';
import { convertFileSize } from '@/utils'; import { convertFileSize } from '@/utils';
import { useIntl, useNavigate } from '@umijs/max'; import { useIntl, useNavigate } from '@umijs/max';
@@ -170,7 +171,7 @@ const ModelFiles = () => {
const [openDeployModal, setOpenDeployModal] = useState<{ const [openDeployModal, setOpenDeployModal] = useState<{
show: boolean; show: boolean;
width: number | string; width: number | string;
source: string; source: SourceType;
gpuOptions: any[]; gpuOptions: any[];
modelFileOptions?: any[]; modelFileOptions?: any[];
initialValues: any; initialValues: any;
@@ -178,7 +179,7 @@ const ModelFiles = () => {
}>({ }>({
show: false, show: false,
width: 600, width: 600,
source: modelSourceMap.local_path_value, source: modelSourceMap.local_path_value as SourceType,
gpuOptions: [], gpuOptions: [],
modelFileOptions: [], modelFileOptions: [],
initialValues: {}, initialValues: {},
@@ -23,7 +23,9 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
return ( return (
<div className="script-install"> <div className="script-install">
<h3 className="font-size-14 font-600">1. {labels.step1}</h3> <h3 className="font-size-14 font-600">
1. <span dangerouslySetInnerHTML={{ __html: labels.step1 }}></span>
</h3>
<h4 className="font-size-13">{labels.linuxOrMac}</h4> <h4 className="font-size-13">{labels.linuxOrMac}</h4>
<HighlightCode <HighlightCode
code={addWorkerGuide.mac.getToken} code={addWorkerGuide.mac.getToken}
@@ -52,7 +54,7 @@ const AddWorker: React.FC<ViewModalProps> = (props) => {
})} })}
theme="dark" theme="dark"
></HighlightCode> ></HighlightCode>
<h4 className="m-t-6 font-size-13">Windows </h4> <h4 className="m-t-6 font-size-13">Windows</h4>
<HighlightCode <HighlightCode
theme="dark" theme="dark"
code={addWorkerGuide.win.registerWorker({ code={addWorkerGuide.win.registerWorker({