don't need to recommend ROCm as it's not that good compared to generally available Vulkan

This commit is contained in:
Egor 2025-08-20 10:48:48 -07:00
parent f2aaa6ff4c
commit ed4fa55478
8 changed files with 42 additions and 208 deletions

View file

@ -9,7 +9,6 @@ A koboldcpp manager. <!-- markdownlint-disable MD033 -->
- modern UI for [koboldcpp](https://github.com/LostRuins/koboldcpp) with full support for Linux Wayland - modern UI for [koboldcpp](https://github.com/LostRuins/koboldcpp) with full support for Linux Wayland
- download and keep up-to-date your [koboldcpp](https://github.com/LostRuins/koboldcpp/releases) binary - download and keep up-to-date your [koboldcpp](https://github.com/LostRuins/koboldcpp/releases) binary
- better surface the ROCm-specific builds of koboldcpp from YellowRoseCx and from [koboldai.org](https://koboldai.org/cpplinuxrocm)
- manage the koboldcpp binary to prevent it from running in the background indefinitely - manage the koboldcpp binary to prevent it from running in the background indefinitely
- automatically unpack all downloaded koboldcpp binaries for significantly faster operation and reduced RAM+HDD utilization (up to ~4GB less RAM usage for ROCm) - automatically unpack all downloaded koboldcpp binaries for significantly faster operation and reduced RAM+HDD utilization (up to ~4GB less RAM usage for ROCm)
- added presets for a basic flux or chroma image generation setup - added presets for a basic flux or chroma image generation setup

View file

@ -113,13 +113,7 @@ export const App = () => {
const handleBinaryUpdate = async (download: DownloadItem) => { const handleBinaryUpdate = async (download: DownloadItem) => {
try { try {
const downloadType = download.type === 'rocm' ? 'rocm' : 'asset'; const success = await sharedHandleDownload('asset', download, true, true);
const success = await sharedHandleDownload(
downloadType,
download,
true,
true
);
if (success) { if (success) {
dismissUpdate(); dismissUpdate();

View file

@ -18,7 +18,6 @@ interface DownloadCardProps {
size: string; size: string;
version?: string; version?: string;
description?: string; description?: string;
isRecommended?: boolean;
isCurrent?: boolean; isCurrent?: boolean;
isInstalled?: boolean; isInstalled?: boolean;
isDownloading?: boolean; isDownloading?: boolean;
@ -36,7 +35,6 @@ export const DownloadCard = ({
size, size,
version, version,
description, description,
isRecommended = false,
isCurrent = false, isCurrent = false,
isInstalled = false, isInstalled = false,
isDownloading = false, isDownloading = false,
@ -122,11 +120,6 @@ export const DownloadCard = ({
Current Current
</Badge> </Badge>
)} )}
{isRecommended && (
<Badge variant="light" color="blue" size="sm">
Recommended
</Badge>
)}
{hasUpdate && ( {hasUpdate && (
<Badge variant="light" color="orange" size="sm"> <Badge variant="light" color="orange" size="sm">
Update Available Update Available

View file

@ -1,20 +1,8 @@
import { useState, useCallback, useEffect, useRef } from 'react'; import { useState, useCallback, useEffect, useRef } from 'react';
import { import { Card, Text, Title, Loader, Stack, Container } from '@mantine/core';
Card,
Text,
Title,
Loader,
Stack,
Container,
Anchor,
} from '@mantine/core';
import { DownloadCard } from '@/components/DownloadCard'; import { DownloadCard } from '@/components/DownloadCard';
import { getPlatformDisplayName, formatDownloadSize } from '@/utils'; import { getPlatformDisplayName, formatDownloadSize } from '@/utils';
import { import { getAssetDescription, sortDownloadsByType } from '@/utils/assets';
isAssetRecommended,
sortAssetsByRecommendation,
getAssetDescription,
} from '@/utils/assets';
import { useKoboldVersions } from '@/hooks/useKoboldVersions'; import { useKoboldVersions } from '@/hooks/useKoboldVersions';
import type { DownloadItem } from '@/types/electron'; import type { DownloadItem } from '@/types/electron';
@ -33,27 +21,20 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
handleDownload: sharedHandleDownload, handleDownload: sharedHandleDownload,
} = useKoboldVersions(); } = useKoboldVersions();
const [downloadingType, setDownloadingType] = useState<
'asset' | 'rocm' | null
>(null);
const [downloadingAsset, setDownloadingAsset] = useState<string | null>(null); const [downloadingAsset, setDownloadingAsset] = useState<string | null>(null);
const downloadingItemRef = useRef<HTMLDivElement>(null); const downloadingItemRef = useRef<HTMLDivElement>(null);
const loading = loadingPlatform || loadingRemote; const loading = loadingPlatform || loadingRemote;
const regularDownloads = availableDownloads.filter((d) => d.type === 'asset'); const sortedDownloads = sortDownloadsByType(availableDownloads);
const rocmDownload = availableDownloads.find((d) => d.type === 'rocm');
const handleDownload = useCallback( const handleDownload = useCallback(
async (type: 'asset' | 'rocm', download?: DownloadItem) => { async (download: DownloadItem) => {
if (type === 'asset' && !download) return; setDownloadingAsset(download.name);
setDownloadingType(type);
setDownloadingAsset(type === 'asset' ? download!.name : null);
try { try {
const success = await sharedHandleDownload( const success = await sharedHandleDownload(
type, 'asset',
download, download,
false, false,
false false
@ -63,17 +44,15 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
onDownloadComplete(); onDownloadComplete();
setTimeout(() => { setTimeout(() => {
setDownloadingType(null);
setDownloadingAsset(null); setDownloadingAsset(null);
}, 200); }, 200);
} }
} catch (error) { } catch (error) {
window.electronAPI.logs.logError( window.electronAPI.logs.logError(
`Failed to download ${type}:`, `Failed to download ${download.name}:`,
error as Error error as Error
); );
} finally { } finally {
setDownloadingType(null);
setDownloadingAsset(null); setDownloadingAsset(null);
} }
}, },
@ -89,38 +68,6 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
} }
}, [downloading]); }, [downloading]);
const renderROCmCard = () => {
if (!rocmDownload) return null;
const isDownloading = Boolean(downloading) && downloadingType === 'rocm';
return (
<div ref={isDownloading ? downloadingItemRef : null}>
<DownloadCard
name={rocmDownload.name}
size={formatDownloadSize(rocmDownload.size, rocmDownload.url, true)}
description={getAssetDescription(rocmDownload.name)}
version={rocmDownload.version}
isRecommended={isAssetRecommended(
rocmDownload.name,
platformInfo.hasAMDGPU
)}
isDownloading={isDownloading}
downloadProgress={
downloadingType === 'rocm'
? downloadProgress[rocmDownload.name] || 0
: 0
}
disabled={Boolean(downloading) && downloadingType !== 'rocm'}
onDownload={(e) => {
e.stopPropagation();
handleDownload('rocm', rocmDownload);
}}
/>
</div>
);
};
return ( return (
<Container size="sm"> <Container size="sm">
<Stack gap="xl"> <Stack gap="xl">
@ -139,54 +86,9 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
<> <>
{availableDownloads.length > 0 ? ( {availableDownloads.length > 0 ? (
<Stack gap="sm"> <Stack gap="sm">
{platformInfo.hasAMDGPU && {sortedDownloads.map((download) => {
!platformInfo.hasROCm &&
platformInfo.platform === 'linux' && (
<Card withBorder p="md" bg="orange.0">
<Stack gap="xs">
<div
style={{
display: 'flex',
alignItems: 'center',
gap: '8px',
}}
>
<Text fw={600} c="orange.9">
AMD GPU Detected
</Text>
</div>
<Text size="sm" c="orange.8">
For best performance with your AMD GPU,
consider installing ROCm support.{' '}
<Anchor
href="#"
onClick={(e) => {
e.preventDefault();
window.electronAPI.app.openExternal(
'https://rocm.docs.amd.com/projects/install-on-linux/en/latest/reference/system-requirements.html'
);
}}
size="sm"
c="orange.8"
>
Learn more
</Anchor>
</Text>
</Stack>
</Card>
)}
{rocmDownload &&
platformInfo.hasAMDGPU &&
renderROCmCard()}
{sortAssetsByRecommendation(
regularDownloads,
platformInfo.hasAMDGPU
).map((download) => {
const isDownloading = const isDownloading =
Boolean(downloading) && Boolean(downloading) &&
downloadingType === 'asset' &&
downloadingAsset === download.name; downloadingAsset === download.name;
return ( return (
@ -202,10 +104,6 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
)} )}
version={download.version} version={download.version}
description={getAssetDescription(download.name)} description={getAssetDescription(download.name)}
isRecommended={isAssetRecommended(
download.name,
platformInfo.hasAMDGPU
)}
isDownloading={isDownloading} isDownloading={isDownloading}
downloadProgress={ downloadProgress={
isDownloading isDownloading
@ -218,16 +116,12 @@ export const DownloadScreen = ({ onDownloadComplete }: DownloadScreenProps) => {
} }
onDownload={(e) => { onDownload={(e) => {
e.stopPropagation(); e.stopPropagation();
handleDownload('asset', download); handleDownload(download);
}} }}
/> />
</div> </div>
); );
})} })}
{rocmDownload &&
!platformInfo.hasAMDGPU &&
renderROCmCard()}
</Stack> </Stack>
) : ( ) : (
<Card withBorder p="md" bg="yellow.0" c="yellow.9"> <Card withBorder p="md" bg="yellow.0" c="yellow.9">

View file

@ -12,11 +12,7 @@ import {
} from '@mantine/core'; } from '@mantine/core';
import { RotateCcw, ExternalLink } from 'lucide-react'; import { RotateCcw, ExternalLink } from 'lucide-react';
import { DownloadCard } from '@/components/DownloadCard'; import { DownloadCard } from '@/components/DownloadCard';
import { import { getAssetDescription, sortDownloadsByType } from '@/utils/assets';
getAssetDescription,
sortAssetsByRecommendation,
isAssetRecommended,
} from '@/utils/assets';
import { import {
getDisplayNameFromPath, getDisplayNameFromPath,
formatDownloadSize, formatDownloadSize,
@ -34,14 +30,12 @@ interface VersionInfo {
isCurrent: boolean; isCurrent: boolean;
downloadUrl?: string; downloadUrl?: string;
installedPath?: string; installedPath?: string;
isROCm?: boolean;
hasUpdate?: boolean; hasUpdate?: boolean;
newerVersion?: string; newerVersion?: string;
} }
export const VersionsTab = () => { export const VersionsTab = () => {
const { const {
platformInfo,
availableDownloads, availableDownloads,
loadingPlatform, loadingPlatform,
loadingRemote, loadingRemote,
@ -131,7 +125,9 @@ export const VersionsTab = () => {
const versions: VersionInfo[] = []; const versions: VersionInfo[] = [];
const processedInstalled = new Set<string>(); const processedInstalled = new Set<string>();
availableDownloads.forEach((download) => { const sortedDownloads = sortDownloadsByType(availableDownloads);
sortedDownloads.forEach((download) => {
const downloadBaseName = stripAssetExtensions(download.name); const downloadBaseName = stripAssetExtensions(download.name);
const installedVersion = installedVersions.find((v) => { const installedVersion = installedVersions.find((v) => {
@ -162,7 +158,6 @@ export const VersionsTab = () => {
isCurrent, isCurrent,
downloadUrl: download.url, downloadUrl: download.url,
installedPath: installedVersion.path, installedPath: installedVersion.path,
isROCm: download.type === 'rocm',
hasUpdate, hasUpdate,
newerVersion: hasUpdate ? download.version : undefined, newerVersion: hasUpdate ? download.version : undefined,
}); });
@ -174,7 +169,6 @@ export const VersionsTab = () => {
isInstalled: false, isInstalled: false,
isCurrent: false, isCurrent: false,
downloadUrl: download.url, downloadUrl: download.url,
isROCm: download.type === 'rocm',
}); });
} }
}); });
@ -193,18 +187,12 @@ export const VersionsTab = () => {
isInstalled: true, isInstalled: true,
isCurrent, isCurrent,
installedPath: installed.path, installedPath: installed.path,
isROCm: false,
}); });
} }
}); });
return sortAssetsByRecommendation(versions, platformInfo.hasAMDGPU); return versions;
}, [ }, [availableDownloads, installedVersions, currentVersion]);
availableDownloads,
installedVersions,
currentVersion,
platformInfo.hasAMDGPU,
]);
useEffect(() => { useEffect(() => {
if (downloading && downloadingItemRef.current) { if (downloading && downloadingItemRef.current) {
@ -222,9 +210,8 @@ export const VersionsTab = () => {
throw new Error('Download not found'); throw new Error('Download not found');
} }
const downloadType = download.type === 'rocm' ? 'rocm' : 'asset';
const success = await sharedHandleDownload( const success = await sharedHandleDownload(
downloadType, 'asset',
download, download,
false, false,
false false
@ -245,10 +232,9 @@ export const VersionsTab = () => {
throw new Error('Download not found'); throw new Error('Download not found');
} }
const downloadType = download.type === 'rocm' ? 'rocm' : 'asset';
const wasCurrentBinary = version.isCurrent; const wasCurrentBinary = version.isCurrent;
const success = await sharedHandleDownload( const success = await sharedHandleDownload(
downloadType, 'asset',
download, download,
true, true,
wasCurrentBinary wasCurrentBinary
@ -368,19 +354,11 @@ export const VersionsTab = () => {
name={version.name} name={version.name}
size={ size={
version.size version.size
? formatDownloadSize( ? formatDownloadSize(version.size, version.downloadUrl)
version.size,
version.downloadUrl,
version.isROCm
)
: '' : ''
} }
version={version.version} version={version.version}
description={getAssetDescription(version.name)} description={getAssetDescription(version.name)}
isRecommended={isAssetRecommended(
version.name,
platformInfo.hasAMDGPU
)}
isCurrent={version.isCurrent} isCurrent={version.isCurrent}
isInstalled={version.isInstalled} isInstalled={version.isInstalled}
isDownloading={isDownloading} isDownloading={isDownloading}

View file

@ -38,10 +38,6 @@ export const KOBOLDAI_URLS = {
DOMAIN: 'koboldai.org', DOMAIN: 'koboldai.org',
} as const; } as const;
export const GITHUB_URLS = {
DOMAIN: 'github.com',
} as const;
export const ROCM = { export const ROCM = {
BINARY_NAME: 'koboldcpp-linux-x64-rocm', BINARY_NAME: 'koboldcpp-linux-x64-rocm',
DOWNLOAD_URL: KOBOLDAI_URLS.ROCM_DOWNLOAD, DOWNLOAD_URL: KOBOLDAI_URLS.ROCM_DOWNLOAD,

View file

@ -5,11 +5,11 @@ export const getAssetDescription = (assetName: string): string => {
const name = stripAssetExtensions(assetName).toLowerCase(); const name = stripAssetExtensions(assetName).toLowerCase();
if (name.includes(ASSET_SUFFIXES.ROCM)) { if (name.includes(ASSET_SUFFIXES.ROCM)) {
return 'Optimized for AMD GPUs with ROCm support.'; return 'Adds dedicated ROCm support for AMD GPUs. Note that ROCm is unlikely to perform better than modern Vulkan in most cases.';
} }
if (name.endsWith(ASSET_SUFFIXES.OLDPC)) { if (name.endsWith(ASSET_SUFFIXES.OLDPC)) {
return 'Meant for old PCs that cannot run the standard build.'; return 'Meant for old PCs with outdated CPUs that may not work with the standard build. Does not support modern AVX2 CPU architectures.';
} }
if (name.endsWith(ASSET_SUFFIXES.NOCUDA)) { if (name.endsWith(ASSET_SUFFIXES.NOCUDA)) {
@ -19,24 +19,6 @@ export const getAssetDescription = (assetName: string): string => {
return "Standard build that's ideal for most cases."; return "Standard build that's ideal for most cases.";
}; };
export const isAssetRecommended = (
assetName: string,
hasAMDGPU: boolean
): boolean => {
const name = stripAssetExtensions(assetName).toLowerCase();
if (hasAMDGPU && name.includes(ASSET_SUFFIXES.ROCM)) {
return true;
}
return (
!hasAMDGPU &&
!name.includes(ASSET_SUFFIXES.ROCM) &&
!name.endsWith(ASSET_SUFFIXES.OLDPC) &&
!name.endsWith(ASSET_SUFFIXES.NOCUDA)
);
};
export const isAssetStandard = (assetName: string): boolean => { export const isAssetStandard = (assetName: string): boolean => {
const name = stripAssetExtensions(assetName).toLowerCase(); const name = stripAssetExtensions(assetName).toLowerCase();
@ -47,23 +29,27 @@ export const isAssetStandard = (assetName: string): boolean => {
); );
}; };
export const sortAssetsByRecommendation = <T extends { name: string }>( export const sortDownloadsByType = <T extends { name: string }>(
assets: T[], downloads: T[]
hasAMDGPU: boolean
): T[] => ): T[] =>
[...assets].sort((a, b) => { [...downloads].sort((a, b) => {
const aRecommended = isAssetRecommended(a.name, hasAMDGPU); const aName = stripAssetExtensions(a.name).toLowerCase();
const bRecommended = isAssetRecommended(b.name, hasAMDGPU); const bName = stripAssetExtensions(b.name).toLowerCase();
const aStandard = isAssetStandard(a.name);
const bStandard = isAssetStandard(b.name);
if (aRecommended && !bRecommended) return -1; const getOrderPriority = (name: string): number => {
if (!aRecommended && bRecommended) return 1; if (isAssetStandard(name)) return 0;
if (name.includes(ASSET_SUFFIXES.ROCM)) return 1;
if (name.endsWith(ASSET_SUFFIXES.NOCUDA)) return 2;
if (name.endsWith(ASSET_SUFFIXES.OLDPC)) return 3;
return 4;
};
if (aRecommended === bRecommended) { const aPriority = getOrderPriority(aName);
if (aStandard && !bStandard) return -1; const bPriority = getOrderPriority(bName);
if (!aStandard && bStandard) return 1;
if (aPriority !== bPriority) {
return aPriority - bPriority;
} }
return 0; return a.name.localeCompare(b.name);
}); });

View file

@ -1,16 +1,10 @@
import { formatFileSizeInMB } from '@/utils/fileSize'; import { formatFileSizeInMB } from '@/utils/fileSize';
import { KOBOLDAI_URLS, GITHUB_URLS } from '@/constants'; import { KOBOLDAI_URLS } from '@/constants';
export const formatDownloadSize = ( export const formatDownloadSize = (size: number, url?: string): string => {
size: number,
url?: string,
isROCm?: boolean
): string => {
if (!size) return ''; if (!size) return '';
const isApproximateSize = const isApproximateSize = url?.includes(KOBOLDAI_URLS.DOMAIN);
url?.includes(KOBOLDAI_URLS.DOMAIN) ||
(isROCm && !url?.includes(GITHUB_URLS.DOMAIN));
return isApproximateSize return isApproximateSize
? `~${formatFileSizeInMB(size)}` ? `~${formatFileSizeInMB(size)}`