# rfa_train
LoRA training CLI for RFA hybrid models (RWKV-7 + Qwen GQA). Supports multi-threaded parallel training and INT8 quantized base weights.
**Source:** `examples/rfa_train.c`
**Build:** `make USE_BLAS=1 rfa-train`
**Tests:** `make USE_BLAS=1 test-rfa-train` (9 tests), `make USE_BLAS=1 test-rfa-parallel` (6 tests)
---
## Overview
Trains LoRA adapters on the 7 Qwen GQA blocks in the RWKV-7 + Qwen hybrid model. The 21 RWKV-7 blocks are frozen. Uses pre-tokenized binary data (from `pre_tokenize.py`).
7 LoRA layers per Qwen block (Q, K, V, O, gate, up, down) = **49 LoRA layers** total.
At rank 32: ~9.2M trainable parameters.
Features:
- Multi-threaded data-parallel training (`--parallel N`)
- INT8 quantized base weights (`--quantize`)
- Cross-machine LoRA adapter merging (via `rfa_lora_merge`)
- Gradient checkpointing (recompute vs store intermediates)
- AdamW optimizer with warmup + cosine annealing
---
## Quick Start
### 1. Pre-tokenize data
```bash
pip install tokenizers # one-time
python3 rwkv-fullAttention/scripts/pre_tokenize.py \
--tokenizer models/base/ARWKV-R1-1B5/tokenizer.json \
--data models/datasets/oasst1_chat.jsonl \
--output models/datasets/oasst1_qwen_tokens.bin \
--max-seq-len 256
```
### 2. Train (single-threaded)
```bash
./examples/rfa_train \
--model ../models/base/rwkv-qwen-hybrid-1.5B.safetensors \
--data ../models/datasets/oasst1_qwen_tokens.bin \
--output ../models/loras/rfa-qwen-lora/ \
--lora-rank 32 --lora-alpha 64 \
--lr 2e-5 --epochs 3 --max-seq-len 256 --log-every 10
```
### 3. Train (parallel with INT8)
```bash
./examples/rfa_train \
--model ../models/base/rwkv-qwen-hybrid-1.5B.safetensors \
--data ../models/datasets/oasst1_qwen_tokens.bin \
--output ../models/loras/rfa-qwen-lora/ \
--lora-rank 32 --lora-alpha 64 \
--lr 2e-5 --epochs 3 --max-seq-len 256 \
--parallel 6 --quantize
```
---
## CLI Options
| Flag | Default | Description |
|------|---------|-------------|
| `--model PATH` | (required) | Hybrid model safetensors file |
| `--data PATH` | (required) | Pre-tokenized binary data file |
| `--output DIR` | (required) | Output directory for LoRA checkpoints |
| `--lora-rank N` | 32 | LoRA rank |
| `--lora-alpha F` | 64 | LoRA alpha scaling |
| `--lr F` | 2e-5 | Learning rate |
| `--epochs N` | 3 | Training epochs |
| `--max-seq-len N` | 256 | Max sequence length per example |
| `--warmup N` | 100 | LR warmup steps |
| `--log-every N` | 10 | Log metrics every N steps |
| `--save-every N` | 1000 | Save checkpoint every N steps |
| `--grad-clip F` | 1.0 | Gradient clipping norm |
| `--max-cache N` | 2048 | Max KV cache per Qwen block |
| `--parallel N` | 1 | Number of training threads |
| `--quantize` | off | Use INT8 quantized base weights |
---
## Parallel Training
When `--parallel N` is set (N > 1), the training data is split into N chunks. Each thread independently trains a LoRA adapter on its chunk with its own state, optimizer, and adapter. After all threads complete, the N adapters are averaged element-wise (FedAvg) and saved as `adapter_final.safetensors`.
```
Main Thread
1. Load model (shared, read-only)
2. Split dataset into N chunks
3. Spawn N pthreads
4. Wait for all threads to finish
5. Average N adapters -> save merged
Thread 0 Thread 1 Thread N
┌─────────┐ ┌─────────┐ ┌─────────┐
│ state │ │ state │ │ state │
│ adapter │ │ adapter │ │ adapter │
│ optimizer│ │ optimizer│ │ optimizer│
│ chunk │ │ chunk │ │ chunk │
└─────────┘ └─────────┘ └─────────┘
↑ shared model (read-only) ↑
```
Output files with parallel training:
```
output_dir/
├── thread_0.safetensors # Per-thread adapter
├── thread_1.safetensors
├── ...
└── adapter_final.safetensors # Averaged adapter
```
---
## INT8 Quantized Training
The `--quantize` flag quantizes all base model linear weights from f32 to INT8 (per-row symmetric). Forward-pass matvecs use NEON 4x-unrolled INT8 dot products (~3x faster). LoRA weights remain f32 for gradient accuracy.
Both f32 and INT8 weights are kept in memory — f32 is needed for backward pass gradient computation through base weights.
---
## Pre-Tokenization
The RFA model uses Qwen's vocabulary (151K tokens). Data must be pre-tokenized:
```bash
python3 rwkv-fullAttention/scripts/pre_tokenize.py \
--tokenizer models/base/ARWKV-R1-1B5/tokenizer.json \
--data models/datasets/oasst1_chat.jsonl \
--output models/datasets/oasst1_qwen_tokens.bin \
--max-seq-len 256
```
Binary format: `[n_examples: u32] [seq_len: u32, tokens: u32[seq_len]] x N`
### Dataset splitting for multi-machine training
```bash
# Equal split into N chunks
python3 rwkv-fullAttention/scripts/pre_tokenize.py \
... --split 2
# Weighted split (e.g., 75%/25% for M4+M3)
python3 rwkv-fullAttention/scripts/pre_tokenize.py \
... --split-weights 6,2
```
Output: `_0.bin`, `_1.bin`, etc.
---
## Cross-Machine Training
Train on separate machines, then merge adapters:
```bash
# Machine 1 (M4 24GB):
./examples/rfa_train --model model.safetensors \
--data chunk_0.bin --output loras_m4/ \
--parallel 6 --quantize --epochs 3
# Machine 2 (M3 8GB):
./examples/rfa_train --model model.safetensors \
--data chunk_1.bin --output loras_m3/ \
--parallel 2 --quantize --epochs 3
# Merge:
scp m3:loras_m3/adapter_final.safetensors ./
./examples/rfa_lora_merge \
--output merged.safetensors \
loras_m4/adapter_final.safetensors loras_m3/adapter_final.safetensors
```
---
## Memory Requirements
| Config | Memory (1.5B model) |
|--------|-------------------|
| Single-thread, f32 | ~8 GB |
| Single-thread, INT8 (`--quantize`) | ~9.5 GB |
| 6 threads, INT8 (`--parallel 6 --quantize`) | ~20 GB |
Reduce memory with shorter sequences (`--max-seq-len 128`) or fewer threads.
---
## Output Files
```
output_dir/
├── checkpoint_1000.safetensors # Step 1000
├── checkpoint_2000.safetensors # Step 2000
└── adapter_final.safetensors # End of training (or merged from threads)
```
---
## See Also
- [rfa-generate](rfa-generate.md) - Inference with trained adapters
- [rfa-lora-merge](rfa-lora-merge.md) - Merge adapters from multiple machines
- [rwkvx-train](rwkvx-train.md) - Training for RWKV-X models
- [lora](lora.md) - LoRA adapter module