joelniklaus HF Staff commited on
Commit
93ebc2c
Β·
1 Parent(s): 1dd6b75

refactored the throughput optimization visualization

Browse files
app/src/content/embeds/d3-optimization-sweep.html CHANGED
@@ -141,28 +141,31 @@
141
 
142
  // ── Data ──
143
  const DATA = [
144
- { model: 'Qwen3-0.6B', family: 'Qwen3', baseTp: 1, baseTps: 13527, t0Tps: 14069, t0Speedup: 1.04, t0Params: 'mns=512', t1Tps: 12330, t1Speedup: 0.91, t1Params: 'mns=512, gmu=95', bestTps: 14069, bestSpeedup: 1.04, bestParams: 'mns=512' },
145
- { model: 'Qwen3-1.7B', family: 'Qwen3', baseTp: 1, baseTps: 11710, t0Tps: 12313, t0Speedup: 1.05, t0Params: 'mnbt=32768', t1Tps: 11262, t1Speedup: 0.96, t1Params: 'mnbt=32768, gmu=95', bestTps: 12313, bestSpeedup: 1.05, bestParams: 'mnbt=32768' },
146
- { model: 'Qwen3-14B', family: 'Qwen3', baseTp: 1, baseTps: 4414, t0Tps: 4549, t0Speedup: 1.03, t0Params: 'tp=2', t1Tps: 4158, t1Speedup: 0.94, t1Params: 'tp=2', bestTps: 4549, bestSpeedup: 1.03, bestParams: 'tp=2' },
147
- { model: 'Qwen3-30B-A3B', family: 'Qwen3', baseTp: 1, baseTps: 2977, t0Tps: 5310, t0Speedup: 1.78, t0Params: 'tp=2, mns=512, mnbt=32768', t1Tps: 5064, t1Speedup: 1.70, t1Params: 'tp=2, mns=512, mnbt=32768, gmu=95', bestTps: 5310, bestSpeedup: 1.78, bestParams: 'tp=2, mns=512, mnbt=32768' },
148
- { model: 'Qwen3-32B', family: 'Qwen3', baseTp: 4, baseTps: 1987, t0Tps: 2072, t0Speedup: 1.04, t0Params: 'mns=512, mnbt=16384', t1Tps: 2078, t1Speedup: 1.05, t1Params: 'mns=512, mnbt=16384, gmu=95', bestTps: 2078, bestSpeedup: 1.05, bestParams: 'mns=512, mnbt=16384, gmu=95' },
149
- { model: 'Qwen3-4B', family: 'Qwen3', baseTp: 1, baseTps: 7919, t0Tps: 8086, t0Speedup: 1.02, t0Params: 'mnbt=32768', t1Tps: 7751, t1Speedup: 0.98, t1Params: 'mnbt=32768, gmu=95', bestTps: 8086, bestSpeedup: 1.02, bestParams: 'mnbt=32768' },
150
- { model: 'Qwen3-8B', family: 'Qwen3', baseTp: 1, baseTps: 6338, t0Tps: 6338, t0Speedup: 1.00, t0Params: '(baseline)', t1Tps: 6443, t1Speedup: 1.02, t1Params: 'gmu=95', bestTps: 6443, bestSpeedup: 1.02, bestParams: 'gmu=95' },
151
- { model: 'Qwen3-Next-80B-A3B', family: 'Qwen3', baseTp: 4, baseTps: 2034, t0Tps: 2678, t0Speedup: 1.32, t0Params: 'mns=512', t1Tps: 2481, t1Speedup: 1.22, t1Params: 'mns=512', bestTps: 2678, bestSpeedup: 1.32, bestParams: 'mns=512' },
152
- { model: 'SmolLM2-1.7B', family: 'SmolLM2', baseTp: 1, baseTps: 5255, t0Tps: 5437, t0Speedup: 1.03, t0Params: 'mns=2048, mnbt=32768', t1Tps: 9220, t1Speedup: 1.75, t1Params: 'mns=2048, mnbt=32768, gmu=95, spec=suffix_32', bestTps: 9220, bestSpeedup: 1.75, bestParams: 'mns=2048, mnbt=32768, gmu=95, spec=suffix_32' },
153
- { model: 'SmolLM2-135M', family: 'SmolLM2', baseTp: 1, baseTps: 28391, t0Tps: 31186, t0Speedup: 1.10, t0Params: 'mns=512, mnbt=32768', t1Tps: 45540, t1Speedup: 1.60, t1Params: 'mns=512, mnbt=32768, spec=ngram_6', bestTps: 45540, bestSpeedup: 1.60, bestParams: 'mns=512, mnbt=32768, spec=ngram_6' },
154
- { model: 'SmolLM2-360M', family: 'SmolLM2', baseTp: 1, baseTps: 17887, t0Tps: 18844, t0Speedup: 1.05, t0Params: 'mns=512', t1Tps: 23996, t1Speedup: 1.34, t1Params: 'mns=512, spec=ngram_6', bestTps: 23996, bestSpeedup: 1.34, bestParams: 'mns=512, spec=ngram_6' },
155
- { model: 'Gemma-3-12B', family: 'Gemma3', baseTp: 1, baseTps: 2999, t0Tps: 2999, t0Speedup: 1.00, t0Params: '(baseline)', t1Tps: 3046, t1Speedup: 1.02, t1Params: 'gmu=95', bestTps: 3046, bestSpeedup: 1.02, bestParams: 'gmu=95' },
156
- { model: 'Gemma-3-1B', family: 'Gemma3', baseTp: 1, baseTps: 14838, t0Tps: 16762, t0Speedup: 1.13, t0Params: 'mns=4096, mnbt=32768', t1Tps: 13832, t1Speedup: 0.93, t1Params: 'mns=4096, mnbt=32768, gmu=95', bestTps: 16762, bestSpeedup: 1.13, bestParams: 'mns=4096, mnbt=32768' },
157
- { model: 'Gemma-3-270M', family: 'Gemma3', baseTp: 1, baseTps: 22996, t0Tps: 23585, t0Speedup: 1.03, t0Params: 'mnbt=32768', t1Tps: 21030, t1Speedup: 0.91, t1Params: 'mnbt=32768', bestTps: 23585, bestSpeedup: 1.03, bestParams: 'mnbt=32768' },
158
- { model: 'Gemma-3-27B', family: 'Gemma3', baseTp: 2, baseTps: 1724, t0Tps: 1724, t0Speedup: 1.00, t0Params: '(baseline)', t1Tps: 1671, t1Speedup: 0.97, t1Params: 'gmu=95', bestTps: 1724, bestSpeedup: 1.00, bestParams: '(baseline)' },
159
- { model: 'Gemma-3-4B', family: 'Gemma3', baseTp: 1, baseTps: 8501, t0Tps: 9253, t0Speedup: 1.09, t0Params: 'mns=1024, mnbt=32768', t1Tps: 8361, t1Speedup: 0.98, t1Params: 'mns=1024, mnbt=32768', bestTps: 9253, bestSpeedup: 1.09, bestParams: 'mns=1024, mnbt=32768' },
160
- { model: 'GPT-OSS-120B', family: 'GPT-OSS', baseTp: 1, baseTps: 3138, t0Tps: 6117, t0Speedup: 1.95, t0Params: 'tp=2, mns=1024, mnbt=32768', t1Tps: 5450, t1Speedup: 1.74, t1Params: 'tp=2, mns=1024, mnbt=32768', bestTps: 6117, bestSpeedup: 1.95, bestParams: 'tp=2, mns=1024, mnbt=32768' },
161
- { model: 'GPT-OSS-20B', family: 'GPT-OSS', baseTp: 1, baseTps: 12432, t0Tps: 14671, t0Speedup: 1.18, t0Params: 'mns=512, mnbt=16384', t1Tps: 13004, t1Speedup: 1.05, t1Params: 'mns=512, mnbt=16384', bestTps: 14671, bestSpeedup: 1.18, bestParams: 'mns=512, mnbt=16384' },
162
  ];
