dacorvo HF Staff commited on
Commit
8b2d95e
·
verified ·
1 Parent(s): d3e420f

Say why a listed model was not tested

Browse files
Files changed (2) hide show
  1. app.js +59 -25
  2. index.html +4 -3
app.js CHANGED
@@ -12,6 +12,9 @@ const ORDERS = ["selected", "trending", "downloads"];
12
  const HUB_SORTS = { trending: "trendingScore", downloads: "downloads" };
13
  // How many of the Hub's top models a trending or downloads list shows.
14
  const TOP = 100;
 
 
 
15
  const COLUMNS = ["model_id", "tp", "result", "wall (s)", "Memory (GB)", "notes"];
16
 
17
  // An element with attributes and children. Strings become text nodes, never HTML.
@@ -41,19 +44,37 @@ function cached(key, load) {
41
  return cache.get(key);
42
  }
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  // The dataset's files at its latest commit: one per (neuronx-cc, library version)
45
- // pair, and one selection per library.
46
  async function loadIndex() {
47
  const info = await (await fetchOk(`${HUB}/api/datasets/${DATASET}`)).json();
48
  const pairs = [];
49
- const selections = new Map();
50
  for (const { rfilename: path } of info.siblings) {
51
  const pair = path.match(/^data\/neuronxcc-([^/]+)\/(.+)-([^-/]+)\.parquet$/);
52
  if (pair) pairs.push({ sdk: pair[1], library: pair[2], version: pair[3], path });
53
- const selection = path.match(/^selection\/(.+)\.csv$/);
54
- if (selection) selections.set(selection[1], path);
55
  }
56
- return { sha: info.sha, pairs, selections };
57
  }
58
 
59
  const index = await loadIndex().catch((e) => e);
@@ -63,11 +84,12 @@ const loadRuns = (path) => cached(path, async () =>
63
  parquetReadObjects({ file: await (await fetchOk(fileUrl(path))).arrayBuffer() }));
64
 
65
  // Each model's position in a selection, best-ranked first.
66
- const loadSelection = (path) => cached(path, async () => {
67
- const [header, ...lines] = (await (await fetchOk(fileUrl(path))).text()).trim().split("\n");
68
- const column = header.split(",").indexOf("model_id");
69
- return new Map(lines.map((line, rank) => [line.split(",")[column], rank]));
70
- });
 
71
 
72
  // A model's task on the Hub, or "unknown" if the Hub no longer has it.
