Spaces:
Running on Zero
Running on Zero
Recognize ROCmFP4/ROCmFPX GGUF quants
Browse filesThese are AMD ROCm floating-point block-quant formats from the
ciru-ai/ROCmFPX fork; stock llama.cpp cannot run them. Add their
effective bits-per-weight to the quant table (4.34 for ROCmFP4,
7.08 for ROCmFPX), derived from the real chadrock3.6 GGUF file
sizes (reproduces 14.82 GiB / 31.42 GiB within rounding).
quant_from_filename already matches the longer names first, so
the chadrock filenames resolve to ROCmFP4/ROCmFPX correctly.
command_preview switches to the rocmfpx-llama-server runner and
adds a note that the pinned ROCmFPX build is required. The UI
fetch handler now syncs the Quantization dropdown to the quant
detected from the chosen GGUF filename. Four new tests.
Co-Authored-By: Claude <noreply@anthropic.com>
- app.py +24 -8
- tests/test_vramcalc.py +35 -0
- vramcalc/quant.py +11 -0
- vramcalc/report.py +14 -1
app.py
CHANGED
|
@@ -60,25 +60,41 @@ def list_gguf_files(repo_id: str, hf_token: str):
|
|
| 60 |
|
| 61 |
|
| 62 |
def fetch_arch(repo_id: str, filename: str, hf_token: str):
|
| 63 |
-
"""Range-read a GGUF header from HF and return editable arch fields.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 64 |
if not repo_id or not filename:
|
| 65 |
-
return _empty_arch_fields(), "Pick a GGUF file first."
|
| 66 |
try:
|
| 67 |
meta = parse_hf_range(
|
| 68 |
repo_id.strip(), filename, token=hf_token or None
|
| 69 |
)
|
| 70 |
except Exception as e: # noqa: BLE001
|
| 71 |
-
return _empty_arch_fields(), f"Error reading GGUF header: {e}"
|
|
|
|
| 72 |
if not meta.n_layer:
|
| 73 |
-
return
|
|
|
|
| 74 |
"Parsed header but architecture fields look empty; "
|
| 75 |
-
"edit them manually below."
|
|
|
|
| 76 |
)
|
| 77 |
-
return
|
|
|
|
| 78 |
f"Fetched {meta.architecture or 'model'}: "
|
| 79 |
f"{meta.n_layer} layers, {meta.n_embd} embd, "
|
| 80 |
f"{meta.n_head}/{meta.n_head_kv} heads, ctx {meta.training_ctx}, "
|
| 81 |
-
f"params {meta.params or 'n/a'}."
|
|
|
|
| 82 |
)
|
| 83 |
|
| 84 |
|
|
@@ -354,7 +370,7 @@ def build_ui():
|
|
| 354 |
)
|
| 355 |
fetch_btn.click(
|
| 356 |
fn=fetch_arch, inputs=[repo_id, file_picker, hf_token],
|
| 357 |
-
outputs=[*arch_inputs, fetch_status],
|
| 358 |
).then(
|
| 359 |
fn=_store_arch, inputs=arch_inputs, outputs=arch_state
|
| 360 |
)
|
|
|
|
| 60 |
|
| 61 |
|
| 62 |
def fetch_arch(repo_id: str, filename: str, hf_token: str):
|
| 63 |
+
"""Range-read a GGUF header from HF and return editable arch fields.
|
| 64 |
+
|
| 65 |
+
Also returns a gr.update for the Quantization dropdown that syncs it to the
|
| 66 |
+
quant detected from the filename (e.g. ROCmFP4/ROCmFPX) — a no-op when the
|
| 67 |
+
filename has no recognized quant token, so the user's manual pick is kept.
|
| 68 |
+
The GGUF header's tensor types for ROCm formats use custom IDs stock
|
| 69 |
+
llama.cpp does not map, so the filename is the reliable source here.
|
| 70 |
+
"""
|
| 71 |
+
detected = quant_from_filename(filename) if filename else None
|
| 72 |
+
if detected and detected not in QUANT_CHOICES:
|
| 73 |
+
detected = None
|
| 74 |
+
quant_update = gr.update(value=detected) if detected else gr.update()
|
| 75 |
if not repo_id or not filename:
|
| 76 |
+
return _empty_arch_fields(), "Pick a GGUF file first.", quant_update
|
| 77 |
try:
|
| 78 |
meta = parse_hf_range(
|
| 79 |
repo_id.strip(), filename, token=hf_token or None
|
| 80 |
)
|
| 81 |
except Exception as e: # noqa: BLE001
|
| 82 |
+
return _empty_arch_fields(), f"Error reading GGUF header: {e}", quant_update
|
| 83 |
+
quant_note = f", quant {detected}" if detected else ""
|
| 84 |
if not meta.n_layer:
|
| 85 |
+
return (
|
| 86 |
+
_arch_to_fields(meta),
|
| 87 |
"Parsed header but architecture fields look empty; "
|
| 88 |
+
"edit them manually below.",
|
| 89 |
+
quant_update,
|
| 90 |
)
|
| 91 |
+
return (
|
| 92 |
+
_arch_to_fields(meta),
|
| 93 |
f"Fetched {meta.architecture or 'model'}: "
|
| 94 |
f"{meta.n_layer} layers, {meta.n_embd} embd, "
|
| 95 |
f"{meta.n_head}/{meta.n_head_kv} heads, ctx {meta.training_ctx}, "
|
| 96 |
+
f"params {meta.params or 'n/a'}{quant_note}.",
|
| 97 |
+
quant_update,
|
| 98 |
)
|
| 99 |
|
| 100 |
|
|
|
|
| 370 |
)
|
| 371 |
fetch_btn.click(
|
| 372 |
fn=fetch_arch, inputs=[repo_id, file_picker, hf_token],
|
| 373 |
+
outputs=[*arch_inputs, fetch_status, quant],
|
| 374 |
).then(
|
| 375 |
fn=_store_arch, inputs=arch_inputs, outputs=arch_state
|
| 376 |
)
|
tests/test_vramcalc.py
CHANGED
|
@@ -24,6 +24,11 @@ def test_quant_table_has_common_types():
|
|
| 24 |
assert q in QUANT_BPW
|
| 25 |
|
| 26 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
def test_quant_from_filename():
|
| 28 |
assert quant_from_filename("Llama-3-8B-Instruct-Q4_K_M.gguf") == "Q4_K_M"
|
| 29 |
assert quant_from_filename("model.Q5_K_S.gguf") == "Q5_K_S"
|
|
@@ -32,6 +37,25 @@ def test_quant_from_filename():
|
|
| 32 |
assert quant_from_filename("model.gguf") is None
|
| 33 |
|
| 34 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
def test_weight_bytes():
|
| 36 |
# 8B params at Q4_K_M (4.84375 bpw) -> ~4.84 GB
|
| 37 |
b = weight_bytes(8_000_000_000, "Q4_K_M")
|
|
@@ -224,6 +248,17 @@ def test_command_preview_yarn_when_target_exceeds():
|
|
| 224 |
assert "--rope-freq-scale 0.5" in cmd
|
| 225 |
|
| 226 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 227 |
def test_format_bytes():
|
| 228 |
assert "GiB" in format_bytes(5 << 30)
|
| 229 |
assert "MiB" in format_bytes(5 << 20)
|
|
|
|
| 24 |
assert q in QUANT_BPW
|
| 25 |
|
| 26 |
|
| 27 |
+
def test_quant_table_has_rocmfp_types():
|
| 28 |
+
for q in ["ROCmFP4", "ROCmFPX"]:
|
| 29 |
+
assert q in QUANT_BPW
|
| 30 |
+
|
| 31 |
+
|
| 32 |
def test_quant_from_filename():
|
| 33 |
assert quant_from_filename("Llama-3-8B-Instruct-Q4_K_M.gguf") == "Q4_K_M"
|
| 34 |
assert quant_from_filename("model.Q5_K_S.gguf") == "Q5_K_S"
|
|
|
|
| 37 |
assert quant_from_filename("model.gguf") is None
|
| 38 |
|
| 39 |
|
| 40 |
+
def test_quant_from_filename_rocmfp():
|
| 41 |
+
# Real chadrock filenames — case-insensitive token match.
|
| 42 |
+
assert quant_from_filename(
|
| 43 |
+
"CHADROCK3.6-27B-Coder-MTP-ROCmFP4-STRIX_LEAN.gguf"
|
| 44 |
+
) == "ROCmFP4"
|
| 45 |
+
assert quant_from_filename(
|
| 46 |
+
"CHADROCK3.6-35B-A3B-Coder-MTP-ROCmFPX-MoEQuality-7.08BPW.gguf"
|
| 47 |
+
) == "ROCmFPX"
|
| 48 |
+
# plain ROCm with no FP version must not falsely match ROCmFP4/FPX
|
| 49 |
+
assert quant_from_filename("model-ROCM.gguf") is None
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def test_weight_bytes_rocmfp4_matches_real_file():
|
| 53 |
+
# 27B Strix Lean: 14,817,251,680 bytes for 27,320,697,856 tensor elems.
|
| 54 |
+
# Effective bpw from the real file -> weight estimate reproduces file size.
|
| 55 |
+
b = weight_bytes(27_320_697_856, "ROCmFP4")
|
| 56 |
+
assert 14.6e9 < b < 15.0e9 # ~14.82 GiB file size
|
| 57 |
+
|
| 58 |
+
|
| 59 |
def test_weight_bytes():
|
| 60 |
# 8B params at Q4_K_M (4.84375 bpw) -> ~4.84 GB
|
| 61 |
b = weight_bytes(8_000_000_000, "Q4_K_M")
|
|
|
|
| 248 |
assert "--rope-freq-scale 0.5" in cmd
|
| 249 |
|
| 250 |
|
| 251 |
+
def test_command_preview_rocmfp_uses_custom_runner():
|
| 252 |
+
arch = PRESETS["Llama-3 8B"]
|
| 253 |
+
from vramcalc import Inputs
|
| 254 |
+
for q in ("ROCmFP4", "ROCmFPX"):
|
| 255 |
+
cmd = command_preview(arch, Inputs(quant=q, n_ctx=8192,
|
| 256 |
+
gpu_vram_gb=[24.0]))
|
| 257 |
+
# custom runner, not stock llama-server
|
| 258 |
+
assert cmd.startswith("rocmfpx-llama-server")
|
| 259 |
+
assert "ROCmFPX" in cmd # warning note present
|
| 260 |
+
|
| 261 |
+
|
| 262 |
def test_format_bytes():
|
| 263 |
assert "GiB" in format_bytes(5 << 30)
|
| 264 |
assert "MiB" in format_bytes(5 << 20)
|
vramcalc/quant.py
CHANGED
|
@@ -48,6 +48,17 @@ QUANT_BPW: dict[str, float] = {
|
|
| 48 |
"F16": 16.0,
|
| 49 |
"BF16": 16.0,
|
| 50 |
"F32": 32.0,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
}
|
| 52 |
|
| 53 |
|
|
|
|
| 48 |
"F16": 16.0,
|
| 49 |
"BF16": 16.0,
|
| 50 |
"F32": 32.0,
|
| 51 |
+
# AMD ROCm floating-point block quants (ciru-ai/ROCmFPX fork types).
|
| 52 |
+
# These are *effective* bits-per-weight = real_file_size * 8 / total_params,
|
| 53 |
+
# measured from the jcbtc/chadrock3.6 ROCmFP4/FPX GGUFs. They are higher than
|
| 54 |
+
# the nominal 4.0/8.0 because the typical model is a hybrid attention+SSM
|
| 55 |
+
# (Mamba) qwen35 with co-stored F32 state/conv tensors and mixed tensor types
|
| 56 |
+
# (the 35B FPX file is mostly Q6_K experts + ROCmFPX attention). Using the
|
| 57 |
+
# effective BPW reproduces the real file size, which is what a VRAM estimate
|
| 58 |
+
# needs. NOTE: stock llama.cpp cannot run these — they require the pinned
|
| 59 |
+
# ciru-ai/ROCmFPX runner; see command_preview for the warning.
|
| 60 |
+
"ROCmFP4": 4.34, # 27B Strix Lean: 14.82 GB / 27.32B params
|
| 61 |
+
"ROCmFPX": 7.08, # 35B A3B MoEQuality: model card states 7.08 BPW
|
| 62 |
}
|
| 63 |
|
| 64 |
|
vramcalc/report.py
CHANGED
|
@@ -160,9 +160,17 @@ def format_bytes(n: float) -> str:
|
|
| 160 |
return f"{n:.0f} B"
|
| 161 |
|
| 162 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 163 |
def command_preview(arch: ModelArch, inp: Inputs) -> str:
|
| 164 |
"""Generate a llama.cpp launch command from the current inputs."""
|
| 165 |
-
|
|
|
|
|
|
|
| 166 |
parts.append(f"-m model-{inp.quant}.gguf")
|
| 167 |
parts.append(f"-c {inp.n_ctx}")
|
| 168 |
parts.append("-ngl 999") # full offload assumption
|
|
@@ -196,4 +204,9 @@ def command_preview(arch: ModelArch, inp: Inputs) -> str:
|
|
| 196 |
parts.extend(yarn_args)
|
| 197 |
if arch.n_mtp > 0:
|
| 198 |
parts.append(f"--mtp {arch.n_mtp}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 199 |
return " \\\n ".join(parts)
|
|
|
|
| 160 |
return f"{n:.0f} B"
|
| 161 |
|
| 162 |
|
| 163 |
+
# AMD ROCm floating-point block quants that stock llama.cpp cannot run. They
|
| 164 |
+
# require the pinned ciru-ai/ROCmFPX runner (see
|
| 165 |
+
# https://github.com/ciru-ai/ROCmFPX). Listed so command_preview can flag it.
|
| 166 |
+
ROCMFP_QUANTS = {"ROCmFP4", "ROCmFPX"}
|
| 167 |
+
|
| 168 |
+
|
| 169 |
def command_preview(arch: ModelArch, inp: Inputs) -> str:
|
| 170 |
"""Generate a llama.cpp launch command from the current inputs."""
|
| 171 |
+
is_rocmfp = inp.quant in ROCMFP_QUANTS
|
| 172 |
+
runner = "rocmfpx-llama-server" if is_rocmfp else "llama-server"
|
| 173 |
+
parts = [runner]
|
| 174 |
parts.append(f"-m model-{inp.quant}.gguf")
|
| 175 |
parts.append(f"-c {inp.n_ctx}")
|
| 176 |
parts.append("-ngl 999") # full offload assumption
|
|
|
|
| 204 |
parts.extend(yarn_args)
|
| 205 |
if arch.n_mtp > 0:
|
| 206 |
parts.append(f"--mtp {arch.n_mtp}")
|
| 207 |
+
if is_rocmfp:
|
| 208 |
+
parts.append(
|
| 209 |
+
f"# NOTE: {inp.quant} needs the ciru-ai/ROCmFPX runner "
|
| 210 |
+
f"(stock llama.cpp cannot read these tensor types)."
|
| 211 |
+
)
|
| 212 |
return " \\\n ".join(parts)
|