163
 
164
  const FAMILIES = ['Qwen3', 'SmolLM2', 'Gemma3', 'GPT-OSS'];
165
  const TIERS = ['Baseline', 'Tier 0', 'Tier 1'];
 
 
 
166
 
167
  // ── Colors & shapes ──
168
  function getFamilyColors() {
@@ -177,17 +180,12 @@
177
  const familyPalette = getFamilyColors();
178
  const familyColor = (family) => familyPalette[FAMILIES.indexOf(family)] || 'var(--primary-color)';
179
 
180
- // D3 symbol generators for each tier
181
- const SHAPE_SIZE = 42;
182
  const shapeGenerators = {
183
  'Baseline': d3.symbol().type(d3.symbolCircle),
184
  'Tier 0': d3.symbol().type(d3.symbolSquare),
185
  'Tier 1': d3.symbol().type(d3.symbolTriangle),
186
  };
187
 
188
- // Vertical offsets within each band: spread tiers apart
189
- const TIER_Y_OFFSET = { 'Baseline': -0.5, 'Tier 0': 0, 'Tier 1': 0.5 };
190
-
191
  // ── Tooltip ──
192
  let tip = container.querySelector('.d3-tooltip');
193
  let tipInner;
@@ -199,12 +197,10 @@
199
 
200
  function showTip(html, mx, my) {
201
  tipInner.innerHTML = html;
202
- const tipW = tip.offsetWidth || 200;
203
- const tipH = tip.offsetHeight || 100;
204
  const cw = container.clientWidth;
205
  let tx = mx + 14, ty = my - 10;
206
- if (tx + tipW > cw - 8) tx = mx - tipW - 14;
207
- if (ty + tipH > container.clientHeight) ty = container.clientHeight - tipH - 4;
208
  if (ty < 0) ty = 4;
209
  tip.style.transform = `translate(${tx}px, ${ty}px)`;
210
  tip.style.opacity = '1';
@@ -214,6 +210,31 @@
214
  tip.style.transform = 'translate(-9999px, -9999px)';
215
  }
216
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
217
  // ── SVG ──
218
  const svg = d3.select(container).append('svg').attr('width', '100%').style('display', 'block');
219
  const gRoot = svg.append('g');
@@ -234,28 +255,25 @@
234
  return d;
235
  }
236
 
237
- // ── Layout ──
238
- const margin = { top: 24, right: 62, bottom: 42, left: 148 };
239
- let width = 800, height = 500;
240
-
241
- function updateSize() {
242
- width = container.clientWidth || 800;
243
- height = Math.max(400, DATA.length * 36 + margin.top + margin.bottom);
244
- svg.attr('width', width).attr('height', height);
245
- gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
246
- return { iw: width - margin.left - margin.right, ih: height - margin.top - margin.bottom };
247
  }
248
 
249
  // ── Render ──
