improve gguf analysis to work with http models and present the data better

This commit is contained in:
Egor 2025-10-25 17:26:32 -07:00
parent 2480106943
commit 07eab89e58
4 changed files with 47 additions and 74 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "gerbil", "name": "gerbil",
"productName": "Gerbil", "productName": "Gerbil",
"version": "1.7.4", "version": "1.7.5",
"description": "Run Large Language Models locally", "description": "Run Large Language Models locally",
"main": "out/main/index.js", "main": "out/main/index.js",
"homepage": "./", "homepage": "./",

View file

@ -1,5 +1,5 @@
import { Modal } from '@/components/Modal'; import { Modal } from '@/components/Modal';
import { Stack, Group, Text, Divider, Alert, rem } from '@mantine/core'; import { Stack, Group, Text, Alert, rem } from '@mantine/core';
import { Info } from 'lucide-react'; import { Info } from 'lucide-react';
import type { ModelAnalysis } from '@/types'; import type { ModelAnalysis } from '@/types';
@ -21,7 +21,7 @@ const InfoRow = ({ label, value }: InfoRowProps) => {
return ( return (
<Group gap="md" wrap="nowrap"> <Group gap="md" wrap="nowrap">
<Text size="sm" c="dimmed" style={{ minWidth: rem(160) }}> <Text size="sm" c="dimmed" style={{ minWidth: rem(150) }}>
{label}: {label}:
</Text> </Text>
<Text size="sm" fw={500}> <Text size="sm" fw={500}>
@ -47,7 +47,6 @@ export const ModelAnalysisModal = ({
<Text>Model Analysis</Text> <Text>Model Analysis</Text>
</Group> </Group>
} }
size="lg"
showCloseButton showCloseButton
> >
{loading && ( {loading && (
@ -63,72 +62,38 @@ export const ModelAnalysisModal = ({
)} )}
{analysis && ( {analysis && (
<Stack gap="md"> <Stack gap="xs">
<div> {analysis.general.name && (
<Text size="sm" fw={600} mb="xs"> <InfoRow label="Model Name" value={analysis.general.name} />
General Information
</Text>
<Stack gap="xs">
<InfoRow
label="Architecture"
value={analysis.general.architecture}
/>
{analysis.general.name && (
<InfoRow label="Model Name" value={analysis.general.name} />
)}
{analysis.general.parameterCount && (
<InfoRow
label="Parameters"
value={analysis.general.parameterCount}
/>
)}
<InfoRow label="File Size" value={analysis.general.fileSize} />
{analysis.architecture.layers && (
<InfoRow label="Layers" value={analysis.architecture.layers} />
)}
{analysis.architecture.expertCount && (
<InfoRow
label="Expert Count (MoE)"
value={analysis.architecture.expertCount}
/>
)}
</Stack>
</div>
{analysis.context.maxContextLength && (
<>
<Divider />
<div>
<Text size="sm" fw={600} mb="xs">
Context
</Text>
<Stack gap="xs">
<InfoRow
label="Max Context Length"
value={analysis.context.maxContextLength}
/>
</Stack>
</div>
</>
)} )}
<InfoRow label="Architecture" value={analysis.general.architecture} />
{analysis.general.parameterCount && (
<InfoRow label="Parameters" value={analysis.general.parameterCount} />
)}
<InfoRow label="File Size" value={analysis.general.fileSize} />
<Divider /> {analysis.architecture.expertCount && (
<InfoRow
<div> label="Expert Count (MoE)"
<Text size="sm" fw={600} mb="xs"> value={analysis.architecture.expertCount}
Memory Estimates />
</Text> )}
<Stack gap="xs"> {analysis.context.maxContextLength && (
<InfoRow label="Full VRAM" value={analysis.estimates.fullGpuVram} /> <InfoRow
<InfoRow label="Full RAM" value={analysis.estimates.systemRam} /> label="Max Context Length"
{analysis.estimates.vramPerLayer && ( value={analysis.context.maxContextLength}
<InfoRow />
label="VRAM per Layer" )}
value={analysis.estimates.vramPerLayer} {analysis.architecture.layers && (
/> <InfoRow
)} label="Layers / VRAM"
</Stack> value={`${analysis.architecture.layers} (${analysis.estimates.vramPerLayer || 'N/A'} per layer) = ${analysis.estimates.fullGpuVram}`}
</div> />
)}
{!analysis.architecture.layers && (
<InfoRow label="Full VRAM" value={analysis.estimates.fullGpuVram} />
)}
<InfoRow label="Full RAM" value={analysis.estimates.systemRam} />
</Stack> </Stack>
)} )}
</Modal> </Modal>

View file

@ -46,8 +46,6 @@ export const ModelFileField = ({
return undefined; return undefined;
}; };
const isLocalFile = value.trim() && validationState === 'local';
const handleAnalyzeModel = async () => { const handleAnalyzeModel = async () => {
if (!value.trim()) return; if (!value.trim()) return;
@ -99,7 +97,7 @@ export const ModelFileField = ({
</ActionIcon> </ActionIcon>
</Tooltip> </Tooltip>
)} )}
{showAnalyze && isLocalFile && ( {showAnalyze && value.trim() && validationState !== 'invalid' && (
<Tooltip label="Analyze model"> <Tooltip label="Analyze model">
<ActionIcon <ActionIcon
onClick={handleAnalyzeModel} onClick={handleAnalyzeModel}

View file

@ -43,11 +43,21 @@ function getMetadataValue(metadata: Record<string, unknown>, key: string) {
export async function analyzeGGUFModel(filePath: string) { export async function analyzeGGUFModel(filePath: string) {
try { try {
const stats = await stat(filePath); const isUrl =
const fileSize = stats.size; filePath.startsWith('http://') || filePath.startsWith('https://');
let fileSize: number;
if (isUrl) {
const response = await fetch(filePath, { method: 'HEAD' });
const contentLength = response.headers.get('content-length');
fileSize = contentLength ? parseInt(contentLength, 10) : 0;
} else {
const stats = await stat(filePath);
fileSize = stats.size;
}
const { metadata } = await gguf(filePath, { const { metadata } = await gguf(filePath, {
allowLocalFile: true, allowLocalFile: !isUrl,
}); });
const metadataRecord = metadata as Record<string, unknown>; const metadataRecord = metadata as Record<string, unknown>;