We demonstrate how to train Qwen3-0.6B to reverse a small chunk of text. We will use a SFT warmup to learn the skill of text reversal on longer documents and then a quick RL run on the reverse-text-v1 taskset. We use a similar setup in our CI and for development.
The commands in this example were designed to be run on 2 GPUs (one trainer and one inference GPU). It is possible to run on less or more GPUs using different deployment strategies. If you run on a different setup, you may need to adjust the start commands.
The reverse-text-v1 taskset is included through the Verifiers workspace. After syncing the repository, verify it with:
uv run python -c "import reverse_text_v1"First, let's start a tmux session which we will use throughout the experiment.
bash scripts/tmux.shLet's check how well Qwen3-0.6B does out-of-the-box on the reverse-text environment.
# Run this in the `Inference` pane
uv run inference --model.name Qwen/Qwen3-0.6B# Run this in the `Trainer` pane
uv run eval reverse-text-v1 --harness.id null -m Qwen/Qwen3-0.6B --client.base-url http://localhost:8000/v1 -n 20 -r 3 --sampling.max-tokens 1024 --no-pushThis is of course just a quick vibe check and no full-fledged evaluation, but we can see that the model struggles with this task. In this specific instance, we got an average reward of ~0.05 across the 20x3 rollouts. Let's do some training!
We will fine-tune PrimeIntellect/Qwen3-0.6B (HF), which is a clone of Qwen/Qwen3-0.6B (HF) with a chat template suitable for multi-turn RL, on willcb/R1-reverse-wikipedia-paragraphs-v1-1000 (HF) which contains 1K examples of reversals of small paragraphs.
Check out the logs of the SFT run on W&B.
To train on a single GPU, run
# In the `Trainer` pane
uv run sft @ examples/basic/reverse-text/sft.toml \
--wandb.project ... \
--wandb.name ...To train on multiple GPUs, run
# In the `Trainer` pane
uv run torchrun \
--local-ranks-filter 0 \
--nproc-per-node ... \
src/prime_rl/trainer/sft/train.py @ examples/basic/reverse-text/sft.toml \
--wandb.project ... \
--wandb.name ...This should write a weight checkpoint in outputs/weights/step_100. Upload it to HF to be able to use it as the base model for RL.
uv run hf upload <user>/Qwen3-0.6B-Reverse-Text-SFT outputs/weights/step_100We have uploaded the final model as PrimeIntellect/Qwen3-0.6B-Reverse-Text-SFT.
For the RL we will only do 20 steps at 8x16 rollouts, for a total batch size of 128 and sequence length 128. Because of the small context, training should be extremely quick.
Check out the logs of the RL run on W&B.
# Run this in the `Trainer` pane
uv run rl @ examples/basic/reverse-text/rl.toml \
--model.name ... \
--wandb.project ... \
--wandb.name ...This will write a weight checkpoint in outputs/weights/step_20. As before, let's upload it to HF.
uv run hf upload <user>/Qwen3-0.6B-Reverse-Text-RL outputs/weights/step_20We have uploaded the final model as PrimeIntellect/Qwen3-0.6B-Reverse-Text-RL.
Let's see how our final RL checkpoints perform on the reverse-text environment.
# Run this in the `Inference` pane
uv run inference --model.name PrimeIntellect/Qwen3-0.6B-Reverse-Text-RL# Run this in the `Trainer` pane
uv run eval reverse-text-v1 --harness.id null -m PrimeIntellect/Qwen3-0.6B-Reverse-Text-RL --client.base-url http://localhost:8000/v1 -n 20 -r 3 --sampling.max-tokens 1024 --no-pushWay better! Now we get an average reward of ~0.8.
If you're running on Kubernetes, you can deploy this example using the provided Helm chart. The Helm chart automatically configures all components with proper networking and shared storage.
The reverse-text example is configured with autoStart: true for RL training, but we need to run SFT first. Deploy with autoStart disabled:
cd k8s
helm install my-exp ./prime-rl -f ./prime-rl/examples/reverse-text.yaml \
--set orchestrator.autoStart=false \
--set inference.autoStart=false \
--set trainer.autoStart=false
# Check pods are ready
kubectl get pods -l app=prime-rl,example=reverse-text
# All pods should show 1/1 RunningExec into the trainer pod and run SFT:
kubectl exec -it my-exp-trainer-0 -- bash
uv run sft @ /app/examples/basic/reverse-text/sft.toml --output-dir /data/outputs
# This will save checkpoints to /data/outputs/weights/step_100Upload the checkpoint to HuggingFace or use it directly from shared storage:
# Option 1: Upload to HuggingFace (from within the pod)
uv run hf upload <user>/Qwen3-0.6B-Reverse-Text-SFT /data/outputs/weights/step_100
# Option 2: Use local checkpoint path in RL config
# Update the model.name in the RL configs to point to /data/outputs/weights/step_100Now upgrade the deployment to enable autoStart (uses the default autoStart: true from reverse-text.yaml):
# Exit the pod first (Ctrl+D)
cd k8s
helm upgrade my-exp ./prime-rl -f ./prime-rl/examples/reverse-text.yaml
# Check pods have restarted
kubectl get pods -l app=prime-rl,example=reverse-textThe RL components will automatically start. Monitor the logs:
# View inference server logs
kubectl logs -f my-exp-inference-0
# View orchestrator logs
kubectl logs -f my-exp-orchestrator-0
# View trainer logs
kubectl logs -f my-exp-trainer-0If you prefer to run RL components manually, keep autoStart: false and exec into each pod:
# Terminal 1 - Inference
kubectl exec -it my-exp-inference-0 -- bash
uv run inference @ /app/k8s/prime-rl/examples/reverse-text/infer.toml
# Terminal 2 - Orchestrator
kubectl exec -it my-exp-orchestrator-0 -- bash
uv run orchestrator @ /app/k8s/prime-rl/examples/reverse-text/orch.toml --output-dir /data/outputs --client.base-url '["'$INFERENCE_URL'"]'
# Terminal 3 - Trainer
kubectl exec -it my-exp-trainer-0 -- bash
uv run trainer @ /app/k8s/prime-rl/examples/reverse-text/train.toml --output-dir /data/outputsAll outputs are written to /data/outputs on the shared NFS storage:
# Exec into any pod to access outputs
kubectl exec -it my-exp-trainer-0 -- bash
ls -lh /data/outputs/weights/
ls -lh /data/outputs/rollouts/To evaluate the trained RL model:
# Exec into trainer pod
kubectl exec -it my-exp-trainer-0 -- bash
# If inference server isn't running with the RL model, start it in another terminal:
kubectl exec -it my-exp-inference-0 -- bash
uv run inference --model.name /data/outputs/weights/step_20
# Back in trainer pod, run evaluation
uv run eval reverse-text-v1 --harness.id null \
-m /data/outputs/weights/step_20 \
--client.base-url $INFERENCE_URL \
-n 20 -r 3 --sampling.max-tokens 1024 --no-pushhelm uninstall my-exp
# Optionally delete shared data:
kubectl delete pvc prime-rl-shared-dataWhat the Helm chart provides:
- Predictably named pods:
<release-name>-trainer-0,<release-name>-inference-0,<release-name>-orchestrator-0 - Shared NFS storage at
/datafor model checkpoints and outputs - GPU resources (1 GPU per inference/trainer pod)
- Kubernetes services for component communication
- Auto-start capability with proper networking configured
See the K8s deployment guide for more details on scaling, distributed training, and advanced configurations.