250
  function render() {
251
- const { iw, ih } = updateSize();
252
- const data = sortedData();
253
- const modelNames = data.map(d => d.model);
 
254
 
255
- const yScale = d3.scaleBand().domain(modelNames).range([0, ih]).padding(0.35);
 
256
  const bandH = yScale.bandwidth();
257
 
258
- // X scale depends on metric
259
  let xScale;
260
  if (state.metric === 'throughput') {
261
  const maxTps = d3.max(data, d => Math.max(d.baseTps, d.t0Tps, d.t1Tps));
@@ -266,55 +284,36 @@
266
  xScale = d3.scaleLinear().domain([Math.min(0.85, minSpd - 0.05), Math.max(2.05, maxSpd + 0.1)]).range([0, iw]).nice();
267
  }
268
 
269
- // ── Grid ──
270
- const gridTicks = xScale.ticks(8);
271
  gRoot.selectAll('.grid').data([0]).join('g').attr('class', 'grid')
272
  .call(g => {
273
- const lines = g.selectAll('line').data(gridTicks, d => d);
274
- lines.join('line')
275
  .attr('x1', d => xScale(d)).attr('x2', d => xScale(d))
276
  .attr('y1', 0).attr('y2', ih)
277
  .attr('stroke', 'var(--grid-color)').attr('stroke-width', 1);
278
  });
279
 
280
- // ── X axis ──
281
- const xAxisGen = state.metric === 'throughput'
282
- ? d3.axisBottom(xScale).ticks(8).tickFormat(d => d >= 1000 ? (d / 1000) + 'k' : d)
283
- : d3.axisBottom(xScale).ticks(8).tickFormat(d => d.toFixed(1) + 'x');
284
  gRoot.selectAll('.axis-x').data([0]).join('g').attr('class', 'axis-x')
285
  .attr('transform', `translate(0,${ih})`)
286
- .call(xAxisGen)
287
- .call(g => {
288
- g.selectAll('path, line').attr('stroke', 'var(--axis-color)');
289
- g.selectAll('text').attr('fill', 'var(--tick-color)').style('font-size', '11px');
290
- });
291
-
292
- // ── X axis top (mirrored ticks) ──
293
- const xAxisTopGen = state.metric === 'throughput'
294
- ? d3.axisTop(xScale).ticks(8).tickFormat(d => d >= 1000 ? (d / 1000) + 'k' : d)
295
- : d3.axisTop(xScale).ticks(8).tickFormat(d => d.toFixed(1) + 'x');
296
  gRoot.selectAll('.axis-x-top').data([0]).join('g').attr('class', 'axis-x-top')
297
- .call(xAxisTopGen)
298
- .call(g => {
299
- g.selectAll('path, line').attr('stroke', 'var(--axis-color)');
300
- g.selectAll('text').attr('fill', 'var(--tick-color)').style('font-size', '11px');
301
- });
302
 
303
- // ── X axis label ──
304
  const xLabel = state.metric === 'throughput' ? 'Tokens per second per GPU' : 'Speedup vs baseline';
305
  gRoot.selectAll('.x-label').data([0]).join('text').attr('class', 'x-label')
306
  .attr('x', iw / 2).attr('y', ih + margin.bottom - 4)
307
  .attr('text-anchor', 'middle').attr('fill', 'var(--muted-color)')
308
  .attr('font-size', 11).text(xLabel);
309
 
310
- // ── Y axis (model names) ──
311
  gRoot.selectAll('.axis-y').data([0]).join('g').attr('class', 'axis-y')
312
  .call(g => {
313
- const labels = g.selectAll('text.y-label-text').data(data, d => d.model);
314
- labels.join(
315
  enter => enter.append('text').attr('class', 'y-label-text')
316
- .attr('x', -8).attr('dy', '0.35em')
317
- .attr('text-anchor', 'end'),
318
  update => update,
319
  exit => exit.remove()
320
  )
@@ -323,7 +322,7 @@
323
  .text(d => d.model);
324
  });
325
 
326
- // ── Reference line at 1.0x in speedup mode ──
327
  gRoot.selectAll('.ref-line').data(state.metric === 'speedup' ? [1.0] : []).join('line')
328
  .attr('class', 'ref-line')
329
  .attr('x1', d => xScale(d)).attr('x2', d => xScale(d))
@@ -331,30 +330,39 @@
331
  .attr('stroke', 'var(--text-color)').attr('stroke-width', 1.5)
332
  .attr('stroke-dasharray', '4,3').attr('opacity', 0.5);
333
 
334
- // ── Dumbbell rows ──
335
  if (state.metric === 'throughput') {
336
- renderThroughput(data, xScale, yScale, bandH, iw);
 
337
  } else {
338
- renderSpeedup(data, xScale, yScale, bandH, iw);
 
 
339
  }
340
- }
341
 
