tuning feedback: structured outcome (faster / already_fastest / busy_pc / failed / stopped + values), tuned values readable (presets effective, settings carry them), which tuning is active and off/on, applied at every start (restart and unload/load tests)

This commit is contained in:
Niko1221 committed 2026-10-16 14:16:07 +02:00
1 parent 9f24934663
commit dab63e9c38
21 files changed
+407 -29

No files matched your search

+40
View File
@@ -43,6 +43,11 @@ KEYS = [
"Start tuning",
"Tuning your model",
'Answers can differ from run to run on this PC even with the standard setup; this setting does not add to that.',
"Using your tuning '{0}'",
'Switch off',
'Switch on',
'Your tuning is off. The standard settings are used.',
'Your PC was busy, so the measurements were not reliable. Nothing was changed. Try again when it is quiet.',
]
T = {
@@ -62,6 +67,11 @@ T = {
"长提示的编程", "允许可能略微改变回答的设置", "已保存的调优", "使用中", "使用",
"再次检查(约 2 分钟)", "再次调优", "开始调优", "正在调优你的模型",
'在这台电脑上,即使使用标准设置,每次运行的回答也可能不同;此设置不会增加这种差异。',
'正在使用你的调优“{0}”',
'关闭',
'开启',
'你的调优已关闭,使用标准设置。',
'你的电脑当时很忙,测量结果不可靠。没有做任何改动。请在电脑空闲时再试。',
],
"ja": [
"このモデルをチューニング", "あなたの PC で最も速い設定を探しています…", "残り約 {0} 分",
@@ -79,6 +89,11 @@ T = {
"長いプロンプトでのコーディング", "回答がわずかに変わる可能性のある設定も許可する", "保存済みチューニング", "使用中", "使用する",
"もう一度チェック(約 2 分)", "もう一度チューニング", "チューニングを開始", "モデルをチューニング中",
'このPCでは標準設定でも実行のたびに回答が変わることがあります。この設定がそれを増やすことはありません。',
'あなたのチューニング「{0}」を使用中',
'オフにする',
'オンにする',
'チューニングはオフです。標準設定を使用します。',
'PCが忙しく、測定結果が信頼できませんでした。何も変更していません。PCが空いているときにもう一度お試しください。',
],
"de": [
"Dieses Modell abstimmen", "Die schnellsten Einstellungen für deinen PC werden gesucht…", "noch etwa {0} Min.",
@@ -96,6 +111,11 @@ T = {
"Programmieren mit langen Prompts", "Einstellungen erlauben, die Antworten leicht verändern können", "Gespeicherte Abstimmungen", "In Gebrauch", "Verwenden",
"Erneut prüfen (etwa 2 Minuten)", "Erneut abstimmen", "Abstimmung starten", "Dein Modell wird abgestimmt",
'Auf diesem PC können die Antworten von Lauf zu Lauf abweichen, auch mit der Standardeinstellung; diese Einstellung verstärkt das nicht.',
'Deine Abstimmung „{0}“ wird verwendet',
'Ausschalten',
'Einschalten',
'Deine Abstimmung ist aus. Es gelten die Standardeinstellungen.',
'Dein PC war ausgelastet, daher waren die Messungen nicht zuverlässig. Es wurde nichts geändert. Versuche es erneut, wenn er ruhig ist.',
],
"es": [
"Ajustar este modelo", "Buscando los ajustes más rápidos para tu PC…", "faltan unos {0} min",
@@ -113,6 +133,11 @@ T = {
"Programar con prompts largos", "Permitir ajustes que pueden cambiar ligeramente las respuestas", "Ajustes guardados", "En uso", "Usar",
"Comprobar de nuevo (unos 2 minutos)", "Ajustar de nuevo", "Empezar a ajustar", "Ajustando tu modelo",
'En este PC las respuestas pueden variar de una ejecución a otra incluso con el ajuste estándar; este ajuste no lo aumenta.',
'Usando tu ajuste «{0}»',
'Desactivar',
'Activar',
'Tu ajuste está desactivado. Se usan los ajustes estándar.',
'Tu PC estaba ocupado, así que las mediciones no fueron fiables. No se cambió nada. Inténtalo de nuevo cuando esté tranquilo.',
],
"ru": [
"Настроить эту модель", "Ищем самые быстрые настройки для вашего ПК…", "осталось примерно {0} мин.",
@@ -130,6 +155,11 @@ T = {
"Программирование с длинными промптами", "Разрешить настройки, которые могут немного изменить ответы", "Сохранённые настройки", "Используется", "Использовать",
"Проверить снова (около 2 минут)", "Настроить снова", "Начать настройку", "Настройка вашей модели",
'На этом ПК ответы могут отличаться от запуска к запуску даже при стандартных настройках; эта настройка этого не усиливает.',
'Используется ваша настройка «{0}»',
'Выключить',
'Включить',
'Ваша настройка выключена. Используются стандартные параметры.',
'Ваш ПК был занят, поэтому измерения были ненадёжными. Ничего не изменено. Повторите, когда он освободится.',
],
"pt": [
"Ajustar este modelo", "Procurando as configurações mais rápidas para o seu PC…", "faltam cerca de {0} min",
@@ -147,6 +177,11 @@ T = {
"Programação com prompts longos", "Permitir configurações que podem mudar um pouco as respostas", "Ajustes salvos", "Em uso", "Usar",
"Verificar de novo (cerca de 2 minutos)", "Ajustar de novo", "Iniciar ajuste", "Ajustando o seu modelo",
'Neste PC as respostas podem variar de uma execução para outra mesmo com a configuração padrão; esta configuração não aumenta isso.',
'Usando o seu ajuste “{0}”',
'Desligar',
'Ligar',
'O seu ajuste está desligado. As configurações padrão são usadas.',
'O seu PC estava ocupado, então as medições não foram confiáveis. Nada foi alterado. Tente de novo quando ele estiver tranquilo.',
],
"ko": [
"이 모델 최적화", "내 PC에서 가장 빠른 설정을 찾는 중…", "약 {0}분 남음",
@@ -164,6 +199,11 @@ T = {
"긴 프롬프트로 코딩", "답변을 조금 바꿀 수 있는 설정 허용", "저장된 최적화", "사용 중", "사용",
"다시 점검 (약 2분)", "다시 최적화", "최적화 시작", "모델을 최적화하는 중",
'이 PC에서는 표준 설정에서도 실행할 때마다 답변이 달라질 수 있으며, 이 설정이 그 차이를 키우지는 않습니다.',
'내 최적화 ‘{0}’ 사용 중',
'끄기',
'켜기',
'최적화가 꺼져 있습니다. 표준 설정을 사용합니다.',
'PC가 바빠서 측정 결과를 믿을 수 없었습니다. 아무것도 변경하지 않았습니다. PC가 한가할 때 다시 시도하세요.',
],
}
+4 -1
View File
@@ -775,6 +775,8 @@ async function api(req, res, path, method, url) {
if(path==='/api/native/benchmark'&&method==='GET')return json(res,200,native.benchmarkReport());
/* The auto-tuner's saved tunings (Strata Control's presets, shared with the Terminal). Starting a tune or a re-check is POST /api/native/operations with kind autotune / recheck. */
if(path==='/api/native/tune/status'&&method==='GET')return json(res,200,await native.tuneStatus({model:url.searchParams.get('model'),resourceProfile:url.searchParams.get('resourceProfile')||undefined,vision:url.searchParams.get('vision')||undefined}));
if(path==='/api/native/tune/effective'&&method==='GET')return json(res,200,await native.tuneEffective({model:url.searchParams.get('model'),resourceProfile:url.searchParams.get('resourceProfile')||undefined,vision:url.searchParams.get('vision')||undefined}));
if(path==='/api/native/tune/off'&&method==='POST'){const body=await readBody(req);return json(res,200,await native.tuneOff({model:body.model,resourceProfile:body.resourceProfile,vision:body.vision}));}
if(path==='/api/native/tune/presets'&&method==='GET')return json(res,200,await native.tunePresets.list());
if(path==='/api/native/tune/undo'&&method==='POST'){const body=await readBody(req);return json(res,200,await native.tunePresets.undoFor({model:body.model,resourceProfile:body.resourceProfile,vision:body.vision}));}
const tunePreset=/^\/api\/native\/tune\/presets\/([0-9a-f]{8,32})\/(apply|rename|delete|undo)$/.exec(path);
@@ -784,7 +786,8 @@ async function api(req, res, path, method, url) {
const nativeInstance=/^\/api\/native\/instances\/([a-z0-9-]+)\/(unload|default)$/.exec(path);
if(nativeInstance&&method==='POST')return json(res,200,nativeInstance[2]==='unload'?await native.unload(nativeInstance[1]):native.setDefault(nativeInstance[1]));
const nativeSettings=/^\/api\/native\/models\/([a-z0-9_-]+)\/settings$/.exec(path);
if(nativeSettings&&method==='GET')return json(res,200,native.settings(nativeSettings[1]));
/* Expert mode shows what will actually run: the saved tuning's values come with the settings (null when none applies or unreadable). */
if(nativeSettings&&method==='GET'){const settings=native.settings(nativeSettings[1]);let tuned=null;try{const t=await native.tuneEffective({model:nativeSettings[1],resourceProfile:url.searchParams.get('resourceProfile')||undefined,vision:url.searchParams.get('vision')||undefined});tuned=t.applied?t:null;}catch{tuned=null;}return json(res,200,{...settings,tuned});}
if(nativeSettings&&method==='PATCH')return json(res,200,native.save(nativeSettings[1],await readBody(req)));
// Credentials are handled only on the host. Responses and events expose a key-set flag.
+5
View File
@@ -14,6 +14,7 @@ const USES = new Set(['chat', 'coding']);
export function resultSentence(result) {
if (!result) return 'The tune did not finish.';
const name = result.preset?.name;
if (result.code === 'busy_pc') return 'Your PC was busy, so the measurements were not reliable. Nothing was changed. Try again when it is quiet.';
if (result.outcome === 'improved') {
const gain = Math.max(0, Math.round(result.gain_pct ?? 0));
return `+${gain}% faster answers${name ? `, saved as '${name}'` : ''}`;
@@ -54,6 +55,10 @@ export function createAutoTune({ launcher, workDir, spawnProcess = spawn, pollMs
run,
/** Which saved tuning these choices start with, and whether it is still valid (reads only). */
async status(configPath) { return need(await run(['presets', 'status', configPath, '--output', 'json'], { timeoutMs: 45_000 }), 'The saved tuning could not be read.'); },
/** What a start will really run: the saved tuning's values on top of the configuration (Expert mode shows these). */
async effective(configPath) { return need(await run(['presets', 'effective', configPath, '--output', 'json'], { timeoutMs: 45_000 }), 'The tuned values could not be read.'); },
/** Switch the saved tuning off for these choices (kept, can be switched on again with apply). */
async off(configPath) { return need(await run(['presets', 'off', configPath, '--output', 'json'], { timeoutMs: 45_000 }), 'The saved tuning could not be switched off.'); },
async list() { return need(await run(['presets', 'list', '--output', 'json'], { timeoutMs: 30_000 }), 'The saved tunings could not be read.'); },
async action(kind, id, extra = {}) {
if (!/^[0-9a-f]{8,32}$/.test(String(id ?? '')) && kind !== 'undo') throw new Error('Choose a saved tuning.');
+3 -1
View File
@@ -184,6 +184,8 @@ export function createNativeControl({directory,requests=()=>[],changed=()=>{},sp
/* What the tuner measures is what a load would start: the saved model config with the user's choices and the resource profile. */
function tuneConfig(args){const {config,entry}=record(args.model);if(!entry.ready)throw new Error('Prepare the model before tuning it.');const {cfg}=buildConfig({model:args.model,vision:args.vision,resourceProfile:args.resourceProfile,gpus:args.gpus},config);return {path:writeTuneConfig(directory,args.model,cfg),entry};}
async function tuneStatus(args){await refresh();const {path}=tuneConfig(args);return autotuner.status(path);}
async function tuneEffective(args){await refresh();const {path}=tuneConfig(args);return autotuner.effective(path);}
async function tuneOff(args){await refresh();const {path}=tuneConfig(args);return autotuner.off(path);}
async function autotune(job,args){
await refresh(true);if(job.state!=='running')return;
if(packaged){const engine=await control.engine();if(!engine.usable)throw new Error(engine.notice??'No engine for this PC was found.');}
@@ -201,5 +203,5 @@ export function createNativeControl({directory,requests=()=>[],changed=()=>{},sp
async function close(){closing=true;clearInterval(timer);const operations=[];for(const job of jobs.values()){job.abort?.abort();if(job.process)operations.push(stopProcess(job.process));if(job.state==='running')job.state='cancelled';}await Promise.all(operations);await Promise.all([...instances.keys()].map(id=>unload(id,true).catch(()=>{})));disconnect();}
// Remove nonserializable process references from public jobs.
const publicState=()=>{const value=state();value.jobs=value.jobs.map(({process,...job})=>job);return value;};
return {state:publicState,tuneStatus,tunePresets:{list:()=>autotuner.list(),action:(kind,id,extra)=>autotuner.action(kind,id,extra),undoFor:async args=>autotuner.undoFor(tuneConfig(args).path)},refresh,configure,setDownloadDirectory,plan,settings,save,operation,cancel,unload,resolveInstance,poll,logs:()=>logs.slice(),benchmarkReport:()=>readJson(join(directory,'native-benchmark.json'),null),setDefault(id){defaultId=resolveInstance(id).id;emit();return publicState();},hasLive:()=>[...instances.values()].some(i=>i.status==='ready'),readyInstances:()=>[...instances.values()].filter(i=>i.status==='ready'),close};
return {state:publicState,tuneStatus,tuneEffective,tuneOff,tunePresets:{list:()=>autotuner.list(),action:(kind,id,extra)=>autotuner.action(kind,id,extra),undoFor:async args=>autotuner.undoFor(tuneConfig(args).path)},refresh,configure,setDownloadDirectory,plan,settings,save,operation,cancel,unload,resolveInstance,poll,logs:()=>logs.slice(),benchmarkReport:()=>readJson(join(directory,'native-benchmark.json'),null),setDefault(id){defaultId=resolveInstance(id).id;emit();return publicState();},hasLive:()=>[...instances.values()].some(i=>i.status==='ready'),readyInstances:()=>[...instances.values()].filter(i=>i.status==='ready'),close};
}
@@ -11,6 +11,7 @@ import { useStore } from '../lib/store';
import { NativePresets } from './NativePresets';
import { NativeBenchmark } from './NativeOperations';
import { NativeTune } from './NativeTune';
import { NativeTuneBadge } from './NativeTuneBadge';
import { nativeCatalogModels, catalogVersionLabel } from '../lib/native-catalog';
export function NativeModels({ onOtherModels, onReady, onImport, onConnectEngine }: {
onOtherModels: () => void;
@@ -92,6 +93,7 @@ export function NativeModels({ onOtherModels, onReady, onImport, onConnectEngine
{tr(active ? <NativeProgress job={active}/> : null)}
{tr(job?.state === 'error' ? <ErrorNotice message={job.error ?? job.summary} action={tr("Retry")} onRetry={() => void load()}/> : null)}
{tr(error ? <ErrorNotice message={error}/> : null)}
{selectedSize && installed?.ready ? <NativeTuneBadge model={selectedSize.id} resourceProfile={resourceProfile} vision={selectedSize.images ? vision : 'no'}/> : null}
<div className="native-model-actions">{tr(onReady && native?.instances.some(i => i.status === 'ready') ? <Btn variant="primary" onClick={onReady}>{tr("Start chatting")}</Btn> : <><Btn icon="gauge" disabled={!installed?.ready || !native?.available || !!active || !selectedSize} onClick={() => setTuneOpen(true)}>{tr("Tune model")}</Btn><Btn variant="primary" icon="models" disabled={pending || !native?.available || !!active || !selectedSize || expert && deviceChoice === 'split' && gpus.length < 2} onClick={() => void load()}>{tr(pending ? 'Checking…' : installed?.ready ? 'Load model' : 'Download & load')}</Btn></>)}</div>
</div>
<aside className="stack stack--lg"><section className="st-card"><h2 className="h4">{tr("This computer")}</h2><p>{tr(native?.hardware?.ram_gb || hardware?.memory.totalGb || '—')}{tr(" GB RAM")}</p>{tr(native?.hardware?.gpus.length ? native.hardware.gpus.map(g => <p className="small" key={g.index}>{tr(g.name)} · {tr(g.vram_gb)}{tr(" GB")}</p>) : <p className="small muted">{tr("No supported GPU reported by this installation.")}</p>)}<p className="small muted">{tr("Compatibility and sizes come from your installed Strata version.")}</p></section><section className="st-card"><h2 className="h4">{tr("Model files")}</h2><p className="small muted">{tr("Strata prepares all shards, the draft layer and shared files. Completed downloads are reused.")}</p><code className="native-path">{installed?.dataFolder ?? native?.downloadDirectory ?? native?.installed?.data_folder ?? 'Choose an installation'}</code><Btn size="sm" variant="ghost" onClick={() => { setSettingsSection('Storage'); go('settings'); }}>{tr("Storage settings")}</Btn></section><Btn size="sm" variant="ghost" onClick={onOtherModels}>{tr("Other local models\u2026")}</Btn></aside>
+1 -1
View File
@@ -70,7 +70,7 @@ export function NativeTune({ open, onClose, model, resourceProfile, vision }: {
actions = <><Btn onClick={() => void act(() => nativeAction(`operations/${job!.id}/cancel`))} disabled={busy}>{tr("Stop")}</Btn></>;
} else if (phase === 'result') {
const improved = result?.outcome === 'improved' || result?.verdict === 'keep';
const text = stopped ? tr("Stopped. Your settings were not changed.") : job?.state === 'error' ? tr("The tune did not finish. Your settings were not changed.") : result?.verdict ? (result.verdict === 'keep' ? tr("Still as fast as when it was saved.") : result.verdict === 'retune' ? tr("It is slower than before. You can tune again.") : tr("Could not tell right now. Your tuning was kept.")) : result?.outcome === 'improved' ? tr("+{0}% faster answers, saved as '{1}'", [Math.max(0, Math.round(result.gain_pct ?? 0)), result.preset?.name ?? '']) : result?.outcome === 'default_confirmed' ? tr("Your current settings are already the fastest we found.") : (result?.message ?? tr("Nothing was changed."));
const text = stopped ? tr("Stopped. Your settings were not changed.") : job?.state === 'error' ? tr("The tune did not finish. Your settings were not changed.") : result?.verdict ? (result.verdict === 'keep' ? tr("Still as fast as when it was saved.") : result.verdict === 'retune' ? tr("It is slower than before. You can tune again.") : tr("Could not tell right now. Your tuning was kept.")) : result?.outcome === 'improved' ? tr("+{0}% faster answers, saved as '{1}'", [Math.max(0, Math.round(result.gain_pct ?? 0)), result.preset?.name ?? '']) : result?.code === 'busy_pc' ? tr("Your PC was busy, so the measurements were not reliable. Nothing was changed. Try again when it is quiet.") : result?.outcome === 'default_confirmed' ? tr("Your current settings are already the fastest we found.") : (result?.message ?? tr("Nothing was changed."));
body = <div className="tune-progress">{orbit(improved || result?.outcome === 'default_confirmed')}<h3 role="status">{text}</h3>
{result?.outcome === 'default_confirmed' && result.preset ? <p className="small muted">{tr("Saved as '{0}', so it is not tuned again when you reopen the app.", [result.preset.name])}</p> : null}
{result?.outcome === 'improved' ? <><p className="small muted">{tr("Speed: {0} → {1} tok/s", [Math.round((result.base?.decode_tok_s ?? 0) * 10) / 10, Math.round((result.chosen?.decode_tok_s ?? 0) * 10) / 10])}</p>{notice(result.answers?.class, result.answers?.default_repeatable)}</> : null}
@@ -0,0 +1,37 @@
import { tr } from '../lib/i18n';
import { useCallback, useEffect, useState } from 'react';
import { Btn } from './ui';
import { answerNotice, tunePresetAction, tuneOff, tuneStatus, type TuneStatus } from '../lib/native-tune';
import { useNative } from '../lib/native';
/**
* Which saved tuning a start will use, in plain words, and a way to switch it off (back to the standard settings) or on again.
* Shown on the Models screen so nobody has to guess what runs after an unload or a restart. The tuning is applied by Strata at every
* start (see strata_control/presets.py); this only reads it and flips it. Layout is the UI worker's: it is one line of text and one button.
*/
export function NativeTuneBadge({ model, resourceProfile, vision }: { model: string; resourceProfile: 'work' | 'performance'; vision: string }) {
const native = useNative();
const [status, setStatus] = useState<TuneStatus | null>(null), [busy, setBusy] = useState(false), [error, setError] = useState('');
const jobs = native?.jobs.filter(j => j.state === 'running').length ?? 0;
const read = useCallback(async () => {
try { setStatus(await tuneStatus(model, resourceProfile, vision)); setError(''); }
catch { setStatus(null); }
}, [model, resourceProfile, vision]);
useEffect(() => { void read(); }, [read, jobs, native?.instances.length]);
async function flip(fn: () => Promise<unknown>) {
setBusy(true); setError('');
try { await fn(); await read(); }
catch (e) { setError(e instanceof Error ? e.message : String(e)); }
finally { setBusy(false); }
}
if (!status || (!status.active && !status.last_active)) return null;
const active = status.active;
return <p className="small muted" data-tuning-active={!!active} role="status">
{active ? <>{tr("Using your tuning '{0}'", [active.name])}{status.state === 'hard' ? <> {'·'} {tr("This PC changed since it was tuned, so these settings are not used until you tune again.")}</> : null}
{active.answers && active.answers.class !== 'exact' ? <> {'·'} {tr(answerNotice(active.answers.class, active.answers.default_repeatable))}</> : null}{' '}
<Btn size="sm" variant="ghost" disabled={busy} onClick={() => void flip(() => tuneOff(model, resourceProfile, vision))}>{tr("Switch off")}</Btn></>
: <>{tr("Your tuning is off. The standard settings are used.")}{' '}
<Btn size="sm" variant="ghost" disabled={busy} onClick={() => void flip(() => tunePresetAction(status.last_active!.id, 'apply'))}>{tr("Switch on")}</Btn></>}
{error ? <span className="inline-error" role="alert"> {error}</span> : null}
</p>;
}
+13
View File
@@ -25,6 +25,9 @@ export interface TuneStatus {
reasons: string[];
presets: TunePreset[];
can_undo: boolean;
/** the tuning was switched off for these choices (it is kept: last_active can be switched on again) */
switched_off?: boolean;
last_active?: TunePreset | null;
}
export interface TuneProgress { stage: string; text: string | null; minutesLeft: number | null; runsDone: number; runsPlanned: number; elapsedS: number; step: string }
@@ -42,6 +45,11 @@ export interface TuneResult {
answers?: { class: 'exact' | 'placement' | 'nonrepeatable' | 'quality'; fields: string[]; default_repeatable?: boolean };
preset?: { id: string; name: string } | null;
notes?: string[];
/** what the screen shows: faster | already_fastest | busy_pc | failed | stopped */
code?: 'faster' | 'already_fastest' | 'busy_pc' | 'failed' | 'stopped';
/** busy_pc: noisy | drift | not_tried; failed: start_failed | no_memory */
reason_code?: string;
values?: { gain_pct?: number; gain_decode_pct?: number; base_tok_s?: number | null; tuned_tok_s?: number | null; noise_pct?: number | null; drift_pct?: number | null; minutes?: number; pairs?: number; faster_pairs?: number; preset_name?: string | null };
verdict?: 'keep' | 'retune' | 'inconclusive';
reason?: string;
}
@@ -52,6 +60,11 @@ export const tuneStatus = (model: string, resourceProfile: string, vision: strin
api.get<TuneStatus>(`/api/native/tune/status?${q({ model, resourceProfile, vision })}`);
export const tunePresetAction = (id: string, action: 'apply' | 'rename' | 'delete' | 'undo', body: Record<string, unknown> = {}) =>
api.post(`/api/native/tune/presets/${id}/${action}`, body);
export const tuneOff = (model: string, resourceProfile: string, vision: string) =>
api.post('/api/native/tune/off', { model, resourceProfile, vision });
/** What a start will really run: the saved tuning's values on top of the configuration (Expert mode shows these). */
export const tuneEffective = (model: string, resourceProfile: string, vision: string) =>
api.get<{ applied: boolean; preset: { id: string; name: string } | null; args: string[]; env: Record<string, string>; changed: { flag: string; was: string | null; now: string | null }[]; why_not?: string | null }>(`/api/native/tune/effective?${q({ model, resourceProfile, vision })}`);
export const tuneUndo = (model: string, resourceProfile: string, vision: string) =>
api.post('/api/native/tune/undo', { model, resourceProfile, vision });
+6 -1
View File
@@ -36,5 +36,10 @@
"tune35": "Erneut abstimmen",
"tune36": "Abstimmung starten",
"tune37": "Dein Modell wird abgestimmt",
"tune38": "Auf diesem PC können die Antworten von Lauf zu Lauf abweichen, auch mit der Standardeinstellung; diese Einstellung verstärkt das nicht."
"tune38": "Auf diesem PC können die Antworten von Lauf zu Lauf abweichen, auch mit der Standardeinstellung; diese Einstellung verstärkt das nicht.",
"tune39": "Deine Abstimmung „{0}“ wird verwendet",
"tune40": "Ausschalten",
"tune41": "Einschalten",
"tune42": "Deine Abstimmung ist aus. Es gelten die Standardeinstellungen.",
"tune43": "Dein PC war ausgelastet, daher waren die Messungen nicht zuverlässig. Es wurde nichts geändert. Versuche es erneut, wenn er ruhig ist."
}
+6 -1
View File
@@ -36,5 +36,10 @@
"tune35": "Tune again",
"tune36": "Start tuning",
"tune37": "Tuning your model",
"tune38": "Answers can differ from run to run on this PC even with the standard setup; this setting does not add to that."
"tune38": "Answers can differ from run to run on this PC even with the standard setup; this setting does not add to that.",
"tune39": "Using your tuning '{0}'",
"tune40": "Switch off",
"tune41": "Switch on",
"tune42": "Your tuning is off. The standard settings are used.",
"tune43": "Your PC was busy, so the measurements were not reliable. Nothing was changed. Try again when it is quiet."
}
+6 -1
View File
@@ -36,5 +36,10 @@
"tune35": "Ajustar de nuevo",
"tune36": "Empezar a ajustar",
"tune37": "Ajustando tu modelo",
"tune38": "En este PC las respuestas pueden variar de una ejecución a otra incluso con el ajuste estándar; este ajuste no lo aumenta."
"tune38": "En este PC las respuestas pueden variar de una ejecución a otra incluso con el ajuste estándar; este ajuste no lo aumenta.",
"tune39": "Usando tu ajuste «{0}»",
"tune40": "Desactivar",
"tune41": "Activar",
"tune42": "Tu ajuste está desactivado. Se usan los ajustes estándar.",
"tune43": "Tu PC estaba ocupado, así que las mediciones no fueron fiables. No se cambió nada. Inténtalo de nuevo cuando esté tranquilo."
}
+6 -1
View File
@@ -36,5 +36,10 @@
"tune35": "もう一度チューニング",
"tune36": "チューニングを開始",
"tune37": "モデルをチューニング中",
"tune38": "このPCでは標準設定でも実行のたびに回答が変わることがあります。この設定がそれを増やすことはありません。"
"tune38": "このPCでは標準設定でも実行のたびに回答が変わることがあります。この設定がそれを増やすことはありません。",
"tune39": "あなたのチューニング「{0}」を使用中",
"tune40": "オフにする",
"tune41": "オンにする",
"tune42": "チューニングはオフです。標準設定を使用します。",
"tune43": "PCが忙しく、測定結果が信頼できませんでした。何も変更していません。PCが空いているときにもう一度お試しください。"
}
+6 -1
View File
@@ -36,5 +36,10 @@
"tune35": "다시 최적화",
"tune36": "최적화 시작",
"tune37": "모델을 최적화하는 중",
"tune38": "이 PC에서는 표준 설정에서도 실행할 때마다 답변이 달라질 수 있으며, 이 설정이 그 차이를 키우지는 않습니다."
"tune38": "이 PC에서는 표준 설정에서도 실행할 때마다 답변이 달라질 수 있으며, 이 설정이 그 차이를 키우지는 않습니다.",
"tune39": "내 최적화 ‘{0}’ 사용 중",
"tune40": "끄기",
"tune41": "켜기",
"tune42": "최적화가 꺼져 있습니다. 표준 설정을 사용합니다.",
"tune43": "PC가 바빠서 측정 결과를 믿을 수 없었습니다. 아무것도 변경하지 않았습니다. PC가 한가할 때 다시 시도하세요."
}
+6 -1
View File
@@ -36,5 +36,10 @@
"tune35": "Ajustar de novo",
"tune36": "Iniciar ajuste",
"tune37": "Ajustando o seu modelo",
"tune38": "Neste PC as respostas podem variar de uma execução para outra mesmo com a configuração padrão; esta configuração não aumenta isso."
"tune38": "Neste PC as respostas podem variar de uma execução para outra mesmo com a configuração padrão; esta configuração não aumenta isso.",
"tune39": "Usando o seu ajuste “{0}”",
"tune40": "Desligar",
"tune41": "Ligar",
"tune42": "O seu ajuste está desligado. As configurações padrão são usadas.",
"tune43": "O seu PC estava ocupado, então as medições não foram confiáveis. Nada foi alterado. Tente de novo quando ele estiver tranquilo."
}
+6 -1
View File
@@ -36,5 +36,10 @@
"tune35": "Настроить снова",
"tune36": "Начать настройку",
"tune37": "Настройка вашей модели",
"tune38": "На этом ПК ответы могут отличаться от запуска к запуску даже при стандартных настройках; эта настройка этого не усиливает."
"tune38": "На этом ПК ответы могут отличаться от запуска к запуску даже при стандартных настройках; эта настройка этого не усиливает.",
"tune39": "Используется ваша настройка «{0}»",
"tune40": "Выключить",
"tune41": "Включить",
"tune42": "Ваша настройка выключена. Используются стандартные параметры.",
"tune43": "Ваш ПК был занят, поэтому измерения были ненадёжными. Ничего не изменено. Повторите, когда он освободится."
}
+6 -1
View File
@@ -36,5 +36,10 @@
"tune35": "再次调优",
"tune36": "开始调优",
"tune37": "正在调优你的模型",
"tune38": "在这台电脑上,即使使用标准设置,每次运行的回答也可能不同;此设置不会增加这种差异。"
"tune38": "在这台电脑上,即使使用标准设置,每次运行的回答也可能不同;此设置不会增加这种差异。",
"tune39": "正在使用你的调优“{0}”",
"tune40": "关闭",
"tune41": "开启",
"tune42": "你的调优已关闭,使用标准设置。",
"tune43": "你的电脑当时很忙,测量结果不可靠。没有做任何改动。请在电脑空闲时再试。"
}
+21
View File
@@ -15,6 +15,7 @@ function handler(log, { result, hold = false, delayMs = 60 } = {}) {
const out = json => { child.stdout.emit('data', Buffer.from(JSON.stringify(json))); child.exitCode = 0; child.emit('exit', 0); child.emit('close', 0); };
if (cmd[0] === 'presets' && cmd[1] === 'status') return out({ ok: true, active: null, suggested_name: 'IQ2_XS 32K – Full speed', presets: [], state: null, reasons: [] });
if (cmd[0] === 'presets' && cmd[1] === 'list') return out({ ok: true, presets: [{ id: 'a'.repeat(32), name: 'x' }] });
if (cmd[0] === 'presets' && cmd[1] === 'effective') return out({ ok: true, applied: true, preset: { id: 'a'.repeat(32), name: 'IQ2_XS 32K – Work' }, args: ['--kv-resident', '65536'], changed: [{ flag: '--kv-resident', was: '32768', now: '65536' }] });
if (cmd[0] === 'presets') return out({ ok: true, done: cmd[1] });
if (cmd[0] === 'tune') {
const status = arg(cmd, '--status-file');
@@ -135,3 +136,23 @@ test('a model that is not prepared cannot be tuned', async () => {
assert.match(job.error, /Prepare the model/);
} finally { await f.cleanup(); }
});
test('Expert mode can read the tuned values and the tuning can be switched off for these choices', async () => {
const log = [];
const f = nativeFixture({ tuneHandler: handler(log, {}) });
try {
await f.control.refresh(true);
const eff = await f.control.tuneEffective({ model: 'iq2_xs', resourceProfile: 'work' });
assert.equal(eff.applied, true);
assert.deepEqual(eff.changed, [{ flag: '--kv-resident', was: '32768', now: '65536' }]);
const cmd = log.find(c => c[1] === 'effective');
const cfg = JSON.parse(readFileSync(cmd[2], 'utf8'));
assert.equal(cfg.ui_resource_profile, 'work'); // the choices a load would make, so the right tuning is found
await f.control.tuneOff({ model: 'iq2_xs', resourceProfile: 'work' });
assert.deepEqual(log.at(-1).slice(0, 2), ['presets', 'off']);
} finally { await f.cleanup(); }
});
test('a busy PC is a plain result, not an error', () => {
assert.match(resultSentence({ outcome: 'inconclusive', code: 'busy_pc', message: 'x' }), /PC was busy/);
});
+30 -17
View File
@@ -7126,6 +7126,34 @@ def lift_reasoning_effort_arg(cfg: dict) -> None:
SAMPLING_ALIASES = {"repeat_penalty": "repetition_penalty", "repeat_last_n": "penalty_last_n"}
def apply_saved_tuning(cfg: dict, *, say=None) -> dict:
"""The run config with the active saved tuning for its choices put on top (strata_control/presets.py), with no measurement.
Every start path runs this (the Terminal, `runtime start`, the app's load, a restart of the app, an unload and a load), so the
tuning is applied every time and never re-measured. A tune's own worker sets STRATA_NO_PRESET so that it measures the
configuration it was given. Never fails a start: a store that cannot be read means the configuration as it is."""
if os.environ.get("STRATA_NO_PRESET"):
return cfg
say = say or (lambda line: print(line, flush=True))
try:
from strata_control import presets as _presets
out, applied = _presets.overlay(cfg)
if applied.get("applied"):
changes = {**(applied.get("args") or {}), **(applied.get("env") or {})}
note = applied.get("notice") or ""
say(f"[strata] saved tuning '{applied['preset']['name']}' applied: "
f"{' '.join(f'{k} {v}' for k, v in changes.items()) or 'the standard settings (nothing to change)'}"
f"{'; ' + note if note and 'identical' not in note else ''}")
if applied.get("state") == "soft":
say("[strata] this PC or engine changed since it was saved (" + "; ".join(applied["reasons"]) +
"): a short re-check is available (strata presets recheck)")
elif applied.get("why_not"):
say(f"[strata] saved tuning '{applied['preset']['name']}' {applied['why_not']}; re-tune with strata tune auto")
return out
except Exception as e: # noqa: BLE001
say(f"[strata] saved tuning could not be read ({type(e).__name__}); starting with the configuration as it is")
return cfg
def sampling_defaults_from_config(cfg: dict) -> dict:
"""The run config's optional `sampling` block: defaults for the sampling fields a request leaves out, so
a plain client gets configured sampling instead of greedy. Supported: temperature, top_p, top_k, min_p,
@@ -7413,23 +7441,8 @@ def main(argv=None, *, lifecycle_event=None, interrupt_exit_code: int = 0) -> in
ap.error("--shutdown-timeout must be in 0..300 seconds")
config_bytes = Path(a.config).read_bytes() if a.config else b""
cfg = json.loads(config_bytes.decode("utf-8-sig")) if a.config else {} # Notepad adds a BOM
if a.config and a.engine == "strata" and not os.environ.get("STRATA_NO_PRESET"):
# the saved tuning for this model and these choices (strata_control/presets.py): applied with no measurement. A tune's
# own worker sets STRATA_NO_PRESET so that it measures the configuration it was given. Never fails the start.
try:
from strata_control import presets as _presets
cfg, _applied = _presets.overlay(cfg)
if _applied.get("applied"):
print(f"[strata] saved tuning '{_applied['preset']['name']}' applied: "
f"{' '.join(f'{k} {v}' for k, v in {**(_applied.get('args') or {}), **(_applied.get('env') or {})}.items())}"
f"{'; ' + _applied['notice'] if _applied.get('notice') and 'identical' not in _applied['notice'] else ''}", flush=True)
if _applied.get("state") == "soft":
print("[strata] this PC or engine changed since it was saved (" + "; ".join(_applied["reasons"]) +
"): a short re-check is available (strata presets recheck)", flush=True)
elif _applied.get("why_not"):
print(f"[strata] saved tuning '{_applied['preset']['name']}' {_applied['why_not']}; re-tune with strata tune auto", flush=True)
except Exception as _e: # noqa: BLE001
print(f"[strata] saved tuning could not be read ({type(_e).__name__}); starting with the configuration as it is", flush=True)
if a.config and a.engine == "strata":
cfg = apply_saved_tuning(cfg)
attestation_key = os.environ.pop("STRATA_OWNER_ATTEST_KEY", None)
launch_attestation = None
if a.owner_fd is not None:
+44 -1
View File
@@ -74,6 +74,10 @@ def add_presets(sp: argparse._SubParsersAction) -> None:
ex.add_argument("--file", default=None)
im = common(psp.add_parser("import", help="read a preset from a file"))
im.add_argument("file")
ef = common(psp.add_parser("effective", help="what a start with these choices will actually run: the saved tuning's values on top of the configuration"))
ef.add_argument("target", help="a saved profile name or a run configuration (JSON)")
of = common(psp.add_parser("off", help="switch the saved tuning off for these choices (back to the standard settings; it is kept and can be switched on again)"))
of.add_argument("target", help="a saved profile name or a run configuration (JSON)")
rc = common(psp.add_parser("recheck", help="a short check (about 2 minutes) that a preset is still as fast"))
rc.add_argument("preset")
rc.add_argument("target", help="the profile name or run configuration the preset belongs to")
@@ -244,6 +248,9 @@ def auto(a: argparse.Namespace) -> int:
except presets.PresetError as e:
result.notes.append(str(e))
out["preset"] = ({"id": saved["id"], "name": saved["name"]} if saved else None)
autotune.Tuner.finalize(result)
out.update({"code": result.code, "reason_code": result.reason_code, "values": result.values})
out["values"]["preset_name"] = saved["name"] if saved else None
state.update({"state": "done", "result": out, "stage": "done"})
_write_status(a.status_file, state)
if machine:
@@ -292,6 +299,10 @@ def presets_command(a: argparse.Namespace) -> int:
return _print({"presets": [_summary(p) for p in items]}, machine, human)
if cmd == "status":
return _status(a, machine)
if cmd == "effective":
return _effective(a, machine)
if cmd == "off":
return _off(a, machine)
if cmd == "show":
p = presets.find(a.preset)
return _print({"preset": p}, machine, json.dumps(p, indent=2, ensure_ascii=False))
@@ -346,6 +357,36 @@ def _summary(p: dict[str, Any]) -> dict[str, Any]:
"recheck": p.get("recheck"), "slot": p.get("slot")}
def _effective(a: argparse.Namespace, machine: bool) -> int:
"""The values a start will really use (owner feedback, item 11): the configuration with the active saved tuning put on top,
plus the list of what the tuning changed, so Expert mode can show the tuned numbers and not the untuned ones."""
from strata_control import presets, tune_knobs
_path, cfg = _load_target(a.target)
merged, info = presets.overlay(cfg)
before = [str(x) for x in cfg.get("args") or []]
after = [str(x) for x in merged.get("args") or []]
changed = []
for flag in sorted({x for x in before + after if x.startswith("--")}):
was, now = tune_knobs._current(before, flag), tune_knobs._current(after, flag)
if was != now:
changed.append({"flag": flag, "was": was, "now": now})
payload = {"applied": bool(info.get("applied")), "preset": info.get("preset"), "state": info.get("state"), "reasons": info.get("reasons", []),
"why_not": info.get("why_not"), "notice": info.get("notice"), "args": merged.get("args"), "env": merged.get("env") or {},
"sampling": merged.get("sampling") or {}, "changed": changed, "per_request": info.get("per_request") or {}}
human = (f"Using your tuning '{info['preset']['name']}'" if info.get("applied") else "No saved tuning is applied; the standard settings run.")
return _print(payload, machine, human)
def _off(a: argparse.Namespace, machine: bool) -> int:
"""Switch the saved tuning off for these choices. It is kept (and listed), so switching it back on is one step."""
from strata_control import presets
_path, cfg = _load_target(a.target)
ident = presets.current_identity(cfg)
slot = presets.slot_key(presets.choices_from_config(cfg), (ident.get("model") or {}).get("sha256_shard1"))
presets.deactivate_slot(slot)
return _print({"active": None}, machine, "Back to the standard settings. Your tuning is kept and can be switched on again.")
def _status(a: argparse.Namespace, machine: bool) -> int:
from strata_control import presets
_path, cfg = _load_target(a.target)
@@ -360,7 +401,9 @@ def _status(a: argparse.Namespace, machine: bool) -> int:
return _print({"slot": slot, "suggested_name": default, "choices": choices,
"active": _summary(active) if active else None, "state": stale["state"] if stale else None,
"reasons": stale["reasons"] if stale else [], "presets": [_summary(p) for p in items],
"can_undo": bool(idx.get("history")) or bool(active)},
"can_undo": bool(idx.get("history")) or bool(active),
"switched_off": bool(not active and idx.get("history")),
"last_active": next((_summary(p) for p in items if p["id"] == (idx.get("history") or [None])[-1]), None) if not active else None},
machine, (f"Active: '{active['name']}' ({stale['state']})" if active else "No saved tuning for these choices yet."))
+29
View File
@@ -95,6 +95,13 @@ class ArmReport:
@dataclass
class TuneResult:
outcome: str = "default_confirmed" # improved | default_confirmed | inconclusive | base_failed | cancelled
#: what a screen shows, one of: faster | already_fastest | busy_pc | failed | stopped (see `finalize`)
code: str = "already_fastest"
#: why, for busy_pc: noisy | drift | not_tried (no memory or time to compare anything); for failed: start_failed | no_engine
reason_code: str = ""
#: the numbers a screen puts in its sentence, all plain numbers: gain_pct, base_tok_s, tuned_tok_s, noise_pct, drift_pct,
#: minutes, pairs, faster_pairs, preset_name
values: dict[str, Any] = field(default_factory=dict)
message: str = ""
mode: str = "quick"
seconds: float = 0.0
@@ -321,8 +328,25 @@ class Tuner:
r.outcome, r.message = "cancelled", "Stopped. Your settings were not changed."
r.seconds = round(self.clock() - self.started, 1)
r.waited_s = round(self.waited_s, 1)
self.finalize(r)
return r
@staticmethod
def finalize(r: "TuneResult") -> None:
"""The structured outcome a screen shows inside the tune dialog (owner feedback, item 13): a code and plain numbers, so no
screen has to read a sentence. faster / already_fastest / busy_pc (noisy, drifting, or nothing could be compared) /
failed / stopped. `busy_pc` is a result, not an error: nothing was changed and trying again later is the answer."""
r.code = {"improved": "faster", "default_confirmed": "already_fastest", "inconclusive": "busy_pc",
"base_failed": "failed", "cancelled": "stopped"}.get(r.outcome, "failed")
if r.outcome == "default_confirmed" and r.noise.get("noisy"):
r.reason_code = r.reason_code or "noisy_but_nothing_better"
base = (r.base or {}).get("decode_tok_s")
tuned = (r.chosen or {}).get("decode_tok_s")
r.values = {"gain_pct": r.gain_pct, "gain_decode_pct": r.gain_decode_pct, "base_tok_s": base, "tuned_tok_s": tuned,
"noise_pct": round(100 * (r.noise or {}).get("spread", 0.0), 1) if r.noise else None,
"drift_pct": round(100 * (r.noise or {}).get("drift", 0.0), 1) if r.noise else None,
"minutes": round(r.seconds / 60.0 - r.waited_s / 60.0, 1), "pairs": r.pairs, "faster_pairs": r.faster_pairs}
def _spec(self) -> RequestSet:
long_tokens = 0
if self.ctx.context_tokens >= 65536:
@@ -343,6 +367,7 @@ class Tuner:
why = self._ram_guard(0, "your current settings")
if why:
r.outcome, r.message = "base_failed", "Not tuned: " + why
r.reason_code = "no_memory"
return
pr_rows: list[dict[str, Any]] = []
@@ -355,6 +380,7 @@ class Tuner:
self.base_runs.append(base1)
if base1.status != "PASS" or not metric(base1, o.use):
r.outcome = "base_failed"
r.reason_code = "start_failed"
r.message = "Your current settings could not be measured: " + (base1.reason or base1.status)
return
r.base = base1.to_dict()
@@ -574,6 +600,7 @@ class Tuner:
chosen_args, chosen_env, winners, final_pairs = {}, {}, [], []
per_request_chosen = {}
r.outcome = "inconclusive"
r.reason_code = "drift"
r.message = ("The PC changed speed while measuring (about %.0f%%), more than the gain found, so nothing "
"was changed. Try again when nothing else is running." % (drift * 100))
if r.outcome != "inconclusive":
@@ -581,10 +608,12 @@ class Tuner:
r.outcome = "improved"
elif r.noise["noisy"] and (spread > 2 * STAGE_GAP_NOISY):
r.outcome = "inconclusive"
r.reason_code = "noisy"
r.message = "The PC was too noisy to tell settings apart, so nothing was changed. Try again when it is idle."
elif arms and not any(a.status in ("tested", "dropped", "failed") for a in arms) and not r.per_request:
# candidates were planned but none could be measured (no free memory, no time): that is not "already the fastest"
r.outcome = "inconclusive"
r.reason_code = "not_tried"
r.message = ("Other settings could not be tried (not enough free memory or time), so nothing was compared and nothing was "
"changed. Try again when the PC is quiet.")
else:
+130
View File
@@ -0,0 +1,130 @@
"""A saved tuning is applied at every start: after an app restart (a new process), after an unload and a load, and it can be seen
(`presets effective`, `presets status`) and switched off and on again."""
from __future__ import annotations
import json
import os
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path
ROOT = Path(__file__).resolve().parents[2]
def cli(env, *args):
out = subprocess.run([sys.executable, str(ROOT / "strata.py"), *args, "--output", "json"], capture_output=True, text=True,
env=env, cwd=str(ROOT), timeout=120)
return json.loads(out.stdout[out.stdout.index("{"):]), out
def fresh_process(env, code):
"""A new Python process: what an app restart or a new server start is."""
r = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True, env=env, cwd=str(ROOT), timeout=120)
assert r.returncode == 0, r.stderr
return json.loads(r.stdout.strip().splitlines()[-1])
class PersistenceTest(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
self.addCleanup(self.tmp.cleanup)
t = Path(self.tmp.name)
(t / "model.gguf").write_bytes(b"gguf" * 1000)
(t / "engine.exe").write_bytes(b"x")
(t / "tok").mkdir()
self.cfg_path = t / "strata-model.json"
self.cfg = {"exe": str(t / "engine.exe"), "tokenizer": str(t / "tok"), "model_name": "iq3_s-test", "ui_resource_profile": "work",
"args": ["--native", str(t / "model.gguf"), "--expert-cache", "auto", "--spec", "4", "--max-context", "65536",
"--kv", "int8", "--kv-resident", "32768"]}
self.cfg_path.write_text(json.dumps(self.cfg))
self.env = dict(os.environ, STRATA_PRESET_DIR=str(t / "presets"), PYTHONPATH=str(ROOT), STRATA_NO_PRESET="")
self.env.pop("STRATA_NO_PRESET")
# the tune's result, saved as a preset by the library, the way `strata tune auto` does
sys.path.insert(0, str(ROOT))
os.environ["STRATA_PRESET_DIR"] = str(t / "presets")
self.addCleanup(lambda: os.environ.pop("STRATA_PRESET_DIR", None))
from strata_control import presets
self.presets = presets
ident = presets.current_identity(self.cfg, exe=self.cfg["exe"])
result = {"outcome": "improved", "seconds": 900, "settings": {"args": {"--kv-resident": 65536}, "env": {}, "per_request": {}},
"answers": {"class": "exact", "fields": []}, "base": {"decode_tok_s": 60.0}, "chosen": {"decode_tok_s": 64.0},
"gain_pct": 6.6, "pairs": 2, "faster_pairs": 2, "noise": {}}
self.preset = presets.create(result, choices=presets.choices_from_config(self.cfg), identity=ident, name="IQ3_S 64K – Work")
CODE = ("import json,sys;sys.path.insert(0,'.');from serve import server;"
"c=json.load(open(%r));out=server.apply_saved_tuning(c,say=lambda l:None);"
"print(json.dumps(out['args']))")
def args_at_start(self):
return fresh_process(self.env, self.CODE % str(self.cfg_path))
def test_applied_after_restart_and_after_unload_and_load(self):
for _ in range(3): # three starts = a restart, an unload, a load: every one is a new server process
args = self.args_at_start()
self.assertEqual(args[args.index("--kv-resident") + 1], "65536")
self.assertEqual(args[args.index("--spec") + 1], "4") # the user's own flags are untouched
self.assertEqual(json.loads(self.cfg_path.read_text())["args"], self.cfg["args"]) # the saved run config itself never changes
def test_a_tune_worker_measures_the_configuration_as_given(self):
env = dict(self.env, STRATA_NO_PRESET="1")
args = fresh_process(env, self.CODE % str(self.cfg_path))
self.assertEqual(args[args.index("--kv-resident") + 1], "32768")
def test_expert_mode_can_read_what_will_run(self):
out, _ = cli(self.env, "presets", "effective", str(self.cfg_path))
self.assertTrue(out["applied"])
self.assertEqual(out["preset"]["name"], "IQ3_S 64K – Work")
self.assertEqual(out["changed"], [{"flag": "--kv-resident", "was": "32768", "now": "65536"}])
self.assertEqual(out["args"][out["args"].index("--kv-resident") + 1], "65536")
def test_which_tuning_is_in_use_and_switching_it_off_and_on(self):
st, _ = cli(self.env, "presets", "status", str(self.cfg_path))
self.assertEqual(st["active"]["name"], "IQ3_S 64K – Work")
self.assertFalse(st["switched_off"])
off, _ = cli(self.env, "presets", "off", str(self.cfg_path))
self.assertIsNone(off["active"])
args = self.args_at_start()
self.assertEqual(args[args.index("--kv-resident") + 1], "32768") # back to the standard settings
eff, _ = cli(self.env, "presets", "effective", str(self.cfg_path))
self.assertFalse(eff["applied"])
st, _ = cli(self.env, "presets", "status", str(self.cfg_path))
self.assertIsNone(st["active"])
self.assertTrue(st["switched_off"])
self.assertEqual(st["last_active"]["name"], "IQ3_S 64K – Work") # kept, and listed, so it can be switched on again
on, _ = cli(self.env, "presets", "apply", st["last_active"]["id"])
self.assertTrue(on["ok"])
args = self.args_at_start()
self.assertEqual(args[args.index("--kv-resident") + 1], "65536")
def test_a_changed_gpu_is_not_applied_and_says_why(self):
p = self.presets.load(self.preset["id"])
p["identity"]["hardware"]["hard"]["gpus"] = [{"name": "Another GPU", "vram_mib": 1}]
self.presets._write_json(self.presets._path(p["id"]), p)
out, _ = cli(self.env, "presets", "effective", str(self.cfg_path))
self.assertFalse(out["applied"])
self.assertIn("graphics card", out["why_not"])
class OutcomeCodesTest(unittest.TestCase):
def test_codes_and_values(self):
from strata_control import autotune
mk = autotune.TuneResult
r = mk(outcome="improved", gain_pct=7.2, seconds=1200, waited_s=120, pairs=5, faster_pairs=4)
r.base, r.chosen = {"decode_tok_s": 50.0}, {"decode_tok_s": 53.6}
autotune.Tuner.finalize(r)
self.assertEqual(r.code, "faster")
self.assertEqual(r.values["gain_pct"], 7.2)
self.assertEqual(r.values["minutes"], 18.0)
for outcome, reason, code in (("default_confirmed", "", "already_fastest"), ("inconclusive", "noisy", "busy_pc"),
("inconclusive", "drift", "busy_pc"), ("inconclusive", "not_tried", "busy_pc"),
("base_failed", "start_failed", "failed"), ("cancelled", "", "stopped")):
x = mk(outcome=outcome, reason_code=reason)
autotune.Tuner.finalize(x)
self.assertEqual((x.code, x.reason_code), (code, reason), outcome)
if __name__ == "__main__":
unittest.main()