loading more

This commit is contained in:
Dobromir Popov
2026-07-15 12:54:51 +02:00
parent 22f28bd69a
commit 2852b1f80b
4 changed files with 41 additions and 11 deletions

View File

@@ -332,6 +332,7 @@
<div id="model-placement-dialog-title"></div>
<label for="model-placement-node">Node</label>
<select id="model-placement-node"></select>
<label id="model-placement-replace" style="display:none"><input type="checkbox" id="model-placement-replace-confirm"> Unload the currently loaded model before loading this one</label>
<div class="placement-dialog-actions"><button value="cancel">Cancel</button><button value="confirm">Confirm</button></div>
</form>
</dialog>
@@ -1812,8 +1813,8 @@ async function requestSelectedModelLoad() {
$("chat-status").textContent = `load queued on ${short(assignment.node_id || "node")} for layers ${assignment.shard_start}-${assignment.shard_end}`;
}
async function requestAdminModelLoad(model, nodeId) {
const result = await apiCall("/v1/models/load", "POST", { model, node_id: nodeId, force: true });
async function requestAdminModelLoad(model, nodeId, replacing) {
const result = await apiCall("/v1/models/load", "POST", { model, node_id: nodeId, force: replacing });
if (!result.ok) return showAdminModelPlacementStatus(result.data.error || "model load request failed", true);
const assignment = result.data.assignment || {};
showAdminModelPlacementStatus(`Load queued on ${short(assignment.node_id || "node")} for ${model}.`);
@@ -1887,14 +1888,31 @@ $("admin-model-placement").addEventListener("click", event => {
function chooseModelPlacementNode(action, model) {
const dialog = $("model-placement-dialog");
const select = $("model-placement-node");
const aliases = new Set([model].filter(Boolean));
const nodes = (lastNetworkMap?.nodes || []).filter(node => action === "load" || aliases.has(node.model) || aliases.has(node.hf_repo));
const targetAlias = modelAliasKey(model);
const nodes = (lastNetworkMap?.nodes || []).filter(node => action === "load" ||
modelAliasKey(node.model) === targetAlias || modelAliasKey(node.hf_repo) === targetAlias);
if (!nodes.length) return showAdminModelPlacementStatus(`No node can ${action} ${model}.`, true);
$("model-placement-dialog-title").textContent = `${action === "load" ? "Load" : "Release"} ${model} on a node`;
select.innerHTML = nodes.map(node => `<option value="${esc(node.node_id)}">${esc(short(node.friendly_name || node.node_id, 20))}${esc(node.hf_repo || node.model || "unassigned")}</option>`).join("");
const replace = $("model-placement-replace");
const replaceConfirm = $("model-placement-replace-confirm");
const selectedNode = () => nodes.find(node => node.node_id === select.value);
const updateReplacementWarning = () => {
const node = selectedNode();
const occupied = action === "load" && node && node.shard_start != null && node.shard_end != null &&
modelAliasKey(node.hf_repo || node.model) !== targetAlias;
replace.style.display = occupied ? "" : "none";
replaceConfirm.checked = false;
};
select.onchange = updateReplacementWarning;
updateReplacementWarning();
dialog.onclose = () => {
if (dialog.returnValue !== "confirm") return;
if (action === "load") void requestAdminModelLoad(model, select.value);
const replacing = replace.style.display !== "none";
if (replacing && !replaceConfirm.checked) {
return showAdminModelPlacementStatus("Confirm unloading the existing model before replacing it.", true);
}
if (action === "load") void requestAdminModelLoad(model, select.value, replacing);
else void releaseAdminModel(model, select.value);
};
dialog.showModal();

View File

@@ -1535,7 +1535,9 @@ def _request_model_load_locked(
return None
def _force_model_load_locked(server: "_TrackerHTTPServer", model_key: str) -> dict | None:
def _force_model_load_locked(
server: "_TrackerHTTPServer", model_key: str, node_id: str | None = None,
) -> dict | None:
"""Replace the fastest ready assignment after an explicit admin eviction."""
resolved_name, preset = _resolve_model_preset(server.model_presets, model_key)
if preset is None or not preset.get("hf_repo"):
@@ -1544,8 +1546,11 @@ def _force_model_load_locked(server: "_TrackerHTTPServer", model_key: str) -> di
# An explicit admin eviction is permitted to recover a stuck/loading node
# and to use the preset default precision. It must only avoid a node that
# already has another assignment in flight.
candidates = [node for node in server.registry.values()
if node.pending_new_assignment is None]
candidates = [
node for node in server.registry.values()
if node.pending_new_assignment is None
and (node_id is None or node.node_id == node_id)
]
if not candidates:
return None
node = max(candidates, key=lambda item: item.benchmark_tokens_per_sec)
@@ -4917,8 +4922,8 @@ class _TrackerHandler(http.server.BaseHTTPRequestHandler):
with server.lock:
self._purge_expired_nodes()
assignment = _request_model_load_locked(server, model, node_id)
if assignment is None and node_id is None and body.get("force") is True:
assignment = _force_model_load_locked(server, model)
if assignment is None and body.get("force") is True:
assignment = _force_model_load_locked(server, model, node_id)
if assignment is None:
self._send_json(409, {"error": "no ready joined node has an available model slot and sufficient capacity"})
return