342
- function renderThroughput(data, xScale, yScale, bandH, iw) {
343
- // Remove speedup bars
344
- gRoot.selectAll('.speedup-bar').remove();
 
 
 
 
 
 
345
 
 
346
  // Connecting lines between the three staggered dots
347
- const connData = [];
348
- data.forEach(d => {
349
- const cyCenter = yScale(d.model) + bandH / 2;
350
- const points = [
351
- { x: xScale(d.baseTps), y: cyCenter + TIER_Y_OFFSET['Baseline'] * bandH },
352
- { x: xScale(d.t0Tps), y: cyCenter + TIER_Y_OFFSET['Tier 0'] * bandH },
353
- { x: xScale(d.t1Tps), y: cyCenter + TIER_Y_OFFSET['Tier 1'] * bandH },
354
- ];
355
- connData.push({ model: d.model, family: d.family, points });
356
- });
357
  const lineGen = d3.line().x(p => p.x).y(p => p.y);
 
 
 
 
 
 
 
 
 
 
358
  gRoot.selectAll('.conn-line').data(connData, d => d.model).join('path')
359
  .attr('class', 'conn-line')
360
  .attr('d', d => lineGen(d.points))
@@ -362,54 +370,27 @@
362
  .attr('stroke', d => familyColor(d.family))
363
  .attr('stroke-width', 1.5).attr('opacity', 0.35);
364
 
365
- // Dots: 3 per model (baseline, tier0, tier1) with vertical stagger
366
  const dots = [];
367
  data.forEach(d => {
368
- const cyCenter = yScale(d.model) + bandH / 2;
369
- ['Baseline', 'Tier 0', 'Tier 1'].forEach(tier => {
370
- const val = tier === 'Baseline' ? d.baseTps : tier === 'Tier 0' ? d.t0Tps : d.t1Tps;
371
- dots.push({ ...d, tier, val, cx: xScale(val), cy: cyCenter + TIER_Y_OFFSET[tier] * bandH });
372
  });
373
  });
374
 
375
- gRoot.selectAll('.dot').data(dots, d => d.model + '-' + d.tier).join('path')
376
  .attr('class', 'dot')
377
  .attr('d', d => shapeGenerators[d.tier].size(SHAPE_SIZE)())
378
  .attr('transform', d => `translate(${d.cx},${d.cy})`)
379
  .attr('fill', d => familyColor(d.family))
380
  .attr('stroke', 'none')
381
- .attr('opacity', 0.9)
382
- .attr('cursor', 'pointer')
383
- .on('mouseenter', function (event, d) {
384
- d3.select(this).attr('opacity', 1);
385
- const [mx, my] = d3.pointer(event, container);
386
- showTip(buildTooltip(d), mx, my);
387
- })
388
- .on('mousemove', function (event) {
389
- const [mx, my] = d3.pointer(event, container);
390
- tip.style.transform = `translate(${mx + 14}px, ${my - 10}px)`;
391
- })
392
- .on('mouseleave', function () {
393
- d3.select(this).attr('opacity', 0.9);
394
- hideTip();
395
- });
396
-
397
- // Speedup annotation on right
398
- gRoot.selectAll('.speedup-label').data(data, d => d.model).join('text')
399
- .attr('class', 'speedup-label')
400
- .attr('x', iw + 6)
401
- .attr('y', d => yScale(d.model) + bandH / 2)
402
- .attr('dy', '0.35em')
403
- .attr('fill', 'var(--muted-color)')
404
- .text(d => d.bestSpeedup.toFixed(2) + 'x');
405
  }
406
 
407
- function renderSpeedup(data, xScale, yScale, bandH, iw) {
408
- // Remove throughput elements
409
- gRoot.selectAll('.conn-line').remove();
410
- gRoot.selectAll('.dot').remove();
411
-
412
- // Two bars per model: Tier 0 speedup, Tier 1 speedup
413
  const barH = bandH * 0.38;
414
  const barData = [];
415
  data.forEach(d => {
@@ -419,7 +400,7 @@
419
  });
420
 
421
  const oneX = xScale(1.0);
422
- gRoot.selectAll('.speedup-bar').data(barData, d => d.model + '-' + d.tier).join('rect')
423
  .attr('class', 'speedup-bar')
424
  .attr('x', d => d.val >= 1.0 ? oneX : xScale(d.val))
425
  .attr('y', d => d.y)
@@ -429,30 +410,8 @@
429
  .attr('fill', d => familyColor(d.family))
430
  .attr('opacity', d => d.tier === 'Tier 0' ? 0.9 : 0.55)
431
  .attr('stroke', d => d.val < 1.0 ? '#e05252' : 'none')
432
- .attr('stroke-width', d => d.val < 1.0 ? 1 : 0)
433
- .attr('cursor', 'pointer')
434
- .on('mouseenter', function (event, d) {
435
- d3.select(this).attr('opacity', 1);
436
- const [mx, my] = d3.pointer(event, container);
437
- showTip(buildTooltip(d), mx, my);
438
- })
439
- .on('mousemove', function (event) {
440
- const [mx, my] = d3.pointer(event, container);
441
- tip.style.transform = `translate(${mx + 14}px, ${my - 10}px)`;
442
- })
443
- .on('mouseleave', function (event, d) {
444
- d3.select(this).attr('opacity', d.tier === 'Tier 0' ? 0.9 : 0.55);
445
- hideTip();
446
- });
447
-
448
- // Speedup annotation on right
449
- gRoot.selectAll('.speedup-label').data(data, d => d.model).join('text')
450
- .attr('class', 'speedup-label')
451
- .attr('x', iw + 6)
452
- .attr('y', d => yScale(d.model) + bandH / 2)
453
- .attr('dy', '0.35em')
454
- .attr('fill', 'var(--muted-color)')
455
- .text(d => d.bestSpeedup.toFixed(2) + 'x');
456
  }
457
 
458
  function buildTooltip(d) {
@@ -468,48 +427,41 @@
468
  }
469
 
470
  // ── Controls ──
471
- const controls = document.createElement('div'); controls.className = 'controls';
472
-
473
- // Metric select
474
- const metricGroup = document.createElement('div'); metricGroup.className = 'control-group';
475
- const metricLabel = document.createElement('label'); metricLabel.textContent = 'Metric'; metricLabel.setAttribute('for', 'metric-sel-optsweep');
476
- const metricSel = document.createElement('select'); metricSel.id = 'metric-sel-optsweep';
477
- [['throughput', 'Throughput'], ['speedup', 'Speedup']].forEach(([v, t]) => {
478
- const o = document.createElement('option'); o.value = v; o.textContent = t; metricSel.appendChild(o);
479
- });
480
- metricSel.value = state.metric;
481
- metricSel.addEventListener('change', () => { state.metric = metricSel.value; render(); });
482
- metricGroup.appendChild(metricLabel); metricGroup.appendChild(metricSel);
483
-
484
- // Sort select
485
- const sortGroup = document.createElement('div'); sortGroup.className = 'control-group';
486
- const sortLabel = document.createElement('label'); sortLabel.textContent = 'Sort'; sortLabel.setAttribute('for', 'sort-sel-optsweep');
487
- const sortSel = document.createElement('select'); sortSel.id = 'sort-sel-optsweep';
488
- [['speedup', 'By Best Speedup'], ['baseline', 'By Baseline Throughput'], ['family', 'By Model Family']].forEach(([v, t]) => {
489
- const o = document.createElement('option'); o.value = v; o.textContent = t; sortSel.appendChild(o);
490
- });
491
- sortSel.value = state.sort;
492
- sortSel.addEventListener('change', () => { state.sort = sortSel.value; render(); });
493
- sortGroup.appendChild(sortLabel); sortGroup.appendChild(sortSel);
494
 
