Team Ai
Apppublic

valeriow/parallel-constrained-decoding

sourceHugging Faceapache-2.0updated 24d agoView on Hugging Face
0likes
app.js386 linesDownload Raw Back to web
1// Minimal App State2let presets = [];3let activePreset = null;4 5const $ = (id) => document.getElementById(id);6 7const els = {8  presetSelect: $('preset-select'),9  btnRun: $('btn-run'),10  summaryBar: $('summary-bar'),11  sumSpeedup: $('sum-speedup'),12  sumTimes: $('sum-times'),13  14  // Left: Parallel Constrained15  timerParallel: $('timer-parallel'),16  parallelOutput: $('parallel-output'),17  bodyParallel: $('body-parallel'),18  19  // Right: Naive20  timerNaive: $('timer-naive'),21  streamOutput: $('stream-output'),22  bodyNaive: $('body-naive'),23  badgeNaiveHallucinated: $('badge-naive-hallucinated'),24};25 26// Synchronized scrolling for side-by-side comparison27let isSyncingNaive = false;28let isSyncingParallel = false;29 30function setupScrollSync() {31  if (els.bodyNaive && els.bodyParallel) {32    els.bodyNaive.addEventListener('scroll', () => {33      if (isSyncingNaive) return;34      isSyncingParallel = true;35      els.bodyParallel.scrollTop = els.bodyNaive.scrollTop;36      els.bodyParallel.scrollLeft = els.bodyNaive.scrollLeft;37      requestAnimationFrame(() => { isSyncingParallel = false; });38    });39    40    els.bodyParallel.addEventListener('scroll', () => {41      if (isSyncingParallel) return;42      isSyncingNaive = true;43      els.bodyNaive.scrollTop = els.bodyParallel.scrollTop;44      els.bodyNaive.scrollLeft = els.bodyParallel.scrollLeft;45      requestAnimationFrame(() => { isSyncingNaive = false; });46    });47  }48}49 50function escapeHtml(str) {51  if (typeof str !== 'string') str = String(str);52  return str53    .replace(/&/g, '&amp;')54    .replace(/</g, '&lt;')55    .replace(/>/g, '&gt;');56}57 58function escapeAttr(str) {59  if (typeof str !== 'string') str = String(str);60  return str61    .replace(/&/g, '&amp;')62    .replace(/"/g, '&quot;');63}64 65function formatValue(val) {66  if (val === null || val === undefined) {67    return '<span class="json-val-null">null</span>';68  }69  if (typeof val === 'boolean') {70    return `<span class="json-val-bool bool-${val}">${val}</span>`;71  }72  if (typeof val === 'number') {73    return `<span class="json-val-num">${val}</span>`;74  }75  if (typeof val === 'string') {76    return `<span class="json-val-str">"${escapeHtml(val)}"</span>`;77  }78  if (Array.isArray(val)) {79    return `<span class="json-punct">[</span>${val.map(formatValue).join('<span class="json-punct">, </span>')}<span class="json-punct">]</span>`;80  }81  return `<span class="json-val-str">${escapeHtml(JSON.stringify(val))}</span>`;82}83 84// Single-line JSON formatters so keys line up line-for-line across both panels85function formatParallelJson(obj) {86  if (!obj || typeof obj !== 'object') return '<div class="json-row"><span class="json-punct">{ }</span></div>';87  const schemaKeys = activePreset && activePreset.schema ? Object.keys(activePreset.schema) : Object.keys(obj);88  const keys = schemaKeys.filter(k => k in obj);89  Object.keys(obj).forEach(k => {90    if (!keys.includes(k)) keys.push(k);91  });92  93  const lines = ['<div class="json-row"><span class="json-punct">{</span></div>'];94  keys.forEach((key, idx) => {95    const isLast = idx === keys.length - 1;96    const comma = isLast ? '' : '<span class="json-punct">,</span>';97    const val = obj[key];98    99    let inner = '';100    if (val && typeof val === 'object' && 'value' in val && 'prob' in val) {101      const valHtml = formatValue(val.value);102      const probNum = typeof val.prob === 'number' ? val.prob.toFixed(4) : escapeHtml(val.prob);103      inner = `  <span class="json-key">"${escapeHtml(key)}"</span><span class="json-punct">: { </span><span class="json-sub">"value"</span><span class="json-punct">: </span>${valHtml}<span class="json-punct">, </span><span class="json-sub">"prob"</span><span class="json-punct">: </span><span class="json-val-prob">${probNum}</span><span class="json-punct"> }</span>${comma}`;104    } else {105      inner = `  <span class="json-key">"${escapeHtml(key)}"</span><span class="json-punct">: </span>${formatValue(val)}${comma}`;106    }107    108    lines.push(`<div class="json-row" data-key="${escapeHtml(key)}">${inner}</div>`);109  });110  lines.push('<div class="json-row"><span class="json-punct">}</span></div>');111  return lines.join('');112}113 114function formatNaiveJson(obj) {115  if (!obj || typeof obj !== 'object') return '<div class="json-row"><span class="json-punct">{ }</span></div>';116  const schemaKeys = activePreset && activePreset.schema ? Object.keys(activePreset.schema) : Object.keys(obj);117  const keys = schemaKeys.filter(k => k in obj);118  Object.keys(obj).forEach(k => {119    if (!keys.includes(k)) keys.push(k);120  });121  122  const lines = ['<div class="json-row"><span class="json-punct">{</span></div>'];123  keys.forEach((key, idx) => {124    const isLast = idx === keys.length - 1;125    const comma = isLast ? '' : '<span class="json-punct">,</span>';126    const val = obj[key];127    const inner = `  <span class="json-key">"${escapeHtml(key)}"</span><span class="json-punct">: </span>${formatValue(val)}${comma}`;128    lines.push(`<div class="json-row" data-key="${escapeHtml(key)}">${inner}</div>`);129  });130  lines.push('<div class="json-row"><span class="json-punct">}</span></div>');131  return lines.join('');132}133 134function highlightStreamJson(text) {135  if (!text) return '';136  const lines = text.split('\n');137  const rendered = lines.map((line, idx) => {138    const isLastLine = idx === lines.length - 1;139    let lineHtml = escapeHtml(line);140    141    if (lineHtml.includes('":')) {142      lineHtml = lineHtml.replace(/"([^"]+)"(\s*:)/g, '<span class="json-key">"$1"</span>$2');143    } else if (isLastLine && /^(\s*)"([^"]*)$/.test(lineHtml)) {144      lineHtml = lineHtml.replace(/^(\s*)"([^"]*)$/, '$1<span class="json-key">"$2</span>');145    } else if (isLastLine && /^(\s*)"([^"]+)"(\s*)$/.test(lineHtml)) {146      lineHtml = lineHtml.replace(/^(\s*)"([^"]+)"(\s*)$/, '$1<span class="json-key">"$2"</span>$3');147    }148    149    return lineHtml;150  });151  152  return rendered.join('\n');153}154 155function setupRowHoverSync() {156  document.addEventListener('mouseover', (e) => {157    const row = e.target.closest('.json-row');158    if (row && row.dataset.key) {159      const key = row.dataset.key;160      document.querySelectorAll(`.json-row[data-key="${CSS.escape(key)}"]`).forEach(el => {161        el.classList.add('row-hover');162      });163    }164  });165 166  document.addEventListener('mouseout', (e) => {167    const row = e.target.closest('.json-row');168    if (row && row.dataset.key) {169      const key = row.dataset.key;170      document.querySelectorAll(`.json-row[data-key="${CSS.escape(key)}"]`).forEach(el => {171        el.classList.remove('row-hover');172      });173    }174  });175}176 177// Initialize178async function init() {179  setupScrollSync();180  setupRowHoverSync();181  try {182    const res = await fetch('/api/presets');183    presets = await res.json();184    185    els.presetSelect.innerHTML = '';186    presets.forEach((p) => {187      const opt = document.createElement('option');188      opt.value = p.id;189      opt.textContent = `${p.title}`;190      els.presetSelect.appendChild(opt);191    });192    193    if (presets.length > 0) {194      activePreset = presets[0];195    }196  } catch (e) {197    console.error('Error loading presets:', e);198  }199 200  els.presetSelect.addEventListener('change', (e) => {201    activePreset = presets.find(p => p.id === e.target.value);202    reset();203  });204 205  els.btnRun.addEventListener('click', runComparison);206}207 208function reset() {209  els.timerNaive.textContent = '0.0 ms';210  els.timerParallel.textContent = '0.0 ms';211  els.streamOutput.innerHTML = '<span class="placeholder-text">Click "Run Comparison" to start...</span>';212  els.parallelOutput.innerHTML = '<span class="placeholder-text">Click "Run Comparison" to start...</span>';213  els.summaryBar.classList.add('hidden');214  if (els.badgeNaiveHallucinated) {215    els.badgeNaiveHallucinated.classList.add('hidden');216    els.badgeNaiveHallucinated.textContent = '';217  }218}219 220// Execute Parallel Constrained Generation221async function runParallel(payload) {222  els.parallelOutput.innerHTML = '<span class="placeholder-text">// Evaluating parallel forward pass across all schema fields...</span>';223  224  const t0 = performance.now();225  let running = true;226  function tickParallel() {227    if (!running) return;228    const ms = performance.now() - t0;229    els.timerParallel.textContent = `${ms.toFixed(1)} ms`;230    requestAnimationFrame(tickParallel);231  }232  requestAnimationFrame(tickParallel);233  234  try {235    const res = await fetch('/api/run-parallel', {236      method: 'POST',237      headers: { 'Content-Type': 'application/json' },238      body: JSON.stringify(payload)239    });240    const data = await res.json();241    running = false;242    const elapsed = data.elapsed_ms || (performance.now() - t0);243    els.timerParallel.textContent = `${elapsed.toFixed(1)} ms`;244    renderParallel(data);245    return data;246  } catch (err) {247    running = false;248    throw err;249  }250}251 252function renderParallel(data) {253  if (data.parsed_json) {254    els.parallelOutput.innerHTML = formatParallelJson(data.parsed_json);255  }256}257 258// Stream Naive259async function streamNaive(payload) {260  els.streamOutput.textContent = '';261  262  const t0 = performance.now();263  let running = true;264  function tickNaive() {265    if (!running) return;266    const ms = performance.now() - t0;267    els.timerNaive.textContent = `${ms.toFixed(1)} ms`;268    requestAnimationFrame(tickNaive);269  }270  requestAnimationFrame(tickNaive);271  272  let tokenCount = 0;273  let finalResult = null;274  275  try {276    const response = await fetch('/api/stream-naive', {277      method: 'POST',278      headers: { 'Content-Type': 'application/json' },279      body: JSON.stringify(payload)280    });281    282    const reader = response.body.getReader();283    const decoder = new TextDecoder();284    let text = '';285    286    while (true) {287      const { value, done } = await reader.read();288      if (done) break;289      290      const chunk = decoder.decode(value, { stream: true });291      const lines = chunk.split('\n');292      293      for (const line of lines) {294        if (line.startsWith('data: ')) {295          try {296            const ev = JSON.parse(line.substring(6));297            if (ev.type === 'token') {298              text += ev.token;299              tokenCount = ev.token_count;300              els.streamOutput.innerHTML = highlightStreamJson(text);301              els.streamOutput.scrollTop = els.streamOutput.scrollHeight;302            } else if (ev.type === 'done') {303              finalResult = ev.result;304            }305          } catch (e) {}306        }307      }308    }309  } finally {310    running = false;311  }312  313  if (finalResult) {314    els.timerNaive.textContent = `${finalResult.elapsed_ms.toFixed(1)} ms`;315    let parsedObj = finalResult.parsed_json;316    if (parsedObj) {317      els.streamOutput.innerHTML = formatNaiveJson(parsedObj);318    } else {319      try {320        const cleaned = text.substring(text.indexOf('{'), text.lastIndexOf('}') + 1);321        parsedObj = JSON.parse(cleaned);322        els.streamOutput.innerHTML = formatNaiveJson(parsedObj);323      } catch (e) {324        els.streamOutput.innerHTML = highlightStreamJson(text);325      }326    }327 328    // Check for missing / hallucinated fields329    const expectedKeys = activePreset && activePreset.schema ? Object.keys(activePreset.schema) : [];330    let missingCount = 0;331    if (parsedObj && typeof parsedObj === 'object') {332      const parsedKeys = Object.keys(parsedObj);333      const missing = expectedKeys.filter(k => !parsedKeys.includes(k));334      const extra = parsedKeys.filter(k => !expectedKeys.includes(k));335      const invalidEnums = finalResult.invalid_enums || [];336      missingCount = missing.length + extra.length + invalidEnums.length;337    } else {338      missingCount = expectedKeys.length;339    }340 341    if (missingCount > 0 && els.badgeNaiveHallucinated) {342      els.badgeNaiveHallucinated.textContent = `${missingCount} field${missingCount > 1 ? 's' : ''} hallucinated`;343      els.badgeNaiveHallucinated.classList.remove('hidden');344    }345  }346  347  return finalResult;348}349 350// Side-by-Side Comparison Runner351async function runComparison() {352  if (!activePreset) return;353  354  reset();355  els.btnRun.disabled = true;356  els.btnRun.textContent = 'Running...';357  358  const payload = {359    context: activePreset.context,360    schema: activePreset.schema361  };362  363  try {364    // 1. Run Parallel Constrained inference first365    const parallelData = await runParallel(payload);366    367    // 2. Stream Naive right after368    const naiveData = await streamNaive(payload);369    370    // 3. Update summary pill371    if (parallelData && naiveData) {372      const speedup = (naiveData.elapsed_ms / Math.max(parallelData.elapsed_ms, 1.0)).toFixed(1);373      els.sumSpeedup.textContent = `${speedup}x FASTER`;374      els.sumTimes.textContent = `${parallelData.elapsed_ms.toFixed(1)} ms vs ${naiveData.elapsed_ms.toFixed(1)} ms`;375      els.summaryBar.classList.remove('hidden');376    }377  } catch (err) {378    console.error('Run failed:', err);379  } finally {380    els.btnRun.disabled = false;381    els.btnRun.innerHTML = '<span>⚡ Run Comparison</span>';382  }383}384 385document.addEventListener('DOMContentLoaded', init);386