~/satyajit

HauhauCS FastMTP: a signed manifest that actually verifies

mdjsonmcp

2026-08-30 · 11 min · gguf · quantization · llama-cpp · multi-token-prediction · speculative-decoding · security

HauhauCS/Qwen3.8-27B-Uncensored-HauhauCS-Aggressive-MTP-GGUF is, by Hugging Face's own numbers, one of the more-downloaded community quants this site has looked at: 1,061,687 downloads, 752 likes, 172.47 GB spread across 21 files. It is also, as the name says, an uncensored derivative — HauhauCS's "Aggressive" refusal-suppression profile applied on top of Qwen's own Qwen3.8-27B, with the card claiming 0 refusals across 465 prompts. That part of the story is not new, and it is not this article's subject; abliterated Qwen3.8-27B derivatives are already their own small ecosystem, and the Qwen3.8-Flash-Next piece catalogued two others in it.

What is new — and, unlike the abliteration itself, fully checkable without touching model behavior at all — is what else the repository ships. Sitting next to the eleven GGUF quants and a vision projector are four files a quantizer almost never bothers with: a signed release manifest, a signed build-provenance file for one specific artifact, the Ed25519 public key to check both against, and a small llama.cpp patch — 27 lines added, one removed — against a pinned commit. None of that requires taking HauhauCS's word for anything — the manifest names exact byte counts and hashes, the signatures are checkable against a key shipped in the same repo, and the patch is a diff against a public commit with a public history. So this article did the checking.

RepoHauhauCS/…-Aggressive-MTP-GGUF · 1,061,687 downloads · 752 likes
BaseQwen/Qwen3.8-27B — dense, 64 layers, 48 Gated DeltaNet + 16 gated-attention
Size172.47 GB, 21 files · 11 text GGUFs, IQ2_M (10.32 GB) to Q8_K_P (31.46 GB)
Visionseparate mmproj BF16 projector, 931 MB, 334 tensors
FastMTP sidecar-FastMTP-32K.gguf, 903 MB · trims the MTP head to a 32,768-token draft vocabulary
AuthenticityEd25519-signed manifest + provenance, a raw public key, and a plaintext SHA256SUMS
Patchone file, src/models/qwen35.cpp, +27/−1 lines, against ggerganov/llama.cpp@4df29be4…
LicenseApache-2.0, inherited from Qwen3.8-27B
repo size
172.47 GB
license
apache-2.0
downloads
1.6M
likes
1.0K
files
21

The headline check: do the signatures actually verify

The method was plain: download HauhauCS-RELEASE-MANIFEST.json, its .sig, FastMTP-PROVENANCE.json, its .sig, and HauhauCS-FastMTP-Ed25519-PUBLIC.pem directly from the repo's /resolve/main/, then verify with Python's cryptography library — load_pem_public_key on the PEM, then pub.verify(sig, data) on the exact downloaded bytes of each document.

Ed25519 verification, run against the downloaded bytes
HauhauCS-RELEASE-MANIFES…4,828 bytessha256 1413ec1a…901826HauhauCS-RELEASE-MANIFES…64 bytes · Ed25519bd4c23d6…41a300HauhauCS-FastMTP-Ed25519…32-byte raw key0abc20cb…74183bpub.verify(sig, data)VALIDno exception raisedrepo-wide inventory: sha256 + byte count for 17 of the repo's 21 files
artifacts listed: 17 (12 GGUFs, README, patch, PEM, provenance + its .sig)
not listed: itself, its own .sig, SHA256SUMS, .gitattributes

Both documents verify. The public key is checked too, not just trusted: its DER (SubjectPublicKeyInfo) fingerprint, computed locally from the downloaded .pem, is f7be4a2335…23833c — matching the public_key_der_sha256 field FastMTP-PROVENANCE.json declares about itself, so the key that verifies the signature is the same key the signed document says it should be. The two documents do different jobs: the manifest is a repo-wide inventory — 17 of the 21 published files, each with a byte count and sha256 — while provenance is a build attestation for a single file, naming the exact upstream commit and patch it was built against. A community GGUF repo shipping either one is unusual; this one ships both, signed with the same key, and both check out.

Both signatures verify. No exception, no fallback, no "couldn't check." And the public key itself isn't just trusted on arrival — its DER fingerprint, computed locally from the downloaded .pem, matches the public_key_der_sha256 field the provenance file declares about itself, so the key doing the verifying is provably the key the signed document says it should be.

