Async GRPO with LoRA across HF Jobs: a bucket, a proxy, and no NCCL TRL v1.14 shipped LoRA support in its AsyncGRPOTrainer via PR #7017, letting the asynchronous trainer train a rank-1 LoRA adapter and sync only that adapter to vLLM instead of the full model. A Hugging Face project running the trainer and two vLLM replicas as separate HF Jobs, connected through a Storage Bucket mounted at the same path in all three and a proxy that adds auth headers and routes rollouts to the replica holding the matching KV prefix, cut 500 training steps from 3 h 27 min to 53 min across five runs. The rank-1 adapter is a few megabytes versus roughly 3 GB for the full 1.5B model, removing the need for NCCL or any network path between Jobs. Text Generation • 2B • Updated • 403k • 1.58k https://huggingface.co/deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B Async GRPO with LoRA across HF Jobs: a bucket, a proxy, and no NCCL Update on GitHub https://github.com/huggingface/blog/blob/main/asyncgrpo-lora-hfjobs.md TL;DR AsyncGRPOTrainer can now train a LoRA adapter and sync only that adapter to vLLM TRL v1.14 .- A rank-1 adapter is a few megabytes, so it can travel through a Storage Bucket mounted in every Job instead of over NCCL. The trainer and the vLLM replicas run as separate Hugging Face Jobs on separate machines. - A small proxy in front of the replicas adds the auth header, routes each rollout to the replica that already holds its KV prefix, and broadcasts adapter loads to every replica. - The AsyncGRPO metrics show where the bottleneck sits. Five runs take the same recipe from 3 h 27 min to 53 min for 500 steps. LoRA support recently landed in TRL's AsyncGRPOTrainer https://huggingface.co/docs/trl/en/async grpo trainer with PR 7017 https://github.com/huggingface/trl/pull/7017 , and ships with TRL v1.14. The asynchronous trainer can now train an adapter instead of the full model, and it syncs only the LoRA adapter to vLLM. This post covers a real-world project built on top of it, where training and inference no longer share a machine. LoRA training is particularly suited for RL, as shown in Thinking Machines's blog LoRA Without Regret https://thinkingmachines.ai/blog/lora/ . They show that LoRA can match full fine-tuning for policy-gradient RL, even with rank 1. This stems from the fact that the advantage function only gives ~O 1 bits of information per episode, so there is not that much to learn from each step, from a total-bits-of-information point of view. A rank-1 adapter has enough capacity to absorb it. There is also a systems consequence of LoRA training. A rank-1 adapter for a 1.5B model is a few megabytes, while the full model is around 3 GB. Instead of sending the full policy to the inference workers after every update, we can just send the adapter. vLLM can also keep several adapters loaded at once. Old rollouts finish with the policy they started with, while new rollouts use the latest one. TRL's AsyncGRPOTrainer already separates training and generation. The trainer and vLLM can run on different machines and at their own speed. This is easy in a single-node or cluster setting where both processes share a filesystem or can form an NCCL group. What we want is to run the same setup with Hugging Face Jobs https://huggingface.co/docs/huggingface hub/guides/jobs . Essentially, an HF Job is one container running on one VM. This means that one Job cannot spawn multiple nodes at least for now to hold a trainer and a fleet of vLLM servers we are limited to 8xH200 at most per node . The AsyncGRPOTrainer is built for exactly that kind of scale, so the question became: how far can we get if we drop the requirement that the trainer and the inference servers share a node? Well, with a full-weight sync, the answer would be "not far". Every update would have to move gigabytes between machines, which is what NCCL is for in a dense cluster, but Jobs can't communicate across nodes. There is no shared local disk and obviously no shared localhost . With LoRA, a sync is only a few megabytes. For the filesystem part, HF Jobs provide volumes backed by Storage Buckets https://huggingface.co/docs/hub/storage-buckets These buckets can then be mounted as a FUSE filesystem in every Job and are enough to work as a shared FS between nodes. No network path between the Jobs is needed at all. The setup ended up being quite small: - a trainer Job running AsyncGRPOTrainer with LoRA and FSDP, more on that later , - two vLLM Jobs , each serving the base model plus whatever adapter the trainer last published, - a Storage Bucket mounted in all three at the same path, which is how the adapter gets from the trainer to the servers, - a proxy server . We'll dive deeper into why we need one, but at a high level we need a proxy that routes each rollout to the replica most likely to hold its KV cache, and broadcasts every adapter update to all vLLM replicas. The architecture: leveraging Hugging Face Jobs and Storage Buckets 🪣 The new adapter-only sync path in AsyncGRPOTrainer works like this. The trainer does not send tensors to vLLM. Every few optimizer steps, it saves the adapter under