Pure KDA 300M on Primus — End-to-End Guide (FLA-validated)#
This document is a runnable walkthrough for the 300M pure Kimi Delta Attention (KDA) pretraining recipe in Primus, validated on 8× AMD MI300X against the Flash Linear Attention (FLA) reference implementation. It covers every step from raw dataset → tokenization → training → checkpoint conversion → lm-eval benchmark.
The same recipe scales up to the 1B pure-KDA config (kda_1B_BF16-pretrain.yaml).
It mirrors gdn-guide.md and reuses the same Megatron-LM
patches, dataset shim, FLA-init flow, and lm-eval wrapper pattern.
Final result#
After 4768 iterations (≈10B tokens) on FineWeb-Edu sample-10BT:
Axis |
FLA reference |
Primus (this branch) |
Δ |
|---|---|---|---|
Per-iteration time (steady state, iter > 200) |
1493 ms |
1466.8 ms |
−1.8 % (Primus faster) |
Throughput |
175,617 tok/s/GPU |
178,810 tok/s/GPU |
+1.8 % |
TFLOP/s/GPU |
— |
626.9 |
— |
Wall time (4768 iters, 8× MI300X, healthy node) |
1h 58m 39s |
1h 56m 33s |
−126 s |
Loss @ iter 1 |
11.9673 |
11.9669 |
−0.00 % (bit-perfect) |
Loss @ iter 4700 (final logged) |
3.3388 |
3.3624 |
+0.71 % |
First Primus-below-FLA crossover |
— |
iter 2600 |
— |
Loss trajectories overlap from iter ~2000 onward; the only persistent gap
is in the LR-warmup region (iter 50–500) and closes monotonically. See
kda-fla-parity.md for the deep-dive on every
patch and env var.
lm-eval-harness (FLA-paper 8-task suite)#
Random chance is 100 / num_choices — 25 % for the 4-choice tasks
(arc, hellaswag, openbookqa, mmlu, race) and 50 % for the 2-choice tasks
(piqa, winogrande). Any score above random shows the model has learned
something; the FLA and Primus rows show how closely the two training
stacks track each other on the same 10 B-token diet.
Task |
Metric |
Random |
FLA |
Primus |
Δ (Primus − FLA) |
|---|---|---|---|---|---|
arc_challenge |
acc_norm |
25.00 |
25.17 |
25.00 |
−0.17 pp |
arc_easy |
acc |
25.00 |
48.78 |
47.94 |
−0.84 pp |
arc_easy |
acc_norm |
25.00 |
42.76 |
43.39 |
+0.63 pp |
hellaswag |
acc_norm |
25.00 |
29.16 |
29.18 |
+0.02 pp |
openbookqa |
acc_norm |
25.00 |
30.40 |
29.00 |
−1.40 pp |
piqa |
acc_norm |
50.00 |
60.99 |
60.34 |
−0.65 pp |
winogrande |
acc |
50.00 |
51.85 |
52.72 |
+0.87 pp |
mmlu (aggregate) |
acc |
25.00 |
22.88 |
23.12 |
+0.24 pp |
race |
acc |
25.00 |
25.07 |
25.45 |
+0.38 pp |
mean absolute Δ |
0.58 pp |
Every task within ±1.4 pp — well inside the ±1.5 pp tolerance set by the 0.49% mid-training loss delta. Both stacks comfortably beat random on arc_easy, hellaswag, openbookqa and piqa; mmlu/race/arc_challenge are at random-chance for both training stacks (expected for a 300 M model on only 10 B tokens — those benchmarks need 7 B+ parameters and/or trillion-token training to lift above 25 %).
Table of contents#
Overview#
The 300M pure-KDA model has:
12 Kimi-Delta-Attention blocks + 12 MLP blocks → 24 Megatron “sublayers”
hidden_size = 1024,ffn_hidden_size = 4096num_heads = num_v_heads = 8(Q, K, V all share head count)head_k_dim = 32,head_v_dim = 64(expand_v = 2.0)Short-conv kernel size 4 (depthwise, on the concatenated QKV)
Per-head output gate (
g_a → g_b) + per-head decay gate (f_a → f_b, combined with learnableA_loganddt_biasviasoftplus)Tied embeddings, no positional encoding (delta-rule recurrence), RMSNorm with
eps = 1e-6Tokenizer:
meta-llama/Llama-3.2-1B(128k vocab)Total parameters: 0.302 B
Training schedule (matched to FLA’s kda_300M.json):
4768 iterations × 1024 global batch × 2048 seq len = 10.0 B tokens
AdamW (β1=0.9, β2=0.95, wd=0.01), peak LR
2e-4, cosine decay, 200-step warmupBF16 training, no dropout, gradient clip 1.0
Prerequisites#
Hardware: 8× AMD MI300X (or compatible ROCm GPU) on a single node
Software: ROCm ≥ 7.0, Docker ≥ 24.0
Container image:
rocm/primus:v26.2(orv25.10with the same patches)HF token:
HF_TOKENset for the gatedmeta-llama/Llama-3.2-1BtokenizerDisk: ~20 GB for the FLA-aligned tokenized dataset + ~5 GB per saved checkpoint
flash-linear-attention checked out at
/home/<user>/flash-linear-attention(or installed viapip install -e .) — provides the FLAKDAForCausalLMclass for HF conversion + lm-eval, plus the Triton kernels that thePRIMUS_FLA_*toggles route into.
Step 1: Environment#
1.1 Start the dev container#
docker run -it \
--device /dev/dri --device /dev/kfd \
--device=/dev/infiniband --network host --ipc host \
--group-add video --cap-add SYS_PTRACE \
--security-opt seccomp=unconfined --privileged \
-v $HOME:$HOME -v $(pwd):$(pwd) -w $(pwd) --shm-size 64G --name primus_hybrid_new \
rocm/primus:v26.2
This runs the rocm/primus:v26.2 image with /dev/dri, /dev/kfd, IB
devices, --privileged, your $HOME mounted in-place, and --shm-size 64G.
The container is named primus_hybrid_new.
To re-attach later:
docker exec -it primus_hybrid_new bash
cd /home/<user>/Primus
1.2 Install Python dependencies inside the container#
pip install -r requirements.txt
pip install -e /home/<user>/flash-linear-attention # FLA model classes + Triton kernels
pip install lm-eval # for benchmark evaluation
The editable FLA install removes the need to set PYTHONPATH for every
later command.
Step 2: Dataset preparation#
Identical to the GDN recipe — see
gdn-guide.md. KDA reuses the
same FineWeb-Edu sample-10BT preprocessed Arrow shards and the same
Llama-3.2-1B tokenizer.
The default 300M YAML already points at the FLA-aligned binary:
train_data_path: >
/home/<user>/Primus/data/fla_aligned/fla_fineweb_edu_10BT_text_sentence
(adjust the user prefix in
examples/megatron/configs/MI300X/kda_300M_BF16-pretrain.yaml
to match your home directory).
Step 3: Megatron-LM patches (automatic — no action needed)#
KDA uses the same six patches as GDN — no KDA-specific Megatron patch
is required. They’re implemented as runtime monkey-patches using Primus’s
own patch system (primus/core/patches), living under
[primus/backends/megatron/patches/](https://github.com/AMD-AGI/Primus/tree/main/primus/backends/megatron/patches).
Each patch registers unconditionally but is gated behind a condition= on
the relevant config flag, so nothing needs to be run by hand.
See gdn-guide.md
§3 for the patch-by-patch breakdown.
Step 4: (Optional) Initialize from FLA weights#
For bit-perfect iter-1 loss alignment, the validated run loads FLA’s
initialized but untrained KDA-300M checkpoint and then trains from
there. The YAML’s load: field points at this directory:
load: /home/<user>/Primus/output/fla_init_kda_300M
finetune: true # load weights, ignore optimizer state and iteration count
no_load_optim: true
no_load_rng: true
Generate it once with:
python tools/hybrid/convert_fla_kda_init_to_megatron.py
# → output/fla_init_kda_300M/iter_0000000/mp_rank_00/model_optim_rng.pt
The script instantiates FLA’s KDAForCausalLM with seed=42, harvests
its randomly-initialized weights, concatenates the six FLA hidden_states → X projections into Primus’s single fused in_proj, and writes a
Megatron-shape checkpoint. Skip this step if you’re happy with Primus’s
own random init — final loss is identical, only iter-1 drifts by ~5e-3.
Step 5: Train#
5.1 Inspect the config#
The training config lives at
examples/megatron/configs/MI300X/kda_300M_BF16-pretrain.yaml.
Key parameters (matched to FLA):
train_iters: 4768 # ≈ 10B tokens at global_batch=1024, seq=2048
micro_batch_size: 128 # per-GPU
global_batch_size: 1024 # 8 GPUs × 128 = 1024
seq_length: 2048
lr: 2.0e-4
min_lr: 2.0e-5 # min_lr_rate=0.1 → 2e-5
lr_warmup_iters: 200
lr_decay_iters: 4768
lr_decay_style: cosine
adam_beta1: 0.9
adam_beta2: 0.95
weight_decay: 0.01
clip_grad: 1.0
seed: 42
layernorm_epsilon: 1.0e-6 # MUST be explicit — TransformerConfig default 1e-5 silently overrides the model YAML
hidden_dropout: 0.0 # MUST be explicit — language_model.yaml default 0.1 leaks through
attention_dropout: 0.0
spec: ['primus.backends.megatron.core.models.hybrid.hybrid_mamba_mla_layer_specs', 'kda_hybrid_stack_spec_no_te']
use_fla_triton_kda: true
use_fla_kda_in_kernel_gate: true
use_fla_fused_norm_gated: true
use_distributed_optimizer: false # 300M fits — ZeRO-1 adds allreduce overhead
finetune: true
load: /home/<user>/Primus/output/fla_init_kda_300M
no_load_optim: true
no_load_rng: true
The architecture-only YAML it extends from is
primus/configs/models/megatron/kda_300M.yaml.
5.2 Launch#
# inside the container, in /home/<user>/Primus
./primus-cli direct --log_file primus_kda.log \
-- train pretrain \
--config examples/megatron/configs/MI300X/kda_300M_BF16-pretrain.yaml
AMD Triton 3.6 cannot compile the num_stages = 4 variants of FLA’s KDA
intra-chunk kernels — the MLIR pass pipeline aborts inside
tritonamdgpu-schedule-loops — so the first autotune sweep fails. The training
process removes those candidates from the autotune space itself, at startup,
via the megatron.fla.kda_safe_autotune patch
(fla_kda_autotune_patches.py) —
nothing on disk is modified, so the installed fla package stays pristine. It
fires on ROCm for any run that resolves to the FLA KDA backend, which this one
does via use_fla_triton_kda: true, and the num_warps sweep is kept so the
autotuner still picks the best remaining config. Look for this line in the log:
[Patch:megatron.fla.kda_safe_autotune] chunk_kda_bwd_kernel_intra: kept 4/12 autotune configs ...
On Triton 3.7 the full sweep compiles and the autotuner picks num_stages = 2
for this model anyway, so the narrowing is free here; the patch stays until the
supported toolchain floor reaches Triton 3.7.
Expected wall time on a healthy MI300X box: ~1h 56m for the full 4768 iters (about 2 min faster than FLA’s HF-Trainer reference run).
5.3 Recommended toggle profile (for FLA parity)#
Preferred (canonical) — add to the experiment YAML’s overrides: block:
# FLA runtime knobs (consumed by primus.backends.megatron.patches.fla_runtime_patches)
use_fla_fused_swiglu: true # FLA Triton SwiGLU
use_fla_fused_rmsnorm: true # FLA fused RMSNorm
use_fla_fused_gated_norm: true # FLA FusedRMSNormGated for KDA gated output norm
use_fla_short_conv: true # FLA Triton causal_conv1d (no transpose round-trip)
fused_ce_mode: 1 # FLA FusedLinearCrossEntropyLoss (chunked, no full logits tensor)
fused_ce_chunks: 32 # Chunk count for FLA fused CE
# Only if you want bit-identical iter-1 batch ordering:
use_fla_data: true
fla_cache_dir: /home/<user>/Primus/data/huggingface
Legacy (still supported, env-var wins over YAML when set):
export PRIMUS_FUSED_CE=1 # FLA FusedLinearCrossEntropyLoss (chunked, no full logits tensor)
export PRIMUS_FLA_SWIGLU=1 # FLA Triton SwiGLU
export PRIMUS_FLA_NORM=1 # FLA fused RMSNorm
export PRIMUS_FLA_CONV=1 # FLA Triton causal_conv1d (no transpose round-trip)
export PRIMUS_TORCH_OPTIM=1 # torch.optim.AdamW(fused=True), matches FLA exactly
# Only if you want bit-identical iter-1 batch ordering:
export PRIMUS_FLA_DATA=1
export PRIMUS_FLA_CACHE_DIR=/home/<user>/Primus/data/huggingface
See kda-fla-parity.md for the cost-of-each-flag
breakdown.
5.4 Output layout#
Checkpoints land under Primus’s work_group/user_name/exp_name template:
output/amd/root/kda_300M_BF16-pretrain/
├── checkpoints/
│ ├── iter_0001024/
│ ├── iter_0002048/
│ ├── iter_0003072/
│ ├── iter_0004096/
│ ├── iter_0004768/ ← FINAL (~4.5 GB)
│ │ └── mp_rank_00/
│ │ └── model_optim_rng.pt
│ └── latest_checkpointed_iteration.txt → "4768"
└── logs/
└── pre_trainer/
save_interval: 1024 in the YAML produces 4 mid-training checkpoints plus
the final one.
Step 6: Monitor and compare against FLA#
Megatron logs iteration / elapsed_ms_inst / elapsed_ms_avg / TFLOP/s/GPU / tok/s/GPU / lm loss every 100 steps. A representative tail looks like:
iteration 4700/ 4768 | elapsed time per iteration (ms): 1467.8/1466.1 |
TFLOP/s/GPU: 626.1 | tokens per GPU (tokens/s/GPU): 178596.5 | lm loss: 3.362445E+00
To diff against FLA’s reference log
(/home/<user>/checkpoints/kda_300M_10B/trainer_state.json), divide
the FLA loss field by 8 (DeepSpeed reports sum-across-ranks):
iter |
FLA / 8 |
Primus |
Δ % |
Notes |
|---|---|---|---|---|
1 |
11.9673 |
11.9669 |
−0.00 % |
bit-perfect |
100 |
7.7171 |
9.6903 |
+25.6 % |
warmup gap (peak) |
500 |
4.7349 |
4.8390 |
+2.20 % |
warmup closing |
1000 |
4.0357 |
4.0720 |
+0.90 % |
LR-warmup done |
2000 |
3.6009 |
3.6141 |
+0.37 % |
converged |
2600 |
3.5056 |
3.5047 |
−0.03 % |
first Primus < FLA crossover |
3000 |
3.4356 |
3.4571 |
+0.63 % |
matched |
3600 |
3.4107 |
3.4075 |
−0.09 % |
Primus slightly lower |
4000 |
3.3831 |
3.3861 |
+0.09 % |
identical |
4500 |
3.3603 |
3.3694 |
+0.27 % |
identical |
4700 |
3.3388 |
3.3624 |
+0.71 % |
identical |
Final wall time on a healthy MI300X box: 6993 s vs FLA 7119 s = Primus 126 s faster.
Step 7: Convert checkpoint to HuggingFace format#
Use tools/hybrid/convert_kda_to_fla_hf.py
to translate the Megatron checkpoint into FLA’s native
KDAForCausalLM HF format:
python tools/hybrid/convert_kda_to_fla_hf.py \
--checkpoint-path output/amd/root/kda_300M_BF16-pretrain/checkpoints/iter_0004768 \
--output-dir output/kda_300M_fla_hf \
--config /home/<user>/flash-linear-attention/legacy/training/configs/kda_300M.json \
--tokenizer-src /home/<user>/checkpoints/kda_300M_10B
What it does:
Reads
mp_rank_00/model_optim_rng.ptand pulls themodelstate dictFor each of the 12 FLA layers, pairs the alternating Megatron sublayers:
KDA sublayer (even index) → FLA
model.layers.<i>.attn.*MLP sublayer (odd index) → FLA
model.layers.<i>.mlp.*
Splits Primus’s fused projections into FLA’s separate ones:
mixer.in_proj.weight(rows =2·qk_dim + v_dim + 2·head_v_dim + num_v_heads) →q_proj / k_proj / v_proj / f_proj.0 / g_proj.0 / b_projmlp.linear_fc1.weight(rows =2·intermediate_size) →gate_proj / up_proj
Preserves
A_log,dt_bias, per-headg_norm(FLA’sFusedRMSNormGated),o_proj,f_proj.1,g_proj.1, embeddings, tiedlm_head, final normCopies tokenizer files from
--tokenizer-srcinto the output dir
Output:
output/kda_300M_fla_hf/
├── config.json # KDAConfig, architectures=["KDAForCausalLM"]
├── model.safetensors # ~870 MB
└── tokenizer{,_config}.json + special_tokens_map.json
Step 8: Verify conversion#
Quick smoke test in the container (with FLA importable):
PYTHONPATH=/home/<user>/flash-linear-attention \
python - <<'PY'
import torch
import fla # auto-registers "kda" with transformers.AutoConfig
from transformers import AutoModelForCausalLM, AutoTokenizer
ckpt = "output/kda_300M_fla_hf"
tok = AutoTokenizer.from_pretrained(ckpt)
model = AutoModelForCausalLM.from_pretrained(
ckpt, trust_remote_code=True, torch_dtype=torch.bfloat16
).cuda().eval()
for prompt in [
"The capital of France is",
"Once upon a time, there was a small",
"The first law of thermodynamics states that",
]:
inp = tok(prompt, return_tensors="pt").to("cuda")
with torch.no_grad():
out = model.generate(**inp, max_new_tokens=40, do_sample=False)
print("---"); print(tok.decode(out[0], skip_special_tokens=True))
PY
Expected output for a healthy 300 M-on-10 B model: grammatical but repetitive English (canonical small-undertrained-LM failure mode under greedy decoding with no repetition penalty). Knowing “capital of France” → “Paris” is the standard sanity-check pass.
If AutoConfig raises model type kda not recognized, FLA was not
imported before AutoModelForCausalLM. Either prepend
PYTHONPATH=/home/<user>/flash-linear-attention or run
pip install -e /home/<user>/flash-linear-attention so the
auto-registration in fla/models/kda/__init__.py fires on import.
Step 9: Run lm-eval-harness benchmarks#
Use tools/hybrid/eval_kda_lm_eval.py, which
imports fla first (so AutoConfig recognizes the kda model type) and
patches KDAForCausalLM.__init__ / KDAModel.__init__ to accept the
dtype kwarg that transformers ≥ 4.55 passes internally.
Do not invoke lm_eval --model hf ... directly — AutoConfig.from_pretrained
will fail with model type kda not recognized.
9.1 Evaluate the Primus checkpoint (~15–30 min on one MI300X)#
mkdir -p output/kda_300M_eval_results_primus
PYTHONPATH=/home/<user>/flash-linear-attention \
HIP_VISIBLE_DEVICES=0 \
TOKENIZERS_PARALLELISM=false \
python tools/hybrid/eval_kda_lm_eval.py \
--model hf \
--model_args pretrained=output/kda_300M_fla_hf,dtype=bfloat16,trust_remote_code=True,tokenizer=meta-llama/Llama-3.2-1B \
--tasks arc_easy,arc_challenge,hellaswag,openbookqa,piqa,winogrande,mmlu,race \
--batch_size auto \
--output_path output/kda_300M_eval_results_primus \
2>&1 | tee output/kda_300M_eval_results_primus/lm_eval.log
9.2 Evaluate the FLA reference checkpoint (apples-to-apples)#
mkdir -p output/kda_300M_eval_results_fla
PYTHONPATH=/home/<user>/flash-linear-attention \
HIP_VISIBLE_DEVICES=1 \
TOKENIZERS_PARALLELISM=false \
python tools/hybrid/eval_kda_lm_eval.py \
--model hf \
--model_args pretrained=/home/<user>/checkpoints/kda_300M_10B,dtype=bfloat16,trust_remote_code=True,tokenizer=meta-llama/Llama-3.2-1B \
--tasks arc_easy,arc_challenge,hellaswag,openbookqa,piqa,winogrande,mmlu,race \
--batch_size auto \
--output_path output/kda_300M_eval_results_fla \
2>&1 | tee output/kda_300M_eval_results_fla/lm_eval.log
9.3 Diff the two result JSONs#
python - <<'PY'
import json, glob
def load_latest(d):
return json.load(open(sorted(glob.glob(f"{d}/**/results_*.json", recursive=True))[-1]))
fla = load_latest("output/kda_300M_eval_results_fla")
primus = load_latest("output/kda_300M_eval_results_primus")
print(f"{'task':<18} {'FLA':>8} {'Primus':>8} {'Δ':>+8}")
for task in sorted(set(fla['results']) & set(primus['results'])):
for k in ('acc,none', 'acc_norm,none'):
if k in fla['results'][task] and k in primus['results'][task]:
f, p = fla['results'][task][k], primus['results'][task][k]
print(f"{task[:17]:<18} {f:>8.4f} {p:>8.4f} {p-f:>+8.4f} ({k})")
PY
Measured result (validated on tw006, this branch). The Random
column is 100 / num_choices for the lm-eval task — anything above it
means the model learned something:
Task |
Metric |
Random |
FLA |
Primus |
Δ (Primus − FLA) |
|---|---|---|---|---|---|
arc_challenge |
acc_norm |
25.00 |
25.17 |
25.00 |
−0.17 pp |
arc_easy |
acc |
25.00 |
48.78 |
47.94 |
−0.84 pp |
arc_easy |
acc_norm |
25.00 |
42.76 |
43.39 |
+0.63 pp |
hellaswag |
acc_norm |
25.00 |
29.16 |
29.18 |
+0.02 pp |
openbookqa |
acc_norm |
25.00 |
30.40 |
29.00 |
−1.40 pp |
piqa |
acc_norm |
50.00 |
60.99 |
60.34 |
−0.65 pp |
winogrande |
acc |
50.00 |
51.85 |
52.72 |
+0.87 pp |
mmlu (aggregate) |
acc |
25.00 |
22.88 |
23.12 |
+0.24 pp |
race |
acc |
25.00 |
25.07 |
25.45 |
+0.38 pp |
mean absolute Δ |
0.58 pp |
Every task within ±1.4 pp — consistent with the 0.49% loss delta at the end of training. mmlu / race / arc_challenge are at random-chance for both stacks (300 M params + 10 B tokens is below the threshold those benchmarks need to lift above noise).
Configs and tools used#
docs/04-technical-guides/hybrid-models/
├── kda-guide.md ← this file
└── kda-fla-parity.md ← deep-dive on every change
examples/megatron/configs/MI300X/
└── kda_300M_BF16-pretrain.yaml ← training config
primus/configs/models/megatron/
└── kda_300M.yaml ← architecture-only config
primus/backends/megatron/core/models/hybrid/
├── kimi_delta_attention.py ← FLA-aligned mixer (fused in_proj, FLA Triton paths)
├── kimi_delta_attention_layer.py ← eps propagation, optional pre-norm
└── hybrid_mamba_mla_layer_specs.py ← kda_hybrid_stack_spec_no_te
primus/backends/megatron/patches/ ← same 6 patches as GDN (Primus patch system, shared)
├── gdn_config_patches.py ← registers use_fla_triton_kda + fusion flags + hybrid init
├── mamba_fused_ce_patches.py ← FLA fused cross-entropy for MambaModel
├── torch_fused_adam_patches.py ← PRIMUS_TORCH_OPTIM opt-in
├── mlp_fla_swiglu_patches.py ← FLA Triton SwiGLU for MLP
├── torch_norm_fla_rmsnorm_patches.py ← FLA RMSNorm for WrappedTorchNorm
├── fla_runtime_patches.py ← resolves PRIMUS_FLA_* knobs onto args
├── fla_kda_autotune_patches.py ← narrows the KDA Triton autotune space on ROCm (auto-applied, in-process)
└── mamba_fla_data_patches.py ← FLA-order dataset shim wiring
tools/hybrid/
├── convert_fla_to_megatron.py ← FLA Arrow → Megatron .bin/.idx (shared)
├── fla_order_dataset.py ← FLA-order dataset shim (shared)
├── convert_fla_kda_init_to_megatron.py ← FLA HF init → Megatron sharded ckpt
├── convert_kda_to_fla_hf.py ← Megatron sharded ckpt → FLA HF
└── eval_kda_lm_eval.py ← lm-eval wrapper (registers KDA)
Troubleshooting#
KeyError: 'kda' at AutoModelForCausalLM.from_pretrained#
You imported transformers before fla (or didn’t import fla at all).
fla/models/kda/__init__.py runs
AutoConfig.register(KDAConfig.model_type, KDAConfig, exist_ok=True)
on import. Either:
Prepend
PYTHONPATH=/home/<user>/flash-linear-attentionandimport flain your script BEFORE thetransformersimport, ORpip install -e /home/<user>/flash-linear-attentiononce and forget aboutPYTHONPATH, ORUse the wrapper:
python tools/hybrid/eval_kda_lm_eval.py ...
Conversion: KeyError: 'decoder.layers.0.mixer.in_proj.weight'#
You trained with an older code branch that still had six separate
projections. Either re-train with the current fused-in_proj branch or
patch the converter to read the unfused q_proj_weight/k_proj_weight/…
keys (see git history of tools/hybrid/convert_kda_to_fla_hf.py).
Iter 1 loss ~12.05 instead of ~11.97#
The layernorm_epsilon: 1.0e-6 override is being silently overwritten by
the TransformerConfig default of 1e-5. Confirm it’s in the training
YAML’s overrides: block (not just the model YAML).
Iter 1 loss not bit-matching FLA but converges fine#
You probably didn’t load the FLA-init checkpoint (Step 4) or didn’t set
PRIMUS_FLA_DATA=1. Without either, the first batch differs (Megatron
shuffler vs HF DistributedSampler) and the per-parameter nn.init.normal_
draw order differs (Megatron traverses Primus’s fused in_proj, FLA
traverses 6 separate nn.Linear modules). The gap disappears by iter
~2000 even without either fix.
Loss is +0.2–0.4 above FLA across the whole run (with FLA-init loaded)#
You probably have use_fla_kda_in_kernel_gate: false or
use_fla_fused_norm_gated: false. Those toggles select the bit-identical-
to-old-FLA fused_kda_gate + _apply_gated_norm paths, which run the
gate compute in fp32 (slightly different rounding than the in-kernel bf16
accumulator). Set both to true to match the current FLA reference.
Per-iter time ≫ 1500 ms#
Most likely you have PRIMUS_FLA_CONV=0. The Tri-Dao causal_conv1d_fn
on ROCm requires [B, D, T] layout, so each iteration pays two
transpose+contiguous copies of the (B, qk_dim·2 + v_dim, T) tensor —
about 35 ms wasted per iter at micro_batch=128. Set PRIMUS_FLA_CONV=1
to switch to FLA’s Triton causal_conv1d (accepts [B, T, D] natively).
Out-of-memory at iter 1#
Two common culprits:
PYTORCH_ALLOC_CONF=expandable_segments:Trueis unset — set it.q.contiguous()/k.contiguous()/v.contiguous()removed from KDA forward — the Triton kernel will allocate its own copies while autograd still pins the original views, doubling Q/K/V activation memory. Restore the explicit contiguous calls (seekimi_delta_attention.pyaround thechunk_kdacall site).
Eval truncation warnings#
Some samples exceed the model’s max_position_embeddings = 2048. Add
max_length=1024 to --model_args if it bothers you; it only
meaningfully affects RACE.
See also#
docs/04-technical-guides/hybrid-models/README.md— full Zebra hybrid family overview (1 B / 3 B / 8 B Mamba+MLA, KDA variants)docs/04-technical-guides/hybrid-models/gdn-guide.md— the GDN companion recipe (shares Megatron patches and dataset shim with this one)kda-fla-parity.md— exhaustive list of code/config/runtime changes that made KDA parity possibleFLA upstream: fla-org/flash-linear-attention