Lab 03-01: From Hugging Face to GGUF¶
Objective¶
Take a model published in safetensors, convert it to GGUF F16 with convert_hf_to_gguf.py, check that the file is
complete (metadata, tokenizer, chat template), run it with llama cli and quantize it to Q8_0 and Q4_K_M. By the
end you will have done by hand what ggml-org and bartowski do for every model that comes out.
Prerequisites¶
llamainstalled (module 01);git,python3≥ 3.10 withvenv.- ~3 GB of disk (sources + CPU torch ≈ 2 GB, model ≈ 0.5 GB) and an internet connection.
- You have read section 4 of the module.
Materials¶
| What | Where | Size |
|---|---|---|
HuggingFaceTB/SmolLM2-135M-Instruct (Apache 2.0, Llama architecture): config.json, model.safetensors, tokenizer.json |
Hugging Face | ~270 MB (BF16) |
| llama.cpp sources (only for the Python scripts, we do not build them) | git clone |
~250 MB |
| Alternative with nothing to install: the GGUF-my-repo space | browser | – |
The model is small on purpose: conversion takes less than a minute and the F16 weighs ~270 MB. The steps are identical for an 8 B model (only with 16 GB of F16 to write to disk).
Steps¶
-
Sources and Python environment:
git clone --depth 1 https://github.com/ggml-org/llama.cpp ~/llama.cpp cd ~/llama.cpp python3 -m venv .venv && source .venv/bin/activate python3 -m pip install -r requirements.txt # CPU torch, transformers, gguf, safetensors… python3 convert_hf_to_gguf.py --help | head -40Expected in the help:
--outtype {f32,f16,bf16,q8_0,tq1_0,tq2_0,auto},--outfile,--remote,--mmproj,--vocab-only,--print-supported-models. Also trypython3 convert_hf_to_gguf.py --print-supported-models | grep -i llama. -
Download the model (or use
--remotein step 3):pip install -U "huggingface_hub[cli]" hf download HuggingFaceTB/SmolLM2-135M-Instruct --local-dir ~/modelli/smollm2-135m ls -la ~/modelli/smollm2-135mExpected:
config.json,generation_config.json,model.safetensors(~270 MB),tokenizer.json,tokenizer_config.json(containschat_template). Openconfig.json:"architectures": ["LlamaForCausalLM"],hidden_size 576,num_hidden_layers 30,torch_dtype bfloat16. -
Convert to F16:
python3 convert_hf_to_gguf.py ~/modelli/smollm2-135m --outtype f16 --outfile ~/modelli/smollm2-135m-f16.ggufExpected in the log: the architecture detection (
Model architecture: LlamaForCausalLM), one line per tensor (token_embd.weight, torch.bfloat16 --> F16, shape = {576, 49152},blk.0.attn_q.weight …), the tokenizer (gguf: … chat_template) and at the endModel successfully exported to …. Size ≈ 270 MB. Variant without download:python3 convert_hf_to_gguf.py --remote HuggingFaceTB/SmolLM2-135M-Instruct --outtype f16 --outfile ~/modelli/smollm2-135m-f16.gguf. -
Inspect the GGUF with the tools from lab 00-02:
Check:
general.architecture = llama,llama.block_count = 30,tokenizer.ggml.tokenswith 49,152 entries,tokenizer.chat_templatepresent,general.file_type = 1(F16), and in the tensor indexF16types for the weights andF32for the norms. -
Run it:
Expected: a banner with
ftype : F16, a sensible answer (it is a 135 M model: the answer is short and in English or in rough Italian) and the timing line. If the model rambles or does not stop, suspect number one is the chat template:llama cli -v … | grep example_format. -
Quantize to two types and compare the sizes:
llama quantize ~/modelli/smollm2-135m-f16.gguf ~/modelli/smollm2-135m-Q8_0.gguf Q8_0 llama quantize ~/modelli/smollm2-135m-f16.gguf ~/modelli/smollm2-135m-Q4_K_M.gguf Q4_K_M ls -la ~/modelli/smollm2-135m-*.ggufExpected:
Q8_0≈ 145 MB,Q4_K_M≈ 100 MB (the 49,152 × 576 embedding stays atq6_Kand the vocabulary weighs a lot in such a small model: the ratio is not 16:4). The two final linesmodel size/quant sizegive the bpw. -
Perplexity of the three files on the same text, to see what quantization costs on a tiny model:
for q in f16 Q8_0 Q4_K_M; do llama perplexity -m ~/modelli/smollm2-135m-$q.gguf -f ../lab-03-02-scala-quantizzazioni/testo-it.txt -c 128 -t 4 2>&1 | grep Final; doneExpected: F16 ≈ Q8_0 < Q4_K_M, with a proportionally larger gap than the one you see on the 0.8 B model in lab 03-02: the smaller the model, the more every bit counts.
Check the result¶
-
convert_hf_to_gguf.pyproduced a file withgeneral.architecture = llamaand the chat template. -
llama cli -mloads it and answers; the banner saysF16. - You have three files (F16, Q8_0, Q4_K_M) and can explain why Q4_K_M is not a quarter of F16.
- You have three perplexities in increasing order.
Questions and variants¶
- Why does the documentation recommend
--outtype auto(orbf16) instead off16for models trained in BF16? (Think about the range of representable values.) - What does
tokenizer_config.jsoncontain that ends up in the GGUF? Andgeneration_config.json, which does not? - Variant: convert a model with
--vocab-onlyand look at its size: it is a GGUF with only the tokenizer. - Docker variant:
docker run -v ~/modelli:/models ghcr.io/ggml-org/llama.cpp:full --all-in-one /models/ …as indocs/docker.md, or enter the image (--entrypoint bash) and usepython3 /app/convert_hf_to_gguf.py. - Browser variant: load the same repo in GGUF-my-repo, pick
Q4_K_M, and compare the produced file with yours (gguf-dump: same build? same type for the embedding?). - If you have a multimodal model (Gemma 3 4B, Qwen3.5-VL): add the
--mmproj --outtype q8_0step and tryllama cli -m … --mmproj … --image foto.jpg.
Deliverable¶
lab-03-01.md with: the first 20 lines of the conversion log; the gguf-dump output filtered on the keys
general.*, llama.block_count, tokenizer.chat_template (only the beginning); the llama cli answer; the table
file → size → bpw → perplexity; the answers to the first two questions.