Skip to main content
Module: rllm.trainer.backend_protocol
The BackendProtocol is the abstract interface that decouples the unified trainer from any specific training infrastructure. By implementing this protocol, you can plug in any model-serving, optimization, and checkpointing system while reusing the trainer’s episode generation, data transformation, rejection sampling, and logging machinery.

Class signature

The two type parameters let you declare your backend-specific types:
  • TDataset — the iterable type returned by get_dataloader (e.g. torch.utils.data.DataLoader)
  • TBatch — the batch type consumed by your pipeline methods (e.g. list[tinker.Datum])

What a backend provides

A backend implementation is responsible for four categories of functionality:

Setup methods

init_rollout_engine(**kwargs) -> RolloutEngine

Called once during trainer initialization. The backend must create and return a RolloutEngine that the workflow engine will use for model inference. The trainer passes the parsed config objects as keyword arguments:

validate_config() -> None

Called during trainer initialization to validate backend-specific configuration. Raise or warn on invalid settings.

get_dataloader(dataset, trainer_state) -> TDataset

Called at the start of each epoch (training) and at each validation round. Use trainer_state.is_training to distinguish between training and validation and return the appropriate dataloader.

shutdown() -> None

Called when the trainer is torn down. Release GPU memory, close connections, etc.

Pipeline methods

These are called by the trainer in a fixed order during each training batch. The TrainerState object is the shared mutable context throughout a batch.

Stage 1: generate_episodes

Produce episodes by running workflows on the input batch. A typical implementation:
  1. Prepares the batch (e.g. repeat each task group_size times for GRPO)
  2. Sets the current model on the rollout engine
  3. Delegates to agent_workflow_engine.execute_tasks(...)

Stage 4: transform_to_backend_batch

Convert the framework’s TrajectoryGroup objects into your backend-native format. This is a sync method since it is typically pure data transformation. Some backends defer transformation to process_backend_batch and return a placeholder here.

Stage 5: process_backend_batch

The main computational stage. Common operations:
  • Run a forward pass to compute training logprobs
  • Run a backward pass to compute gradients
  • Store results in trainer_state.backend_batch and trainer_state.extra_info
This method updates trainer_state in place (no return value).

Stage 6: compute_advantages

Compute per-step advantages and store them on the Step objects within each trajectory. The base class provides a default implementation using rLLM-native advantage estimators (GRPO, REINFORCE):
Pre-computed advantages: If advantages are already set on the Step objects (e.g. computed during episode generation via a workflow decorator), the default implementation detects this and skips re-computation.

Stage 7: update_policy

Run the optimizer step to update model weights. Some backends fuse this into process_backend_batch and make update_policy a no-op (see flexible stage organization below).

Lifecycle hooks

All hooks are async def methods with default no-op implementations. Override only what you need.

Training hooks

Validation hooks

Common uses for hooks

on_batch_end runs after the pipeline but before logger.log(...). This makes it the right place to inject derived metrics (e.g. KL divergence, learning rate) into trainer_state.metrics.

Flexible stage organization

The protocol defines stages 4-7 as separate methods, but backends are free to redistribute work across them. The trainer always calls them in the same order — it is the backend’s responsibility to decide what each stage does internally.

Example: TinkerBackend’s fused mode

The TinkerBackend demonstrates this flexibility. When fuse_forward_backward_and_optim_step is enabled:
Both modes produce the same end result, but the fused path reduces round-trips to the training server. The trainer does not need to know which path is active — it simply calls all four methods in order.

Example: Pre-computed advantages

For workflows that compute per-token advantages during episode generation (stage 1) — for example, distillation flows that derive a reverse-KL signal from a teacher policy — every Step already has its .advantage field set by the time compute_advantages (stage 6) runs. The default implementation in BackendProtocol detects this and skips re-computation, collecting only metrics. This means such workflows can use the standard TinkerBackend directly — no custom backend subclass is needed.

Implementing a custom backend

1

Subclass BackendProtocol

2

Implement required methods

At minimum, implement these abstract methods:
3

Override lifecycle hooks as needed

4

Wire it up

Reference: TinkerBackend

The TinkerBackend (rllm.trainer.tinker.tinker_backend) is the primary production backend. It serves as a comprehensive reference implementation.

Setup

Pipeline

Lifecycle hooks

Key patterns

  1. Deferred transformation. transform_to_backend_batch returns a placeholder; the real work happens in process_backend_batch. This is valid because the trainer only checks trainer_state.has_backend_batch after process_backend_batch runs.
  2. Checkpoint-driven sampling client. The sampling_client (used by workflows for inference) is updated in on_batch_end after each checkpoint save. This ensures workflows always sample from the latest policy.
  3. Metrics injection in on_batch_end. Since on_batch_end runs after the pipeline but before logger.log(...), it is the natural place to compute derived metrics (KL divergence, entropy, learning rate) and add them to trainer_state.metrics.

Data flow summary