LLM Learning Roadmap
✨ Awesome-Anything
🖼️ Digital Image Processing
🍃 LLM Components
🌱 LLM Pre-training
☘️ LLM Post-Training
🍀 LLM Popular Models
🪴 LLM Applications
🌿 LLM Optimization
🌾 LLM Compression
🌵 LLM Hands-on Practice
🌴 LLM Must-read Papers
🌳 LLM Q&A
🐝 VLM Image Encoders
📝 MISC.
8.3 OpenRLHF: SFT Qwen3-0.6B and Code Analysis
8.3.1 Introduction and Installation of OpenRLHF
OpenRLHF is a high-performance RLHF framework based on Ray, DeepSpeed, and HF Transformers:
- Easy to Use: OpenRLHF is one of the simplest and high-performance open-source RLHF libraries, fully compatible with Huggingface models and datasets.
- High Performance: In RLHF training, 80% of the time is spent on sample generation. Thanks to the acceleration capabilities of Ray, Packing Samples, and vLLM, OpenRLHF achieves performance gains of over 3–4x compared to DeepSpeedChat with Hybrid Engine.
- Distributed RLHF: OpenRLHF uses Ray to distribute the Actor, Reward, Reference, and Critic models across different GPUs while placing the Adam optimizer on the CPU. This allows efficient fine-tuning of models over 70B parameters on multiple A100 80G GPUs and vLLM, or fine-tuning 7B models on several 24GB RTX 4090 GPUs.
Additionally, OpenRLHF supports full-parameter SFT, efficient QLoRA fine-tuning, DPO algorithms, PRM training, etc.
Environment Setup:
You can create a Conda environment and install OpenRLHF by following the official quick-start guide. Colab is recommended, and the full code is available at the end.
<ins/>
8.3.2 SFT Training
We will perform supervised fine-tuning (SFT) on Qwen3-0.6B using OpenRLHF. Let’s start with an example provided by OpenRLHF, and modify it step by step for our experiment:
Prepare Dataset
Same as Chapter 8.2, we use alpaca dataset to fine-tune the base model. To lanuch the training script
openrlhf.cli.train_sft, it requires to input input_key and output_key for our dataset. However, alpaca has the following format: instruction + [optional input] → output. We need to manually combine instruction and optional input as input. Then save it into disk.Modify the OpenRLHF's Source Code for Model Saving
By default, OpenRLHF calls DeepSpeed’s
save_modelmethod after training, which unconditionally uses DeepSpeed’s _consolidated_16bit_state_dict() function. However, when we run with zero_stage = 0 (because parallelization isn’t needed for a single GPU), DeepSpeed wraps the model but performs no ZeRO partitioning. The _consolidated_16bit_state_dict()function works only when weights are partitioned. Since at stage 0 there’s no partitioning, calling this function triggers an error (This is a bug, and may be fixed in the future version). To avoid this, we disable OpenRLHF’s default model-saving call and instead save the model directly using PyTorch.- Let’s check OpenRLHF’s SFT training script.
The output show that after training, OpenRLHF save model by calling
strategy.save_model function.- We need to remove this saving method, and use torch function to save the model and tokenizer.
- Then, we observe that the file is been correctly modified
Training the Model By OpenRLHF
- By default, you run
deepspeeddirectly from the terminal. In this example, we use Python’ssubprocessmodule to calldeepspeedfrom within a Python script.
- For this example, we train for only 256 iterations with a batch size of 2 to avoid CUDA out-of-memory errors.
Optional: Setup wandb to Track Training Process
- Weights & Biases (W&B) is a machine learning experiment tracking and collaboration platform. After signing up, you’ll receive a 40-digit API key.
- The
wandbpackage is installed automatically when you install OpenRLHF, so you only need to prepare your API key, and no additional installation steps required.
- To enable, please uncomment
--use_wandb(and provide your API key),--wandb_project, and--wandb_run_name.
The training parameters are set as follows:
Why we choose these settings:
input_keyandoutput_key: These match the field names defined in the dataset.
- Flash Attention (
flash_attn) is disabled: Flash Attention only works on Ampere GPU architectures (RTX 30 series and newer). Since Google T4 GPUs do not support it, we disable it.
- Learning rate scheduler (
lr_scheduler): By default, it usescosine_with_min_lr, which gradually decreases the learning rate for better convergence. However, for this small-scale example, we set it toconstantso the model can learn more aggressively over the short run.
input_template: This must match the format used during inference. If we train with theapply_chat_templateoption but infer with a different format, the model may fail to recognize the correct end-of-sequence (EOS) stop token<|im_end|>.
After training, we can open wandb to view the training process:

Load and Test the Model
Since we saved the fine-tuned model and tokenizer fully to local disk, we can load them directly without downloading from Hugging Face:
Let’s test the model with a basic prompt:
After fine-tuning with OpenRLHF, the model correctly stops generating when it finishes the answer. Remember that in Chapter 8.2, the base model (model before SFT) doesn’t know how to generate EOS token.
Important: The chat template at inference must match the format used during training. Since we didn’t introduce
<think> and </think> tokens in training (unlike the default Qwen3-0.6B chat template), we also exclude them here during inference.<ins/>
8.3.3 OpenRLHF Code Analysis
Note: All of the following analysis is based on release version v0.7.4. Since the code may change in future releases, every link below explicitly targets v0.7.4 to ensure you can reproduce these results.
Preparing the Dataset
The
__getitem__ method in SFTDataset (view source) is called each time you iterate over the dataset. It performs these steps:- Append the
<EOS>token to the end of the concatenated prompt and response (unless in pretrain mode).
- Tokenize the resulting text.
- Generate a loss mask.
- Return the input token IDs, attention mask, and loss mask.
Computing the Loss Mask: In causal language model training, we shift labels one token to the left:
- At input position , the model predicts token .
- There is no “next” token for the final input position, so we do not provide a label (and thus no loss) for that logit.
Collation and Padding: When batching, the
collate_fn pads all sequences to the length of the longest sequence in the batch:Example of Data Generation
Consider a toy vocabulary/tokenizer scenario (ignoring blank tokens):
- Prompt:
<|im_start|> How are you <|im_end|> <|im_start|>assistant
- Model Output:
I 'm doing well . <|im_end|>
- Prompt IDs (length 7):
<|im_start|>, How, are, you, <|im_end|>, <|im_start|>, assistant- Response IDs (length 6):
I, 'm, doing, well, .,
<|im_end|>- Concatenated Input IDs (length 13). Suppose the batch’s max length is 15, two
<|pad_token|>tokens are appended.
Token | ID | Attn Mask | Model’s Predicts | Loss Mask |
<|im_start|> | 1 | 1 | How | 0 |
How | 101 | 1 | are | 0 |
are | 102 | 1 | you | 0 |
you | 103 | 1 | <|im_end|> | 0 |
<|im_end|> | 2 | 1 | <|im_start|> | 0 |
<|im_start|> | 1 | 1 | assistant | 0 |
assistant | 104 | 1 | I | 1 |
I | 105 | 1 | 'm | 1 |
'm | 106 | 1 | doing | 1 |
doing | 107 | 1 | well | 1 |
well | 108 | 1 | . | 1 |
. | 109 | 1 | <|im_end|> | 1 |
<|im_end|> | 2 | 1 | ??? | 0 |
<|pad_token|> | 3 | 0 | ??? | 0 |
<|pad_token|> | 3 | 0 | ??? | 0 |
- Attention mask is 1 for all non-padding tokens.
- Loss mask is 1 for all response tokens (i.e., those the model should learn to predict).
Loss Calculation in SFT Training
The key training code starts at line 135 of the sft_trainer.py file. A simple version (after removing auxiliary features) is the following:
The SFT Loss Function
Here,
masked_mean multiplies the negative log‐probs by the loss mask (1 for tokens to include, 0 otherwise), sums them, and divides by the total count of masked tokens:Putting it all together, if we index the batch by and tokens by , the SFT loss is
where:
- is the batch size,
- is the number of prompt tokens in example
- is the number of response tokens,
- is the model’s predicted probability for the token.
Before version v0.7.0 (see v0.6.4 code), the loss was computed simply as:
Here,
self.loss_fn was a standard cross-entropy loss:Average over all non-padding tokens . In practice, the old code masked out prompt tokens by setting their labels to a special ignore index (e.g.,
-100), so only response tokens contributed.Because cross-entropy with an ignore-index behaves exactly like the current version (masked mean of negative log-probabilities), the old implementation is mathematically equivalent to the current
SFTLoss formulation.Colab example link: OpenRLHF Supervised Fine-tuning Qwen3-0.6B.
Prev
8.2 Instruction Fine-Tuning Qwen3-0.6B: A Minimal Working Example
Next
8.4 OpenRLHF: Reward Model Training and Code Analysis
Loading...