ui : parse imatrix and standalone draft sidecar filenames

extractQuantMeta now recognizes a -draft tail after the sidecar token
(Model-MTP-draft.gguf), a bare sidecar token filename (imatrix.gguf) and
suffix sidecars whose head carries no quant (Model-imatrix.gguf). imatrix
is an aux sidecar: it stays in the download chips with a badge, is never
a serve-command option, and mmproj alone drives the Vision capability.

Assisted-by: pi:zai-org/GLM-5.3
This commit is contained in:
Aleksander Grygier
2026-09-03 13:25:47 +02:00
parent 20769a4cb5
commit 422b7ece77
6 changed files with 42 additions and 30 deletions
@@ -3,7 +3,7 @@
import ModelsDiscoverModelDetailsHeader from './ModelsDiscoverModelDetailsHeader.svelte';
import ModelsDiscoverModelDetailsReadme from './ModelsDiscoverModelDetailsReadme.svelte';
import ModelsDiscoverModelDetailsSkeleton from './ModelsDiscoverModelDetailsSkeleton.svelte';
import { isAuxSidecar } from '$lib/constants';
import { ModelAuxSidecar } from '$lib/enums';
import { HuggingFaceService } from '$lib/services';
import type { HfModelDetailInfo, HfModelSibling } from '$lib/types/huggingface';
import { detectThinkingSupport, detectToolUseSupport } from '$lib/utils';
@@ -37,11 +37,7 @@
// Capabilities derived from HF metadata. Vision comes from an mmproj sidecar
// or a multimodal pipeline tag; tool use / reasoning from the chat template.
let hasMmproj = $derived(
files.some((f) => {
const sidecar = HuggingFaceService.extractQuantMeta(f.path)?.sidecar;
return sidecar !== null && sidecar !== undefined && isAuxSidecar(sidecar);
})
files.some((f) => HuggingFaceService.extractQuantMeta(f.path)?.sidecar === ModelAuxSidecar.MMPROJ)
);
let hasVision = $derived(hasMmproj || details?.pipeline_tag === 'image-text-to-text');
let hasTools = $derived(detectToolUseSupport(gguf?.chat_template ?? ''));
@@ -57,8 +53,9 @@
for (const file of files) {
const meta = HuggingFaceService.extractQuantMeta(file.path);
// mmproj sidecars are already conveyed by the Vision capability badge.
if (meta?.sidecar && isAuxSidecar(meta.sidecar)) continue;
// mmproj sidecars are already conveyed by the Vision capability badge;
// imatrix ships as a normal chip with its own badge.
if (meta?.sidecar === ModelAuxSidecar.MMPROJ) continue;
const depth = meta?.quant ? HuggingFaceService.getBitDepth(meta.quant) : null;
const bucket = depth ?? 99;
@@ -4,7 +4,6 @@
import { Check, Download, Loader2, Pause, Play, RotateCw, X } from '@lucide/svelte';
import DialogConfirmation from '$lib/components/app/dialogs/DialogConfirmation.svelte';
import * as Tooltip from '$lib/components/ui/tooltip';
import { isAuxSidecar } from '$lib/constants';
import { HuggingFaceService } from '$lib/services';
import { modelsStore } from '$lib/stores';
import type { HfModelSibling } from '$lib/types';
@@ -65,7 +64,7 @@
onclick={() => (confirmDeleteOpen = true)}
type="button"
>
{#if meta?.sidecar && !isAuxSidecar(meta.sidecar)}
{#if meta?.sidecar}
<span
class="rounded-md bg-primary px-1 py-0.5 text-[10px] font-semibold tracking-wide text-primary-foreground uppercase"
>
@@ -131,7 +130,7 @@
}}
type="button"
>
{#if meta?.sidecar && !isAuxSidecar(meta.sidecar)}
{#if meta?.sidecar}
<span
class="rounded-md bg-primary px-1 py-0.5 text-[10px] font-semibold tracking-wide text-primary-foreground uppercase"
>
@@ -224,7 +223,7 @@
</span>
{/if}
{#if meta?.sidecar && !isAuxSidecar(meta.sidecar)}
{#if meta?.sidecar}
<span
class="rounded-md bg-primary px-1 py-0.5 text-[10px] font-semibold tracking-wide text-primary-foreground uppercase"
>
@@ -1,4 +1,4 @@
import { isAuxSidecar, type ModelSidecar } from '$lib/constants';
import { isAuxSidecar, isDraftSidecar, type ModelSidecar } from '$lib/constants';
import { ModelAuxSidecar, ModelDraftSidecar } from '$lib/enums';
import { HuggingFaceService } from '$lib/services';
import type { HfModelSibling } from '$lib/types/huggingface';
@@ -43,9 +43,13 @@ export function classify(path: string): 'main' | 'draft' | 'aux' {
/** Display label of a file: its quant, else the file name without the extension. */
export function labelFor(path: string): string {
const quant = HuggingFaceService.extractQuantMeta(path)?.quant;
const meta = HuggingFaceService.extractQuantMeta(path);
if (quant) return quant;
if (meta?.quant) return meta.quant;
// Quantless sidecar files: the chip badge already carries the sidecar type,
// so the label only marks draft files; aux sidecars badge alone.
if (meta?.sidecar) return isDraftSidecar(meta.sidecar) ? 'draft' : '';
const basename = path.split('/').pop() ?? path;
@@ -65,9 +69,10 @@ export function quantBitDepth(quant: string | null): number {
return bit ? Number(bit) : 99;
}
// LLAMA-APP-REUSE: --spec-type value for each draft sidecar
// LLAMA-APP-REUSE: --spec-type value for each draft sidecar; aux sidecars stay empty
export const SPEC_TYPE: Record<ModelSidecar, string> = {
[ModelAuxSidecar.MMPROJ]: '',
[ModelAuxSidecar.IMATRIX]: '',
[ModelDraftSidecar.DFLASH]: 'draft-dflash',
[ModelDraftSidecar.DSPARK]: 'draft-dspark',
[ModelDraftSidecar.EAGLE3]: 'eagle3',
@@ -75,10 +75,12 @@ export const MODEL_ID = {
/**
* Trailing `-<type>` suffix marking a GGUF with an embedded draft in the
* same weight file (MTP) or a sidecar download entry, e.g.
* `Hy3-IQ1_M-mtp.gguf`, `Q4_K_M-dspark`. The captured prefix is the
* candidate model id; the caller decides whether it looks quantized.
* `Hy3-IQ1_M-mtp.gguf`, `Q4_K_M-dspark`. An optional `-draft` tail covers
* standalone sidecar files, e.g. `Model-MTP-draft.gguf`. The captured
* prefix is the candidate model id; the caller decides whether it looks
* quantized.
*/
SIDECAR_SUFFIX_REGEX: new RegExp(`^(.*)-(${SIDECAR_TOKEN_ALTERNATION})$`, 'i'),
SIDECAR_SUFFIX_REGEX: new RegExp(`^(.*)-(${SIDECAR_TOKEN_ALTERNATION})(-draft)?$`, 'i'),
/** Matches a trailing weight file extension, e.g. `model.gguf` -> `model`. */
WEIGHT_EXTENSION_REGEX: /\.(gguf|ggml)$/i
+3 -1
View File
@@ -32,5 +32,7 @@ export enum ModelDraftSidecar {
*/
export enum ModelAuxSidecar {
/** Multimodal projector: unlocks vision and/or audio input modalities. */
MMPROJ = 'mmproj'
MMPROJ = 'mmproj',
/** Importance-matrix data used to build imatrix quants; not loaded at serve time. */
IMATRIX = 'imatrix'
}
@@ -168,8 +168,9 @@ export class HuggingFaceService {
* Extract the GGUF quantization token (e.g. `Q4_K_M`) and any sidecar type
* (`mtp`, `dflash`, `mmproj`, ...) from a `.gguf` filename. The sidecar token
* shows up either as a sidecar prefix (`mtp-<name>.gguf`, `dflash-<name>.gguf`,
* `mmproj-<name>.gguf`) or as the `-mtp` suffix when the draft model is
* embedded in the same GGUF weight file.
* `mmproj-<name>.gguf`), as a `-mtp` suffix, or as the whole filename
* (`imatrix.gguf`); a `-draft` tail marks a standalone sidecar file
* (`Model-MTP-draft.gguf`).
*
* `sidecarForm` records which side of the filename the sidecar token sat
* on so callers can render badges differently (e.g. prefix on the left of
@@ -197,6 +198,14 @@ export class HuggingFaceService {
let sidecar: ModelSidecar | null = null;
let sidecarForm: SidecarForm | null = null;
// A file named just the sidecar token (`imatrix.gguf`) is the sidecar
// itself: no name or quant segments to parse.
const bareSidecar = sidecarFromFileToken(source.toLowerCase());
if (bareSidecar) {
return { quant: null, shared: false, sidecar: bareSidecar, sidecarForm: SidecarForm.PREFIX };
}
const prefixMatch = source.match(MODEL_ID.SIDECAR_PREFIX_REGEX);
if (prefixMatch) {
@@ -207,14 +216,12 @@ export class HuggingFaceService {
const suffixMatch = source.match(MODEL_ID.SIDECAR_SUFFIX_REGEX);
if (suffixMatch) {
const candidate = suffixMatch[1];
const headSeg = candidate.split(MODEL_ID.SEGMENT_SEPARATOR).pop();
if (headSeg && MODEL_ID.QUANTIZATION_SEGMENT_REGEX.test(headSeg)) {
sidecar = sidecarFromFileToken(suffixMatch[2].toLowerCase());
sidecarForm = SidecarForm.SUFFIX;
source = candidate;
}
// Take the suffix sidecar even when the head carries no quant:
// embedded drafts end in one (`Hy3-IQ1_M-mtp`), standalone sidecar
// files do not (`Model-MTP-draft`, `Model-imatrix`).
sidecar = sidecarFromFileToken(suffixMatch[2].toLowerCase());
sidecarForm = SidecarForm.SUFFIX;
source = suffixMatch[1];
}
}