Ship Assistent 0.13.0: real QLoRA pipeline and GGUF Ollama register.
Replace fake train loop with TRL SFTTrainer, HF column mapping with fiction preset, safetensors to GGUF conversion, and ollama create using ollama_base plus ADAPTER. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+116
-7
@@ -9168,6 +9168,7 @@ ${HELP_TEXT}`);
|
||||
hfResults: [],
|
||||
hfSelected: null,
|
||||
hfCheck: null,
|
||||
hfMapping: null,
|
||||
trainWs: null,
|
||||
polling: null,
|
||||
agentSettings: { enabled: true, auto_link_on_approve: true, heard_quota: 3 },
|
||||
@@ -9242,7 +9243,10 @@ ${HELP_TEXT}`);
|
||||
refreshSamples();
|
||||
loadAgentHeardSettings();
|
||||
}
|
||||
if (state.ttab === "train") syncModelfileModels();
|
||||
if (state.ttab === "train") {
|
||||
syncModelfileModels();
|
||||
syncQloraModels();
|
||||
}
|
||||
if (state.ttab === "models") refreshTrainModels();
|
||||
}
|
||||
async function refreshSamples() {
|
||||
@@ -9303,6 +9307,78 @@ ${HELP_TEXT}`);
|
||||
await SA2.request("AssistentUpsertTrainSample", patch);
|
||||
await refreshSamples();
|
||||
}
|
||||
function hfStringColumns(check) {
|
||||
const cols = check?.schema?.columns;
|
||||
if (Array.isArray(cols) && cols.length) return cols;
|
||||
const feats = check?.features;
|
||||
if (Array.isArray(feats)) {
|
||||
return feats.map((f) => f?.name).filter(Boolean);
|
||||
}
|
||||
if (feats && typeof feats === "object") return Object.keys(feats);
|
||||
return [];
|
||||
}
|
||||
function renderHfMappingUI(check) {
|
||||
const row = $("sa_hf_mapping_row");
|
||||
if (!row) return;
|
||||
const gate = check?.gate;
|
||||
const schemaKind = check?.schema?.kind;
|
||||
const needsMapping = gate === "mapping" || schemaKind === "fiction_tags_text";
|
||||
row.hidden = !needsMapping;
|
||||
if (!needsMapping) {
|
||||
state.hfMapping = null;
|
||||
return;
|
||||
}
|
||||
const cols = hfStringColumns(check);
|
||||
const userSel = $("sa_hf_user_col");
|
||||
const asstSel = $("sa_hf_asst_col");
|
||||
const presetSel = $("sa_hf_mapping_preset");
|
||||
if (userSel) {
|
||||
userSel.innerHTML = cols.map((c) => `<option value="${escapeHtml(c)}">${escapeHtml(c)}</option>`).join("");
|
||||
if (cols.includes("tags")) userSel.value = "tags";
|
||||
else if (cols.includes("title")) userSel.value = "title";
|
||||
}
|
||||
if (asstSel) {
|
||||
asstSel.innerHTML = cols.map((c) => `<option value="${escapeHtml(c)}">${escapeHtml(c)}</option>`).join("");
|
||||
if (cols.includes("text")) asstSel.value = "text";
|
||||
else if (cols.includes("output")) asstSel.value = "output";
|
||||
}
|
||||
if (schemaKind === "fiction_tags_text" && presetSel) {
|
||||
presetSel.value = "fiction_tags_text";
|
||||
state.hfMapping = { kind: "fiction_tags_text", preset: "fiction_tags_text" };
|
||||
}
|
||||
}
|
||||
function buildHfMappingPayload() {
|
||||
const preset = $("sa_hf_mapping_preset")?.value;
|
||||
if (preset === "fiction_tags_text") {
|
||||
return { kind: "fiction_tags_text", preset: "fiction_tags_text" };
|
||||
}
|
||||
const userCol = $("sa_hf_user_col")?.value;
|
||||
const asstCol = $("sa_hf_asst_col")?.value;
|
||||
if (userCol && asstCol) {
|
||||
return { kind: "custom", user_col: userCol, assistant_col: asstCol };
|
||||
}
|
||||
return state.hfMapping;
|
||||
}
|
||||
async function syncQloraModels() {
|
||||
try {
|
||||
const baseUrl = $("sa_base_url")?.value || localStorage.getItem("swarm_assistent_base_url") || "";
|
||||
const data = await SA2.request("AssistentListModels", { baseUrl });
|
||||
const models = data?.models || [];
|
||||
const sel = $("sa_qlora_ollama_base");
|
||||
if (!sel) return;
|
||||
const cur = sel.value;
|
||||
sel.innerHTML = '<option value="">\u2014</option>';
|
||||
for (const m of models) {
|
||||
const opt = document.createElement("option");
|
||||
opt.value = m;
|
||||
opt.textContent = m;
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
if (cur) sel.value = cur;
|
||||
else if ($("sa_model")?.value) sel.value = $("sa_model").value;
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
function renderHfList() {
|
||||
const root = $("sa_hf_list");
|
||||
if (!root) return;
|
||||
@@ -9353,6 +9429,7 @@ ${HELP_TEXT}`);
|
||||
}
|
||||
const importRow = $("sa_hf_import_row");
|
||||
if (importRow) importRow.hidden = data.gate === "rejected";
|
||||
renderHfMappingUI(data);
|
||||
} catch (e) {
|
||||
if (status) status.textContent = String(e.message || e);
|
||||
}
|
||||
@@ -9364,8 +9441,9 @@ ${HELP_TEXT}`);
|
||||
}
|
||||
const id = state.hfSelected || state.hfCheck.id;
|
||||
const limit = Number($("sa_hf_import_limit")?.value) || 200;
|
||||
const mapping = buildHfMappingPayload();
|
||||
try {
|
||||
const data = await SA2.request("AssistentImportHfDataset", { dataset: id, limit });
|
||||
const data = await SA2.request("AssistentImportHfDataset", { dataset: id, limit, mapping });
|
||||
setTrainStatus(`\u0418\u043C\u043F\u043E\u0440\u0442\u0438\u0440\u043E\u0432\u0430\u043D\u043E: ${data.imported}${data.runner_only ? " (runner-only)" : ""}`);
|
||||
await refreshSamples();
|
||||
} catch (e) {
|
||||
@@ -9435,8 +9513,9 @@ ${HELP_TEXT}`);
|
||||
async function pollTrainJob() {
|
||||
try {
|
||||
const data = await SA2.request("AssistentGetTrainJob", {});
|
||||
const prog = data?.job?.progress_json ? JSON.parse(data.job.progress_json) : null;
|
||||
const prog = data?.progress || (data?.job?.progress_json ? JSON.parse(data.job.progress_json) : null);
|
||||
const active = data?.training_active || data?.job?.status === "running";
|
||||
const status = data?.job?.status || prog?.status;
|
||||
setTrainingLock(active, prog?.status === "running" ? `\u0422\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0430 \xB7 ${prog?.percent ?? 0}%` : "\u0418\u0434\u0451\u0442 \u0442\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0430\u2026");
|
||||
const logEl = $("sa_train_log");
|
||||
const bar = $("sa_train_progress_fill");
|
||||
@@ -9450,6 +9529,24 @@ ${HELP_TEXT}`);
|
||||
clearInterval(state.polling);
|
||||
state.polling = null;
|
||||
$("sa_btn_qlora_cancel").hidden = true;
|
||||
setTrainingLock(false);
|
||||
if (status === "completed" || status === "completed_with_warnings") {
|
||||
const ollama = prog?.ollama;
|
||||
if (ollama?.success) {
|
||||
setTrainStatus(`\u0413\u043E\u0442\u043E\u0432\u043E: \u043C\u043E\u0434\u0435\u043B\u044C ${ollama.name} \u0432 Ollama`);
|
||||
SA2.app?.refreshModels?.();
|
||||
} else if (ollama?.skipped) {
|
||||
setTrainStatus(ollama.note || ollama.error || "\u0410\u0434\u0430\u043F\u0442\u0435\u0440 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D, Ollama \u2014 \u0432\u0440\u0443\u0447\u043D\u0443\u044E");
|
||||
} else if (ollama?.error) {
|
||||
setTrainStatus(`\u041E\u0431\u0443\u0447\u0435\u043D\u0438\u0435 OK, Ollama: ${ollama.error}`);
|
||||
} else if (status === "completed_with_warnings") {
|
||||
setTrainStatus("\u041E\u0431\u0443\u0447\u0435\u043D\u0438\u0435 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E \u0441 \u043F\u0440\u0435\u0434\u0443\u043F\u0440\u0435\u0436\u0434\u0435\u043D\u0438\u044F\u043C\u0438 \u2014 \u0441\u043C. \u043B\u043E\u0433");
|
||||
} else {
|
||||
setTrainStatus("QLoRA \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E");
|
||||
}
|
||||
} else if (status === "failed") {
|
||||
setTrainStatus(`\u041E\u0448\u0438\u0431\u043A\u0430 \u0442\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0438 (exit ${prog?.exit_code ?? "?"})`);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
}
|
||||
@@ -9457,18 +9554,23 @@ ${HELP_TEXT}`);
|
||||
async function startQlora() {
|
||||
setTrainStatus("\u0417\u0430\u043F\u0443\u0441\u043A\u2026");
|
||||
try {
|
||||
const hfDs = ($("sa_qlora_hf_dataset")?.value || "").trim();
|
||||
const mapping = hfDs ? buildHfMappingPayload() : void 0;
|
||||
await SA2.request("AssistentStartTrainJob", {
|
||||
base_url: $("sa_base_url")?.value,
|
||||
chat_model: $("sa_model")?.value,
|
||||
base_model: $("sa_qlora_base")?.value,
|
||||
ollama_base: $("sa_qlora_ollama_base")?.value,
|
||||
output_name: $("sa_qlora_name")?.value,
|
||||
rank: Number($("sa_qlora_rank")?.value) || 16,
|
||||
alpha: Number($("sa_qlora_alpha")?.value) || 32,
|
||||
lr: Number($("sa_qlora_lr")?.value) || 2e-4,
|
||||
epochs: Number($("sa_qlora_epochs")?.value) || 3,
|
||||
seq_len: Number($("sa_qlora_seq")?.value) || 2048,
|
||||
max_samples: Number($("sa_qlora_max_samples")?.value) || 0,
|
||||
four_bit: !!$("sa_qlora_4bit")?.checked,
|
||||
hf_dataset: ($("sa_qlora_hf_dataset")?.value || "").trim() || void 0
|
||||
hf_dataset: hfDs || void 0,
|
||||
hf_mapping: mapping
|
||||
});
|
||||
$("sa_btn_qlora_cancel").hidden = false;
|
||||
setTrainingLock(true, "\u0418\u0434\u0451\u0442 \u0442\u0440\u0435\u043D\u0438\u0440\u043E\u0432\u043A\u0430\u2026");
|
||||
@@ -9504,10 +9606,12 @@ ${HELP_TEXT}`);
|
||||
try {
|
||||
await SA2.request("AssistentSaveRunnerSettings", {
|
||||
python: $("sa_runner_python")?.value,
|
||||
kind: $("sa_runner_kind")?.value,
|
||||
kind: $("sa_runner_kind")?.value || "builtin",
|
||||
workdir: $("sa_runner_workdir")?.value,
|
||||
cmd: $("sa_runner_cmd")?.value,
|
||||
gguf_script: $("sa_runner_gguf_script")?.value
|
||||
gguf_script: $("sa_runner_gguf_script")?.value,
|
||||
gguf_base_path: $("sa_runner_gguf_base")?.value,
|
||||
gguf_cmd: $("sa_runner_gguf_cmd")?.value
|
||||
});
|
||||
setTrainStatus("\u0420\u0430\u043D\u043D\u0435\u0440 \u0441\u043E\u0445\u0440\u0430\u043D\u0451\u043D");
|
||||
} catch (e) {
|
||||
@@ -9519,10 +9623,12 @@ ${HELP_TEXT}`);
|
||||
const data = await SA2.request("AssistentGetRunnerSettings", {});
|
||||
const s = data?.settings || {};
|
||||
if ($("sa_runner_python") && s.python) $("sa_runner_python").value = s.python;
|
||||
if ($("sa_runner_kind") && s.kind) $("sa_runner_kind").value = s.kind;
|
||||
if ($("sa_runner_kind")) $("sa_runner_kind").value = s.kind || "builtin";
|
||||
if ($("sa_runner_workdir") && s.workdir) $("sa_runner_workdir").value = s.workdir;
|
||||
if ($("sa_runner_cmd") && s.cmd) $("sa_runner_cmd").value = s.cmd;
|
||||
if ($("sa_runner_gguf_script") && s.gguf_script) $("sa_runner_gguf_script").value = s.gguf_script;
|
||||
if ($("sa_runner_gguf_base") && s.gguf_base_path) $("sa_runner_gguf_base").value = s.gguf_base_path;
|
||||
if ($("sa_runner_gguf_cmd") && s.gguf_cmd) $("sa_runner_gguf_cmd").value = s.gguf_cmd;
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
@@ -9629,6 +9735,9 @@ ${HELP_TEXT}`);
|
||||
await checkHfLink();
|
||||
});
|
||||
$("sa_btn_hf_check")?.addEventListener("click", checkHfLink);
|
||||
$("sa_hf_mapping_preset")?.addEventListener("change", () => {
|
||||
state.hfMapping = buildHfMappingPayload();
|
||||
});
|
||||
$("sa_btn_hf_import")?.addEventListener("click", importHf);
|
||||
document.querySelectorAll('input[name="sa_train_mode"]').forEach((r) => {
|
||||
r.addEventListener("change", () => setTrainMode(r.value));
|
||||
|
||||
Reference in New Issue
Block a user