Downloads · 30 days
31
11% of all-time downloads
SinclairSchneider/Apertus-v1.5-70B-W4A16-KV8
Apertus-v1.5-70B-W4A16-KV8 is a text generation model from SinclairSchneider. Use it when you need the model to write or continue text. It is set up for transformers. The card lists the license as apache-2.0.
INT4 (W4A16, GPTQ) quantisation of swiss-ai/Apertus-v1.5-70B, calibrated on German text at 8192-token sequences, with static fp8 KV-cache scales written into the checkpoint.
Downloads · 30 days
31
11% of all-time downloads
All-time downloads
282
Public
Parameters
12.6B
43 GB on disk
Likes
0
Public
Click a slice to open those files.
.safetensors43 GB · 100%
How the weights are stored.
I3268.5B · 95%
From the Hugging Face model README
INT4 (W4A16, GPTQ) quantisation of
swiss-ai/Apertus-v1.5-70B,
calibrated on German text at 8192-token sequences, with static fp8
KV-cache scales written into the checkpoint.
German perplexity degrades 0.78% against the BF16 reference at 40 GiB — roughly a third of the model's original footprint. English degrades 9.98% over the same weights. That asymmetry is the point: the calibration is German, and German is what is preserved.
| German ppl | English ppl | Size | |
|---|---|---|---|
| BF16 reference | 6.1042 | 3.8545 | ~134 GiB |
| this checkpoint | 6.1520 (+0.78%) | 4.2392 (+9.98%) | 40 GiB |
| FP8 sibling | 6.3876 (+4.64%) | 3.9712 (+3.03%) | 71 GiB |
Take this one for German-language work. Take the FP8 sibling if English matters more — its weights are quantised data-free, so no language is favoured.
| Weights | 40 GiB (from ~134 GiB BF16) |
| Scheme | compressed-tensors / pack-quantized, INT4 symmetric, group size 128 |
| Activations | 16-bit, unquantised |
| KV cache | static fp8 E4M3, per-tensor, calibrated |
| Quantised modules | 480 Linears (80 decoder layers × 6) |
| Left in original precision | lm_head, embed_tokens, vision tokenizer (fp32), WavTokenizer (fp32) |
The vision and audio tokenizers are not quantised. Swiss AI note they are
precision-sensitive and stay in float32 on half-precision loads; the
WavTokenizer additionally uses weight-norm parametrised convolutions.
recipe.yaml in this repository records the exact recipe.
This checkpoint does not load with upstream transformers. Apertus 1.5 uses
Apertus1p5ForConditionalGeneration, whose config class ships only in Swiss
AI's transformers fork:
pip install "transformers[torch,vision,audio] @ \
git+https://github.com/swiss-ai/transformers.git@3797303dda74844e3d1f8977ff5518bb91f818b4"
You also need a vLLM with Apertus 1.5 support — Swiss AI's fork, upstream PR
#50496, or the prebuilt
image onpremai/vllm-apertus-1p5. Verify both before downloading 40 GiB:
from transformers.models.auto.configuration_auto import CONFIG_MAPPING_NAMES
from vllm.model_executor.models.registry import ModelRegistry
assert "apertus1p5" in CONFIG_MAPPING_NAMES, "wrong transformers"
assert "Apertus1p5ForConditionalGeneration" in ModelRegistry.get_supported_archs()
apertus alone in CONFIG_MAPPING_NAMES is Apertus 1.0 support and is not
sufficient.
vllm serve <path-or-repo> \
--dtype auto \
--enforce-eager \
--chat-template <path>/chat_template.jinja \
--chat-template-content-format string \
--limit-mm-per-prompt '{"image":0,"audio":0}' \
--enable-auto-tool-choice --tool-call-parser apertus \
--tensor-parallel-size 2 \
--max-model-len 98304 \
--gpu-memory-utilization 0.96
At 40 GiB this is ~20 GiB per GPU at TP=2, which leaves room for a large KV cache on 48 GB cards.
--kv-cache-dtype auto resolves to fp8 because this checkpoint carries KV
scales, roughly doubling the KV budget. Unlike the FP8 sibling, that is not free
here:
| German | English | |
|---|---|---|
| fp8 KV cost vs bf16 KV | +0.72% | +1.43% |
| Top-1 agreement between the two modes | 0.957 | 0.952 |
So the trade is roughly double the context for 0.7% German perplexity. Pass
--kv-cache-dtype bfloat16 to decline it. One checkpoint covers both.
On some builds vLLM's xIELU activation falls back to forward_native under
torch.compile and then emits a log line dynamo cannot trace:
torch._dynamo.exc.Unsupported: logging.Logger method not supported
Workaround: --enforce-eager. This is a vLLM/xIELU interaction, not a property
of the checkpoint.
Everything below was measured. Perplexity and top-1 agreement on 64 held-out
samples × 2048 tokens per language from wikimedia/wikipedia
(20231101.de / 20231101.en), 131,008 scored positions each. Generations
greedy (temperature=0, top_p=1, seed=0). Measured on one NVIDIA H200 (SM90)
under vLLM with --enforce-eager. Eval samples use a different shuffle seed
from the calibration set (1234 vs 42).
| config | German | Δ | English | Δ |
|---|---|---|---|---|
| BF16 reference | 6.1042 | — | 3.8545 | — |
| W4A16, bf16 KV | 6.1520 | +0.78% | 4.2392 | +9.98% |
| W4A16, fp8 KV | 6.1963 | +1.51% | 4.2998 | +11.55% |
Fraction of positions where the argmax token is unchanged. More sensitive than perplexity to damage that shows up as malformed morphology, and independent of the perplexity baseline.
| config | German | English |
|---|---|---|
| W4A16, bf16 KV | 0.9046 | 0.8786 |
| W4A16, fp8 KV | 0.8987 | 0.8716 |
Reported in absolute nats rather than as a ratio, because the two languages start from different baselines and a sharper distribution shows a larger relative rise from the same absolute damage.
| German | English | German as % of English | |
|---|---|---|---|
| Mean NLL increase (nats/token) | +0.0078 | +0.0951 | 8% |
Every W4A16 build in this family shows the same direction. The one exception is the FP8 sibling, whose weights are quantised data-free and where the asymmetry reverses — German damage becomes 152% of English. That contrast is the clearest evidence available here that the calibration data is doing the work, and it is why the FP8 build is worth having as a control.
Two GPTQ builds, identical in every respect except the calibration sequence length:
| Calibration | German | English | Top-1 de |
|---|---|---|---|
| 512 × 4096 tokens | +0.93% | +10.04% | 0.9088 |
| 512 × 8192 tokens | +0.78% | +9.98% | 0.9046 |
Longer calibration sequences improved German by 0.15 points. If your workload involves long inputs or long generations — this checkpoint was built for multi-thousand-token German report writing — calibrating at the depth you actually operate at is worth the extra runtime.
A separate AutoRound build calibrated at comparable depth landed at +0.79%, statistically indistinguishable from this one. The apparent advantage of AutoRound over GPTQ in an earlier comparison was calibration length, not the algorithm.
Two five-sentence German prompts, each requiring five specific terms, greedy. Fluent, grammatical German with well-formed nominal compounds; zero non-Latin token bleed in either KV mode.
Same base model, same German calibration corpus, same BF16 reference, same eval:
| Build | Weight scales | Activations | German | English | Size |
|---|---|---|---|---|---|
| W4A16 GPTQ, 8192 calib (this) | INT4, group-128 | 16-bit | +0.78% | +9.98% | 40 GiB |
| W4A16 AutoRound, 8192 calib | INT4, group-128 | 16-bit | +0.79% | +7.76% | 40 GiB |
| W4A16 GPTQ, 4096 calib | INT4, group-128 | 16-bit | +0.93% | +10.04% | 40 GiB |
| FP8 dynamic (sibling) | FP8, per-channel | FP8, per-token dynamic | +4.64% | +3.03% | 71 GiB |
| FP8 static (discarded) | FP8, per-tensor | FP8, static per-tensor | +13.73% | +9.87% | 71 GiB |
The ranking is monotonic in scale granularity, not bit width. Group-128 INT4 beats per-channel FP8, which beats per-tensor FP8, even though the bit widths run the other way. The last row also quantises activations statically, which compounds the problem: a fixed per-tensor activation scale must accommodate the largest outlier seen during calibration, crushing the resolution available to everything else.
English degrades roughly ten times as much as German. That is the intended trade, but it is a real cost. If your workload is bilingual, measure it, or use the FP8 sibling.
No standard benchmark suite. No MMLU, HellaSwag or similar. Perplexity, top-1 agreement and targeted German probes only.
Calibration corpus is general German, not domain text: 512 samples × 8192
tokens from German Wikipedia (wikimedia/wikipedia, 20231101.de), filtered to
samples that fill the full window. Domain-matched calibration would plausibly do
better on that domain and is untested here.
Evaluated at 2048-token sequences. The calibration is at 8192, but the perplexity eval is at 2048, so the long-context benefit is inferred from the calibration-length comparison rather than measured directly at depth. Not validated at the model's full 262,144-token window.
No English-calibrated control. German degrades far less than English on every calibrated build, and the data-free FP8 build reverses that — strong circumstantial evidence, but a matched English-calibrated build would settle it and does not exist.
Single-GPU measurement. All numbers from one NVIDIA H200 (SM90). Not tested under tensor parallelism, and not measured on Ada (SM89) despite that being the intended serving target.
The quantisation script is in this repository as
quantize_model.py.
# Isolated venv. The swiss-ai transformers fork is OLDER than current upstream
# releases and will replace whatever you have, so do not install it globally.
python3 -m venv venv && source venv/bin/activate
export PYTHONNOUSERSITE=1
pip install "transformers[torch,vision,audio] @ \
git+https://github.com/swiss-ai/transformers.git@3797303dda74844e3d1f8977ff5518bb91f818b4"
pip install llmcompressor datasets accelerate
hf auth login # swiss-ai repos are gated
Verify the pair imports before going further — this is the failure mode you are most likely to hit:
from compressed_tensors.quantization import is_cached_attention_module
from llmcompressor.modifiers.quantization import GPTQModifier
from transformers.models.auto.configuration_auto import CONFIG_MAPPING_NAMES
assert "apertus1p5" in CONFIG_MAPPING_NAMES
Runs the entire path — model load, module walk, GPTQ, serialisation, the output assertions — against a token-sized calibration set. Minutes rather than hours, and it catches environment problems before an eleven-hour run does.
MODEL=apertus-8b QUANT_TESTING=1 QUANT_KV_FP8=1 python quantize_model.py
Writes to TESTING-Apertus-v1.5-8B-W4A16-KV8-de, which is useless as a model but
structurally identical. Delete it afterwards.
MODEL=apertus-70b QUANT_SCHEME=w4a16 QUANT_KV_FP8=1 SEQLEN=8192 NSAMP=512 \
CUDA_VISIBLE_DEVICES=0 python quantize_model.py 2>&1 | tee quant.log
Output lands in Apertus-v1.5-70B-W4A16-KV8-de (this repository drops nothing
but the internal -seq8192 suffix used to distinguish it from a 4096-token
build). About 11 hours on one H200 and 40 GiB written. The calibration
set is streamed from German Wikipedia on the first run and cached as
calib_de_512x8192.parquet; subsequent runs reuse it.
Watch the first block for starting GPTQModifier on Apertus1p5TextModel: 480 Linears. If it reports a different module or a different count, the module walk
picked the wrong submodule and the encoders would be quantised — stop there.
Everything is an environment variable, so the same invocation works under tmux or a scheduler.
| Variable | Default | What it does |
|---|---|---|
MODEL | apertus-70b | apertus-8b, apertus-70b, mistral-medium-3.5. Anything else via MODEL_REPO + EXPECTED_LINEARS. |
QUANT_SCHEME | w4a16 | w4a16 — INT4 group-128 via GPTQ, what produced this checkpoint. fp8 — GPTQ with the FP8 preset: per-tensor weight scales and static activations, and it measured far worse (+13.73% German). fp8dyn — FP8_DYNAMIC, per-channel weights derived from min/max with no calibration, which is the FP8 sibling. |
QUANT_KV_FP8 | 0 | Compute static fp8 KV-cache scales from the calibration activations and write them into config.json. Must be set at build time — see the warning below about adding them afterwards. |
SEQLEN | 4096 | Calibration sequence length. 8192 here; the difference is worth 0.15 points of German perplexity (see above). |
NSAMP | 512 | Calibration samples. Below ~128 the Hessian estimate degrades. |
QUANT_CALIB_LANG | de | de or en. Only the Wikipedia config and the chars-per-token filter change, so a German and an English build differ in calibration language and nothing else. |
QUANT_TESTING | 0 | Smoke test, as above. |
QUANT_DAMPENING | 0.01 | Hessian diagonal damping. Raise to 0.05 if GPTQ throws a Cholesky or singular-matrix error. |
QUANT_OFFLOAD_HESSIANS | 0 | Hessians to host RAM. ~268 MB per Linear at hidden 8192, accumulating within a layer. Unnecessary on a 140 GiB card. |
OUT_SUFFIX | "" | Appended to the output directory. The name encodes scheme and KV but not calibration length, so builds differing only in SEQLEN collide; the script refuses to overwrite and this is how you distinguish them. |
The calibration corpus is the piece most worth changing. quantize_model.py
streams general German Wikipedia because that keeps a German-vs-English
comparison symmetric — but for a specific domain, replacing
build_calibration() with text from that domain is the single highest-leverage
edit, and it costs nothing extra at build time.
llmcompressor and compressed-tensors must be a matched pair, and vLLM pins
compressed-tensors exactly — do not install vLLM into the same venv, it will
drag the version back and break the import with
cannot import name 'is_cached_attention_module'.
The fused xIELU CUDA kernel is optional and several times faster, but note that
xielu 0.1 diverges in the backward pass at 70B scale — every block reports
loss iter 0: nan -> iter 0: 3.4028e38, meaning no optimisation happened. It is
fine at 8B and fine for GPTQ, which is forward-only. Uninstall it before any
gradient-based run at 70B.
Quantised with llm-compressor 0.13.0. These cost real time to find:
Apertus1p5ForConditionalGeneration and Apertus1p5Model both lack their own
forward(), so llm-compressor can trace neither. Pass
model.model.language_model (Apertus1p5TextModel) to oneshot(), then save
the parent so the wrapper config, lm_head and encoders are written too.audio_tokenizer even though the class
is WavTokenizerModel, so a regex on wavtokenizer silently matches nothing.
Targeting language_model avoids this structurally.name_or_path, so llm-compressor cannot auto-load a
processor. Pass processor=tok explicitly.save_pretrained must be patched on the parent via modify_save_pretrained(),
or the weights are written as fake-quantised BF16 — all of the quality loss,
none of the size saving. Check the output size, not just that it saved. The
patch uses functools.wraps, so __qualname__ still reports
PreTrainedModel.save_pretrained even when active; test the _overridden
attribute instead._retie_embeddings() compares input and output embeddings with torch.equal()
without aligning devices, and compression onloads modules to the GPU before it
runs. On an untied model it returns early anyway, so the script patches it to
skip.memoryless_minmax
recomputes scales that differ from the learned ones and remaps every weight
onto a different grid; one such attempt produced +326% perplexity. Build the KV
scales in the same pass as the weights.recipe = GPTQModifier(
targets="Linear",
scheme="W4A16",
ignore=["lm_head", "re:.*vision_tokenizer.*", "re:.*audio_tokenizer.*"],
kv_cache_scheme={"num_bits": 8, "type": "float", "strategy": "tensor",
"symmetric": True, "dynamic": False},
)
Inherits the licence and usage policy of
swiss-ai/Apertus-v1.5-70B.
The base model ships EU AI Act documentation and a public training-data summary;
consult the upstream repository for both.
Quantisation only — no fine-tuning, no additional training data, no modification of model behaviour beyond numerical precision.