Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: devbox support gpu #5281

Open
wants to merge 31 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e722796
feat: basic logic
mlhiter Dec 9, 2024
a7a588d
feat: basic logic gpu runtime
mlhiter Dec 10, 2024
c56ade5
feat: pricebox and quota box
mlhiter Dec 10, 2024
4b7f7a7
fix: bug
mlhiter Dec 10, 2024
1dbbea8
chore: ubuntu-cuda svg
mlhiter Dec 10, 2024
cb6d891
fix: price bug
mlhiter Dec 10, 2024
23bf2dc
fix: build bug
mlhiter Dec 10, 2024
2c5dbe0
fix: gpu bug
mlhiter Dec 10, 2024
c0d66c5
fix: build bug
mlhiter Dec 10, 2024
c23b7a8
chore: pytorch svg
mlhiter Dec 10, 2024
0fd2f8d
fix: delete will not be effective
mlhiter Dec 10, 2024
ea3d1a8
chore: cursor->vscode
mlhiter Dec 10, 2024
a122ce7
feat: detail support gpu
mlhiter Dec 11, 2024
9aa727f
chore: name change
mlhiter Dec 11, 2024
6d968ca
fix: gpu inventory bug
mlhiter Dec 11, 2024
4132c9b
fix: delete devbox
mlhiter Dec 11, 2024
a66dc60
fix: bug
mlhiter Dec 11, 2024
ac0e1a4
fix: next build bug
mlhiter Dec 13, 2024
7d8a391
fix: next build bug
mlhiter Dec 13, 2024
454d4e0
chore:delete coonsole.log
mlhiter Dec 17, 2024
0e11709
perf: code shorten
mlhiter Dec 17, 2024
6ee5e1d
chore: deploy update
mlhiter Dec 23, 2024
9c87d5e
fix: add runtimeClassName
mlhiter Dec 23, 2024
03cb538
Merge branch 'main' into feature/gpu-support
mlhiter Dec 25, 2024
706afc1
fix: basic fix
mlhiter Dec 25, 2024
6cde1f9
fix: gpu bug
mlhiter Dec 25, 2024
dc40a9f
fix: type bug
mlhiter Dec 25, 2024
035b3f3
chore: prettier modify
mlhiter Dec 26, 2024
dd047b1
chore: prettier every file
mlhiter Dec 26, 2024
133159e
Merge branch 'main' into feature/gpu-support
mlhiter Dec 26, 2024
a80029d
fix: get->watch
mlhiter Dec 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/providers/devbox/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ DEVBOX_AFFINITY_ENABLE=
SQUASH_ENABLE=
NODE_TLS_REJECT_UNAUTHORIZED=
ROOT_RUNTIME_NAMESPACE=
GPU_ENABLE=
6 changes: 3 additions & 3 deletions frontend/providers/devbox/api/devbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import {
DevboxEditType,
DevboxListItemType,
DevboxPatchPropsType,
DevboxVersionListItemType,
runtimeNamespaceMapType
DevboxVersionListItemType
} from '@/types/devbox'
import {
adaptAppListItem,
Expand All @@ -14,6 +13,7 @@ import {
adaptDevboxVersionListItem,
adaptPod
} from '@/utils/adapt'
import { RuntimeNamespaceMap } from '@/types/static'
import { GET, POST, DELETE } from '@/services/request'
import { KBDevboxType, KBDevboxReleaseType } from '@/types/k8s'
import { MonitorDataResult, MonitorQueryKey } from '@/types/monitor'
Expand All @@ -34,7 +34,7 @@ export const applyYamlList = (yamlList: string[], type: 'create' | 'replace' | '

export const createDevbox = (payload: {
devboxForm: DevboxEditType
runtimeNamespaceMap: runtimeNamespaceMapType
runtimeNamespaceMap: RuntimeNamespaceMap
}) => POST(`/api/createDevbox`, payload)

export const updateDevbox = (payload: { patch: DevboxPatchPropsType; devboxName: string }) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,31 @@ import {
import { throttle } from 'lodash'
import dynamic from 'next/dynamic'
import { customAlphabet } from 'nanoid'
import { useEffect, useState } from 'react'
import { useTranslations } from 'next-intl'
import { UseFormReturn, useFieldArray } from 'react-hook-form'
import { MySelect, MySlider, Tabs, useMessage } from '@sealos/ui'
import { useEffect, useMemo, useState } from 'react'
import { MySelect, MySlider, MyTooltip, Tabs, useMessage } from '@sealos/ui'

import { useRouter } from '@/i18n'
import MyIcon from '@/components/Icon'
import PriceBox from '@/components/PriceBox'
import QuotaBox from '@/components/QuotaBox'

import { useEnvStore } from '@/stores/env'
import { usePriceStore } from '@/stores/price'
import { useDevboxStore } from '@/stores/devbox'
import { useRuntimeStore } from '@/stores/runtime'

import { ProtocolList } from '@/constants/devbox'
import type { DevboxEditType } from '@/types/devbox'
import { obj2Query } from '@/utils/tools'
import { useGlobalStore } from '@/stores/global'
import type { DevboxEditType } from '@/types/devbox'
import { CpuSlideMarkList, MemorySlideMarkList } from '@/constants/devbox'
import { GpuAmountMarkList, ProtocolList } from '@/constants/devbox'

const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 12)

const labelWidth = 100

export type CustomAccessModalParams = {
publicDomain: string
customDomain: string
Expand All @@ -48,11 +52,13 @@ const CustomAccessModal = dynamic(() => import('@/components/modals/CustomAccess
const Form = ({
formHook,
pxVal,
isEdit
isEdit,
countGpuInventory
}: {
formHook: UseFormReturn<DevboxEditType, any>
pxVal: number
isEdit: boolean
countGpuInventory: (type: string) => number
}) => {
const theme = useTheme()
const router = useRouter()
Expand Down Expand Up @@ -83,9 +89,12 @@ const Form = ({
osTypeList,
getRuntimeVersionList,
getRuntimeVersionDefault,
getRuntimeDetailLabel
getRuntimeDetailLabel,
isGPURuntimeType
} = useRuntimeStore()
const { env } = useEnvStore()
const { sourcePrice } = usePriceStore()
const { devboxList } = useDevboxStore()

const [customAccessModalData, setCustomAccessModalData] = useState<CustomAccessModalParams>()
const navList: { id: string; label: string; icon: string }[] = [
Expand All @@ -102,7 +111,6 @@ const Form = ({
]
const { message: toast } = useMessage()
const [activeNav, setActiveNav] = useState(navList[0].id)
const { devboxList } = useDevboxStore()

// listen scroll and set activeNav
useEffect(() => {
Expand Down Expand Up @@ -131,6 +139,50 @@ const Form = ({
// eslint-disable-next-line
}, [])

// add NoGPU select item
const gpuSelectList = useMemo(
() =>
sourcePrice?.gpu
? [
{
label: t('No GPU'),
value: ''
},
...sourcePrice.gpu.map((item) => ({
icon: 'nvidia',
label: (
<Flex>
<Box color={'myGray.900'}>{item.alias}</Box>
<Box mx={3} color={'grayModern.900'}>
|
</Box>
<Box color={'grayModern.900'}>
{t('vm')} : {Math.round(item.vm)}G
</Box>
<Box mx={3} color={'grayModern.900'}>
|
</Box>
<Flex pr={3}>
<Box color={'grayModern.900'}>{t('Inventory')}&ensp;:&ensp;</Box>
<Box color={'#FB7C3C'}>{countGpuInventory(item.type)}</Box>
</Flex>
</Flex>
),
value: item.type
}))
]
: [],
[countGpuInventory, t, sourcePrice?.gpu]
)
const selectedGpu = () => {
const selected = sourcePrice?.gpu?.find((item) => item.type === getValues('gpu.type'))
if (!selected) return
return {
...selected,
inventory: countGpuInventory(selected.type)
}
}

if (!formHook) return null

const Label = ({
Expand Down Expand Up @@ -248,7 +300,13 @@ const Form = ({
{
cpu: getValues('cpu'),
memory: getValues('memory'),
nodeports: devboxList.length
nodeports: devboxList.length,
gpu: !!getValues('gpu.type')
? {
type: getValues('gpu.type'),
amount: getValues('gpu.amount')
}
: undefined
}
]}
/>
Expand Down Expand Up @@ -356,6 +414,7 @@ const Form = ({
'runtimeVersion',
languageVersionMap[getValues('runtimeType')][0].id
)
setValue('gpu.type', '')
setValue(
'networks',
languageVersionMap[getValues('runtimeType')][0].defaultPorts.map(
Expand Down Expand Up @@ -437,6 +496,7 @@ const Form = ({
'runtimeVersion',
frameworkVersionMap[getValues('runtimeType')][0].id
)
setValue('gpu.type', '')
setValue(
'networks',
frameworkVersionMap[getValues('runtimeType')][0].defaultPorts.map(
Expand Down Expand Up @@ -518,6 +578,7 @@ const Form = ({
'runtimeVersion',
osVersionMap[getValues('runtimeType')][0].id
)
setValue('gpu.type', '')
setValue(
'networks',
osVersionMap[getValues('runtimeType')][0].defaultPorts.map(
Expand Down Expand Up @@ -576,7 +637,7 @@ const Form = ({
{...register('runtimeVersion', {
required: t('This runtime field is required')
})}
width={'200px'}
width={'300px'}
placeholder={`${t('runtime')} ${t('version')}`}
defaultValue={
getValues('runtimeVersion') ||
Expand Down Expand Up @@ -613,6 +674,79 @@ const Form = ({
/>
)}
</Flex>

{/* GPU */}
{sourcePrice?.gpu && isGPURuntimeType(getValues('runtimeType')) && (
<Box mb={7}>
<Flex alignItems={'center'}>
<Label w={100}>GPU</Label>
<MySelect
width={'300px'}
placeholder={t('No GPU') || ''}
value={getValues('gpu.type')}
list={gpuSelectList}
onchange={(type: any) => {
const selected = sourcePrice?.gpu?.find((item) => item.type === type)
const inventory = countGpuInventory(type)
if (type === '' || (selected && inventory > 0)) {
setValue('gpu.type', type)
}
}}
/>
</Flex>
{!!getValues('gpu.type') && (
<Box mt={4} pl={`${labelWidth}px`}>
<Box mb={1}>{t('Amount')}</Box>
<Flex alignItems={'center'}>
{GpuAmountMarkList.map((item) => {
const inventory = selectedGpu()?.inventory || 0

const hasInventory = item.value <= inventory

return (
<MyTooltip
key={item.value}
label={hasInventory ? '' : t('Under Stock')}>
<Center
mr={2}
w={'32px'}
h={'32px'}
borderRadius={'md'}
border={'1px solid'}
bg={'white'}
{...(getValues('gpu.amount') === item.value
? {
borderColor: 'brightBlue.500',
boxShadow: '0px 0px 0px 2.4px rgba(33, 155, 244, 0.15)'
}
: {
borderColor: 'grayModern.200',
bgColor: 'grayModern.100'
})}
{...(hasInventory
? {
cursor: 'pointer',
onClick: () => {
setValue('gpu.amount', item.value)
}
}
: {
cursor: 'default',
opacity: 0.5
})}>
{item.label}
</Center>
</MyTooltip>
)
})}
<Box ml={3} color={'MyGray.500'}>
/ {t('Card')}
</Box>
</Flex>
</Box>
)}
</Box>
)}
{/* CPU */}
<Flex mb={10} pr={3} alignItems={'flex-start'}>
<Label w={100}>{t('cpu')}</Label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { useLoading } from '@/hooks/useLoading'
import { useEnvStore } from '@/stores/env'
import { useIDEStore } from '@/stores/ide'
import { useUserStore } from '@/stores/user'
import { usePriceStore } from '@/stores/price'
import { useDevboxStore } from '@/stores/devbox'
import { useGlobalStore } from '@/stores/global'
import { useRuntimeStore } from '@/stores/runtime'
Expand All @@ -47,12 +48,14 @@ const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyz', 12)
const DevboxCreatePage = () => {
const router = useRouter()
const t = useTranslations()
const { Loading, setIsLoading } = useLoading()

const searchParams = useSearchParams()
const { message: toast } = useMessage()

const { env } = useEnvStore()
const { addDevboxIDE } = useIDEStore()
const { sourcePrice, setSourcePrice } = usePriceStore()
const { checkQuotaAllow } = useUserStore()
const { setDevboxDetail, devboxList } = useDevboxStore()
const { runtimeNamespaceMap, languageVersionMap, frameworkVersionMap, osVersionMap } =
Expand All @@ -62,7 +65,6 @@ const DevboxCreatePage = () => {
const formOldYamls = useRef<YamlItemType[]>([])
const oldDevboxEditData = useRef<DevboxEditType>()

const { Loading, setIsLoading } = useLoading()
const [errorMessage, setErrorMessage] = useState('')
const [forceUpdate, setForceUpdate] = useState(false)
const [yamlList, setYamlList] = useState<YamlItemType[]>([])
Expand Down Expand Up @@ -191,12 +193,26 @@ const DevboxCreatePage = () => {
[]
)

const countGpuInventory = useCallback(
(type?: string) => {
const inventory = sourcePrice?.gpu?.find((item) => item.type === type)?.inventory || 0

return inventory
},
[sourcePrice?.gpu]
)

// watch form change, compute new yaml
formHook.watch((data) => {
data && formOnchangeDebounce(data as DevboxEditType)
setForceUpdate(!forceUpdate)
})

const { refetch: refetchPrice } = useQuery(['init-price'], setSourcePrice, {
enabled: !!sourcePrice?.gpu,
refetchInterval: 6000
})

useQuery(
['initDevboxCreateData'],
() => {
Expand Down Expand Up @@ -259,6 +275,18 @@ const DevboxCreatePage = () => {
setIsLoading(true)

try {
// gpu inventory check
if (formData.gpu?.type) {
const inventory = countGpuInventory(formData.gpu?.type)
if (formData.gpu?.amount > inventory) {
return toast({
status: 'warning',
title: t('Gpu under inventory Tip', {
gputype: formData.gpu.type
})
})
}
}
// quote check
const quoteCheckRes = checkQuotaAllow(
{ ...formData, nodeports: devboxList.length + 1 } as DevboxEditType & {
Expand Down Expand Up @@ -310,11 +338,14 @@ const DevboxCreatePage = () => {
} else {
await createDevbox({ devboxForm: formData, runtimeNamespaceMap })
}
addDevboxIDE('cursor', formData.name)
addDevboxIDE('vscode', formData.name)
toast({
title: t(applySuccess),
status: 'success'
})
if (sourcePrice?.gpu) {
refetchPrice()
}
router.push(lastRoute)
} catch (error) {
console.error(error)
Expand Down Expand Up @@ -361,7 +392,12 @@ const DevboxCreatePage = () => {
/>
<Box flex={'1 0 0'} h={0} w={'100%'} pb={4}>
{tabType === 'form' ? (
<Form formHook={formHook} pxVal={pxVal} isEdit={isEdit} />
<Form
formHook={formHook}
pxVal={pxVal}
isEdit={isEdit}
countGpuInventory={countGpuInventory}
/>
) : (
<Yaml yamlList={yamlList} pxVal={pxVal} />
)}
Expand Down
Loading
Loading