mirror of
https://github.com/R0m1k3/Loki.git
synced 2026-10-19 13:08:15 +02:00
Moteur : décodage spéculatif MTP en opt-in — tête détectée dans le fichier, garde-fous VRAM, et un essai raté ne se rejoue pas
Une tête MTP (Qwen3.6/3.8, intégrée ou publiée à part en mtp-*.gguf) accélère
le décodage sans rien changer à la sortie : chaque jeton émis est tiré par
l'échantillonneur du modèle cible, un jeton du brouillon n'est gardé que s'il
coïncide. Le prix est ailleurs — 1 à 2 Go de VRAM, un prefill parfois plus
lent, une fonction très récente du moteur — d'où l'opt-in : rien ne change
pour un preset existant.
- SPEC=off (défaut, clé absente comprise) / auto / mtp. La tête se reconnaît
au TENSEUR blk.{N-1}.nextn.eh_proj (backend_gguf.go), comme llama.cpp : la
clé nextn_predict_layers seule ne prouve rien.
- MODEL_DRAFT : résolu comme MMPROJ, mais introuvable ne bloque pas le
lancement. Tête à part → -md + --spec-type draft-mtp explicite (llama.cpp ne
devine que sur la première tranche) ; autre brouillon → draft-simple, sinon
chargé en VRAM pour rien.
- auto seulement si --fit place tout (ni couches chiffrées, -ts, -ot, experts
sur CPU, -sm row, --fit off, -dev), contexte chiffré (fit pourrait sinon le
réduire), pas de vision, build officiel ≥ 11009, jamais qwen4exp. mtp impose,
avec un avertissement.
- Aide du moteur lue à chaque fois : draft-mtp et --spec-draft-n-max requis ;
--draft/--draft-max jamais émis. EXTRA_ARGS (--spec-type, -md, -hfd,
--spec-default…) et LLAMA_ARG_SPEC_* gardent la main.
- Tirage greedy fixé (exact pour toute chaîne). probabilistic seulement avec
SPEC=mtp, refusé avec mirostat ou adaptive-p. SPEC_N_MAX → --spec-draft-n-max.
- Jeton de tentative : « loki serve » le pose avant de lancer, le process web
l'efface dès que le moteur répond (sans page ouverte). Resté au lancement
suivant, même preset, moteur et build → auto coupé et dit. Couches poussées
en RAM après chargement : même verdict. Les erreurs MTP/brouillon du journal
ont leur message.
- Éditeur : « auto » et « MTP » écrivent SPEC, « non » écrit SPEC=off ; champ
Brouillon ; la recherche HF propose la tête MTP du dépôt, décochée.
- Tests : table de specArgs (gardes, sauts, brouillons, tirage), place avant
EXTRA_ARGS, cycle du jeton sur une vraie base, lecture du journal.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
7f49310b7f
commit
3a6ad6f23b
10 files changed
+963
-36
No files matched your search
@@ -1255,16 +1255,19 @@ document.documentElement.setAttribute('data-side',localStorage.getItem('loki-sid
|
||||
<option value="bf16">bf16 (numérique différente de f16)</option>
|
||||
</select></span></span>
|
||||
</div>
|
||||
<!-- Décodage spéculatif : le type vient de la liste que llama-server
|
||||
accepte pour --spec-type. Le nombre de jetons anticipés
|
||||
<!-- Décodage spéculatif : « auto » et « MTP » écrivent la clé SPEC,
|
||||
que Loki traduit au lancement (tête MTP détectée dans le fichier,
|
||||
garde-fous VRAM) ; les autres types vont tels quels dans
|
||||
--spec-type. « non » écrit SPEC=off. Le nombre de jetons anticipés
|
||||
(--spec-draft-n-max) n'a de sens qu'une fois un type choisi, donc
|
||||
sa ligne n'apparaît qu'à ce moment. -->
|
||||
<div class="pe-row">
|
||||
<span class="pe-row-l">Décodage spéculatif<span class="pe-sub">anticipe des jetons pour accélérer</span></span>
|
||||
<span class="pe-row-c"><span class="pe-selc"><select id="s-spec" onchange="onSpecType()">
|
||||
<option value="">non</option>
|
||||
<option value="auto">auto (MTP si le modèle en a une et que la VRAM suit)</option>
|
||||
<optgroup label="Modèle brouillon">
|
||||
<option value="draft-mtp">MTP (intégré au modèle)</option>
|
||||
<option value="draft-mtp">MTP (imposé)</option>
|
||||
<option value="draft-eagle3">EAGLE-3</option>
|
||||
<option value="draft-simple">modèle brouillon séparé</option>
|
||||
<option value="draft-dflash">dFlash</option>
|
||||
@@ -1283,6 +1286,12 @@ document.documentElement.setAttribute('data-side',localStorage.getItem('loki-sid
|
||||
<span class="pe-row-l">Jetons anticipés<span class="pe-sub">par étape — défaut 3</span></span>
|
||||
<span class="pe-row-c"><input id="s-spec-n" class="pe-val" type="number" min="1" step="1" placeholder="3" oninput="eaSetValued('--spec-draft-n-max', this.value)"></span>
|
||||
</div>
|
||||
<!-- Tête MTP publiée à part (mtp-*.gguf) ou petit modèle brouillon :
|
||||
clé MODEL_DRAFT, résolue au lancement comme le projecteur vision. -->
|
||||
<div class="pe-row" id="s-draft-row" style="display:none">
|
||||
<span class="pe-row-l">Brouillon<span class="pe-sub">tête MTP à part ou petit modèle</span></span>
|
||||
<span class="pe-row-c"><span class="pe-selc"><select id="m-draft" onchange="onPickDraft()"></select></span></span>
|
||||
</div>
|
||||
<div class="pe-row">
|
||||
<span class="pe-row-l">Raisonnement<span class="pe-sub" id="s-reasoning-sub">réflexion étape par étape</span></span>
|
||||
<span class="pe-row-c"><label class="pe-switch"><input type="checkbox" id="s-reasoning" onchange="cfgWriteKey('REASONING', this.checked?'on':'off')"><span class="pe-sl"></span></label></span>
|
||||
|
||||
@@ -165,7 +165,7 @@ async function openItem(kind, key){
|
||||
document.getElementById('m-quant').value = currentQuantInTextarea();
|
||||
populateSettings();
|
||||
attachDownload(); // téléchargement encore en cours côté serveur ?
|
||||
await Promise.all([loadGpuDevices(), populateModelPicker(), populateMmproj(),
|
||||
await Promise.all([loadGpuDevices(), populateModelPicker(), populateMmproj(), populateDraft(),
|
||||
populateDlDirs(), refreshInstalled()]);
|
||||
if(seq !== openSeq) return;
|
||||
}
|
||||
@@ -523,7 +523,7 @@ async function deleteInstalled(m){
|
||||
}
|
||||
if(!r.ok){ toast('erreur : ' + (r.error||'')); return; }
|
||||
toast('modèle supprimé — ' + fmtSize(r.freed||0) + ' libérés');
|
||||
await Promise.all([populateModelPicker(), populateMmproj(), populateDlDirs(),
|
||||
await Promise.all([populateModelPicker(), populateMmproj(), populateDraft(), populateDlDirs(),
|
||||
populateModelDirs(), refreshInstalled()]);
|
||||
}
|
||||
// Un .gguf donné est-il déjà sur le disque, complet ? Sert avant de proposer un
|
||||
@@ -897,7 +897,7 @@ function populateSettings(){
|
||||
set('s-reppen', cfgReadKey('REPEAT_PENALTY'));
|
||||
set('s-moe', eaGetValued('--n-cpu-moe'));
|
||||
set('s-spec-n', eaGetValued('--spec-draft-n-max'));
|
||||
setSpecType(eaGetValued('--spec-type'));
|
||||
setSpecType(eaGetValued('--spec-type') || specFromKey(cfgReadKey('SPEC')));
|
||||
const chk = (id,v)=>{ const e=document.getElementById(id); if(e) e.checked=v; };
|
||||
// Raisonnement : trois états dans le fichier, deux positions sur l'interrupteur.
|
||||
// « off » est une interdiction explicite passée au moteur ; la clé ABSENTE, elle,
|
||||
@@ -1046,7 +1046,14 @@ function syncLoadModeSub(mode){
|
||||
const el = document.getElementById('s-loadmode-sub');
|
||||
if(el) el.textContent = LOAD_MODE_SUB[mode] ?? mode;
|
||||
}
|
||||
// --- Décodage spéculatif (--spec-type / --spec-draft-n-max) ----------------
|
||||
// --- Décodage spéculatif (SPEC, MODEL_DRAFT, --spec-type, --spec-draft-n-max)
|
||||
// « auto » et « MTP » passent par la clé SPEC : Loki cherche la tête MTP dans
|
||||
// le fichier et pose les garde-fous au lancement. Les autres types restent des
|
||||
// --spec-type d'EXTRA_ARGS, qui ont toujours le dernier mot côté serveur.
|
||||
function specFromKey(v){
|
||||
v = String(v||'').trim().toLowerCase();
|
||||
return v === 'auto' ? 'auto' : v === 'mtp' ? 'draft-mtp' : '';
|
||||
}
|
||||
// Sélectionne le type courant. --spec-type accepte en réalité une LISTE séparée
|
||||
// par des virgules ; une valeur composée (ou un type sorti après cette version
|
||||
// de loki) ne correspondrait à aucune option et serait silencieusement effacée
|
||||
@@ -1072,17 +1079,55 @@ function syncSpecRow(){
|
||||
if(!sel || !row) return;
|
||||
const on = !!sel.value;
|
||||
row.style.display = on ? '' : 'none';
|
||||
// Le brouillon ne sert qu'à SPEC (auto / MTP) ; un brouillon déjà posé reste
|
||||
// visible pour qu'on puisse le retirer.
|
||||
const dr = document.getElementById('s-draft-row');
|
||||
if(dr) dr.style.display = (sel.value === 'auto' || sel.value === 'draft-mtp' || cfgReadKey('MODEL_DRAFT')) ? '' : 'none';
|
||||
return on;
|
||||
}
|
||||
function onSpecType(){
|
||||
const v = document.getElementById('s-spec').value;
|
||||
eaSetValued('--spec-type', v);
|
||||
const viaKey = v === 'auto' || v === 'draft-mtp';
|
||||
// « non » écrit SPEC=off : explicite, et identique au défaut du serveur.
|
||||
cfgWriteKey('SPEC', v === 'auto' ? 'auto' : v === 'draft-mtp' ? 'mtp' : v ? '' : 'off');
|
||||
eaSetValued('--spec-type', viaKey ? '' : v);
|
||||
if(!v){
|
||||
eaSetValued('--spec-draft-n-max', '');
|
||||
document.getElementById('s-spec-n').value = '';
|
||||
}
|
||||
syncSpecRow();
|
||||
}
|
||||
// Liste des brouillons : tous les .gguf sauf les projecteurs vision. Une tête
|
||||
// MTP publiée à part (mtp-*.gguf) est ce qu'on y choisira le plus souvent.
|
||||
async function populateDraft(){
|
||||
const sel = document.getElementById('m-draft');
|
||||
if(!sel) return;
|
||||
const list = await jget('/api/models');
|
||||
const cur = readEnvKey(document.getElementById('m-content').value, 'MODEL_DRAFT');
|
||||
const items = (list||[]).filter(m => !isMmprojName(m.name));
|
||||
const hit = matchModel(cur, items);
|
||||
let html = '<option value="">— aucun —</option>';
|
||||
let matched = false;
|
||||
for(const m of items){
|
||||
const on = (m === hit) ? ' selected' : '';
|
||||
if(on) matched = true;
|
||||
html += '<option value="'+escHtml(m.value)+'"'+on+'>'+escHtml(m.name)+' ('+fmtSize(m.size)+')</option>';
|
||||
}
|
||||
if(cur && !matched){
|
||||
html += '<option value="'+escHtml(cur)+'" selected>'+escHtml(baseName(cur)||cur)+' (introuvable)</option>';
|
||||
}
|
||||
sel.innerHTML = html;
|
||||
syncSpecRow();
|
||||
}
|
||||
// Choisir un brouillon, c'est demander la spéculation : on passe en « auto » si
|
||||
// elle était coupée (les garde-fous de Loki restent en place).
|
||||
function onPickDraft(){
|
||||
const v = document.getElementById('m-draft').value;
|
||||
cfgWriteKey('MODEL_DRAFT', v);
|
||||
const sel = document.getElementById('s-spec');
|
||||
if(v && sel && !sel.value){ sel.value = 'auto'; onSpecType(); toast('décodage spéculatif : auto'); }
|
||||
syncSpecRow();
|
||||
}
|
||||
|
||||
// Replie/déplie l'éditeur du fichier .env (config brute) dans le modal preset.
|
||||
function toggleRaw(){
|
||||
@@ -1214,7 +1259,7 @@ let dlQueue = [];
|
||||
// fichier. Le fichier a pu atterrir hors du dossier loki : l'option porte alors
|
||||
// le chemin complet, pas le simple nom.
|
||||
async function selectInstalled(fname){
|
||||
await Promise.all([populateModelPicker(), populateMmproj(), populateDlDirs(), refreshInstalled()]);
|
||||
await Promise.all([populateModelPicker(), populateMmproj(), populateDraft(), populateDlDirs(), refreshInstalled()]);
|
||||
const pick = (id, cb)=>{
|
||||
const s = document.getElementById(id);
|
||||
if(!s) return false;
|
||||
@@ -1222,6 +1267,7 @@ async function selectInstalled(fname){
|
||||
if(!o) return false;
|
||||
s.value = o.value; cb(); return true;
|
||||
};
|
||||
if(/^mtp-/i.test(fname)) return pick('m-draft', onPickDraft);
|
||||
return isMmprojName(fname) ? pick('m-mmproj', onPickMmproj) : pick('m-model', onPickModel);
|
||||
}
|
||||
async function startDownload(){
|
||||
@@ -1514,11 +1560,27 @@ async function hfPickRepo(repo){
|
||||
}
|
||||
o.appendChild(note);
|
||||
|
||||
// Tête MTP publiée à part (mtp-*.gguf) : même règle que le projecteur, elle
|
||||
// ne vaut que pour SON modèle. Décochée : la spéculation reste un choix.
|
||||
let dfChk = null;
|
||||
const df = (r.drafts||[])[0] && hfBestProjector(r.drafts);
|
||||
if(df){
|
||||
const dn = document.createElement('label');
|
||||
dn.className = 'hf-mm';
|
||||
dfChk = document.createElement('input');
|
||||
dfChk.type = 'checkbox';
|
||||
const t = document.createElement('span');
|
||||
t.textContent = ' installer aussi la tête MTP (décodage plus rapide, ~1-2 Go de VRAM) — '+df.name+' ('+fmtSize(df.size)+')'
|
||||
+ (installed(df.name) ? ' — déjà sur le disque' : '');
|
||||
dn.append(dfChk, t);
|
||||
o.appendChild(dn);
|
||||
}
|
||||
|
||||
const list = document.createElement('div'); list.className = 'hf-list';
|
||||
for(const m of r.models){
|
||||
const row = document.createElement('div');
|
||||
row.className = 'hf-row';
|
||||
row.onclick = ()=>hfInstall(m, mm && mmChk && mmChk.checked ? mm : null);
|
||||
row.onclick = ()=>hfInstall(m, mm && mmChk && mmChk.checked ? mm : null, df && dfChk && dfChk.checked ? df : null);
|
||||
const n = document.createElement('span'); n.className = 'hf-name';
|
||||
n.textContent = m.quant || m.name;
|
||||
const meta = document.createElement('span'); meta.className = 'hf-meta';
|
||||
@@ -1565,14 +1627,14 @@ function hfBestProjector(list){
|
||||
|
||||
// Installe : le modèle d'abord, le projecteur ensuite (la file s'en charge). Le
|
||||
// champ Vision du preset ne se remplit correctement que dans cet ordre.
|
||||
async function hfInstall(model, projector){
|
||||
async function hfInstall(model, projector, draft){
|
||||
// Le fichier est là : rien à télécharger, donc rien à confirmer — la sonde le
|
||||
// constate côté serveur et startDownloadURL le sélectionne.
|
||||
const here = await modelIsOnDisk(model.name);
|
||||
if(!here && model.verdict === 'trop' && !await askConfirm(
|
||||
(model.why||'Ce modèle dépasse la mémoire disponible.')+'\n\nLe téléchargement fonctionnera, mais le moteur risque de ne pas le charger.',
|
||||
{title:'Installer quand même ?', okText:'Installer', danger:true})) return;
|
||||
dlQueue = projector ? [projector.url] : [];
|
||||
dlQueue = [projector, draft].filter(Boolean).map(x => x.url);
|
||||
document.getElementById('m-hf-url').value = model.url;
|
||||
await startDownloadURL(model.url);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user