73
  const loadTask = (id) => cached(`task:${id}`, async () => {
@@ -78,14 +100,15 @@ const loadTask = (id) => cached(`task:${id}`, async () => {
78
  return (await response.json()).pipeline_tag ?? "unknown";
79
  }).catch(() => "unknown");
80
 
81
- // The Hub's top models of a library and task, as its model list shows them.
 
82
  const loadTop = (library, task, order) => cached(`top:${library}:${task}:${order}`, async () => {
83
  const sort = HUB_SORTS[order];
84
- const query = new URLSearchParams({
85
- library, pipeline_tag: task, sort, direction: "-1", limit: TOP, "expand[]": sort,
86
- });
87
  const models = await (await fetchOk(`${HUB}/api/models?${query}`)).json();
88
- return models.map((model) => ({ id: model.id, score: model[sort] ?? 0 }));
89
  });
90
 
91
  // Newest first. A version that is not a release, such as a git commit pin,
@@ -154,12 +177,13 @@ async function renderPanel() {
154
  tasks,
155
  results);
156
 
157
- let runs, rank, taskOf;
158
  try {
159
- const selection = index.selections.get(active);
160
- [runs, rank] = await Promise.all([
161
  loadRuns(pairs.find((p) => p.sdk === s.sdk && p.version === s.version).path),
162
  selection ? loadSelection(selection) : new Map(),
 
163
  ]);
164
  taskOf = new Map(await Promise.all(runs.map(async (run) => [run.model_id, await loadTask(run.model_id)])));
165
  } catch (e) {
@@ -178,13 +202,23 @@ async function renderPanel() {
178
  if (s.order === "selected") {
179
  return runs.filter((run) => rank.has(run.model_id) && ticked.includes(taskOf.get(run.model_id)))
180
  .sort((a, b) => rank.get(a.model_id) - rank.get(b.model_id) || a.tp - b.tp)
181
- .map((run) => [run.model_id, run]);
182
  }
183
  const lists = await Promise.all(ticked.map((t) => loadTop(active, t, s.order)));
184
  return [...new Map(lists.flat().map((model) => [model.id, model])).values()]
185
  .sort((a, b) => b.score - a.score || a.id.localeCompare(b.id))
186
  .slice(0, TOP)
187
- .flatMap(({ id }) => (runsOf.get(id) ?? [null]).map((run) => [id, run]));
 
 
 
 
 
 
 
 
 
 
188
  }
189
 
190
  const renderResults = async () => {
@@ -204,7 +238,7 @@ async function renderPanel() {
204
  : "The Hub lists no model for the ticked tasks."));
205
  return;
206
  }
207
- const tested = shown.map(([, run]) => run).filter(Boolean);
208
  const counts = new Map();
209
  for (const run of tested) counts.set(run.result, (counts.get(run.result) ?? 0) + 1);
210
  const failures = [...counts].filter(([result]) => result !== "PASS").sort(([a], [b]) => a.localeCompare(b));
@@ -217,20 +251,20 @@ async function renderPanel() {
217
  : `None of these ${untested} models was tested on this pair.`),
218
  h("div", { class: "scroll" }, h("table", {},
219
  h("thead", {}, h("tr", {}, COLUMNS.map((c) => h("th", {}, c)))),
220
- h("tbody", {}, shown.map(([id, run]) => run ? h("tr", {
221
  tabindex: 0,
222
  onclick: () => showRun(run),
223
  onkeydown: (e) => { if (e.key === "Enter") showRun(run); },
224
  },
225
- h("td", {}, h("code", {}, id)),
226
  h("td", { class: "num" }, run.tp),
227
  h("td", { class: "result" }, badge(run.result)),
228
  h("td", { class: "num" }, run.wall_s ?? ""),
229
  h("td", { class: "num" }, memory(run)),
230
  h("td", { class: "notes" }, run.notes ?? ""))
231
  : h("tr", { class: "untested" },
232
- h("td", {}, h("code", {}, id)), h("td"), h("td", { class: "result" }, "not tested"),
233
- h("td"), h("td"), h("td")))))));
234
  };
