Skip to content

Output control

steerability.algorithms.output_control.base

Output control base classes.

Output controls participate in decoding through two mechanisms:

  • Logits-processor composition: each control's get_logits_processors() results are gathered in pipeline controls list order and composed by LogitsProcessorList.
  • Stopping-criteria composition: each control's get_stopping_criteria() results are gathered the same way; generation stops when any criterion fires.
  • The decode loop is exclusive: it is implemented by exactly one DecodingDriver, which receives the composed stacks as explicit parameters and must apply them at every scoring step of every forward pass it issues.

Examples of output controls:

  • Reward-augmented decoding (a step-level control)
  • Self-disciplined autoregressive sampling (a step-level control)
  • Decoding-time alignment / lookahead search (a decoding driver)
  • Phase splicing / thinking intervention (a decoding driver)

See Also:

  • steerability.algorithms.output_control: Implementations of output control methods
  • steerability.algorithms.output_control.common: Shared component library
  • steerability.algorithms.core.steering_pipeline: Integration with steering pipeline

DecodingDriver

Bases: OutputControl

An output control that implements the decoding procedure.

Exactly one enabled driver may exist per pipeline (the decode loop does not compose). Driver contract: logits_processors and stopping_criteria are the composed, authoritative stacks for this generation; the driver applies them at every scoring step of every forward pass it issues. Delegating to model.generate(..., logits_processor=..., stopping_criteria=...) satisfies the contract; hand-rolled loops apply them explicitly.

A driver is also an OutputControl: it may additionally contribute processors or criteria of its own via the get_* hooks, which the pipeline composes like any other control's.

The pipeline passes session=, the SteeringSession for this generation. Drivers issue their rollouts through it (resolve_generate_callable returns the right callable), so a driver runs on any backend whose session serves its rollout parameters; model is None on backends without a live model.

Source code in steerability/algorithms/output_control/base.py
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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
class DecodingDriver(OutputControl):
    """An output control that implements the decoding procedure.

    Exactly one enabled driver may exist per pipeline (the decode loop does not compose).
    Driver contract: `logits_processors` and `stopping_criteria` are the composed,
    authoritative stacks for this generation; the driver applies them at every scoring
    step of every forward pass it issues. Delegating to `model.generate(...,
    logits_processor=..., stopping_criteria=...)` satisfies the contract; hand-rolled
    loops apply them explicitly.

    A driver is also an `OutputControl`: it may additionally contribute processors or
    criteria of its own via the `get_*` hooks, which the pipeline composes like any other
    control's.

    The pipeline passes `session=`, the `SteeringSession` for this generation. Drivers issue
    their rollouts through it (`resolve_generate_callable` returns the right callable), so a
    driver runs on any backend whose session serves its rollout parameters; `model` is None on
    backends without a live model.
    """

    def max_rollouts_per_query(self) -> int | None:
        """An upper bound on the number of continuations this driver generates per input row.

        Counts every sequence the driver requests through the session for one row of one
        `decode()` call, including proposals it discards. A session call over a frontier of `F`
        rows with `num_return_sequences=n` counts `F * n`. Returns None when the configuration
        admits no static bound.

        Callers use the bound to budget or refuse a configuration before executing it. The
        default returns None.

        Returns:
            The per-row rollout bound, or None when no static bound applies.
        """
        return None

    @abstractmethod
    def decode(
        self,
        input_ids: torch.Tensor,
        attention_mask: torch.Tensor,
        model: PreTrainedModel | None,
        logits_processors: LogitsProcessorList,
        stopping_criteria: StoppingCriteriaList,
        runtime_kwargs: dict | None,
        session=None,
        **gen_kwargs,
    ) -> torch.Tensor:
        """Run the decoding procedure; return full sequence ids (prompt + continuation)."""

RUNTIME_KWARGS_SCHEMA = [] class-attribute instance-attribute

args = self.Args.validate(*args, **kwargs) instance-attribute

enabled = True class-attribute instance-attribute

include_in_scoring = True class-attribute instance-attribute

same_model_forwards = False class-attribute instance-attribute

supports_batching = False class-attribute instance-attribute

cleanup()

Release resources allocated during steer().

Override in subclasses that allocate GPU memory or other resources during steering. Default no-op.

