Troubleshooting¶
Reinfors validates the native/Python boundary strictly so malformed network outputs fail near the callback rather than corrupting search. Match a traceback fragment here first:
| Exception contains | Likely cause | Go to |
|---|---|---|
unknown reward key |
The game does not emit that named reward event | Construction is rejected |
incompatible policy/learner composition |
The policy and learner cannot form an Engine | Construction is rejected |
incompatible encoder |
The encoder belongs to another game | Construction is rejected |
not compatible with, would be clairvoyant, or supports at most |
The algorithm does not support that game's semantics | Construction is rejected |
requires a two-player sequential game |
Minimax was composed with a game it cannot serve | Construction is rejected |
requires an antisymmetric reward |
Minimax's zero-sum negation needs loss = -win, draw = 0 |
Construction is rejected |
search tree exceeds |
Full-width search outgrew the node bound at collection time | Construction is rejected |
win_food_lead is a two-snake rule |
win_food_lead was combined with num_snakes != 2 |
Construction is rejected |
was removed in 0.3.0 |
The config carries pre-0.3 n_groups/pad_rows_to fields |
Construction is rejected |
expected … per-player infer callables |
The callback sequence does not match the player count | Per-player callback count |
must be a float64 or float32 ndarray |
Unsupported callback dtype or non-array output | Callback dtype or shape errors |
must be a 1-d ndarray, must be a 2-d ndarray, or must be a 3-d ndarray |
Wrong callback rank | Callback dtype or shape errors |
infer returned shape, or AlphaZero/Ppo infer must return |
Wrong callback rows, heads, or action count | Wrong row or action count |
outputs must contain only finite values |
The callback returned NaN or infinity | NaN or infinity from inference |
this engine is held by a collect_stream |
A stream still owns the engine | The engine is held by a stream |
snapshot is from a different composition |
Snapshot and Engine fingerprints differ | Snapshot restoration is rejected |
policy_version mismatch |
The loaded model is not the snapshot's model | Snapshot restoration is rejected |
Construction is rejected¶
unknown reward key names the rejected key and the valid keys for that game. Choose event names
from the game catalogue; unknown names are never ignored.
engine config field 'n_groups' was removed in 0.3.0 (likewise pad_rows_to): 0.3.0
replaced grouped collection with the threshold scheduler. Delete the field and tune
n_threads/batch_size (or pad) instead — see
scheduler knobs.
incompatible policy/learner composition means the two handles do not share a training contract.
Use an exact pairing from the algorithm catalogue and confirm the game
supports it in the compatibility matrix.
incompatible encoder means the handle passed through encoder= belongs to a different game.
Choose one from that game's rows in the encoder catalogue.
search tree exceeds arrives at collection time rather than construction: Minimax is full-width, and the action vocabulary cannot statically predict realized legal branching, so the tree bound is checked as the search grows. Lower depth or set top_k.
Errors containing not compatible with, would be clairvoyant, or supports at most reject a
game/algorithm pairing because of information, player-count, or dynamics requirements. Choose a
supported row from the same compatibility matrix rather than bypassing the constructor check.
win_food_lead is defined only for exactly two snakes. Leave it as None when num_snakes is
greater than two; the constructor rejects the combination rather than inventing a multi-player
interpretation of the lead.
Per-player callback count¶
A bare callable is shared by every player. A sequence selects per-player inference instead and must contain exactly one callable per game player, in player order. This rule applies to Engine collection and Deep CFR; a short sequence never silently reuses its final callback.
Callback dtype or shape errors¶
Engine observations arrive as a two-dimensional float32 NumPy array. Outputs may be float32 or
float64; native float32 is recommended and widened exactly inside reinfors. Q-family policies
require (rows, heads, actions), while AlphaZero requires a tuple of logits
(rows, width >= actions) and values (rows,). Deep CFR instead returns (rows, actions).
Framework tensors must be detached, moved to CPU, and converted to NumPy before returning. Do not
remove a singleton Q head: (rows, 1, actions) is distinct from (rows, actions). The complete
shapes are in the inference contract. Dtype/rank errors report the returned
object's observed Python type, dtype, and shape.
Wrong row or action count¶
Return exactly one output row for every input row, even when a row has no actions you wish to train. The action dimension is the game's fixed action vocabulary, not the number of currently legal actions. Q-family and Deep CFR widths must match it exactly. AlphaZero may return a wider padded policy head; reinfors ignores columns after the action vocabulary. Legality is applied around the network.
NaN or infinity from inference¶
Callbacks must return finite values. Reinfors rejects NaN or infinity before those values enter search. Check normalization, empty reductions, masked logits, and division by zero in the caller's forward path.
The engine is held by a stream¶
An active CollectStream exclusively borrows its engine. Calling engine.collect, starting another
stream, restoring, or snapshotting during that borrow raises this engine is held by a
collect_stream. Use a context manager and call pause() for a lossless checkpoint barrier or
stop() when queued work may be discarded. A paused stream is finished; create a new stream to
continue. See concurrent collection.
Snapshot restoration is rejected¶
snapshot is from a different composition means restore found that the snapshot's internal
composition fingerprint does not match the destination Engine. Reconstruct the Engine from the
resolved configuration saved with that snapshot. Do not compare engine.config_fingerprint() with
snapshot.fingerprint; those public values intentionally hash different representations.
policy_version mismatch means expect_policy_version does not name the model recorded in the
snapshot. Load that exact model checkpoint and pass its identifier. Do not remove the check merely
to make restoration proceed: native search state and external weights would then describe different
points in training. See the crash-resume checklist.
Training runs but the agent plays badly¶
This class of failure may not raise an exception:
- With a transforming encoder, convert between the network-head and game-action frames. A missed conversion can select legal but wrong actions; see action frames.
- Confirm reward keys and weights against the game catalogue. A valid all-zero mapping produces no learning signal.
- For DQN, use
batch.can_bootstrapand reduce losses over included bootstrap-mask entries. mean_sigmaandmean_disagreementare always0.0atn_heads=1; they measure ensemble variation only atn_heads >= 2.
Changes are missing in a source checkout¶
Python binding edits and native Rust changes do not affect an already-built extension. From the repository environment, rebuild with:
Then rerun the smallest relevant test before the full suite. Pure Markdown and Python-only changes
do not require rebuilding Rust. Use maturin develop --release only for performance measurements;
the default debug build gives the faster development cycle described in
development setup.
Next steps¶
- Verify exact fields in batch formats.
- Interpret throughput and search counters with telemetry fields.
- Report a reproducible defect with the resolved configuration, build profile, seed, and complete exception traceback.