Checking the hashes without downloading 172GB of model weights

A signature only proves the manifest wasn't altered after signing — it says nothing about whether the manifest's own claims about the files match the files. That part is checkable too, and cheaply. Every small file the manifest covers — FastMTP-PROVENANCE.json, both .sig files, the PEM, the patch, README.md — was downloaded and hashed directly: all five match the manifest's declared sha256 with no exceptions. For the twelve GGUFs, downloading 172GB just to hash them defeats the point of having a manifest at all, so this article cross-checked the manifest's declared sha256 against Hugging Face's own file metadata instead, via https://huggingface.co/api/models/<repo>?blobs=true, which reports each LFS file's sha256 without transferring the file. Every one of the twelve — including the vision projector — matches.

There's a second, less obvious layer. Each GGUF entry in the manifest carries two hashes: a plain sha256 of the raw file, and a canonical_tensor_sha256 computed over "sha256-sorted-name-type-shape- payload" — HauhauCS's own stated algorithm name, not independently auditable here since the tool that computes it isn't published. The idea, stated in the README's own authenticity section, is that the plain hash "identifies byte-for-byte mirrors after renaming" while the canonical one "continue[s] to identify HauhauCS tensors after metadata-only rewriting" — i.e. it should survive someone stripping or editing the GGUF's key-value header while leaving the actual weights untouched, and change if the weights themselves do. The field is present and internally consistent for every GGUF in the manifest; whether the specific algorithm behind it does what it claims isn't something this article could verify without the tool that produced it, so take the concept as sound and the implementation as unverified.

What isn't covered by the signed manifest is smaller than it sounds: itself, its own .sig, and .gitattributes. A third file, SHA256SUMS — a plain, unsigned text ledger in the classic sha256sum format — closes that gap: it lists the manifest and its signature alongside everything else, and sha256sum -c against the small files downloaded for this piece reports every one OK. Between the signed manifest and the plaintext ledger, every file in the repo except .gitattributes has a declared hash checkable from at least one of the two.

What FastMTP actually is, and where the MTP head comes from

Qwen3.8-27B is dense — every parameter active per token, no routed experts — which makes "where does the MTP head come from" a fair question, since multi-token prediction usually shows up on MoE models like DeepSeek-V3 or Qwen3.8-Flash-Next. The answer is in the README's own "Specs" section: Qwen ships a native embedded MTP/NextN head on the dense model itself, and every text GGUF in this repo preserves it unmodified — nothing HauhauCS added. That head projects directly onto the model's full, padded 248,320-token vocabulary, same as the main output layer, and it already works today with plain --spec-type draft-mtp on any of the target quants alone, no patch required. If the mechanics of multi-token prediction as a training objective and a self- speculative decoder are unfamiliar, the MTP explainer covers both Meta's parallel-head and DeepSeek's sequential-module flavors in more depth than this piece needs to.

HauhauCS FastMTP is a separate, second checkpoint — the 903 MB -FastMTP-32K.gguf — that reuses the same idea but trims the head's output vocabulary down to a fixed, much smaller set of tokens. The "32K" in the filename is the trimmed vocabulary size: 32,768, confirmed by the README's own diagnostic example for a mismatched build ("expected 5120, 248320, got 5120, 32768") rather than asserted in prose anywhere. A smaller output vocabulary means a smaller LM-head matmul on every draft step — the whole point of a draft model is to be cheap — at the cost of the draft never being able to propose a token outside that fixed 32,768. Running it requires the patch, because mainline qwen35.cpp has nowhere to put a trimmed-vocabulary head.

the MTP head’s output matmul, src/models/qwen35.cpp
hidden state5,120-dimoutput.weight[5,120 × 32,768]logits over 32,768 draft tokens → scattered via d2t into:0248,320everywhere else: −∞ (ggml_fill, then ggml_set_rows)
output columns: 32,7687.58× fewer than nativegate: mtp_only && d2t present in the GGUF

Every text GGUF in the repo already carries Qwen’s own native MTP/NextN head, which projects straight onto the full 248,320-token vocabulary — the top row above. The FastMTP sidecar is a second, separate checkpoint (the 903 MB -32K.gguf) whose head only knows 32,768 tokens — the “32K” in its filename — and the patch’s job is to let qwen35.cpp load and run that smaller head: trim output.weight to 32,768 columns instead of 248,320 when the GGUF carries a d2t tensor, then scatter its logits back into full-vocabulary space with ggml_set_rows, everywhere else forced to −∞. That tensor and that scatter are not new inventions — they’re the same d2t convention llama.cpp already uses for EAGLE-3 and DFlash-style dedicated drafters, now wired into a dense model’s own embedded MTP graph for the first time. The target model still verifies in full-vocabulary space either way — the trim only changes what the draft is capable of proposing, never what gets accepted.

