Batching
steerability.evaluation.batching
Lock-leader batching collator for the steering-pipeline model provider.
Inspect issues one async request per sample, many concurrently; a SteeringPipeline is synchronous
and permits one in-flight generation, but accepts batched calls. The collator turns concurrent
requests into batched pipeline.generate() calls with no background task and no timer. One
anyio.Lock guards pipeline access: every request enqueues its record and then acquires the lock;
whoever holds it (the leader) takes its record plus the compatible queued records, dispatches one
batched call in a worker thread, and completes every member. A waiter that wakes with its record
already served returns without dispatching. The lock holder is the only caller of
pipeline.generate, so the one-in-flight invariant holds by construction.
An uncontended lock acquisition yields once to the event loop, so requests admitted in the same scheduling window enqueue before the first leader takes its batch, and while a batch is in flight newly arriving requests accumulate; the in-flight generation is the collation window. With a batch ceiling of 1 the same code path degenerates to strict serialization.
Records share a dispatch only when the batched call is semantically identical to per-request calls. The batch key digests the canonicalized call-scoped generation kwargs (seed and stop strings included), the sorted key set of the record's retained per-sample runtime kwargs (values may differ per row; keys may not), and a single-candidate flag; multi-candidate requests always form singleton dispatches. Per-sample values never enter the key.
Runtime kwargs reach the collator on two tiers and are interpreted against the declared scopes of
the pipeline's enabled controls. A "row"-scoped static value is one row's value broadcast to every
row of the dispatch; a "call"-scoped static value passes through unchanged. A per-sample value of a
"row"-scoped key is collated row by row across the dispatch; a per-sample key declared
"call"-scoped is rejected at admission. A name that no enabled control declares is inert on either
tier: it is dropped from the call, excluded from the batch key, recorded in inert_runtime_kwargs,
and logged once per collator.
A failed multi-row dispatch triggers one poison-isolation pass: the members re-run serially, once, so each record receives its own result or its own exception. Two consequences follow. First, the serial pass regenerates the members that would have succeeded in the batch, so under sampling their outputs differ from what the batch would have produced. Second, a batch that fails while every member succeeds serially indicates a shape problem rather than a bad sample (the signature of a row-scoped value whose per-row form is wrong); the collator logs a warning once per distinct batch-level exception message, naming the batch size, so the misconfiguration surfaces instead of degrading silently into serial throughput.
Reproducibility contract: which samples are evaluated is fixed by the suite, independent of batch
composition. A seeded dispatch carries seed_scope from ProviderOptions (default "dispatch"): on the
Hugging Face backend the dispatch decodes in one batched pass under one derived seed, so the dispatch is
reproducible as a whole while an individual sample's continuation depends on its dispatch-mates and row
position; under "item" scope each row derives its own seed and the session decodes rows one at a time. On
the vLLM backends per-request seeds execute inside one engine batch under either scope. Under the collator a
sample's dispatch membership and row index depend on async arrival timing, so bitwise reproducibility of
stochastic sampling is not preserved under concurrency under either scope; a bitwise-reproducible stochastic
run requires a batch ceiling of 1 and Inspect max_connections=1. Greedy decoding sidesteps seed sensitivity,
with the one caveat that padded-batch numerics can differ from single-item numerics on some kernels.
logger = logging.getLogger(__name__)
module-attribute
BatchRequest
dataclass
One admitted generation request, queued for a batched dispatch.
Attributes:
| Name | Type | Description |
|---|---|---|
prompt |
Any
|
One conversation (a list of chat-message dicts) on the messages path, or one rendered string on the text path. |
gen_kwargs |
dict[str, Any]
|
The call-scoped generation kwargs mapped from the request's config. |
per_sample_runtime_kwargs |
dict[str, Any]
|
The request's retained per-sample runtime kwargs; every key is
declared |
num_choices |
int
|
Number of candidates requested; values above 1 dispatch as a singleton. |
batch_key |
str
|
Digest governing which records may share a dispatch. |
done |
Event
|
Event set by the leader once the record holds its result. |
output |
Output | None
|
The record's |
error |
BaseException | None
|
The record's exception on failure, else None. |
Source code in steerability/evaluation/batching.py
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 | |
batch_key
instance-attribute
done = field(default_factory=anyio.Event)
class-attribute
instance-attribute
error = None
class-attribute
instance-attribute
gen_kwargs
instance-attribute
num_choices
instance-attribute
output = None
class-attribute
instance-attribute
per_sample_runtime_kwargs
instance-attribute
prompt
instance-attribute
LockLeaderCollator
Collate concurrent async requests into batched pipeline.generate() calls.
See the module docstring for the protocol, the batch-key semantics, poison isolation, and the
reproducibility contract. admit() validates and builds a record without enqueueing; the
async serve() enqueues it and runs the leader protocol.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pipeline
|
SteeringPipeline
|
The steered pipeline; the lock holder is its only caller. |
required |
max_batch_size
|
int
|
Dispatch ceiling; the caller passes the effective value (already clamped to 1 when the pipeline does not support batching). |
required |
prompt_path
|
Literal['messages', 'text']
|
|
required |
declared_scopes
|
Mapping[str, str]
|
Runtime-kwarg names declared by the pipeline's enabled controls, mapped
to their scope ( |
required |
static_runtime_kwargs
|
Mapping[str, Any]
|
Runtime kwargs applied to every dispatch: |
required |
chat_template_kwargs
|
Mapping[str, Any] | None
|
Forwarded to |
None
|
Source code in steerability/evaluation/batching.py
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 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 | |
closed
property
Whether the collator has been closed.
inert_runtime_kwargs
property
Runtime kwargs seen on either tier that no enabled control declares.
admit(prompt, gen_kwargs, per_sample_runtime_kwargs, num_choices)
Validate one request and build its record, without enqueueing it.
Per-sample keys are interpreted against the declared scopes: a "row"-scoped key is retained
for row-aligned collation, a "call"-scoped key is rejected, and an undeclared key is inert
(dropped from the record and the batch key, and recorded in inert_runtime_kwargs).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
prompt
|
Any
|
The converted prompt (a conversation or a rendered string). |
required |
gen_kwargs
|
Mapping[str, Any]
|
The mapped call-scoped generation kwargs. |
required |
per_sample_runtime_kwargs
|
Mapping[str, Any]
|
The request's per-sample runtime kwargs. |
required |
num_choices
|
int
|
Number of candidates requested (at least 1). |
required |
Returns:
| Type | Description |
|---|---|
BatchRequest
|
The admitted |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If the collator is closed. |
ValueError
|
If a per-sample key is also supplied statically, or is declared
|
Source code in steerability/evaluation/batching.py
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 | |
close()
Refuse new admissions; an in-flight batch completes and delivers its results.
Source code in steerability/evaluation/batching.py
159 160 161 | |
serve(record)
async
Enqueue one admitted record and run the leader protocol until it holds its result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
record
|
BatchRequest
|
A record built by |
required |
Returns:
| Type | Description |
|---|---|
Output
|
The record's |
Raises:
| Type | Description |
|---|---|
Exception
|
The record's own dispatch exception, re-raised on the requesting task. |
Source code in steerability/evaluation/batching.py
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 278 | |