Zebra-Llama: Hybrid Recurrent-Attention Models on AMD GPUs#
Zebra-Llama is a family of hybrid models that combine recurrent layers (Mamba SSM, KDA, or GDN) with Multi-Latent Attention (MLA) and SwiGLU MLP layers. These models are designed to achieve competitive quality with sub-quadratic inference cost.
This guide covers the complete workflow: environment setup, data preparation, pretraining, checkpoint conversion, and evaluation.
FLA-validated recipes: For runnable, FLA-parity-validated walkthroughs of the pure-recurrent variants, see Pure GDN guide and Pure KDA guide. For the exhaustive list of code/config/runtime changes required for exact parity with the Flash Linear Attention (FLA) reference implementation, see GDN ⇄ FLA parity and KDA ⇄ FLA parity.
Table of Contents#
Architecture Overview#
Zebra-Llama interleaves three types of layers in a repeating pattern:
[Attention] [MLP] [Recurrent] [MLP] [Recurrent] [MLP] ... [Attention] [MLP] ...
Recurrent layers — one of Mamba SSM, Kimi Delta Attention (KDA), or Gated Delta Net (GDN)
Attention layers — Multi-Latent Attention with YaRN rotary embeddings and LoRA-compressed KV
MLP layers — SwiGLU feed-forward with Transformer Engine fused norms
The hybrid_attention_ratio parameter controls what fraction of recurrent+attention layer pairs use attention (default 0.25 = 1 attention layer per 3 recurrent layers). Setting it to 0.0 yields a pure recurrent model (all KDA or GDN), while 1.0 yields a pure MLA attention model.
Available Configurations#
Pretrain Configs (examples/megatron/configs/MI300X/)#
Config |
Model |
Recurrent Type |
Seq Length |
Params |
Tokenizer |
|---|---|---|---|---|---|
|
1B (Mamba+MLA) |
Mamba SSM |
2048 |
~1B |
|
|
1B (KDA+MLA) |
Kimi Delta Attention |
8192 |
~1B |
|
|
1B (pure KDA) |
Kimi Delta Attention |
2048 |
~1.2B |
|
|
1B (GDN only) |
Gated Delta Net |
8192 |
~1B |
|
|
1B (pure GDN) |
Gated Delta Net |
2048 |
~1.2B |
|
|
3B (Mamba+MLA) |
Mamba SSM |
8192 |
~3B |
|
|
8B (Mamba+MLA) |
Mamba SSM |
8192 |
~8B |
|
Model Configs (primus/configs/models/megatron/)#
Config |
Layers |
Hidden |
FFN |
Attention Ratio |
Attention Type |
|---|---|---|---|---|---|
|
32 |
2048 |
8192 |
0.25 |
MLA |
|
32 |
2048 |
8192 |
0.0 (pure KDA) |
None |
|
32 |
2048 |
8192 |
0.0 (pure GDN) |
None |
|
32 (16 GDN+16 MLP) |
2048 |
8192 |
0.0 (pure GDN) |
None |
|
56 |
3072 |
8192 |
0.25 |
MLA |
|
64 |
4096 |
14436 |
0.25 |
MLA |
Note on pure KDA: The
zebra_llama_1B_kda_pureconfig matches FLA’skda_1B_pure.jsonarchitecture (16 KDA layers,head_dim=32for keys,head_dim=64for values, tied embeddings,norm_eps=1e-6). It uses the FLA Triton kernel (use_fla_triton_kda: true) for fused forward+backward during training.
Note on pure GDN: The
zebra_llama_1B_gdn_pureconfig matches FLA’sgated_deltanet_1B_pure.jsonarchitecture (16 GDN + 16 MLP layers,num_heads=8,num_v_heads=16, short convolution with kernel size 4, tied embeddings). This config has been validated end-to-end against FLA on MI300X — the training loss curves match within ~1% across 76K steps on FineWeb-Edu 10BT. See Step 4 for conversion to FLA’s HuggingFace format.
Prerequisites#
Hardware: AMD Instinct MI300X (or compatible ROCm GPUs)
Software: ROCm drivers >= 7.0, Docker >= 24.0
HuggingFace Token: Required for gated tokenizers (
HF_TOKEN)Disk Space: ~50 GB for FineWeb-Edu 10BT tokenized data
Step 1: Environment Setup#
1.1 Pull the Docker Image#
docker pull docker.io/rocm/primus:v25.10
1.2 Clone the Repository#
git clone --recurse-submodules https://github.com/AMD-AIG-AIMA/Primus.git
cd Primus
1.3 Start a Development Container#
# Quick start (mounts Primus into /workspace/Primus)
bash tools/docker/start_container.sh
This creates a persistent container named dev_primus_<user>. You can customize it with environment variables:
DOCKER_IMAGE=docker.io/rocm/primus:v25.10 \
DATA_PATH=/path/to/data \
bash tools/docker/start_container.sh
Then exec into the container:
docker exec -it dev_primus_$(whoami) bash
cd /workspace/Primus
1.4 Install Python Dependencies (inside container)#
pip install -r requirements.txt
For GDN models (required for the Triton kernel and FLA model classes):
pip install flash-linear-attention
For evaluation, also install:
pip install lm-eval
Step 2: Dataset Preparation#
Zebra-Llama uses the FineWeb-Edu dataset, preprocessed into Megatron binary format.
2.1 Set Up Environment#
export HF_TOKEN="hf_your_token_here"
export PYTHONPATH="$(pwd)/third_party/Megatron-LM:${PYTHONPATH}"
2.2 Run Data Preparation#
python examples/megatron/prepare_fineweb_edu.py \
--primus-path . \
--data-path ./data \
--tokenizer-type HuggingFaceTokenizer \
--tokenizer-model meta-llama/Llama-3.2-1B \
--sample-size 10BT
This will:
Download the FineWeb-Edu 10BT dataset from HuggingFace
Tokenize it into Megatron binary format (
.bin+.idxfiles)Output files to
./data/fineweb-edu-10BT/HuggingFaceTokenizer/
Available sample sizes: 10BT, 100BT, 350BT
The script uses all available CPU cores by default. To limit parallelism, add --workers N.
Note: The context length (sequence length) is not set during data prep. It is configured at training time via
seq_lengthin your pretrain YAML.
2.3 Using a Different Tokenizer#
For the GDN config which uses fla-hub/gla-1.3B-100B:
python examples/megatron/prepare_fineweb_edu.py \
--primus-path . \
--data-path ./data \
--tokenizer-type HuggingFaceTokenizer \
--tokenizer-model fla-hub/gla-1.3B-100B \
--sample-size 10BT
2.4 FLA-Aligned Data for Pure GDN (Recommended)#
When training a pure GDN model for comparison against FLA, it is critical that both frameworks see identical tokens in the same order. The standard Megatron data pipeline produces different token ordering than FLA’s preprocess.py (which shuffles with seed=42 and concatenates into fixed-length chunks without EOD tokens). This difference alone can cause persistent training loss divergence.
To ensure exact alignment:
Step 1: Preprocess with FLA (if not already done):
cd /path/to/flash-linear-attention/legacy/training
python preprocess.py \
--dataset HuggingFaceFW/fineweb-edu \
--name sample-10BT \
--tokenizer meta-llama/Llama-3.2-1B \
--seq_len 2048 --num_proc 64
This produces an Arrow dataset under data/HuggingFaceFW/fineweb-edu/sample-10BT/train/.
Step 2: Convert FLA’s Arrow data to Megatron binary format:
python convert_fla_to_megatron.py
Note: Edit the
FLA_DATAandOUT_PREFIXpaths at the top ofconvert_fla_to_megatron.pyto match your environment before running.
This reads the Arrow shard files directly with PyArrow and produces Megatron-compatible .bin + .idx files. Each FLA 2048-token sequence becomes one Megatron “document”. The script verifies token-level consistency after writing.
Step 3: Point the pretrain config at the converted data:
train_data_path: >
/path/to/data/fla_aligned/fla_fineweb_edu_10BT_text_sentence
mock_data: false
2.5 Update Data Paths in Config#
After preparation (standard or FLA-aligned), update the train_data_path in your pretrain config YAML to point to the generated files:
# Standard Megatron data prep (multiple shards)
train_data_path: >
/path/to/data/fineweb-edu-10BT/HuggingFaceTokenizer/fineweb_edu_10BT_0_text_sentence
/path/to/data/fineweb-edu-10BT/HuggingFaceTokenizer/fineweb_edu_10BT_1_text_sentence
/path/to/data/fineweb-edu-10BT/HuggingFaceTokenizer/fineweb_edu_10BT_2_text_sentence
/path/to/data/fineweb-edu-10BT/HuggingFaceTokenizer/fineweb_edu_10BT_3_text_sentence
mock_data: false
Step 3: Pretraining#
Single-Node (Local / Docker)#
Launch training inside a Docker container on a single node:
# Zebra-Llama 1B with KDA (Kimi Delta Attention)
export DATA_PATH=./data
GPUS_PER_NODE=8 HF_TOKEN=$HF_TOKEN \
./primus-cli container --volume "$DATA_PATH:$DATA_PATH" \
--env DATA_PATH \
-- train pretrain --config examples/megatron/configs/MI300X/zebra_llama_1B_kda-pretrain.yaml
Other model variants (same launcher, different --config):
# Zebra-Llama 1B with Mamba SSM
./primus-cli container -- train pretrain \
--config examples/megatron/configs/MI300X/zebra_llama_1B-pretrain.yaml
# Zebra-Llama 1B with pure KDA (no attention layers)
./primus-cli container -- train pretrain \
--config examples/megatron/configs/MI300X/zebra_llama_1B_kda_pure-pretrain.yaml
# Zebra-Llama 1B with GDN (pure recurrent, no attention)
./primus-cli container -- train pretrain \
--config examples/megatron/configs/MI300X/zebra_llama_1B_gdn-pretrain.yaml
# Zebra-Llama 1B pure GDN (FLA-validated, 4-GPU)
GPUS_PER_NODE=4 ./primus-cli container -- train pretrain \
--config examples/megatron/configs/MI300X/zebra_llama_1B_gdn_pure-pretrain.yaml
# Zebra-Llama 3B
./primus-cli container -- train pretrain \
--config examples/megatron/configs/MI300X/zebra_llama_3B-pretrain.yaml
# Zebra-Llama 8B
./primus-cli container -- train pretrain \
--config examples/megatron/configs/MI300X/zebra_llama_8B-pretrain.yaml
Multi-Node (Slurm)#
For multi-node training on a Slurm cluster:
export DATA_PATH=/shared/data
./primus-cli slurm srun -N 2 \
-- container --volume "$DATA_PATH:$DATA_PATH" \
--env DATA_PATH \
-- train pretrain --config examples/megatron/configs/MI300X/zebra_llama_1B_kda-pretrain.yaml
Ensure the global_batch_size in your config is divisible by micro_batch_size * GPUS_PER_NODE * NNODES.
If Already Inside a Container#
If you are already inside a Docker container or on a bare-metal node with the environment set up, use direct mode instead of container — it skips the container launch and runs torchrun in place:
./primus-cli direct -- train pretrain \
--config examples/megatron/configs/MI300X/zebra_llama_1B_kda-pretrain.yaml
Mock Data (Smoke Test)#
To quickly verify the model runs without real data, the 3B and 8B configs come with mock_data: true by default. For the 1B configs, you can override on the command line — every argument after --config is forwarded to the Primus Python CLI:
./primus-cli container -- train pretrain \
--config examples/megatron/configs/MI300X/zebra_llama_1B_kda-pretrain.yaml \
--mock_data true --train_iters 10
Key Training Parameters#
Parameter |
Description |
Typical Values |
|---|---|---|
|
Total training iterations |
38147 (1B KDA pure), 400000 (1B Mamba) |
|
Per-GPU batch size |
4 (KDA/GDN), 16 (Mamba 1B) |
|
Total batch size across all GPUs |
micro_batch_size * num_gpus |
|
Sequence length |
2048, 4096, 8192 |
|
Peak learning rate |
2.0e-4 |
|
Checkpoint save frequency |
1000 |
|
Auto-resume from last checkpoint on crash |
|
|
Fraction of attention layers (0.0 = pure recurrent) |
0.0, 0.25 |
Step 4: Checkpoint Conversion to HuggingFace#
Convert a Megatron checkpoint to HuggingFace format for inference and evaluation.
4.1 Convert Checkpoint#
Pure GDN Models#
Pure GDN models use a dedicated converter that maps Primus’s fused projections to FLA’s native GatedDeltaNetForCausalLM format:
python tools/hybrid/convert_gdn_to_fla_hf.py \
--checkpoint-path output/amd/root/zebra_llama_1B_gdn_pure-pretrain/checkpoints/iter_0076294 \
--output-dir output/gdn_pure_1B_fla_hf \
--config /path/to/gated_deltanet_1B_pure.json
This handles:
Splitting the fused
in_proj(3104 → q/k/v/gate/beta/alpha projections)Splitting the fused
conv1d(q/k/v convolutions)Splitting the fused SwiGLU
fc1(gate_proj + up_proj)Mapping alternating GDN/MLP sublayers to combined FLA layers
Handling tied embeddings
After conversion, verify with the sanity check:
python tools/hybrid/verify_gdn_conversion.py --model-path output/gdn_pure_1B_fla_hf
Expected output: Loss ~2-4, top prediction for “The capital of France is” should be “Paris”.
KDA / Hybrid Models#
The general converter auto-detects architecture from the checkpoint’s saved arguments:
# KDA+MLA hybrid model
python tools/hybrid/convert_zebra_llama_to_hf.py \
--checkpoint-path output/zebra_llama_1B_kda-pretrain/iter_0028000 \
--output-dir output/zebra_llama_1B_kda_hf_iter_0028000
# Pure KDA model
python tools/hybrid/convert_zebra_llama_to_hf.py \
--checkpoint-path output/zebra_llama_1B_kda_pure-pretrain/iter_0038000 \
--output-dir output/zebra_llama_1B_kda_pure_hf
The converter will:
Read the Megatron checkpoint and training arguments
Auto-detect architecture parameters (
hybrid_attention_ratio,kda_num_heads,q_lora_rank, etc.)Remap parameter names from Megatron conventions to HuggingFace conventions
Save
pytorch_model.bin,config.json, and a model cardREADME.mdin the output directoryCopy
modeling_zebra_llama.pyinto the output directory fortrust_remote_codeloading
4.2 Verify Conversion#
The script prints a summary of missing, extra, and shape-mismatched keys. A successful conversion shows:
0 missing, 0 extra, 0 shape mismatches
4.3 Supported Architectures#
Architecture |
|
Layer pattern |
|---|---|---|
Pure KDA |
|
All KDA + MLP |
KDA + MLA hybrid |
|
Mix of KDA and MLA + MLP |
Pure MLA |
|
All MLA + MLP |
Pure GDN |
|
All GDN + MLP |
Mamba + MLA hybrid |
|
Mix of Mamba and MLA + MLP |
Step 5: Evaluation with lm-eval-harness#
5.1 Pure GDN Models (FLA format)#
Pure GDN models use a dedicated eval wrapper (tools/hybrid/eval_gdn_lm_eval.py) that pre-registers FLA’s GatedDeltaNetForCausalLM with transformers’ AutoModel and patches compatibility issues with transformers >= 4.55:
python tools/hybrid/eval_gdn_lm_eval.py \
--model hf \
--model_args pretrained=output/gdn_pure_1B_fla_hf,trust_remote_code=True,tokenizer=meta-llama/Llama-3.2-1B \
--tasks arc_easy,arc_challenge,hellaswag,mmlu,openbookqa,piqa,race,winogrande \
--batch_size auto \
--output_path eval_results/gdn_pure_1B
Note: Do not use
lm_eval --model hfdirectly — it will fail becauseAutoConfigdoes not recognizegated_deltanetwithout FLA being imported first. The wrapper handles this. Thetokenizer=meta-llama/Llama-3.2-1Bargument is required since the converted model directory does not contain tokenizer files.
5.2 KDA / Hybrid Models (Zebra-Llama format)#
KDA and hybrid models use the custom ZebraLlamaForCausalLM architecture, which requires a dedicated lm-eval wrapper:
python3 tools/hybrid/lm_harness_eval.py --model zebra_llama \
--model_args pretrained=output/zebra_llama_1B_kda_pure_hf,dtype=bfloat16 \
--tasks arc_easy,arc_challenge,hellaswag,mmlu,openbookqa,piqa,race,winogrande \
--batch_size auto
5.3 Using the Eval Shell Script (KDA/Hybrid)#
bash tools/hybrid/eval_zebra_llama_lm_eval.sh \
--checkpoint output/zebra_llama_1B_kda_pure_hf \
--tasks arc_easy,arc_challenge,hellaswag,mmlu,openbookqa,piqa,race,winogrande \
--batch-size auto \
--dtype bfloat16 \
--output eval_results/zebra_llama_1B_kda_pure
Important: The eval script internally invokes
python3 tools/hybrid/lm_harness_eval.py --model zebra_llama(notlm_eval --model hf). This ensures the custom model architecture is properly registered.
5.4 Available Benchmarks#
Task |
Description |
Metric |
|---|---|---|
|
ARC Easy (science QA) |
acc, acc_norm |
|
ARC Challenge (harder science QA) |
acc, acc_norm |
|
HellaSwag (commonsense NLI) |
acc, acc_norm |
|
MMLU (57 subject knowledge benchmark) |
acc |
|
OpenBookQA |
acc, acc_norm |
|
PIQA (physical intuition QA) |
acc, acc_norm |
|
RACE (reading comprehension) |
acc |
|
Winogrande (coreference resolution) |
acc |
5.5 Memory Considerations#
The pure-PyTorch KDA chunked attention is memory-intensive. If you encounter OOM errors:
Use
--batch_size autoto let lm-eval find the largest fitting batch sizeReduce
max_length(e.g.,max_length=1024in--model_args)Reduce
--batch_sizeto 1
Configuration Reference#
Hybrid Layer Specs#
The spec field in the pretrain config selects the layer arrangement:
Spec |
Description |
|---|---|
|
Mamba SSM + MLA hybrid |
|
KDA + MLA hybrid (or pure KDA with |
|
GDN + MLA hybrid (or pure GDN with |
Environment Variables#
Variable |
Description |
Default |
|---|---|---|
|
Docker image for training |
|
|
Path to experiment config YAML |
|
|
Path to dataset directory |
|
|
HuggingFace API token |
(required for gated models) |
|
Weights & Biases API key |
(optional) |
|
Number of GPUs per node |
8 |
|
Number of nodes |
1 |
|
Master node address |
|
|
Master node port |
|
Troubleshooting#
OOM During Training#
Reduce
micro_batch_sizeorseq_lengthEnable activation checkpointing: add
recompute_granularity: selectiveto the config
OOM During Evaluation#
Use
--batch_size 1or--batch_size autoAdd
max_length=1024to--model_args
ModuleNotFoundError: No module named 'megatron'#
Set the Python path before running data preparation:
export PYTHONPATH="$(pwd)/third_party/Megatron-LM:${PYTHONPATH}"
Checkpoint Conversion Shape Mismatches#
Ensure the modeling_zebra_llama.py model definition matches the architecture of your checkpoint (Mamba vs KDA vs GDN). The converter auto-detects architecture from checkpoint args, but the HF model code in tools/hybrid/modeling_zebra_llama.py must support the target architecture. Common causes of shape mismatches:
Mismatched
hybrid_attention_ratiobetween config and checkpointIncorrect
kda_num_headsor head dimension settingsUsing a
modeling_zebra_llama.pythat doesn’t support the checkpoint’s attention type
ValueError: model type 'zebra_llama' not recognized#
This occurs when using lm_eval --model hf directly instead of the custom wrapper. Always use:
python3 tools/hybrid/lm_harness_eval.py --model zebra_llama ...
Or the eval shell script, which handles this automatically.
Truncation Warnings During Eval#
Messages like Combined length of context and continuation exceeds model's maximum length mean some eval samples are being truncated. This has minimal impact on most benchmarks but can affect long-context tasks like RACE. To avoid truncation, increase max_length in --model_args.
NCCL / RCCL Timeout During Training#
On MI300X, intermittent RCCL hangs can occur (typically during checkpoint saves). Mitigations:
Set
auto_continue_train: truein the pretrain config to auto-resume from the last checkpointIncrease the heartbeat timeout:
export TORCH_NCCL_HEARTBEAT_TIMEOUT_SEC=7200
File Reference#
Primus/
├── examples/megatron/
│ ├── configs/MI300X/
│ │ ├── zebra_llama_1B-pretrain.yaml # 1B Mamba+MLA
│ │ ├── zebra_llama_1B_kda-pretrain.yaml # 1B KDA+MLA hybrid
│ │ ├── zebra_llama_1B_kda_pure-pretrain.yaml # 1B pure KDA
│ │ ├── zebra_llama_1B_gdn-pretrain.yaml # 1B GDN
│ │ ├── zebra_llama_1B_gdn_pure-pretrain.yaml # 1B pure GDN (FLA-validated)
│ │ ├── zebra_llama_3B-pretrain.yaml # 3B Mamba+MLA
│ │ └── zebra_llama_8B-pretrain.yaml # 8B Mamba+MLA
│ ├── prepare_fineweb_edu.py # Data preparation script
│ ├── prepare_fineweb_edu.sh # Data prep shell wrapper
│ └── preprocess_data.py # Megatron tokenizer
├── primus/configs/models/megatron/
│ ├── zebra_llama_1B.yaml # 1B model architecture
│ ├── zebra_llama_1B_kda_pure.yaml # 1B pure KDA architecture
│ ├── zebra_llama_1B_gdn.yaml # 1B GDN architecture
│ ├── zebra_llama_1B_gdn_pure.yaml # 1B pure GDN (FLA-validated)
│ ├── zebra_llama_3B.yaml # 3B model architecture
│ └── zebra_llama_8B.yaml # 8B model architecture
├── tools/
│ ├── hybrid/
│ │ ├── convert_zebra_llama_to_hf.py # Megatron → HF converter (KDA/hybrid)
│ │ ├── convert_gdn_to_fla_hf.py # Megatron → FLA HF converter (pure GDN)
│ │ ├── verify_gdn_conversion.py # Post-conversion sanity check (pure GDN)
│ │ ├── eval_gdn_lm_eval.py # lm-eval wrapper for GDN (registers FLA)
│ │ ├── convert_zebra_llama_to_hf.sh # Converter shell wrapper
│ │ ├── modeling_zebra_llama.py # HF model definition (KDA/hybrid)
│ │ ├── lm_harness_eval.py # lm-eval wrapper
│ │ ├── eval_zebra_llama_lm_eval.sh # Eval shell wrapper
│ │ ├── run_zebra_eval.sh # Quick eval script
│ │ ├── chat_zebra_llama.py # Interactive chat
│ │ └── convert_fla_to_megatron.py # FLA Arrow → Megatron binary converter
│ └── docker/start_container.sh # Dev container launcher
├── runner/
│ ├── primus-cli # Unified launcher (direct/container/slurm)
│ └── helpers/envs/base_env.sh # NCCL/ROCm/cache env defaults
└── requirements.txt # Python dependencies