Source code in steerability/algorithms/core/base_control.py
195
196
197
198
199
200
201
def cleanup(self) -> None:
    """Release resources allocated during `steer()`.

    Override in subclasses that allocate GPU memory or other resources during steering. Default
    no-op.
    """
    pass

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 reseed().

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
def clone_for_call(self, seed: int | None = None) -> "BaseControl":
    """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.

    Args:
        seed: Optional seed forwarded to the clone's `reseed()`.

    Returns:
        The clone.
    """
    clone = copy.copy(self)
    if seed is not None:
        reseed = getattr(clone, "reseed", None)
        if callable(reseed):
            reseed(seed)
    return clone

decode(input_ids, attention_mask, model, logits_processors, stopping_criteria, runtime_kwargs, session=None, **gen_kwargs) abstractmethod

Run the decoding procedure; return full sequence ids (prompt + continuation).

Source code in steerability/algorithms/output_control/base.py
265
266
267
268
269
270
271
272
273
274
275
276
277
@abstractmethod
def decode(
    self,
    input_ids: torch.Tensor,
    attention_mask: torch.Tensor,
    model: PreTrainedModel | None,
    logits_processors: LogitsProcessorList,
    stopping_criteria: StoppingCriteriaList,
    runtime_kwargs: dict | None,
    session=None,
    **gen_kwargs,
) -> torch.Tensor:
    """Run the decoding procedure; return full sequence ids (prompt + continuation)."""

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 generate().

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
def export_constraint(self, runtime_kwargs: dict | None = None) -> ConstraintSource | 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.

    Args:
        runtime_kwargs: Per-call parameters supplied to `generate()`.

    Returns:
        The constraint source, or None.
    """
    return None

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 generate().

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
def export_generation_params(self, runtime_kwargs: dict | None = None) -> Mapping[str, Any] | 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.

    Args:
        runtime_kwargs: Per-call parameters supplied to `generate()`.

    Returns:
        The parameter contribution, or None.
    """
    return None

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 generate().

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
def export_processor_spec(self, runtime_kwargs: dict | None = None) -> ProcessorSpec | 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.

    Args:
        runtime_kwargs: Per-call parameters supplied to `generate()`.

    Returns:
        The processor spec, or None.
    """
    return None

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
def export_state(self) -> dict[str, Any]:
    """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:
        Mapping from logical name to artifact value.
    """
    return {}

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
def fit_identity(self) -> Any | None:
    """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:
        The fit-identity object, or None.
    """
    return None

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 export_state().

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
def frozen_form(self, state: dict[str, Any]) -> tuple[str, dict[str, Any]]:
    """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.

    Args:
        state: The output of `export_state()`.

    Returns:
        The frozen method key and its constructor kwargs.

    Raises:
        NotFreezableError: If the control produces fits or state but declares no frozen
            form.
    """
    if not state and not self.steer_fits():
        from steerability.algorithms.core.registry import method_key_for

        args = getattr(self, "args", None)
        kwargs = {field.name: getattr(args, field.name) for field in fields(args) if field.init} \
            if args is not None else {}
        return method_key_for(type(self)), kwargs
    raise NotFreezableError(
        f"{type(self).__name__} produces steer-time state but declares no frozen form; "
        "override frozen_form() (and export_state()) to support freezing."
    )

get_logits_processors(input_ids, runtime_kwargs, **kwargs)

The control's logits processors for the current generation.

Called once per generate() / compute_logprobs() call, after input and state controls have prepared the prompt (mirrors StateControl.get_hooks). **kwargs carries attention_mask and the caller's generation kwargs. Returned objects follow the HF LogitsProcessor convention; in-list order is preserved by the composition.

A processor must behave as a function of (prefix_ids, scores). Internal state is permitted only as memoization keyed on the prefix and must re-derive on a prefix mismatch, since drivers may restart, rewind, or reorder sequences, and scoring replays prefixes teacher-forced (subclass common.processors.base.PrefixKeyedProcessor to satisfy this mechanically). Return fresh processor instances from this hook; it is invoked once per call precisely so that per-generation state is isolated.

Parameters:

Name Type Description Default
input_ids Tensor

The steered prompt token ids [batch, seq_len].

required
runtime_kwargs dict | None

Per-call parameters supplied to generate().

required

Returns:

Type Description
list

A list of HF LogitsProcessor-style objects.

Source code in steerability/algorithms/output_control/base.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def get_logits_processors(self, input_ids: torch.Tensor, runtime_kwargs: dict | None, **kwargs) -> list:
    """The control's logits processors for the current generation.

    Called once per `generate()` / `compute_logprobs()` call, after input and state
    controls have prepared the prompt (mirrors `StateControl.get_hooks`). `**kwargs`
    carries `attention_mask` and the caller's generation kwargs. Returned objects
    follow the HF `LogitsProcessor` convention; in-list order is preserved by the composition.

    A processor must behave as a function of `(prefix_ids, scores)`. Internal state is
    permitted only as memoization keyed on the prefix and must re-derive on a prefix mismatch,
    since drivers may restart, rewind, or reorder sequences, and scoring replays prefixes
    teacher-forced (subclass `common.processors.base.PrefixKeyedProcessor` to satisfy this
    mechanically). Return fresh processor instances from this hook; it is invoked once per call
    precisely so that per-generation state is isolated.

    Args:
        input_ids: The steered prompt token ids `[batch, seq_len]`.
        runtime_kwargs: Per-call parameters supplied to `generate()`.

    Returns:
        A list of HF `LogitsProcessor`-style objects.
    """
    return []

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 [batch, seq_len].

required
runtime_kwargs dict | None

Per-call parameters supplied to generate().

required

Returns:

Type Description
list

A list of HF StoppingCriteria-style objects.

Source code in steerability/algorithms/output_control/base.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def get_stopping_criteria(self, input_ids: torch.Tensor, runtime_kwargs: dict | None, **kwargs) -> list:
    """The control's stopping criteria.

    Not applied during scoring (there is no loop to stop). Same call convention as
    `get_logits_processors`.

    Args:
        input_ids: The steered prompt token ids `[batch, seq_len]`.
        runtime_kwargs: Per-call parameters supplied to `generate()`.

    Returns:
        A list of HF `StoppingCriteria`-style objects.
    """
    return []

max_rollouts_per_query()

An upper bound on the number of continuations this driver generates per input row.

Counts every sequence the driver requests through the session for one row of one decode() call, including proposals it discards. A session call over a frontier of F rows with num_return_sequences=n counts F * n. Returns None when the configuration admits no static bound.

Callers use the bound to budget or refuse a configuration before executing it. The default returns None.

Returns:

Type Description
int | None

The per-row rollout bound, or None when no static bound applies.

Source code in steerability/algorithms/output_control/base.py
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
def max_rollouts_per_query(self) -> int | None:
    """An upper bound on the number of continuations this driver generates per input row.

    Counts every sequence the driver requests through the session for one row of one
    `decode()` call, including proposals it discards. A session call over a frontier of `F`
    rows with `num_return_sequences=n` counts `F * n`. Returns None when the configuration
    admits no static bound.

    Callers use the bound to budget or refuse a configuration before executing it. The
    default returns None.

    Returns:
        The per-row rollout bound, or None when no static bound applies.
    """
    return None

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
def requirements(self) -> 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:
        The control's phase-keyed requirements.
    """
    score = needs(Capability.IN_PROCESS_TORCH) if self.include_in_scoring else ()
    return Requirements(generate=needs(Capability.IN_PROCESS_TORCH), score=score)

