stevhliu HF Staff Claude Opus 5.5 commited on
Commit
3a22edb
·
1 Parent(s): 9b65d3b

Recommend methods by source: on the fly or from a checkpoint

Browse files

The receipt now lists a curated set of methods per precision, in
recommendation order, chosen by a new "quantized on the fly / from a
checkpoint" slot in the sentence. Adds NVFP4, GGUF, AutoRound and Quark;
drops HQQ, optimum-quanto, HIGGS and AQLM, which no list includes.

Also drops the ?/* qualifiers from the receipt and the focus outline on
the precision hit targets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

app/compare/page.tsx CHANGED
@@ -4,6 +4,7 @@ import CapabilityRow from "@/components/CapabilityRow";
4
  import {
5
  GOALS,
6
  GPUS,
 
7
  methodsFor,
8
  round1,
9
  } from "@/lib/data";
@@ -16,7 +17,8 @@ export default async function ComparePage({ searchParams }: PageProps<"/compare"
16
  const bits = Number(first(sp.bits, "4"));
17
  const gpu = GPUS.find((g) => g.id === first(sp.gpu, "a100-80gb")) ?? GPUS[0];
18
  const goal = GOALS.find((g) => g.id === first(sp.goal, "serve")) ?? GOALS[0];
19
- const methods = methodsFor(bits, gpu.backend, goal);
 
20
  // The calculator knows the footprint; it arrives as ?gb= rather than being
21
  // recomputed here, since the model is now whatever the user typed.
22
  const total = Number(first(sp.gb, ""));
 
4
  import {
5
  GOALS,
6
  GPUS,
7
+ SOURCES,
8
  methodsFor,
9
  round1,
10
  } from "@/lib/data";
 
17
  const bits = Number(first(sp.bits, "4"));
18
  const gpu = GPUS.find((g) => g.id === first(sp.gpu, "a100-80gb")) ?? GPUS[0];
19
  const goal = GOALS.find((g) => g.id === first(sp.goal, "serve")) ?? GOALS[0];
20
+ const source = SOURCES.find((s) => s.id === first(sp.source, "on-the-fly")) ?? SOURCES[0];
21
+ const methods = methodsFor(bits, gpu.backend, goal, source.id);
22
  // The calculator knows the footprint; it arrives as ?gb= rather than being
23
  // recomputed here, since the model is now whatever the user typed.
24
  const total = Number(first(sp.gb, ""));
app/page.tsx CHANGED
@@ -2,6 +2,7 @@
2
 
3
  import { useState } from "react";
4
  import SentenceCombobox from "@/components/SentenceCombobox";
 
5
  import ModelInput from "@/components/ModelInput";
6
  import MemoryBar from "@/components/MemoryBar";
7
  import MethodReceipt from "@/components/MethodReceipt";
@@ -9,16 +10,19 @@ import {
9
  GOALS,
10
  GPUS,
11
  PRECISIONS,
 
12
  methodsFor,
13
  overheadGB,
14
  weightsGB,
15
  type Model,
 
16
  } from "@/lib/data";
17
 
18
  export default function CalculatorPage() {
19
  const [model, setModel] = useState<Model | null>(null);
20
  const [gpuId, setGpuId] = useState("a100-80gb");
21
  const [hoverBits, setHoverBits] = useState<number | null>(null);
 
22
 
23
  const gpu = GPUS.find((g) => g.id === gpuId)!;
24
  const contextTokens = 8192;
@@ -31,9 +35,9 @@ export default function CalculatorPage() {
31
  PRECISIONS.find((p) => weightsGB(model, p) + overhead <= gpu.vram);
32
  const shownBits = hoverBits ?? bestFitting ?? 4;
33
 
34
- // No goal filter: every method that reaches this precision on this backend.
35
  const methods = hoverBits
36
- ? methodsFor(hoverBits, gpu.backend, GOALS[0])
37
  : [];
38
 
39
  return (
@@ -54,6 +58,15 @@ export default function CalculatorPage() {
54
  options={GPUS.map((g) => ({ id: g.id, label: g.label }))}
55
  onChange={setGpuId}
56
  />
 
 
 
 
 
 
 
 
 
57
  </div>
58
 
59
  <div className="pt-11">
@@ -64,7 +77,7 @@ export default function CalculatorPage() {
64
  selectedBits={shownBits}
65
  hoverBits={hoverBits}
66
  onHoverBits={setHoverBits}
67
- hoverCard={<MethodReceipt methods={methods} />}
68
  />
69
  </div>
70
  </main>
 
2
 
3
  import { useState } from "react";
4
  import SentenceCombobox from "@/components/SentenceCombobox";
5
+ import SentenceSelect from "@/components/SentenceSelect";
6
  import ModelInput from "@/components/ModelInput";
7
  import MemoryBar from "@/components/MemoryBar";
8
  import MethodReceipt from "@/components/MethodReceipt";
 
10
  GOALS,
11
  GPUS,
12
  PRECISIONS,
13
+ SOURCES,
14
  methodsFor,
15
  overheadGB,
16
  weightsGB,
17
  type Model,
18
+ type Source,
19
  } from "@/lib/data";
20
 
21
  export default function CalculatorPage() {
22
  const [model, setModel] = useState<Model | null>(null);
23
  const [gpuId, setGpuId] = useState("a100-80gb");
24
  const [hoverBits, setHoverBits] = useState<number | null>(null);
25
+ const [source, setSource] = useState<Source>("on-the-fly");
26
 
27
  const gpu = GPUS.find((g) => g.id === gpuId)!;
28
  const contextTokens = 8192;
 
35
  PRECISIONS.find((p) => weightsGB(model, p) + overhead <= gpu.vram);
36
  const shownBits = hoverBits ?? bestFitting ?? 4;
37
 
38
+ // No goal filter: every recommended method for this precision and backend.
39
  const methods = hoverBits
40
+ ? methodsFor(hoverBits, gpu.backend, GOALS[0], source)
41
  : [];
42
 
43
  return (
 
58
  options={GPUS.map((g) => ({ id: g.id, label: g.label }))}
59
  onChange={setGpuId}
60
  />
61
+ <span className="text-[40px] font-medium leading-[44px] tracking-[-0.04em] text-stone">
62
+ quantized
63
+ </span>
64
+ <SentenceSelect
65
+ ariaLabel="Where the quantized weights come from"
66
+ value={source}
67
+ options={SOURCES}
68
+ onChange={(id) => setSource(id as Source)}
69
+ />
70
  </div>
71
 
72
  <div className="pt-11">
 
77
  selectedBits={shownBits}
78
  hoverBits={hoverBits}
79
  onHoverBits={setHoverBits}
80
+ hoverCard={<MethodReceipt methods={methods} source={source} />}
81
  />
82
  </div>
83
  </main>
components/MemoryBar.tsx CHANGED
@@ -250,7 +250,7 @@ function FilledBar({ model, gpu, contextTokens, selectedBits, onHoverBits, hover
250
  width={HIT_W}
251
  height={barBottom}
252
  fill="transparent"
253
- className="cursor-pointer"
254
  onMouseEnter={() => {
255
  cancelClose();
256
  onHoverBits?.(t.bits);
 
250
  width={HIT_W}
251
  height={barBottom}
252
  fill="transparent"
253
+ className="cursor-pointer outline-none"
254
  onMouseEnter={() => {
255
  cancelClose();
256
  onHoverBits?.(t.bits);
components/MethodReceipt.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import type { Method, Support } from "@/lib/data";
2
 
3
  /**
4
  * The four capabilities, in the order they appear on every row. Lane widths
@@ -13,14 +13,12 @@ const LANES = [
13
  ] as const;
14
 
15
  interface Cell {
16
- /** the word as rendered, with its qualifier suffix */
17
  text: string;