The patch, read line by line

HauhauCS-FastMTP-llama.cpp.patch touches exactly one file, src/models/qwen35.cpp, and it applies cleanly — checked here with patch -p1 --dry-run — against the exact commit its own provenance file names, ggerganov/llama.cpp@4df29be4f4c3673f428170fda944a5b19f743bb8, fetched independently from GitHub for the comparison. Two hunks, two jobs:

+    int64_t n_vocab_out = n_vocab;
+    const ggml_tensor * d2t_meta = ml.get_tensor_meta("d2t");
+    if (mtp_only && d2t_meta) {
+        n_vocab_out = d2t_meta->ne[0];
+        d2t = create_tensor(tn(LLM_TENSOR_D2T), { n_vocab_out }, 0);
+    }
     tok_embd = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), { n_embd, n_vocab }, 0);
     output_norm = create_tensor(tn(LLM_TENSOR_OUTPUT_NORM, "weight"), { n_embd }, 0);
-    output = create_tensor(tn(LLM_TENSOR_OUTPUT, "weight"), { n_embd, n_vocab }, TENSOR_NOT_REQUIRED);
+    output = create_tensor(tn(LLM_TENSOR_OUTPUT, "weight"), { n_embd, n_vocab_out }, TENSOR_NOT_REQUIRED);
     if (output == NULL) {
+        GGML_ASSERT(!d2t && "d2t draft-vocab trim requires output.weight");
         output = create_tensor(tn(LLM_TENSOR_TOKEN_EMBD, "weight"), { n_embd, n_vocab }, TENSOR_DUPLICATED);
     }

The first hunk only trims output.weight when building the MTP-only graph (mtp_only, i.e. the drafter, not the target model) and the loaded GGUF actually carries a d2t tensor — a GGUF without one falls through unchanged. It also refuses to let a trimmed head fall back to the tied token- embedding matrix, correctly, since that matrix is sized to the full vocabulary and can't stand in for a smaller one. The second hunk, inside the MTP graph builder itself, takes the head's output over the trimmed vocabulary and scatters it into a full-vocabulary tensor pre-filled with negative infinity, using d2t as the index map — so the target model, verifying downstream, still sees an ordinary 248,320-wide logit vector, just one where most entries can never win.