steer(model, tokenizer=None, session=None, **kwargs)

Optional one-time preparation (e.g., load a reward model, fit a probe).

session is a SteeringSession on the steering backend, provided by the pipeline.

Source code in steerability/algorithms/output_control/base.py
207
208
209
210
211
212
def steer(self, model: PreTrainedModel, tokenizer=None, session=None, **kwargs) -> None:
    """Optional one-time preparation (e.g., load a reward model, fit a probe).

    `session` is a `SteeringSession` on the steering backend, provided by the pipeline.
    """
    pass

steer_access()

The model access this instance's steer step requires, on the ModelAccess ladder.

The default is ModelAccess.FACTS (layout and tokenizer only). A control whose steer step generates or scores through the session declares ROLLOUTS, one that captures hidden states declares CAPTURE, and one that touches the model as a live torch.nn.Module declares MODULE. Configuration determines the result. The pipeline hands steer() a session scoped to the declared rung, and the live model only at MODULE. A control may retain the pipeline model beyond steer() only if its generate phase requires Capability.IN_PROCESS_TORCH.

Returns:

Type Description
ModelAccess

The declared access rung.

Source code in steerability/algorithms/core/base_control.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def steer_access(self) -> ModelAccess:
    """The model access this instance's steer step requires, on the `ModelAccess` ladder.

    The default is `ModelAccess.FACTS` (layout and tokenizer only). A control whose steer
    step generates or scores through the session declares `ROLLOUTS`, one that captures
    hidden states declares `CAPTURE`, and one that touches the model as a live
    `torch.nn.Module` declares `MODULE`. Configuration determines the result. The pipeline
    hands `steer()` a session scoped to the declared rung, and the live model only at
    `MODULE`. A control may retain the pipeline model beyond `steer()` only if its
    generate phase requires `Capability.IN_PROCESS_TORCH`.

    Returns:
        The declared access rung.
    """
    return ModelAccess.FACTS

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
def steer_fits(self) -> tuple[tuple[str, str], ...]:
    """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:
        The declared fit artifacts, in declaration order.
    """
    return ()

