Skip to content

Commit 7efa2c2

Browse files
gguf-viewer: contributors ggml-org#6715
Added improvements based on their feedback and ideas
1 parent e819866 commit 7efa2c2

File tree

7 files changed

+101
-54
lines changed

7 files changed

+101
-54
lines changed

tools/gguf-viewer/public/architecture.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,28 @@
577577
note.textContent = options.note;
578578
li.appendChild(note);
579579
}
580+
if (tensor.name) {
581+
const actions = document.createElement("div");
582+
actions.className = "architecture-tensor__actions";
583+
584+
const encodedName = encodeURIComponent(tensor.name);
585+
586+
const heatmapButton = document.createElement("button");
587+
heatmapButton.type = "button";
588+
heatmapButton.dataset.action = "heatmap";
589+
heatmapButton.dataset.name = encodedName;
590+
heatmapButton.textContent = "Heatmap";
591+
actions.appendChild(heatmapButton);
592+
593+
const statisticsButton = document.createElement("button");
594+
statisticsButton.type = "button";
595+
statisticsButton.dataset.action = "statistics";
596+
statisticsButton.dataset.name = encodedName;
597+
statisticsButton.textContent = "Statistics";
598+
actions.appendChild(statisticsButton);
599+
600+
li.appendChild(actions);
601+
}
580602
return li;
581603
}
582604

@@ -863,7 +885,6 @@
863885
currentPosition = position;
864886
const blockIndex = blockIndices[position];
865887
select.value = String(blockIndex);
866-
navSummary.textContent = `Showing block ${blockIndex} (${position + 1} of ${blockIndices.length})`;
867888
prevButton.disabled = position === 0;
868889
nextButton.disabled = position === blockIndices.length - 1;
869890

@@ -992,7 +1013,7 @@
9921013
displayName: "output.weight",
9931014
variant: "info",
9941015
badgeLabel: "info",
995-
note: "No dedicated tensor exported; logits projection is fused or quantized in the GGUF payload.",
1016+
note: "Output projection not found. Logits are likely computed via tied embeddings (token_embd.weight) or integrated into the final transformer block.",
9961017
}),
9971018
);
9981019
}

