Paste TaskSpec YAML and submit to API server.
Basic tasks require code_path; Advanced tasks use kind: advanced with a custom command.
""".strip()
tpl_ppo = ppo.replace("\n", "\\n").replace('"', '\\"')
tpl_grpo = grpo.replace("\n", "\\n").replace('"', '\\"')
tpl_sft = sft.replace("\n", "\\n").replace('"', '\\"')
tpl_adv = adv.replace("\n", "\\n").replace('"', '\\"')
tpl_eval = evaluation.replace("\n", "\\n").replace('"', '\\"')
tpl_merge = merge.replace("\n", "\\n").replace('"', '\\"')
script = (
"""
document.getElementById("nav-ray-dashboard").href = curOriginWithPort(8265);
const yamlEl = document.getElementById("yaml");
const msg = document.getElementById("msg");
const TPL_PPO = "__TPL_PPO__";
const TPL_GRPO = "__TPL_GRPO__";
const TPL_SFT = "__TPL_SFT__";
const TPL_ADV = "__TPL_ADV__";
const TPL_EVAL = "__TPL_EVAL__";
const TPL_MERGE = "__TPL_MERGE__";
function showMode(mode) {
const a = document.getElementById("mode-yaml");
const b = document.getElementById("mode-form");
const py = document.getElementById("panel-yaml");
const pf = document.getElementById("panel-form");
if (mode === "yaml") {
a.classList.add("active"); b.classList.remove("active");
py.style.display = ""; pf.style.display = "none";
} else {
b.classList.add("active"); a.classList.remove("active");
pf.style.display = ""; py.style.display = "none";
}
}
function yamlQuote(v) {
v = String(v || "");
if (!v) return '""';
if (/^[A-Za-z0-9_./:-]+$/.test(v) && !v.includes("#")) return v;
return '"' + v.replaceAll("\\\\","\\\\\\\\").replaceAll('"','\\\\"') + '"';
}
function updateFormVisibility() {
const tpl = document.getElementById("f-tpl").value;
const basic = document.getElementById("f-basic");
const adv = document.getElementById("f-adv");
const sftDev = document.getElementById("f-sft-device-wrap");
if (tpl === "advanced" || tpl === "merge") {
basic.style.display = "none";
adv.style.display = "";
sftDev.style.display = "none";
} else {
basic.style.display = "";
adv.style.display = "none";
sftDev.style.display = (tpl === "sft") ? "" : "none";
}
}
function updatePreview() {
const tpl = document.getElementById("f-tpl").value;
if (tpl === "advanced" || tpl === "merge") {
const nnodes = Number(document.getElementById("f-adv-nnodes").value || "1") || 1;
const gpn = Number(document.getElementById("f-adv-gpn").value || "0") || 0;
const cmd = (document.getElementById("f-command").value || "").trimEnd();
const out = `kind: advanced\\n` +
`nnodes: ${nnodes}\\n` +
`n_gpus_per_node: ${gpn}\\n\\n` +
`command: |\\n` +
cmd.split("\\n").map(line => " " + line).join("\\n") + "\\n";
return out;
}
const workload = tpl;
const codePath = document.getElementById("f-code-path").value || "";
const modelId = document.getElementById("f-model-id").value || "";
const trainFile = document.getElementById("f-train-file").value || "";
const valFile = document.getElementById("f-val-file").value || "";
const nnodes = Number(document.getElementById("f-nnodes").value || "2") || 2;
const gpn = Number(document.getElementById("f-gpn").value || "4") || 4;
const epochs = Number(document.getElementById("f-epochs").value || "1") || 1;
const steps = (document.getElementById("f-steps").value || "").trim();
const saveFreq = Number(document.getElementById("f-save-freq").value || "10") || 10;
const testFreq = (document.getElementById("f-test-freq").value || "").trim();
const trainerDevice = document.getElementById("f-trainer-device").value || "cpu";
let y = "";
y += `workload: ${workload}\\n`;
y += `code_path: ${yamlQuote(codePath)}\\n`;
y += `model_id: ${yamlQuote(modelId)}\\n`;
y += `train_file: ${yamlQuote(trainFile)}\\n`;
y += `val_file: ${yamlQuote(valFile)}\\n`;
y += `nnodes: ${nnodes}\\n`;
y += `n_gpus_per_node: ${gpn}\\n`;
y += `total_epochs: ${epochs}\\n`;
if (steps) y += `total_training_steps: ${Number(steps)}\\n`;
y += `save_freq: ${saveFreq}\\n`;
if (testFreq) y += `test_freq: ${Number(testFreq)}\\n`;
if (workload === "sft") y += `trainer_device: ${trainerDevice}\\n`;
return y;
}
function bindStepper(inputId, decId, incId, min) {
const inp = document.getElementById(inputId);
document.getElementById(decId).onclick = () => { inp.value = String(Math.max(min, (Number(inp.value || "0") || 0) - 1)); inp.dispatchEvent(new Event("input")); };
document.getElementById(incId).onclick = () => { inp.value = String((Number(inp.value || "0") || 0) + 1); inp.dispatchEvent(new Event("input")); };
}
document.getElementById("tpl-ppo").onclick = () => { yamlEl.value = TPL_PPO; };
document.getElementById("tpl-grpo").onclick = () => { yamlEl.value = TPL_GRPO; };
document.getElementById("tpl-sft").onclick = () => { yamlEl.value = TPL_SFT; };
document.getElementById("tpl-adv").onclick = () => { yamlEl.value = TPL_ADV; };
document.getElementById("tpl-eval").onclick = () => { yamlEl.value = TPL_EVAL; };
document.getElementById("tpl-merge").onclick = () => { yamlEl.value = TPL_MERGE; };
document.getElementById("f-code-path").value = "/private/common/code/verl/verl_repo";
document.getElementById("f-model-id").value = "Qwen/Qwen2.5-0.5B-Instruct";
document.getElementById("f-train-file").value = "/private/common/datasets/gsm8k/train.parquet";
document.getElementById("f-val-file").value = "/private/common/datasets/gsm8k/test.parquet";
document.getElementById("f-nnodes").value = "2";
document.getElementById("f-gpn").value = "4";
document.getElementById("f-epochs").value = "1";
document.getElementById("f-save-freq").value = "10";
document.getElementById("f-trainer-device").value = "cpu";
document.getElementById("f-adv-nnodes").value = "2";
document.getElementById("f-adv-gpn").value = "4";
document.getElementById("f-command").value = `PYTHONUNBUFFERED=1 \\\\\\npython3 -m verl.trainer.main_ppo \\\\\\n data.train_files=$HOME/common/datasets/gsm8k/train.parquet \\\\\\n data.val_files=$HOME/common/datasets/gsm8k/test.parquet \\\\\\n +ray_kwargs.ray_init.address=auto`;
bindStepper("f-nnodes", "f-nnodes-dec", "f-nnodes-inc", 1);
bindStepper("f-gpn", "f-gpn-dec", "f-gpn-inc", 0);
bindStepper("f-epochs", "f-epochs-dec", "f-epochs-inc", 1);
bindStepper("f-adv-nnodes", "f-adv-nnodes-dec", "f-adv-nnodes-inc", 1);
bindStepper("f-adv-gpn", "f-adv-gpn-dec", "f-adv-gpn-inc", 0);
document.getElementById("f-tpl").onchange = () => {
const tpl = document.getElementById("f-tpl").value;
if (tpl === "ppo") {
document.getElementById("f-train-file").value = "/private/common/datasets/gsm8k/train.parquet";
document.getElementById("f-val-file").value = "/private/common/datasets/gsm8k/test.parquet";
} else if (tpl === "grpo") {
document.getElementById("f-train-file").value = "/private/common/datasets/gsm8k/train.parquet";
document.getElementById("f-val-file").value = "/private/common/datasets/gsm8k/test.parquet";
} else if (tpl === "sft") {
document.getElementById("f-train-file").value = "/private/common/datasets/gsm8k_sft/train.parquet";
document.getElementById("f-val-file").value = "/private/common/datasets/gsm8k_sft/test.parquet";
} else if (tpl === "advanced") {
document.getElementById("f-adv-nnodes").value = "2";
document.getElementById("f-adv-gpn").value = "4";
document.getElementById("f-command").value = TPL_ADV.split("command: |")[1]?.trimStart() || document.getElementById("f-command").value;
} else if (tpl === "merge") {
document.getElementById("f-adv-nnodes").value = "1";
document.getElementById("f-adv-gpn").value = "0";
document.getElementById("f-command").value = TPL_MERGE.split("command: |")[1]?.trimStart() || document.getElementById("f-command").value;
}
updateFormVisibility();
updatePreview();
};
for (const id of ["f-code-path","f-model-id","f-train-file","f-val-file","f-nnodes","f-gpn","f-epochs","f-steps","f-save-freq","f-test-freq","f-trainer-device","f-adv-nnodes","f-adv-gpn","f-command"]) {
const el = document.getElementById(id);
if (el) el.oninput = () => updatePreview();
}
document.getElementById("mode-yaml").onclick = () => {
// When switching to YAML, sync from form if form is visible.
const formVisible = document.getElementById("panel-form").style.display !== "none";
if (formVisible) yamlEl.value = updatePreview();
showMode("yaml");
};
document.getElementById("mode-form").onclick = () => {
showMode("form");
};
showMode("yaml");
document.getElementById("submit").onclick = async () => {
msg.textContent = "Submitting...";
const body = (document.getElementById("panel-form").style.display !== "none") ? updatePreview() : yamlEl.value;
const resp = await apiFetch("/api/v2/tasks", { method: "POST", headers: {"Content-Type":"text/plain"}, body });
const text = await resp.text();
if (!resp.ok) { msg.textContent = "Error: " + resp.status + "\\n" + text; return; }
const obj = JSON.parse(text);
msg.textContent = "OK: " + fmtJson(obj);
if (obj.task_id) window.location.href = "/ui/tasks/" + obj.task_id;
};
""".strip()
.replace("__TPL_PPO__", tpl_ppo)
.replace("__TPL_GRPO__", tpl_grpo)
.replace("__TPL_SFT__", tpl_sft)
.replace("__TPL_ADV__", tpl_adv)
.replace("__TPL_EVAL__", tpl_eval)
.replace("__TPL_MERGE__", tpl_merge)
)
return HTMLResponse(content=page("New Task", "new", body, script))