mirror of
https://github.com/lone-cloud/gerbil
synced 2026-06-03 09:33:10 -07:00
allow users to launch with support for multiple GPUs, image gen UI bug fixes, adding the new 1.98.0 sdflashattention and sdconvdirect image gen options, remove cspell
This commit is contained in:
parent
8c6f3a308d
commit
2ef23888df
26 changed files with 509 additions and 1391 deletions
|
|
@ -1,102 +0,0 @@
|
|||
alsa
|
||||
AMDGPU
|
||||
ansi
|
||||
ANSI
|
||||
APPIMAGE
|
||||
stripansi
|
||||
asar
|
||||
Autoencoder
|
||||
BLAS
|
||||
clblast
|
||||
clinfo
|
||||
Consolas
|
||||
contextsize
|
||||
Cooldown
|
||||
cublas
|
||||
cuda
|
||||
CUDA
|
||||
Dolfino
|
||||
finetuned
|
||||
flashattention
|
||||
Flashattention
|
||||
friendlykobold
|
||||
geforce
|
||||
ggml
|
||||
gguf
|
||||
GGUF
|
||||
gpulayers
|
||||
hipblas
|
||||
kcpps
|
||||
kcppt
|
||||
koboldai
|
||||
KOBOLDAI
|
||||
koboldcpp
|
||||
KoboldCpp
|
||||
KOBOLDCPP
|
||||
libxss
|
||||
lora
|
||||
lowvram
|
||||
Lowvram
|
||||
makepkg
|
||||
maximizable
|
||||
minimizable
|
||||
MMAP
|
||||
mmproj
|
||||
mmq
|
||||
multiuser
|
||||
noavx
|
||||
nocertify
|
||||
nocuda
|
||||
noheader
|
||||
nommq
|
||||
noshift
|
||||
nsis
|
||||
nvidia
|
||||
oldpc
|
||||
OLDPC
|
||||
opencl
|
||||
optdepends
|
||||
paru
|
||||
pkexec
|
||||
pkgbase
|
||||
PKGBUILD
|
||||
pkgdesc
|
||||
pkgdir
|
||||
pkgname
|
||||
pkgrel
|
||||
pkgver
|
||||
quantmatmul
|
||||
radeon
|
||||
remotetunnel
|
||||
rocm
|
||||
ROCM
|
||||
rocminfo
|
||||
safetensors
|
||||
sdclipg
|
||||
sdclipl
|
||||
sdlora
|
||||
sdmodel
|
||||
sdphotomaker
|
||||
sdui
|
||||
sdvae
|
||||
SDXL
|
||||
Segoe
|
||||
sonarjs
|
||||
SPACEBAR
|
||||
squashfs
|
||||
SRCINFO
|
||||
taskkill
|
||||
Tauri
|
||||
togglefullscreen
|
||||
treemap
|
||||
trycloudflare
|
||||
unquantized
|
||||
useclblast
|
||||
usecuda
|
||||
usemmap
|
||||
usevulkan
|
||||
vram
|
||||
vulkan
|
||||
vulkaninfo
|
||||
wayland
|
||||
websearch
|
||||
6
.vscode/extensions.json
vendored
6
.vscode/extensions.json
vendored
|
|
@ -1,7 +1,3 @@
|
|||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"streetsidesoftware.code-spell-checker"
|
||||
]
|
||||
"recommendations": ["dbaeumer.vscode-eslint", "esbenp.prettier-vscode"]
|
||||
}
|
||||
|
|
|
|||
14
.vscode/tasks.json
vendored
14
.vscode/tasks.json
vendored
|
|
@ -46,19 +46,7 @@
|
|||
}
|
||||
},
|
||||
{
|
||||
"label": "Spell Check",
|
||||
"type": "shell",
|
||||
"command": "yarn spell-check",
|
||||
"group": "test",
|
||||
"presentation": {
|
||||
"echo": true,
|
||||
"reveal": "always",
|
||||
"focus": false,
|
||||
"panel": "shared"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Check All (Lint + Type + Spell)",
|
||||
"label": "Check All (Lint + Type)",
|
||||
"type": "shell",
|
||||
"command": "yarn check-all",
|
||||
"group": "test",
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ A desktop app for running Large Language Models locally. <!-- markdownlint-disab
|
|||
|
||||
Download the latest release for your platform from the [GitHub Releases page](https://github.com/lone-cloud/friendly-kobold/releases/latest):
|
||||
|
||||
- **Windows**: `Friendly.Kobold.Portable X.X.X.exe` (portable executable)
|
||||
- **Windows**: `Friendly.Kobold.Setup X.X.X.exe` (installer executable)
|
||||
- **Windows**: `Friendly.Kobold-Portable-X.X.X.exe` (portable executable)
|
||||
- **Windows**: `Friendly.Kobold-Setup-X.X.X.exe` (installer executable)
|
||||
- **macOS**: `Friendly.Kobold-X.X.X.dmg` (disk image)
|
||||
- **Linux**: `Friendly.Kobold-X.X.X.AppImage` (portable application)
|
||||
|
||||
|
|
|
|||
66
cspell.json
66
cspell.json
|
|
@ -1,66 +0,0 @@
|
|||
{
|
||||
"version": "0.2",
|
||||
"language": "en",
|
||||
"dictionaryDefinitions": [
|
||||
{
|
||||
"name": "project-terms",
|
||||
"path": "./.cspell/project-terms.txt",
|
||||
"addWords": true
|
||||
}
|
||||
],
|
||||
"dictionaries": [
|
||||
"typescript",
|
||||
"node",
|
||||
"npm",
|
||||
"html",
|
||||
"css",
|
||||
"bash",
|
||||
"en-gb",
|
||||
"companies",
|
||||
"softwareTerms",
|
||||
"misc",
|
||||
"project-terms"
|
||||
],
|
||||
"ignorePaths": [
|
||||
"node_modules/**",
|
||||
"dist/**",
|
||||
"dist-electron/**",
|
||||
"out/**",
|
||||
"release/**",
|
||||
"build/**",
|
||||
"*.min.js",
|
||||
"*.min.css",
|
||||
"package-lock.json",
|
||||
"**/*.log",
|
||||
"coverage/**",
|
||||
".git/**",
|
||||
".vscode/**",
|
||||
"*.map",
|
||||
".cspell/**"
|
||||
],
|
||||
"languageSettings": [
|
||||
{
|
||||
"languageId": "typescript,javascript",
|
||||
"caseSensitive": true,
|
||||
"dictionaries": ["typescript", "node", "npm", "softwareTerms"]
|
||||
},
|
||||
{
|
||||
"languageId": "css,scss,less",
|
||||
"dictionaries": ["css", "fonts"]
|
||||
},
|
||||
{
|
||||
"languageId": "html",
|
||||
"dictionaries": ["html", "css", "typescript"]
|
||||
}
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"filename": "**/*.md",
|
||||
"dictionaries": ["en-gb", "typescript", "node", "npm", "companies"]
|
||||
},
|
||||
{
|
||||
"filename": "**/README.md",
|
||||
"words": ["github", "screenshot", "screenshots", "workflow", "workflows"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import reactRefresh from 'eslint-plugin-react-refresh';
|
|||
import react from 'eslint-plugin-react';
|
||||
import importPlugin from 'eslint-plugin-import';
|
||||
import sonarjs from 'eslint-plugin-sonarjs';
|
||||
import cspell from '@cspell/eslint-plugin';
|
||||
import noComments from 'eslint-plugin-no-comments';
|
||||
|
||||
const config = [
|
||||
|
|
@ -47,7 +46,6 @@ const config = [
|
|||
react: react,
|
||||
import: importPlugin,
|
||||
sonarjs: sonarjs,
|
||||
'@cspell': cspell,
|
||||
'no-comments': noComments,
|
||||
},
|
||||
settings: {
|
||||
|
|
@ -116,8 +114,6 @@ const config = [
|
|||
|
||||
'sonarjs/cognitive-complexity': ['warn', 25],
|
||||
|
||||
'@cspell/spellchecker': ['warn'],
|
||||
|
||||
'no-comments/disallowComments': 'error',
|
||||
},
|
||||
},
|
||||
|
|
|
|||
24
package.json
24
package.json
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "friendly-kobold",
|
||||
"productName": "Friendly Kobold",
|
||||
"version": "0.7.0",
|
||||
"version": "0.8.0",
|
||||
"description": "A desktop app for running Large Language Models locally",
|
||||
"main": "out/main/index.js",
|
||||
"homepage": "./",
|
||||
|
|
@ -23,20 +23,16 @@
|
|||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"compile": "tsc --noEmit",
|
||||
"spell-check": "cspell \"**/*.{ts,tsx,js,jsx,md,json}\" --no-progress",
|
||||
"spell-check:fix": "cspell \"**/*.{ts,tsx,js,jsx,md,json}\" --no-progress --show-suggestions",
|
||||
"check-all": "yarn lint && yarn compile && yarn spell-check",
|
||||
"check-all": "yarn lint && yarn compile",
|
||||
"release": "yarn dlx tsx scripts/release.ts",
|
||||
"prepare": "husky"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx}": [
|
||||
"eslint --fix",
|
||||
"cspell \"**/*.{ts,tsx,js,jsx,md,json}\" --no-progress",
|
||||
"prettier --write --ignore-path .gitignore"
|
||||
],
|
||||
"*.{json,css,md}": [
|
||||
"cspell \"**/*.{ts,tsx,js,jsx,md,json}\" --no-progress",
|
||||
"prettier --write --ignore-path .gitignore"
|
||||
]
|
||||
},
|
||||
|
|
@ -56,17 +52,15 @@
|
|||
},
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"devDependencies": {
|
||||
"@cspell/eslint-plugin": "^9.2.0",
|
||||
"@eslint/js": "^9.34.0",
|
||||
"@types/node": "^24.3.0",
|
||||
"@types/react": "^19.1.11",
|
||||
"@types/react-dom": "^19.1.7",
|
||||
"@types/strip-ansi": "^5.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.40.0",
|
||||
"@typescript-eslint/parser": "^8.40.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.41.0",
|
||||
"@typescript-eslint/parser": "^8.41.0",
|
||||
"@vitejs/plugin-react": "^5.0.1",
|
||||
"cross-env": "^10.0.0",
|
||||
"cspell": "^9.2.0",
|
||||
"electron": "^37.3.1",
|
||||
"electron-builder": "^26.0.12",
|
||||
"electron-vite": "^4.0.0",
|
||||
|
|
@ -76,7 +70,7 @@
|
|||
"eslint-plugin-react": "^7.37.5",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.20",
|
||||
"eslint-plugin-sonarjs": "^3.0.4",
|
||||
"eslint-plugin-sonarjs": "^3.0.5",
|
||||
"globals": "^16.3.0",
|
||||
"husky": "^9.1.7",
|
||||
"jiti": "^2.5.1",
|
||||
|
|
@ -95,7 +89,7 @@
|
|||
"react": "^19.1.1",
|
||||
"react-dom": "^19.1.1",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"systeminformation": "^5.27.7",
|
||||
"systeminformation": "^5.27.8",
|
||||
"winston": "^3.17.0",
|
||||
"winston-daily-rotate-file": "^5.0.0",
|
||||
"zustand": "^5.0.8"
|
||||
|
|
@ -168,7 +162,7 @@
|
|||
]
|
||||
},
|
||||
"nsis": {
|
||||
"artifactName": "${productName} Setup ${version}.${ext}",
|
||||
"artifactName": "${productName}-Setup-${version}.${ext}",
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"createDesktopShortcut": true,
|
||||
|
|
@ -176,11 +170,11 @@
|
|||
"shortcutName": "Friendly Kobold"
|
||||
},
|
||||
"portable": {
|
||||
"artifactName": "${productName} Portable ${version}.${ext}"
|
||||
"artifactName": "${productName}-Portable-${version}.${ext}"
|
||||
},
|
||||
"linux": {
|
||||
"compression": "store",
|
||||
"category": "Development",
|
||||
"category": "Utility",
|
||||
"desktop": {
|
||||
"entry": {
|
||||
"Name": "Friendly Kobold",
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import { useUpdateChecker } from '@/hooks/useUpdateChecker';
|
|||
import { useKoboldVersions } from '@/hooks/useKoboldVersions';
|
||||
import { UI } from '@/constants';
|
||||
import type { DownloadItem } from '@/types/electron';
|
||||
import type { InterfaceTab } from '@/types';
|
||||
|
||||
type Screen = 'welcome' | 'download' | 'launch' | 'interface';
|
||||
|
||||
|
|
@ -21,11 +22,9 @@ export const App = () => {
|
|||
const [settingsOpened, setSettingsOpened] = useState(false);
|
||||
const [hasInitialized, setHasInitialized] = useState(false);
|
||||
const [showEjectModal, setShowEjectModal] = useState(false);
|
||||
const [activeInterfaceTab, setActiveInterfaceTab] = useState<string | null>(
|
||||
'terminal'
|
||||
);
|
||||
const [isImageGenerationMode, setIsImageGenerationMode] =
|
||||
useState<boolean>(false);
|
||||
const [activeInterfaceTab, setActiveInterfaceTab] =
|
||||
useState<InterfaceTab>('terminal');
|
||||
const [isImageGenerationMode, setIsImageGenerationMode] = useState(false);
|
||||
|
||||
const {
|
||||
updateInfo: binaryUpdateInfo,
|
||||
|
|
|
|||
|
|
@ -12,14 +12,15 @@ import {
|
|||
} from '@mantine/core';
|
||||
import { Settings, ArrowLeft } from 'lucide-react';
|
||||
import { soundAssets, playSound, initializeAudio } from '@/utils';
|
||||
import type { InterfaceTab } from '@/types';
|
||||
import iconUrl from '/icon.png';
|
||||
|
||||
type Screen = 'welcome' | 'download' | 'launch' | 'interface';
|
||||
|
||||
interface AppHeaderProps {
|
||||
currentScreen: Screen | null;
|
||||
activeInterfaceTab: string | null;
|
||||
setActiveInterfaceTab: (tab: string | null) => void;
|
||||
activeInterfaceTab: InterfaceTab;
|
||||
setActiveInterfaceTab: (tab: InterfaceTab) => void;
|
||||
isImageGenerationMode: boolean;
|
||||
onEject: () => void;
|
||||
onSettingsOpen: () => void;
|
||||
|
|
@ -109,8 +110,10 @@ export const AppHeader = ({
|
|||
|
||||
{currentScreen === 'interface' && (
|
||||
<Select
|
||||
value={activeInterfaceTab || 'terminal'}
|
||||
onChange={(value) => setActiveInterfaceTab(value || 'terminal')}
|
||||
value={activeInterfaceTab}
|
||||
onChange={(value) =>
|
||||
setActiveInterfaceTab((value || 'terminal') as InterfaceTab)
|
||||
}
|
||||
data={[
|
||||
{
|
||||
value: 'chat',
|
||||
|
|
|
|||
23
src/components/LabelWithTooltip.tsx
Normal file
23
src/components/LabelWithTooltip.tsx
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { Group, Text } from '@mantine/core';
|
||||
import { InfoTooltip } from '@/components/InfoTooltip';
|
||||
|
||||
interface LabelWithTooltipProps {
|
||||
label: string;
|
||||
tooltip?: string;
|
||||
fontWeight?: number;
|
||||
marginBottom?: string;
|
||||
}
|
||||
|
||||
export const LabelWithTooltip = ({
|
||||
label,
|
||||
tooltip,
|
||||
fontWeight = 500,
|
||||
marginBottom = 'xs',
|
||||
}: LabelWithTooltipProps) => (
|
||||
<Group gap="xs" align="center" mb={marginBottom}>
|
||||
<Text size="sm" fw={fontWeight}>
|
||||
{label}
|
||||
</Text>
|
||||
{tooltip && <InfoTooltip label={tooltip} />}
|
||||
</Group>
|
||||
);
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { Group, TextInput, Button } from '@mantine/core';
|
||||
import { File, Search } from 'lucide-react';
|
||||
import { SectionHeader } from '@/components/SectionHeader';
|
||||
import { LabelWithTooltip } from '@/components/LabelWithTooltip';
|
||||
import { getInputValidationState } from '@/utils';
|
||||
import styles from '@/styles/layout.module.css';
|
||||
|
||||
|
|
@ -39,12 +39,7 @@ export const ModelFileField = ({
|
|||
|
||||
return (
|
||||
<div>
|
||||
<SectionHeader
|
||||
title={label}
|
||||
tooltip={tooltip}
|
||||
fontWeight={500}
|
||||
marginBottom="xs"
|
||||
/>
|
||||
<LabelWithTooltip label={label} tooltip={tooltip} />
|
||||
<Group gap="xs" align="flex-start">
|
||||
<div className={styles.flex1}>
|
||||
<TextInput
|
||||
|
|
|
|||
40
src/components/SelectWithTooltip.tsx
Normal file
40
src/components/SelectWithTooltip.tsx
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import type { CSSProperties } from 'react';
|
||||
import { Select } from '@mantine/core';
|
||||
import type { ComboboxItem } from '@mantine/core';
|
||||
import { LabelWithTooltip } from '@/components/LabelWithTooltip';
|
||||
|
||||
interface SelectWithTooltipProps {
|
||||
label: string;
|
||||
tooltip: string;
|
||||
value: string;
|
||||
onChange: (value: string | null, option: ComboboxItem) => void;
|
||||
data: { value: string; label: string }[];
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
clearable?: boolean;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
export const SelectWithTooltip = ({
|
||||
label,
|
||||
tooltip,
|
||||
value,
|
||||
onChange,
|
||||
data,
|
||||
placeholder,
|
||||
disabled = false,
|
||||
clearable = false,
|
||||
style,
|
||||
}: SelectWithTooltipProps) => (
|
||||
<div style={style}>
|
||||
<LabelWithTooltip label={label} tooltip={tooltip} />
|
||||
<Select
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
data={data}
|
||||
disabled={disabled}
|
||||
clearable={clearable}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
import { useRef } from 'react';
|
||||
import { Box, Text, Stack } from '@mantine/core';
|
||||
import { UI } from '@/constants';
|
||||
import type { ServerTabMode } from '@/types';
|
||||
|
||||
interface ServerTabProps {
|
||||
serverUrl?: string;
|
||||
isServerReady?: boolean;
|
||||
mode: 'chat' | 'image-generation';
|
||||
mode: ServerTabMode;
|
||||
}
|
||||
|
||||
export const ServerTab = ({
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
import { useState, useCallback } from 'react';
|
||||
import { ServerTab } from '@/components/screens/Interface/ServerTab';
|
||||
import { TerminalTab } from '@/components/screens/Interface/TerminalTab';
|
||||
import type { InterfaceTab } from '@/types';
|
||||
|
||||
interface InterfaceScreenProps {
|
||||
activeTab?: string | null;
|
||||
onTabChange?: (tab: string | null) => void;
|
||||
activeTab?: InterfaceTab | null;
|
||||
onTabChange?: (tab: InterfaceTab) => void;
|
||||
isImageGenerationMode?: boolean;
|
||||
}
|
||||
|
||||
|
|
@ -21,10 +22,10 @@ export const InterfaceScreen = ({
|
|||
setServerUrl(url);
|
||||
setIsServerReady(true);
|
||||
if (onTabChange) {
|
||||
onTabChange(isImageGenerationMode ? 'image' : 'chat');
|
||||
onTabChange('chat');
|
||||
}
|
||||
},
|
||||
[onTabChange, isImageGenerationMode]
|
||||
[onTabChange]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
@ -48,18 +49,6 @@ export const InterfaceScreen = ({
|
|||
mode={isImageGenerationMode ? 'image-generation' : 'chat'}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
display: activeTab === 'image' ? 'block' : 'none',
|
||||
}}
|
||||
>
|
||||
<ServerTab
|
||||
serverUrl={serverUrl}
|
||||
isServerReady={isServerReady}
|
||||
mode="image-generation"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
flex: 1,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import { Text, Group, Select, Checkbox, TextInput } from '@mantine/core';
|
|||
import { useState, useEffect, useRef } from 'react';
|
||||
import { InfoTooltip } from '@/components/InfoTooltip';
|
||||
import { BackendSelectItem } from '@/components/screens/Launch/GeneralTab/BackendSelectItem';
|
||||
import { GpuDeviceSelector } from '@/components/screens/Launch/GeneralTab/GpuDeviceSelector';
|
||||
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
||||
|
||||
interface BackendSelectorProps {
|
||||
|
|
@ -11,11 +12,9 @@ interface BackendSelectorProps {
|
|||
export const BackendSelector = ({ onBackendsReady }: BackendSelectorProps) => {
|
||||
const {
|
||||
backend,
|
||||
gpuDevice,
|
||||
gpuLayers,
|
||||
autoGpuLayers,
|
||||
handleBackendChange,
|
||||
handleGpuDeviceChange,
|
||||
handleGpuLayersChange,
|
||||
handleAutoGpuLayersChange,
|
||||
} = useLaunchConfig();
|
||||
|
|
@ -72,7 +71,7 @@ export const BackendSelector = ({ onBackendsReady }: BackendSelectorProps) => {
|
|||
return (
|
||||
<div>
|
||||
<Group justify="space-between" align="flex-start" mb="xs">
|
||||
<div style={{ flex: 1, marginRight: '2rem' }}>
|
||||
<div style={{ flex: 1, marginRight: '1rem' }}>
|
||||
<Group gap="xs" align="center" mb="xs">
|
||||
<Text size="sm" fw={500}>
|
||||
Backend
|
||||
|
|
@ -104,33 +103,6 @@ export const BackendSelector = ({ onBackendsReady }: BackendSelectorProps) => {
|
|||
);
|
||||
}}
|
||||
/>
|
||||
{(() => {
|
||||
const selectedBackend = availableBackends.find(
|
||||
(b) => b.value === backend
|
||||
);
|
||||
const isGpuBackend = backend === 'cuda' || backend === 'rocm';
|
||||
const hasMultipleDevices =
|
||||
selectedBackend?.devices && selectedBackend.devices.length > 1;
|
||||
|
||||
return (
|
||||
isGpuBackend &&
|
||||
hasMultipleDevices && (
|
||||
<Select
|
||||
label="GPU Device"
|
||||
placeholder="Select GPU device"
|
||||
value={gpuDevice.toString()}
|
||||
onChange={(value) =>
|
||||
value && handleGpuDeviceChange(parseInt(value, 10))
|
||||
}
|
||||
data={selectedBackend.devices!.map((device, index) => ({
|
||||
value: index.toString(),
|
||||
label: `GPU ${index}: ${device}`,
|
||||
}))}
|
||||
mt="xs"
|
||||
/>
|
||||
)
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1 }}>
|
||||
|
|
@ -169,6 +141,8 @@ export const BackendSelector = ({ onBackendsReady }: BackendSelectorProps) => {
|
|||
</Group>
|
||||
</div>
|
||||
</Group>
|
||||
|
||||
<GpuDeviceSelector availableBackends={availableBackends} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
100
src/components/screens/Launch/GeneralTab/GpuDeviceSelector.tsx
Normal file
100
src/components/screens/Launch/GeneralTab/GpuDeviceSelector.tsx
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
import { Text, Group, Select, TextInput } from '@mantine/core';
|
||||
import { InfoTooltip } from '@/components/InfoTooltip';
|
||||
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
||||
|
||||
interface GpuDeviceSelectorProps {
|
||||
availableBackends: Array<{
|
||||
value: string;
|
||||
label: string;
|
||||
devices?: string[];
|
||||
}>;
|
||||
}
|
||||
|
||||
export const GpuDeviceSelector = ({
|
||||
availableBackends,
|
||||
}: GpuDeviceSelectorProps) => {
|
||||
const {
|
||||
backend,
|
||||
gpuDeviceSelection,
|
||||
tensorSplit,
|
||||
handleGpuDeviceSelectionChange,
|
||||
handleTensorSplitChange,
|
||||
} = useLaunchConfig();
|
||||
|
||||
const selectedBackend = availableBackends.find((b) => b.value === backend);
|
||||
const isGpuBackend =
|
||||
backend === 'cuda' ||
|
||||
backend === 'rocm' ||
|
||||
backend === 'vulkan' ||
|
||||
backend === 'clblast';
|
||||
const hasMultipleDevices =
|
||||
selectedBackend?.devices && selectedBackend.devices.length > 1;
|
||||
const showTensorSplit =
|
||||
(backend === 'cuda' || backend === 'rocm' || backend === 'vulkan') &&
|
||||
hasMultipleDevices &&
|
||||
gpuDeviceSelection === 'all';
|
||||
|
||||
if (!isGpuBackend || !hasMultipleDevices) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const deviceOptions =
|
||||
backend === 'clblast'
|
||||
? selectedBackend.devices!.map((device, index) => ({
|
||||
value: index.toString(),
|
||||
label: `GPU ${index}: ${device}`,
|
||||
}))
|
||||
: [
|
||||
{ value: 'all', label: 'All GPUs' },
|
||||
...selectedBackend.devices!.map((device, index) => ({
|
||||
value: index.toString(),
|
||||
label: `GPU ${index}: ${device}`,
|
||||
})),
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Group align="flex-start" gap="md">
|
||||
<div style={{ flex: 1, marginRight: '1rem' }}>
|
||||
<Group gap="xs" align="center" mb="xs">
|
||||
<Text size="sm" fw={500}>
|
||||
GPU Device
|
||||
</Text>
|
||||
<InfoTooltip label="Select which GPU device(s) to use. Choose 'All GPUs' to use multiple devices with tensor splitting." />
|
||||
</Group>
|
||||
<Select
|
||||
placeholder="Select GPU device"
|
||||
value={gpuDeviceSelection}
|
||||
onChange={(value) => {
|
||||
if (value) {
|
||||
handleGpuDeviceSelectionChange(value);
|
||||
}
|
||||
}}
|
||||
data={deviceOptions}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div style={{ flex: 1 }}>
|
||||
{showTensorSplit && (
|
||||
<>
|
||||
<Group gap="xs" align="center" mb="xs">
|
||||
<Text size="sm" fw={500}>
|
||||
Tensor Split
|
||||
</Text>
|
||||
<InfoTooltip label='When using multiple GPUs this option controls how large tensors should be split across all GPUs. Uses a comma-separated list of non-negative values that assigns the proportion of data that each GPU should get in order. For example, "3,2" will assign 60% of the data to GPU 0 and 40% to GPU 1.' />
|
||||
</Group>
|
||||
<TextInput
|
||||
placeholder="e.g., 3,2 or 1,1,1"
|
||||
value={tensorSplit}
|
||||
onChange={(event) =>
|
||||
handleTensorSplitChange(event.target.value)
|
||||
}
|
||||
size="sm"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Group>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
import { Stack, Text, Group, TextInput, Button, Slider } from '@mantine/core';
|
||||
import { File, Search } from 'lucide-react';
|
||||
import { Stack, Text, Group, TextInput, Slider } from '@mantine/core';
|
||||
import { InfoTooltip } from '@/components/InfoTooltip';
|
||||
import { BackendSelector } from '@/components/screens/Launch/GeneralTab/BackendSelector';
|
||||
import { getInputValidationState } from '@/utils';
|
||||
import { ModelFileField } from '@/components/ModelFileField';
|
||||
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
||||
import styles from '@/styles/layout.module.css';
|
||||
|
||||
interface GeneralTabProps {
|
||||
onBackendsReady?: () => void;
|
||||
|
|
@ -19,57 +17,20 @@ export const GeneralTab = ({ onBackendsReady }: GeneralTabProps) => {
|
|||
handleContextSizeChangeWithStep,
|
||||
} = useLaunchConfig();
|
||||
|
||||
const validationState = getInputValidationState(modelPath);
|
||||
|
||||
const getHelperText = () => {
|
||||
if (!modelPath.trim()) return undefined;
|
||||
|
||||
if (validationState === 'invalid') {
|
||||
return 'Enter a valid URL or file path to the .gguf';
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<BackendSelector onBackendsReady={onBackendsReady} />
|
||||
|
||||
<div>
|
||||
<Text size="sm" fw={500} mb="xs">
|
||||
Text Model File
|
||||
</Text>
|
||||
<Group gap="xs" align="flex-start">
|
||||
<div className={styles.flex1}>
|
||||
<TextInput
|
||||
placeholder="Select a .gguf model file or enter a direct URL to file"
|
||||
value={modelPath}
|
||||
onChange={(e) => handleModelPathChange(e.target.value)}
|
||||
error={
|
||||
validationState === 'invalid' ? getHelperText() : undefined
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
onClick={handleSelectModelFile}
|
||||
variant="light"
|
||||
leftSection={<File size={16} />}
|
||||
>
|
||||
Browse
|
||||
</Button>
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.electronAPI.app.openExternal(
|
||||
'https://huggingface.co/models?pipeline_tag=text-generation&library=gguf&sort=trending'
|
||||
);
|
||||
}}
|
||||
variant="outline"
|
||||
leftSection={<Search size={16} />}
|
||||
>
|
||||
Search HF
|
||||
</Button>
|
||||
</Group>
|
||||
</div>
|
||||
<ModelFileField
|
||||
label="Text Model File"
|
||||
value={modelPath}
|
||||
placeholder="Select a .gguf model file or enter a direct URL to file"
|
||||
tooltip="Select a GGUF text generation model file for chat and completion tasks."
|
||||
onChange={handleModelPathChange}
|
||||
onSelectFile={handleSelectModelFile}
|
||||
showSearchHF
|
||||
searchUrl="https://huggingface.co/models?pipeline_tag=text-generation&library=gguf&sort=trending"
|
||||
/>
|
||||
|
||||
<div>
|
||||
<Group justify="space-between" align="center" mb="xs">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { Stack, Select } from '@mantine/core';
|
||||
import { Stack } from '@mantine/core';
|
||||
import { useState } from 'react';
|
||||
import { SectionHeader } from '@/components/SectionHeader';
|
||||
import { ModelFileField } from '@/components/ModelFileField';
|
||||
import { SelectWithTooltip } from '@/components/SelectWithTooltip';
|
||||
import { IMAGE_MODEL_PRESETS } from '@/utils';
|
||||
import { useLaunchConfig } from '@/hooks/useLaunchConfig';
|
||||
|
||||
|
|
@ -14,50 +14,54 @@ export const ImageGenerationTab = () => {
|
|||
sdphotomaker,
|
||||
sdvae,
|
||||
sdlora,
|
||||
sdconvdirect,
|
||||
handleSdmodelChange,
|
||||
handleSelectSdmodelFile,
|
||||
handleSdt5xxlChange,
|
||||
handleSelectSdt5xxlFile,
|
||||
handleSdcliplChange,
|
||||
handleSelectSdcliplFile,
|
||||
handleSdclipgChange,
|
||||
handleSelectSdclipgFile,
|
||||
handleSdphotomakerChange,
|
||||
handleSelectSdphotomakerFile,
|
||||
handleSdvaeChange,
|
||||
handleSelectSdvaeFile,
|
||||
handleSdloraChange,
|
||||
handleSelectSdloraFile,
|
||||
handleSdconvdirectChange,
|
||||
handleApplyPreset,
|
||||
handleSelectSdmodelFile,
|
||||
handleSelectSdt5xxlFile,
|
||||
handleSelectSdcliplFile,
|
||||
handleSelectSdclipgFile,
|
||||
handleSelectSdphotomakerFile,
|
||||
handleSelectSdvaeFile,
|
||||
handleSelectSdloraFile,
|
||||
} = useLaunchConfig();
|
||||
|
||||
const [selectedPreset, setSelectedPreset] = useState<string | null>(null);
|
||||
|
||||
return (
|
||||
<Stack gap="md">
|
||||
<div>
|
||||
<SectionHeader
|
||||
title="Model Preset"
|
||||
tooltip="Quick presets for popular image generation models with pre-configured encoders."
|
||||
fontWeight={500}
|
||||
marginBottom="xs"
|
||||
/>
|
||||
<Select
|
||||
placeholder="Choose a preset..."
|
||||
data={IMAGE_MODEL_PRESETS.map((preset) => ({
|
||||
value: preset.name,
|
||||
label: preset.name,
|
||||
}))}
|
||||
value={selectedPreset}
|
||||
onChange={(value) => {
|
||||
setSelectedPreset(value);
|
||||
if (value) {
|
||||
handleApplyPreset(value);
|
||||
}
|
||||
}}
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
<SelectWithTooltip
|
||||
label="Model Preset"
|
||||
tooltip="Quick presets for popular image generation models with pre-configured encoders."
|
||||
placeholder="Choose a preset..."
|
||||
data={IMAGE_MODEL_PRESETS.map((preset) => ({
|
||||
value: preset.name,
|
||||
label: preset.name,
|
||||
}))}
|
||||
value={selectedPreset ?? ''}
|
||||
onChange={(value) => {
|
||||
setSelectedPreset(value);
|
||||
if (value) {
|
||||
handleApplyPreset(value);
|
||||
} else {
|
||||
handleSdmodelChange('');
|
||||
handleSdt5xxlChange('');
|
||||
handleSdcliplChange('');
|
||||
handleSdclipgChange('');
|
||||
handleSdphotomakerChange('');
|
||||
handleSdvaeChange('');
|
||||
handleSdloraChange('');
|
||||
}
|
||||
}}
|
||||
clearable
|
||||
/>
|
||||
|
||||
<ModelFileField
|
||||
label="Image Gen. Model File"
|
||||
|
|
@ -123,6 +127,22 @@ export const ImageGenerationTab = () => {
|
|||
onChange={handleSdloraChange}
|
||||
onSelectFile={handleSelectSdloraFile}
|
||||
/>
|
||||
|
||||
<SelectWithTooltip
|
||||
label="Conv2D Direct"
|
||||
tooltip="May improve performance or reduce memory usage. WARNING: Might crash if not supported by your backend! Only enable if you're sure your GPU and drivers support it."
|
||||
value={sdconvdirect}
|
||||
onChange={(value) => {
|
||||
if (value === 'off' || value === 'vaeonly' || value === 'full') {
|
||||
handleSdconvdirectChange(value);
|
||||
}
|
||||
}}
|
||||
data={[
|
||||
{ value: 'off', label: 'Off' },
|
||||
{ value: 'vaeonly', label: 'VAE Only' },
|
||||
{ value: 'full', label: 'Full' },
|
||||
]}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -49,7 +49,8 @@ export const LaunchScreen = ({
|
|||
quantmatmul,
|
||||
usemmap,
|
||||
backend,
|
||||
gpuDevice,
|
||||
gpuDeviceSelection,
|
||||
tensorSplit,
|
||||
gpuPlatform,
|
||||
sdmodel,
|
||||
sdt5xxl,
|
||||
|
|
@ -58,6 +59,7 @@ export const LaunchScreen = ({
|
|||
sdphotomaker,
|
||||
sdvae,
|
||||
sdlora,
|
||||
sdconvdirect,
|
||||
parseAndApplyConfigFile,
|
||||
loadConfigFromFile,
|
||||
handleModelPathChange,
|
||||
|
|
@ -180,10 +182,8 @@ export const LaunchScreen = ({
|
|||
usecuda: backend === 'cuda' || backend === 'rocm',
|
||||
usevulkan: backend === 'vulkan',
|
||||
useclblast: backend === 'clblast',
|
||||
clBlastInfo:
|
||||
backend === 'clblast'
|
||||
? ([gpuDevice, gpuPlatform] as [number, number])
|
||||
: undefined,
|
||||
gpuDeviceSelection,
|
||||
tensorSplit,
|
||||
sdmodel,
|
||||
sdt5xxl,
|
||||
sdclipl,
|
||||
|
|
@ -285,7 +285,9 @@ export const LaunchScreen = ({
|
|||
failsafe,
|
||||
backend,
|
||||
lowvram,
|
||||
gpuDevice,
|
||||
gpuDeviceSelection,
|
||||
gpuPlatform,
|
||||
tensorSplit,
|
||||
quantmatmul,
|
||||
usemmap,
|
||||
additionalArguments,
|
||||
|
|
@ -295,6 +297,7 @@ export const LaunchScreen = ({
|
|||
sdphotomaker,
|
||||
sdvae,
|
||||
sdlora,
|
||||
sdconvdirect,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ export const useLaunchConfig = () => {
|
|||
quantmatmul: state.quantmatmul,
|
||||
usemmap: state.usemmap,
|
||||
backend: state.backend,
|
||||
gpuDevice: state.gpuDevice,
|
||||
gpuDeviceSelection: state.gpuDeviceSelection,
|
||||
tensorSplit: state.tensorSplit,
|
||||
gpuPlatform: state.gpuPlatform,
|
||||
sdmodel: state.sdmodel,
|
||||
sdt5xxl: state.sdt5xxl,
|
||||
|
|
@ -37,6 +38,7 @@ export const useLaunchConfig = () => {
|
|||
sdphotomaker: state.sdphotomaker,
|
||||
sdvae: state.sdvae,
|
||||
sdlora: state.sdlora,
|
||||
sdconvdirect: state.sdconvdirect,
|
||||
|
||||
handleGpuLayersChange: state.setGpuLayers,
|
||||
handleAutoGpuLayersChange: state.setAutoGpuLayers,
|
||||
|
|
@ -58,7 +60,8 @@ export const useLaunchConfig = () => {
|
|||
handleQuantmatmulChange: state.setQuantmatmul,
|
||||
handleUsemmapChange: state.setUsemmap,
|
||||
handleBackendChange: state.setBackend,
|
||||
handleGpuDeviceChange: state.setGpuDevice,
|
||||
handleGpuDeviceSelectionChange: state.setGpuDeviceSelection,
|
||||
handleTensorSplitChange: state.setTensorSplit,
|
||||
handleGpuPlatformChange: state.setGpuPlatform,
|
||||
handleSdmodelChange: state.setSdmodel,
|
||||
handleSdt5xxlChange: state.setSdt5xxl,
|
||||
|
|
@ -67,6 +70,7 @@ export const useLaunchConfig = () => {
|
|||
handleSdphotomakerChange: state.setSdphotomaker,
|
||||
handleSdvaeChange: state.setSdvae,
|
||||
handleSdloraChange: state.setSdlora,
|
||||
handleSdconvdirectChange: state.setSdconvdirect,
|
||||
|
||||
parseAndApplyConfigFile: state.parseAndApplyConfigFile,
|
||||
loadConfigFromFile: state.loadConfigFromFile,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { useState, useCallback } from 'react';
|
||||
import { parseCLBlastDevice } from '@/utils';
|
||||
import type { SdConvDirectMode } from '@/types';
|
||||
|
||||
interface UseLaunchLogicProps {
|
||||
modelPath: string;
|
||||
|
|
@ -25,7 +26,9 @@ interface LaunchArgs {
|
|||
failsafe: boolean;
|
||||
backend: string;
|
||||
lowvram: boolean;
|
||||
gpuDevice: number | string;
|
||||
gpuDeviceSelection: string;
|
||||
gpuPlatform: number;
|
||||
tensorSplit: string;
|
||||
quantmatmul: boolean;
|
||||
usemmap: boolean;
|
||||
additionalArguments: string;
|
||||
|
|
@ -35,20 +38,18 @@ interface LaunchArgs {
|
|||
sdphotomaker: string;
|
||||
sdvae: string;
|
||||
sdlora: string;
|
||||
sdconvdirect: SdConvDirectMode;
|
||||
}
|
||||
|
||||
const buildModelArgs = (
|
||||
isImageMode: boolean,
|
||||
isTextMode: boolean,
|
||||
modelPath: string,
|
||||
sdmodel: string,
|
||||
launchArgs: LaunchArgs
|
||||
): string[] => {
|
||||
const args: string[] = [];
|
||||
|
||||
if (isImageMode && isTextMode) {
|
||||
args.push('--sdmodel', sdmodel);
|
||||
} else if (isImageMode) {
|
||||
if (isImageMode) {
|
||||
args.push('--sdmodel', sdmodel);
|
||||
|
||||
const imageModels = [
|
||||
|
|
@ -65,6 +66,14 @@ const buildModelArgs = (
|
|||
args.push(flag, value);
|
||||
}
|
||||
});
|
||||
|
||||
if (launchArgs.flashattention) {
|
||||
args.push('--sdflashattention');
|
||||
}
|
||||
|
||||
if (launchArgs.sdconvdirect !== 'off') {
|
||||
args.push('--sdconvdirect', launchArgs.sdconvdirect);
|
||||
}
|
||||
} else {
|
||||
args.push('--model', modelPath);
|
||||
}
|
||||
|
|
@ -72,7 +81,10 @@ const buildModelArgs = (
|
|||
return args;
|
||||
};
|
||||
|
||||
const buildConfigArgs = (launchArgs: LaunchArgs): string[] => {
|
||||
const buildConfigArgs = (
|
||||
isImageMode: boolean,
|
||||
launchArgs: LaunchArgs
|
||||
): string[] => {
|
||||
const args: string[] = [];
|
||||
|
||||
if (launchArgs.autoGpuLayers) {
|
||||
|
|
@ -100,7 +112,7 @@ const buildConfigArgs = (launchArgs: LaunchArgs): string[] => {
|
|||
[launchArgs.nocertify, '--nocertify'],
|
||||
[launchArgs.websearch, '--websearch'],
|
||||
[launchArgs.noshift, '--noshift'],
|
||||
[launchArgs.flashattention, '--flashattention'],
|
||||
[!isImageMode && launchArgs.flashattention, '--flashattention'],
|
||||
[launchArgs.noavx2, '--noavx2'],
|
||||
[launchArgs.failsafe, '--failsafe'],
|
||||
[launchArgs.usemmap, '--usemmap'],
|
||||
|
|
@ -116,41 +128,87 @@ const buildConfigArgs = (launchArgs: LaunchArgs): string[] => {
|
|||
return args;
|
||||
};
|
||||
|
||||
const buildCudaArgs = (launchArgs: LaunchArgs): string[] => {
|
||||
const cudaArgs = ['--usecuda'];
|
||||
cudaArgs.push(launchArgs.lowvram ? 'lowvram' : 'normal');
|
||||
|
||||
if (launchArgs.gpuDeviceSelection === 'all') {
|
||||
cudaArgs.push('0');
|
||||
} else {
|
||||
cudaArgs.push(launchArgs.gpuDeviceSelection || '0');
|
||||
}
|
||||
|
||||
cudaArgs.push(launchArgs.quantmatmul ? 'mmq' : 'nommq');
|
||||
return cudaArgs;
|
||||
};
|
||||
|
||||
const buildVulkanArgs = (): string[] => ['--usevulkan'];
|
||||
|
||||
const buildClblastArgs = (launchArgs: LaunchArgs): string[] => {
|
||||
const clblastArgs = ['--useclblast'];
|
||||
|
||||
if (
|
||||
typeof launchArgs.gpuDeviceSelection === 'string' &&
|
||||
launchArgs.gpuDeviceSelection.includes(':')
|
||||
) {
|
||||
const parsed = parseCLBlastDevice(launchArgs.gpuDeviceSelection);
|
||||
if (parsed) {
|
||||
clblastArgs.push(
|
||||
parsed.platformIndex.toString(),
|
||||
parsed.deviceIndex.toString()
|
||||
);
|
||||
} else {
|
||||
clblastArgs.push(launchArgs.gpuPlatform.toString(), '0');
|
||||
}
|
||||
} else {
|
||||
clblastArgs.push(
|
||||
launchArgs.gpuPlatform.toString(),
|
||||
launchArgs.gpuDeviceSelection || '0'
|
||||
);
|
||||
}
|
||||
|
||||
return clblastArgs;
|
||||
};
|
||||
|
||||
const addTensorSplitArgs = (args: string[], launchArgs: LaunchArgs): void => {
|
||||
if (launchArgs.tensorSplit && launchArgs.tensorSplit.trim()) {
|
||||
const tensorValues = launchArgs.tensorSplit
|
||||
.split(',')
|
||||
.map((value) => value.trim())
|
||||
.filter((value) => value !== '' && !isNaN(Number(value)));
|
||||
|
||||
if (tensorValues.length > 0) {
|
||||
args.push('--tensorsplit', ...tensorValues);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const buildBackendArgs = (launchArgs: LaunchArgs): string[] => {
|
||||
const args: string[] = [];
|
||||
|
||||
if (launchArgs.backend && launchArgs.backend !== 'cpu') {
|
||||
if (launchArgs.backend === 'cuda' || launchArgs.backend === 'rocm') {
|
||||
const cudaArgs = ['--usecuda'];
|
||||
cudaArgs.push(launchArgs.lowvram ? 'lowvram' : 'normal');
|
||||
cudaArgs.push(
|
||||
typeof launchArgs.gpuDevice === 'string'
|
||||
? '0'
|
||||
: launchArgs.gpuDevice.toString()
|
||||
);
|
||||
cudaArgs.push(launchArgs.quantmatmul ? 'mmq' : 'nommq');
|
||||
args.push(...cudaArgs);
|
||||
} else if (launchArgs.backend === 'vulkan') {
|
||||
args.push('--usevulkan');
|
||||
} else if (launchArgs.backend === 'clblast') {
|
||||
const clblastArgs = ['--useclblast'];
|
||||
if (!launchArgs.backend || launchArgs.backend === 'cpu') {
|
||||
return args;
|
||||
}
|
||||
|
||||
if (typeof launchArgs.gpuDevice === 'string') {
|
||||
const parsed = parseCLBlastDevice(launchArgs.gpuDevice);
|
||||
if (parsed) {
|
||||
clblastArgs.push(
|
||||
parsed.deviceIndex.toString(),
|
||||
parsed.platformIndex.toString()
|
||||
);
|
||||
} else {
|
||||
clblastArgs.push('0', '0');
|
||||
}
|
||||
} else {
|
||||
clblastArgs.push(launchArgs.gpuDevice.toString(), '0');
|
||||
}
|
||||
const isTensorSplitSupported =
|
||||
launchArgs.backend === 'cuda' ||
|
||||
launchArgs.backend === 'rocm' ||
|
||||
launchArgs.backend === 'vulkan';
|
||||
|
||||
args.push(...clblastArgs);
|
||||
if (launchArgs.backend === 'cuda' || launchArgs.backend === 'rocm') {
|
||||
args.push(...buildCudaArgs(launchArgs));
|
||||
|
||||
if (launchArgs.gpuDeviceSelection === 'all' && isTensorSplitSupported) {
|
||||
addTensorSplitArgs(args, launchArgs);
|
||||
}
|
||||
} else if (launchArgs.backend === 'vulkan') {
|
||||
args.push(...buildVulkanArgs());
|
||||
|
||||
if (launchArgs.gpuDeviceSelection === 'all' && isTensorSplitSupported) {
|
||||
addTensorSplitArgs(args, launchArgs);
|
||||
}
|
||||
} else if (launchArgs.backend === 'clblast') {
|
||||
args.push(...buildClblastArgs(launchArgs));
|
||||
}
|
||||
|
||||
return args;
|
||||
|
|
@ -177,14 +235,8 @@ export const useLaunchLogic = ({
|
|||
|
||||
try {
|
||||
const args: string[] = [
|
||||
...buildModelArgs(
|
||||
isImageMode,
|
||||
isTextMode,
|
||||
modelPath,
|
||||
sdmodel,
|
||||
launchArgs
|
||||
),
|
||||
...buildConfigArgs(launchArgs),
|
||||
...buildModelArgs(isImageMode, modelPath, sdmodel, launchArgs),
|
||||
...buildConfigArgs(isImageMode, launchArgs),
|
||||
...buildBackendArgs(launchArgs),
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { create } from 'zustand';
|
||||
import type { ConfigFile } from '@/types';
|
||||
import type { ConfigFile, SdConvDirectMode } from '@/types';
|
||||
import type { ImageModelPreset } from '@/utils/imageModelPresets';
|
||||
import { DEFAULT_CONTEXT_SIZE } from '@/constants';
|
||||
|
||||
|
|
@ -24,7 +24,8 @@ interface LaunchConfigState {
|
|||
quantmatmul: boolean;
|
||||
usemmap: boolean;
|
||||
backend: string;
|
||||
gpuDevice: number;
|
||||
gpuDeviceSelection: string;
|
||||
tensorSplit: string;
|
||||
gpuPlatform: number;
|
||||
sdmodel: string;
|
||||
sdt5xxl: string;
|
||||
|
|
@ -33,6 +34,7 @@ interface LaunchConfigState {
|
|||
sdphotomaker: string;
|
||||
sdvae: string;
|
||||
sdlora: string;
|
||||
sdconvdirect: SdConvDirectMode;
|
||||
|
||||
setGpuLayers: (layers: number) => void;
|
||||
setAutoGpuLayers: (auto: boolean) => void;
|
||||
|
|
@ -54,7 +56,8 @@ interface LaunchConfigState {
|
|||
setQuantmatmul: (quantmatmul: boolean) => void;
|
||||
setUsemmap: (usemmap: boolean) => void;
|
||||
setBackend: (backend: string) => void;
|
||||
setGpuDevice: (device: number) => void;
|
||||
setGpuDeviceSelection: (selection: string) => void;
|
||||
setTensorSplit: (split: string) => void;
|
||||
setGpuPlatform: (platform: number) => void;
|
||||
setSdmodel: (model: string) => void;
|
||||
setSdt5xxl: (model: string) => void;
|
||||
|
|
@ -63,6 +66,7 @@ interface LaunchConfigState {
|
|||
setSdphotomaker: (model: string) => void;
|
||||
setSdvae: (vae: string) => void;
|
||||
setSdlora: (loraModel: string) => void;
|
||||
setSdconvdirect: (mode: SdConvDirectMode) => void;
|
||||
|
||||
parseAndApplyConfigFile: (configPath: string) => Promise<void>;
|
||||
loadConfigFromFile: (
|
||||
|
|
@ -102,7 +106,8 @@ export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
|
|||
quantmatmul: true,
|
||||
usemmap: true,
|
||||
backend: '',
|
||||
gpuDevice: 0,
|
||||
gpuDeviceSelection: '0',
|
||||
tensorSplit: '',
|
||||
gpuPlatform: 0,
|
||||
sdmodel: '',
|
||||
sdt5xxl: '',
|
||||
|
|
@ -111,6 +116,7 @@ export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
|
|||
sdphotomaker: '',
|
||||
sdvae: '',
|
||||
sdlora: '',
|
||||
sdconvdirect: 'off' as const,
|
||||
|
||||
setGpuLayers: (layers) => set({ gpuLayers: layers }),
|
||||
setAutoGpuLayers: (auto) => set({ autoGpuLayers: auto }),
|
||||
|
|
@ -131,8 +137,14 @@ export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
|
|||
setLowvram: (lowvram) => set({ lowvram }),
|
||||
setQuantmatmul: (quantmatmul) => set({ quantmatmul }),
|
||||
setUsemmap: (usemmap) => set({ usemmap }),
|
||||
setBackend: (backend) => set({ backend }),
|
||||
setGpuDevice: (device) => set({ gpuDevice: device }),
|
||||
setBackend: (backend) =>
|
||||
set({
|
||||
backend,
|
||||
gpuDeviceSelection: '0',
|
||||
tensorSplit: '',
|
||||
}),
|
||||
setGpuDeviceSelection: (selection) => set({ gpuDeviceSelection: selection }),
|
||||
setTensorSplit: (split) => set({ tensorSplit: split }),
|
||||
setGpuPlatform: (platform) => set({ gpuPlatform: platform }),
|
||||
setSdmodel: (model) => set({ sdmodel: model }),
|
||||
setSdt5xxl: (model) => set({ sdt5xxl: model }),
|
||||
|
|
@ -141,6 +153,7 @@ export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
|
|||
setSdphotomaker: (model) => set({ sdphotomaker: model }),
|
||||
setSdvae: (vae) => set({ sdvae: vae }),
|
||||
setSdlora: (loraModel) => set({ sdlora: loraModel }),
|
||||
setSdconvdirect: (mode) => set({ sdconvdirect: mode }),
|
||||
|
||||
// eslint-disable-next-line sonarjs/cognitive-complexity
|
||||
parseAndApplyConfigFile: async (configPath: string) => {
|
||||
|
|
@ -262,7 +275,7 @@ export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
|
|||
) {
|
||||
const [vramMode, deviceId, mmqMode] = configData.usecuda;
|
||||
updates.lowvram = vramMode === 'lowvram';
|
||||
updates.gpuDevice = parseInt(deviceId, 10) || 0;
|
||||
updates.gpuDeviceSelection = deviceId || '0';
|
||||
updates.quantmatmul = mmqMode === 'mmq';
|
||||
}
|
||||
} else if (configData.usevulkan === true) {
|
||||
|
|
@ -273,12 +286,20 @@ export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
|
|||
) {
|
||||
updates.backend = 'clblast';
|
||||
const [deviceIndex, platformIndex] = configData.useclblast;
|
||||
updates.gpuDevice = deviceIndex;
|
||||
updates.gpuDeviceSelection = deviceIndex.toString();
|
||||
updates.gpuPlatform = platformIndex;
|
||||
} else {
|
||||
updates.backend = 'cpu';
|
||||
}
|
||||
|
||||
if (typeof configData.gpuDeviceSelection === 'string') {
|
||||
updates.gpuDeviceSelection = configData.gpuDeviceSelection;
|
||||
}
|
||||
|
||||
if (typeof configData.tensorSplit === 'string') {
|
||||
updates.tensorSplit = configData.tensorSplit;
|
||||
}
|
||||
|
||||
if (typeof configData.sdmodel === 'string') {
|
||||
updates.sdmodel = configData.sdmodel;
|
||||
}
|
||||
|
|
@ -306,6 +327,12 @@ export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
|
|||
if (typeof configData.sdlora === 'string') {
|
||||
updates.sdlora = configData.sdlora;
|
||||
}
|
||||
if (
|
||||
typeof configData.sdconvdirect === 'string' &&
|
||||
['off', 'vaeonly', 'full'].includes(configData.sdconvdirect)
|
||||
) {
|
||||
updates.sdconvdirect = configData.sdconvdirect as SdConvDirectMode;
|
||||
}
|
||||
|
||||
set(updates);
|
||||
}
|
||||
|
|
@ -396,6 +423,7 @@ export const useLaunchConfigStore = create<LaunchConfigState>((set, get) => ({
|
|||
|
||||
applyImageModelPreset: (preset: ImageModelPreset) => {
|
||||
set({
|
||||
sdmodel: preset.sdmodel,
|
||||
sdt5xxl: preset.sdt5xxl,
|
||||
sdclipl: preset.sdclipl,
|
||||
sdclipg: preset.sdclipg || '',
|
||||
|
|
|
|||
1
src/types/electron.d.ts
vendored
1
src/types/electron.d.ts
vendored
|
|
@ -121,7 +121,6 @@ export interface KoboldAPI {
|
|||
usecuda?: boolean;
|
||||
usevulkan?: boolean;
|
||||
useclblast?: boolean;
|
||||
clBlastInfo?: [number, number];
|
||||
sdmodel?: string;
|
||||
sdt5xxl?: string;
|
||||
sdclipl?: string;
|
||||
|
|
|
|||
6
src/types/index.d.ts
vendored
6
src/types/index.d.ts
vendored
|
|
@ -4,6 +4,12 @@ export interface ConfigFile {
|
|||
size: number;
|
||||
}
|
||||
|
||||
export type InterfaceTab = 'terminal' | 'chat';
|
||||
|
||||
export type SdConvDirectMode = 'off' | 'vaeonly' | 'full';
|
||||
|
||||
export type ServerTabMode = 'chat' | 'image-generation';
|
||||
|
||||
export interface GitHubAsset {
|
||||
name: string;
|
||||
browser_download_url: string;
|
||||
|
|
|
|||
|
|
@ -38,14 +38,4 @@ export const IMAGE_MODEL_PRESETS: ImageModelPreset[] = [
|
|||
sdvae:
|
||||
'https://huggingface.co/lodestones/Chroma/resolve/main/ae.safetensors?download=true',
|
||||
},
|
||||
{
|
||||
name: 'Custom',
|
||||
description: 'Start with empty fields for custom configuration',
|
||||
sdmodel: '',
|
||||
sdt5xxl: '',
|
||||
sdclipl: '',
|
||||
sdclipg: '',
|
||||
sdphotomaker: '',
|
||||
sdvae: '',
|
||||
},
|
||||
];
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue