File size: 7,118 Bytes
93ab527
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/**

 * The page: one repo id in, one honest breakdown out.

 *

 * Every rule lives in precision_map.js, which the parity gate holds to

 * the Python implementation. Nothing here decides anything — it fetches,

 * calls, and renders, so a rule can never be quietly different in the UI

 * from the one that was tested.

 */
import { FORMATS, RULES_MEASURED_ON, inspect } from './precision_map.js';
import { progress } from './progress.js';
import { ensureNextStepStyles, nextSteps, repoFromUrl } from './tools.js';

const $ = (id) => document.getElementById(id);

// A link from another tool carries the model over; arriving at an empty
// form is what makes five tools feel like five tools instead of one.
ensureNextStepStyles();
const carried = repoFromUrl();
if (carried) { $('repo').value = carried; }

const out = $('out');
const esc = (s) => String(s).replace(/[&<>"]/g, (c) =>
  ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]));

const GIB = 1024 ** 3;
const gib = (b) => (b / GIB >= 0.01 ? `${(b / GIB).toFixed(2)} GiB`
  : `${(b / (1024 ** 2)).toFixed(0)} MiB`);
const pct = (part, whole) => `${((part / whole) * 100).toFixed(1)}%`;

function renderFormats() {
  $('formats').innerHTML = Object.entries(FORMATS).map(([method, f]) =>
    `<tr>

       <td><code>${esc(method)}</code></td>

       <td>${esc(f.tool)}</td>

       <td>${f.producer ? `<code>${esc(f.producer.join('.').replace('config.', ''))}</code>`

    : '<span class="muted">records none</span>'}</td>

       <td class="small">${esc(f.verifiedOn)}</td>

     </tr>`).join('');
}

function card(cls, title, body) {
  return `<div class="card ${cls}"><h3>${title}</h3>${body}</div>`;
}

/** The stacked bar. Three categories, in the order they are explained. */
function bar(b, total) {
  const seg = (k, cls) => (b[k] > 0
    ? `<span class="seg ${cls}" style="width:${(b[k] / total) * 100}%"

         title="${esc(k)}${gib(b[k])}"></span>` : '');
  return `<div class="bar">${seg('quantized', 'q')}${seg('metadata', 'm')}${seg('full', 'f')}${seg('buffer', 'b')}</div>`;
}

function render(r) {
  const b = r.buckets;
  const t = r.totalBytes;

  const version = r.producerVersion
    ? `<code>${esc(r.producerVersion)}</code>`
    : '<span class="muted">no version recorded in the pack</span>';

  const meta = `<div class="meta">

      <span>repo <code>${esc(r.repo)}</code></span>

      <span>declared <code>${esc(r.method)}</code></span>

      <span>${r.shards} shard${r.shards === 1 ? '' : 's'}, ${r.tensorCount} tensors</span>

    </div>`;

  if (!r.knownFormat && r.method !== 'none') {
    return meta + card('check', `No convention on file for ${esc(r.method)}`,
      `<p>This reader has never been verified against a

       <code>${esc(r.method)}</code> pack, so the split below rests on dtypes

       alone and the metadata line may be undercounted. Treat it as an

       estimate, not a reading.</p>` + bar(b, t) + table(r));
  }

  if (!r.recognised) {
    return meta + card('problem', 'This layout is not understood',
      `<p>The pack declares <code>${esc(r.method)}</code>, which quantizes

       something — but no tensor here is in a packed dtype or under a packed

       name. Either the convention moved since ${esc(RULES_MEASURED_ON)}, or

       this pack stores its weights somewhere this cannot see.</p>

       <p>No breakdown is shown, on purpose. A number here would look like a

       measurement and be a guess.</p>`);
  }

  if (r.method === 'none') {
    return meta + card('clear', 'Not quantized',
      `<p>Every tensor is at full precision — ${esc(gib(b.full))} of it. No

       quantization method is declared and none is detectable from the

       dtypes.</p>` + tableFamilies(r, b.full));
  }

  const headline = `${pct(b.full, t)} of this pack is still full precision`;

  return meta + card('check', headline,
    `<p>Written by <strong>${esc(r.tool)}</strong>, ${version}.</p>`
    + bar(b, t)
    + table(r)
    + `<h4>What stayed whole</h4>`
    + tableFamilies(r, b.full)
    + (r.headQuantized
      ? `<p class="warn"><strong>The output head is quantized.</strong> That is

         a real choice with a real cost: measured on this repo's own weights,

         quantizing <code>lm_head</code> flips the model's chosen token on

         about a fifth of positions. It buys size; it is worth knowing you

         bought it.</p>`
      : `<p class="note">The output head was left at full precision — the

         usual choice, and the reason a "4-bit" pack is never 4-bit

         throughout.</p>`)
    + (r.unknownDtypes.length
      ? `<p class="warn">Unknown dtypes counted at 2 bytes:

         <code>${r.unknownDtypes.map(esc).join('</code>, <code>')}</code>.</p>`
      : ''));
}

function table(r) {
  const b = r.buckets;
  const t = r.totalBytes;
  const row = (label, key, why) => (b[key] > 0
    ? `<tr><td>${label}</td><td class="num">${gib(b[key])}</td>

         <td class="num">${pct(b[key], t)}</td><td class="small">${why}</td></tr>`
    : '');
  return `<div class="scroll"><table class="rows">

    ${row('Quantized payload', 'quantized', 'the weights that were compressed')}

    ${row('Quantization data', 'metadata', 'scales, zero points, group indices')}

    ${row('Full precision', 'full', 'never quantized, at source precision')}

    ${row('Index buffers', 'buffer', 'positions, masks, stored shapes')}

  </table></div>`;
}

function tableFamilies(r, fullTotal) {
  const rows = Object.entries(r.fullByFamily);
  if (!rows.length) return '';
  return `<div class="scroll"><table class="rows">${rows.map(([fam, size]) =>

    `<tr><td>${esc(fam)}</td><td class="num">${gib(size)}</td>

       <td class="num">${pct(size, fullTotal)}</td></tr>`).join('')}

  </table></div>`;
}

async function run(repo) {
  const go = $('go');
  go.disabled = true;
  // Indeterminate until the shard count is known — reading config.json
  // and the shard index says nothing about how much work follows.
  const pg = progress(out).start(`reading ${repo}`);
  try {
    const r = await inspect(repo, (done, total) => {
      if (done === 0) {
        pg.total(total).start(total === 1
          ? 'reading the safetensors header'
          : `reading ${total} safetensors headers`);
      } else {
        pg.step();
      }
    });
    pg.done();
    out.innerHTML = render(r) + nextSteps('precision', repo);
  } catch (e) {
    out.innerHTML = card('problem', 'Could not read the repo',
      `<p>${esc(e.message || e)}</p>

       <p>Gated and private repos cannot be read from the browser.</p>`);
  } finally {
    go.disabled = false;
  }
}

$('form').addEventListener('submit', (e) => {
  e.preventDefault();
  const v = $('repo').value.trim();
  if (v) run(v);
});
document.querySelectorAll('[data-ex]').forEach((btn) =>
  btn.addEventListener('click', () => {
    $('repo').value = btn.dataset.ex;
    run(btn.dataset.ex);
  }));

$('measured').textContent = RULES_MEASURED_ON;
renderFormats();