OutputControl

Bases: BaseControl

Base class for output-control steering methods.

An OutputControl participates in decoding through the composable mechanisms above. Controls that implement a decoding procedure subclass DecodingDriver instead.

Class attributes

include_in_scoring: Whether this control's logits processors also apply during SteeringPipeline.compute_logprobs() (per-position, teacher-forced). Defaults to True. Set False when the processors are too expensive to evaluate per reference position (see BaseCandidateValue.scoring_cost). same_model_forwards: Whether this component issues additional forward passes through the pipeline's own model during decoding. Such passes must be wrapped in auxiliary_pass() (see steerability.algorithms.core.utils.auxiliary_pass), which keeps them out of state-control condition scoring, gate updates, and fallback position counting. Defaults to False; the flag is declarative metadata and is not read by the pipeline.

Source code in steerability/algorithms/output_control/base.py
 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
class OutputControl(BaseControl):
    """Base class for output-control steering methods.

    An `OutputControl` participates in decoding through the composable mechanisms above.
    Controls that implement a decoding procedure subclass `DecodingDriver` instead.

    Class attributes:
        include_in_scoring: Whether this control's logits processors also apply during
            `SteeringPipeline.compute_logprobs()` (per-position, teacher-forced). Defaults
            to True. Set False when the processors are too expensive to evaluate per reference
            position (see `BaseCandidateValue.scoring_cost`).
        same_model_forwards: Whether this component issues additional forward passes through the
            pipeline's own model during decoding. Such passes must be wrapped in
            `auxiliary_pass()` (see `steerability.algorithms.core.utils.auxiliary_pass`), which
            keeps them out of state-control condition scoring, gate updates, and fallback
            position counting. Defaults to False; the flag is declarative metadata and is not
            read by the pipeline.
    """

    Args: Type[BaseArgs] | None = None
    RUNTIME_KWARGS_SCHEMA: list[dict] = []

    enabled: bool = True
    supports_batching: bool = False
    include_in_scoring: bool = True
    same_model_forwards: bool = False

    def get_logits_processors(self, input_ids: torch.Tensor, runtime_kwargs: dict | None, **kwargs) -> list:
        """The control's logits processors for the current generation.

        Called once per `generate()` / `compute_logprobs()` call, after input and state
        controls have prepared the prompt (mirrors `StateControl.get_hooks`). `**kwargs`
        carries `attention_mask` and the caller's generation kwargs. Returned objects
        follow the HF `LogitsProcessor` convention; in-list order is preserved by the composition.

        A processor must behave as a function of `(prefix_ids, scores)`. Internal state is
        permitted only as memoization keyed on the prefix and must re-derive on a prefix mismatch,
        since drivers may restart, rewind, or reorder sequences, and scoring replays prefixes
        teacher-forced (subclass `common.processors.base.PrefixKeyedProcessor` to satisfy this
        mechanically). Return fresh processor instances from this hook; it is invoked once per call
        precisely so that per-generation state is isolated.

        Args:
            input_ids: The steered prompt token ids `[batch, seq_len]`.
            runtime_kwargs: Per-call parameters supplied to `generate()`.

        Returns:
            A list of HF `LogitsProcessor`-style objects.
        """
        return []

    def get_stopping_criteria(self, input_ids: torch.Tensor, runtime_kwargs: dict | None, **kwargs) -> list:
        """The control's stopping criteria.

        Not applied during scoring (there is no loop to stop). Same call convention as
        `get_logits_processors`.

        Args:
            input_ids: The steered prompt token ids `[batch, seq_len]`.
            runtime_kwargs: Per-call parameters supplied to `generate()`.

        Returns:
            A list of HF `StoppingCriteria`-style objects.
        """
        return []

    def export_generation_params(self, runtime_kwargs: dict | None = None) -> Mapping[str, Any] | 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.

        Args:
            runtime_kwargs: Per-call parameters supplied to `generate()`.

        Returns:
            The parameter contribution, or None.
        """
        return None

    def export_processor_spec(self, runtime_kwargs: dict | None = None) -> ProcessorSpec | 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.

        Args:
            runtime_kwargs: Per-call parameters supplied to `generate()`.

        Returns:
            The processor spec, or None.
        """
        return None

    def export_constraint(self, runtime_kwargs: dict | None = None) -> ConstraintSource | 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.

        Args:
            runtime_kwargs: Per-call parameters supplied to `generate()`.

        Returns:
            The constraint source, or None.
        """
        return None

    def steer(self, model: PreTrainedModel, tokenizer=None, session=None, **kwargs) -> None:
        """Optional one-time preparation (e.g., load a reward model, fit a probe).

        `session` is a `SteeringSession` on the steering backend, provided by the pipeline.
        """
        pass

    def requirements(self) -> 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:
            The control's phase-keyed requirements.
        """
        score = needs(Capability.IN_PROCESS_TORCH) if self.include_in_scoring else ()
        return Requirements(generate=needs(Capability.IN_PROCESS_TORCH), score=score)