235
  tasks.append(...taskList.map((t) => h("label", {},
236
  h("input", {
 
12
  const HUB_SORTS = { trending: "trendingScore", downloads: "downloads" };
13
  // How many of the Hub's top models a trending or downloads list shows.
14
  const TOP = 100;
15
+ // The test instance: 4 Trainium2 devices of 96 GiB of HBM each.
16
+ const DEVICES = 4;
17
+ const DEVICE_HBM = 96 * 1024 ** 3;
18
  const COLUMNS = ["model_id", "tp", "result", "wall (s)", "Memory (GB)", "notes"];
19
 
20
  // An element with attributes and children. Strings become text nodes, never HTML.
 
44
  return cache.get(key);
45
  }
46
 
47
+ // The rows of a CSV file as objects keyed by its header. Fields may be quoted.
48
+ function parseCsv(text) {
49
+ const rows = [[]];
50
+ let field = "";
51
+ let quoted = false;
52
+ for (let i = 0; i < text.length; i++) {
53
+ const c = text[i];
54
+ if (quoted && c === '"' && text[i + 1] === '"') field += text[i++];
55
+ else if (c === '"') quoted = !quoted;
56
+ else if (quoted || (c !== "," && c !== "\n" && c !== "\r")) field += c;
57
+ else if (c === ",") { rows.at(-1).push(field); field = ""; }
58
+ else if (c === "\n") { rows.at(-1).push(field); field = ""; rows.push([]); }
59
+ }
60
+ if (field || rows.at(-1).length) rows.at(-1).push(field);
61
+ const [header, ...lines] = rows.filter((row) => row.length);
62
+ return lines.map((line) => Object.fromEntries(header.map((key, i) => [key, line[i]])));
63
+ }
64
+
65
  // The dataset's files at its latest commit: one per (neuronx-cc, library version)
66
+ // pair, and the selection and excluded list of each library.
67
  async function loadIndex() {
68
  const info = await (await fetchOk(`${HUB}/api/datasets/${DATASET}`)).json();
69
  const pairs = [];
70
+ const lists = { selection: new Map(), excluded: new Map() };
71
  for (const { rfilename: path } of info.siblings) {
72
  const pair = path.match(/^data\/neuronxcc-([^/]+)\/(.+)-([^-/]+)\.parquet$/);
73
  if (pair) pairs.push({ sdk: pair[1], library: pair[2], version: pair[3], path });
74
+ const list = path.match(/^(selection|excluded)\/(.+)\.csv$/);
75
+ if (list) lists[list[1]].set(list[2], path);
76
  }
77
+ return { sha: info.sha, pairs, ...lists };
78
  }
79
 
80
  const index = await loadIndex().catch((e) => e);
 
84
  parquetReadObjects({ file: await (await fetchOk(fileUrl(path))).arrayBuffer() }));
85
 
86
  // Each model's position in a selection, best-ranked first.
87
+ const loadSelection = (path) => cached(path, async () =>
88
+ new Map(parseCsv(await (await fetchOk(fileUrl(path))).text()).map((row, rank) => [row.model_id, rank])));
89
+
90
+ // Each left-out model's row of an excluded list.
91
+ const loadExcluded = (path) => cached(path, async () =>
92
+ new Map(parseCsv(await (await fetchOk(fileUrl(path))).text()).map((row) => [row.model_id, row])));
93
 
94
  // A model's task on the Hub, or "unknown" if the Hub no longer has it.
95
  const loadTask = (id) => cached(`task:${id}`, async () => {
 
100
  return (await response.json()).pipeline_tag ?? "unknown";
101
  }).catch(() => "unknown");
102
 
103
+ // The Hub's top models of a library and task, as its model list shows them,
104
+ // with their parameter count when the Hub knows it.
105
  const loadTop = (library, task, order) => cached(`top:${library}:${task}:${order}`, async () => {
106
  const sort = HUB_SORTS[order];
107
+ const query = new URLSearchParams({ library, pipeline_tag: task, sort, direction: "-1", limit: TOP });
108
+ query.append("expand[]", sort);
109
+ query.append("expand[]", "safetensors");
110
  const models = await (await fetchOk(`${HUB}/api/models?${query}`)).json();
111
+ return models.map((model) => ({ id: model.id, score: model[sort] ?? 0, params: model.safetensors?.total }));
112
  });
113
 
114
  // Newest first. A version that is not a release, such as a git commit pin,
 
177
  tasks,
178
  results);
179
 
180
+ let runs, rank, exclusions, taskOf;
181
  try {
182
+ const [selection, excluded] = [index.selection.get(active), index.excluded.get(active)];
183
+ [runs, rank, exclusions] = await Promise.all([
184
  loadRuns(pairs.find((p) => p.sdk === s.sdk && p.version === s.version).path),
185
  selection ? loadSelection(selection) : new Map(),
186
+ excluded ? loadExcluded(excluded) : new Map(),
187
  ]);
188
  taskOf = new Map(await Promise.all(runs.map(async (run) => [run.model_id, await loadTask(run.model_id)])));
189
  } catch (e) {
 
202
  if (s.order === "selected") {
203
  return runs.filter((run) => rank.has(run.model_id) && ticked.includes(taskOf.get(run.model_id)))
204
  .sort((a, b) => rank.get(a.model_id) - rank.get(b.model_id) || a.tp - b.tp)
205
+ .map((run) => ({ id: run.model_id, run }));
206
  }
207
  const lists = await Promise.all(ticked.map((t) => loadTop(active, t, s.order)));
208
  return [...new Map(lists.flat().map((model) => [model.id, model])).values()]
209
  .sort((a, b) => b.score - a.score || a.id.localeCompare(b.id))
210
  .slice(0, TOP)
211
+ .flatMap((model) => (runsOf.get(model.id) ?? [null]).map((run) => ({ ...model, run })));
212
+ }
213
+
214
+ // Why a model was not tested: weights larger than the test instance's HBM, or
215
+ // the reason the selection left it out, with the value its rule found.
216
+ function untestedReason({ id, params }) {
217
+ if (params * 2 > DEVICES * DEVICE_HBM) return `Requires more than ${DEVICES} Trainium2 devices`;
218
+ const exclusion = exclusions.get(id);
219
+ if (!exclusion) return "";
220
+ return exclusion.matched_pattern.includes("=") ? `${exclusion.reason} (${exclusion.matched_pattern})`
221
+ : exclusion.reason;
222
  }
223
 
224
  const renderResults = async () => {
 
238
  : "The Hub lists no model for the ticked tasks."));
239
  return;
240
  }
