fix: gpu hardware state (#4650)

* fix: gpu hardware state

* chore: cleanup state

* chore: clear state
This commit is contained in:
Faisal Amir 2025-02-14 11:21:18 +07:00 committed by GitHub
parent 7a6890bd7f
commit dde260e723
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 186 additions and 142 deletions

View File

@ -509,61 +509,61 @@ __metadata:
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension": "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension":
version: 0.1.10 version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=2e50a2&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension" resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=429572&locator=%40janhq%2Fassistant-extension%40workspace%3Aassistant-extension"
dependencies: dependencies:
rxjs: "npm:^7.8.1" rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0" ulidx: "npm:^2.3.0"
checksum: 10c0/92904b534f4f82c31effd9eade0f8685b87165e28f0c3ba3a0c18a6a0a21bf21ee769a98c562c4ba7488ceb1238e2dacce2bb2f729c1bdd84ad44054fb985da0 checksum: 10c0/18b8ca8795ccf2c89e2225668ce2824e4ce7b32872764a739155d2b68a773676188186d721e70dbcf2605bb28697b97d6c01348d34bc440e829087ebd6a3158c
languageName: node languageName: node
linkType: hard linkType: hard
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension": "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension":
version: 0.1.10 version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=2e50a2&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension" resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=429572&locator=%40janhq%2Fconversational-extension%40workspace%3Aconversational-extension"
dependencies: dependencies:
rxjs: "npm:^7.8.1" rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0" ulidx: "npm:^2.3.0"
checksum: 10c0/92904b534f4f82c31effd9eade0f8685b87165e28f0c3ba3a0c18a6a0a21bf21ee769a98c562c4ba7488ceb1238e2dacce2bb2f729c1bdd84ad44054fb985da0 checksum: 10c0/18b8ca8795ccf2c89e2225668ce2824e4ce7b32872764a739155d2b68a773676188186d721e70dbcf2605bb28697b97d6c01348d34bc440e829087ebd6a3158c
languageName: node languageName: node
linkType: hard linkType: hard
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension": "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension":
version: 0.1.10 version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=2e50a2&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension" resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=429572&locator=%40janhq%2Fengine-management-extension%40workspace%3Aengine-management-extension"
dependencies: dependencies:
rxjs: "npm:^7.8.1" rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0" ulidx: "npm:^2.3.0"
checksum: 10c0/92904b534f4f82c31effd9eade0f8685b87165e28f0c3ba3a0c18a6a0a21bf21ee769a98c562c4ba7488ceb1238e2dacce2bb2f729c1bdd84ad44054fb985da0 checksum: 10c0/18b8ca8795ccf2c89e2225668ce2824e4ce7b32872764a739155d2b68a773676188186d721e70dbcf2605bb28697b97d6c01348d34bc440e829087ebd6a3158c
languageName: node languageName: node
linkType: hard linkType: hard
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension": "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension":
version: 0.1.10 version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=2e50a2&locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension" resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=429572&locator=%40janhq%2Fhardware-management-extension%40workspace%3Ahardware-management-extension"
dependencies: dependencies:
rxjs: "npm:^7.8.1" rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0" ulidx: "npm:^2.3.0"
checksum: 10c0/92904b534f4f82c31effd9eade0f8685b87165e28f0c3ba3a0c18a6a0a21bf21ee769a98c562c4ba7488ceb1238e2dacce2bb2f729c1bdd84ad44054fb985da0 checksum: 10c0/18b8ca8795ccf2c89e2225668ce2824e4ce7b32872764a739155d2b68a773676188186d721e70dbcf2605bb28697b97d6c01348d34bc440e829087ebd6a3158c
languageName: node languageName: node
linkType: hard linkType: hard
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension": "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension":
version: 0.1.10 version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=2e50a2&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension" resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=429572&locator=%40janhq%2Finference-cortex-extension%40workspace%3Ainference-cortex-extension"
dependencies: dependencies:
rxjs: "npm:^7.8.1" rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0" ulidx: "npm:^2.3.0"
checksum: 10c0/92904b534f4f82c31effd9eade0f8685b87165e28f0c3ba3a0c18a6a0a21bf21ee769a98c562c4ba7488ceb1238e2dacce2bb2f729c1bdd84ad44054fb985da0 checksum: 10c0/18b8ca8795ccf2c89e2225668ce2824e4ce7b32872764a739155d2b68a773676188186d721e70dbcf2605bb28697b97d6c01348d34bc440e829087ebd6a3158c
languageName: node languageName: node
linkType: hard linkType: hard
"@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension": "@janhq/core@file:../../core/package.tgz::locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension":
version: 0.1.10 version: 0.1.10
resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=2e50a2&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension" resolution: "@janhq/core@file:../../core/package.tgz#../../core/package.tgz::hash=429572&locator=%40janhq%2Fmodel-extension%40workspace%3Amodel-extension"
dependencies: dependencies:
rxjs: "npm:^7.8.1" rxjs: "npm:^7.8.1"
ulidx: "npm:^2.3.0" ulidx: "npm:^2.3.0"
checksum: 10c0/92904b534f4f82c31effd9eade0f8685b87165e28f0c3ba3a0c18a6a0a21bf21ee769a98c562c4ba7488ceb1238e2dacce2bb2f729c1bdd84ad44054fb985da0 checksum: 10c0/18b8ca8795ccf2c89e2225668ce2824e4ce7b32872764a739155d2b68a773676188186d721e70dbcf2605bb28697b97d6c01348d34bc440e829087ebd6a3158c
languageName: node languageName: node
linkType: hard linkType: hard

View File

@ -4,7 +4,7 @@ import * as React from 'react'
import { useState } from 'react' import { useState } from 'react'
import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd' import { DragDropContext, Draggable, Droppable } from '@hello-pangea/dnd'
import { Gpu } from '@janhq/core'
import { Progress, ScrollArea, Switch } from '@janhq/joi' import { Progress, ScrollArea, Switch } from '@janhq/joi'
import { useAtom, useAtomValue } from 'jotai' import { useAtom, useAtomValue } from 'jotai'
import { atomWithStorage } from 'jotai/utils' import { atomWithStorage } from 'jotai/utils'
@ -20,19 +20,24 @@ import {
import { toGibibytes } from '@/utils/converter' import { toGibibytes } from '@/utils/converter'
import { utilizedMemory } from '@/utils/memory'
import { import {
cpuUsageAtom, cpuUsageAtom,
ramUtilitizedAtom, ramUtilitizedAtom,
totalRamAtom, totalRamAtom,
usedRamAtom, usedRamAtom,
gpusAtom,
} from '@/helpers/atoms/SystemBar.atom' } from '@/helpers/atoms/SystemBar.atom'
const gpusAtom = atomWithStorage<Gpu[]>('gpus', [], undefined, { const orderGpusAtom = atomWithStorage<any>('orderGpus', [], undefined, {
getOnInit: true, getOnInit: true,
}) })
const Hardware = () => { const Hardware = () => {
const { hardware } = useGetHardwareInfo() const { hardware, mutate } = useGetHardwareInfo()
const [isActivatingGpu, setIsActivatingGpu] = useState<Set<string>>(new Set())
const [openPanels, setOpenPanels] = useState<Record<number, boolean>>({}) const [openPanels, setOpenPanels] = useState<Record<number, boolean>>({})
const cpuUsage = useAtomValue(cpuUsageAtom) const cpuUsage = useAtomValue(cpuUsageAtom)
@ -40,7 +45,9 @@ const Hardware = () => {
const usedRam = useAtomValue(usedRamAtom) const usedRam = useAtomValue(usedRamAtom)
const ramUtilitized = useAtomValue(ramUtilitizedAtom) const ramUtilitized = useAtomValue(ramUtilitizedAtom)
const [gpus, setGpus] = useAtom<Gpu[]>(gpusAtom) const [gpus, setGpus] = useAtom(gpusAtom)
const [orderGpus, setOrderGpus] = useAtom(orderGpusAtom)
const togglePanel = (index: number) => { const togglePanel = (index: number) => {
setOpenPanels((prev) => ({ setOpenPanels((prev) => ({
@ -51,16 +58,18 @@ const Hardware = () => {
// Handle switch toggle for GPU activation // Handle switch toggle for GPU activation
const handleSwitchChange = async (id: string, isActive: boolean) => { const handleSwitchChange = async (id: string, isActive: boolean) => {
setIsActivatingGpu((prev) => new Set(prev).add(id))
const updatedGpus = gpus.map((gpu) => const updatedGpus = gpus.map((gpu) =>
gpu.id === id ? { ...gpu, activated: isActive } : gpu gpu.id === id ? { ...gpu, activated: isActive } : gpu
) )
setGpus(updatedGpus)
// Call the API to update the active GPUs // Call the API to update the active GPUs
try { try {
const activeGpuIds = updatedGpus const activeGpuIds = updatedGpus
.filter((gpu) => gpu.activated) .filter((gpu: any) => gpu.activated)
.map((gpu) => Number(gpu.id)) .map((gpu: any) => Number(gpu.id))
await setActiveGpus({ gpus: activeGpuIds }) await setActiveGpus({ gpus: activeGpuIds })
mutate()
} catch (error) { } catch (error) {
console.error('Failed to update active GPUs:', error) console.error('Failed to update active GPUs:', error)
} }
@ -71,23 +80,36 @@ const Hardware = () => {
const reorderedGpus = Array.from(gpus) const reorderedGpus = Array.from(gpus)
const [movedGpu] = reorderedGpus.splice(result.source.index, 1) const [movedGpu] = reorderedGpus.splice(result.source.index, 1)
reorderedGpus.splice(result.destination.index, 0, movedGpu) reorderedGpus.splice(result.destination.index, 0, movedGpu)
setGpus(reorderedGpus) // Update the atom, which persists to localStorage
setGpus(reorderedGpus)
setOrderGpus(reorderedGpus.map((gpu) => gpu.id))
} }
React.useEffect(() => { React.useEffect(() => {
if (hardware?.gpus) { if (hardware?.gpus) {
setGpus((prevGpus) => { setGpus((prevGpus: any) => {
// Create a map of existing GPUs by UUID for quick lookup // Create a map of existing GPUs by UUID for quick lookup
const gpuMap = new Map(prevGpus.map((gpu) => [gpu.uuid, gpu])) const gpuMap = new Map(prevGpus.map((gpu: any) => [gpu.uuid, gpu]))
// Update existing GPUs or add new ones // Update existing GPUs or add new ones
const updatedGpus = hardware.gpus.map((newGpu) => { const updatedGpus = hardware.gpus.map((newGpu) => {
const existingGpu = gpuMap.get(newGpu.uuid) const existingGpu: any = gpuMap.get(newGpu.uuid)
if (existingGpu) { if (existingGpu) {
// Update the GPU properties while keeping the original order // Update the GPU properties while keeping the original order
if (existingGpu.activated !== newGpu.activated) {
setIsActivatingGpu((prev) => {
const updated = new Set(prev)
updated.delete(existingGpu.id)
updated.clear()
return updated
})
}
return { return {
...existingGpu, ...existingGpu,
activated: newGpu.activated,
free_vram: newGpu.free_vram, free_vram: newGpu.free_vram,
total_vram: newGpu.total_vram, total_vram: newGpu.total_vram,
} }
@ -100,7 +122,8 @@ const Hardware = () => {
// Append GPUs from the previous state that are not in the hardware.gpus // Append GPUs from the previous state that are not in the hardware.gpus
// This preserves user-reordered GPUs that aren't present in the new data // This preserves user-reordered GPUs that aren't present in the new data
const remainingGpus = prevGpus.filter( const remainingGpus = prevGpus.filter(
(prevGpu) => !hardware.gpus?.some((gpu) => gpu.uuid === prevGpu.uuid) (prevGpu: any) =>
!hardware.gpus?.some((gpu) => gpu.uuid === prevGpu.uuid)
) )
return [...updatedGpus, ...remainingGpus] return [...updatedGpus, ...remainingGpus]
@ -202,8 +225,25 @@ const Hardware = () => {
ref={provided.innerRef} ref={provided.innerRef}
className="mt-4" className="mt-4"
> >
{gpus.map((item, i) => ( {gpus
<Draggable key={i} draggableId={String(i)} index={i}> .sort((a, b) => {
const orderA = orderGpus.indexOf(a.id)
const orderB = orderGpus.indexOf(b.id)
return orderA - orderB
})
.map((item: any, i) => {
const gpuUtilization = utilizedMemory(
item.free_vram,
item.total_vram
)
const isLoading = isActivatingGpu.has(item.id)
return (
<Draggable
key={i}
draggableId={String(i)}
index={i}
>
{(provided, snapshot) => ( {(provided, snapshot) => (
<div <div
ref={provided.innerRef} ref={provided.innerRef}
@ -239,23 +279,12 @@ const Hardware = () => {
{item.activated && ( {item.activated && (
<div className="flex w-40 items-center gap-3"> <div className="flex w-40 items-center gap-3">
<Progress <Progress
value={Math.round( value={gpuUtilization}
((Number(item.total_vram) -
Number(item.free_vram)) /
Number(item.total_vram)) *
100
)}
size="small" size="small"
className="w-full" className="w-full"
/> />
<span className="font-medium"> <span className="font-medium">
{Math.round( {gpuUtilization}%
((Number(item.total_vram) -
Number(item.free_vram)) /
Number(item.total_vram)) *
100
).toFixed()}
%
</span> </span>
</div> </div>
)} )}
@ -281,6 +310,13 @@ const Hardware = () => {
<Switch <Switch
checked={item.activated} checked={item.activated}
className={twMerge(
isLoading && 'pointer-events-none'
)}
disabled={
Boolean(isActivatingGpu.size) &&
!isLoading
}
onChange={(e) => onChange={(e) =>
handleSwitchChange( handleSwitchChange(
item.id, item.id,
@ -289,6 +325,10 @@ const Hardware = () => {
} }
/> />
{isLoading && (
<div className="ml-2 h-4 w-4 animate-spin rounded-full border-t-2 border-solid border-blue-500" />
)}
<ChevronDownIcon <ChevronDownIcon
size={14} size={14}
className={twMerge( className={twMerge(
@ -320,7 +360,10 @@ const Hardware = () => {
Compute Capability Compute Capability
</div> </div>
<span> <span>
{item.additional_information?.compute_cap} {
item.additional_information
?.compute_cap
}
</span> </span>
</div> </div>
</div> </div>
@ -328,7 +371,8 @@ const Hardware = () => {
</div> </div>
)} )}
</Draggable> </Draggable>
))} )
})}
{provided.placeholder} {provided.placeholder}
</div> </div>
)} )}