The interesting part isn't the mechanism — it's where it came from. LLM_TENSOR_D2T and model.d2t ("draft to target vocabulary mapping," in the field's own comment) are not new infrastructure this patch invents; they already exist in llama.cpp's shared architecture code at that same commit. A GitHub code search for d2t under src/models in ggml-org/llama.cpp turns up exactly two files using it: eagle3.cpp and dflash.cpp — the dedicated draft-model architectures behind EAGLE-3 and DFlash, both already covered on this site. qwen35.cpp itself has no d2t reference before this patch. So HauhauCS's contribution here is narrow and precise: not a new vocabulary-trimming idea, but the first wiring of llama.cpp's existing one into a dense model's own embedded MTP head, rather than into a purpose-built external drafter.

Does it actually go faster?

Here the checking has to stop being independent, because reproducing it would mean running the model. HauhauCS's own numbers, measured on one RTX PRO 6000 Blackwell (96 GB), one lane, --no-mmap, full CUDA offload: FastMTP at depth 3 reaches up to 3.02x document throughput and 1.93x reasoning throughput against MTP disabled entirely, and up to 35.2% more document throughput and 21.1% more reasoning throughput than the native embedded MTP head running at depth 2. Every FastMTP run is reported to reproduce the corresponding embedded-MTP output token-for-token — the expected result, since the target model still verifies every drafted token regardless of which head proposed it, and a correctly-implemented draft head can only change speed, not output. That claim, and the underlying tok/s table across all ten quants, is self-reported, single-hardware, and not reproduced by this article — unlike the signatures and hashes above, there's no cheap independent way to check a throughput number without the GPU it was measured on.

The K_P quant ladder: checking one more claim

The card makes a second, smaller, checkable claim about its own custom "K_P" quants: each one "bumps quality up by one or two quant levels at only around 5-15% more size than the base quant." Its own Downloads table gives both numbers directly — the real K_P file's bits-per-weight, and a reference bpw for the standard llama.cpp quant type sitting right underneath it — so this is arithmetic, not another benchmark to trust blind.

the card’s own “5–15% more size” claim vs its own bpw table
Q3_K_P vs Q3_K_M+0.77% size · below the claimed 5–15%
Q3_K_P
3.93 bpw
Q3_K_M
3.90 bpw
Q5_K_P vs Q5_K_M+3.86% size · below the claimed 5–15%
Q5_K_P
5.92 bpw
Q5_K_M
5.70 bpw
Q4_K_P vs Q4_K_M+7.58% size · within claimed band
Q4_K_P
5.25 bpw
Q4_K_M
4.88 bpw
Q8_K_P vs Q8_0+8.35% size · within claimed band
Q8_K_P
9.21 bpw
Q8_0
8.50 bpw
Q6_K_P vs Q6_K+15.00% size · below the claimed 5–15%
Q6_K_P
7.59 bpw
Q6_K
6.60 bpw

Sorted by delta, the claim holds for three of the five checkable pairs — Q8_K_P at +8.35%, Q4_K_P at +7.58%, Q6_K_P right at the ceiling at +15.00% — and misses low for two. Q5_K_P comes in at +3.86%, under the stated floor, and Q3_K_P is the outlier: +0.77% more size than plain Q3_K_M, close enough to call it the same footprint. None of this contradicts the size math elsewhere in this article — every file’s own bpw and byte count check out against the repo’s published sizes — it’s the separate marketing claim about how K_P quants compare to standard ones that is directionally true but not uniform: two of the five pairs the card’s own table invites you to compare don’t land in the range it states.

Three of the five checkable pairs land inside the stated band. Two don't: Q5_K_P is a few points under the 5% floor, and Q3_K_P is barely bigger than plain Q3_K_M at all. None of this touches the size and hash checks earlier in this piece — every file's bpw and byte count match what's actually on Hugging Face — it's specifically the comparison to the standard quant types that the card's own numbers don't uniformly support.

The vision claim, checked against what's actually shipped

The repo's tags include multimodal and vision, and a vision claim on a GGUF release is exactly the kind of thing worth checking against the file list rather than the tag list: a GGUF text model needs a separate multimodal projector (mmproj) file to actually accept image input, and plenty of releases carry the tag without carrying the file. This one does carry the file — mmproj-Qwen3.8-27B-Uncensored-HauhauCS-Aggressive-BF16.gguf, 931 MB, 334 tensors, its sha256 present in the signed manifest and matching Hugging Face's own metadata like everything else checked above. The vision claim holds up against what's actually in the repository, for whatever the underlying Qwen3.8-27B vision tower is itself capable of — a question this article didn't re-test.

What this is, and isn't

Verified directly. Both Ed25519 signatures check out against a public key whose own fingerprint is independently confirmed. Every declared hash — for text files by direct download, for the twelve GGUFs against Hugging Face's own LFS metadata — matches. The patch applies cleanly against the exact commit its provenance file names, and the mechanism it adds is a real, pre-existing llama.cpp convention already used by two draft-model architectures this site has covered, not a novel or unverifiable trick. The vision projector the tags promise is actually in the file list.

Self-reported, not independently reproduced here. The speedup numbers — the 3.02x/1.93x headline and the full per-quant table — come from HauhauCS's own single-GPU benchmark. Checking them would mean running a 172GB download through the patched runtime on matching hardware, which this piece didn't do.

Checked and found uneven. The K_P "5-15% more size" framing holds for three of five comparable pairs and misses low for two, most notably Q3_K_P.

The uncensoring is the reason this repository exists and the reason it has a million downloads; it is also the one part of the release this article isn't going to explain, demonstrate, or evaluate. What made it worth a full pass is everything sitting next to it: a signed manifest and a signed provenance file are not standard practice for a community requant, checking them costs nothing but bandwidth and a public key, and in this case, they hold up.

Cite this article

For attribution, please use the following reference or BibTeX:

Satyajit Ghana, "HauhauCS FastMTP: a signed manifest that actually verifies", ai.thesatyajit.com, August 2026.

bibtex
@misc{ghana2026hauhaucsqwenfastmtp,
  author = {Satyajit Ghana},
  title  = {HauhauCS FastMTP: a signed manifest that actually verifies},
  url    = {https://ai.thesatyajit.com/articles/hauhaucs-qwen-fastmtp},
  year   = {2026}
}
share