241
+ const tested = shown.map((entry) => entry.run).filter(Boolean);
242
  const counts = new Map();
243
  for (const run of tested) counts.set(run.result, (counts.get(run.result) ?? 0) + 1);
244
  const failures = [...counts].filter(([result]) => result !== "PASS").sort(([a], [b]) => a.localeCompare(b));
 
251
  : `None of these ${untested} models was tested on this pair.`),
252
  h("div", { class: "scroll" }, h("table", {},
253
  h("thead", {}, h("tr", {}, COLUMNS.map((c) => h("th", {}, c)))),
254
+ h("tbody", {}, shown.map(({ run, ...model }) => run ? h("tr", {
255
  tabindex: 0,
256
  onclick: () => showRun(run),
257
  onkeydown: (e) => { if (e.key === "Enter") showRun(run); },
258
  },
259
+ h("td", {}, h("code", {}, model.id)),
260
  h("td", { class: "num" }, run.tp),
261
  h("td", { class: "result" }, badge(run.result)),
262
  h("td", { class: "num" }, run.wall_s ?? ""),
263
  h("td", { class: "num" }, memory(run)),
264
  h("td", { class: "notes" }, run.notes ?? ""))
265
  : h("tr", { class: "untested" },
266
+ h("td", {}, h("code", {}, model.id)), h("td"), h("td", { class: "result" }, "not tested"),
267
+ h("td"), h("td"), h("td", { class: "notes" }, untestedReason(model))))))));
268
  };
269
  tasks.append(...taskList.map((t) => h("label", {},
270
  h("input", {
index.html CHANGED
@@ -105,9 +105,10 @@ summary { cursor: pointer; color: var(--accent); margin: 12px 0 4px; }
105
  <h1>torch-neuronx model support</h1>
106
  <p class="intro">
107
  Which Hugging Face Hub models run unmodified on AWS Trainium through the native PyTorch backend
108
- <a href="https://github.com/aws-neuron/torch-neuronx">torch-neuronx</a>. Select a model to see how
109
- to run it. The <a href="https://huggingface.co/datasets/aws-neuron/torch-neuronx-model-support">dataset
110
- card</a> describes each test and what each result means.
 
111
  </p>
112
  <nav id="tabs" role="tablist"></nav>
113
  <main id="panel"><p class="status">Loading…</p></main>
 
105
  <h1>torch-neuronx model support</h1>
106
  <p class="intro">
107
  Which Hugging Face Hub models run unmodified on AWS Trainium through the native PyTorch backend
108
+ <a href="https://github.com/aws-neuron/torch-neuronx">torch-neuronx</a>. For now, only models that
109
+ fit on 4 Trainium2 devices (TP=16) are tested. Select a model to see how to run it. The
110
+ <a href="https://huggingface.co/datasets/aws-neuron/torch-neuronx-model-support">dataset card</a>
111
+ describes each test and what each result means.
112
  </p>
113
  <nav id="tabs" role="tablist"></nav>
114
  <main id="panel"><p class="status">Loading…</p></main>