tools/gguf-viewer/public/common.js

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const heatmapTensorSelect = document.getElementById("heatmap-tensor-select");
2626
const heatmapSliceInput = document.getElementById("heatmap-slice-input");
2727
const heatmapMinInput = document.getElementById("heatmap-min-input");
2828
const heatmapMaxInput = document.getElementById("heatmap-max-input");
29-
const heatmapAutoButton = document.getElementById("heatmap-auto-button");
29+
const heatmapSliceButton = document.getElementById("heatmap-slice-button");
3030
const heatmapP1Button = document.getElementById("heatmap-p1-button");
3131
const heatmapP5Button = document.getElementById("heatmap-p5-button");
3232
const heatmapP10Button = document.getElementById("heatmap-p10-button");
@@ -498,7 +498,7 @@ const heatmapControlElements = [
498498
heatmapSliceInput,
499499
heatmapMinInput,
500500
heatmapMaxInput,
501-
heatmapAutoButton,
501+
heatmapSliceButton,
502502
heatmapStepInput,
503503
heatmapTightenButton,
504504
heatmapWidenButton,
@@ -563,10 +563,10 @@ const heatmapState = {
563563
windowX: 0,
564564
windowY: 0,
565565
slice: 0,
566-
autoMin: undefined,
567-
autoMax: undefined,
568-
sliceMinBound: undefined,
569-
sliceMaxBound: undefined,
566+
viewMin: undefined,
567+
viewMax: undefined,
568+
sliceMin: undefined,
569+
sliceMax: undefined,
570570
scaleMin: -1,
571571
scaleMax: 1,
572572
scaleInitialized: false,
@@ -613,8 +613,8 @@ const heatmapDragState = {
613613
syncPageFromLocation();
614614

615615
function getSliceBounds() {
616-
let minBound = Number.isFinite(heatmapState.sliceMinBound) ? heatmapState.sliceMinBound : undefined;
617-
let maxBound = Number.isFinite(heatmapState.sliceMaxBound) ? heatmapState.sliceMaxBound : undefined;
616+
let minBound = Number.isFinite(heatmapState.sliceMin) ? heatmapState.sliceMin : undefined;
617+
let maxBound = Number.isFinite(heatmapState.sliceMax) ? heatmapState.sliceMax : undefined;
618618

619619
if (minBound !== undefined && maxBound !== undefined && minBound > maxBound) {
620620
const tmp = minBound;
@@ -676,7 +676,10 @@ function sanitizeScale(minValue, maxValue) {
676676
min = Number.isFinite(heatmapState.scaleMin) ? heatmapState.scaleMin : undefined;
677677
}
678678
if (!Number.isFinite(min)) {
679-
min = Number.isFinite(heatmapState.autoMin) ? heatmapState.autoMin : undefined;
679+
min = Number.isFinite(heatmapState.viewMin) ? heatmapState.viewMin : undefined;
680+
}
681+
if (!Number.isFinite(min)) {
682+
min = Number.isFinite(heatmapState.sliceMin) ? heatmapState.sliceMin : undefined;
680683
}
681684
if (!Number.isFinite(min)) {
682685
min = minBound;
@@ -690,7 +693,10 @@ function sanitizeScale(minValue, maxValue) {
690693
max = Number.isFinite(heatmapState.scaleMax) ? heatmapState.scaleMax : undefined;
691694
}
692695
if (!Number.isFinite(max)) {
693-
max = Number.isFinite(heatmapState.autoMax) ? heatmapState.autoMax : undefined;
696+
max = Number.isFinite(heatmapState.viewMax) ? heatmapState.viewMax : undefined;
697+
}
698+
if (!Number.isFinite(max)) {
699+
max = Number.isFinite(heatmapState.sliceMax) ? heatmapState.sliceMax : undefined;
694700
}
695701
if (!Number.isFinite(max)) {
696702
max = maxBound;
@@ -920,9 +926,9 @@ function syncHeatmapControls() {
920926
heatmapMaxInput.disabled = !ready;
921927
}
922928

923-
if (heatmapAutoButton) {
924-
const hasAuto = Number.isFinite(heatmapState.autoMin) && Number.isFinite(heatmapState.autoMax);
925-
heatmapAutoButton.disabled = !ready || !hasAuto;
929+
if (heatmapSliceButton) {
930+
const hasSliceScale = Number.isFinite(heatmapState.sliceMin) && Number.isFinite(heatmapState.sliceMax);
931+
heatmapSliceButton.disabled = !ready || !hasSliceScale;
926932
}
927933

928934
const hasPercentiles = ready && heatmapState.valid > 0;

tools/gguf-viewer/public/heatmap.js

Lines changed: 38 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -160,10 +160,10 @@ function resetHeatmap(message = HEATMAP_DEFAULT_STREAM_MESSAGE) {
160160
heatmapState.windowX = 0;
161161
heatmapState.windowY = 0;
162162
heatmapState.slice = 0;
163-
heatmapState.autoMin = undefined;
164-
heatmapState.autoMax = undefined;
165-
heatmapState.sliceMinBound = undefined;
166-
heatmapState.sliceMaxBound = undefined;
163+
heatmapState.viewMin = undefined;
164+
heatmapState.viewMax = undefined;
165+
heatmapState.sliceMin = undefined;
166+
heatmapState.sliceMax = undefined;
167167
heatmapState.scaleMin = -1;
168168
heatmapState.scaleMax = 1;
169169
heatmapState.scaleInitialized = false;
@@ -213,12 +213,12 @@ function layoutFromTensor(tensor) {
213213

214214
if (!Number.isFinite(width) || width <= 0) {
215215
if (Array.isArray(tensor.shape) && tensor.shape.length > 0) {
216-
width = tensor.shape[tensor.shape.length - 1];
216+
width = Number(tensor.shape[0]);
217217
}
218218
}
219219
if (!Number.isFinite(height) || height <= 0) {
220220
if (Array.isArray(tensor.shape) && tensor.shape.length > 1) {
221-
height = tensor.shape[tensor.shape.length - 2];
221+
height = Number(tensor.shape[1]);
222222
} else {
223223
height = 1;
224224
}
@@ -274,7 +274,7 @@ function updateHeatmapHeader() {
274274
const coverageTotal = heatmapState.viewWidth * heatmapState.viewHeight;
275275
const coverageText = coverageTotal > 0 ? `${heatmapState.valid}/${coverageTotal}` : "0/0";
276276
const statusParts = [
277-
`Layout ${layoutHeight} x ${layoutWidth}`,
277+
`Layout ${layoutWidth} x ${layoutHeight}`,
278278
`X ${x0} - ${x1}`,
279279
`Y ${y0} - ${y1}`,
280280
`Cells ${coverageText}`,
@@ -603,13 +603,13 @@ async function fetchHeatmapWindow() {
603603
void fetchHistogram();
604604
}
605605

606-
heatmapState.autoMin = typeof data.min === "number" ? data.min : undefined;
607-
heatmapState.autoMax = typeof data.max === "number" ? data.max : undefined;
608-
heatmapState.sliceMinBound = typeof data.sliceMin === "number" ? data.sliceMin : undefined;
609-
heatmapState.sliceMaxBound = typeof data.sliceMax === "number" ? data.sliceMax : undefined;
606+
heatmapState.viewMin = typeof data.min === "number" ? data.min : undefined;
607+
heatmapState.viewMax = typeof data.max === "number" ? data.max : undefined;
608+
heatmapState.sliceMin = typeof data.sliceMin === "number" ? data.sliceMin : undefined;
609+
heatmapState.sliceMax = typeof data.sliceMax === "number" ? data.sliceMax : undefined;
610610

611611
if (!heatmapState.scaleInitialized) {
612-
setHeatmapScale(heatmapState.autoMin, heatmapState.autoMax, { reapply: false, sync: false });
612+
setHeatmapScale(heatmapState.viewMin, heatmapState.viewMax, { reapply: false, sync: false });
613613
} else {
614614
const clamped = sanitizeScale(heatmapState.scaleMin, heatmapState.scaleMax);
615615
heatmapState.scaleMin = clamped.min;
@@ -770,16 +770,32 @@ function openHeatmap(nameEncoded, options = {}) {
770770
}
771771

772772

773+
function handleTensorAction(target) {
774+
if (!target || target.tagName !== "BUTTON" || !target.dataset.name) {
775+
return;
776+
}
777+
778+
const action = target.dataset.action || "heatmap";
779+
const destination = action === "statistics" ? "statistics" : "heatmap";
780+
const current = normalizePageId(window.location.hash.slice(1));
781+
if (current !== destination) {
782+
window.location.hash = destination;
783+
}
784+
openHeatmap(target.dataset.name);
785+
}
786+
773787
tensorBody.addEventListener("click", (event) => {
774788
const target = event.target;
775-
if (target.tagName === "BUTTON" && target.dataset.name) {
776-
if (normalizePageId(window.location.hash.slice(1)) !== "heatmap") {
777-
window.location.hash = "heatmap";
778-
}
779-
openHeatmap(target.dataset.name);
780-
}
789+
handleTensorAction(target);
781790
});
782791

792+
if (architectureContent) {
793+
architectureContent.addEventListener("click", (event) => {
794+
const target = event.target;
795+
handleTensorAction(target);
796+
});
797+
}
798+
783799
function commitSliceInput() {
784800
if (!heatmapSliceInput || !heatmapState.tensor) {
785801
return;
@@ -867,15 +883,15 @@ if (heatmapMaxInput) {
867883
});
868884
}
869885

870-
if (heatmapAutoButton) {
871-
heatmapAutoButton.addEventListener("click", () => {
886+
if (heatmapSliceButton) {
887+
heatmapSliceButton.addEventListener("click", () => {
872888
if (!heatmapState.tensor) {
873889
return;
874890
}
875-
if (!Number.isFinite(heatmapState.autoMin) || !Number.isFinite(heatmapState.autoMax)) {
891+
if (!Number.isFinite(heatmapState.sliceMin) || !Number.isFinite(heatmapState.sliceMax)) {
876892
return;
877893
}
878-
setHeatmapScale(heatmapState.autoMin, heatmapState.autoMax);
894+
setHeatmapScale(heatmapState.sliceMin, heatmapState.sliceMax);
879895
});
880896
}
881897

tools/gguf-viewer/public/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ <h2 id="heatmap-title" class="model-title">
120120
<span>Max</span>
121121
<input type="number" id="heatmap-max-input" value="1" step="0.0001">
122122
</label>
123-
<button id="heatmap-auto-button" type="button">SLICE</button>
123+
<button id="heatmap-slice-button" type="button">SLICE</button>
124124
<button id="heatmap-p1-button" type="button">P1</button>
125125
<button id="heatmap-p5-button" type="button">P5</button>
126126
<button id="heatmap-p10-button" type="button">P10</button>

tools/gguf-viewer/public/tensors.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,17 @@ function setTensorsMessage(message) {
99

1010
function renderTensorRow(tensor) {
1111
const tr = document.createElement("tr");
12+
const encodedName = encodeURIComponent(tensor.name);
1213
tr.innerHTML = `
1314
<td><code>${tensor.name}</code></td>
1415
<td>${tensor.type}</td>
1516
<td><code>${formatShape(tensor.shape)}</code></td>
1617
<td>${tensor.nElements}</td>
1718
<td>${tensor.fileOffset}</td>
18-
<td><button data-name="${encodeURIComponent(tensor.name)}">Heatmap</button></td>
19+
<td class="tensor-table-actions">
20+
<button type="button" data-action="heatmap" data-name="${encodedName}">Heatmap</button>
21+
<button type="button" data-action="statistics" data-name="${encodedName}">Statistics</button>
22+
</td>
1923
`;
2024
return tr;
2125
}

tools/gguf-viewer/public/viewer.css

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ h2 {
2929
font-size: 0.9rem;
3030
font-weight: 600;
3131
letter-spacing: 0.05em;
32-
text-transform: uppercase;
3332
color: #8ea2c8;
3433
}
3534
.tabs {
@@ -50,7 +49,6 @@ h2 {
5049
padding: 0.3rem 0.6rem;
5150
font-size: 0.8rem;
5251
letter-spacing: 0.05em;
53-
text-transform: uppercase;
5452
cursor: pointer;
5553
font-weight: 500;
5654
}
@@ -89,7 +87,6 @@ h2 {
8987
margin: 0;
9088
font-size: 0.7rem;
9189
letter-spacing: 0.08em;
92-
text-transform: uppercase;
9390
color: #8ea2c8;
9491
}
9592
.info-card p {
@@ -161,7 +158,6 @@ tbody tr:nth-of-type(2n) {
161158
thead th {
162159
font-size: 0.72rem;
163160
letter-spacing: 0.06em;
164-
text-transform: uppercase;
165161
color: #8ea2c8;
166162
}
167163
code {
@@ -176,7 +172,6 @@ button {
176172
color: inherit;
177173
padding: 0.3rem 0.65rem;
178174
font-size: 0.78rem;
179-
text-transform: uppercase;
180175
letter-spacing: 0.05em;
181176
cursor: pointer;
182177
}
@@ -190,6 +185,14 @@ button:disabled {
190185
background: #0f1625;
191186
opacity: 0.6;
192187
}
188+
.tensor-table-actions {
189+
display: flex;
190+
flex-wrap: wrap;
191+
gap: 0.3rem;
192+
}
193+
.tensor-table-actions button {
194+
flex: 0 0 auto;
195+
}
193196
.controls {
194197
display: flex;
195198
flex-wrap: wrap;
@@ -390,7 +393,6 @@ select:focus {
390393
color: #a8bee5;
391394
font-size: 0.68rem;
392395
letter-spacing: 0.06em;
393-
text-transform: uppercase;
394396
}
395397
.tokenizer-controls {
396398
align-items: center;
@@ -515,7 +517,6 @@ select:focus {
515517
margin: 0;
516518
font-size: 0.75rem;
517519
letter-spacing: 0.08em;
518-
text-transform: uppercase;
519520
color: #8ea2c8;
520521
}
521522
.architecture-block {
@@ -534,10 +535,6 @@ select:focus {
534535
flex-direction: column;
535536
gap: 0.35rem;
536537
}
537-
.architecture-block-nav__summary {
538-
font-size: 0.74rem;
539-
color: #9fb4d9;
540-
}
541538
.architecture-block-nav__controls {
542539
display: flex;
543540
align-items: center;
@@ -584,7 +581,6 @@ select:focus {
584581
.architecture-subgroup__title {
585582
font-size: 0.72rem;
586583
letter-spacing: 0.06em;
587-
text-transform: uppercase;
588584
color: #9fb4d9;
589585
}
590586
.architecture-tensor {
@@ -612,6 +608,11 @@ select:focus {
612608
.architecture-tensor__note {
613609
color: #b7c6e6;
614610
}
611+
.architecture-tensor__actions {
612+
display: flex;
613+
flex-wrap: wrap;
614+
gap: 0.3rem;
615+
}
615616
.architecture-tensor--mismatch {
616617
border-left-color: #f59f80;
617618
}
@@ -638,7 +639,6 @@ select:focus {
638639
color: #a8bee5;
639640
font-size: 0.65rem;
640641
letter-spacing: 0.06em;
641-
text-transform: uppercase;
642642
}
643643
.architecture-badge--mismatch {
644644
background: #c2413a;

tools/gguf-viewer/viewer.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -435,8 +435,8 @@ tensor_layout compute_tensor_layout(const std::vector<int64_t> & shape, size_t n
435435
layout.width = width;
436436
layout.height = height;
437437
} else if (shape.size() == 2) {
438-
layout.width = static_cast<size_t>(std::max<int64_t>(1, shape.back()));
439-
layout.height = static_cast<size_t>(std::max<int64_t>(1, shape.front()));
438+
layout.width = static_cast<size_t>(std::max<int64_t>(1, shape.front()));
439+
layout.height = static_cast<size_t>(std::max<int64_t>(1, shape.back()));
440440
} else {
441441
const size_t depth = static_cast<size_t>(std::max<int64_t>(1, shape.back()));
442442
size_t width = static_cast<size_t>(std::max<int64_t>(1, shape[shape.size() - 2]));

0 commit comments

Comments
 (0)