RUNTIME_KWARGS_SCHEMA = [] class-attribute instance-attribute

args = self.Args.validate(*args, **kwargs) instance-attribute

enabled = True class-attribute instance-attribute

include_in_scoring = True class-attribute instance-attribute

same_model_forwards = False class-attribute instance-attribute

supports_batching = False class-attribute instance-attribute

cleanup()

Release resources allocated during steer().

Override in subclasses that allocate GPU memory or other resources during steering. Default no-op.

Source code in steerability/algorithms/core/base_control.py
195
196
197
198
199
200
201
def cleanup(self) -> None:
    """Release resources allocated during `steer()`.

    Override in subclasses that allocate GPU memory or other resources during steering. Default
    no-op.
    """
    pass

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 reseed().

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
def clone_for_call(self, seed: int | None = None) -> "BaseControl":
    """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.

    Args:
        seed: Optional seed forwarded to the clone's `reseed()`.

    Returns:
        The clone.
    """
    clone = copy.copy(self)
    if seed is not None:
        reseed = getattr(clone, "reseed", None)
        if callable(reseed):
            reseed(seed)
    return clone

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 generate().

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
def export_constraint(self, runtime_kwargs: dict | None = None) -> ConstraintSource | 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.

    Args:
        runtime_kwargs: Per-call parameters supplied to `generate()`.

    Returns:
        The constraint source, or None.
    """
    return None

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 generate().

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
def export_generation_params(self, runtime_kwargs: dict | None = None) -> Mapping[str, Any] | 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.

    Args:
        runtime_kwargs: Per-call parameters supplied to `generate()`.

    Returns:
        The parameter contribution, or None.
    """
    return None

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 generate().

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
def export_processor_spec(self, runtime_kwargs: dict | None = None) -> ProcessorSpec | 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.

    Args:
        runtime_kwargs: Per-call parameters supplied to `generate()`.

    Returns:
        The processor spec, or None.
    """
    return None

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
def export_state(self) -> dict[str, Any]:
    """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:
        Mapping from logical name to artifact value.
    """
    return {}

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
def fit_identity(self) -> Any | None:
    """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:
        The fit-identity object, or None.
    """
    return None

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 export_state().

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
def frozen_form(self, state: dict[str, Any]) -> tuple[str, dict[str, Any]]:
    """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.

    Args:
        state: The output of `export_state()`.

    Returns:
        The frozen method key and its constructor kwargs.

    Raises:
        NotFreezableError: If the control produces fits or state but declares no frozen
            form.
    """
    if not state and not self.steer_fits():
        from steerability.algorithms.core.registry import method_key_for

        args = getattr(self, "args", None)
        kwargs = {field.name: getattr(args, field.name) for field in fields(args) if field.init} \
            if args is not None else {}
        return method_key_for(type(self)), kwargs
    raise NotFreezableError(
        f"{type(self).__name__} produces steer-time state but declares no frozen form; "
        "override frozen_form() (and export_state()) to support freezing."
    )

get_logits_processors(input_ids, runtime_kwargs, **kwargs)

The control's logits processors for the current generation.

Called once per generate() / compute_logprobs() call, after input and state controls have prepared the prompt (mirrors StateControl.get_hooks). **kwargs carries attention_mask and the caller's generation kwargs. Returned objects follow the HF LogitsProcessor convention; in-list order is preserved by the composition.

A processor must behave as a function of (prefix_ids, scores). Internal state is permitted only as memoization keyed on the prefix and must re-derive on a prefix mismatch, since drivers may restart, rewind, or reorder sequences, and scoring replays prefixes teacher-forced (subclass common.processors.base.PrefixKeyedProcessor to satisfy this mechanically). Return fresh processor instances from this hook; it is invoked once per call precisely so that per-generation state is isolated.

Parameters:

Name Type Description Default
input_ids Tensor

The steered prompt token ids [batch, seq_len].

required
runtime_kwargs dict | None

Per-call parameters supplied to generate().

required

Returns:

Type Description
list

A list of HF LogitsProcessor-style objects.

Source code in steerability/algorithms/output_control/base.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
def get_logits_processors(self, input_ids: torch.Tensor, runtime_kwargs: dict | None, **kwargs) -> list:
    """The control's logits processors for the current generation.

    Called once per `generate()` / `compute_logprobs()` call, after input and state
    controls have prepared the prompt (mirrors `StateControl.get_hooks`). `**kwargs`
    carries `attention_mask` and the caller's generation kwargs. Returned objects
    follow the HF `LogitsProcessor` convention; in-list order is preserved by the composition.

    A processor must behave as a function of `(prefix_ids, scores)`. Internal state is
    permitted only as memoization keyed on the prefix and must re-derive on a prefix mismatch,
    since drivers may restart, rewind, or reorder sequences, and scoring replays prefixes
    teacher-forced (subclass `common.processors.base.PrefixKeyedProcessor` to satisfy this
    mechanically). Return fresh processor instances from this hook; it is invoked once per call
    precisely so that per-generation state is isolated.

    Args:
        input_ids: The steered prompt token ids `[batch, seq_len]`.
        runtime_kwargs: Per-call parameters supplied to `generate()`.

    Returns:
        A list of HF `LogitsProcessor`-style objects.
    """
    return []

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 [batch, seq_len].

required
runtime_kwargs dict | None

Per-call parameters supplied to generate().

required

Returns:

Type Description
list

A list of HF StoppingCriteria-style objects.

Source code in steerability/algorithms/output_control/base.py
139
140
141
142
143
144
145
146
147
148
149
150
151
152
def get_stopping_criteria(self, input_ids: torch.Tensor, runtime_kwargs: dict | None, **kwargs) -> list:
    """The control's stopping criteria.

    Not applied during scoring (there is no loop to stop). Same call convention as
    `get_logits_processors`.

    Args:
        input_ids: The steered prompt token ids `[batch, seq_len]`.
        runtime_kwargs: Per-call parameters supplied to `generate()`.

    Returns:
        A list of HF `StoppingCriteria`-style objects.
    """
    return []

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
def requirements(self) -> 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:
        The control's phase-keyed requirements.
    """
    score = needs(Capability.IN_PROCESS_TORCH) if self.include_in_scoring else ()
    return Requirements(generate=needs(Capability.IN_PROCESS_TORCH), score=score)

