Optimize max peak memory usage and add warnings on mobile for higher models
This commit is contained in:
1 parent
f6019b3a42
commit
a0ef5548af
10 files changed
+428
-7
No files matched your search
+65
-6
@@ -32,8 +32,12 @@ import {
|
||||
DEFAULT_MODEL_ID,
|
||||
DEFAULT_TASK,
|
||||
MODELS,
|
||||
estimatePeakLoadBytes,
|
||||
formatModelSize,
|
||||
getModel,
|
||||
resolveDefaultDtype,
|
||||
} from "./lib/models";
|
||||
import { shouldWarnBeforeLoad, type MobileMemoryHint } from "./lib/mobileMemoryGuard";
|
||||
import {
|
||||
applyLoadProgress,
|
||||
initialLoadProgress,
|
||||
@@ -84,6 +88,12 @@ export function App() {
|
||||
// entirely. Session-only on purpose: a browser update may fix the backend.
|
||||
const deviceOverrides = useRef(new Map<string, InferenceDevice>());
|
||||
|
||||
// Mobile memory-crash warnings the user already accepted this session
|
||||
// (D-027), keyed "modelId::device" — once confirmed, repeat loads of the
|
||||
// same combination (e.g. the lazy auto-load after an explicit Download)
|
||||
// don't ask again.
|
||||
const mobileWarningAcknowledged = useRef(new Set<string>());
|
||||
|
||||
// Resolves when the current browser-engine load reports ready (rejects on
|
||||
// load failure/cancel). Transcribe posts await it, so no request is ever
|
||||
// queued inside a worker that a load-restart (D-025) is about to terminate
|
||||
@@ -203,6 +213,46 @@ export function App() {
|
||||
return deviceOverrides.current.get(overrideKey(forModelId, preference)) ?? preference;
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm before a browser-engine load that risks crashing the tab on
|
||||
* mobile (D-027). Returns true if the load should proceed — either it
|
||||
* isn't risky, or the user already accepted the risk (this load, or an
|
||||
* identical one earlier this session). A declined confirm returns false
|
||||
* with no side effects, so callers can bail out before touching any state.
|
||||
*/
|
||||
function confirmRiskyMobileLoad(forModelId: string, preference: DevicePreference): boolean {
|
||||
const targetModel = getModel(forModelId);
|
||||
if (!targetModel) return true;
|
||||
// Mirrors ModelPicker's size-estimate fallback: until the worker reports
|
||||
// an active device, assume WASM unless the user asked for an accelerator.
|
||||
const device: InferenceDevice =
|
||||
activeDevice ??
|
||||
(preference === "webgpu" ? "webgpu" : preference === "webnn" ? "webnn" : "wasm");
|
||||
const key = overrideKey(forModelId, device);
|
||||
if (mobileWarningAcknowledged.current.has(key)) return true;
|
||||
|
||||
const peakBytes = estimatePeakLoadBytes(targetModel, resolveDefaultDtype(forModelId, device));
|
||||
const hint: MobileMemoryHint = {
|
||||
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
|
||||
maxTouchPoints: typeof navigator !== "undefined" ? navigator.maxTouchPoints : undefined,
|
||||
deviceMemoryGB:
|
||||
typeof navigator !== "undefined"
|
||||
? (navigator as Navigator & { deviceMemory?: number }).deviceMemory
|
||||
: undefined,
|
||||
};
|
||||
if (!shouldWarnBeforeLoad(hint, peakBytes)) return true;
|
||||
|
||||
const proceed = window.confirm(
|
||||
`${targetModel.displayName} needs roughly ${formatModelSize(peakBytes)} of memory at ` +
|
||||
"once to load — more than many phones and tablets allow a browser tab to use " +
|
||||
"before force-closing it. This can crash your browser (and on some phones, the " +
|
||||
"whole screen briefly goes back to the home screen). A smaller model (Tiny, Base " +
|
||||
"or Small) is much safer on mobile.\n\nContinue anyway?",
|
||||
);
|
||||
if (proceed) mobileWarningAcknowledged.current.add(key);
|
||||
return proceed;
|
||||
}
|
||||
|
||||
function newLoadGate(): void {
|
||||
let resolve!: () => void;
|
||||
let reject!: (error: Error) => void;
|
||||
@@ -406,6 +456,7 @@ export function App() {
|
||||
/** Explicit model download (D-020) — pre-caches weights for offline use. */
|
||||
function handleDownloadModel() {
|
||||
if (busy || liveStatus !== "idle" || loadStarted) return;
|
||||
if (!confirmRiskyMobileLoad(modelId, devicePreference)) return;
|
||||
startLoad(modelId, devicePreference);
|
||||
}
|
||||
|
||||
@@ -457,13 +508,17 @@ export function App() {
|
||||
|
||||
async function handleFile(file: File) {
|
||||
if (busy || liveStatus !== "idle") return;
|
||||
const run = ++fileRun.current;
|
||||
// The toggle's choice only takes effect with a session and an available
|
||||
// server engine (D-010); everything else runs on-device as always.
|
||||
const useServer =
|
||||
engineChoice === "server" &&
|
||||
auth.user !== null &&
|
||||
serverStatus?.available === true;
|
||||
// Browser-engine runs may need to load the model right away (D-020); bail
|
||||
// out before any state changes if the user declines a mobile crash risk
|
||||
// warning (D-027).
|
||||
if (!useServer && !confirmRiskyMobileLoad(modelId, devicePreference)) return;
|
||||
const run = ++fileRun.current;
|
||||
setBusy(true);
|
||||
setRunEngine(useServer ? "server" : "browser");
|
||||
setError(null);
|
||||
@@ -603,6 +658,15 @@ export function App() {
|
||||
|
||||
async function startLive() {
|
||||
if (busy || liveStatus !== "idle") return;
|
||||
const useServer =
|
||||
engineChoice === "server" &&
|
||||
auth.user !== null &&
|
||||
serverStatus?.available === true;
|
||||
// Browser-engine live sessions may need to load the model right away
|
||||
// (D-020); bail out before any state changes if the user declines a
|
||||
// mobile crash risk warning (D-027).
|
||||
if (!useServer && !confirmRiskyMobileLoad(modelId, devicePreference)) return;
|
||||
|
||||
setError(null);
|
||||
setResult(null);
|
||||
setPartialText("");
|
||||
@@ -612,11 +676,6 @@ export function App() {
|
||||
setLivePending(0);
|
||||
setLiveStatus("starting");
|
||||
|
||||
const useServer =
|
||||
engineChoice === "server" &&
|
||||
auth.user !== null &&
|
||||
serverStatus?.available === true;
|
||||
|
||||
const options = (getModel(modelId)?.multilingual ?? false) ? { language, task } : {};
|
||||
|
||||
if (useServer) {
|
||||
|
||||
Reference in new issue
Block a user