diff --git a/src/pages/llmodels/apis/index.ts b/src/pages/llmodels/apis/index.ts index 14d7cec7..e498dab2 100644 --- a/src/pages/llmodels/apis/index.ts +++ b/src/pages/llmodels/apis/index.ts @@ -3,6 +3,7 @@ import { PipelineType } from '@huggingface/tasks'; import { request } from '@umijs/max'; import { FormData, + GPUListItem, ListItem, ModelInstanceFormData, ModelInstanceListItem @@ -24,6 +25,12 @@ export async function queryModelsList( }); } +export async function queryGPUList() { + return request>(`/gpu-devices`, { + methos: 'GET' + }); +} + export async function createModel(params: { data: FormData }) { return request(`${MODELS_API}`, { method: 'POST', diff --git a/src/pages/llmodels/components/advance-config.tsx b/src/pages/llmodels/components/advance-config.tsx new file mode 100644 index 00000000..f4ce7f45 --- /dev/null +++ b/src/pages/llmodels/components/advance-config.tsx @@ -0,0 +1,262 @@ +import LabelSelector from '@/components/label-selector'; +import SealSelect from '@/components/seal-form/seal-select'; +import { InfoCircleOutlined, RightOutlined } from '@ant-design/icons'; +import { useIntl } from '@umijs/max'; +import { + Checkbox, + Collapse, + Form, + FormInstance, + Select, + Tooltip, + Typography +} from 'antd'; +import React, { useCallback, useMemo } from 'react'; +import { placementStrategyOptions } from '../config'; +import { FormData } from '../config/types'; +import dataformStyles from '../style/data-form.less'; +import GPUCard from './gpu-card'; + +interface AdvanceConfigProps { + form: FormInstance; + gpuOptions: Array; +} + +const AdvanceConfig: React.FC = (props) => { + const { form, gpuOptions } = props; + + const intl = useIntl(); + const wokerSelector = Form.useWatch('worker_selector', form); + const scheduleType = Form.useWatch('scheduleType', form); + + const placementStrategyTips = [ + { + title: 'Spread', + tips: intl.formatMessage({ + id: 'resources.form.spread.tips' + }) + }, + { + title: 'Binpack', + tips: intl.formatMessage({ + id: 'resources.form.binpack.tips' + }) + } + ]; + + const scheduleTypeTips = [ + { + title: intl.formatMessage({ + id: 'models.form.scheduletype.auto' + }), + tips: intl.formatMessage({ + id: 'models.form.scheduletype.auto.tips' + }) + }, + { + title: intl.formatMessage({ + id: 'models.form.scheduletype.manual' + }), + tips: intl.formatMessage({ + id: 'models.form.scheduletype.manual.tips' + }) + } + ]; + + const renderSelectTips = (list: Array<{ title: string; tips: string }>) => { + return ( +
+ {list.map((item, index) => { + return ( +
+ + {item.title}: + + + {item.tips} + +
+ ); + })} +
+ ); + }; + + const handleWorkerLabelsChange = useCallback( + (labels: Record) => { + form.setFieldValue('worker_selector', labels); + }, + [] + ); + + const collapseItems = useMemo(() => { + const children = ( + <> + + + + {scheduleType === 'auto' && ( + name="placement_strategy"> + + + )} + {scheduleType === 'auto' && ( + name="worker_selector"> + + {intl.formatMessage({ + id: 'resources.form.workerSelector.description' + })} + + } + > + + )} + {scheduleType === 'manual' && ( + + name="gpu_selector" + rules={[ + { + required: true, + message: intl.formatMessage( + { + id: 'common.form.rule.select' + }, + { + name: 'gpu_selector' + } + ) + } + ]} + > + + {gpuOptions.map((item) => ( + + + + ))} + + + )} +
+ + name="partial_offload" + valuePropName="checked" + style={{ padding: '0 10px', marginBottom: 0 }} + noStyle + > + + + + {intl.formatMessage({ + id: 'resources.form.enablePartialOffload' + })} + + + + + +
+ {scheduleType === 'auto' && ( +
+ + name="distributed_inference_across_workers" + valuePropName="checked" + style={{ padding: '0 10px', marginBottom: 0 }} + noStyle + > + + + + {intl.formatMessage({ + id: 'resources.form.enableDistributedInferenceAcrossWorkers' + })} + + + + + +
+ )} + + ); + return [ + { + key: '1', + label: ( + + {intl.formatMessage({ id: 'resources.form.advanced' })} + + ), + children + } + ]; + }, [scheduleType]); + + return ( + ( + + )} + items={collapseItems} + > + ); +}; + +export default AdvanceConfig; diff --git a/src/pages/llmodels/components/data-form.tsx b/src/pages/llmodels/components/data-form.tsx index 14eadc8e..43d7d37a 100644 --- a/src/pages/llmodels/components/data-form.tsx +++ b/src/pages/llmodels/components/data-form.tsx @@ -1,28 +1,21 @@ -import LabelSelector from '@/components/label-selector'; import SealAutoComplete from '@/components/seal-form/auto-complete'; import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; -import { InfoCircleOutlined, RightOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; -import { Checkbox, Collapse, Form, Select, Tooltip, Typography } from 'antd'; +import { Form } from 'antd'; import _ from 'lodash'; import React, { forwardRef, - useCallback, useEffect, useImperativeHandle, - useMemo + useState } from 'react'; -import { - modelSourceMap, - ollamaModelOptions, - placementStrategyOptions -} from '../config'; -import { FormData } from '../config/types'; -import dataformStyles from '../style/data-form.less'; -import GPUCard from './gpu-card'; +import { queryGPUList } from '../apis'; +import { modelSourceMap, ollamaModelOptions } from '../config'; +import { FormData, GPUListItem } from '../config/types'; +import AdvanceConfig from './advance-config'; interface DataFormProps { ref?: any; @@ -45,109 +38,29 @@ const sourceOptions = [ } ]; -const gpuOptions = [ - { - label: 'NVIDIA A100', - value: '1', - index: 0, - worker_name: 'mbp-1.local', - worker: { - GPU: { - name: 'A100', - count: 1, - memory: 16384 - }, - CPU: { - count: 16, - memory: 16384 - }, - os: { - name: 'Ubuntu 20.04', - version: '20.04' - }, - name: 'mbp-1.local', - address: '192.168.1.1' - } - }, - { - label: 'NVIDIA H100', - value: '2', - index: 1, - worker_name: 'mbp-2.local', - worker: { - GPU: { - name: 'H100', - count: 1, - memory: 16384 - }, - CPU: { - count: 16, - memory: 16384 - }, - os: { - name: 'Ubuntu 20.04', - version: '20.04' - }, - name: 'mbp-2.local', - address: '192.168.1.2' - } - }, - { - label: 'NVIDIA A100', - value: '3', - index: 2, - worker_name: 'mbp-3.local', - worker: { - GPU: { - name: 'A100', - count: 1, - memory: 16384 - }, - CPU: { - count: 16, - memory: 16384 - }, - os: { - name: 'Ubuntu 20.04', - version: '20.04' - }, - name: 'mbp-3.local', - address: '192.168.1.3' - } - }, - { - label: 'NVIDIA H100', - value: '4', - index: 3, - worker_name: 'mbp-4.local', - worker: { - GPU: { - name: 'H100', - count: 1, - memory: 16384 - }, - CPU: { - count: 16, - memory: 16384 - }, - os: { - name: 'Ubuntu 20.04', - version: '20.04' - }, - name: 'mbp-4.local', - address: '192.168.1.4' - } - } -]; - const DataForm: React.FC = forwardRef((props, ref) => { const { action, repo, onOk } = props; const [form] = Form.useForm(); const intl = useIntl(); - const wokerSelector = Form.useWatch('worker_selector', form); - // const [scheduleType, setScheduleType] = React.useState(false); // false: auto, true: manual + const [gpuOptions, setGpuOptions] = useState< + Array + >([]); - const scheduleType = Form.useWatch('scheduleType', form); + const getGPUList = async () => { + const data = await queryGPUList(); + const list = _.map(data.items, (item: GPUListItem) => { + return { + ...item, + label: item.name, + value: item.name + }; + }); + setGpuOptions(list); + }; + + useEffect(() => { + getGPUList(); + }, []); const handleSumit = () => { form.submit(); @@ -172,40 +85,6 @@ const DataForm: React.FC = forwardRef((props, ref) => { [] ); - const placementStrategyTips = [ - { - title: 'Spread', - tips: intl.formatMessage({ - id: 'resources.form.spread.tips' - }) - }, - { - title: 'Binpack', - tips: intl.formatMessage({ - id: 'resources.form.binpack.tips' - }) - } - ]; - - const scheduleTypeTips = [ - { - title: intl.formatMessage({ - id: 'models.form.scheduletype.auto' - }), - tips: intl.formatMessage({ - id: 'models.form.scheduletype.auto.tips' - }) - }, - { - title: intl.formatMessage({ - id: 'models.form.scheduletype.manual' - }), - tips: intl.formatMessage({ - id: 'models.form.scheduletype.manual.tips' - }) - } - ]; - const handleOnSelectModel = () => { console.log('repo=============', repo); if (!repo) { @@ -228,13 +107,6 @@ const DataForm: React.FC = forwardRef((props, ref) => { } }; - const handleWorkerLabelsChange = useCallback( - (labels: Record) => { - form.setFieldValue('worker_selector', labels); - }, - [] - ); - const renderHuggingfaceFields = () => { return ( <> @@ -355,163 +227,26 @@ const DataForm: React.FC = forwardRef((props, ref) => { } }; - const renderSelectTips = (list: Array<{ title: string; tips: string }>) => { - return ( -
- {list.map((item, index) => { - return ( -
- - {item.title}: - - - {item.tips} - -
- ); - })} -
- ); + const handleOk = (formdata: FormData) => { + const gpu = _.find(gpuOptions, (item: any) => { + return item.value === formdata.gpu_selector; + }); + if (gpu) { + onOk({ + ..._.omit(formdata, ['scheduleType']), + gpu_selector: { + gpu_name: gpu.name, + gpu_index: gpu.index, + worker_name: gpu.worker_name + } + }); + } else { + onOk({ + ..._.omit(formdata, ['scheduleType']) + }); + } }; - const collapseItems = useMemo(() => { - const children = ( - <> - - - - {scheduleType === 'auto' && ( - name="placement_strategy"> - - - )} - {scheduleType === 'auto' && ( - name="worker_selector"> - - {intl.formatMessage({ - id: 'resources.form.workerSelector.description' - })} - - } - > - - )} - {scheduleType === 'manual' && ( - name="gpu_selector"> - - {gpuOptions.map((item) => ( - - - - ))} - - - )} -
- - name="partial_offload" - valuePropName="checked" - style={{ padding: '0 10px', marginBottom: 0 }} - noStyle - > - - - - {intl.formatMessage({ - id: 'resources.form.enablePartialOffload' - })} - - - - - -
- {scheduleType === 'auto' && ( -
- - name="distributed_inference_across_workers" - valuePropName="checked" - style={{ padding: '0 10px', marginBottom: 0 }} - noStyle - > - - - - {intl.formatMessage({ - id: 'resources.form.enableDistributedInferenceAcrossWorkers' - })} - - - - - -
- )} - - ); - return [ - { - key: '1', - label: ( - - {intl.formatMessage({ id: 'resources.form.advanced' })} - - ), - children - } - ]; - }, [scheduleType]); - useEffect(() => { handleOnSelectModel(); }, [repo]); @@ -520,7 +255,7 @@ const DataForm: React.FC = forwardRef((props, ref) => {
= forwardRef((props, ref) => { replicas: 1, source: props.source, placement_strategy: 'spread', - partial_offload: true, + partial_offload: false, scheduleType: 'auto', - distributed_inference_across_workers: true + distributed_inference_across_workers: false }} > @@ -612,20 +347,7 @@ const DataForm: React.FC = forwardRef((props, ref) => { })} > - - ( - - )} - items={collapseItems} - > + ); }); diff --git a/src/pages/llmodels/components/gpu-card.tsx b/src/pages/llmodels/components/gpu-card.tsx index a399c33d..9f96cff7 100644 --- a/src/pages/llmodels/components/gpu-card.tsx +++ b/src/pages/llmodels/components/gpu-card.tsx @@ -13,9 +13,11 @@ const GPUCard: React.FC<{
VRAM: - Total {convertFileSize(22906503168)} - Used {convertFileSize(3322640640)} - Utilization {_.round(3.79, 2)}% + Total {convertFileSize(data?.memory?.total || 0)} + Used {convertFileSize(data?.memory?.used || 0)} + + Utilization {_.round(data?.memory?.utilization_rate || 0, 2)}% +
); diff --git a/src/pages/llmodels/components/update-modal.tsx b/src/pages/llmodels/components/update-modal.tsx index f32669bb..c655bd59 100644 --- a/src/pages/llmodels/components/update-modal.tsx +++ b/src/pages/llmodels/components/update-modal.tsx @@ -1,23 +1,20 @@ -import LabelSelector from '@/components/label-selector'; import ModalFooter from '@/components/modal-footer'; import SealAutoComplete from '@/components/seal-form/auto-complete'; -import FormItemWrapper from '@/components/seal-form/components/wrapper'; import SealInput from '@/components/seal-form/seal-input'; import SealSelect from '@/components/seal-form/seal-select'; import { PageAction } from '@/config'; import { PageActionType } from '@/config/types'; import { convertFileSize } from '@/utils'; -import { InfoCircleOutlined, RightOutlined } from '@ant-design/icons'; import { useIntl } from '@umijs/max'; -import { Checkbox, Collapse, Form, Modal, Tooltip, Typography } from 'antd'; +import { Form, Modal } from 'antd'; import _ from 'lodash'; -import { memo, useCallback, useEffect, useState } from 'react'; +import { memo, useEffect, useState } from 'react'; import SimpleBar from 'simplebar-react'; import 'simplebar-react/dist/simplebar.min.css'; -import { queryHuggingfaceModelFiles, queryHuggingfaceModels } from '../apis'; -import { modelSourceMap, placementStrategyOptions } from '../config'; -import { FormData, ListItem } from '../config/types'; -import dataformStyles from '../style/data-form.less'; +import { queryGPUList, queryHuggingfaceModelFiles } from '../apis'; +import { modelSourceMap } from '../config'; +import { FormData, GPUListItem, ListItem } from '../config/types'; +import AdvanceConfig from './advance-config'; type AddModalProps = { title: string; @@ -46,16 +43,25 @@ const UpdateModal: React.FC = (props) => { const { title, action, open, onOk, onCancel } = props || {}; const [form] = Form.useForm(); const intl = useIntl(); + const [gpuOptions, setGpuOptions] = useState([]); const [loading, setLoading] = useState(false); const modelSource = Form.useWatch('source', form); - const wokerSelector = Form.useWatch('worker_selector', form); - const [repoOptions, setRepoOptions] = useState< - { label: string; value: string }[] - >([]); const [fileOptions, setFileOptions] = useState< { label: string; value: string }[] >([]); + const getGPUList = async () => { + const data = await queryGPUList(); + const list = _.map(data.items, (item: GPUListItem) => { + return { + ...item, + label: item.name, + value: item.name + }; + }); + setGpuOptions(list); + }; + const initFormValue = () => { if (action === PageAction.CREATE && open) { form.setFieldsValue({ @@ -65,7 +71,8 @@ const UpdateModal: React.FC = (props) => { } if (action === PageAction.EDIT && open) { form.setFieldsValue({ - ...props.data + ...props.data, + scheduleType: props.data?.gpu_selector ? 'manual' : 'auto' }); } }; @@ -112,32 +119,6 @@ const UpdateModal: React.FC = (props) => { handleFetchModelFiles(repo); }; - const handleOnSearchRepo = async (text: string) => { - try { - const params = { - search: { - query: text, - tags: ['gguf'] - } - }; - const models = await queryHuggingfaceModels(params); - const list = _.map(models || [], (item: any) => { - return { - ...item, - value: item.name, - label: item.name - }; - }); - setRepoOptions(list); - } catch (error) { - setRepoOptions([]); - } - }; - - const debounceSearch = _.debounce((text: string) => { - handleOnSearchRepo(text); - }, 300); - const renderHuggingfaceFields = () => { return ( <> @@ -258,157 +239,33 @@ const UpdateModal: React.FC = (props) => { } }; - const handleWorkerLabelsChange = useCallback( - (labels: Record) => { - console.log('labels========', labels); - form.setFieldValue('worker_selector', labels); - }, - [] - ); - - const handleOnSelectModel = useCallback((item: any) => { - const repo = item.name; - if (form.getFieldValue('source') === modelSourceMap.huggingface_value) { - form.setFieldValue('huggingface_repo_id', repo); - handleFetchModelFiles(repo); - } else { - form.setFieldValue('ollama_library_model_name', repo); - } - }, []); - - const handleSourceChange = useCallback((value: string) => { - form.setFieldValue('source', value); - }, []); - const handleSumit = () => { form.submit(); }; - const collapseItems = [ - { - key: '1', - label: ( - - {intl.formatMessage({ id: 'resources.form.advanced' })} - - ), - children: ( - <> - name="placement_strategy"> - -
- - Spread: - - - {intl.formatMessage({ - id: 'resources.form.spread.tips' - })} - -
-
- - Binpack: - - - {intl.formatMessage({ - id: 'resources.form.binpack.tips' - })} - -
- - } - >
- -
- - - name="partial_offload" - valuePropName="checked" - style={{ padding: '0 10px', marginBottom: 0 }} - > - - - - {intl.formatMessage({ - id: 'resources.form.enablePartialOffload' - })} - - - - - - -
-
- - - name="distributed_inference_across_workers" - valuePropName="checked" - style={{ padding: '0 10px', marginBottom: 0 }} - > - - - - {intl.formatMessage({ - id: 'resources.form.enableDistributedInferenceAcrossWorkers' - })} - - - - - - -
- name="worker_selector"> - - {intl.formatMessage({ - id: 'resources.form.workerSelector.description' - })} - - } - > - - - ) + const handleOk = (formdata: FormData) => { + const gpu = _.find(gpuOptions, (item: any) => { + return item.value === formdata.gpu_selector; + }); + if (gpu) { + onOk({ + ..._.omit(formdata, ['scheduleType']), + gpu_selector: { + gpu_name: gpu.name, + gpu_index: gpu.index, + worker_name: gpu.worker_name + } + }); + } else { + onOk({ + ..._.omit(formdata, ['scheduleType']) + }); } - ]; + }; + + useEffect(() => { + getGPUList(); + }, []); return ( = (props) => {
= (props) => { })} > - ( - - )} - items={collapseItems} - > + +
diff --git a/src/pages/llmodels/config/types.ts b/src/pages/llmodels/config/types.ts index d2207f1a..3c07d990 100644 --- a/src/pages/llmodels/config/types.ts +++ b/src/pages/llmodels/config/types.ts @@ -13,6 +13,12 @@ export interface ListItem { id: number; created_at: string; updated_at: string; + gpu_selector?: { + worker_name: string; + gpu_index: number; + gpu_name: string; + }; + worker_selector?: object; } export interface FormData { @@ -22,6 +28,11 @@ export interface FormData { s3_address: string; ollama_library_model_name: 'string'; distributed_inference_across_workers?: boolean; + gpu_selector?: { + worker_name: string; + gpu_index: number; + gpu_name: string; + }; placement_strategy?: string; partial_offload?: boolean; worker_selector?: object; @@ -61,3 +72,26 @@ export interface ModelInstanceFormData { huggingface_repo_id: 'string'; huggingface_filename: 'string'; } + +export interface GPUListItem { + name: string; + uuid: string; + vendor: string; + index: number; + core: { + total: number; + utilization_rate: number; + }; + memory: { + total: number; + utilization_rate: number; + is_unified_memory: boolean; + used: number; + allocated: number; + }; + temperature: number; + id: string; + worker_id: number; + worker_name: string; + worker_ip: string; +}