ui : model memory-fit estimation

Replace the raw runtime-memory estimate with the app's compatibility check:
the smallest real Mac memory tier that fits a model file, budgeted as
RAM x 0.75 minus fixed overhead with headroom on the file size. The constants
move to lib; the unused runtime-memory estimate is dropped. browser-info's
OS detection is exported for reuse.

Assisted-by: pi:GLM-5.3-Flash
This commit is contained in:
Aleksander Grygier
2026-09-16 13:53:33 +02:00
parent c9462ce786
commit 3e43ff1b33
6 changed files with 107 additions and 2 deletions
+1
View File
@@ -46,6 +46,7 @@ export * from './path-display.constants';
export * from './model-id.constants';
export * from './model-loading.constants';
export * from './models-discover.constants';
export * from './model-compatibility.constants';
export * from './huggingface.constants';
export * from './precision.constants';
export * from './pwa.constants';
@@ -0,0 +1,32 @@
/**
* Model memory-fit constants.
*
* Mirrors the app's compatibility check (Model+Compatibility.swift):
* budget = RAM x RAM_BUDGET_RATIO - RAM_OVERHEAD_MB
* weightBytes = fileBytes x QUANT_WEIGHT
* a file fits when weightBytes <= budget. Kept here so the estimation util and
* any caller share one source.
*/
/** Bytes in one mebibyte (MiB), used to convert a file size to MB. */
export const MIB_BYTES = 1_048_576;
/** MB in one GB. */
export const MB_PER_GB = 1024;
/** Overhead multiplier applied to the file size when estimating weight memory. */
export const QUANT_WEIGHT = 1.05;
/** Share of RAM the app allows the model to occupy. */
export const RAM_BUDGET_RATIO = 0.75;
/** Fixed RAM overhead (MB) reserved for the system and KV cache. */
export const RAM_OVERHEAD_MB = 2048;
/**
* Memory tiers (GB) covering the RAM sizes common machines ship with, in
* small enough steps that the requirement reads honestly. Device-agnostic on
* purpose: the server exposes no host RAM, so the UI presents the tier and
* lets the user judge.
*/
export const MEM_TIERS = [4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128, 192, 256, 384, 512, 768, 1024];
+1 -1
View File
@@ -16,7 +16,7 @@ import {
} from '$lib/constants';
import type { ToolExecutionResult } from '$lib/types';
function detectOs(userAgent: string): string {
export function detectOs(userAgent: string): string {
for (const [pattern, os] of BROWSER_INFO_OS_UA_PATTERNS) {
if (pattern.test(userAgent)) return os;
}
@@ -0,0 +1,29 @@
/**
* Detects whether a model's chat template supports tool calling.
*
* There is no server flag for tool support, so we infer it from the chat
* template. A template that accepts a `tools` array or emits tool-call tokens
* is treated as tool-capable.
*/
/** Tool-call tokens emitted by the template for assistant tool calls, matched case-insensitively. */
const TOOL_CALL_TOKENS = [
'tool_call',
'tool_calls',
'function_call',
'tool_use',
'<tool',
'<|tool'
];
/** Jinja reference to the `tools` array passed in by the caller. */
const JINJA_TOOLS_VAR = /\{\{[^{}]*\btools\b[^{}]*\}\}|\{%[^{}]*\btools\b[^{}]*%\}/i;
export function detectToolUseSupport(t: string): boolean {
if (!t) return false;
if (JINJA_TOOLS_VAR.test(t)) return true;
const template = t.toLowerCase();
return TOOL_CALL_TOKENS.some((token) => template.includes(token));
}
+7 -1
View File
@@ -343,7 +343,13 @@ export { buildSandboxToolDefinition, SANDBOX_TOOL_DEFINITION } from './sandbox-t
export { executeGetDatetimeTool } from './get-datetime';
// Browser fallback for the server's get_info tool
export { executeBrowserInfoTool } from './browser-info';
export { detectOs, executeBrowserInfoTool } from './browser-info';
// Tool-use support detection from a chat template
export { detectToolUseSupport } from './chat-template-tool-detector';
// Model memory estimation
export { minMemoryTierGb } from './model-compatibility';
// Cryptography utilities
@@ -0,0 +1,37 @@
/**
* Model memory estimation.
*
* Mirrors the app's compatibility check (Model+Compatibility.swift): the
* runtime budget is RAM x 0.75 minus a fixed overhead, and a file fits when
* its size with headroom stays under that budget. The result is the smallest
* memory tier that can run the model, so the UI presents an honest machine
* requirement instead of a raw file size. Context length and
* device-specific budgets are deliberately ignored - callers present the
* requirement and let the user judge.
*/
import {
MB_PER_GB,
MEM_TIERS,
MIB_BYTES,
QUANT_WEIGHT,
RAM_BUDGET_RATIO,
RAM_OVERHEAD_MB
} from '$lib/constants';
/**
* Smallest memory tier (GB) that can run a model of the given file size,
* or null if nothing fits even the largest tier.
*/
export function minMemoryTierGb(sizeBytes: number): number | null {
if (!sizeBytes) return null;
const weightMb = (sizeBytes / MIB_BYTES) * QUANT_WEIGHT;
for (const tier of MEM_TIERS) {
const budgetMb = tier * MB_PER_GB * RAM_BUDGET_RATIO - RAM_OVERHEAD_MB;
if (weightMb <= budgetMb) return tier;
}
return null;
}