steer(model, tokenizer=None, session=None, **kwargs)

Optional one-time preparation (e.g., load a reward model, fit a probe).

session is a SteeringSession on the steering backend, provided by the pipeline.

Source code in steerability/algorithms/output_control/base.py
207
208
209
210
211
212
def steer(self, model: PreTrainedModel, tokenizer=None, session=None, **kwargs) -> None:
    """Optional one-time preparation (e.g., load a reward model, fit a probe).

    `session` is a `SteeringSession` on the steering backend, provided by the pipeline.
    """
    pass

steer_access()

The model access this instance's steer step requires, on the ModelAccess ladder.

The default is ModelAccess.FACTS (layout and tokenizer only). A control whose steer step generates or scores through the session declares ROLLOUTS, one that captures hidden states declares CAPTURE, and one that touches the model as a live torch.nn.Module declares MODULE. Configuration determines the result. The pipeline hands steer() a session scoped to the declared rung, and the live model only at MODULE. A control may retain the pipeline model beyond steer() only if its generate phase requires Capability.IN_PROCESS_TORCH.

Returns:

Type Description
ModelAccess

The declared access rung.

Source code in steerability/algorithms/core/base_control.py
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
def steer_access(self) -> ModelAccess:
    """The model access this instance's steer step requires, on the `ModelAccess` ladder.

    The default is `ModelAccess.FACTS` (layout and tokenizer only). A control whose steer
    step generates or scores through the session declares `ROLLOUTS`, one that captures
    hidden states declares `CAPTURE`, and one that touches the model as a live
    `torch.nn.Module` declares `MODULE`. Configuration determines the result. The pipeline
    hands `steer()` a session scoped to the declared rung, and the live model only at
    `MODULE`. A control may retain the pipeline model beyond `steer()` only if its
    generate phase requires `Capability.IN_PROCESS_TORCH`.

    Returns:
        The declared access rung.
    """
    return ModelAccess.FACTS

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
def steer_fits(self) -> tuple[tuple[str, str], ...]:
    """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:
        The declared fit artifacts, in declaration order.
    """
    return ()

