Below is a warp of 32 lanes. Each lane holds a value from 0 to 31. Use the threshold slider to set a branch condition — lanes whose value is below the threshold take the if-branch (shown in blue); the rest take the else-branch (shown in orange).
<!-- Warp divergence demo — 32 lanes, one branch, watch serialization happen -->
<div class="controls">
<label for="threshold">Branch threshold: <span id="thresh-val">16</span></label>
<input type="range" id="threshold" min="0" max="32" value="16" />
<button id="btn-run" type="button">Run warp</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div class="legend">
<span class="dot if-dot"></span> if-branch (lane < threshold)
<span class="dot else-dot"></span> else-branch (lane >= threshold)
<span class="dot idle-dot"></span> masked (idle)
</div>
<div id="warp-grid" class="warp-grid" title="Warp lanes colored by branch direction"></div>
<div id="passes" class="passes"></div>
<div id="summary" class="summary"></div>
/* Styles for the warp divergence demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.controls { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-bottom: .5rem; }
label { font-size: .85rem; font-weight: 600; white-space: nowrap; }
input[type=range] { flex: 1; min-width: 120px; max-width: 240px; }
button { font: 600 13px system-ui; padding: .35rem .75rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.legend { display: flex; align-items: center; gap: .8rem; font-size: .8rem; margin-bottom: .4rem; flex-wrap: wrap; }
.dot { display: inline-block; width: 12px; height: 12px; border-radius: 3px; margin-right: 3px; vertical-align: middle; }
.if-dot { background: #3a86ff; }
.else-dot { background: #ff6b35; }
.idle-dot { background: #c9ccd1; }
/* Lane grid — 16 per row so all 32 fit in two compact rows */
.warp-grid { display: grid; grid-template-columns: repeat(16, 1fr); gap: 3px; margin-bottom: .6rem; }
.lane { height: 28px; border-radius: 5px; display: flex; align-items: center; justify-content: center;
font: 700 10px ui-monospace, monospace; color: #fff; transition: background .2s; cursor: default; }
.lane.if-lane { background: #3a86ff; }
.lane.else-lane { background: #ff6b35; }
.lane.idle-lane { background: #c9ccd1; color: #555; }
/* Serial pass breakdown panels */
.passes { display: flex; flex-direction: column; gap: .4rem; margin-bottom: .5rem; }
.pass-row { border: 1px solid #cdd9e3; border-radius: 8px; padding: .35rem .55rem; }
.pass-title { font: 700 12px system-ui; margin-bottom: .25rem; }
.pass-grid { display: grid; grid-template-columns: repeat(16, 1fr); gap: 2px; }
.pl { height: 20px; border-radius: 3px; display: flex; align-items: center; justify-content: center;
font: 600 9px ui-monospace, monospace; }
.pl.active { color: #fff; }
.pl.masked { background: #ebebeb; color: #bbb; }
.pl.if-active { background: #3a86ff; }
.pl.else-active { background: #ff6b35; }
.pass-stat { font-size: .78rem; margin-top: .2rem; color: #555; }
/* Summary message styling */
.summary { font-size: .9rem; font-weight: 600; min-height: 1.3em; }
.summary.good { color: #0a7d33; }
.summary.warn { color: #c07000; }
// GPU warp divergence simulator — SIMT serialization model
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
const LANES = 32;
const sliderEl = document.getElementById('threshold');
const threshValEl = document.getElementById('thresh-val');
const gridEl = document.getElementById('warp-grid');
const passesEl = document.getElementById('passes');
const summaryEl = document.getElementById('summary');
// Current threshold and whether the simulation has run
let threshold = 16;
let simRan = false;
function buildGrid() {
// Build the 32-lane visual with if/else coloring
gridEl.innerHTML = '';
for (let i = 0; i < LANES; i++) {
const d = document.createElement('div');
if (!simRan) {
d.className = 'lane ' + (i < threshold ? 'if-lane' : 'else-lane');
} else {
d.className = 'lane ' + (i < threshold ? 'if-lane' : 'else-lane');
}
d.textContent = i;
d.title = i18nFmt('Lane %id% — value: %val%', { id: i, val: i });
gridEl.appendChild(d);
}
}
function computePasses() {
// Compute which lanes take each path
const ifLanes = [];
const elseLanes = [];
for (let i = 0; i < LANES; i++) {
if (i < threshold) ifLanes.push(i); else elseLanes.push(i);
}
const passes = [];
if (ifLanes.length > 0) passes.push({ label: 'if-branch', active: new Set(ifLanes), cls: 'if-active' });
if (elseLanes.length > 0) passes.push({ label: 'else-branch', active: new Set(elseLanes), cls: 'else-active' });
return passes;
}
function renderPasses(passes) {
// Render serial pass breakdown rows
passesEl.innerHTML = '';
passes.forEach((pass, pi) => {
const row = document.createElement('div');
row.className = 'pass-row';
const title = document.createElement('div');
title.className = 'pass-title';
title.textContent = i18nFmt('Pass %n%: %label%', { n: pi + 1, label: pass.label });
row.appendChild(title);
const pg = document.createElement('div');
pg.className = 'pass-grid';
for (let i = 0; i < LANES; i++) {
const pl = document.createElement('div');
if (pass.active.has(i)) {
pl.className = 'pl active ' + pass.cls;
} else {
pl.className = 'pl masked';
}
pl.textContent = i;
pg.appendChild(pl);
}
row.appendChild(pg);
const stat = document.createElement('div');
stat.className = 'pass-stat';
stat.textContent = i18nFmt('%active% / %total% lanes active', { active: pass.active.size, total: LANES });
row.appendChild(stat);
passesEl.appendChild(row);
});
}
function renderSummary(passes) {
// Show final divergence summary
const k = passes.length;
if (k === 0) {
summaryEl.textContent = 'Move the slider and click Run warp.';
summaryEl.className = 'summary';
return;
}
if (k === 1) {
summaryEl.textContent = i18nFmt('No divergence — all %active% lanes take the same path. One pass, full efficiency.', { active: LANES });
summaryEl.className = 'summary good';
} else {
const ifCount = threshold;
const elseCount = LANES - threshold;
summaryEl.textContent = i18nFmt('Warp diverged into %k% serial passes: %if_count% if-lanes, %else_count% else-lanes.', { k: k, if_count: ifCount, else_count: elseCount });
summaryEl.className = 'summary warn';
}
}
function runSim() {
// Execute the simulated warp with current threshold
simRan = true;
buildGrid();
const passes = computePasses();
renderPasses(passes);
renderSummary(passes);
}
function reset() {
// Clear pass panels and return to initial state
simRan = false;
passesEl.innerHTML = '';
summaryEl.textContent = '';
summaryEl.className = 'summary';
buildGrid();
}
sliderEl.addEventListener('input', () => {
threshold = parseInt(sliderEl.value, 10);
threshValEl.textContent = threshold;
if (simRan) runSim(); else buildGrid();
});
document.getElementById('btn-run').addEventListener('click', runSim);
document.getElementById('btn-reset').addEventListener('click', reset);
// Initial render on page load
buildGrid();
<!-- Warp divergence demo — 32 lanes, one branch, watch serialization happen -->
<div class="controls">
<label for="threshold">Branch threshold: <span id="thresh-val">16</span></label>
<input type="range" id="threshold" min="0" max="32" value="16" />
<button id="btn-run" type="button">Run warp</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div class="legend">
<span class="dot if-dot"></span> if-branch (lane < threshold)
<span class="dot else-dot"></span> else-branch (lane >= threshold)
<span class="dot idle-dot"></span> masked (idle)
</div>
<div id="warp-grid" class="warp-grid" title="Warp lanes colored by branch direction"></div>
<div id="passes" class="passes"></div>
<div id="summary" class="summary"></div>
/* Styles for the warp divergence demo */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.controls { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; margin-bottom: .5rem; }
label { font-size: .85rem; font-weight: 600; white-space: nowrap; }
input[type=range] { flex: 1; min-width: 120px; max-width: 240px; }
button { font: 600 13px system-ui; padding: .35rem .75rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
.legend { display: flex; align-items: center; gap: .8rem; font-size: .8rem; margin-bottom: .4rem; flex-wrap: wrap; }
.dot { display: inline-block; width: 12px; height: 12px; border-radius: 3px; margin-right: 3px; vertical-align: middle; }
.if-dot { background: #3a86ff; }
.else-dot { background: #ff6b35; }
.idle-dot { background: #c9ccd1; }
/* Lane grid — 16 per row so all 32 fit in two compact rows */
.warp-grid { display: grid; grid-template-columns: repeat(16, 1fr); gap: 3px; margin-bottom: .6rem; }
.lane { height: 28px; border-radius: 5px; display: flex; align-items: center; justify-content: center;
font: 700 10px ui-monospace, monospace; color: #fff; transition: background .2s; cursor: default; }
.lane.if-lane { background: #3a86ff; }
.lane.else-lane { background: #ff6b35; }
.lane.idle-lane { background: #c9ccd1; color: #555; }
/* Serial pass breakdown panels */
.passes { display: flex; flex-direction: column; gap: .4rem; margin-bottom: .5rem; }
.pass-row { border: 1px solid #cdd9e3; border-radius: 8px; padding: .35rem .55rem; }
.pass-title { font: 700 12px system-ui; margin-bottom: .25rem; }
.pass-grid { display: grid; grid-template-columns: repeat(16, 1fr); gap: 2px; }
.pl { height: 20px; border-radius: 3px; display: flex; align-items: center; justify-content: center;
font: 600 9px ui-monospace, monospace; }
.pl.active { color: #fff; }
.pl.masked { background: #ebebeb; color: #bbb; }
.pl.if-active { background: #3a86ff; }
.pl.else-active { background: #ff6b35; }
.pass-stat { font-size: .78rem; margin-top: .2rem; color: #555; }
/* Summary message styling */
.summary { font-size: .9rem; font-weight: 600; min-height: 1.3em; }
.summary.good { color: #0a7d33; }
.summary.warn { color: #c07000; }
// GPU warp divergence simulator — SIMT serialization model
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
const LANES = 32;
const sliderEl = document.getElementById('threshold');
const threshValEl = document.getElementById('thresh-val');
const gridEl = document.getElementById('warp-grid');
const passesEl = document.getElementById('passes');
const summaryEl = document.getElementById('summary');
// Current threshold and whether the simulation has run
let threshold = 16;
let simRan = false;
function buildGrid() {
// Build the 32-lane visual with if/else coloring
gridEl.innerHTML = '';
for (let i = 0; i < LANES; i++) {
const d = document.createElement('div');
if (!simRan) {
d.className = 'lane ' + (i < threshold ? 'if-lane' : 'else-lane');
} else {
d.className = 'lane ' + (i < threshold ? 'if-lane' : 'else-lane');
}
d.textContent = i;
d.title = i18nFmt('Lane %id% — value: %val%', { id: i, val: i });
gridEl.appendChild(d);
}
}
function computePasses() {
// Compute which lanes take each path
const ifLanes = [];
const elseLanes = [];
for (let i = 0; i < LANES; i++) {
if (i < threshold) ifLanes.push(i); else elseLanes.push(i);
}
const passes = [];
if (ifLanes.length > 0) passes.push({ label: 'if-branch', active: new Set(ifLanes), cls: 'if-active' });
if (elseLanes.length > 0) passes.push({ label: 'else-branch', active: new Set(elseLanes), cls: 'else-active' });
return passes;
}
function renderPasses(passes) {
// Render serial pass breakdown rows
passesEl.innerHTML = '';
passes.forEach((pass, pi) => {
const row = document.createElement('div');
row.className = 'pass-row';
const title = document.createElement('div');
title.className = 'pass-title';
title.textContent = i18nFmt('Pass %n%: %label%', { n: pi + 1, label: pass.label });
row.appendChild(title);
const pg = document.createElement('div');
pg.className = 'pass-grid';
for (let i = 0; i < LANES; i++) {
const pl = document.createElement('div');
if (pass.active.has(i)) {
pl.className = 'pl active ' + pass.cls;
} else {
pl.className = 'pl masked';
}
pl.textContent = i;
pg.appendChild(pl);
}
row.appendChild(pg);
const stat = document.createElement('div');
stat.className = 'pass-stat';
stat.textContent = i18nFmt('%active% / %total% lanes active', { active: pass.active.size, total: LANES });
row.appendChild(stat);
passesEl.appendChild(row);
});
}
function renderSummary(passes) {
// Show final divergence summary
const k = passes.length;
if (k === 0) {
summaryEl.textContent = 'Move the slider and click Run warp.';
summaryEl.className = 'summary';
return;
}
if (k === 1) {
summaryEl.textContent = i18nFmt('No divergence — all %active% lanes take the same path. One pass, full efficiency.', { active: LANES });
summaryEl.className = 'summary good';
} else {
const ifCount = threshold;
const elseCount = LANES - threshold;
summaryEl.textContent = i18nFmt('Warp diverged into %k% serial passes: %if_count% if-lanes, %else_count% else-lanes.', { k: k, if_count: ifCount, else_count: elseCount });
summaryEl.className = 'summary warn';
}
}
function runSim() {
// Execute the simulated warp with current threshold
simRan = true;
buildGrid();
const passes = computePasses();
renderPasses(passes);
renderSummary(passes);
}
function reset() {
// Clear pass panels and return to initial state
simRan = false;
passesEl.innerHTML = '';
summaryEl.textContent = '';
summaryEl.className = 'summary';
buildGrid();
}
sliderEl.addEventListener('input', () => {
threshold = parseInt(sliderEl.value, 10);
threshValEl.textContent = threshold;
if (simRan) runSim(); else buildGrid();
});
document.getElementById('btn-run').addEventListener('click', runSim);
document.getElementById('btn-reset').addEventListener('click', reset);
// Initial render on page load
buildGrid();
When all lanes take the same path the warp stays perfectly parallel — one pass, full efficiency. The moment even one lane disagrees, the hardware must run two serial passes: first the if-lanes (else-lanes masked off), then the else-lanes (if-lanes masked off). The active lane count in each pass tells you exactly how much parallelism you have left. Maximizing it is what GPU programmers mean by "avoiding divergence."
Comments
Loading comments...