Cross-Configuration Logprob Drift Contract¶
Status: V1 implementation contract
Related work:
Goal and boundary¶
This framework isolates configuration changes that can make rollout-selected log probabilities differ from training-side recomputation. It provides typed plans, lifecycle-aware runtime materialization, exact semantic-operator selection, paired read-only scoring, append-only artifacts, and safe resume.
It does not implement production AG, RS, GEMM, attention, logprob, TP-invariant, CP-aware, or deterministic collective kernels. Those implementations remain owned by their operator workstreams and integrate through the semantic operator catalog described below.
The only pass/fail rule¶
For every active selected response/action token:
The fixed threshold is loaded from the repository numerical contract. It is not a config field, CLI flag, experiment axis, workload policy, or operator option. Equality with the threshold is not a mismatch.
Mean, percentiles, maximum absolute difference, mismatch ratio, worst-token location, and approximate KL are diagnostics only. They never change pass/fail. The token-level artifact persists both logprob tensors, the active mask, and the resolved threshold so the mask can be recomputed offline.
Zero active tokens produce ZERO_ACTIVE_TOKENS, never a pass. Non-finite or
non-floating active scores produce INVALID_ARTIFACT.
Identity before numerics¶
A comparison is valid only when both scorers use the same logical input:
- immutable checkpoint and model version;
- tokenizer ID and tokenization policy;
- generated token IDs and selected-token IDs;
- active and attention masks;
- pre-update model state;
- required position, cache, and packing metadata.
The training scorer teacher-forces the already generated sequence. It cannot
generate replacement tokens, use a KV cache when the frozen identity forbids
one, own an optimizer, update parameters or buffers, or leave the model in a
different mode. An identity violation is INVALID_IDENTITY, not numerical
drift.
V1 configuration¶
The user supplies one explicit baseline plus declared interventions. Lists do not imply a Cartesian product.
{
"schema_version": "cross_config.experiment_config.v1",
"experiment_id": "qwen3-8b-alignment",
"scenario_id": "qwen3-8b-tp4-cp4-bf16",
"contract_source": "ws1",
"contract_version": "current",
"strategy": "one_at_a_time",
"strict_fallback": true,
"identity": {"...": "frozen scoring identity"},
"baseline": {
"batch": {"size": 8},
"rollout": {
"tensor_parallel_size": 4,
"context_parallel_size": 4,
"dtype": "bfloat16",
"enable_prefix_caching": true,
"enforce_eager": false
},
"training": {
"attention_backend": "flash_attention_2",
"compute_dtype": "bfloat16",
"sharding": "fsdp"
},
"logp": {"backend": "native"}
},
"interventions": [
{"path": "batch.size", "values": [1]},
{"path": "logp.backend", "values": ["rlkernel.reference_logp"]}
],
"scenario": {"level": "S3", "device": "cuda"}
}
scenario is metadata. Execution mode and authorization policy belong to the
CLI, so a config cannot hide plan_only, expected test outcomes, or permission
to activate temporary operators.
Exact knob allowlist¶
| Knob | Minimum lifecycle | Meaning |
|---|---|---|
batch.size |
request | Canonical sample chunking only. |
rollout.tensor_parallel_size |
process | Rollout TP world. |
rollout.context_parallel_size |
process | Rollout CP world. |
rollout.dtype |
engine construction | Rollout numerical dtype. |
rollout.enable_prefix_caching |
engine construction | Engine cache policy. |
rollout.enforce_eager |
engine construction | Eager versus optimized/graph path. |
training.attention_backend |
engine construction | Training scorer attention implementation. |
training.compute_dtype |
engine construction | Training scorer compute dtype. |
logp.backend |
engine construction | Both-sides selected-logprob shortcut. |
training.sharding |
process | Training topology, such as unsharded or FSDP. |
TP/vocabulary layout is derived and recorded, not user-settable. Tokenization, masks, positions, checkpoint identity, and pre-update state are invariants, not ordinary knobs. Quantization, FP8, MoE, speculative decoding, pipeline parallelism, and arbitrary runtime fields are deferred.
Planning¶
one_at_a_time emits one baseline and cases that change exactly one declared
path. pairwise is opt-in and expands only explicitly listed path pairs. The
planner normalizes aliases, validates the allowlist and capability constraints,
and reports structured issues without creating engines. A fixed 256-case
framework cap stops OAT or pairwise expansion before unbounded accumulation.
Stable case IDs hash normalized requested values, identity, contract version, and scenario definition. Runtime readback never rewrites a case ID. A retry gets a new attempt ID under the same case.
Architecture and extension points¶
The core has one-way responsibilities:
strict config -> Planner -> ExperimentPlan -> build_execution_plan
|
operator-bound ExecutionPlan
|
runtime adapter -> RuntimeBinding
|
paired runner
/ \
artifact store comparator
config.pyowns the external schema and strict JSON loading.schema.pyowns immutable, versioned domain records.planner.pyowns normalization, the knob catalog, OAT, and pairwise cases.execution_plan.pybinds the selected rollout/training operators into each immutable case and produces canonical rows shared by planning and execution.runtime.pyowns the adapter protocol, three-stage materialization, lifecycle fingerprints, and a backend-neutral execution binding.comparison.pyowns identity validation and the fixed-threshold result.runner.pycoordinates paired execution and atomic publication; private execution, provenance, and resume modules isolate process supervision and validation details.artifacts.pyowns append-only attempts and resume discovery.semantic_registry.pyowns generic operator descriptors and case-local resolution sessions; it is shared by future alignment features.
The package root exposes only the common planning and execution facade. Runtime, artifact, schema, and operator internals remain in their owning modules.
Runtime adapters¶
A runtime adapter receives the normalized case and returns one application record per knob:
Each record includes status (applied, fallback, unsupported,
unobservable, or error), evidence, and lifecycle. The facade derives
construction, distributed-context, and process fingerprints. Reuse is allowed
only when all relevant fingerprints and operator bindings match.
Adapters may construct repository-native vLLM, training, or stateless config objects internally. The core runner receives only backend-neutral batch, side configuration, topology, scorer, operator-backend, and runtime-kind mappings, so adding a runtime does not add branches to the planner or runner.
Strict execution rejects fallback, ignored settings, unobservable critical values, stale registry state, and incompatible reuse. Fallback is measurable only when it is itself the declared intervention.
Semantic operator selection¶
The first semantic operator is selected_logprob. Rollout and training can
select implementations independently:
{
"operators": {
"selected_logprob": {
"rollout": "rlkernel.reference_logp",
"training": {
"backend": "rlkernel.reference_logp",
"options": {}
}
}
}
}
When operators is absent, logp.backend selects the same backend on both
sides. An explicit mapping is bound into execution identity before a runtime is
created. A logp.backend intervention cannot be combined with a fixed explicit
mapping because that would create a knob that no longer changes execution.
Each backend descriptor declares:
- semantic operation and backend ID;
- supported target tags, devices, dtypes, and per-target required topology values;
- alignment properties and lifecycle;
- implementation factory and version/build fingerprint;
- explicit fallback policy and temporary-test marker.
SemanticOperatorCatalog stores immutable descriptors. Each case creates an
OperatorSession for resolution, instantiation, caching, and provenance. Failed
or cached state cannot leak into the next case. Strict resolution never invokes
legacy priority fallback.
Adding a production implementation requires its existing semantic interface, one descriptor, runtime injection hooks where needed, operator-owned correctness tests, and one framework case. It does not require a planner change.
Artifacts and resume¶
Attempts are append-only:
runs/<experiment>/
experiment.json
plan.jsonl
cases/<case>/<attempt>/
requested.json
materialized.json
actual.json
identity.json
score_rollout.pt
score_training.pt
comparison.json
token_diffs.pt
COMPLETE
COMPLETE is published last and seals every required payload with a SHA-256
digest. Resume accepts only a complete attempt whose case, identity,
materialization, scorer, operator, environment, comparison, and tensor artifacts
match the current execution key. Partial, malformed, or tampered attempts are
ignored; an older valid attempt may still be reused. Existing files are never
overwritten.
CPU smoke boundary¶
The only executable adapter delivered here is under
rl_engine.alignment.testing.cpu_cross_config. It is explicitly CPU-only and
uses a deterministic synthetic model plus read-only stateless scoring. Named
distributed and accelerator scenarios are configuration/plan coverage only.
Temporary selected-logprob backends live together under
rl_engine/alignment/testing/smoke_ops:
smoke_only.logp_reference: PyTorchlog_softmaxplus gather;smoke_only.logp_offset: the same result with an authorized deterministic offset used to prove mismatch detection.
They are CPU-only, marked is_smoke_only, unregistered by default, and require
both explicit registration and execution policy authorization. Their exact
removal procedure is in SMOKE_OPERATORS.md. Remove them when equivalent
production operators pass the same framework cases, then remove the opt-in flag
and temporary test marker.
Scenario levels and claims¶
| Level | Purpose | Current claim |
|---|---|---|
| S0 | Local CPU framework smoke | Executable: config, planner, operator selection, paired scoring, comparison, artifacts, resume. |
| S1 | Small distributed lifecycle smoke | Plan only until suitable hardware/runtime adapters exist. |
| S2 | Named vLLM TP versus training FSDP comparison | Plan only. |
| S3 | Qwen3-8B TP=4, CP=4, BF16 milestone | Plan only; no production alignment claim. |
Run the shipped examples with:
python -m rl_engine.alignment.cross_config plan \
examples/cross_config_s3_qwen3_8b_tp4_cp4_bf16.json
python -m rl_engine.alignment.cross_config run \
examples/cross_config_s0_cpu_smoke.json \
--runtime cpu-smoke \
--allow-smoke-operators
Passing S0 proves framework plumbing only. It does not prove accelerator, distributed, production-operator, or roadmap numerical alignment.