495
- controls.appendChild(metricGroup); controls.appendChild(sortGroup);
 
 
 
 
 
 
496
  container.appendChild(controls);
497
 
498
  // ── Legend ──
499
  const legend = document.createElement('div'); legend.className = 'legend';
500
-
501
- // Tier shapes section
502
  const tierTitle = document.createElement('div'); tierTitle.className = 'legend-title'; tierTitle.textContent = 'Legend';
503
  legend.appendChild(tierTitle);
 
 
504
  const tierSection = document.createElement('div'); tierSection.className = 'legend-section';
 
505
  TIERS.forEach(tier => {
506
  const item = document.createElement('span'); item.className = 'item';
507
- // SVG swatch for shape
508
- const svgNS = 'http://www.w3.org/2000/svg';
509
  const shapeSvg = document.createElementNS(svgNS, 'svg');
510
  shapeSvg.setAttribute('width', '14'); shapeSvg.setAttribute('height', '14');
511
- shapeSvg.setAttribute('viewBox', '-8 -8 16 16');
512
- shapeSvg.style.display = 'block';
513
  const path = document.createElementNS(svgNS, 'path');
514
  path.setAttribute('d', shapeGenerators[tier].size(SHAPE_SIZE)());
515
  path.setAttribute('fill', 'var(--text-color)');
@@ -520,7 +472,7 @@
520
  });
521
  legend.appendChild(tierSection);
522
 
523
- // Family colors section
524
  const famSection = document.createElement('div'); famSection.className = 'legend-section'; famSection.style.marginTop = '4px';