resolve_generate_callable(model, runtime_kwargs, session=None)

Resolve the generate callable a driver rolls out with.

Drivers generate through the pipeline's session (a SteeredSession carrying this generation's control entries), so a driver runs on any backend whose session serves its rollout parameters.

Parameters:

Name Type Description Default
model PreTrainedModel | None

The pipeline model, or None on backends without a live model; unused.

required
runtime_kwargs dict | None

Per-call parameters; unused.

required
session Any

The SteeringSession for this generation.

None

Returns:

Type Description
Callable[..., Tensor]

A callable with the model.generate calling convention returning full sequences.

Raises:

Type Description
ValueError

If no session was provided.

Source code in steerability/algorithms/output_control/base.py
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
def resolve_generate_callable(
    model: PreTrainedModel | None, runtime_kwargs: dict | None, session: Any = None
) -> Callable[..., torch.Tensor]:
    """Resolve the generate callable a driver rolls out with.

    Drivers generate through the pipeline's session (a `SteeredSession` carrying this
    generation's control entries), so a driver runs on any backend whose session serves its
    rollout parameters.

    Args:
        model: The pipeline model, or None on backends without a live model; unused.
        runtime_kwargs: Per-call parameters; unused.
        session: The `SteeringSession` for this generation.

    Returns:
        A callable with the `model.generate` calling convention returning full sequences.

    Raises:
        ValueError: If no session was provided.
    """
    if session is None:
        raise ValueError("No generate callable available: the driver received no session.")

    def _generate(input_ids, attention_mask=None, **gen_kwargs):
        return session_generate(session, input_ids, attention_mask, **gen_kwargs)

    return _generate

stack_generate_kwargs(logits_processors, stopping_criteria)

Build the model.generate kwargs for the composed stacks, each included only when non-empty.

Shared by every driver that delegates to model.generate (the default driver, and the segment and phase drivers per rollout or per phase) so the "pass the stack only when non-empty" rule lives in one place.

Source code in steerability/algorithms/output_control/base.py
44
45
46
47
48
49
50
51
52
53
54
55
56
def stack_generate_kwargs(logits_processors, stopping_criteria) -> dict:
    """Build the `model.generate` kwargs for the composed stacks, each included only when non-empty.

    Shared by every driver that delegates to `model.generate` (the default driver, and the segment
    and phase drivers per rollout or per phase) so the "pass the stack only when non-empty" rule
    lives in one place.
    """
    extra: dict = {}
    if logits_processors is not None and len(logits_processors):
        extra["logits_processor"] = logits_processors
    if stopping_criteria is not None and len(stopping_criteria):
        extra["stopping_criteria"] = stopping_criteria
    return extra