RAD
steerability.algorithms.output_control.rad
args
control
logger = logging.getLogger(__name__)
module-attribute
RAD
Bases: OutputControl
Implementation of RAD (Reward-Augmented Decoding) from Deng and Raffel, 2023.
RAD works in two phases:
- Preparation (
steer): load anAutoModelForSequenceClassificationreward model. - Controlled decoding (
get_logits_processors): at each decode step, the top-top_kcandidate tokens are scored by the reward model, their reward is clamped to[0, 1], and the candidate logits are shifted bybeta * rewardwhile non-candidate logits are masked to-inf.
The score read from the reward model is column score_index of its output, after score_transform
("none" reads a raw logit, "sigmoid" and "softmax" map it into [0, 1]). With invert=True
the shift uses 1 - reward, steering away from the scored attribute. The "clamp" normalization
is absolute rather than relative to the candidate set, so the shift spread across candidates is
beta * (max_reward - min_reward) and benign steps preserve the base distribution. When
score_transform="none" the reward is an unbounded logit, so invert=True gives 1 - clamp(v),
which saturates to 0 for any logit at or above 1; pass score_transform="sigmoid" to invert a
logit head meaningfully.
RAD is a step-level control. steer() loads the reward model into a candidate value, and
get_logits_processors() returns a fresh ValueGuidedProcessor per call. Candidates are the
top-top_k of the scores this processor receives, so its position in a composed output stack
matters. Caller-supplied sampling kwargs (temperature, top_p, repetition penalty) apply around
the shift; in particular temperature rescales the effective beta, so a protocol-faithful run
passes do_sample=True and nothing else. A value_trace list passed via runtime_kwargs
receives one ValueStepRecord per scored step (the candidate ids, their pre-shift scores, and
the per-candidate reward).
Two scoring paths back the value. When efficient=True and the reward model is decoder-only and
shares the language model's vocabulary, steer() builds a CachedRewardModelValue that memoizes
the reward model's prefix activations across steps (the paper's O(km) unidirectional path). When
a precondition or a steer-time smoke forward fails, RAD emits one UserWarning naming the failed
precondition and falls back to RewardModelValue(shared_vocab=True), which produces the same
scores at higher cost. When the vocabularies differ, RAD uses RewardModelValue(shared_vocab=False),
which decodes candidates to text and re-encodes with the reward-model tokenizer. Toggling
efficient changes speed only, not scores.
Reference:
- "Reward-Augmented Decoding: Efficient Controlled Text Generation With a Unidirectional Reward Model"
Haikang Deng, Colin Raffel
[https://arxiv.org/abs/2310.09520](https://arxiv.org/abs/2310.09520)
Source code in steerability/algorithms/output_control/rad/control.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 | |
RUNTIME_KWARGS_SCHEMA = [{'name': 'value_trace', 'type': 'list', 'scope': 'call', 'help': 'A caller-owned list that receives one ValueStepRecord per scored step of this call.'}]
class-attribute
instance-attribute
args = self.Args.validate(*args, **kwargs)
instance-attribute
beta
instance-attribute
enabled = True
class-attribute
instance-attribute
include_in_scoring = True
class-attribute
instance-attribute
same_model_forwards = False
class-attribute
instance-attribute
scoring_path
property
Which scoring path steer() resolved, or None before steer().
Returns "cached" for a CachedRewardModelValue (the paper's O(km) unidirectional path),
"stateless" for a shared-vocabulary RewardModelValue, and "text" for a
RewardModelValue over a different vocabulary (the decode-and-re-encode path).
supports_batching = False
class-attribute
instance-attribute
tokenizer = None
class-attribute
instance-attribute
cleanup()
Release the reward model and tokenizer to free memory.
Source code in steerability/algorithms/output_control/rad/control.py
248 249 250 251 252 253 254 255 256 257 258 259 | |
clone_for_call(seed=None)
A configuration-preserving shallow clone for one generation call.
The clone shares steer-time artifacts (memories, steering vectors, attached tokenizers)
with the original but has its own attribute namespace, so per-call attribute mutation on
the clone never races another call using the original. When seed is given and the
control defines reseed(seed), the clone's client-side RNG is re-seeded.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed
|
int | None
|
Optional seed forwarded to the clone's |
None
|
Returns:
| Type | Description |
|---|---|
BaseControl
|
The clone. |
Source code in steerability/algorithms/core/base_control.py
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 | |
export_constraint(runtime_kwargs=None)
The control's declarative constrained-decoding source, or None.
A control whose per-step masking compiles from a declarative source returns a
ConstraintSource; on a backend advertising Capability.GUIDED_DECODING the pipeline
renders it onto the engine's native structured-output parameters in place of the
control's live processor. The default returns None, which keeps the control on the live
processor mechanism.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runtime_kwargs
|
dict | None
|
Per-call parameters supplied to |
None
|
Returns:
| Type | Description |
|---|---|
ConstraintSource | None
|
The constraint source, or None. |
Source code in steerability/algorithms/output_control/base.py
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 | |
export_generation_params(runtime_kwargs=None)
The control's sampling-expressible contribution, or None.
A control whose behavior is expressible as normalized generation parameters returns a
mapping over a subset of stop_strings, stop_token_ids, max_new_tokens, and
min_new_tokens; the pipeline merges it into the call's GenerationParams (stop rules
union with the caller's; token bounds only tighten) and does not additionally collect
the control's live processors and criteria for that call, so the control executes on
every backend through the session's composed stop rules. The default returns None, which
keeps the control on the live processor/criteria mechanism.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runtime_kwargs
|
dict | None
|
Per-call parameters supplied to |
None
|
Returns:
| Type | Description |
|---|---|
Mapping[str, Any] | None
|
The parameter contribution, or None. |
Source code in steerability/algorithms/output_control/base.py
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 | |
export_processor_spec(runtime_kwargs=None)
The control's engine-hosted processor form, or None.
A control whose per-step logit math is expressible in an engine's served processor
vocabulary returns a ProcessorSpec; on a backend advertising
Capability.PER_STEP_LOGIT_SPECS with the spec's kind, the pipeline submits it as a
ProcessorSpecEntry in place of the control's live processor. The default returns
None, which keeps the control on the live processor mechanism.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runtime_kwargs
|
dict | None
|
Per-call parameters supplied to |
None
|
Returns:
| Type | Description |
|---|---|
ProcessorSpec | None
|
The processor spec, or None. |
Source code in steerability/algorithms/output_control/base.py
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 | |
export_state()
Steer-time state to persist when freezing, keyed by logical name.
Values are typed artifacts (SteeringVector, Probe, ProbeSet, a Memory
implementation, CheckpointArtifact, LoRAArtifact, torch.Tensor, or a Path to an
on-disk product). Must be called after steer(). The default returns an empty mapping
(nothing to persist).
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Mapping from logical name to artifact value. |
Source code in steerability/algorithms/core/base_control.py
117 118 119 120 121 122 123 124 125 126 127 128 | |
fit_identity()
The object whose canonical form defines this control's fit digest, or None.
The digest detects recipe edits that invalidate frozen artifacts, so the returned object should cover exactly the inputs a re-fit would consume (training data, fit specs) and exclude inert application parameters (strengths, multipliers). The default returns None (no fits).
Returns:
| Type | Description |
|---|---|
Any | None
|
The fit-identity object, or None. |
Source code in steerability/algorithms/core/base_control.py
161 162 163 164 165 166 167 168 169 170 171 172 | |
frozen_form(state)
The (registry method key, constructor kwargs) of this control's frozen form.
state is the output of export_state(); artifact values inside the returned kwargs
are replaced by store references at encode time. When steer_fits() is empty and
state is empty, the recipe is the frozen form and the control's own key and recipe
args are returned unchanged. A control whose freeze expands into several entries may
return a list of (method key, kwargs) pairs instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
state
|
dict[str, Any]
|
The output of |
required |
Returns:
| Type | Description |
|---|---|
tuple[str, dict[str, Any]]
|
The frozen method key and its constructor kwargs. |
Raises:
| Type | Description |
|---|---|
NotFreezableError
|
If the control produces fits or state but declares no frozen form. |
Source code in steerability/algorithms/core/base_control.py
130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | |
get_logits_processors(input_ids, runtime_kwargs, attention_mask=None, **kwargs)
Return a fresh ValueGuidedProcessor implementing RAD's reward-augmented shift.
Candidates are the top-top_k of the scores this processor receives; non-candidate tokens
are masked to -inf; candidate rewards are clamped to [0, 1] (inverted when invert is
set) and the candidate logits are shifted by beta * reward. A value_trace list in
runtime_kwargs receives one ValueStepRecord per scored step; compute_logprobs replays
also append when include_in_scoring is True.
Source code in steerability/algorithms/output_control/rad/control.py
222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 | |
get_stopping_criteria(input_ids, runtime_kwargs, **kwargs)
The control's stopping criteria.
Not applied during scoring (there is no loop to stop). Same call convention as
get_logits_processors.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
input_ids
|
Tensor
|
The steered prompt token ids |
required |
runtime_kwargs
|
dict | None
|
Per-call parameters supplied to |
required |
Returns:
| Type | Description |
|---|---|
list
|
A list of HF |
Source code in steerability/algorithms/output_control/base.py
139 140 141 142 143 144 145 146 147 148 149 150 151 152 | |
requirements()
Backend requirements computed from this instance's configuration, per phase.
The default requires Capability.IN_PROCESS_TORCH at generate and, when
include_in_scoring is True, at score as well, since remote prompt-logprob computation
applies neither live processors nor engine-registered sampling processors to prefill
logits. Setting include_in_scoring=False removes the score-phase requirement.
Returns:
| Type | Description |
|---|---|
Requirements
|
The control's phase-keyed requirements. |
Source code in steerability/algorithms/output_control/base.py
214 215 216 217 218 219 220 221 222 223 224 225 226 | |
steer(model, tokenizer=None, **__)
Load the reward model and build the candidate value.
Loads an AutoModelForSequenceClassification reward model and builds a
CachedRewardModelValue when efficient is set and the reward model is decoder-only and
shares the language model's vocabulary (verified by a smoke forward), otherwise a
RewardModelValue. Derives supports_batching from the resolved value. Performs no network
downloads or filesystem writes (model loading may hit the HF cache).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
PreTrainedModel
|
The base language model to be steered. |
required |
tokenizer
|
PreTrainedTokenizerBase | None
|
Tokenizer for the base model. |
None
|
Source code in steerability/algorithms/output_control/rad/control.py
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | |
steer_access()
ModelAccess.MODULE; the reward model's placement follows the live model at steer time
(the generate phase is in-process).
Source code in steerability/algorithms/output_control/rad/control.py
81 82 83 84 | |
steer_fits()
The fit artifacts this instance's steer step will produce, for the steer plan.
Each entry is (artifact, artifact_class), where artifact is the fit source or
recipe class name and artifact_class is "direction" or "calibrated". The default
is an empty tuple (no fits).
Returns:
| Type | Description |
|---|---|
tuple[tuple[str, str], ...]
|
The declared fit artifacts, in declaration order. |
Source code in steerability/algorithms/core/base_control.py
105 106 107 108 109 110 111 112 113 114 115 | |
utils
PrefixRewardTrainSpec
dataclass
Training settings for a prefix-scored reward model.
Attributes:
| Name | Type | Description |
|---|---|---|
max_length |
int
|
Maximum token length per training example (right-padded). |
batch_size |
int
|
Examples per optimizer step. |
epochs |
int
|
Passes over the data. |
learning_rate |
float
|
AdamW learning rate. |
weight_decay |
float
|
AdamW weight decay. |
seed |
int
|
Seed for shuffling and initialization of the fresh scalar head. |
log_every |
int
|
Log the running loss every this many optimizer steps. |
Source code in steerability/algorithms/output_control/rad/utils/reward_training.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | |
batch_size = 64
class-attribute
instance-attribute
epochs = 1
class-attribute
instance-attribute
learning_rate = 1e-05
class-attribute
instance-attribute
log_every = 50
class-attribute
instance-attribute
max_length = 64
class-attribute
instance-attribute
seed = 0
class-attribute
instance-attribute
weight_decay = 0.01
class-attribute
instance-attribute
prefix_reward_loss(predictions, labels, attention_mask)
The cumulative squared-error loss over prefixes, averaged over the batch.
For one sequence of length l with prediction r_t at every prefix t (1-indexed) and label
r, the loss is sum_t t * (r_t - r)^2 / S_l with S_l = l * (l + 1) / 2. Positions with
attention_mask == 0 are excluded and l counts the included positions, so a fully masked row
contributes nothing. The loss is computed in float32 regardless of the input dtypes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
predictions
|
Tensor
|
Per-position predictions |
required |
labels
|
Tensor
|
Per-sequence labels |
required |
attention_mask
|
Tensor
|
|
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
A float32 scalar tensor, the mean per-sequence prefix loss over the non-empty rows. |
Source code in steerability/algorithms/output_control/rad/utils/reward_training.py
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | |
prefix_rewards(model, input_ids, attention_mask)
Per-position rewards [B, T] from a sequence-classification model with a scalar head.
Runs the backbone (getattr(model, model.base_model_prefix)) once, applies model.score at every
position rather than only the pooled last token, and returns the sigmoid. Training and the
prefix-tracking diagnostics use this instead of the classifier's own forward, which pools the
last token only.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
An |
required | |
input_ids
|
Tensor
|
Token ids |
required |
attention_mask
|
Tensor
|
|
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
Per-position rewards |
Source code in steerability/algorithms/output_control/rad/utils/reward_training.py
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | |
train_prefix_reward_model(backbone_name_or_path, texts, labels, output_dir, *, spec=None, device=None)
Fine-tune a scalar-head sequence classifier on texts with prefix_reward_loss and save it.
Builds the classifier from the causal-LM backbone with num_labels=1 (the head class is selected
by sequence_classifier_class, so a Granite backbone resolves), scales the fresh scalar head down
by HEAD_INIT_SCALE so its initial sigmoid output is unsaturated, right-pads with the backbone
tokenizer (pad falls back to eos), trains with AdamW under bf16 autocast and a linear decay
schedule, and writes the model and tokenizer to output_dir. The saved config.json records
num_labels: 1 and the head's architecture name, so load_sequence_classifier(output_dir)
reloads it.
Labels are the dataset's native attribute (for civil_comments, toxicity in [0, 1]); the head
predicts the attribute, and RAD's invert=True turns the prediction into a reward.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
backbone_name_or_path
|
str
|
HF id or local path of the causal-LM backbone. |
required |
texts
|
Sequence[str]
|
Training texts. |
required |
labels
|
Sequence[float]
|
Per-text labels in |
required |
output_dir
|
str | Path
|
Directory the trained model and tokenizer are written to. |
required |
spec
|
PrefixRewardTrainSpec | None
|
Training settings; defaults to |
None
|
device
|
str | device | None
|
Device to train on; defaults to CUDA when available, else CPU. |
None
|
Returns:
| Type | Description |
|---|---|
Path
|
|
Source code in steerability/algorithms/output_control/rad/utils/reward_training.py
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | |
reward_training
Train a prefix-scored reward model, the recipe RAD's cached path needs (Deng and Raffel, 2023, §2.1).
RAD scores the reward model at every prefix during decoding, so the reward model must be trained to predict the sequence-level attribute from any prefix, not only the complete text. This module fits a scalar-head sequence classifier on a causal-LM backbone with the paper's cumulative prefix loss: the squared error between the per-prefix prediction and the sequence label, weighted by prefix length and averaged so a full-length sequence and a short one contribute comparably.
The label is the dataset's native attribute (for detoxification the civil_comments toxicity score in
[0, 1]), so the trained head predicts toxicity; RAD's invert=True turns the prediction into a
reward that steers away from it. Training is a plain PyTorch loop under bf16 autocast, with no
Trainer or TRL reward trainer (those implement pairwise preference losses, not this regression).
HEAD_INIT_SCALE = 0.01
module-attribute
logger = logging.getLogger(__name__)
module-attribute
PrefixRewardTrainSpec
dataclass
Training settings for a prefix-scored reward model.
Attributes:
| Name | Type | Description |
|---|---|---|
max_length |
int
|
Maximum token length per training example (right-padded). |
batch_size |
int
|
Examples per optimizer step. |
epochs |
int
|
Passes over the data. |
learning_rate |
float
|
AdamW learning rate. |
weight_decay |
float
|
AdamW weight decay. |
seed |
int
|
Seed for shuffling and initialization of the fresh scalar head. |
log_every |
int
|
Log the running loss every this many optimizer steps. |
Source code in steerability/algorithms/output_control/rad/utils/reward_training.py
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | |
batch_size = 64
class-attribute
instance-attribute
epochs = 1
class-attribute
instance-attribute
learning_rate = 1e-05
class-attribute
instance-attribute
log_every = 50
class-attribute
instance-attribute
max_length = 64
class-attribute
instance-attribute
seed = 0
class-attribute
instance-attribute
weight_decay = 0.01
class-attribute
instance-attribute
prefix_reward_loss(predictions, labels, attention_mask)
The cumulative squared-error loss over prefixes, averaged over the batch.
For one sequence of length l with prediction r_t at every prefix t (1-indexed) and label
r, the loss is sum_t t * (r_t - r)^2 / S_l with S_l = l * (l + 1) / 2. Positions with
attention_mask == 0 are excluded and l counts the included positions, so a fully masked row
contributes nothing. The loss is computed in float32 regardless of the input dtypes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
predictions
|
Tensor
|
Per-position predictions |
required |
labels
|
Tensor
|
Per-sequence labels |
required |
attention_mask
|
Tensor
|
|
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
A float32 scalar tensor, the mean per-sequence prefix loss over the non-empty rows. |
Source code in steerability/algorithms/output_control/rad/utils/reward_training.py
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | |
prefix_rewards(model, input_ids, attention_mask)
Per-position rewards [B, T] from a sequence-classification model with a scalar head.
Runs the backbone (getattr(model, model.base_model_prefix)) once, applies model.score at every
position rather than only the pooled last token, and returns the sigmoid. Training and the
prefix-tracking diagnostics use this instead of the classifier's own forward, which pools the
last token only.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
An |
required | |
input_ids
|
Tensor
|
Token ids |
required |
attention_mask
|
Tensor
|
|
required |
Returns:
| Type | Description |
|---|---|
Tensor
|
Per-position rewards |
Source code in steerability/algorithms/output_control/rad/utils/reward_training.py
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | |
train_prefix_reward_model(backbone_name_or_path, texts, labels, output_dir, *, spec=None, device=None)
Fine-tune a scalar-head sequence classifier on texts with prefix_reward_loss and save it.
Builds the classifier from the causal-LM backbone with num_labels=1 (the head class is selected
by sequence_classifier_class, so a Granite backbone resolves), scales the fresh scalar head down
by HEAD_INIT_SCALE so its initial sigmoid output is unsaturated, right-pads with the backbone
tokenizer (pad falls back to eos), trains with AdamW under bf16 autocast and a linear decay
schedule, and writes the model and tokenizer to output_dir. The saved config.json records
num_labels: 1 and the head's architecture name, so load_sequence_classifier(output_dir)
reloads it.
Labels are the dataset's native attribute (for civil_comments, toxicity in [0, 1]); the head
predicts the attribute, and RAD's invert=True turns the prediction into a reward.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
backbone_name_or_path
|
str
|
HF id or local path of the causal-LM backbone. |
required |
texts
|
Sequence[str]
|
Training texts. |
required |
labels
|
Sequence[float]
|
Per-text labels in |
required |
output_dir
|
str | Path
|
Directory the trained model and tokenizer are written to. |
required |
spec
|
PrefixRewardTrainSpec | None
|
Training settings; defaults to |
None
|
device
|
str | device | None
|
Device to train on; defaults to CUDA when available, else CPU. |
None
|
Returns:
| Type | Description |
|---|---|
Path
|
|
Source code in steerability/algorithms/output_control/rad/utils/reward_training.py
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | |