From 3e43ff1b33a08bd2c1cd32cdd42cc1be6d4eead4 Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Fri, 4 Sep 2026 21:35:33 +0200 Subject: [PATCH] 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 --- tools/ui/src/lib/constants/index.ts | 1 + .../model-compatibility.constants.ts | 32 ++++++++++++++++ tools/ui/src/lib/utils/browser-info.ts | 2 +- .../lib/utils/chat-template-tool-detector.ts | 29 +++++++++++++++ tools/ui/src/lib/utils/index.ts | 8 +++- tools/ui/src/lib/utils/model-compatibility.ts | 37 +++++++++++++++++++ 6 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 tools/ui/src/lib/constants/model-compatibility.constants.ts create mode 100644 tools/ui/src/lib/utils/chat-template-tool-detector.ts create mode 100644 tools/ui/src/lib/utils/model-compatibility.ts diff --git a/tools/ui/src/lib/constants/index.ts b/tools/ui/src/lib/constants/index.ts index c66a2f7715..8d99180abd 100644 --- a/tools/ui/src/lib/constants/index.ts +++ b/tools/ui/src/lib/constants/index.ts @@ -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'; diff --git a/tools/ui/src/lib/constants/model-compatibility.constants.ts b/tools/ui/src/lib/constants/model-compatibility.constants.ts new file mode 100644 index 0000000000..327e41f81b --- /dev/null +++ b/tools/ui/src/lib/constants/model-compatibility.constants.ts @@ -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]; diff --git a/tools/ui/src/lib/utils/browser-info.ts b/tools/ui/src/lib/utils/browser-info.ts index c96abb01e0..e313d42584 100644 --- a/tools/ui/src/lib/utils/browser-info.ts +++ b/tools/ui/src/lib/utils/browser-info.ts @@ -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; } diff --git a/tools/ui/src/lib/utils/chat-template-tool-detector.ts b/tools/ui/src/lib/utils/chat-template-tool-detector.ts new file mode 100644 index 0000000000..2b8f915c57 --- /dev/null +++ b/tools/ui/src/lib/utils/chat-template-tool-detector.ts @@ -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', + ' template.includes(token)); +} diff --git a/tools/ui/src/lib/utils/index.ts b/tools/ui/src/lib/utils/index.ts index 8896ea234e..06ab5d7b6a 100644 --- a/tools/ui/src/lib/utils/index.ts +++ b/tools/ui/src/lib/utils/index.ts @@ -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 diff --git a/tools/ui/src/lib/utils/model-compatibility.ts b/tools/ui/src/lib/utils/model-compatibility.ts new file mode 100644 index 0000000000..4f7e32fe67 --- /dev/null +++ b/tools/ui/src/lib/utils/model-compatibility.ts @@ -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; +}