18
  support: Support;
19
  }
20
 
21
  function cell(label: string, support: Support): Cell {
22
- const suffix = support === "unknown" ? "?" : support === "partial" ? "*" : "";
23
- return { text: label + suffix, support };
24
  }
25
 
26
  function cellsFor(m: Method): Cell[] {
@@ -39,28 +37,26 @@ function toneFor(support: Support): string {
39
  return "text-ash";
40
  }
41
 
42
- /** Most capable first, so the row shapes go from solid to sparse. */
43
- function score(m: Method): number {
44
- return cellsFor(m).filter((c) => c.support === "yes").length;
45
- }
46
-
47
- export default function MethodReceipt({ methods }: { methods: Method[] }) {
48
  if (methods.length === 0) {
49
  return (
50
  <div className="w-[466px] rounded-[6px] bg-paper px-[17px] py-4 ring-hairline shadow-[0_6px_20px_rgb(0_0_0/7%)]">
51
  <p className="font-mono text-[13px] text-graphite">
52
- Nothing supports this precision here.
 
 
53
  </p>
54
  </div>
55
  );
56
  }
57
 
58
- const ranked = [...methods].sort((a, b) => score(b) - score(a));
59
-
60
  return (
61
  <div className="w-[466px] rounded-[6px] bg-paper px-[17px] pb-[17px] pt-[15px] ring-hairline shadow-[0_6px_20px_rgb(0_0_0/7%)]">
62
  <div className="flex flex-col gap-[9px]">
63
- {ranked.map((m) => (
64
  <div key={m.id} className="flex items-center gap-[9px]">
65
  <a
66
  href={m.docs}
@@ -99,9 +95,6 @@ export default function MethodReceipt({ methods }: { methods: Method[] }) {
99
  <p className="font-mono text-[10px] leading-[15px] tracking-[0.04em] text-graphite">
100
  save = save_pretrained keeps it quantized
101
  </p>
102
- <p className="font-mono text-[10px] leading-[15px] tracking-[0.04em] text-ash">
103
- ? = the docs do not say · * = partly
104
- </p>
105
  </div>
106
  </div>
107
  );
 
1
+ import type { Method, Source, Support } from "@/lib/data";
2
 
3
  /**
4
  * The four capabilities, in the order they appear on every row. Lane widths
 
13
  ] as const;
14
 
15
  interface Cell {
 
16
  text: string;
17
  support: Support;
18
  }
19
 
20
  function cell(label: string, support: Support): Cell {
21
+ return { text: label, support };
 
22
  }
23
 
24
  function cellsFor(m: Method): Cell[] {
 
37
  return "text-ash";
38
  }
39
 
40
+ /**
41
+ * Methods arrive in recommendation order, best first, so the receipt keeps it.
42
+ */
43
+ export default function MethodReceipt({ methods, source }: { methods: Method[]; source: Source }) {
 
 
44
  if (methods.length === 0) {
45
  return (
46
  <div className="w-[466px] rounded-[6px] bg-paper px-[17px] py-4 ring-hairline shadow-[0_6px_20px_rgb(0_0_0/7%)]">
47
  <p className="font-mono text-[13px] text-graphite">
48
+ {source === "on-the-fly"
49
+ ? "Nothing quantizes to this precision on the fly. Try a checkpoint."
50
+ : "Nothing supports this precision here."}
51
  </p>
52
  </div>
53
  );
54
  }
55
 
 
 
56
  return (
57
  <div className="w-[466px] rounded-[6px] bg-paper px-[17px] pb-[17px] pt-[15px] ring-hairline shadow-[0_6px_20px_rgb(0_0_0/7%)]">
58
  <div className="flex flex-col gap-[9px]">
59
+ {methods.map((m) => (
60
  <div key={m.id} className="flex items-center gap-[9px]">
61
  <a
62
  href={m.docs}
 
95
  <p className="font-mono text-[10px] leading-[15px] tracking-[0.04em] text-graphite">
96
  save = save_pretrained keeps it quantized
97
  </p>
 
 
 
98
  </div>
99
  </div>
100
  );
lib/data.ts CHANGED
@@ -115,24 +115,10 @@ export const METHODS: Method[] = [
115
  config: "AwqConfig(bits=4)",
116
  docs: "https://huggingface.co/docs/transformers/main/en/quantization/awq",
117
  },
118
- {
119
- id: "hqq",
120
- name: "HQQ",
121
- bits: [2, 3, 4, 8],
122
- backends: ["cuda", "intel", "cpu"],
123
- onTheFly: true,
124
- compile: "yes",
125
- peft: "yes",
126
- serializable: "no",
127
- setupMinutes: 0,
128
- blurb: "Fast to apply and needs no dataset, but the result cannot be saved.",
129
- config: "HqqConfig(nbits=4, group_size=64)",
130
- docs: "https://huggingface.co/docs/transformers/main/en/quantization/hqq",
131
- },
132
  {
133
  id: "compressed-tensors",
134
  name: "compressed-tensors",
135
- bits: [4, 8],
136
  backends: ["cuda", "rocm", "intel", "cpu"],
137
  onTheFly: false,
138
  compile: "no",
@@ -158,60 +144,74 @@ export const METHODS: Method[] = [
158
  docs: "https://huggingface.co/docs/transformers/main/en/quantization/torchao",
159
  },
160
  {
161
- id: "optimum-quanto",
162
- name: "optimum-quanto",
163
- bits: [2, 4, 8],
164
- backends: ["cuda", "metal", "intel", "cpu"],
165
  onTheFly: true,
166
- compile: "yes",
167
  peft: "no",
168
- serializable: "no",
169
  setupMinutes: 0,
170
- blurb: "Portable across backends, including Metal. Cannot save the result.",
171
- config: "QuantoConfig(weights='int4')",
172
- docs: "https://huggingface.co/docs/transformers/main/en/quantization/quanto",
173
  },
174
  {
175
- id: "higgs",
176
- name: "HIGGS",
177
- bits: [2, 4],
178
  backends: ["cuda"],
179
  onTheFly: true,
180
  compile: "yes",
181
  peft: "no",
182
- serializable: "yes",
183
  setupMinutes: 0,
184
- blurb: "Calibration-free at 2 and 4 bits. No PEFT support.",
185
- config: "HiggsConfig(bits=4)",
186
- docs: "https://huggingface.co/docs/transformers/main/en/quantization/higgs",
187
  },
188
  {
189
- id: "aqlm",
190
- name: "AQLM",
191
- bits: [2],
192
- backends: ["cuda", "intel", "cpu"],
193
  onTheFly: false,
194
  compile: "yes",
195
- peft: "yes",
196
- serializable: "yes",
197
- setupMinutes: 90,
198
- blurb: "Additive quantization for extreme compression. Long calibration.",
199
- config: "load an AQLM checkpoint",
200
- docs: "https://huggingface.co/docs/transformers/main/en/quantization/aqlm",
201
  },
202
  {
203
- id: "finegrained-fp8",
204
- name: "FINEGRAINED_FP8",
205
- bits: [8],
206
- backends: ["cuda", "intel"],
207
- onTheFly: true,
208
  compile: "no",
209
  peft: "no",
210
  serializable: "yes",
211
  setupMinutes: 0,
212
- blurb: "Built into Transformers, so there is no extra dependency to pin.",
213
- config: "FineGrainedFP8Config()",
214
- docs: "https://huggingface.co/docs/transformers/main/en/quantization/finegrained_fp8",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
  },
216
  ];
217
 
@@ -237,14 +237,48 @@ export function totalGB(model: Model, bits: number, contextTokens: number): numb
237
  return weightsGB(model, bits) + overheadGB(model, contextTokens);
238
  }
239
 
240
- export function methodsFor(bits: number, backend: Backend, goal: Goal): Method[] {
241
- return METHODS.filter((m) => {
242
- if (!m.bits.includes(bits)) return false;
243
- if (!m.backends.includes(backend)) return false;
244
- if (goal.requires === "peft" && m.peft !== "yes") return false;
245
- if (goal.requires === "serializable" && m.serializable === "no") return false;
246
- return true;
247
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  }
249
 
250
  export function round1(n: number): number {
 
115
  config: "AwqConfig(bits=4)",
116
  docs: "https://huggingface.co/docs/transformers/main/en/quantization/awq",
117
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
  {
119
  id: "compressed-tensors",
120
  name: "compressed-tensors",
121
+ bits: [2, 3, 4, 8],
122
  backends: ["cuda", "rocm", "intel", "cpu"],
123
  onTheFly: false,
124
  compile: "no",
 
144
  docs: "https://huggingface.co/docs/transformers/main/en/quantization/torchao",
145
  },
146
  {
147
+ id: "finegrained-fp8",
148
+ name: "FINEGRAINED_FP8",
149
+ bits: [8],
150
+ backends: ["cuda", "intel"],
151
  onTheFly: true,
152
+ compile: "no",
153
  peft: "no",
154
+ serializable: "yes",
155
  setupMinutes: 0,
156
+ blurb: "Built into Transformers, so there is no extra dependency to pin.",
157
+ config: "FineGrainedFP8Config()",
158
+ docs: "https://huggingface.co/docs/transformers/main/en/quantization/finegrained_fp8",
159
  },
160
  {
161
+ id: "nvfp4",
162
+ name: "NVFP4",
163
+ bits: [4],
164
  backends: ["cuda"],
165
  onTheFly: true,
166
  compile: "yes",
167
  peft: "no",
168
+ serializable: "no",
169
  setupMinutes: 0,
170
+ blurb: "NVIDIA's 4-bit float format, run through Hub kernels. Needs a Blackwell card.",
171
+ config: "see the NVFP4 docs",
172
+ docs: "https://huggingface.co/docs/transformers/main/en/quantization/nvfp4",
173
  },
174
  {
175
+ id: "gguf",
176
+ name: "GGUF",
177
+ bits: [2, 3, 4, 8],
178
+ backends: ["cuda", "metal", "intel", "cpu"],
179
  onTheFly: false,
180
  compile: "yes",
181
+ peft: "no",
182
+ serializable: "no",
183
+ setupMinutes: 0,
184
+ blurb: "The llama.cpp format. Pick a file from any of the GGUF repos on the Hub.",
185
+ config: 'from_pretrained(repo, gguf_file="model-Q4_K_M.gguf")',
186
+ docs: "https://huggingface.co/docs/transformers/main/en/quantization/gguf",
187
  },
188
  {
189
+ id: "auto-round",
190
+ name: "AutoRound",
191
+ bits: [2, 3, 4, 8],
192
+ backends: ["cuda", "intel", "cpu"],
193
+ onTheFly: false,
194
  compile: "no",
195
  peft: "no",
196
  serializable: "yes",
197
  setupMinutes: 0,
198
+ blurb: "Intel's weight-rounding method, with checkpoints from 2 to 8 bits.",
199
+ config: "load an AutoRound checkpoint",
200
+ docs: "https://huggingface.co/docs/transformers/main/en/quantization/auto_round",
201
+ },
202
+ {
203
+ id: "quark",
204
+ name: "Quark",
205
+ bits: [2, 4, 8],
206
+ backends: ["cuda", "rocm", "metal", "intel", "cpu"],
207
+ onTheFly: false,
208
+ compile: "unknown",
209
+ peft: "no",
210
+ serializable: "no",
211
+ setupMinutes: 0,
212
+ blurb: "AMD's toolkit. Loads Quark checkpoints on AMD and NVIDIA hardware.",
213
+ config: "load a Quark checkpoint",
214
+ docs: "https://huggingface.co/docs/transformers/main/en/quantization/quark",
215
  },
216
  ];
217
 
 
237
  return weightsGB(model, bits) + overheadGB(model, contextTokens);
238
  }
239
 
240
+ /**
241
+ * Where the quantized weights come from: made as the model loads, or read
242
+ * from a checkpoint someone already quantized.
243
+ */
244
+ export type Source = "on-the-fly" | "checkpoint";
245
+
246
+ export const SOURCES: { id: Source; label: string }[] = [
247
+ { id: "on-the-fly", label: "on the fly" },
248
+ { id: "checkpoint", label: "from a checkpoint" },
249
+ ];
250
+
251
+ const BELOW_4 = ["gguf", "auto-round", "compressed-tensors", "quark"];
252
+
253
+ /**
254
+ * The methods we recommend for each source and precision, best first. Anything
255
+ * not listed is left out even when it technically supports the combination.
256
+ */
257
+ export const RECOMMENDED: Record<Source, Record<number, string[]>> = {
258
+ "on-the-fly": {
259
+ 8: ["finegrained-fp8", "bitsandbytes", "torchao"],
260
+ 4: ["bitsandbytes", "torchao", "nvfp4"],
261
+ 3: [],
262
+ 2: [],
263
+ },
264
+ checkpoint: {
265
+ 8: ["finegrained-fp8", "bitsandbytes", "torchao", "gguf", "auto-round", "compressed-tensors", "quark"],
266
+ 4: ["bitsandbytes", "torchao", "nvfp4", "gguf", "auto-round", "gptqmodel", "awq", "compressed-tensors", "quark"],
267
+ 3: BELOW_4,
268
+ 2: BELOW_4,
269
+ },
270
+ };
271
+
272
+ export function methodsFor(bits: number, backend: Backend, goal: Goal, source: Source): Method[] {
273
+ return (RECOMMENDED[source][bits] ?? [])
274
+ .map((id) => METHODS.find((m) => m.id === id)!)
275
+ .filter((m) => {
276
+ if (!m.bits.includes(bits)) return false;
277
+ if (!m.backends.includes(backend)) return false;
278
+ if (goal.requires === "peft" && m.peft !== "yes") return false;
279
+ if (goal.requires === "serializable" && m.serializable === "no") return false;
280
+ return true;
281
+ });
282
  }
283
 
284
  export function round1(n: number): number {