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",
"productName": "Gerbil",
"version": "1.7.4",
"version": "1.7.5",
"description": "Run Large Language Models locally",
"main": "out/main/index.js",
"homepage": "./",

View file

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

View file

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

View file

@ -43,11 +43,21 @@ function getMetadataValue(metadata: Record<string, unknown>, key: string) {
export async function analyzeGGUFModel(filePath: string) {
try {
const stats = await stat(filePath);
const fileSize = stats.size;
const isUrl =
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, {
allowLocalFile: true,
allowLocalFile: !isUrl,
});
const metadataRecord = metadata as Record<string, unknown>;