Skip to content

Commit

Permalink
feat: add 'Legend' to document symbols on screen
Browse files Browse the repository at this point in the history
Roughly, this is an attempt at "UX profiling": the more sentences and
more complex the concepts involved, the more urgent "refining" that
particular symbol is.

It's also trying to capture the implicit context that allows deriving
meaning from the current symbols, such as:

1. The behavior of an `OpLoad` (or other SPIR-V opcodes)
2. Anticipating whether the next operation will be sequential or
   parallel
3. The overall "purpose" of the FILL_IDX program
  • Loading branch information
sethp committed Aug 6, 2024
1 parent fca7b59 commit 6e3bc39
Show file tree
Hide file tree
Showing 2 changed files with 315 additions and 48 deletions.
343 changes: 295 additions & 48 deletions pages/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ const vis = {

<html>
<head>
<meta charset="utf-8" />
<title>gpgpu playground</title>
<script>
import { LITTLE_ENDIAN } from '../lib/binding';
Expand Down Expand Up @@ -242,7 +243,7 @@ const vis = {
for (const buf of Mod.Buffers) {
const figEl = document.createElement('figure');
const captionEl = document.createElement('figcaption');
captionEl.innerText = `Buffer '${buf.Name.deref()?.asString() ?? '<unnamed>'}' (${buf.Size} bytes):'`;
captionEl.innerText = `Buffer '${buf.Name.deref()?.asString() ?? '<unnamed>'}' (${buf.Size} bytes):`;
figEl.appendChild(captionEl);

const listEl = document.createElement('ol');
Expand Down Expand Up @@ -1490,53 +1491,299 @@ const vis = {
</defs>
</svg>
</shadow-root>
<table>
<caption>Legend</caption>
<colgroup>
<col span="2" />
<col />
</colgroup>
<thead>
<tr>
<th scope="colgroup" colspan="2">Symbol</th>
<th scope="col">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="2"
><svg height="30" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 3 4">
<title>orange box</title>
<rect width="4" height="3" fill="chocolate"></rect>
</svg></td
><td
>Core: an execution unit that has up to 8 associated lanes for executing program code against data. It has
a physical ID like 0, 1, 2, 3... etc. and each lane associated with it also has a physical id relative the
the core itself (so there will be as many lane 0s as there are cores). This toy example GPU has one core
with with four lanes.<span class="footnote">[1]</span></td
>
</tr>
<tr>
<td>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 110" height="60px">
<title>outlined box with the "flag" on top</title>
<defs>
<symbol id="handle" width="30" height="30" viewBox="0 0 25 30" preserveAspectRatio="none">
<polygon points="5 0,25 0,25 20,15 30,5 20"></polygon>
</symbol>
</defs>

<use href="#handle" style="fill-opacity: 0; stroke:black;" x="12"></use>
<rect x="0" y="30" height="80" width="60" style="fill-opacity: 0; stroke: black;"></rect>
</svg>
</td><td><pre>&gt;</pre> (in textarea)</td><td
>Program Counter (PC): a pointer into the program text associated with a core that indicates the next
operation which will progress. For the purposes of our model, each core only has one PC (per program<span
class="footnote">[2]</span
>). That choice is more or less what defines the SIMT model of computation: a core will <em>dispatch</em> as
many operations as it has (active) lanes at the same time, but those operations will <em>complete</em> independently<span
class="footnote">[3]</span
> as the computation .
</td>
</tr>
<tr>
<td colspan="2">Not Pictured</td>
<td
>a sense of overall progress against entire logical space (i.e. all {'{group id, work id, ...}'} coordinates);
this leaves the part of the "work mapping" entirely up to the reader, unless their problem space is sized 1:1
with a single core.</td
>
</tr>
<tr>
<td colspan="2"
><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26" height="30px">
<circle cx="13" cy="13" r="12" style="fill: white; stroke: black;"></circle>
<text x="13" y="13" dominant-baseline="middle" text-anchor="middle" style="font-size: smaller;">%x</text
></svg
></td
><td
>an operation that will produce results with ID %x; these results will have physical {'{core, lane}'} coordinates
as well as logical {'{group id, work id, ...}'} coordinates.</td
>
</tr>
<tr>
<td colspan="2">Not Pictured</td>
<td
>the "type" of the operation (more specifically: the set of architectural hazards that may delay
completion relative to dispatch), like "memory" or "not-memory"</td
>
</tr>
<tr>
<td
><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26" height="30px">
<circle cx="13" cy="13" r="12" style="fill: white; stroke: black;"></circle>
<text x="13" y="13" dominant-baseline="middle" text-anchor="middle" style="font-size: smaller;">st</text
></svg
></td
>
<td
><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26" height="30px">
<circle cx="13" cy="13" r="12" style="fill: white; stroke: black;"></circle>
<text x="13" y="13" dominant-baseline="middle" text-anchor="middle" style="font-size: smaller;"
>ret</text
></svg
></td
>
<td
>an operation that will not produce directly identified results, such as `OpStore` (which stores to
memory) or `OpReturn` (which signals the exit of a program <em>invocation</em>).</td
>
</tr>
<tr>
<td>◦</td><td>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" height="30px">
<title>dashed outlined box</title>
<rect
height="20"
width="20"
style="fill-opacity: 0; stroke: black; stroke-width: 1px; stroke-dasharray: 2px;"></rect>
</svg></td
><td
><em>dispatched</em> operation that will produce a single result. The operation "belongs" to a {
'{core, lane}'
} pair.
</td>
</tr>
<tr>
<td>•</td><td>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" height="30px">
<title>solid outlined box</title>
<rect height="20" width="20" style="fill-opacity: 0; stroke: black; stroke-width: 1px;"></rect>
</svg></td
><td><em>completed</em> operation that has produced a result.</td>
</tr>
<tr>
<td>•• ... <br />(in textarea)</td><td
><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 10" height="30px"
><text x="0" y="1ch" dy="0 0.4em" dx="0 -1ch" style="font-size: medium; font-family: monospace;"
>••</text
></svg
>(in SVG vis view)<td>two results that were computed <em>simultaneously</em>.</td>
</td>
<tr>
<td colspan="2"
><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 10" height="30px"
><text x="0" y="1ch" dy="0 0.4em" dx="0 -0.5ch" style="font-size: medium; font-family: monospace;"
>••</text
></svg
><td>two results that were computed <em>sequentially</em> (here, one tick apart).</td>
</td>
<tr>
<td colspan="2">Not Pictured</td>
<td
>This toy GPU's memory controller that only lets one operation through per tick (per core, probably?)
but has infinite bandwidth per operation</td
>
</tr>
<tr>
<td colspan="2">
<div class="buffers">
<figure style="margin-left: 0;">
<figcaption>Buffer 'a' (16 bytes):</figcaption><ol>
<li>3</li>
<li>4</li>
<li data-prev="17">5</li>
<li data-prev="10">6</li>
</ol>
</figure>
</div>
</td>
<td
>A view into the GPU's memory for the 16 bytes associated with buffer "a" by the `OpBufferTALVOS`
metadata opcode; formatted as an array of 4 elements (ideally: with the help of the associated SPIR-V
type; currently: that's just what you get) with each element identified by its index (offset) such as
`.[1] = ...` which indicates the value of the four bytes at element offset 1 (i.e. four bytes into the
memory range) interpreted as an unsigned 32-bit integer; here, the element with index 1 has value 4.
The view also tracks the most-recently held value, and displays that previous value when the memory
changed in the most recent interaction (either a tick or a step), as seen here in elements 2 and 3.
</td>
</tr>
<tr>
<td colspan="2">Not Pictured</td>
<td
>The type metadata also associated by way of the `OpBufferTALVOS` opcode that indicates the layout of
each element of a Buffer view</td
>
</tr>
<tr>
<td colspan="2">Not Pictured</td>
<td
>Memory safety, and especially bidirectional impact of parallel scalability on the same (i.e. the `if
(i &lt; N)` bit in most CUDA examples).</td
>
</tr>
<tr>
<td colspan="2">Not Pictured</td>
<td>Tracking uninitialized memory, visualizing incorrect access bounds.</td>
</tr>
<tr>
<td colspan="2">(the whole textarea)</td>
<td
>a program in SPIR-V (with Talvos-specific extensions) that will be interpreted by the virtual GPU</td
>
</tr>
<tr>
<td colspan="2"><pre>%x = OpXyz %1 %2 %bar</pre></td>
<td
>A SPIR-V operation (in text form) that will produce result with id %x by `Xyz`ing its arguments %1 %2
and %bar.</td
>
</tr>
<tr>
<td colspan="2">Not Pictured</td>
<td
>the whole SPIR-V spec<span class="footnote">[4]</span> (plus associated references such as the Vulkan
API<span class="footnote">[5]</span>) which describe what an OpXyz does, or why it needs to take
arguments.</td
>
</tr>
</tr>
</tr>
</tbody>
<tfoot>
<tr
><td colspan="3" rowspan="0">
Footnotes
<ol>
<li>
Strictly speaking, the GPU model has four cores with eight lanes each, but the view only currently
supports a single core with four lanes.
</li>
<li>
The many-live-programs-per-core will come up again in "parallelism" as a way the GPU implements
something akin to of "hyper-hyperthreading" to hide memory latencies.
</li>
<li>
Compare with the SIMD model found in almost every modern CPU; a SIMT operation may have <em>some</em> of
its effects visible sooner than others, even if the overall core's pipeline is stalled waiting on a portion
of the result set. That combined with explicit static memory tiering (as opposed to implicit dynamic cache
coherence) and with very deep pipelining (not yet in scope for this project) are three key details to keep
in mind while thinking about adapting problems to fit a GPU computational model.
</li>
<li>
<a href="https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html"
>https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html</a
>
</li>
<li>
e.g. <a
href="https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/GlobalInvocationId.html"
>https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/GlobalInvocationId.html</a
>
</li>
</ol>
</td></tr
>
</tfoot>
</table>
</div>

<script>
const svgEl = document.querySelector<SVGElement>('#talvos svg.vis0')!;

const phyEl = svgEl.querySelector<SVGTSpanElement>('tspan.physical')!;
const logEl = svgEl.querySelector<SVGTSpanElement>('tspan.logical')!;

const laneEls = svgEl.querySelectorAll<SVGElement>('.lane');
for (const el of laneEls) {
el.addEventListener('mouseenter', function () {
phyEl.textContent = this.dataset.phyCoords!;
logEl.textContent = this.dataset.logCoords ?? 'N/A';
});
el.addEventListener('mouseleave', function () {
phyEl.textContent = 'N/A';
logEl.textContent = 'N/A';
});

el.addEventListener('click', function () {
if (this.classList.contains('.disabled')) return;

this.classList.toggle('selected');
});
}

const ctrlEls = svgEl.querySelectorAll<SVGElement>('.ctrl');
for (const [i, el] of ctrlEls.entries()) {
el.addEventListener('mouseenter', function () {
// TODO don't assume lane width
phyEl.textContent = `{ ${i}, [${i * 8}-${i * 8 + 7}] }`;
});
el.addEventListener('mouseleave', function () {
phyEl.textContent = 'N/A';
});

el.addEventListener('click', function () {
// TODO tease apart "the scheduler decided" vs "the human decided"
const selectable = !!this.parentNode!.querySelector(
'.lane[data-state="active"], .lane[data-state="inactive"]'
);
if (!selectable) return;

const hasNoneSelected = !this.parentNode!.querySelector('.lane.selected');
this.parentNode!.querySelectorAll<SVGElement>('.lane').forEach((laneEl) => {
laneEl.classList.toggle('selected', hasNoneSelected);
});
});
}
</script>
</body>
</html>

<script>
const svgEl = document.querySelector<SVGElement>('#talvos svg.vis0')!;

const phyEl = svgEl.querySelector<SVGTSpanElement>('tspan.physical')!;
const logEl = svgEl.querySelector<SVGTSpanElement>('tspan.logical')!;

const laneEls = svgEl.querySelectorAll<SVGElement>('.lane');
for (const el of laneEls) {
el.addEventListener('mouseenter', function () {
phyEl.textContent = this.dataset.phyCoords!;
logEl.textContent = this.dataset.logCoords ?? 'N/A';
});
el.addEventListener('mouseleave', function () {
phyEl.textContent = 'N/A';
logEl.textContent = 'N/A';
});

el.addEventListener('click', function () {
if (this.classList.contains('.disabled')) return;

this.classList.toggle('selected');
});
}

const ctrlEls = svgEl.querySelectorAll<SVGElement>('.ctrl');
for (const [i, el] of ctrlEls.entries()) {
el.addEventListener('mouseenter', function () {
// TODO don't assume lane width
phyEl.textContent = `{ ${i}, [${i * 8}-${i * 8 + 7}] }`;
});
el.addEventListener('mouseleave', function () {
phyEl.textContent = 'N/A';
});

el.addEventListener('click', function () {
// TODO tease apart "the scheduler decided" vs "the human decided"
const selectable = !!this.parentNode!.querySelector('.lane[data-state="active"], .lane[data-state="inactive"]');
if (!selectable) return;

const hasNoneSelected = !this.parentNode!.querySelector('.lane.selected');
this.parentNode!.querySelectorAll<SVGElement>('.lane').forEach((laneEl) => {
laneEl.classList.toggle('selected', hasNoneSelected);
});
});
}
</script>
20 changes: 20 additions & 0 deletions styles/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -118,3 +118,23 @@ nav li:not(:last-child) a::after {
shadow-root:not(:defined) {
display: none;
}
table {
table-layout: fixed;
width: 100%;
border-collapse: collapse;
border: 1px solid;
}
thead th:nth-child(1) {
width: 20%;
}
thead th:nth-child(1) {
width: 20%;
}
thead th,
tbody td {
border: 1px solid;
}
span.footnote {
vertical-align: super;
font-size: smaller;
}

0 comments on commit 6e3bc39

Please sign in to comment.