Spaces:
Running on Zero
Running on Zero
Guard batch launches with the visible saved plan and current run setup
Browse files
client.js
CHANGED
|
@@ -54,6 +54,21 @@ async function runTrackSource(priorContext=null){
|
|
| 54 |
}
|
| 55 |
throw Error('Choose or select a track before starting this run.');
|
| 56 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
function buttons() {
|
| 58 |
const settingsUnavailable=busy||bundleRestoring||bucketOperation;
|
| 59 |
const unavailable=settingsUnavailable||!!job;
|
|
@@ -72,7 +87,11 @@ function buttons() {
|
|
| 72 |
$('forgetLocal').disabled = busy || !localResult;
|
| 73 |
$('track').disabled=unavailable;
|
| 74 |
$('network').disabled=unavailable;
|
| 75 |
-
if($('batch_start'))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 76 |
if($('batch_stop'))$('batch_stop').disabled=!batchActive || batchStopRequested;
|
| 77 |
if($('run_export'))$('run_export').disabled=busy || !runReceipt || pairedAnalysisPending;
|
| 78 |
const report=pairedAnalysisPending?null:(cleanRunReceipt(runReceipt)||cleanRunReceipt(portableReceipt));
|
|
@@ -114,17 +133,17 @@ function syncRunModeUI(unavailable=busy||bundleRestoring||bucketOperation){
|
|
| 114 |
if($('run'))$('run').textContent=evaluation?'Evaluate policy (weights frozen)':'Run training';
|
| 115 |
if($('run_mode_help'))$('run_mode_help').textContent=evaluation
|
| 116 |
?'Frozen evaluation uses the selected policy unchanged in a fresh car. Mutation, decay, crossover, reproduction and winner selection are disabled.'
|
| 117 |
-
:'Training uses
|
| 118 |
}
|
| 119 |
if($('run_mode'))$('run_mode').addEventListener('change',()=>{
|
| 120 |
syncRunModeUI();buttons();
|
| 121 |
if(selectedWinner)say($('run_mode').value==='train'
|
| 122 |
-
?'Selected policy is staged as the starting network for training. The next Run click starts a training run with the
|
| 123 |
:'Selected policy is ready for frozen evaluation. Its weights stay unchanged; the next Run click starts it.');
|
| 124 |
});
|
| 125 |
function showFirstRunTab(tab,focusId){
|
| 126 |
-
document.querySelector('[data-tab="'+tab+'"]')?.click();
|
| 127 |
-
requestAnimationFrame(()=>$(focusId)?.focus());
|
| 128 |
}
|
| 129 |
if($('first_run_track'))$('first_run_track').onclick=()=>showFirstRunTab('drive','trackPresetDrive');
|
| 130 |
if($('first_run_settings'))$('first_run_settings').onclick=()=>showFirstRunTab('training','population');
|
|
@@ -436,6 +455,8 @@ async function encoded(file) {
|
|
| 436 |
}
|
| 437 |
const headers = () => ({'X-Job-Key':job.key});
|
| 438 |
const networkSource=()=>stagedNetwork || $('network').files[0];
|
|
|
|
|
|
|
| 439 |
async function refreshFreezeNetwork(file){
|
| 440 |
if(!file){window.setFreezeNetwork?.(null);return;}
|
| 441 |
try{const network=await validateNetworkBytes(new Uint8Array(await file.arrayBuffer()));if(networkSource()===file)window.setFreezeNetwork?.(network);}
|
|
@@ -1016,7 +1037,7 @@ $('run').onclick = async () => {
|
|
| 1016 |
$('runState').textContent=workLimit?'SETTINGS NEED ADJUSTMENT — no run started':'ERROR — no successful result confirmed';
|
| 1017 |
$('runState').style.color='#ffb5a2';
|
| 1018 |
const quotaRejected=isGpuQuotaError(error)||String(error?.message??'').startsWith('GPU allowance is exhausted for this request.');
|
| 1019 |
-
$('runSummary').textContent=workLimit?'No run was submitted. Change population, generations, or ticks on Training
|
| 1020 |
'Elapsed: '+((performance.now()-started)/1000).toFixed(1)+' s\n'+(quotaRejected
|
| 1021 |
? 'HF rejected this GPU request before a result was published.'
|
| 1022 |
: 'No downloadable result was received. A connection error does not prove the server stopped.');
|
|
@@ -1050,13 +1071,18 @@ $('erase').onclick=async()=>{
|
|
| 1050 |
if($('batch_start')){
|
| 1051 |
$('batch_stop').onclick=()=>{batchStopRequested=true;buttons();$('batch_status').textContent='Stop requested — the current run will finish; no next run will start.';};
|
| 1052 |
$('batch_start').onclick=async()=>{
|
| 1053 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1054 |
let plan,failedCandidateStep=null;const history=[];const started=Date.now();
|
| 1055 |
try{
|
| 1056 |
-
if(busy||job||!capabilitiesReady)throw Error('Finish the current job and connect first');
|
| 1057 |
plan=window.getBatchPlan();
|
| 1058 |
busy=true;buttons(); // Freeze both selectors before an asynchronous track fetch.
|
| 1059 |
-
const trackFile=await trackSource()
|
|
|
|
|
|
|
| 1060 |
await encoded(original); // Reject missing/oversize inputs before dispatch.
|
| 1061 |
const backend=$('backend').value,autosaveSnapshot=bucketToken&&$('bucket_autosave')?.checked?bucketSnapshot():null;
|
| 1062 |
busy=true;batchActive=true;batchStopRequested=false;batchReceipt=null;buttons();
|
|
@@ -1067,7 +1093,7 @@ if($('batch_start')){
|
|
| 1067 |
$('runState').style.color='#ffd073';
|
| 1068 |
showDetails(null);
|
| 1069 |
const result=await window.BatchTraining.execute(plan,original,async(source,settings,i)=>{
|
| 1070 |
-
$('batch_status').textContent='RUNNING · '+plan.name+' · step '+(i+1)+' / '+plan.runs.length+' · '+plan.runs[i].slot;
|
| 1071 |
const inputHash=await sha(await source.arrayBuffer());
|
| 1072 |
job=await submit({track,network:await encoded(source),settings,backend,automatic:true});
|
| 1073 |
const stepJob=job,report=stepJob.report,stepRunReceipt={version:1,state:'completed',build:report?.build??appBuild,backend,output_sha256:stepJob.sha256,report};
|
|
@@ -1095,7 +1121,7 @@ if($('batch_start')){
|
|
| 1095 |
$('batch_history').append(line);
|
| 1096 |
}});
|
| 1097 |
batchReceipt={version:1,build:appBuild,state:result.state,plan,track:trackLibrary?.info()??{source:'upload'},history,elapsed_seconds:(Date.now()-started)/1000};
|
| 1098 |
-
$('batch_status').textContent=result.state.toUpperCase()+' · '+history.length+' / '+plan.runs.length+' runs. '+(localResult?'Last received candidate is available on
|
| 1099 |
$('runState').textContent='BATCH '+result.state.toUpperCase()+' — '+history.length+' / '+plan.runs.length+' runs; server copies deleted';
|
| 1100 |
$('runState').style.color=result.state==='completed'?'#62dfb1':'#ffd073';
|
| 1101 |
$('runSummary').textContent='Backend: '+backend.toUpperCase()+'\nCompleted steps: '+history.length+'\nUse Save received result again for the last candidate. A completed batch does not establish improved driving.';
|
|
@@ -1166,6 +1192,7 @@ async function connect(){
|
|
| 1166 |
$('retry').onclick=()=>{if(!busy&&!job)connect();};
|
| 1167 |
connect();
|
| 1168 |
|
|
|
|
| 1169 |
if($('run_export'))$('run_export').onclick=()=>{
|
| 1170 |
if(!runReceipt)return;
|
| 1171 |
const cleaned=cleanRunReceipt(runReceipt);if(!cleaned)return;
|
|
@@ -1448,3 +1475,5 @@ if($('bundle_receipt_download'))$('bundle_receipt_download').onclick=()=>{
|
|
| 1448 |
const url=URL.createObjectURL(new Blob([JSON.stringify(portableReceipt,null,2)],{type:'application/json'}));
|
| 1449 |
const a=document.createElement('a');a.href=url;a.download='ald-run-receipt.json';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);
|
| 1450 |
};
|
|
|
|
|
|
|
|
|
| 54 |
}
|
| 55 |
throw Error('Choose or select a track before starting this run.');
|
| 56 |
}
|
| 57 |
+
function batchRunContext(){
|
| 58 |
+
const trackInfo=trackLibrary?.info()??{source:'upload'},trackFile=$('track').files[0],network=networkSource(),backend=$('backend').value;
|
| 59 |
+
const hasTrack=trackInfo.source==='builtin'||!!trackFile;
|
| 60 |
+
let reason='';
|
| 61 |
+
if(busy||bundleRestoring||bucketOperation)reason='Finish the current operation before starting a batch.';
|
| 62 |
+
else if(job)reason='Finish or delete the current result before starting a batch.';
|
| 63 |
+
else if(!capabilitiesReady)reason='Connect to the training service before running a batch.';
|
| 64 |
+
else if($('run_mode')?.value==='evaluate')reason='Under Action in Run & results, choose Train a population before running a batch.';
|
| 65 |
+
else if(!network)reason='Choose or create a starting network in Run & results.';
|
| 66 |
+
else if(!hasTrack)reason='Choose a track in Run & results.';
|
| 67 |
+
else if(backend==='gpu'&&!gpuAvailable)reason='GPU is unavailable. Choose CPU in Run & results.';
|
| 68 |
+
return {available:!reason,reason,
|
| 69 |
+
track:trackInfo.source==='builtin'?trackInfo.display_name:trackFile?.name,
|
| 70 |
+
network:network?.name||(network?'Selected network':null),backend:backend.toUpperCase()};
|
| 71 |
+
}
|
| 72 |
function buttons() {
|
| 73 |
const settingsUnavailable=busy||bundleRestoring||bucketOperation;
|
| 74 |
const unavailable=settingsUnavailable||!!job;
|
|
|
|
| 87 |
$('forgetLocal').disabled = busy || !localResult;
|
| 88 |
$('track').disabled=unavailable;
|
| 89 |
$('network').disabled=unavailable;
|
| 90 |
+
if($('batch_start')){
|
| 91 |
+
const context=batchRunContext();
|
| 92 |
+
window.setBatchRunContext?.(context);
|
| 93 |
+
$('batch_start').disabled=!context.available||!window.getBatchReadiness?.()?.ready;
|
| 94 |
+
}
|
| 95 |
if($('batch_stop'))$('batch_stop').disabled=!batchActive || batchStopRequested;
|
| 96 |
if($('run_export'))$('run_export').disabled=busy || !runReceipt || pairedAnalysisPending;
|
| 97 |
const report=pairedAnalysisPending?null:(cleanRunReceipt(runReceipt)||cleanRunReceipt(portableReceipt));
|
|
|
|
| 133 |
if($('run'))$('run').textContent=evaluation?'Evaluate policy (weights frozen)':'Run training';
|
| 134 |
if($('run_mode_help'))$('run_mode_help').textContent=evaluation
|
| 135 |
?'Frozen evaluation uses the selected policy unchanged in a fresh car. Mutation, decay, crossover, reproduction and winner selection are disabled.'
|
| 136 |
+
:'Training uses the current settings in Training & recipes and records winner policies and per-car measurements.';
|
| 137 |
}
|
| 138 |
if($('run_mode'))$('run_mode').addEventListener('change',()=>{
|
| 139 |
syncRunModeUI();buttons();
|
| 140 |
if(selectedWinner)say($('run_mode').value==='train'
|
| 141 |
+
?'Selected policy is staged as the starting network for training. The next Run click starts a training run with the current settings.'
|
| 142 |
:'Selected policy is ready for frozen evaluation. Its weights stay unchanged; the next Run click starts it.');
|
| 143 |
});
|
| 144 |
function showFirstRunTab(tab,focusId){
|
| 145 |
+
const section=document.querySelector('[data-tab="'+tab+'"]');section?.click();
|
| 146 |
+
requestAnimationFrame(()=>{const target=$(focusId);(target&&!target.matches(':disabled')&&!target.closest('[hidden]')?target:section)?.focus();});
|
| 147 |
}
|
| 148 |
if($('first_run_track'))$('first_run_track').onclick=()=>showFirstRunTab('drive','trackPresetDrive');
|
| 149 |
if($('first_run_settings'))$('first_run_settings').onclick=()=>showFirstRunTab('training','population');
|
|
|
|
| 455 |
}
|
| 456 |
const headers = () => ({'X-Job-Key':job.key});
|
| 457 |
const networkSource=()=>stagedNetwork || $('network').files[0];
|
| 458 |
+
window.addEventListener('ald:batch-draft-change',buttons);
|
| 459 |
+
document.addEventListener('track-library-change',buttons);
|
| 460 |
async function refreshFreezeNetwork(file){
|
| 461 |
if(!file){window.setFreezeNetwork?.(null);return;}
|
| 462 |
try{const network=await validateNetworkBytes(new Uint8Array(await file.arrayBuffer()));if(networkSource()===file)window.setFreezeNetwork?.(network);}
|
|
|
|
| 1037 |
$('runState').textContent=workLimit?'SETTINGS NEED ADJUSTMENT — no run started':'ERROR — no successful result confirmed';
|
| 1038 |
$('runState').style.color='#ffb5a2';
|
| 1039 |
const quotaRejected=isGpuQuotaError(error)||String(error?.message??'').startsWith('GPU allowance is exhausted for this request.');
|
| 1040 |
+
$('runSummary').textContent=workLimit?'No run was submitted. Change population, generations, or ticks on Training & recipes, then try again.':
|
| 1041 |
'Elapsed: '+((performance.now()-started)/1000).toFixed(1)+' s\n'+(quotaRejected
|
| 1042 |
? 'HF rejected this GPU request before a result was published.'
|
| 1043 |
: 'No downloadable result was received. A connection error does not prove the server stopped.');
|
|
|
|
| 1071 |
if($('batch_start')){
|
| 1072 |
$('batch_stop').onclick=()=>{batchStopRequested=true;buttons();$('batch_status').textContent='Stop requested — the current run will finish; no next run will start.';};
|
| 1073 |
$('batch_start').onclick=async()=>{
|
| 1074 |
+
const context=batchRunContext(),readiness=window.getBatchReadiness?.();
|
| 1075 |
+
if(!context.available||!readiness?.ready){
|
| 1076 |
+
$('batch_status').textContent=!readiness?.ready?(readiness?.message||'Save a valid batch before running it.'):context.reason;
|
| 1077 |
+
buttons();return;
|
| 1078 |
+
}
|
| 1079 |
let plan,failedCandidateStep=null;const history=[];const started=Date.now();
|
| 1080 |
try{
|
|
|
|
| 1081 |
plan=window.getBatchPlan();
|
| 1082 |
busy=true;buttons(); // Freeze both selectors before an asynchronous track fetch.
|
| 1083 |
+
const trackFile=await trackSource();
|
| 1084 |
+
if(!trackFile)throw Error('Choose a track in Run & results before running a batch.');
|
| 1085 |
+
const trackBytes=new Uint8Array(await trackFile.arrayBuffer()),track=await encoded(trackFile),trackInfo=trackLibrary?.info()??{source:'upload'},original=networkSource();
|
| 1086 |
await encoded(original); // Reject missing/oversize inputs before dispatch.
|
| 1087 |
const backend=$('backend').value,autosaveSnapshot=bucketToken&&$('bucket_autosave')?.checked?bucketSnapshot():null;
|
| 1088 |
busy=true;batchActive=true;batchStopRequested=false;batchReceipt=null;buttons();
|
|
|
|
| 1093 |
$('runState').style.color='#ffd073';
|
| 1094 |
showDetails(null);
|
| 1095 |
const result=await window.BatchTraining.execute(plan,original,async(source,settings,i)=>{
|
| 1096 |
+
$('batch_status').textContent='RUNNING · '+plan.name+' · step '+(i+1)+' / '+plan.runs.length+' · '+plan.runs[i].slot+' · '+plan.runs[i].name;
|
| 1097 |
const inputHash=await sha(await source.arrayBuffer());
|
| 1098 |
job=await submit({track,network:await encoded(source),settings,backend,automatic:true});
|
| 1099 |
const stepJob=job,report=stepJob.report,stepRunReceipt={version:1,state:'completed',build:report?.build??appBuild,backend,output_sha256:stepJob.sha256,report};
|
|
|
|
| 1121 |
$('batch_history').append(line);
|
| 1122 |
}});
|
| 1123 |
batchReceipt={version:1,build:appBuild,state:result.state,plan,track:trackLibrary?.info()??{source:'upload'},history,elapsed_seconds:(Date.now()-started)/1000};
|
| 1124 |
+
$('batch_status').textContent=result.state.toUpperCase()+' · '+history.length+' / '+plan.runs.length+' runs. '+(localResult?'Last received candidate is available on Run & results.':'No candidate received.');
|
| 1125 |
$('runState').textContent='BATCH '+result.state.toUpperCase()+' — '+history.length+' / '+plan.runs.length+' runs; server copies deleted';
|
| 1126 |
$('runState').style.color=result.state==='completed'?'#62dfb1':'#ffd073';
|
| 1127 |
$('runSummary').textContent='Backend: '+backend.toUpperCase()+'\nCompleted steps: '+history.length+'\nUse Save received result again for the last candidate. A completed batch does not establish improved driving.';
|
|
|
|
| 1192 |
$('retry').onclick=()=>{if(!busy&&!job)connect();};
|
| 1193 |
connect();
|
| 1194 |
|
| 1195 |
+
|
| 1196 |
if($('run_export'))$('run_export').onclick=()=>{
|
| 1197 |
if(!runReceipt)return;
|
| 1198 |
const cleaned=cleanRunReceipt(runReceipt);if(!cleaned)return;
|
|
|
|
| 1475 |
const url=URL.createObjectURL(new Blob([JSON.stringify(portableReceipt,null,2)],{type:'application/json'}));
|
| 1476 |
const a=document.createElement('a');a.href=url;a.download='ald-run-receipt.json';a.click();setTimeout(()=>URL.revokeObjectURL(url),1000);
|
| 1477 |
};
|
| 1478 |
+
|
| 1479 |
+
|