@@ -195,3 +195,9 @@ Eine Modellfamilie oder ein vollständiger Hugging-Face-Repository-Link sucht un
### Offizielle llama.cpp-Versionen
### Offizielle llama.cpp-Versionen
Die Update-Abfrage zeigt das aktuelle reguläre Release (`releases/latest`) zuerst und ergänzt die jüngsten Nightlies. Reguläre Tags `vX.Y.Z`, Nightly-Tags `bNNNN` und vollständige Commit-IDs sind zulässig. Reguläre Releases werden mit `LLAMA_BUILD_IS_DEV=OFF`, Nightlies mit `ON` gebaut. Ein reguläres Release ist keine automatische Kompatibilitätszusage für Decks Fit-/MTP-Adapter; Standardwechsel bleiben explizit. Versionsschema: https://github.com/ggml-org/ggml/discussions/1579
Die Update-Abfrage zeigt das aktuelle reguläre Release (`releases/latest`) zuerst und ergänzt die jüngsten Nightlies. Reguläre Tags `vX.Y.Z`, Nightly-Tags `bNNNN` und vollständige Commit-IDs sind zulässig. Reguläre Releases werden mit `LLAMA_BUILD_IS_DEV=OFF`, Nightlies mit `ON` gebaut. Ein reguläres Release ist keine automatische Kompatibilitätszusage für Decks Fit-/MTP-Adapter; Standardwechsel bleiben explizit. Versionsschema: https://github.com/ggml-org/ggml/discussions/1579
### GPU-Reserve pro LLM-Profil
Unter GPU-Auswahl: Automatisch (maximaler Wert aus 512 MiB und 2,5 Prozent je GPU), Manuell (0–32768 MiB je ausgewählter GPU) oder Keine zusätzliche Reserve. Manuelle Werte werden per GPU-UUID gespeichert. Alte Profile verwenden weiter Automatisch. Die Reserve beeinflusst Fit-Ziel und Speicherentscheidung; Modell/KV/MTP sowie die konservative Projektor-Schätzung (Dateigröße plus 2 GiB Arbeitsbedarf) bleiben enthalten. Host-RAM-Grenzen bleiben bestehen. Ohne Reserve ist ein erfolgreicher Modellstart nicht garantiert.
Für einen Versuch ohne CPU-Offloading zusätzlich „Vollständig auf GPU · sonst Start abbrechen“ wählen. Dann gibt es bei negativer Prognose keinen automatischen Layer-Rückfall. „Automatisch“ unter GPU-Ausführung erlaubt ihn weiterhin ausdrücklich. Der Testchat zeigt Layerlimit und Reserve je GPU. Gespeicherte Profile werden durch dieses Update nicht umgestellt.
el('profile-list').innerHTML=rows.map(p=>`<article class="card"><span class="pill">GESPEICHERT AUF ATHENA</span><h3>${e(p.name)}</h3><p class="hub-filename">${e(p.model?.file||'Modelldatei nicht verfügbar')}</p><p>${Object.entries(p.parameters).map(([k,v])=>`${e(labels[k]||k)}: ${e(k==='gpu_devices'?(v.length?v.map((id,i)=>`CUDA${i}: ${gpus.find(g=>g.uuid===id)?.name||id}`).join(', '):'Automatisch'):Array.isArray(v)?(v.join(', ')||'Automatisch'):v)}`).join(' · ')}</p><details><summary>${p.runnable?'Laufzeit bereit · lädt bei Anfrage':'Was fehlt zur Ausführung?'}</summary>${p.blockers.map(t=>`<p>${e(t)}</p>`).join('')}</details><div class="card-actions"><label class="endpoint-choice"><input type="checkbox" data-publish="${p.id}" ${enabled.includes(p.id)?'checked':''}${!p.runnable&&!enabled.includes(p.id)?'disabled':''}> Am API-Endpunkt freigegeben</label><button class="secondary" data-edit="${p.id}">Bearbeiten</button><button class="secondary" data-copy="${p.id}">Duplizieren</button><button class="secondary" data-delete="${p.id}">Löschen</button>${kind==='image'?`<button class="secondary" data-components="${p.id}">Komponenten</button>`:''}</div></article>`).join('')||`<section class="empty"><h3>Noch keine Profile</h3><p>${models.length?'Wähle eine heruntergeladene Modelldatei und lege dein erstes Profil an.':'Lade zuerst eine Gewichtsdatei unter Entdecken herunter.'}</p></section>`;
el('profile-list').innerHTML=rows.map(p=>`<article class="card"><span class="pill">GESPEICHERT AUF ATHENA</span><h3>${e(p.name)}</h3><p class="hub-filename">${e(p.model?.file||'Modelldatei nicht verfügbar')}</p><p>${Object.entries(p.parameters).map(([k,v])=>`${e(labels[k]||k)}: ${e(k==='gpu_reserve_mode'?({auto:'Automatisch',manual:'Manuell',none:'Keine zusätzliche Reserve'}[v]||v):k==='gpu_reserve_mib'?(Object.entries(v).map(([id,n])=>`${gpus.find(g=>g.uuid===id)?.name||id}: ${n} MiB`).join(', ')||'gemäß Reservemodus'):k==='gpu_devices'?(v.length?v.map((id,i)=>`CUDA${i}: ${gpus.find(g=>g.uuid===id)?.name||id}`).join(', '):'Automatisch'):Array.isArray(v)?(v.join(', ')||'Automatisch'):v)}`).join(' · ')}</p><details><summary>${p.runnable?'Laufzeit bereit · lädt bei Anfrage':'Was fehlt zur Ausführung?'}</summary>${p.blockers.map(t=>`<p>${e(t)}</p>`).join('')}</details><div class="card-actions"><label class="endpoint-choice"><input type="checkbox" data-publish="${p.id}" ${enabled.includes(p.id)?'checked':''}${!p.runnable&&!enabled.includes(p.id)?'disabled':''}> Am API-Endpunkt freigegeben</label><button class="secondary" data-edit="${p.id}">Bearbeiten</button><button class="secondary" data-copy="${p.id}">Duplizieren</button><button class="secondary" data-delete="${p.id}">Löschen</button>${kind==='image'?`<button class="secondary" data-components="${p.id}">Komponenten</button>`:''}</div></article>`).join('')||`<section class="empty"><h3>Noch keine Profile</h3><p>${models.length?'Wähle eine heruntergeladene Modelldatei und lege dein erstes Profil an.':'Lade zuerst eine Gewichtsdatei unter Entdecken herunter.'}</p></section>`;
el('profile-list').insertAdjacentHTML('afterbegin',`<p>${kind==='image'?'Höchstens ein Bildprofil ist am API-Endpunkt freigegeben. Ein neuer Haken ersetzt die bisherige Auswahl.':'Mehrere Sprachmodellprofile können gleichzeitig am API-Endpunkt freigegeben sein.'} Freigegeben bedeutet nicht bereits in den GPU-Speicher geladen.</p>`);
el('profile-list').insertAdjacentHTML('afterbegin',`<p>${kind==='image'?'Höchstens ein Bildprofil ist am API-Endpunkt freigegeben. Ein neuer Haken ersetzt die bisherige Auswahl.':'Mehrere Sprachmodellprofile können gleichzeitig am API-Endpunkt freigegeben sein.'} Freigegeben bedeutet nicht bereits in den GPU-Speicher geladen.</p>`);
root.querySelectorAll('[data-publish]').forEach(b=>b.onchange=async()=>{constchecked=b.checked;root.querySelectorAll('[data-publish]').forEach(x=>x.disabled=true);try{awaitapi('endpoint/profile',{id:b.dataset.publish,enabled:checked});awaitload();message('Endpunkt-Freigabe gespeichert. Geladen wird erst bei einer API-Anfrage.');}catch(error){awaitload();message(error.message);}});
root.querySelectorAll('[data-publish]').forEach(b=>b.onchange=async()=>{constchecked=b.checked;root.querySelectorAll('[data-publish]').forEach(x=>x.disabled=true);try{awaitapi('endpoint/profile',{id:b.dataset.publish,enabled:checked});awaitload();message('Endpunkt-Freigabe gespeichert. Geladen wird erst bei einer API-Anfrage.');}catch(error){awaitload();message(error.message);}});
root.querySelectorAll('[data-delete]').forEach(b=>b.onclick=async()=>{constp=rows.find(p=>p.id===b.dataset.delete);if(!confirm(`Profil „${p.name}“ löschen? Modelldateien und Komponenten bleiben in der Bibliothek.`))return;b.disabled=true;try{awaitapi('profiles/delete',{id:p.id,revision:p.revision});panelSequence++;el('profile-editor').replaceChildren();awaitload();message('Profil gelöscht. Modelldateien und Komponenten bleiben erhalten.');}catch(error){message(error.message);b.disabled=false;}});
root.querySelectorAll('[data-delete]').forEach(b=>b.onclick=async()=>{constp=rows.find(p=>p.id===b.dataset.delete);if(!confirm(`Profil „${p.name}“ löschen? Modelldateien und Komponenten bleiben in der Bibliothek.`))return;b.disabled=true;try{awaitapi('profiles/delete',{id:p.id,revision:p.revision});panelSequence++;el('profile-editor').replaceChildren();awaitload();message('Profil gelöscht. Modelldateien und Komponenten bleiben erhalten.');}catch(error){message(error.message);b.disabled=false;}});
@@ -30,7 +30,7 @@ window.ProfilesUI=(()=>{
functiongpuEditor(params){
functiongpuEditor(params){
constselected=params.gpu_devices||[],available=[...gpus];for(constuuidofselected)if(!available.some(g=>g.uuid===uuid))available.push({uuid,name:'Derzeit nicht verfügbar'});
constselected=params.gpu_devices||[],available=[...gpus];for(constuuidofselected)if(!available.some(g=>g.uuid===uuid))available.push({uuid,name:'Derzeit nicht verfügbar'});
return`<h3>GPU-Auswahl und Verteilung</h3><div class="form-grid"><label>GPU-Ausführung<select name="gpu_offload"><option value="auto" ${(params.gpu_offload||'auto')==='auto'?'selected':''}>Automatisch · CPU-Auslagerung bei Platzmangel</option><option value="full" ${params.gpu_offload==='full'?'selected':''}>Vollständig auf GPU · sonst Start abbrechen</option></select></label><label>Erste GPU · CUDA0<select name="gpu_first">${options(selected[0],'Automatische Auswahl')}</select></label><label>Zweite GPU · CUDA1<select name="gpu_second">${options(selected[1],'Keine zweite GPU')}</select></label><label>GPU-Split<select name="split_mode">${[['none','Keine Aufteilung'],['layer','Layer-Split'],['row','Row-Split']].map(([v,t])=>`<option value="${v}" ${(params.split_mode||'none')===v?'selected':''}>${t}</option>`).join('')}</select></label><label>GPU-Verteilung (erste, zweite GPU)<input name="tensor_split" placeholder="z. B. 85,15" value="${e((params.tensor_split||[]).join(','))}"></label></div><p class="small">Die Reihenfolge legt die logischen CUDA-Geräte des Profils fest. Für Medium: RTX 5080 zuerst, RTX 3060 danach, Layer-Split und 85,15. Leere Verteilung bedeutet automatisch. Dies ist keine Zusage, dass Modell und Kontext in den verfügbaren Speicher passen.</p>`;
return`<h3>GPU-Auswahl und Verteilung</h3><div class="form-grid"><label>GPU-Ausführung<select name="gpu_offload"><option value="auto" ${(params.gpu_offload||'auto')==='auto'?'selected':''}>Automatisch · CPU-Auslagerung bei Platzmangel</option><option value="full" ${params.gpu_offload==='full'?'selected':''}>Vollständig auf GPU · sonst Start abbrechen</option></select></label><label>Zusätzliche GPU-Reserve<select name="gpu_reserve_mode">${[['auto','Automatisch · mindestens 512 MiB je GPU'],['manual','Manuell je GPU'],['none','Keine zusätzliche Reserve']].map(([v,t])=>`<option value="${v}" ${(params.gpu_reserve_mode||'auto')===v?'selected':''}>${t}</option>`).join('')}</select></label><label>Reserve erste GPU (MiB)<input name="reserve_first" type="number" min="0" max="32768" step="1" value="${params.gpu_reserve_mib?.[selected[0]]??512}"></label><label>Reserve zweite GPU (MiB)<input name="reserve_second" type="number" min="0" max="32768" step="1" value="${params.gpu_reserve_mib?.[selected[1]]??512}"></label><label>Erste GPU · CUDA0<select name="gpu_first">${options(selected[0],'Automatische Auswahl')}</select></label><label>Zweite GPU · CUDA1<select name="gpu_second">${options(selected[1],'Keine zweite GPU')}</select></label><label>GPU-Split<select name="split_mode">${[['none','Keine Aufteilung'],['layer','Layer-Split'],['row','Row-Split']].map(([v,t])=>`<option value="${v}" ${(params.split_mode||'none')===v?'selected':''}>${t}</option>`).join('')}</select></label><label>GPU-Verteilung (erste, zweite GPU)<input name="tensor_split" placeholder="z. B. 85,15" value="${e((params.tensor_split||[]).join(','))}"></label></div><p class="small">Manuelle Reserven gelten für die jeweils ausgewählte GPU. Ohne Reserve entfällt nur der zusätzliche GPU-Puffer; Modell, KV-Cache und die konservative Projektor-Prognose bleiben berücksichtigt. OOM bleibt möglich. Für einen Abbruch statt CPU-Auslagerung „Vollständig auf GPU“ wählen. Die Reihenfolge legt die logischen CUDA-Geräte des Profils fest. Für Medium: RTX 5080 zuerst, RTX 3060 danach, Layer-Split und 85,15. Leere Verteilung bedeutet automatisch. Dies ist keine Zusage, dass Modell und Kontext in den verfügbaren Speicher passen.</p>`;
el('profile-editor').innerHTML=`<section class="card editor"><h2>${data.id?'Profil bearbeiten':'Profil anlegen'}</h2><form id="profile-form"><div class="form-grid"><label>API-Profilname<input name="name" required minlength="2" maxlength="64" pattern="[a-zA-Z0-9][a-zA-Z0-9_-]{1,63}" placeholder="z. B. qwen-image-test" value="${e(data.name)}"></label><label>Heruntergeladene Modelldatei<select name="model_id">${models.map(m=>`<option value="${m.id}" ${m.id===data.model_id?'selected':''}>${e(m.file)} · ${(m.size/1024**3).toFixed(2)} GiB</option>`).join('')}</select></label>${Object.entries(schema).map(([k,[min,max,defaultValue]])=>`<label>${e(labels[k])}<input name="${k}" type="number" required min="${min}" max="${max}" step="${['guidance','speed','temperature','top_p','mtp_min_p'].includes(k)?'any':['width','height'].includes(k)?64:1}" value="${e(data.parameters[k]??defaultValue)}"></label>`).join('')}</div>${kind==='chat'?gpuEditor(data.parameters)+`<h3>Vision-Projektor</h3><label>Projektor<select name="vision_projector"><option value="">Kein Projektor zugeordnet · Vision aus</option>${projectors.map(x=>`<option value="${e(x.id)}" ${data.parameters.vision_projector===x.id?'selected':''}>${e(x.repo)} / ${e(x.file)}</option>`).join('')}</select></label><section id="projector-discovery" class="card"></section><label>Projektor ausführen auf<select name="vision_device"><option value="cpu">CPU</option>${gpus.map(g=>`<option value="${e(g.uuid)}" ${data.parameters.vision_device===g.uuid?'selected':''}>${e(g.name)}</option>`).join('')}</select></label><p>Passende mmproj.gguf unter Entdecken herunterladen – auch aus einem anderen Repository als das quantisierte Modell. Modell und Projektor müssen zusammenpassen; der Dateiname allein garantiert keine Kompatibilität. GPU-Ziel muss oben ausgewählt sein. GPU-Projektoren erhalten eine zusätzliche konservative Speicherreserve.</p><label><input name="mtp" type="checkbox" ${data.parameters.mtp?'checked':''}> MTP aktivieren (eingebautes Draft-Modul)</label><p class="small">Nur für GGUF-Modelle mit MTP-Gewichten und kompatible Builds. Zusätzlicher Speicherbedarf; Geschwindigkeit hängt vom Modell ab. Referenz Medium: 2 Draft-Token, Mindestwahrscheinlichkeit 0,05, Draft-KV f16.</p>`:''}<p class="note">Speichern startet kein Modell. Die Ausführung benötigt eine passende Laufzeit und gegebenenfalls zusätzliche Modelldateien. ${kind==='chat'?'Das Kontextbudget gilt insgesamt für alle Slots.':''}</p><button>Profil auf Athena speichern</button><button class="secondary" type="button" id="profile-close">Schließen</button><p id="profile-error" role="alert"></p></form></section>`;
el('profile-editor').innerHTML=`<section class="card editor"><h2>${data.id?'Profil bearbeiten':'Profil anlegen'}</h2><form id="profile-form"><div class="form-grid"><label>API-Profilname<input name="name" required minlength="2" maxlength="64" pattern="[a-zA-Z0-9][a-zA-Z0-9_-]{1,63}" placeholder="z. B. qwen-image-test" value="${e(data.name)}"></label><label>Heruntergeladene Modelldatei<select name="model_id">${models.map(m=>`<option value="${m.id}" ${m.id===data.model_id?'selected':''}>${e(m.file)} · ${(m.size/1024**3).toFixed(2)} GiB</option>`).join('')}</select></label>${Object.entries(schema).map(([k,[min,max,defaultValue]])=>`<label>${e(labels[k])}<input name="${k}" type="number" required min="${min}" max="${max}" step="${['guidance','speed','temperature','top_p','mtp_min_p'].includes(k)?'any':['width','height'].includes(k)?64:1}" value="${e(data.parameters[k]??defaultValue)}"></label>`).join('')}</div>${kind==='chat'?gpuEditor(data.parameters)+`<h3>Vision-Projektor</h3><label>Projektor<select name="vision_projector"><option value="">Kein Projektor zugeordnet · Vision aus</option>${projectors.map(x=>`<option value="${e(x.id)}" ${data.parameters.vision_projector===x.id?'selected':''}>${e(x.repo)} / ${e(x.file)}</option>`).join('')}</select></label><section id="projector-discovery" class="card"></section><label>Projektor ausführen auf<select name="vision_device"><option value="cpu">CPU</option>${gpus.map(g=>`<option value="${e(g.uuid)}" ${data.parameters.vision_device===g.uuid?'selected':''}>${e(g.name)}</option>`).join('')}</select></label><p>Passende mmproj.gguf unter Entdecken herunterladen – auch aus einem anderen Repository als das quantisierte Modell. Modell und Projektor müssen zusammenpassen; der Dateiname allein garantiert keine Kompatibilität. GPU-Ziel muss oben ausgewählt sein. GPU-Projektoren erhalten eine zusätzliche konservative Speicherreserve.</p><label><input name="mtp" type="checkbox" ${data.parameters.mtp?'checked':''}> MTP aktivieren (eingebautes Draft-Modul)</label><p class="small">Nur für GGUF-Modelle mit MTP-Gewichten und kompatible Builds. Zusätzlicher Speicherbedarf; Geschwindigkeit hängt vom Modell ab. Referenz Medium: 2 Draft-Token, Mindestwahrscheinlichkeit 0,05, Draft-KV f16.</p>`:''}<p class="note">Speichern startet kein Modell. Die Ausführung benötigt eine passende Laufzeit und gegebenenfalls zusätzliche Modelldateien. ${kind==='chat'?'Das Kontextbudget gilt insgesamt für alle Slots.':''}</p><button>Profil auf Athena speichern</button><button class="secondary" type="button" id="profile-close">Schließen</button><p id="profile-error" role="alert"></p></form></section>`;
if(kind==='chat'){constthreads=el('profile-form').querySelector('[name=threads]').closest('label');constadvanced=document.createElement('details');advanced.innerHTML='<summary>Erweitert: CPU-Threads</summary><p>CPU-Threads steuern CPU-Arbeit und Offloading, nicht die Anzahl der CUDA-Rechenkerne.</p>';advanced.append(threads);el('profile-form').insertBefore(advanced,el('profile-form').querySelector('.note'));}
if(kind==='chat'){constthreads=el('profile-form').querySelector('[name=threads]').closest('label');constadvanced=document.createElement('details');advanced.innerHTML='<summary>Erweitert: CPU-Threads</summary><p>CPU-Threads steuern CPU-Arbeit und Offloading, nicht die Anzahl der CUDA-Rechenkerne.</p>';advanced.append(threads);el('profile-form').insertBefore(advanced,el('profile-form').querySelector('.note'));}
try{constparameters=Object.fromEntries(Object.keys(schema).map(k=>[k,Number(values.get(k))]));if(kind==='chat'){parameters.vision_projector=values.get('vision_projector')||null;parameters.vision_device=values.get('vision_device');parameters.mtp=values.has('mtp');parameters.gpu_offload=values.get('gpu_offload');if(values.get('gpu_second')&&!values.get('gpu_first'))throwError('Bitte zuerst die erste GPU auswählen.');if((data.parameters.gpu_devices||[]).length>2)throwError('Dieses Profil enthält mehr als zwei GPUs; der Editor unterstützt derzeit zwei.');parameters.gpu_devices=[values.get('gpu_first'),values.get('gpu_second')].filter(Boolean);parameters.split_mode=values.get('split_mode');constraw=String(values.get('tensor_split')).trim();parameters.tensor_split=raw?raw.split(',').map(x=>x.trim()?Number(x):NaN):[];}awaitapi('profiles/save',{id:data.id,revision:data.revision,name:values.get('name'),kind,model_id:values.get('model_id'),parameters});if(!root.isConnected)return;el('profile-editor').replaceChildren();awaitload();message('Profil auf Athena gespeichert. Kein Modell gestartet.');}
try{constparameters=Object.fromEntries(Object.keys(schema).map(k=>[k,Number(values.get(k))]));if(kind==='chat'){parameters.vision_projector=values.get('vision_projector')||null;parameters.vision_device=values.get('vision_device');parameters.mtp=values.has('mtp');parameters.gpu_offload=values.get('gpu_offload');if(values.get('gpu_second')&&!values.get('gpu_first'))throwError('Bitte zuerst die erste GPU auswählen.');if((data.parameters.gpu_devices||[]).length>2)throwError('Dieses Profil enthält mehr als zwei GPUs; der Editor unterstützt derzeit zwei.');parameters.gpu_devices=[values.get('gpu_first'),values.get('gpu_second')].filter(Boolean);parameters.gpu_reserve_mode=values.get('gpu_reserve_mode');parameters.gpu_reserve_mib=parameters.gpu_reserve_mode==='manual'?Object.fromEntries([['gpu_first','reserve_first'],['gpu_second','reserve_second']].filter(([g])=>values.get(g)).map(([g,r])=>[values.get(g),Number(values.get(r))])):{};parameters.split_mode=values.get('split_mode');constraw=String(values.get('tensor_split')).trim();parameters.tensor_split=raw?raw.split(',').map(x=>x.trim()?Number(x):NaN):[];}awaitapi('profiles/save',{id:data.id,revision:data.revision,name:values.get('name'),kind,model_id:values.get('model_id'),parameters});if(!root.isConnected)return;el('profile-editor').replaceChildren();awaitload();message('Profil auf Athena gespeichert. Kein Modell gestartet.');}
ifnotisinstance(reserve,dict)orany(knotinparams['gpu_devices']ortype(v)isnotintornot0<=v<=32768fork,vinreserve.items()):raiseValueError('GPU-Reserve muss je ausgewählter GPU zwischen 0 und 32768 MiB liegen.')
ifparams['gpu_reserve_mode']=='manual'and(notparams['gpu_devices']orset(reserve)!=set(params['gpu_devices'])):raiseValueError('Manuelle Reserve erfordert einen Wert für jede ausgewählte GPU.')
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.