525
  FAMILIES.forEach((fam, i) => {
526
  const item = document.createElement('span'); item.className = 'item';
 
141
 
142
  // ── Data ──
143
  const DATA = [
144
+ { model: 'Qwen3-0.6B', family: 'Qwen3', baseTp: 1, baseTps: 13527, t0Tps: 14069, t0Speedup: 1.04, t0Params: 'mns=512', t1Tps: 12330, t1Speedup: 0.91, t1Params: 'mns=512, gmu=95', bestSpeedup: 1.04 },
145
+ { model: 'Qwen3-1.7B', family: 'Qwen3', baseTp: 1, baseTps: 11710, t0Tps: 12313, t0Speedup: 1.05, t0Params: 'mnbt=32768', t1Tps: 11262, t1Speedup: 0.96, t1Params: 'mnbt=32768, gmu=95', bestSpeedup: 1.05 },
146
+ { model: 'Qwen3-14B', family: 'Qwen3', baseTp: 1, baseTps: 4414, t0Tps: 4549, t0Speedup: 1.03, t0Params: 'tp=2', t1Tps: 4158, t1Speedup: 0.94, t1Params: 'tp=2', bestSpeedup: 1.03 },
147
+ { model: 'Qwen3-30B-A3B', family: 'Qwen3', baseTp: 1, baseTps: 2977, t0Tps: 5310, t0Speedup: 1.78, t0Params: 'tp=2, mns=512, mnbt=32768', t1Tps: 5064, t1Speedup: 1.70, t1Params: 'tp=2, mns=512, mnbt=32768, gmu=95', bestSpeedup: 1.78 },
148
+ { model: 'Qwen3-32B', family: 'Qwen3', baseTp: 4, baseTps: 1987, t0Tps: 2072, t0Speedup: 1.04, t0Params: 'mns=512, mnbt=16384', t1Tps: 2078, t1Speedup: 1.05, t1Params: 'mns=512, mnbt=16384, gmu=95', bestSpeedup: 1.05 },
149
+ { model: 'Qwen3-4B', family: 'Qwen3', baseTp: 1, baseTps: 7919, t0Tps: 8086, t0Speedup: 1.02, t0Params: 'mnbt=32768', t1Tps: 7751, t1Speedup: 0.98, t1Params: 'mnbt=32768, gmu=95', bestSpeedup: 1.02 },
150
+ { model: 'Qwen3-8B', family: 'Qwen3', baseTp: 1, baseTps: 6338, t0Tps: 6338, t0Speedup: 1.00, t0Params: '(baseline)', t1Tps: 6443, t1Speedup: 1.02, t1Params: 'gmu=95', bestSpeedup: 1.02 },
151
+ { model: 'Qwen3-Next-80B-A3B', family: 'Qwen3', baseTp: 4, baseTps: 2034, t0Tps: 2678, t0Speedup: 1.32, t0Params: 'mns=512', t1Tps: 2481, t1Speedup: 1.22, t1Params: 'mns=512', bestSpeedup: 1.32 },
152
+ { model: 'SmolLM2-1.7B', family: 'SmolLM2', baseTp: 1, baseTps: 5255, t0Tps: 5437, t0Speedup: 1.03, t0Params: 'mns=2048, mnbt=32768', t1Tps: 9220, t1Speedup: 1.75, t1Params: 'mns=2048, mnbt=32768, gmu=95, spec=suffix_32', bestSpeedup: 1.75 },
153
+ { model: 'SmolLM2-135M', family: 'SmolLM2', baseTp: 1, baseTps: 28391, t0Tps: 31186, t0Speedup: 1.10, t0Params: 'mns=512, mnbt=32768', t1Tps: 45540, t1Speedup: 1.60, t1Params: 'mns=512, mnbt=32768, spec=ngram_6', bestSpeedup: 1.60 },
154
+ { model: 'SmolLM2-360M', family: 'SmolLM2', baseTp: 1, baseTps: 17887, t0Tps: 18844, t0Speedup: 1.05, t0Params: 'mns=512', t1Tps: 23996, t1Speedup: 1.34, t1Params: 'mns=512, spec=ngram_6', bestSpeedup: 1.34 },
155
+ { model: 'Gemma-3-12B', family: 'Gemma3', baseTp: 1, baseTps: 2999, t0Tps: 2999, t0Speedup: 1.00, t0Params: '(baseline)', t1Tps: 3046, t1Speedup: 1.02, t1Params: 'gmu=95', bestSpeedup: 1.02 },
156
+ { model: 'Gemma-3-1B', family: 'Gemma3', baseTp: 1, baseTps: 14838, t0Tps: 16762, t0Speedup: 1.13, t0Params: 'mns=4096, mnbt=32768', t1Tps: 13832, t1Speedup: 0.93, t1Params: 'mns=4096, mnbt=32768, gmu=95', bestSpeedup: 1.13 },
157
+ { model: 'Gemma-3-270M', family: 'Gemma3', baseTp: 1, baseTps: 22996, t0Tps: 23585, t0Speedup: 1.03, t0Params: 'mnbt=32768', t1Tps: 21030, t1Speedup: 0.91, t1Params: 'mnbt=32768', bestSpeedup: 1.03 },
158
+ { model: 'Gemma-3-27B', family: 'Gemma3', baseTp: 2, baseTps: 1724, t0Tps: 1724, t0Speedup: 1.00, t0Params: '(baseline)', t1Tps: 1671, t1Speedup: 0.97, t1Params: 'gmu=95', bestSpeedup: 1.00 },
159
+ { model: 'Gemma-3-4B', family: 'Gemma3', baseTp: 1, baseTps: 8501, t0Tps: 9253, t0Speedup: 1.09, t0Params: 'mns=1024, mnbt=32768', t1Tps: 8361, t1Speedup: 0.98, t1Params: 'mns=1024, mnbt=32768', bestSpeedup: 1.09 },
160
+ { model: 'GPT-OSS-120B', family: 'GPT-OSS', baseTp: 1, baseTps: 3138, t0Tps: 6117, t0Speedup: 1.95, t0Params: 'tp=2, mns=1024, mnbt=32768', t1Tps: 5450, t1Speedup: 1.74, t1Params: 'tp=2, mns=1024, mnbt=32768', bestSpeedup: 1.95 },
161
+ { model: 'GPT-OSS-20B', family: 'GPT-OSS', baseTp: 1, baseTps: 12432, t0Tps: 14671, t0Speedup: 1.18, t0Params: 'mns=512, mnbt=16384', t1Tps: 13004, t1Speedup: 1.05, t1Params: 'mns=512, mnbt=16384', bestSpeedup: 1.18 },
162
  ];
163
 
164
  const FAMILIES = ['Qwen3', 'SmolLM2', 'Gemma3', 'GPT-OSS'];
165
  const TIERS = ['Baseline', 'Tier 0', 'Tier 1'];
166
+ const SHAPE_SIZE = 42;
167
+ const TIER_Y_OFFSET = { 'Baseline': -0.38, 'Tier 0': 0, 'Tier 1': 0.38 };
168
+ const margin = { top: 24, right: 62, bottom: 42, left: 148 };
169
 
170
  // ── Colors & shapes ──
171
  function getFamilyColors() {
 
180
  const familyPalette = getFamilyColors();
181
  const familyColor = (family) => familyPalette[FAMILIES.indexOf(family)] || 'var(--primary-color)';
182
 
 
 
183
  const shapeGenerators = {
184
  'Baseline': d3.symbol().type(d3.symbolCircle),
185
  'Tier 0': d3.symbol().type(d3.symbolSquare),
186
  'Tier 1': d3.symbol().type(d3.symbolTriangle),
187
  };
188
 
 
 
 
189
  // ── Tooltip ──
190
  let tip = container.querySelector('.d3-tooltip');
191
  let tipInner;
 
197
 
198
  function showTip(html, mx, my) {
199
  tipInner.innerHTML = html;
 
 
200
  const cw = container.clientWidth;
201
  let tx = mx + 14, ty = my - 10;
202
+ if (tx + (tip.offsetWidth || 200) > cw - 8) tx = mx - (tip.offsetWidth || 200) - 14;
203
+ if (ty + (tip.offsetHeight || 100) > container.clientHeight) ty = container.clientHeight - (tip.offsetHeight || 100) - 4;
204
  if (ty < 0) ty = 4;
205
  tip.style.transform = `translate(${tx}px, ${ty}px)`;
206
  tip.style.opacity = '1';
 
210
  tip.style.transform = 'translate(-9999px, -9999px)';
211
  }
212
 
213
+ // ── Shared tooltip event handlers ──
214
+ function attachTipEvents(sel, opacityFn) {
215
+ sel
216
+ .attr('cursor', 'pointer')
217
+ .on('mouseenter', function (event, d) {
218
+ d3.select(this).attr('opacity', 1);
219
+ const [mx, my] = d3.pointer(event, container);
220
+ showTip(buildTooltip(d), mx, my);
221
+ })
222
+ .on('mousemove', function (event) {
223
+ const [mx, my] = d3.pointer(event, container);
224
+ tip.style.transform = `translate(${mx + 14}px, ${my - 10}px)`;
225
+ })
226
+ .on('mouseleave', function (event, d) {
227
+ d3.select(this).attr('opacity', opacityFn(d));
228
+ hideTip();
229
+ });
230
+ }
231
+
232
+ // ── Shared axis styling ──
233
+ function styleAxis(g) {
234
+ g.selectAll('path, line').attr('stroke', 'var(--axis-color)');
235
+ g.selectAll('text').attr('fill', 'var(--tick-color)').style('font-size', '11px');
236
+ }
237
+
238
  // ── SVG ──
239
  const svg = d3.select(container).append('svg').attr('width', '100%').style('display', 'block');
240
  const gRoot = svg.append('g');
 
255
  return d;
256
  }
257
 
258
+ // ── X axis tick format helper ──
259
+ function xTickFormat() {
260
+ return state.metric === 'throughput'
261
+ ? (d => d >= 1000 ? (d / 1000) + 'k' : d)
262
+ : (d => d.toFixed(1) + 'x');
 
 
 
 
 
263
  }
264
 
265
  // ── Render ──
266
  function render() {
267
+ const iw = (container.clientWidth || 800) - margin.left - margin.right;
268
+ const ih = Math.max(400, DATA.length * 36 + margin.top + margin.bottom) - margin.top - margin.bottom;
269
+ svg.attr('width', container.clientWidth || 800).attr('height', ih + margin.top + margin.bottom);
270
+ gRoot.attr('transform', `translate(${margin.left},${margin.top})`);
271
 
272
+ const data = sortedData();
273
+ const yScale = d3.scaleBand().domain(data.map(d => d.model)).range([0, ih]).padding(0.35);
274
  const bandH = yScale.bandwidth();
275
 
276
+ // X scale
277
  let xScale;
278
  if (state.metric === 'throughput') {
279
  const maxTps = d3.max(data, d => Math.max(d.baseTps, d.t0Tps, d.t1Tps));
 
284
  xScale = d3.scaleLinear().domain([Math.min(0.85, minSpd - 0.05), Math.max(2.05, maxSpd + 0.1)]).range([0, iw]).nice();
285
  }
286
 
287
+ // Grid
 
288
  gRoot.selectAll('.grid').data([0]).join('g').attr('class', 'grid')
289
  .call(g => {
290
+ g.selectAll('line').data(xScale.ticks(8), d => d).join('line')
 
291
  .attr('x1', d => xScale(d)).attr('x2', d => xScale(d))
292
  .attr('y1', 0).attr('y2', ih)
293
  .attr('stroke', 'var(--grid-color)').attr('stroke-width', 1);
294
  });
295
 
296
+ // X axes (bottom + top)
297
+ const fmt = xTickFormat();
 
 
298
  gRoot.selectAll('.axis-x').data([0]).join('g').attr('class', 'axis-x')
299
  .attr('transform', `translate(0,${ih})`)
300
+ .call(d3.axisBottom(xScale).ticks(8).tickFormat(fmt)).call(styleAxis);
 
 
 
 
 
 
 
 
 
301
  gRoot.selectAll('.axis-x-top').data([0]).join('g').attr('class', 'axis-x-top')
302
+ .call(d3.axisTop(xScale).ticks(8).tickFormat(fmt)).call(styleAxis);
 
 
 
 
303
 
304
+ // X axis label
305
  const xLabel = state.metric === 'throughput' ? 'Tokens per second per GPU' : 'Speedup vs baseline';
306
  gRoot.selectAll('.x-label').data([0]).join('text').attr('class', 'x-label')
307
  .attr('x', iw / 2).attr('y', ih + margin.bottom - 4)
308
  .attr('text-anchor', 'middle').attr('fill', 'var(--muted-color)')
309
  .attr('font-size', 11).text(xLabel);
310
 
311
+ // Y axis (model names)
312
  gRoot.selectAll('.axis-y').data([0]).join('g').attr('class', 'axis-y')
313
  .call(g => {
314
+ g.selectAll('text.y-label-text').data(data, d => d.model).join(
 
315
  enter => enter.append('text').attr('class', 'y-label-text')
316
+ .attr('x', -8).attr('dy', '0.35em').attr('text-anchor', 'end'),
 
317
  update => update,
318
  exit => exit.remove()
319
  )
 
322
  .text(d => d.model);
323
  });
324
 
325
+ // Reference line at 1.0x in speedup mode
326
  gRoot.selectAll('.ref-line').data(state.metric === 'speedup' ? [1.0] : []).join('line')
327
  .attr('class', 'ref-line')
328
  .attr('x1', d => xScale(d)).attr('x2', d => xScale(d))
 
330
  .attr('stroke', 'var(--text-color)').attr('stroke-width', 1.5)
331
  .attr('stroke-dasharray', '4,3').attr('opacity', 0.5);
332
 
333
+ // View-specific elements
334
  if (state.metric === 'throughput') {
335
+ gRoot.selectAll('.speedup-bar').remove();
336
+ renderThroughput(data, xScale, yScale, bandH);
337
  } else {
338
+ gRoot.selectAll('.conn-line').remove();
339
+ gRoot.selectAll('.dot').remove();
340
+ renderSpeedup(data, xScale, yScale, bandH);
341
  }
 
342
 
343
+ // Speedup annotation (shared between both views)
344
+ gRoot.selectAll('.speedup-label').data(data, d => d.model).join('text')
345
+ .attr('class', 'speedup-label')
346
+ .attr('x', iw + 6)
347
+ .attr('y', d => yScale(d.model) + bandH / 2)
348
+ .attr('dy', '0.35em')
349
+ .attr('fill', 'var(--muted-color)')
350
+ .text(d => d.bestSpeedup.toFixed(2) + 'x');
351
+ }
352
 
353
+ function renderThroughput(data, xScale, yScale, bandH) {
354
  // Connecting lines between the three staggered dots
 
 
 
 
 
 
 
 
 
 
355
  const lineGen = d3.line().x(p => p.x).y(p => p.y);
356
+ const connData = data.map(d => {
357
+ const cy = yScale(d.model) + bandH / 2;
358
+ return {
359
+ model: d.model, family: d.family,
360
+ points: TIERS.map((tier, i) => ({
361
+ x: xScale([d.baseTps, d.t0Tps, d.t1Tps][i]),
362
+ y: cy + TIER_Y_OFFSET[tier] * bandH,
363
+ })),
364
+ };
365
+ });
366
  gRoot.selectAll('.conn-line').data(connData, d => d.model).join('path')
367
  .attr('class', 'conn-line')
368
  .attr('d', d => lineGen(d.points))
 
370
  .attr('stroke', d => familyColor(d.family))
371
  .attr('stroke-width', 1.5).attr('opacity', 0.35);
372
 
373
+ // Dots: 3 per model with vertical stagger
374
  const dots = [];
375
  data.forEach(d => {
376
+ const cy = yScale(d.model) + bandH / 2;
377
+ const vals = [d.baseTps, d.t0Tps, d.t1Tps];
378
+ TIERS.forEach((tier, i) => {
379
+ dots.push({ ...d, tier, val: vals[i], cx: xScale(vals[i]), cy: cy + TIER_Y_OFFSET[tier] * bandH });
380
  });
381
  });
382
 
383
+ const dotSel = gRoot.selectAll('.dot').data(dots, d => d.model + '-' + d.tier).join('path')
384
  .attr('class', 'dot')
385
  .attr('d', d => shapeGenerators[d.tier].size(SHAPE_SIZE)())
386
  .attr('transform', d => `translate(${d.cx},${d.cy})`)
387
  .attr('fill', d => familyColor(d.family))
388
  .attr('stroke', 'none')
389
+ .attr('opacity', 0.9);
390
+ attachTipEvents(dotSel, () => 0.9);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  }
392
 
393
+ function renderSpeedup(data, xScale, yScale, bandH) {
 
 
 
 
 
394
  const barH = bandH * 0.38;
395
  const barData = [];
396
  data.forEach(d => {
 
400
  });
401
 
402
  const oneX = xScale(1.0);
403
+ const barSel = gRoot.selectAll('.speedup-bar').data(barData, d => d.model + '-' + d.tier).join('rect')
404
  .attr('class', 'speedup-bar')
405
  .attr('x', d => d.val >= 1.0 ? oneX : xScale(d.val))
406
  .attr('y', d => d.y)
 
410
  .attr('fill', d => familyColor(d.family))
411
  .attr('opacity', d => d.tier === 'Tier 0' ? 0.9 : 0.55)
412
  .attr('stroke', d => d.val < 1.0 ? '#e05252' : 'none')
413
+ .attr('stroke-width', d => d.val < 1.0 ? 1 : 0);
414
+ attachTipEvents(barSel, d => d.tier === 'Tier 0' ? 0.9 : 0.55);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  }
416
 
417
  function buildTooltip(d) {
 
427
  }
428
 
429
  // ── Controls ──
430
+ function makeSelect(id, label, options, initial, onChange) {
431
+ const group = document.createElement('div'); group.className = 'control-group';
432
+ const lbl = document.createElement('label'); lbl.textContent = label; lbl.setAttribute('for', id);
433
+ const sel = document.createElement('select'); sel.id = id;
434
+ options.forEach(([v, t]) => {
435
+ const o = document.createElement('option'); o.value = v; o.textContent = t; sel.appendChild(o);
436
+ });
437
+ sel.value = initial;
438
+ sel.addEventListener('change', () => onChange(sel.value));
439
+ group.appendChild(lbl); group.appendChild(sel);
440
+ return group;
441
+ }
 
 
 
 
 
 
 
 
 
 
 
442
 
443
+ const controls = document.createElement('div'); controls.className = 'controls';
444
+ controls.appendChild(makeSelect('metric-sel-optsweep', 'Metric',
445
+ [['throughput', 'Throughput'], ['speedup', 'Speedup']], state.metric,
446
+ v => { state.metric = v; render(); }));
447
+ controls.appendChild(makeSelect('sort-sel-optsweep', 'Sort',
448
+ [['speedup', 'By Best Speedup'], ['baseline', 'By Baseline Throughput'], ['family', 'By Model Family']], state.sort,
449
+ v => { state.sort = v; render(); }));
450
  container.appendChild(controls);
451
 
452
  // ── Legend ──
453
  const legend = document.createElement('div'); legend.className = 'legend';
 
 
454
  const tierTitle = document.createElement('div'); tierTitle.className = 'legend-title'; tierTitle.textContent = 'Legend';
455
  legend.appendChild(tierTitle);
456
+
457
+ // Tier shapes
458
  const tierSection = document.createElement('div'); tierSection.className = 'legend-section';
459
+ const svgNS = 'http://www.w3.org/2000/svg';
460
  TIERS.forEach(tier => {
461
  const item = document.createElement('span'); item.className = 'item';
 
 
462
  const shapeSvg = document.createElementNS(svgNS, 'svg');
463
  shapeSvg.setAttribute('width', '14'); shapeSvg.setAttribute('height', '14');
464
+ shapeSvg.setAttribute('viewBox', '-8 -8 16 16'); shapeSvg.style.display = 'block';
 
465
  const path = document.createElementNS(svgNS, 'path');
466
  path.setAttribute('d', shapeGenerators[tier].size(SHAPE_SIZE)());
467
  path.setAttribute('fill', 'var(--text-color)');
 
472
  });
473
  legend.appendChild(tierSection);
474
 
475
+ // Family colors
476
  const famSection = document.createElement('div'); famSection.className = 'legend-section'; famSection.style.marginTop = '4px';
477
  FAMILIES.forEach((fam, i) => {
478
  const item = document.createElement('span'); item.className = 'item';