Fine-Tuning LLMs on African Language Datasets
Why Fine-Tune?
A foundation model like Llama 3 70B or Mistral generates fluent English, French, and Arabic. Ask it to write in Swahili or translate a sentence to Yoruba, and quality drops off a cliff.
The reason is straightforward: training data distribution. The largest models are trained on corpora where English comprises 50-80% of tokens. African languages — even widely spoken ones like Swahili (200M speakers) — account for fractions of a percent. The model simply hasn’t seen enough examples to learn the grammar, idioms, and vocabulary of these languages.
Fine-tuning changes that. With as little as 10,000-50,000 high-quality examples in a target language, you can dramatically improve a model’s performance on African language tasks — translation, classification, summarisation, and generation. And thanks to Parameter-Efficient Fine-Tuning (PEFT), this is achievable on hardware you probably already have.
This builds on our earlier deep dives into Swahili NLP and Low-Resource NLP Techniques. Fine-tuning is the next logical step: once you know the landscape and the transfer strategies, you need a practical pipeline to adapt models to your specific language.
Available Datasets
The African NLP community — particularly the Masakhane project — has done the hard work of curating and annotating datasets. Here are the key resources for fine-tuning:
Masakhane datasets: A growing collection of annotated datasets for translation, NER, sentiment, and news classification across 50+ African languages. The Masakhane GitHub hosts the primary repositories.
MasakhaNER (2021, updated 2024): Named Entity Recognition annotations for 20+ African languages including Yoruba, Hausa, Igbo, Swahili, Amharic, and Wolof. Each language has ~10K-15K annotated sentences.
AfriQA: A question-answering dataset covering 10 African languages, built by translating and culturally adapting the SQuAD format. Useful for instruction-tuning LLMs to answer questions in African languages.
MasakhaNEWS: News topic classification (5 categories) for 15 African languages, with ~10K articles per language.
SERAKWAYE: A benchmark for African language understanding covering 42 languages across 3 tasks (sentiment, topic classification, NER). Designed specifically to evaluate multilingual models on African languages.
JW300: A parallel corpus of ~3.5M sentence pairs between English and 300+ languages, including extensive African language coverage. Ideal for translation fine-tuning.
For Swahili specifically, the datasets covered in our Swahili NLP post (CC-100, OSIAN, GlobalVoices, NLLB) provide additional pre-training material.
Three Key Techniques
1. LoRA and QLoRA (Parameter-Efficient Fine-Tuning)
LoRA (Low-Rank Adaptation) freezes the base model weights and injects trainable low-rank matrices into each transformer layer. Instead of updating 70B parameters, you train a few million. This means:
- Memory efficiency: A 7B model fine-tunes on 16GB GPU memory with LoRA
- Speed: Training is 2-4x faster since gradients only flow through LoRA weights
- Portability: The LoRA adapter is a ~25MB file — share it independently of the base model
QLoRA goes further by quantizing the base model to 4-bit (NF4 format) before applying LoRA. This enables fine-tuning a 70B parameter model on a single RTX 4090 (24GB) — something that would otherwise require 8× A100s.
2. Continued Pre-Training with Tokenizer Extension
Base model tokenizers rarely include African language vocabulary efficiently. Our Swahili example from the earlier post showed one word (hawakuwasomea) fragmenting into 7 subword tokens. This wastes context window and increases compute.
The solution is tokenizer extension:
- Train a new BPE tokenizer on a large African language corpus (e.g., OSIAN Swahili)
- Merge the new tokens into the base model’s tokenizer
- Run continued pre-training (masked language modeling or causal LM) on the extended vocabulary
This adds only the tokens that the original tokenizer handled poorly — typically a few thousand new tokens.
3. Instruction Tuning on Translated or Adapted Data
For task-specific fine-tuning (translation, QA, summarisation), convert your datasets into instruction format:
1
2
3
4
5
6
7
8
### Instruction:
Translate the following sentence from English to Swahili.
### Input:
The agricultural sector employs 60% of the workforce in East Africa.
### Response:
Sekta ya kilimo inaajiri asilimia 60 ya wafanyakazi katika Afrika Mashariki.
This format works with chat-tuned models (Llama, Mistral, Qwen) and produces dramatically better results than standard fine-tuning.
Practical Pipeline
Here’s an end-to-end pipeline using HuggingFace TRL (Transformer Reinforcement Learning) for QLoRA fine-tuning of a translation model:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import torch
from transformers import AutoTokenizer, BitsAndBytesConfig
from trl import SFTTrainer
from peft import LoraConfig, get_peft_model
from datasets import load_dataset
# ===== 1. Dataset Preparation =====
# Load JW300 Swahili-English parallel data
dataset = load_dataset("masakhane/jw300", "sw-en", split="train")
def format_instruction(example):
return {
"text": f"### Instruction:\nTranslate English to Swahili.\n\n### Input:\n{example['en']}\n\n### Response:\n{example['sw']}"
}
dataset = dataset.map(format_instruction)
# ===== 2. Tokenizer Extension (if needed) =====
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.2-3B")
# Check tokenizer efficiency on Swahili text
test_word = "hawakuwasomea"
tokens = tokenizer.tokenize(test_word)
print(f"Base tokenizer: {len(tokens)} tokens for '{test_word}'")
# If token count is high, extend the tokenizer here
# new_tokens = train_new_bpe(swahili_corpus)
# tokenizer.add_tokens(new_tokens)
tokenizer.chat_template = None # Use default
# ===== 3. QLoRA Configuration =====
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
)
lora_config = LoraConfig(
r=16, # rank of the LoRA matrices
lora_alpha=32, # scaling factor
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
# ===== 4. Training =====
trainer = SFTTrainer(
model="meta-llama/Llama-3.2-3B",
train_dataset=dataset,
tokenizer=tokenizer,
args=TrainingArguments(
output_dir="./swahili-llm-lora",
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
warmup_steps=100,
num_train_epochs=3,
learning_rate=2e-4,
fp16=True,
logging_steps=25,
save_strategy="epoch",
),
peft_config=lora_config,
dataset_text_field="text",
max_seq_length=1024,
)
trainer.train()
# ===== 5. Merge and Export =====
model = trainer.model.merge_and_unload()
model.save_pretrained("./swahili-llm-merged")
tokenizer.save_pretrained("./swahili-llm-merged")
Hardware Reality Check
The pipeline above uses Llama 3.2 3B as an example — it fine-tunes in about 2-3 hours on a single RTX 4090 (24GB) or RTX 3090.
For larger models, here’s the QLoRA footprint:
- 3B: 8-10 GB — RTX 3060 / any 8GB+ — ~2 hours (3 epochs, 50K examples)
- 7-8B: 14-16 GB — RTX 3090/4090 — ~4 hours
- 13B: 18-20 GB — RTX 4090 — ~6-7 hours
- 70B: 22-24 GB — RTX 4090 (just barely) — ~20-24 hours
This is the core message of our constrained-environment ML theme: you don’t need a cluster. A single consumer GPU, careful use of 4-bit quantization, and LoRA adapters let you produce production-quality African language models on hardware that fits under a desk.
The real constraint isn’t compute — it’s data. Investing in building high-quality African language datasets (via Masakhane, local universities, or community annotation drives) delivers far more impact than buying more GPUs.
Testing Your Fine-Tuned Model
After merging, evaluate on the SERAKWAYE benchmark or run a quick qualitative test:
1
2
3
4
5
6
7
8
from transformers import pipeline
pipe = pipeline("text-generation", model="./swahili-llm-merged")
prompt = "### Instruction:\nTranslate to Swahili.\n\n### Input:\nI would like to buy maize and beans.\n\n### Response:\n"
result = pipe(prompt, max_new_tokens=50)[0]["generated_text"]
print(result)
# Expected: "Ningependa kununua mahindi na maharagwe."
Compare the output against the base model without fine-tuning. The difference is dramatic — and it’s achievable with a weekend’s work and a single GPU.
Summary
Fine-tuning LLMs on African languages is one of the highest-leverage activities in NLP today. The community datasets exist, the techniques (LoRA/QLoRA, tokenizer extension, instruction tuning) are mature, and the hardware requirements are modest. The gap between base model performance and what’s possible after fine-tuning is enormous — largely because the base models were never trained on your target language in the first place.
If you’ve been reading through this series — from Swahili NLP through Low-Resource NLP to this post — you now have the full toolkit: understanding the linguistic challenges, transfer learning strategies, and a practical fine-tuning pipeline. The next step is picking a language and running the experiment.
Next in the series: Evaluating African Language LLMs — benchmarks, human evaluation protocols, and building trust in model outputs for production deployment.
