fix: correct default engine variant setup on first launch (#4747)
* fix: there is a case where app selects incorrect engine variant first launch * refactor: clean up legacy settings hook
This commit is contained in:
parent
ecb14b3621
commit
6a0fb09610
@ -21,7 +21,7 @@ describe('engineVariant', () => {
|
||||
it('should return mac-amd64 when platform is darwin and arch is not arm64', async () => {
|
||||
vi.stubGlobal('PLATFORM', 'darwin')
|
||||
const result = await engineVariant({
|
||||
cpu: { arch: 'x64', instructions: '' },
|
||||
cpu: { arch: 'x64', instructions: [] },
|
||||
gpus: [],
|
||||
vulkan: false,
|
||||
})
|
||||
@ -31,7 +31,7 @@ describe('engineVariant', () => {
|
||||
it('should return windows-amd64-noavx-cuda-12-0 when platform is win32, cuda is enabled, and cuda version is 12', async () => {
|
||||
vi.stubGlobal('PLATFORM', 'win32')
|
||||
const result = await engineVariant({
|
||||
cpu: { arch: 'x64', instructions: 'avx2' },
|
||||
cpu: { arch: 'x64', instructions: ['avx2'] },
|
||||
gpus: [
|
||||
{
|
||||
activated: true,
|
||||
@ -47,7 +47,7 @@ describe('engineVariant', () => {
|
||||
it('should return linux-amd64-noavx-cuda-11-7 when platform is linux, cuda is enabled, and cuda version is 11', async () => {
|
||||
vi.stubGlobal('PLATFORM', 'linux')
|
||||
const result = await engineVariant({
|
||||
cpu: { arch: 'x64', instructions: 'avx2' },
|
||||
cpu: { arch: 'x64', instructions: [] },
|
||||
gpus: [
|
||||
{
|
||||
activated: true,
|
||||
@ -57,16 +57,34 @@ describe('engineVariant', () => {
|
||||
],
|
||||
vulkan: false,
|
||||
})
|
||||
expect(result).toBe('linux-amd64-avx2-cuda-11-7')
|
||||
expect(result).toBe('linux-amd64-noavx-cuda-11-7')
|
||||
})
|
||||
|
||||
it('should return windows-amd64-vulkan when platform is win32 and vulkan is enabled', async () => {
|
||||
vi.stubGlobal('PLATFORM', 'win32')
|
||||
const result = await engineVariant({
|
||||
cpu: { arch: 'x64', instructions: '' },
|
||||
cpu: { arch: 'x64', instructions: [] },
|
||||
gpus: [{ activated: true, version: '12' }],
|
||||
vulkan: true,
|
||||
})
|
||||
expect(result).toBe('windows-amd64-vulkan')
|
||||
})
|
||||
|
||||
it('should return windows-amd64-avx512 when platform is win32, no gpu detected and avx512 cpu instruction is supported', async () => {
|
||||
vi.stubGlobal('PLATFORM', 'win32')
|
||||
const result = await engineVariant({
|
||||
cpu: { arch: 'x64', instructions: ['avx512'] },
|
||||
gpus: [{ activated: true, version: '12' }],
|
||||
})
|
||||
expect(result).toBe('windows-amd64-avx512')
|
||||
})
|
||||
|
||||
it('should return windows-amd64-avx512 when platform is win32, no gpu detected and no accelerated cpu instructions are supported', async () => {
|
||||
vi.stubGlobal('PLATFORM', 'win32')
|
||||
const result = await engineVariant({
|
||||
cpu: { arch: 'x64', instructions: [''] },
|
||||
gpus: [{ activated: true, version: '12' }],
|
||||
})
|
||||
expect(result).toBe('windows-amd64-noavx')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,20 +1,29 @@
|
||||
import { GpuSetting, log } from '@janhq/core'
|
||||
|
||||
// Supported run modes
|
||||
enum RunMode {
|
||||
Cuda = 'cuda',
|
||||
CPU = 'cpu',
|
||||
}
|
||||
|
||||
// Supported instruction sets
|
||||
const instructionBinaryNames = ['noavx', 'avx', 'avx2', 'avx512']
|
||||
|
||||
/**
|
||||
* The GPU runMode that will be set - either 'vulkan', 'cuda', or empty for cpu.
|
||||
* @param settings
|
||||
* @returns
|
||||
*/
|
||||
|
||||
const gpuRunMode = (settings?: GpuSetting): string => {
|
||||
const gpuRunMode = (settings?: GpuSetting): RunMode => {
|
||||
return settings.gpus?.some(
|
||||
(gpu) =>
|
||||
gpu.activated === true &&
|
||||
gpu.activated &&
|
||||
gpu.additional_information &&
|
||||
gpu.additional_information.driver_version
|
||||
)
|
||||
? 'cuda'
|
||||
: ''
|
||||
? RunMode.Cuda
|
||||
: RunMode.CPU
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,13 +46,6 @@ const os = (settings?: GpuSetting): string => {
|
||||
* @returns
|
||||
*/
|
||||
const cudaVersion = (settings?: GpuSetting): '12-0' | '11-7' | undefined => {
|
||||
const isUsingCuda =
|
||||
settings?.vulkan !== true &&
|
||||
settings?.gpus?.some((gpu) => (gpu.activated === true ? 'gpu' : 'cpu')) &&
|
||||
!os().includes('mac')
|
||||
|
||||
if (!isUsingCuda) return undefined
|
||||
// return settings?.cuda?.version === '11' ? '11-7' : '12-0'
|
||||
return settings.gpus?.some((gpu) => gpu.version.includes('12'))
|
||||
? '12-0'
|
||||
: '11-7'
|
||||
@ -65,6 +67,7 @@ export const engineVariant = async (
|
||||
// There is no need to append the variant extension for mac
|
||||
if (platform.startsWith('mac')) return platform
|
||||
|
||||
const runMode = gpuRunMode(gpuSetting)
|
||||
// Only Nvidia GPUs have addition_information set and activated by default
|
||||
let engineVariant =
|
||||
!gpuSetting?.vulkan ||
|
||||
@ -72,14 +75,23 @@ export const engineVariant = async (
|
||||
gpuSetting.gpus.some((e) => e.additional_information && e.activated)
|
||||
? [
|
||||
platform,
|
||||
gpuRunMode(gpuSetting) === 'cuda' &&
|
||||
(gpuSetting.cpu.instructions.includes('avx2') ||
|
||||
gpuSetting.cpu.instructions.includes('avx512'))
|
||||
...(runMode === RunMode.Cuda
|
||||
? // For cuda we only need to check if the cpu supports avx2 or noavx - since other binaries are not shipped with the extension
|
||||
[
|
||||
gpuSetting.cpu?.instructions.includes('avx2') ||
|
||||
gpuSetting.cpu?.instructions.includes('avx512')
|
||||
? 'avx2'
|
||||
: 'noavx',
|
||||
gpuRunMode(gpuSetting),
|
||||
runMode,
|
||||
cudaVersion(gpuSetting),
|
||||
].filter(Boolean) // Remove any falsy values
|
||||
]
|
||||
: // For cpu only we need to check all available supported instructions
|
||||
[
|
||||
(gpuSetting.cpu?.instructions ?? ['noavx']).find((e) =>
|
||||
instructionBinaryNames.includes(e.toLowerCase())
|
||||
) ?? 'noavx',
|
||||
]),
|
||||
].filter(Boolean)
|
||||
: [platform, 'vulkan']
|
||||
|
||||
let engineVariantString = engineVariant.join('-')
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import React from 'react'
|
||||
import { render, waitFor, screen } from '@testing-library/react'
|
||||
import { render } from '@testing-library/react'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||
import { useSettings } from '@/hooks/useSettings'
|
||||
import ModelLabel from '@/containers/ModelLabel'
|
||||
|
||||
jest.mock('jotai', () => ({
|
||||
@ -14,14 +13,9 @@ jest.mock('@/hooks/useActiveModel', () => ({
|
||||
useActiveModel: jest.fn(),
|
||||
}))
|
||||
|
||||
jest.mock('@/hooks/useSettings', () => ({
|
||||
useSettings: jest.fn(),
|
||||
}))
|
||||
|
||||
describe('ModelLabel', () => {
|
||||
const mockUseAtomValue = useAtomValue as jest.Mock
|
||||
const mockUseActiveModel = useActiveModel as jest.Mock
|
||||
const mockUseSettings = useSettings as jest.Mock
|
||||
|
||||
const defaultProps: any = {
|
||||
metadata: {
|
||||
@ -44,7 +38,6 @@ describe('ModelLabel', () => {
|
||||
mockUseActiveModel.mockReturnValue({
|
||||
activeModel: { metadata: { size: 0 } },
|
||||
})
|
||||
mockUseSettings.mockReturnValue({ settings: { run_mode: 'cpu' } })
|
||||
|
||||
const props = {
|
||||
...defaultProps,
|
||||
|
||||
@ -4,8 +4,6 @@ import { useAtomValue } from 'jotai'
|
||||
|
||||
import { useActiveModel } from '@/hooks/useActiveModel'
|
||||
|
||||
import { useSettings } from '@/hooks/useSettings'
|
||||
|
||||
import NotEnoughMemoryLabel from './NotEnoughMemoryLabel'
|
||||
|
||||
import SlowOnYourDeviceLabel from './SlowOnYourDeviceLabel'
|
||||
@ -26,12 +24,12 @@ const ModelLabel = ({ size, compact }: Props) => {
|
||||
const totalRam = useAtomValue(totalRamAtom)
|
||||
const usedRam = useAtomValue(usedRamAtom)
|
||||
const availableVram = useAtomValue(availableVramAtom)
|
||||
const { settings } = useSettings()
|
||||
|
||||
const getLabel = (size: number) => {
|
||||
const minimumRamModel = (size * 1.25) / (1024 * 1024)
|
||||
|
||||
const availableRam = settings?.gpus?.some((gpu) => gpu.activated)
|
||||
const availableRam =
|
||||
availableVram > 0
|
||||
? availableVram * 1000000 // MB to bytes
|
||||
: totalRam -
|
||||
(usedRam +
|
||||
@ -42,7 +40,7 @@ const ModelLabel = ({ size, compact }: Props) => {
|
||||
if (minimumRamModel > totalRam) {
|
||||
return (
|
||||
<NotEnoughMemoryLabel
|
||||
unit={settings?.gpus?.some((gpu) => gpu.activated) ? 'VRAM' : 'RAM'}
|
||||
unit={availableVram > 0 ? 'VRAM' : 'RAM'}
|
||||
compact={compact}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -1,51 +0,0 @@
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
import { fs, GpuSettingInfo, joinPath } from '@janhq/core'
|
||||
|
||||
export type AppSettings = {
|
||||
vulkan: boolean
|
||||
gpus: GpuSettingInfo[]
|
||||
}
|
||||
|
||||
export const useSettings = () => {
|
||||
const [settings, setSettings] = useState<AppSettings>()
|
||||
|
||||
useEffect(() => {
|
||||
readSettings().then((settings) => setSettings(settings as AppSettings))
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
const readSettings = useCallback(async () => {
|
||||
if (!window?.core?.api) {
|
||||
return
|
||||
}
|
||||
const settingsFile = await joinPath(['file://settings', 'settings.json'])
|
||||
if (await fs.existsSync(settingsFile)) {
|
||||
const settings = await fs.readFileSync(settingsFile, 'utf-8')
|
||||
return typeof settings === 'object' ? settings : JSON.parse(settings)
|
||||
}
|
||||
return {}
|
||||
}, [])
|
||||
|
||||
const saveSettings = async ({ vulkan }: { vulkan?: boolean | undefined }) => {
|
||||
const settingsFile = await joinPath(['file://settings', 'settings.json'])
|
||||
const settings = await readSettings()
|
||||
if (vulkan != null) {
|
||||
settings.vulkan = vulkan
|
||||
// GPU enabled, set run_mode to 'gpu'
|
||||
if (settings.vulkan === true) {
|
||||
settings?.gpus?.some((gpu: { activated: boolean }) =>
|
||||
gpu.activated === true ? 'gpu' : 'cpu'
|
||||
)
|
||||
}
|
||||
}
|
||||
await fs.writeFileSync(settingsFile, JSON.stringify(settings))
|
||||
}
|
||||
|
||||
return {
|
||||
readSettings,
|
||||
saveSettings,
|
||||
settings,
|
||||
}
|
||||
}
|
||||
@ -18,8 +18,6 @@ import { MainViewState } from '@/constants/screens'
|
||||
import { useCreateNewThread } from '@/hooks/useCreateNewThread'
|
||||
import useDownloadModel from '@/hooks/useDownloadModel'
|
||||
|
||||
import { useSettings } from '@/hooks/useSettings'
|
||||
|
||||
import { toGigabytes } from '@/utils/converter'
|
||||
|
||||
import { getLogoEngine } from '@/utils/modelEngine'
|
||||
@ -53,16 +51,13 @@ const ModelItemHeader = ({ model, onSelectedModel }: Props) => {
|
||||
const setSelectedSetting = useSetAtom(selectedSettingAtom)
|
||||
const { requestCreateNewThread } = useCreateNewThread()
|
||||
const totalRam = useAtomValue(totalRamAtom)
|
||||
const { settings } = useSettings()
|
||||
|
||||
const nvidiaTotalVram = useAtomValue(nvidiaTotalVramAtom)
|
||||
const setMainViewState = useSetAtom(mainViewStateAtom)
|
||||
|
||||
// Default nvidia returns vram in MB, need to convert to bytes to match the unit of totalRamW
|
||||
let ram = nvidiaTotalVram * 1024 * 1024
|
||||
if (ram === 0 || settings?.gpus?.some((gpu) => gpu.activated !== true)) {
|
||||
ram = totalRam
|
||||
}
|
||||
const ram = nvidiaTotalVram > 0 ? nvidiaTotalVram * 1024 * 1024 : totalRam
|
||||
|
||||
const serverEnabled = useAtomValue(serverEnabledAtom)
|
||||
const assistants = useAtomValue(assistantsAtom)
|
||||
|
||||
|
||||
@ -22,25 +22,6 @@ global.window.core = {
|
||||
},
|
||||
}
|
||||
|
||||
const setSettingsMock = jest.fn()
|
||||
|
||||
// Mock useSettings hook
|
||||
jest.mock('@/hooks/useSettings', () => ({
|
||||
__esModule: true,
|
||||
useSettings: () => ({
|
||||
readSettings: () => ({
|
||||
run_mode: 'gpu',
|
||||
experimental: false,
|
||||
proxy: false,
|
||||
gpus: [{ name: 'gpu-1' }, { name: 'gpu-2' }],
|
||||
gpus_in_use: ['0'],
|
||||
quick_ask: false,
|
||||
}),
|
||||
setSettings: setSettingsMock,
|
||||
}),
|
||||
}))
|
||||
|
||||
import * as toast from '@/containers/Toast'
|
||||
|
||||
jest.mock('@/containers/Toast')
|
||||
|
||||
|
||||
@ -22,23 +22,6 @@ global.window.core = {
|
||||
},
|
||||
}
|
||||
|
||||
const setSettingsMock = jest.fn()
|
||||
|
||||
// Mock useSettings hook
|
||||
jest.mock('@/hooks/useSettings', () => ({
|
||||
__esModule: true,
|
||||
useSettings: () => ({
|
||||
readSettings: () => ({
|
||||
run_mode: 'gpu',
|
||||
experimental: false,
|
||||
proxy: false,
|
||||
gpus: [{ name: 'gpu-1' }, { name: 'gpu-2' }],
|
||||
gpus_in_use: ['0'],
|
||||
quick_ask: false,
|
||||
}),
|
||||
setSettings: setSettingsMock,
|
||||
}),
|
||||
}))
|
||||
|
||||
import * as toast from '@/containers/Toast'
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user