Visual Question Answering
Transformers
Safetensors
cvrr_merged
feature-extraction
cvrr
custom_code
latent-reasoning
Instructions to use dmis-lab/InternVL3-9B-CVRR with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use dmis-lab/InternVL3-9B-CVRR with Transformers:
# Use a pipeline as a high-level helper # Warning: Pipeline type "visual-question-answering" is no longer supported in transformers v5. # You must load the model directly (see below) or downgrade to v4.x with: # pip install "transformers<5.0.0" from transformers import pipeline pipe = pipeline("visual-question-answering", model="dmis-lab/InternVL3-9B-CVRR", trust_remote_code=True)# pip install -U transformers accelerate # Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("dmis-lab/InternVL3-9B-CVRR", trust_remote_code=True, device_map="auto") - Notebooks
- Google Colab
- Kaggle
Update config and release files
Browse files- README.md +8 -4
- config.json +2 -11
- configuration_source_qwen25.py +0 -1142
- configuration_source_qwen3.py +0 -740
- cvrr_release_config.json +0 -19
- modeling_cvrr_merged.py +137 -19
- modeling_source_qwen25.py +0 -0
- modeling_source_qwen3.py +0 -0
- source_perceive.py +0 -414
- source_spatial.py +0 -349
- source_splitting.py +0 -473
README.md
CHANGED
|
@@ -88,12 +88,16 @@ inputs = model.prepare_inputs(
|
|
| 88 |
image,
|
| 89 |
"What is the dominant color? A. Red B. Blue C. Green D. Yellow. Answer with the option letter.",
|
| 90 |
)
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
```
|
| 95 |
|
| 96 |
-
This model
|
| 97 |
|
| 98 |
Use one complete model replica per GPU. This custom Transformers loader does
|
| 99 |
not support automatic model sharding, generic `save_pretrained()` reserialization,
|
|
|
|
| 88 |
image,
|
| 89 |
"What is the dominant color? A. Red B. Blue C. Green D. Yellow. Answer with the option letter.",
|
| 90 |
)
|
| 91 |
+
answer_ids = model.generate(
|
| 92 |
+
**inputs,
|
| 93 |
+
do_sample=False,
|
| 94 |
+
max_new_tokens=64,
|
| 95 |
+
eos_token_id=model.tokenizer.eos_token_id,
|
| 96 |
+
)
|
| 97 |
+
print(model.tokenizer.decode(answer_ids[0], skip_special_tokens=True))
|
| 98 |
```
|
| 99 |
|
| 100 |
+
This model supports strict first-answer-token readout and deterministic greedy answer generation. The default recurrent update coefficient is `beta=0.5`.
|
| 101 |
|
| 102 |
Use one complete model replica per GPU. This custom Transformers loader does
|
| 103 |
not support automatic model sharding, generic `save_pretrained()` reserialization,
|
config.json
CHANGED
|
@@ -15,16 +15,7 @@
|
|
| 15 |
"recurrent_layer": 36,
|
| 16 |
"upper_decoder_start": 37,
|
| 17 |
"inference_T": 4,
|
| 18 |
-
"inference_beta": 0.
|
| 19 |
-
"
|
| 20 |
-
"lora_alpha": 12.0,
|
| 21 |
-
"lora_dropout": 0.01,
|
| 22 |
-
"checkpoint_step": 500,
|
| 23 |
-
"format": "cvrr_native_plus_merged_transition_v1",
|
| 24 |
-
"merged_weight_dtype": "float32",
|
| 25 |
-
"native_weight_bytes": 18277586944,
|
| 26 |
-
"status": "gpu_vstar_comparison_completed",
|
| 27 |
-
"upload_ready": true,
|
| 28 |
-
"hub_model_id": "dmis-lab/InternVL3-9B-CVRR"
|
| 29 |
}
|
| 30 |
}
|
|
|
|
| 15 |
"recurrent_layer": 36,
|
| 16 |
"upper_decoder_start": 37,
|
| 17 |
"inference_T": 4,
|
| 18 |
+
"inference_beta": 0.5,
|
| 19 |
+
"format": "cvrr_native_plus_merged_transition_v1"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
}
|
| 21 |
}
|
configuration_source_qwen25.py
DELETED
|
@@ -1,1142 +0,0 @@
|
|
| 1 |
-
"""Configuration for CLOSE latent reasoning on Qwen2.5-VL.
|
| 2 |
-
|
| 3 |
-
The current ``raw_evidence_question`` path keeps the complete layer-``ell_star``
|
| 4 |
-
visual memory as immutable evidence and recurrently updates the text-only
|
| 5 |
-
question state by cross-attending to it. Its selected interface averages all
|
| 6 |
-
four residual recurrent states and may use a training-only restoration target
|
| 7 |
-
from the normal multimodal question state. Historical slot-workspace fields are
|
| 8 |
-
retained so old checkpoints remain loadable, but they are mutually exclusive
|
| 9 |
-
with the raw-evidence path.
|
| 10 |
-
|
| 11 |
-
Every architectural hyperparameter of Method §3 lives here -- nothing is
|
| 12 |
-
hardcoded in ``modeling_close_qwen2_5_vl.py``.
|
| 13 |
-
|
| 14 |
-
Layer-index convention (used consistently across the repo)
|
| 15 |
-
----------------------------------------------------------
|
| 16 |
-
``ell_star`` is **0-indexed and inclusive**: it is the index of the last decoder
|
| 17 |
-
layer belonging to the lower branch. With ``num_hidden_layers == 28``::
|
| 18 |
-
|
| 19 |
-
F_{<=l*} = language_model.layers[: ell_star + 1] # lower_slice
|
| 20 |
-
F_{>l*} = language_model.layers[ell_star + 1 :] # upper_slice
|
| 21 |
-
|
| 22 |
-
so ``ell_star=18`` means layers 0..18 run before the split and 19..27 after it.
|
| 23 |
-
``ell_star`` is measured once by ``scripts/localize_read_point.py`` (§3.1) and
|
| 24 |
-
frozen before any workspace training.
|
| 25 |
-
|
| 26 |
-
Careful with ``Qwen2_5_VLConfig``: its ``__setattr__`` forwards any attribute
|
| 27 |
-
whose name already exists in ``text_config.__dict__`` down to the sub-config, and
|
| 28 |
-
its ``__init__`` builds ``text_config`` from ``**kwargs``. Workspace fields are
|
| 29 |
-
therefore declared as explicit named parameters so they never enter ``kwargs``
|
| 30 |
-
and never shadow a text-config field. ``tests/test_config_roundtrip.py`` guards
|
| 31 |
-
this.
|
| 32 |
-
"""
|
| 33 |
-
|
| 34 |
-
from __future__ import annotations
|
| 35 |
-
|
| 36 |
-
import math
|
| 37 |
-
|
| 38 |
-
from transformers.models.qwen2_5_vl.configuration_qwen2_5_vl import Qwen2_5_VLConfig
|
| 39 |
-
|
| 40 |
-
#: How the ``S`` workspace slots are assigned M-RoPE positions once they are
|
| 41 |
-
#: spliced into the replacement cache above ``ell_star``. Qwen2.5-VL uses 3D
|
| 42 |
-
#: M-RoPE (t, h, w) with ``mrope_section=[16, 24, 24]``, so slots -- which have
|
| 43 |
-
#: no spatial extent -- need an explicit convention.
|
| 44 |
-
WORKSPACE_ROPE_MODES = (
|
| 45 |
-
# Slots continue the 1-D text position sequence directly after Q*, with
|
| 46 |
-
# t == h == w. Treats the workspace as "more text".
|
| 47 |
-
"continue",
|
| 48 |
-
# Slots reuse the (t, h, w) span the image tokens occupied in the original
|
| 49 |
-
# multimodal prefill, subsampled to S positions. Preserves the read point's
|
| 50 |
-
# positional geometry but reintroduces image-derived indices.
|
| 51 |
-
"image_span",
|
| 52 |
-
# All slots share one position (the first position after Q*). Makes the
|
| 53 |
-
# workspace order-free, matching the set semantics of Perceiver-style slots.
|
| 54 |
-
"shared",
|
| 55 |
-
)
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
class CloseQwen2_5_VLConfig(Qwen2_5_VLConfig):
|
| 59 |
-
r"""Configuration for :class:`CloseQwen2_5_VLForConditionalGeneration`.
|
| 60 |
-
|
| 61 |
-
Args:
|
| 62 |
-
ell_star: 0-indexed inclusive last layer of the lower branch. ``None``
|
| 63 |
-
until §3.1 localization has run; the model refuses to build with
|
| 64 |
-
``None`` rather than falling back to a guessed depth.
|
| 65 |
-
num_workspace_steps: the fixed recurrent horizon (paper notation ``T``;
|
| 66 |
-
per-item segment counts ``n`` exist only in the losses, never here).
|
| 67 |
-
The trainable parameter count is independent of it because
|
| 68 |
-
``f_theta`` is shared across steps, so it is a free experimental
|
| 69 |
-
axis. ``0`` is legal and is the no-recurrence floor: ``Z^(0)`` goes
|
| 70 |
-
straight to the decoder.
|
| 71 |
-
num_workspace_slots: ``S``, slots in ``Z``. Free capacity knob.
|
| 72 |
-
workspace_width: ``d_w``, the slot width. Free capacity knob -- the
|
| 73 |
-
method's claim is path exclusivity (all visual information routed
|
| 74 |
-
through ``Z``), NOT an information bottleneck, so ``S`` and ``d_w``
|
| 75 |
-
carry no methodological commitment. Kept below ``d`` (3584 for the
|
| 76 |
-
7B) since ``P_Z`` projects up at the splice.
|
| 77 |
-
workspace_num_heads: attention heads inside ``r_theta`` / ``f_theta``.
|
| 78 |
-
read_num_blocks: cross-attention blocks in ``r_theta``. The *read* is
|
| 79 |
-
once regardless of depth: all blocks see the same ``V*`` in a single
|
| 80 |
-
forward, and ``V*`` is discarded afterwards.
|
| 81 |
-
transition_num_blocks: blocks in the shared recurrent ``f_theta``.
|
| 82 |
-
workspace_ffn_mult: FFN expansion inside workspace blocks.
|
| 83 |
-
workspace_dropout: dropout inside workspace blocks.
|
| 84 |
-
workspace_rope_mode: one of :data:`WORKSPACE_ROPE_MODES`.
|
| 85 |
-
adapter_rank: rank of the low-rank adapter on the post-``ell_star``
|
| 86 |
-
backbone layers. ``0`` disables it (frozen backbone only).
|
| 87 |
-
adapter_alpha: LoRA-style scaling; effective scale is
|
| 88 |
-
``adapter_alpha / adapter_rank``.
|
| 89 |
-
adapter_dropout: dropout probability applied only to the LoRA branch.
|
| 90 |
-
lambda_traj: legacy trajectory-loss weight. The current method sets it
|
| 91 |
-
to zero and trains with final-answer CE only; it remains serialized
|
| 92 |
-
solely so historical checkpoints can still be inspected.
|
| 93 |
-
"""
|
| 94 |
-
|
| 95 |
-
model_type = "close_qwen2_5_vl"
|
| 96 |
-
|
| 97 |
-
def __init__(
|
| 98 |
-
self,
|
| 99 |
-
ell_star: int | None = None,
|
| 100 |
-
num_workspace_steps: int = 4,
|
| 101 |
-
num_workspace_slots: int = 32,
|
| 102 |
-
workspace_width: int = 512,
|
| 103 |
-
workspace_num_heads: int = 8,
|
| 104 |
-
read_num_blocks: int = 1,
|
| 105 |
-
transition_num_blocks: int = 2,
|
| 106 |
-
workspace_ffn_mult: int = 4,
|
| 107 |
-
workspace_dropout: float = 0.0,
|
| 108 |
-
workspace_rope_mode: str = "continue",
|
| 109 |
-
adapter_rank: int = 16,
|
| 110 |
-
adapter_alpha: int = 32,
|
| 111 |
-
adapter_dropout: float = 0.0,
|
| 112 |
-
adapter_exclude_layers: tuple = (),
|
| 113 |
-
lambda_traj: float = 0.0,
|
| 114 |
-
read_shortcut: bool = False,
|
| 115 |
-
splice_scale_match: bool = False,
|
| 116 |
-
read_gating: bool = False,
|
| 117 |
-
gate_init_eps: float = 0.02,
|
| 118 |
-
read_residual_pure: bool = False,
|
| 119 |
-
residual_norm_cap: float = 0.0,
|
| 120 |
-
pure_warm_start: bool = False,
|
| 121 |
-
final_verify: bool = False,
|
| 122 |
-
mid_read_step: int = 0,
|
| 123 |
-
persistent_slot_id: bool = False,
|
| 124 |
-
mid_read_alpha: float = 1.0,
|
| 125 |
-
evidence_reasoning: bool = False,
|
| 126 |
-
raw_evidence_question: bool = False,
|
| 127 |
-
raw_attention_source_layer: int = -1,
|
| 128 |
-
raw_transition_ffn: bool | None = None,
|
| 129 |
-
raw_state_replacement: bool = False,
|
| 130 |
-
raw_question_anchor: bool = False,
|
| 131 |
-
raw_mean_aggregation: bool = False,
|
| 132 |
-
hierarchical_visual_layers: tuple = (),
|
| 133 |
-
counterfactual_residual_recurrence: bool = False,
|
| 134 |
-
visual_counterfactual_recurrence: bool = False,
|
| 135 |
-
visual_cumulative_recurrence: bool = False,
|
| 136 |
-
visual_full_state_recurrence: bool = False,
|
| 137 |
-
visual_boundary_reentry: bool = False,
|
| 138 |
-
visual_source_centered_reentry: bool = False,
|
| 139 |
-
recurrence_only_adapter: bool = False,
|
| 140 |
-
visual_preserving_adapter_correction: bool = False,
|
| 141 |
-
perceive_deliberate_chain: bool = False,
|
| 142 |
-
local_visual_latent_chain: bool = False,
|
| 143 |
-
pdlc_transition_conditioned: bool = False,
|
| 144 |
-
pdlc_question_visible_through_pair: int = 1,
|
| 145 |
-
pdlc_question_curriculum: bool = False,
|
| 146 |
-
latent_chain_init_token_id: int = -1,
|
| 147 |
-
spatial_visual_recurrence: bool = False,
|
| 148 |
-
spatial_recurrence_steps: int = 8,
|
| 149 |
-
spatial_recurrence_inner_width: int = 256,
|
| 150 |
-
spatial_recurrence_heads: int = 8,
|
| 151 |
-
spatial_step_override: int = -1,
|
| 152 |
-
counterfactual_beta: float = 1.0,
|
| 153 |
-
crr_concat_aggregation: bool = False,
|
| 154 |
-
crr_policy_rank: int = 0,
|
| 155 |
-
crr_policy_alpha: float = 32.0,
|
| 156 |
-
counterfactual_reliance_loss: bool = False,
|
| 157 |
-
transition_aware_reliance_loss: bool = False,
|
| 158 |
-
corrective_transition_reliance_loss: bool = False,
|
| 159 |
-
counterfactual_step_retention_loss: bool = False,
|
| 160 |
-
functional_state_loss: bool = False,
|
| 161 |
-
per_example_answer_loss: bool = False,
|
| 162 |
-
interface_loss: bool = False,
|
| 163 |
-
read_anchor: bool = False,
|
| 164 |
-
**kwargs,
|
| 165 |
-
):
|
| 166 |
-
super().__init__(**kwargs)
|
| 167 |
-
self.ell_star = ell_star
|
| 168 |
-
self.num_workspace_steps = num_workspace_steps
|
| 169 |
-
self.num_workspace_slots = num_workspace_slots
|
| 170 |
-
self.workspace_width = workspace_width
|
| 171 |
-
self.workspace_num_heads = workspace_num_heads
|
| 172 |
-
self.read_num_blocks = read_num_blocks
|
| 173 |
-
self.transition_num_blocks = transition_num_blocks
|
| 174 |
-
self.workspace_ffn_mult = workspace_ffn_mult
|
| 175 |
-
self.workspace_dropout = workspace_dropout
|
| 176 |
-
self.workspace_rope_mode = workspace_rope_mode
|
| 177 |
-
self.adapter_rank = adapter_rank
|
| 178 |
-
self.adapter_alpha = adapter_alpha
|
| 179 |
-
self.adapter_dropout = adapter_dropout
|
| 180 |
-
# E run: dense full-FT 층은 LoRA 대상에서 제외 (이중 파라미터화 방지)
|
| 181 |
-
self.adapter_exclude_layers = list(adapter_exclude_layers or ())
|
| 182 |
-
self.lambda_traj = lambda_traj
|
| 183 |
-
# v2 anti-collapse switches (defaults False so v1 checkpoints reload).
|
| 184 |
-
# read_shortcut: r_theta adds an orthogonally-projected pooled-V* term to
|
| 185 |
-
# every slot, so Z^(0) is image-dependent from step 0 instead of relying
|
| 186 |
-
# on a random cross-attention finding signal.
|
| 187 |
-
# splice_scale_match: P_Z(Z) rows are RMS-matched to the question rows at
|
| 188 |
-
# the splice. Unmatched, slot states are ~O(1) against layer-20 hiddens
|
| 189 |
-
# in the hundreds -- the decoder could ignore them for free, and did
|
| 190 |
-
# (probe 20143: visual sensitivity 0.94 -> 1.0000 over training).
|
| 191 |
-
self.read_shortcut = read_shortcut
|
| 192 |
-
self.splice_scale_match = splice_scale_match
|
| 193 |
-
# v6 candidate: gated re-reads DURING the recurrence. Every re-read
|
| 194 |
-
# goes through r_theta into Z, so the mediation invariant (decoder sees
|
| 195 |
-
# only [Q*; P_Z(Z)]) is untouched; what changes is that the workspace
|
| 196 |
-
# may consult V* again mid-reasoning, scaled by a learned scalar gate.
|
| 197 |
-
# The gate is free at EVERY step -- no hand-coded schedule. Degeneration
|
| 198 |
-
# into answer-time retrieval is disciplined by the objective itself:
|
| 199 |
-
# L_traj pins each intermediate state to its teacher segment, so a
|
| 200 |
-
# wait-then-fetch trajectory misses its intermediate targets. The gate
|
| 201 |
-
# bias initialises negative, i.e. training starts from the proven
|
| 202 |
-
# no-reread regime and departs only where gradients demand it.
|
| 203 |
-
# Default False: v4/v5 checkpoints reload bit-identically.
|
| 204 |
-
self.read_gating = read_gating
|
| 205 |
-
# Initial gate open-rate epsilon_gamma: b_gamma = logit(eps). Sigmoid
|
| 206 |
-
# never reaches exactly 0 at finite logits, so this is near-identity
|
| 207 |
-
# (not bitwise) initialisation w.r.t. the read-once path.
|
| 208 |
-
self.gate_init_eps = gate_init_eps
|
| 209 |
-
# v7: always-on PURE visual residual rereads. Every step adds
|
| 210 |
-
# Z = U + W_o XAttn(LN(U) -> queries, V* -> keys/values): no self-attn,
|
| 211 |
-
# no FFN, no output norm, no biases in the branch, W_o zero-init.
|
| 212 |
-
# Motivation (battery, ep1 ckpt 20752): read_once hurt badly while
|
| 213 |
-
# null_step (blank memory at one step) was harmless -- the gated
|
| 214 |
-
# reader's value was extra recurrent DEPTH, not vision. This branch
|
| 215 |
-
# removes every path that can change Z without V* content, so any
|
| 216 |
-
# benefit it shows IS visual by construction.
|
| 217 |
-
self.read_residual_pure = read_residual_pure
|
| 218 |
-
# v7.1 (advisor): relative-norm trust region on the visual residual.
|
| 219 |
-
# s_k = min(1, rho * RMS(U) / (RMS(Delta) + eps)); Z = U + s_k * Delta
|
| 220 |
-
# Unlike a fixed alpha, the optimizer cannot undo this by rescaling
|
| 221 |
-
# W_o (s_k adapts); ||Delta_used|| <~ rho * ||U|| structurally, and
|
| 222 |
-
# V* = 0 => Delta = 0 keeps the zero-memory identity. 0.0 disables.
|
| 223 |
-
self.residual_norm_cap = residual_norm_cap
|
| 224 |
-
# v7.1: initialise pure_attn K/V projections from the initial visual
|
| 225 |
-
# reader's v_in (same [d_w, d] shape) and Q from its first read
|
| 226 |
-
# block's in-proj slice, instead of random xavier -- the ep1 finding
|
| 227 |
-
# was that Q/K/V stayed ~random while only W_o trained.
|
| 228 |
-
self.pure_warm_start = pure_warm_start
|
| 229 |
-
# MAIN METHOD (advisor-confirmed): Final-Verified Recurrent Workspace.
|
| 230 |
-
# Initial read -> PURE latent recurrence (no mid-step visual anything)
|
| 231 |
-
# -> exactly ONE pure visual verification at the end:
|
| 232 |
-
# Z_out = U^(T) + W_o XAttn(LN(U^(T)), V*), alpha = 1, W_o = 0 init.
|
| 233 |
-
# Uses the same bias-free verifier module as read_residual_pure; the
|
| 234 |
-
# two flags differ only in WHERE the branch fires (every step vs once).
|
| 235 |
-
self.final_verify = final_verify
|
| 236 |
-
# MAIN (advisor 최종 확정): Mid-Read Recurrent Workspace. 고정 상수
|
| 237 |
-
# k* = floor(T/2) = 4 (스텝 sweep 산물이 아니라 판독 전후 동수 전이의
|
| 238 |
-
# midpoint)에서 pure visual read 1회:
|
| 239 |
-
# Z^(4) = U^(4) + W_o XAttn(LN(U^(4)), V*), alpha=1, W_o=0 init.
|
| 240 |
-
# 논리적 step 4 == python 루프 index 3 (0-based) -- off-by-one은
|
| 241 |
-
# tests/test_mid_read.py가 상태 분기 위치로 고정한다. 0이면 비활성.
|
| 242 |
-
self.mid_read_step = mid_read_step
|
| 243 |
-
# advisor(2026-08-06): E_slot(=workspace_read.slots)을 매 recurrent
|
| 244 |
-
# step 입력과 mid-read query에 일시 제공. state에는 누적하지 않는다.
|
| 245 |
-
self.persistent_slot_id = persistent_slot_id
|
| 246 |
-
# 고정 read 스케일 Z = U + alpha*W_o A (advisor scale-mismatch 검사).
|
| 247 |
-
# 1.0 = 기존과 비트동일. eval의 gate_overrides alpha는 절대값으로 이를 대체.
|
| 248 |
-
self.mid_read_alpha = mid_read_alpha
|
| 249 |
-
# advisor 최종안: E(persistent evidence) / R(recurrent reasoning) 분리.
|
| 250 |
-
# states = [E^(0), R^(1..T)], 결합 Z^(T)=E+R^(T)는 prefill에서.
|
| 251 |
-
self.evidence_reasoning = evidence_reasoning
|
| 252 |
-
# Current replacement candidate: no learned evidence/reasoning slots.
|
| 253 |
-
# E is the complete raw V* sequence and R0 is the text-only Q* sequence.
|
| 254 |
-
# A single shared CrossAttn transition produces R1..RT. Residual mode
|
| 255 |
-
# decodes RT; the replacement candidate decodes Q*+HT. Cross-attention
|
| 256 |
-
# uses the backbone's native GQA shape and
|
| 257 |
-
# is initialized from the first upper layer by default. Checkpoints
|
| 258 |
-
# created before the CrossAttn-only decision did not serialize
|
| 259 |
-
# raw_transition_ffn; None therefore preserves their historical FFN
|
| 260 |
-
# for faithful evaluation, while every new training launch passes False.
|
| 261 |
-
self.raw_evidence_question = bool(raw_evidence_question)
|
| 262 |
-
self.raw_attention_source_layer = int(raw_attention_source_layer)
|
| 263 |
-
self.raw_transition_ffn = (
|
| 264 |
-
True if raw_transition_ffn is None else bool(raw_transition_ffn)
|
| 265 |
-
)
|
| 266 |
-
# Experimental simplification of the same raw-E/Q path. The residual
|
| 267 |
-
# carrier is replaced, not augmented:
|
| 268 |
-
# H0=Q*, Hk=CrossAttn(H{k-1}, E), decoder_latent=Q*+HT.
|
| 269 |
-
# False preserves all residual raw-E/Q checkpoints and active runs.
|
| 270 |
-
self.raw_state_replacement = bool(raw_state_replacement)
|
| 271 |
-
# Question-anchored replacement keeps the fixed text scaffold in every
|
| 272 |
-
# recurrent query without restoring the old recurrent identity path:
|
| 273 |
-
# Z0=Q*, Zk=Q*+CrossAttn(Z{k-1}, E), decoder_latent=ZT.
|
| 274 |
-
# This replaces the one-time Q*+HT decoder interface above.
|
| 275 |
-
self.raw_question_anchor = bool(raw_question_anchor)
|
| 276 |
-
# Q-shaped recurrent candidate selected by the V* interface oracle:
|
| 277 |
-
# R0=Q*, Rk=R{k-1}+CrossAttn(R{k-1}, V*)
|
| 278 |
-
# R_agg=mean(R1..RT)
|
| 279 |
-
# This is parameter-free and replaces both question-anchored state
|
| 280 |
-
# replacement and final-state-only decoding.
|
| 281 |
-
self.raw_mean_aggregation = bool(raw_mean_aggregation)
|
| 282 |
-
# A non-empty sequence maps the first T-1 recurrent steps to frozen
|
| 283 |
-
# visual-token states from the listed language layers. The final step
|
| 284 |
-
# uses Q* as memory to integrate the staged reads without another image
|
| 285 |
-
# access. One CrossAttention module is shared by all T steps.
|
| 286 |
-
self.hierarchical_visual_layers = [
|
| 287 |
-
int(layer) for layer in (hierarchical_visual_layers or ())
|
| 288 |
-
]
|
| 289 |
-
# Counterfactual Residual Recurrence (CRR): the image-conditioned and
|
| 290 |
-
# text-only question states are propagated by the same native layer
|
| 291 |
-
# F=L(ell_star+1). Only their counterfactual residual is recurrently
|
| 292 |
-
# refined; there are no slots, V* memory, custom attention modules, or
|
| 293 |
-
# inference-time auxiliary modules. beta is the fixed relaxed-update
|
| 294 |
-
# coefficient; an optional batch-swap reliance loss is training-only.
|
| 295 |
-
self.counterfactual_residual_recurrence = bool(
|
| 296 |
-
counterfactual_residual_recurrence
|
| 297 |
-
)
|
| 298 |
-
# Causal Visual Residual Recurrence (CVRR), implemented as the
|
| 299 |
-
# vision-native CRR transition. At every recurrent step the same
|
| 300 |
-
# native layer is evaluated with and without only Q->vision attention;
|
| 301 |
-
# their difference is the image-caused update. It deliberately
|
| 302 |
-
# reuses CRR's strict Q-shaped decoder/cache interface and mean
|
| 303 |
-
# aggregation, hence the parent CRR flag remains explicit.
|
| 304 |
-
self.visual_counterfactual_recurrence = bool(
|
| 305 |
-
visual_counterfactual_recurrence
|
| 306 |
-
)
|
| 307 |
-
# Minimal cumulative CVRR variant. Unlike the historical anchored
|
| 308 |
-
# mean path, visual updates are accumulated into the running state and
|
| 309 |
-
# the final state is decoded directly:
|
| 310 |
-
# R_k = R_{k-1} + beta * Delta_k^V, decode R_T.
|
| 311 |
-
# One explicit flag controls both choices so a checkpoint cannot
|
| 312 |
-
# silently combine cumulative dynamics with the old mean interface.
|
| 313 |
-
self.visual_cumulative_recurrence = bool(
|
| 314 |
-
visual_cumulative_recurrence
|
| 315 |
-
)
|
| 316 |
-
# Full-state CVRR keeps the native recurrent computation instead of
|
| 317 |
-
# treating the visual on/off intervention difference as the next
|
| 318 |
-
# hidden state:
|
| 319 |
-
# R_tilde_k = Pi_Q F([E; R_{k-1}])
|
| 320 |
-
# R_k = R_{k-1} + beta * (R_tilde_k - R_{k-1}).
|
| 321 |
-
# The visual-off branch remains available to evaluation code, but is
|
| 322 |
-
# not part of this model's train/inference transition.
|
| 323 |
-
self.visual_full_state_recurrence = bool(
|
| 324 |
-
visual_full_state_recurrence
|
| 325 |
-
)
|
| 326 |
-
# Input-grounded closure control. Later recurrent calls rebuild the
|
| 327 |
-
# native post-L20 scaffold and inject only the carried question-state
|
| 328 |
-
# deviation, rather than feeding post-L21 rows back into L21.
|
| 329 |
-
self.visual_boundary_reentry = bool(visual_boundary_reentry)
|
| 330 |
-
# Source-centered boundary recurrence. The text-only L21 state is a
|
| 331 |
-
# fixed anchor, while the native multimodal residual initializes a
|
| 332 |
-
# nonzero, image-dependent deviation. The recurrent cell evolves
|
| 333 |
-
# only that deviation after subtracting its zero-deviation response.
|
| 334 |
-
self.visual_source_centered_reentry = bool(
|
| 335 |
-
visual_source_centered_reentry
|
| 336 |
-
)
|
| 337 |
-
# Restrict LoRA to the single shared recurrent cell L(ell_star+1).
|
| 338 |
-
# Its execution is disabled for R1, the text-only cache, answer-token
|
| 339 |
-
# decoding, and L22+; only recurrent transitions k>=2 enable it.
|
| 340 |
-
self.recurrence_only_adapter = bool(recurrence_only_adapter)
|
| 341 |
-
# Preserve the pretrained image-caused transition while removing the
|
| 342 |
-
# frozen layer's non-visual recurrent drift:
|
| 343 |
-
# Delta_k = F_{base+LoRA,on}(E,R_{k-1}) - F_{base,off}(E,R_{k-1})
|
| 344 |
-
# = (F_{base,on} - F_{base,off})
|
| 345 |
-
# + (F_{base+LoRA,on} - F_{base,on}).
|
| 346 |
-
# This is a full-state recurrence subtype and introduces no new
|
| 347 |
-
# parameters or inference-time module.
|
| 348 |
-
self.visual_preserving_adapter_correction = bool(
|
| 349 |
-
visual_preserving_adapter_correction
|
| 350 |
-
)
|
| 351 |
-
# Local Visual Latent Chain (LVLC): K homogeneous latent-token
|
| 352 |
-
# positions are evaluated in one native Transformer forward. The
|
| 353 |
-
# first latent sees clean Q plus visual source rows; every later latent
|
| 354 |
-
# sees only the immediately preceding latent plus the same visual
|
| 355 |
-
# rows; answer rows see clean Q plus the final latent. This is a local
|
| 356 |
-
# attention graph, not a recurrent forward or temporal aggregation.
|
| 357 |
-
self.local_visual_latent_chain = bool(local_visual_latent_chain)
|
| 358 |
-
# Perceive--Deliberate Latent Chain (PDLC). K must be even and is
|
| 359 |
-
# interpreted as K/2 native LOOK/THINK pairs. The complete VLM stack
|
| 360 |
-
# runs once over a block-sparse graph: LOOK rows may inspect the
|
| 361 |
-
# multimodal prompt, THINK rows may inspect only clean question rows
|
| 362 |
-
# and their paired LOOK, and answer rows may inspect only clean
|
| 363 |
-
# question rows and THINK states. There is no recurrent cell, visual
|
| 364 |
-
# state update, split-state splice, or temporal aggregator.
|
| 365 |
-
# LVLC reuses the same one-pass latent-sequence execution plumbing but
|
| 366 |
-
# replaces the LOOK/THINK token types and visibility graph entirely.
|
| 367 |
-
self.perceive_deliberate_chain = bool(
|
| 368 |
-
perceive_deliberate_chain or self.local_visual_latent_chain
|
| 369 |
-
)
|
| 370 |
-
# Tight PDLC graph: pair 1 bootstraps from prompt/question, later
|
| 371 |
-
# LOOK rows receive visual placeholders plus the preceding latent
|
| 372 |
-
# prefix, later THINK rows receive only that causal latent prefix, and
|
| 373 |
-
# the answer consumes final THINK. It changes visibility only and
|
| 374 |
-
# allocates no additional trainable module.
|
| 375 |
-
self.pdlc_transition_conditioned = bool(
|
| 376 |
-
pdlc_transition_conditioned
|
| 377 |
-
)
|
| 378 |
-
# Strict inference uses 1: only the bootstrap pair reads the clean
|
| 379 |
-
# question. Larger values are an explicit evaluation intervention.
|
| 380 |
-
# The training-only curriculum changes a runtime override and always
|
| 381 |
-
# returns to 1 for validation/inference.
|
| 382 |
-
self.pdlc_question_visible_through_pair = int(
|
| 383 |
-
pdlc_question_visible_through_pair
|
| 384 |
-
)
|
| 385 |
-
self.pdlc_question_curriculum = bool(pdlc_question_curriculum)
|
| 386 |
-
# Both learned latent token types start from one native text embedding
|
| 387 |
-
# (the launcher supplies the tokenizer's newline id). -1 falls back
|
| 388 |
-
# to token 0 for synthetic/unit-test configs only.
|
| 389 |
-
self.latent_chain_init_token_id = int(latent_chain_init_token_id)
|
| 390 |
-
# Spatial Recurrent Visual Field (SRVF). Unlike all historical
|
| 391 |
-
# ell_star methods, the recurrent state is the complete native visual
|
| 392 |
-
# token grid immediately after the vision merger. The final field is
|
| 393 |
-
# inserted into the ordinary Qwen prefix and the full decoder runs from
|
| 394 |
-
# scratch, making the zero-output initialization exactly base-equivalent.
|
| 395 |
-
self.spatial_visual_recurrence = bool(spatial_visual_recurrence)
|
| 396 |
-
self.spatial_recurrence_steps = int(spatial_recurrence_steps)
|
| 397 |
-
self.spatial_recurrence_inner_width = int(
|
| 398 |
-
spatial_recurrence_inner_width
|
| 399 |
-
)
|
| 400 |
-
self.spatial_recurrence_heads = int(spatial_recurrence_heads)
|
| 401 |
-
# -1 uses the trained/default horizon. Non-negative values are an
|
| 402 |
-
# evaluation-only prefix sweep, with 0 being the exact base path.
|
| 403 |
-
self.spatial_step_override = int(spatial_step_override)
|
| 404 |
-
self.counterfactual_beta = float(counterfactual_beta)
|
| 405 |
-
# Learn one feature-wise mixture of the complete recurrent residual
|
| 406 |
-
# trajectory. The bias-free T*d -> d map starts from
|
| 407 |
-
# [I/T, ..., I/T], so enabling it is exactly the historical CRR mean
|
| 408 |
-
# interface before the first optimizer step. A dedicated flag keeps
|
| 409 |
-
# old CRR checkpoints and the historical slot-ER aggregator distinct.
|
| 410 |
-
self.crr_concat_aggregation = bool(crr_concat_aggregation)
|
| 411 |
-
# Optional latent-only policy adapter. It corrects the deterministic
|
| 412 |
-
# C2..CT means but never touches B, C1, or answer-token decoding. Rank 0
|
| 413 |
-
# is the exact historical CRR path; RL checkpoints serialize rank > 0.
|
| 414 |
-
self.crr_policy_rank = int(crr_policy_rank)
|
| 415 |
-
self.crr_policy_alpha = float(crr_policy_alpha)
|
| 416 |
-
# Training-only CRR intervention. A batch-matched residual from a
|
| 417 |
-
# different-answer example replaces the factual residual before the
|
| 418 |
-
# upper decoder. The model returns the factual-minus-swapped answer
|
| 419 |
-
# score gap; the Trainer applies the margin loss. Evaluation and
|
| 420 |
-
# generation never execute the extra branch.
|
| 421 |
-
self.counterfactual_reliance_loss = bool(
|
| 422 |
-
counterfactual_reliance_loss
|
| 423 |
-
)
|
| 424 |
-
# Replace the aggregate residual swap with one intervention inside the
|
| 425 |
-
# recurrent chain. C_k is swapped and C_{k+1:T} is rolled out again,
|
| 426 |
-
# so the answer gap is attributable to a selected transition rather
|
| 427 |
-
# than to an undifferentiated full-trajectory replacement.
|
| 428 |
-
self.transition_aware_reliance_loss = bool(
|
| 429 |
-
transition_aware_reliance_loss
|
| 430 |
-
)
|
| 431 |
-
# Training-only hard-case corrective ranking. At one selected
|
| 432 |
-
# transition k>=2, the actual decoder scores Z_{k-1}, Z_k, and a
|
| 433 |
-
# donor-swapped Z_k. The Trainer requires the factual transition to
|
| 434 |
-
# improve over both detached comparators. This replaces, rather than
|
| 435 |
-
# stacks with, the historical transition-aware reliance objective.
|
| 436 |
-
self.corrective_transition_reliance_loss = bool(
|
| 437 |
-
corrective_transition_reliance_loss
|
| 438 |
-
)
|
| 439 |
-
# Training-only functional no-regression objective. Gold-answer NLL
|
| 440 |
-
# is measured through the actual upper decoder at every recurrent CRR
|
| 441 |
-
# prefix; inference remains the unchanged T-step mean interface.
|
| 442 |
-
self.counterfactual_step_retention_loss = bool(
|
| 443 |
-
counterfactual_step_retention_loss
|
| 444 |
-
)
|
| 445 |
-
# Training-only self-distillation target. The multimodal lower pass
|
| 446 |
-
# already computed for V* also provides its image-conditioned question
|
| 447 |
-
# rows. They supervise R_agg at the split boundary, but are never
|
| 448 |
-
# exposed to the inference decoder.
|
| 449 |
-
self.functional_state_loss = bool(functional_state_loss)
|
| 450 |
-
# Token CE lets long caption answers dominate one-token visual-choice
|
| 451 |
-
# examples. The per-example variant first averages valid answer tokens
|
| 452 |
-
# within each sample, then averages samples. It changes training/eval
|
| 453 |
-
# loss reduction only and has no inference-time effect.
|
| 454 |
-
self.per_example_answer_loss = bool(per_example_answer_loss)
|
| 455 |
-
# ER v2 (advisor 2026-08-11): explicit evidence read —
|
| 456 |
-
# R^(k) = f_theta(R^(k-1), Q*; E), E는 별도 memory로 cross-attn 읽기.
|
| 457 |
-
# (E+R 합산-감산 제거. evidence_reasoning=True 전제.)
|
| 458 |
-
self.explicit_evidence_read = bool(kwargs.pop("explicit_evidence_read", False))
|
| 459 |
-
# Historical field name retained for checkpoint compatibility. In ER
|
| 460 |
-
# v2.2 this means R^(0) is a batch-broadcast set of learnable slots; it
|
| 461 |
-
# is deliberately NOT initialized from Q*. Q* conditions every shared
|
| 462 |
-
# transition instead.
|
| 463 |
-
self.r_init_question = bool(kwargs.pop("r_init_question", False))
|
| 464 |
-
# ER v2.1 (advisor 2026-08-11): evidence read의 competitive normalization —
|
| 465 |
-
# slot 축 softmax 후 evidence 축 재정규화 (Slot Attention식 경쟁).
|
| 466 |
-
# 파라미터/모듈/loss 무추가; evidence_read의 W_qkv/W_o 재사용.
|
| 467 |
-
self.competitive_evidence_read = bool(
|
| 468 |
-
kwargs.pop("competitive_evidence_read", False))
|
| 469 |
-
# ER v2.2 C: k>=1 traj 감독 부재 시 twin 체인 제거 (states = answer 체인).
|
| 470 |
-
self.er_single_chain = bool(kwargs.pop("er_single_chain", False))
|
| 471 |
-
# ER v2.2 C (§5-7): decoder latent = softmax(temporal_logits)로 가중한
|
| 472 |
-
# R1..RT 결합 (전역 스칼라 T개). er_single_chain 전제. (legacy)
|
| 473 |
-
self.temporal_aggregation = bool(kwargs.pop("temporal_aggregation", False))
|
| 474 |
-
# ER v2.2 C 확정판: R_agg = Linear(Concat(R1..RT), bias=False),
|
| 475 |
-
# 초기 W=[I/T,...,I/T] (R_agg=mean(R1..RT)). er_single_chain 전제.
|
| 476 |
-
self.concat_aggregation = bool(kwargs.pop("concat_aggregation", False))
|
| 477 |
-
# ER v2: 워크스페이스 블록의 rank-r 부분공간 attention/FFN (0 = dense).
|
| 478 |
-
# d_w=d에서 state width 유지와 파라미터 폭증을 분리 (advisor).
|
| 479 |
-
self.workspace_low_rank = int(kwargs.pop("workspace_low_rank", 0))
|
| 480 |
-
# advisor: single-layer text-side interface alignment (bridge-only).
|
| 481 |
-
# teacher = frozen-base mm 경로 q_end@l*+1 (adapter off, sg);
|
| 482 |
-
# student = detach(R^T) splice의 l*+1 한 층 재계산. 추론 시 무존재.
|
| 483 |
-
self.interface_loss = interface_loss
|
| 484 |
-
# Training-only visual anchor switch. False in the current answer-only
|
| 485 |
-
# method; when false the model does not even materialize pooled V* for
|
| 486 |
-
# the Trainer.
|
| 487 |
-
self.read_anchor = bool(read_anchor)
|
| 488 |
-
if evidence_reasoning and persistent_slot_id:
|
| 489 |
-
raise ValueError("evidence_reasoning은 persistent_slot_id와 동시 사용 불가")
|
| 490 |
-
if evidence_reasoning and not mid_read_step:
|
| 491 |
-
raise ValueError("evidence_reasoning은 mid_read_step > 0 필요")
|
| 492 |
-
self.validate()
|
| 493 |
-
|
| 494 |
-
# -- derived views -----------------------------------------------------
|
| 495 |
-
|
| 496 |
-
@property
|
| 497 |
-
def num_decoder_layers(self) -> int:
|
| 498 |
-
return self.text_config.num_hidden_layers
|
| 499 |
-
|
| 500 |
-
@property
|
| 501 |
-
def backbone_width(self) -> int:
|
| 502 |
-
"""``d`` -- backbone hidden width (3584 for the 7B)."""
|
| 503 |
-
return self.text_config.hidden_size
|
| 504 |
-
|
| 505 |
-
@property
|
| 506 |
-
def lower_slice(self) -> slice:
|
| 507 |
-
"""Layers forming ``F_{<=l*}``."""
|
| 508 |
-
self._require_ell_star()
|
| 509 |
-
return slice(0, self.ell_star + 1)
|
| 510 |
-
|
| 511 |
-
@property
|
| 512 |
-
def upper_slice(self) -> slice:
|
| 513 |
-
"""Layers forming ``F_{>l*}``."""
|
| 514 |
-
self._require_ell_star()
|
| 515 |
-
return slice(self.ell_star + 1, self.num_decoder_layers)
|
| 516 |
-
|
| 517 |
-
# -- validation --------------------------------------------------------
|
| 518 |
-
|
| 519 |
-
def _require_ell_star(self) -> None:
|
| 520 |
-
if self.ell_star is None:
|
| 521 |
-
raise ValueError(
|
| 522 |
-
"ell_star is unset. Run scripts/localize_read_point.py (Method 3.1) "
|
| 523 |
-
"and pass the measured layer explicitly; it must not be guessed."
|
| 524 |
-
)
|
| 525 |
-
|
| 526 |
-
def validate(self) -> None:
|
| 527 |
-
"""Reject configurations that silently break the §3.2 contract."""
|
| 528 |
-
# ``PretrainedConfig.from_pretrained`` may apply user overrides with
|
| 529 |
-
# setattr *after* __init__. In that path, setting LVLC=True would not
|
| 530 |
-
# re-run the constructor's promotion of the shared one-pass execution
|
| 531 |
-
# flag. Re-establish the invariant before any model reads the config.
|
| 532 |
-
if getattr(self, "local_visual_latent_chain", False):
|
| 533 |
-
self.perceive_deliberate_chain = True
|
| 534 |
-
if self.ell_star is not None:
|
| 535 |
-
n = self.num_decoder_layers
|
| 536 |
-
# Both branches must be non-empty: an empty lower branch means there
|
| 537 |
-
# is no V* to read, an empty upper branch means the workspace never
|
| 538 |
-
# reaches the decoder.
|
| 539 |
-
if not 0 <= self.ell_star <= n - 2:
|
| 540 |
-
raise ValueError(
|
| 541 |
-
f"ell_star={self.ell_star} out of range for {n} decoder layers; "
|
| 542 |
-
f"expected 0 <= ell_star <= {n - 2} so both branches are non-empty."
|
| 543 |
-
)
|
| 544 |
-
# K=0 is the no-recurrence control (read only), not a misconfiguration.
|
| 545 |
-
if self.num_workspace_steps < 0:
|
| 546 |
-
raise ValueError("num_workspace_steps (K) must be >= 0.")
|
| 547 |
-
if self.num_workspace_slots < 1:
|
| 548 |
-
raise ValueError("num_workspace_slots (S) must be >= 1.")
|
| 549 |
-
if self.workspace_num_heads < 1:
|
| 550 |
-
raise ValueError("workspace_num_heads must be >= 1.")
|
| 551 |
-
if self.workspace_width % self.workspace_num_heads != 0:
|
| 552 |
-
raise ValueError(
|
| 553 |
-
f"workspace_width={self.workspace_width} must be divisible by "
|
| 554 |
-
f"workspace_num_heads={self.workspace_num_heads}."
|
| 555 |
-
)
|
| 556 |
-
if self.workspace_width > self.backbone_width:
|
| 557 |
-
raise ValueError(
|
| 558 |
-
f"workspace_width (d_w={self.workspace_width}) cannot exceed "
|
| 559 |
-
f"backbone width (d={self.backbone_width}). Native-width d_w=d is "
|
| 560 |
-
"the current method and uses an Identity decoder interface."
|
| 561 |
-
)
|
| 562 |
-
if self.workspace_rope_mode not in WORKSPACE_ROPE_MODES:
|
| 563 |
-
raise ValueError(
|
| 564 |
-
f"workspace_rope_mode={self.workspace_rope_mode!r} not in "
|
| 565 |
-
f"{WORKSPACE_ROPE_MODES}."
|
| 566 |
-
)
|
| 567 |
-
if self.adapter_rank < 0:
|
| 568 |
-
raise ValueError("adapter_rank must be >= 0 (0 disables the adapter).")
|
| 569 |
-
if not math.isfinite(self.adapter_dropout) or not 0.0 <= self.adapter_dropout < 1.0:
|
| 570 |
-
raise ValueError("adapter_dropout must be finite and in [0, 1).")
|
| 571 |
-
if getattr(self, "crr_policy_rank", 0) < 0:
|
| 572 |
-
raise ValueError("crr_policy_rank must be >= 0.")
|
| 573 |
-
if not math.isfinite(getattr(self, "crr_policy_alpha", 0.0)) or getattr(
|
| 574 |
-
self, "crr_policy_alpha", 0.0
|
| 575 |
-
) <= 0.0:
|
| 576 |
-
raise ValueError("crr_policy_alpha must be finite and > 0.")
|
| 577 |
-
if self.read_num_blocks < 1 or self.transition_num_blocks < 1:
|
| 578 |
-
raise ValueError("read_num_blocks and transition_num_blocks must be >= 1.")
|
| 579 |
-
if self.workspace_ffn_mult < 1:
|
| 580 |
-
raise ValueError("workspace_ffn_mult must be >= 1.")
|
| 581 |
-
mid = int(getattr(self, "mid_read_step", 0) or 0)
|
| 582 |
-
if mid < 0 or mid > self.num_workspace_steps:
|
| 583 |
-
raise ValueError(
|
| 584 |
-
f"mid_read_step={mid} must be in [0, num_workspace_steps="
|
| 585 |
-
f"{self.num_workspace_steps}]."
|
| 586 |
-
)
|
| 587 |
-
low_rank = int(getattr(self, "workspace_low_rank", 0) or 0)
|
| 588 |
-
if low_rank < 0:
|
| 589 |
-
raise ValueError("workspace_low_rank must be >= 0.")
|
| 590 |
-
if low_rank and low_rank % self.workspace_num_heads != 0:
|
| 591 |
-
raise ValueError(
|
| 592 |
-
f"workspace_low_rank={low_rank} must be divisible by "
|
| 593 |
-
f"workspace_num_heads={self.workspace_num_heads}."
|
| 594 |
-
)
|
| 595 |
-
if getattr(self, "competitive_evidence_read", False) and not getattr(
|
| 596 |
-
self, "explicit_evidence_read", False
|
| 597 |
-
):
|
| 598 |
-
raise ValueError(
|
| 599 |
-
"competitive_evidence_read requires explicit_evidence_read=True; "
|
| 600 |
-
"otherwise the flag has no execution path."
|
| 601 |
-
)
|
| 602 |
-
if getattr(self, "explicit_evidence_read", False) and not getattr(
|
| 603 |
-
self, "evidence_reasoning", False
|
| 604 |
-
):
|
| 605 |
-
raise ValueError(
|
| 606 |
-
"explicit_evidence_read requires evidence_reasoning=True; otherwise "
|
| 607 |
-
"the evidence-read module is allocated but never called."
|
| 608 |
-
)
|
| 609 |
-
if getattr(self, "r_init_question", False) and not getattr(
|
| 610 |
-
self, "evidence_reasoning", False
|
| 611 |
-
):
|
| 612 |
-
raise ValueError(
|
| 613 |
-
"r_init_question (legacy name for learnable R0 slots) requires "
|
| 614 |
-
"evidence_reasoning=True; otherwise reasoning_slots are unused."
|
| 615 |
-
)
|
| 616 |
-
if (getattr(self, "concat_aggregation", False)
|
| 617 |
-
or getattr(self, "temporal_aggregation", False)):
|
| 618 |
-
if self.num_workspace_steps < 1:
|
| 619 |
-
raise ValueError("reasoning aggregation requires num_workspace_steps >= 1.")
|
| 620 |
-
if not getattr(self, "evidence_reasoning", False):
|
| 621 |
-
raise ValueError(
|
| 622 |
-
"reasoning aggregation requires evidence_reasoning=True; "
|
| 623 |
-
"otherwise the aggregation module is unused."
|
| 624 |
-
)
|
| 625 |
-
if getattr(self, "concat_aggregation", False) and not getattr(
|
| 626 |
-
self, "er_single_chain", False
|
| 627 |
-
):
|
| 628 |
-
raise ValueError("concat_aggregation requires er_single_chain=True.")
|
| 629 |
-
if getattr(self, "raw_evidence_question", False):
|
| 630 |
-
if self.workspace_width != self.backbone_width:
|
| 631 |
-
raise ValueError(
|
| 632 |
-
"raw_evidence_question requires native workspace_width == "
|
| 633 |
-
"backbone_width: E=V* and R0=Q* are used without projections."
|
| 634 |
-
)
|
| 635 |
-
if self.num_workspace_steps < 1:
|
| 636 |
-
raise ValueError(
|
| 637 |
-
"raw_evidence_question requires num_workspace_steps >= 1."
|
| 638 |
-
)
|
| 639 |
-
source_layer = (
|
| 640 |
-
self.ell_star + 1
|
| 641 |
-
if self.raw_attention_source_layer < 0
|
| 642 |
-
else self.raw_attention_source_layer
|
| 643 |
-
)
|
| 644 |
-
if not self.ell_star < source_layer < self.text_config.num_hidden_layers:
|
| 645 |
-
raise ValueError(
|
| 646 |
-
"raw_attention_source_layer must be an upper-backbone layer, "
|
| 647 |
-
f"got ell_star={self.ell_star}, source={source_layer}."
|
| 648 |
-
)
|
| 649 |
-
incompatible = {
|
| 650 |
-
"evidence_reasoning": bool(getattr(self, "evidence_reasoning", False)),
|
| 651 |
-
"explicit_evidence_read": bool(getattr(self, "explicit_evidence_read", False)),
|
| 652 |
-
"competitive_evidence_read": bool(getattr(self, "competitive_evidence_read", False)),
|
| 653 |
-
"r_init_question": bool(getattr(self, "r_init_question", False)),
|
| 654 |
-
"er_single_chain": bool(getattr(self, "er_single_chain", False)),
|
| 655 |
-
"temporal_aggregation": bool(getattr(self, "temporal_aggregation", False)),
|
| 656 |
-
"concat_aggregation": bool(getattr(self, "concat_aggregation", False)),
|
| 657 |
-
"read_shortcut": bool(getattr(self, "read_shortcut", False)),
|
| 658 |
-
"splice_scale_match": bool(getattr(self, "splice_scale_match", False)),
|
| 659 |
-
"read_gating": bool(getattr(self, "read_gating", False)),
|
| 660 |
-
"read_residual_pure": bool(getattr(self, "read_residual_pure", False)),
|
| 661 |
-
"final_verify": bool(getattr(self, "final_verify", False)),
|
| 662 |
-
"mid_read_step": bool(getattr(self, "mid_read_step", 0)),
|
| 663 |
-
"persistent_slot_id": bool(getattr(self, "persistent_slot_id", False)),
|
| 664 |
-
"interface_loss": bool(getattr(self, "interface_loss", False)),
|
| 665 |
-
"read_anchor": bool(getattr(self, "read_anchor", False)),
|
| 666 |
-
}
|
| 667 |
-
active = [name for name, enabled in incompatible.items() if enabled]
|
| 668 |
-
if active:
|
| 669 |
-
raise ValueError(
|
| 670 |
-
"raw_evidence_question replaces the slot workspace and is "
|
| 671 |
-
f"incompatible with: {', '.join(active)}"
|
| 672 |
-
)
|
| 673 |
-
if self.raw_state_replacement and self.raw_transition_ffn:
|
| 674 |
-
raise ValueError(
|
| 675 |
-
"raw_state_replacement is CrossAttn-only and is incompatible "
|
| 676 |
-
"with raw_transition_ffn=True."
|
| 677 |
-
)
|
| 678 |
-
if self.raw_question_anchor and not self.raw_state_replacement:
|
| 679 |
-
raise ValueError(
|
| 680 |
-
"raw_question_anchor requires raw_state_replacement=True."
|
| 681 |
-
)
|
| 682 |
-
if self.raw_mean_aggregation and (
|
| 683 |
-
self.raw_state_replacement or self.raw_question_anchor
|
| 684 |
-
):
|
| 685 |
-
raise ValueError(
|
| 686 |
-
"raw_mean_aggregation replaces raw_state_replacement and "
|
| 687 |
-
"raw_question_anchor; use the residual recurrence."
|
| 688 |
-
)
|
| 689 |
-
hierarchy = list(
|
| 690 |
-
getattr(self, "hierarchical_visual_layers", ()) or ()
|
| 691 |
-
)
|
| 692 |
-
if hierarchy:
|
| 693 |
-
if not self.raw_mean_aggregation:
|
| 694 |
-
raise ValueError(
|
| 695 |
-
"hierarchical_visual_layers requires "
|
| 696 |
-
"raw_mean_aggregation=True."
|
| 697 |
-
)
|
| 698 |
-
if self.raw_transition_ffn:
|
| 699 |
-
raise ValueError(
|
| 700 |
-
"hierarchical visual recurrence is CrossAttn-only and "
|
| 701 |
-
"requires raw_transition_ffn=False."
|
| 702 |
-
)
|
| 703 |
-
expected_visual_reads = self.num_workspace_steps - 1
|
| 704 |
-
if len(hierarchy) != expected_visual_reads:
|
| 705 |
-
raise ValueError(
|
| 706 |
-
"hierarchical_visual_layers must contain exactly one "
|
| 707 |
-
"layer per visual-read step, followed by one Q* "
|
| 708 |
-
"integration step: "
|
| 709 |
-
f"got {len(hierarchy)} layers for "
|
| 710 |
-
f"T={self.num_workspace_steps} "
|
| 711 |
-
f"(expected {expected_visual_reads})."
|
| 712 |
-
)
|
| 713 |
-
if hierarchy != sorted(set(hierarchy)):
|
| 714 |
-
raise ValueError(
|
| 715 |
-
"hierarchical_visual_layers must be strictly increasing."
|
| 716 |
-
)
|
| 717 |
-
invalid = [
|
| 718 |
-
layer
|
| 719 |
-
for layer in hierarchy
|
| 720 |
-
if not 0 <= layer <= int(self.ell_star)
|
| 721 |
-
]
|
| 722 |
-
if invalid:
|
| 723 |
-
raise ValueError(
|
| 724 |
-
"hierarchical_visual_layers must lie in the frozen lower "
|
| 725 |
-
f"branch [0,{self.ell_star}], got {invalid}."
|
| 726 |
-
)
|
| 727 |
-
if self.functional_state_loss:
|
| 728 |
-
raise ValueError(
|
| 729 |
-
"functional_state_loss is not defined for hierarchical "
|
| 730 |
-
"visual recurrence; use final-answer CE only."
|
| 731 |
-
)
|
| 732 |
-
if self.functional_state_loss and not self.raw_mean_aggregation:
|
| 733 |
-
raise ValueError(
|
| 734 |
-
"functional_state_loss requires raw_mean_aggregation=True "
|
| 735 |
-
"so its target is the exact decoder-facing recurrence."
|
| 736 |
-
)
|
| 737 |
-
elif getattr(self, "raw_state_replacement", False):
|
| 738 |
-
raise ValueError(
|
| 739 |
-
"raw_state_replacement requires raw_evidence_question=True."
|
| 740 |
-
)
|
| 741 |
-
elif getattr(self, "raw_question_anchor", False):
|
| 742 |
-
raise ValueError(
|
| 743 |
-
"raw_question_anchor requires raw_evidence_question=True."
|
| 744 |
-
)
|
| 745 |
-
elif getattr(self, "raw_mean_aggregation", False):
|
| 746 |
-
raise ValueError(
|
| 747 |
-
"raw_mean_aggregation requires raw_evidence_question=True."
|
| 748 |
-
)
|
| 749 |
-
elif getattr(self, "hierarchical_visual_layers", ()):
|
| 750 |
-
raise ValueError(
|
| 751 |
-
"hierarchical_visual_layers requires "
|
| 752 |
-
"raw_evidence_question=True."
|
| 753 |
-
)
|
| 754 |
-
elif getattr(self, "functional_state_loss", False):
|
| 755 |
-
raise ValueError(
|
| 756 |
-
"functional_state_loss requires raw_evidence_question=True."
|
| 757 |
-
)
|
| 758 |
-
if (
|
| 759 |
-
getattr(self, "counterfactual_reliance_loss", False)
|
| 760 |
-
and not getattr(self, "counterfactual_residual_recurrence", False)
|
| 761 |
-
):
|
| 762 |
-
raise ValueError(
|
| 763 |
-
"counterfactual_reliance_loss requires "
|
| 764 |
-
"counterfactual_residual_recurrence=True."
|
| 765 |
-
)
|
| 766 |
-
if (
|
| 767 |
-
getattr(self, "transition_aware_reliance_loss", False)
|
| 768 |
-
and not getattr(self, "counterfactual_reliance_loss", False)
|
| 769 |
-
):
|
| 770 |
-
raise ValueError(
|
| 771 |
-
"transition_aware_reliance_loss requires "
|
| 772 |
-
"counterfactual_reliance_loss=True."
|
| 773 |
-
)
|
| 774 |
-
if (
|
| 775 |
-
getattr(self, "transition_aware_reliance_loss", False)
|
| 776 |
-
and self.num_workspace_steps < 2
|
| 777 |
-
):
|
| 778 |
-
raise ValueError(
|
| 779 |
-
"transition_aware_reliance_loss requires at least two steps."
|
| 780 |
-
)
|
| 781 |
-
if (
|
| 782 |
-
getattr(self, "corrective_transition_reliance_loss", False)
|
| 783 |
-
and not getattr(self, "counterfactual_reliance_loss", False)
|
| 784 |
-
):
|
| 785 |
-
raise ValueError(
|
| 786 |
-
"corrective_transition_reliance_loss requires "
|
| 787 |
-
"counterfactual_reliance_loss=True."
|
| 788 |
-
)
|
| 789 |
-
if (
|
| 790 |
-
getattr(self, "corrective_transition_reliance_loss", False)
|
| 791 |
-
and self.num_workspace_steps < 2
|
| 792 |
-
):
|
| 793 |
-
raise ValueError(
|
| 794 |
-
"corrective_transition_reliance_loss requires at least two steps."
|
| 795 |
-
)
|
| 796 |
-
if (
|
| 797 |
-
getattr(self, "corrective_transition_reliance_loss", False)
|
| 798 |
-
and getattr(self, "transition_aware_reliance_loss", False)
|
| 799 |
-
):
|
| 800 |
-
raise ValueError(
|
| 801 |
-
"corrective_transition_reliance_loss replaces, and cannot be "
|
| 802 |
-
"combined with, transition_aware_reliance_loss."
|
| 803 |
-
)
|
| 804 |
-
if (
|
| 805 |
-
getattr(self, "corrective_transition_reliance_loss", False)
|
| 806 |
-
and getattr(self, "counterfactual_step_retention_loss", False)
|
| 807 |
-
):
|
| 808 |
-
raise ValueError(
|
| 809 |
-
"corrective_transition_reliance_loss replaces, and cannot be "
|
| 810 |
-
"combined with, counterfactual_step_retention_loss."
|
| 811 |
-
)
|
| 812 |
-
if (
|
| 813 |
-
getattr(self, "counterfactual_step_retention_loss", False)
|
| 814 |
-
and not getattr(self, "counterfactual_residual_recurrence", False)
|
| 815 |
-
):
|
| 816 |
-
raise ValueError(
|
| 817 |
-
"counterfactual_step_retention_loss requires "
|
| 818 |
-
"counterfactual_residual_recurrence=True."
|
| 819 |
-
)
|
| 820 |
-
if (
|
| 821 |
-
getattr(self, "counterfactual_step_retention_loss", False)
|
| 822 |
-
and self.num_workspace_steps < 2
|
| 823 |
-
):
|
| 824 |
-
raise ValueError(
|
| 825 |
-
"counterfactual_step_retention_loss requires at least two steps."
|
| 826 |
-
)
|
| 827 |
-
if getattr(self, "crr_concat_aggregation", False) and not getattr(
|
| 828 |
-
self, "counterfactual_residual_recurrence", False
|
| 829 |
-
):
|
| 830 |
-
raise ValueError(
|
| 831 |
-
"crr_concat_aggregation requires "
|
| 832 |
-
"counterfactual_residual_recurrence=True."
|
| 833 |
-
)
|
| 834 |
-
if getattr(self, "counterfactual_residual_recurrence", False):
|
| 835 |
-
if getattr(self, "raw_evidence_question", False):
|
| 836 |
-
raise ValueError(
|
| 837 |
-
"counterfactual_residual_recurrence replaces "
|
| 838 |
-
"raw_evidence_question; enable exactly one q-state method."
|
| 839 |
-
)
|
| 840 |
-
if self.num_workspace_steps < 1:
|
| 841 |
-
raise ValueError(
|
| 842 |
-
"counterfactual_residual_recurrence requires "
|
| 843 |
-
"num_workspace_steps >= 1."
|
| 844 |
-
)
|
| 845 |
-
if self.crr_policy_rank > 0 and self.num_workspace_steps < 2:
|
| 846 |
-
raise ValueError(
|
| 847 |
-
"crr_policy_rank > 0 requires num_workspace_steps >= 2."
|
| 848 |
-
)
|
| 849 |
-
if not 0.0 <= self.counterfactual_beta <= 1.0:
|
| 850 |
-
raise ValueError(
|
| 851 |
-
"counterfactual_beta must lie in [0, 1], got "
|
| 852 |
-
f"{self.counterfactual_beta}."
|
| 853 |
-
)
|
| 854 |
-
incompatible = {
|
| 855 |
-
"evidence_reasoning": bool(getattr(self, "evidence_reasoning", False)),
|
| 856 |
-
"explicit_evidence_read": bool(getattr(self, "explicit_evidence_read", False)),
|
| 857 |
-
"competitive_evidence_read": bool(getattr(self, "competitive_evidence_read", False)),
|
| 858 |
-
"r_init_question": bool(getattr(self, "r_init_question", False)),
|
| 859 |
-
"er_single_chain": bool(getattr(self, "er_single_chain", False)),
|
| 860 |
-
"temporal_aggregation": bool(getattr(self, "temporal_aggregation", False)),
|
| 861 |
-
"concat_aggregation": bool(getattr(self, "concat_aggregation", False)),
|
| 862 |
-
"read_shortcut": bool(getattr(self, "read_shortcut", False)),
|
| 863 |
-
"splice_scale_match": bool(getattr(self, "splice_scale_match", False)),
|
| 864 |
-
"read_gating": bool(getattr(self, "read_gating", False)),
|
| 865 |
-
"read_residual_pure": bool(getattr(self, "read_residual_pure", False)),
|
| 866 |
-
"final_verify": bool(getattr(self, "final_verify", False)),
|
| 867 |
-
"mid_read_step": bool(getattr(self, "mid_read_step", 0)),
|
| 868 |
-
"persistent_slot_id": bool(getattr(self, "persistent_slot_id", False)),
|
| 869 |
-
"interface_loss": bool(getattr(self, "interface_loss", False)),
|
| 870 |
-
"read_anchor": bool(getattr(self, "read_anchor", False)),
|
| 871 |
-
"functional_state_loss": bool(getattr(self, "functional_state_loss", False)),
|
| 872 |
-
}
|
| 873 |
-
active = [name for name, enabled in incompatible.items() if enabled]
|
| 874 |
-
if active:
|
| 875 |
-
raise ValueError(
|
| 876 |
-
"counterfactual_residual_recurrence replaces the slot/raw "
|
| 877 |
-
f"workspace and is incompatible with: {', '.join(active)}"
|
| 878 |
-
)
|
| 879 |
-
elif getattr(self, "crr_policy_rank", 0) > 0:
|
| 880 |
-
raise ValueError(
|
| 881 |
-
"crr_policy_rank > 0 requires "
|
| 882 |
-
"counterfactual_residual_recurrence=True."
|
| 883 |
-
)
|
| 884 |
-
if getattr(self, "visual_counterfactual_recurrence", False):
|
| 885 |
-
if not getattr(
|
| 886 |
-
self, "counterfactual_residual_recurrence", False
|
| 887 |
-
):
|
| 888 |
-
raise ValueError(
|
| 889 |
-
"visual_counterfactual_recurrence requires "
|
| 890 |
-
"counterfactual_residual_recurrence=True."
|
| 891 |
-
)
|
| 892 |
-
# CVRR is the intentionally minimal CE-only candidate. The old
|
| 893 |
-
# CRR tail-rollout losses/policy implement a different transition
|
| 894 |
-
# equation and must not be silently applied to this trajectory.
|
| 895 |
-
incompatible = {
|
| 896 |
-
"counterfactual_reliance_loss": bool(
|
| 897 |
-
getattr(self, "counterfactual_reliance_loss", False)
|
| 898 |
-
),
|
| 899 |
-
"transition_aware_reliance_loss": bool(
|
| 900 |
-
getattr(self, "transition_aware_reliance_loss", False)
|
| 901 |
-
),
|
| 902 |
-
"corrective_transition_reliance_loss": bool(
|
| 903 |
-
getattr(self, "corrective_transition_reliance_loss", False)
|
| 904 |
-
),
|
| 905 |
-
"counterfactual_step_retention_loss": bool(
|
| 906 |
-
getattr(self, "counterfactual_step_retention_loss", False)
|
| 907 |
-
),
|
| 908 |
-
"crr_concat_aggregation": bool(
|
| 909 |
-
getattr(self, "crr_concat_aggregation", False)
|
| 910 |
-
),
|
| 911 |
-
"crr_policy_rank": bool(getattr(self, "crr_policy_rank", 0)),
|
| 912 |
-
}
|
| 913 |
-
active = [name for name, enabled in incompatible.items() if enabled]
|
| 914 |
-
if active:
|
| 915 |
-
raise ValueError(
|
| 916 |
-
"visual_counterfactual_recurrence uses final-answer CE only "
|
| 917 |
-
"and a parameter-free decoder interface; incompatible with: "
|
| 918 |
-
+ ", ".join(active)
|
| 919 |
-
)
|
| 920 |
-
elif getattr(self, "visual_cumulative_recurrence", False):
|
| 921 |
-
raise ValueError(
|
| 922 |
-
"visual_cumulative_recurrence requires "
|
| 923 |
-
"visual_counterfactual_recurrence=True."
|
| 924 |
-
)
|
| 925 |
-
if getattr(self, "visual_full_state_recurrence", False):
|
| 926 |
-
if not getattr(self, "visual_counterfactual_recurrence", False):
|
| 927 |
-
raise ValueError(
|
| 928 |
-
"visual_full_state_recurrence requires "
|
| 929 |
-
"visual_counterfactual_recurrence=True."
|
| 930 |
-
)
|
| 931 |
-
if not getattr(self, "visual_cumulative_recurrence", False):
|
| 932 |
-
raise ValueError(
|
| 933 |
-
"visual_full_state_recurrence requires "
|
| 934 |
-
"visual_cumulative_recurrence=True."
|
| 935 |
-
)
|
| 936 |
-
if getattr(self, "visual_boundary_reentry", False) and not getattr(
|
| 937 |
-
self, "visual_full_state_recurrence", False
|
| 938 |
-
):
|
| 939 |
-
raise ValueError(
|
| 940 |
-
"visual_boundary_reentry requires "
|
| 941 |
-
"visual_full_state_recurrence=True."
|
| 942 |
-
)
|
| 943 |
-
if getattr(self, "visual_source_centered_reentry", False):
|
| 944 |
-
if not getattr(self, "visual_boundary_reentry", False):
|
| 945 |
-
raise ValueError(
|
| 946 |
-
"visual_source_centered_reentry requires "
|
| 947 |
-
"visual_boundary_reentry=True."
|
| 948 |
-
)
|
| 949 |
-
if getattr(self, "visual_preserving_adapter_correction", False):
|
| 950 |
-
raise ValueError(
|
| 951 |
-
"visual_source_centered_reentry cannot be combined with "
|
| 952 |
-
"visual_preserving_adapter_correction."
|
| 953 |
-
)
|
| 954 |
-
if getattr(self, "recurrence_only_adapter", False):
|
| 955 |
-
if not getattr(self, "visual_full_state_recurrence", False):
|
| 956 |
-
raise ValueError(
|
| 957 |
-
"recurrence_only_adapter requires "
|
| 958 |
-
"visual_full_state_recurrence=True."
|
| 959 |
-
)
|
| 960 |
-
if self.adapter_rank <= 0:
|
| 961 |
-
raise ValueError(
|
| 962 |
-
"recurrence_only_adapter requires adapter_rank > 0."
|
| 963 |
-
)
|
| 964 |
-
if self.ell_star is None:
|
| 965 |
-
raise ValueError(
|
| 966 |
-
"recurrence_only_adapter requires an explicit ell_star."
|
| 967 |
-
)
|
| 968 |
-
recurrent_layer = int(self.ell_star) + 1
|
| 969 |
-
if recurrent_layer in set(self.adapter_exclude_layers):
|
| 970 |
-
raise ValueError(
|
| 971 |
-
"recurrence_only_adapter cannot exclude its recurrent "
|
| 972 |
-
f"layer {recurrent_layer} from LoRA."
|
| 973 |
-
)
|
| 974 |
-
if getattr(self, "visual_preserving_adapter_correction", False):
|
| 975 |
-
if not getattr(self, "visual_full_state_recurrence", False):
|
| 976 |
-
raise ValueError(
|
| 977 |
-
"visual_preserving_adapter_correction requires "
|
| 978 |
-
"visual_full_state_recurrence=True."
|
| 979 |
-
)
|
| 980 |
-
if not getattr(self, "recurrence_only_adapter", False):
|
| 981 |
-
raise ValueError(
|
| 982 |
-
"visual_preserving_adapter_correction requires "
|
| 983 |
-
"recurrence_only_adapter=True."
|
| 984 |
-
)
|
| 985 |
-
if getattr(self, "perceive_deliberate_chain", False):
|
| 986 |
-
if self.num_workspace_steps < 2 or self.num_workspace_steps % 2:
|
| 987 |
-
raise ValueError(
|
| 988 |
-
"one-pass latent chains require an even "
|
| 989 |
-
"num_workspace_steps >= 2."
|
| 990 |
-
)
|
| 991 |
-
vocab_size = int(self.text_config.vocab_size)
|
| 992 |
-
if not -1 <= self.latent_chain_init_token_id < vocab_size:
|
| 993 |
-
raise ValueError(
|
| 994 |
-
"latent_chain_init_token_id must be -1 or a valid text "
|
| 995 |
-
f"token id below {vocab_size}."
|
| 996 |
-
)
|
| 997 |
-
num_pairs = self.num_workspace_steps // 2
|
| 998 |
-
if self.local_visual_latent_chain and (
|
| 999 |
-
self.pdlc_transition_conditioned
|
| 1000 |
-
or self.pdlc_question_curriculum
|
| 1001 |
-
or self.pdlc_question_visible_through_pair != 1
|
| 1002 |
-
):
|
| 1003 |
-
raise ValueError(
|
| 1004 |
-
"local_visual_latent_chain replaces PDLC transition and "
|
| 1005 |
-
"question-visibility controls"
|
| 1006 |
-
)
|
| 1007 |
-
if not 1 <= self.pdlc_question_visible_through_pair <= num_pairs:
|
| 1008 |
-
raise ValueError(
|
| 1009 |
-
"pdlc_question_visible_through_pair must lie in "
|
| 1010 |
-
f"[1,{num_pairs}]"
|
| 1011 |
-
)
|
| 1012 |
-
if self.pdlc_question_curriculum and not (
|
| 1013 |
-
getattr(self, "pdlc_transition_conditioned", False)
|
| 1014 |
-
):
|
| 1015 |
-
raise ValueError(
|
| 1016 |
-
"pdlc_question_curriculum requires "
|
| 1017 |
-
"pdlc_transition_conditioned=True"
|
| 1018 |
-
)
|
| 1019 |
-
incompatible = {
|
| 1020 |
-
"raw_evidence_question": bool(
|
| 1021 |
-
getattr(self, "raw_evidence_question", False)
|
| 1022 |
-
),
|
| 1023 |
-
"counterfactual_residual_recurrence": bool(
|
| 1024 |
-
getattr(self, "counterfactual_residual_recurrence", False)
|
| 1025 |
-
),
|
| 1026 |
-
"spatial_visual_recurrence": bool(
|
| 1027 |
-
getattr(self, "spatial_visual_recurrence", False)
|
| 1028 |
-
),
|
| 1029 |
-
"evidence_reasoning": bool(
|
| 1030 |
-
getattr(self, "evidence_reasoning", False)
|
| 1031 |
-
),
|
| 1032 |
-
"read_gating": bool(getattr(self, "read_gating", False)),
|
| 1033 |
-
"read_residual_pure": bool(
|
| 1034 |
-
getattr(self, "read_residual_pure", False)
|
| 1035 |
-
),
|
| 1036 |
-
"final_verify": bool(getattr(self, "final_verify", False)),
|
| 1037 |
-
"mid_read_step": bool(getattr(self, "mid_read_step", 0)),
|
| 1038 |
-
"interface_loss": bool(getattr(self, "interface_loss", False)),
|
| 1039 |
-
"read_anchor": bool(getattr(self, "read_anchor", False)),
|
| 1040 |
-
"counterfactual_reliance_loss": bool(
|
| 1041 |
-
getattr(self, "counterfactual_reliance_loss", False)
|
| 1042 |
-
),
|
| 1043 |
-
"counterfactual_step_retention_loss": bool(
|
| 1044 |
-
getattr(self, "counterfactual_step_retention_loss", False)
|
| 1045 |
-
),
|
| 1046 |
-
"functional_state_loss": bool(
|
| 1047 |
-
getattr(self, "functional_state_loss", False)
|
| 1048 |
-
),
|
| 1049 |
-
}
|
| 1050 |
-
active = [name for name, enabled in incompatible.items() if enabled]
|
| 1051 |
-
if active:
|
| 1052 |
-
raise ValueError(
|
| 1053 |
-
"perceive_deliberate_chain replaces every recurrent/"
|
| 1054 |
-
"workspace path; incompatible with: " + ", ".join(active)
|
| 1055 |
-
)
|
| 1056 |
-
elif getattr(self, "pdlc_transition_conditioned", False):
|
| 1057 |
-
raise ValueError(
|
| 1058 |
-
"pdlc_transition_conditioned requires "
|
| 1059 |
-
"perceive_deliberate_chain=True."
|
| 1060 |
-
)
|
| 1061 |
-
elif getattr(self, "pdlc_question_curriculum", False) or (
|
| 1062 |
-
getattr(self, "pdlc_question_visible_through_pair", 1) != 1
|
| 1063 |
-
):
|
| 1064 |
-
raise ValueError(
|
| 1065 |
-
"PDLC question visibility controls require "
|
| 1066 |
-
"perceive_deliberate_chain=True."
|
| 1067 |
-
)
|
| 1068 |
-
if getattr(self, "spatial_visual_recurrence", False):
|
| 1069 |
-
if not 0.0 <= self.counterfactual_beta <= 1.0:
|
| 1070 |
-
raise ValueError(
|
| 1071 |
-
"counterfactual_beta must lie in [0, 1], got "
|
| 1072 |
-
f"{self.counterfactual_beta}."
|
| 1073 |
-
)
|
| 1074 |
-
if self.spatial_recurrence_steps < 1:
|
| 1075 |
-
raise ValueError(
|
| 1076 |
-
"spatial_recurrence_steps must be >= 1 for training"
|
| 1077 |
-
)
|
| 1078 |
-
if self.spatial_recurrence_inner_width < 1:
|
| 1079 |
-
raise ValueError(
|
| 1080 |
-
"spatial_recurrence_inner_width must be positive"
|
| 1081 |
-
)
|
| 1082 |
-
if self.spatial_recurrence_heads < 1 or (
|
| 1083 |
-
self.spatial_recurrence_inner_width
|
| 1084 |
-
% self.spatial_recurrence_heads
|
| 1085 |
-
):
|
| 1086 |
-
raise ValueError(
|
| 1087 |
-
"spatial_recurrence_inner_width must be divisible by "
|
| 1088 |
-
"spatial_recurrence_heads"
|
| 1089 |
-
)
|
| 1090 |
-
if self.spatial_step_override < -1:
|
| 1091 |
-
raise ValueError("spatial_step_override must be >= -1")
|
| 1092 |
-
if self.adapter_rank != 0:
|
| 1093 |
-
raise ValueError(
|
| 1094 |
-
"spatial_visual_recurrence replaces decoder LoRA; set "
|
| 1095 |
-
"adapter_rank=0 so the T=0 path remains the frozen base model"
|
| 1096 |
-
)
|
| 1097 |
-
incompatible = {
|
| 1098 |
-
"raw_evidence_question": bool(
|
| 1099 |
-
getattr(self, "raw_evidence_question", False)
|
| 1100 |
-
),
|
| 1101 |
-
"counterfactual_residual_recurrence": bool(
|
| 1102 |
-
getattr(self, "counterfactual_residual_recurrence", False)
|
| 1103 |
-
),
|
| 1104 |
-
"evidence_reasoning": bool(
|
| 1105 |
-
getattr(self, "evidence_reasoning", False)
|
| 1106 |
-
),
|
| 1107 |
-
"read_gating": bool(getattr(self, "read_gating", False)),
|
| 1108 |
-
"read_residual_pure": bool(
|
| 1109 |
-
getattr(self, "read_residual_pure", False)
|
| 1110 |
-
),
|
| 1111 |
-
"final_verify": bool(getattr(self, "final_verify", False)),
|
| 1112 |
-
"mid_read_step": bool(getattr(self, "mid_read_step", 0)),
|
| 1113 |
-
"interface_loss": bool(getattr(self, "interface_loss", False)),
|
| 1114 |
-
"read_anchor": bool(getattr(self, "read_anchor", False)),
|
| 1115 |
-
"counterfactual_reliance_loss": bool(
|
| 1116 |
-
getattr(self, "counterfactual_reliance_loss", False)
|
| 1117 |
-
),
|
| 1118 |
-
"counterfactual_step_retention_loss": bool(
|
| 1119 |
-
getattr(self, "counterfactual_step_retention_loss", False)
|
| 1120 |
-
),
|
| 1121 |
-
"functional_state_loss": bool(
|
| 1122 |
-
getattr(self, "functional_state_loss", False)
|
| 1123 |
-
),
|
| 1124 |
-
}
|
| 1125 |
-
active = [name for name, enabled in incompatible.items() if enabled]
|
| 1126 |
-
if active:
|
| 1127 |
-
raise ValueError(
|
| 1128 |
-
"spatial_visual_recurrence replaces every historical "
|
| 1129 |
-
"workspace/reliance path; incompatible with: "
|
| 1130 |
-
+ ", ".join(active)
|
| 1131 |
-
)
|
| 1132 |
-
modes = [bool(getattr(self, m, False)) for m in
|
| 1133 |
-
("read_gating", "read_residual_pure", "final_verify")]
|
| 1134 |
-
modes.append(bool(getattr(self, "mid_read_step", 0)))
|
| 1135 |
-
if sum(modes) > 1:
|
| 1136 |
-
raise ValueError(
|
| 1137 |
-
"read_gating / read_residual_pure / final_verify are mutually "
|
| 1138 |
-
"exclusive reread modes."
|
| 1139 |
-
)
|
| 1140 |
-
|
| 1141 |
-
|
| 1142 |
-
__all__ = ["CloseQwen2_5_VLConfig", "WORKSPACE_ROPE_MODES"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
configuration_source_qwen3.py
DELETED
|
@@ -1,740 +0,0 @@
|
|
| 1 |
-
"""Configuration for CLOSE latent reasoning on Qwen3-VL.
|
| 2 |
-
|
| 3 |
-
The current ``raw_evidence_question`` path keeps the complete layer-``ell_star``
|
| 4 |
-
visual memory as immutable evidence and recurrently updates the text-only
|
| 5 |
-
question state by cross-attending to it. Its selected interface averages all
|
| 6 |
-
four residual recurrent states and may use a training-only restoration target
|
| 7 |
-
from the normal multimodal question state. Historical slot-workspace fields are
|
| 8 |
-
retained so old checkpoints remain loadable, but they are mutually exclusive
|
| 9 |
-
with the raw-evidence path.
|
| 10 |
-
|
| 11 |
-
Every architectural hyperparameter of Method §3 lives here -- nothing is
|
| 12 |
-
hardcoded in ``modeling_close_qwen3_vl.py``.
|
| 13 |
-
|
| 14 |
-
Layer-index convention (used consistently across the repo)
|
| 15 |
-
----------------------------------------------------------
|
| 16 |
-
``ell_star`` is **0-indexed and inclusive**: it is the index of the last decoder
|
| 17 |
-
layer belonging to the lower branch. With ``num_hidden_layers == 28``::
|
| 18 |
-
|
| 19 |
-
F_{<=l*} = language_model.layers[: ell_star + 1] # lower_slice
|
| 20 |
-
F_{>l*} = language_model.layers[ell_star + 1 :] # upper_slice
|
| 21 |
-
|
| 22 |
-
so ``ell_star=18`` means layers 0..18 run before the split and 19..27 after it.
|
| 23 |
-
``ell_star`` is measured once by ``scripts/localize_read_point.py`` (§3.1) and
|
| 24 |
-
frozen before any workspace training.
|
| 25 |
-
|
| 26 |
-
Careful with ``Qwen3VLConfig``: its ``__setattr__`` forwards any attribute
|
| 27 |
-
whose name already exists in ``text_config.__dict__`` down to the sub-config, and
|
| 28 |
-
its ``__init__`` builds ``text_config`` from ``**kwargs``. Workspace fields are
|
| 29 |
-
therefore declared as explicit named parameters so they never enter ``kwargs``
|
| 30 |
-
and never shadow a text-config field. ``tests/test_config_roundtrip.py`` guards
|
| 31 |
-
this.
|
| 32 |
-
"""
|
| 33 |
-
|
| 34 |
-
from __future__ import annotations
|
| 35 |
-
|
| 36 |
-
import math
|
| 37 |
-
|
| 38 |
-
from transformers.models.qwen3_vl.configuration_qwen3_vl import Qwen3VLConfig
|
| 39 |
-
|
| 40 |
-
#: How the ``S`` workspace slots are assigned M-RoPE positions once they are
|
| 41 |
-
#: spliced into the replacement cache above ``ell_star``. Qwen3-VL uses 3D
|
| 42 |
-
#: M-RoPE (t, h, w) with ``mrope_section=[16, 24, 24]``, so slots -- which have
|
| 43 |
-
#: no spatial extent -- need an explicit convention.
|
| 44 |
-
WORKSPACE_ROPE_MODES = (
|
| 45 |
-
# Slots continue the 1-D text position sequence directly after Q*, with
|
| 46 |
-
# t == h == w. Treats the workspace as "more text".
|
| 47 |
-
"continue",
|
| 48 |
-
# Slots reuse the (t, h, w) span the image tokens occupied in the original
|
| 49 |
-
# multimodal prefill, subsampled to S positions. Preserves the read point's
|
| 50 |
-
# positional geometry but reintroduces image-derived indices.
|
| 51 |
-
"image_span",
|
| 52 |
-
# All slots share one position (the first position after Q*). Makes the
|
| 53 |
-
# workspace order-free, matching the set semantics of Perceiver-style slots.
|
| 54 |
-
"shared",
|
| 55 |
-
)
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
class CloseQwen3VLConfig(Qwen3VLConfig):
|
| 59 |
-
r"""Configuration for :class:`CloseQwen3VLForConditionalGeneration`.
|
| 60 |
-
|
| 61 |
-
Args:
|
| 62 |
-
ell_star: 0-indexed inclusive last layer of the lower branch. ``None``
|
| 63 |
-
until §3.1 localization has run; the model refuses to build with
|
| 64 |
-
``None`` rather than falling back to a guessed depth.
|
| 65 |
-
num_workspace_steps: the fixed recurrent horizon (paper notation ``T``;
|
| 66 |
-
per-item segment counts ``n`` exist only in the losses, never here).
|
| 67 |
-
The trainable parameter count is independent of it because
|
| 68 |
-
``f_theta`` is shared across steps, so it is a free experimental
|
| 69 |
-
axis. ``0`` is legal and is the no-recurrence floor: ``Z^(0)`` goes
|
| 70 |
-
straight to the decoder.
|
| 71 |
-
num_workspace_slots: ``S``, slots in ``Z``. Free capacity knob.
|
| 72 |
-
workspace_width: ``d_w``, the slot width. Free capacity knob -- the
|
| 73 |
-
method's claim is path exclusivity (all visual information routed
|
| 74 |
-
through ``Z``), NOT an information bottleneck, so ``S`` and ``d_w``
|
| 75 |
-
carry no methodological commitment. Kept below ``d`` (4096 for the
|
| 76 |
-
7B) since ``P_Z`` projects up at the splice.
|
| 77 |
-
workspace_num_heads: attention heads inside ``r_theta`` / ``f_theta``.
|
| 78 |
-
read_num_blocks: cross-attention blocks in ``r_theta``. The *read* is
|
| 79 |
-
once regardless of depth: all blocks see the same ``V*`` in a single
|
| 80 |
-
forward, and ``V*`` is discarded afterwards.
|
| 81 |
-
transition_num_blocks: blocks in the shared recurrent ``f_theta``.
|
| 82 |
-
workspace_ffn_mult: FFN expansion inside workspace blocks.
|
| 83 |
-
workspace_dropout: dropout inside workspace blocks.
|
| 84 |
-
workspace_rope_mode: one of :data:`WORKSPACE_ROPE_MODES`.
|
| 85 |
-
adapter_rank: rank of the low-rank adapter on the post-``ell_star``
|
| 86 |
-
backbone layers. ``0`` disables it (frozen backbone only).
|
| 87 |
-
adapter_alpha: LoRA-style scaling; effective scale is
|
| 88 |
-
``adapter_alpha / adapter_rank``.
|
| 89 |
-
adapter_dropout: dropout probability applied only to the LoRA branch.
|
| 90 |
-
lambda_traj: legacy trajectory-loss weight. The current method sets it
|
| 91 |
-
to zero and trains with final-answer CE only; it remains serialized
|
| 92 |
-
solely so historical checkpoints can still be inspected.
|
| 93 |
-
"""
|
| 94 |
-
|
| 95 |
-
model_type = "close_qwen3_vl"
|
| 96 |
-
|
| 97 |
-
def __init__(
|
| 98 |
-
self,
|
| 99 |
-
ell_star: int | None = None,
|
| 100 |
-
num_workspace_steps: int = 4,
|
| 101 |
-
num_workspace_slots: int = 32,
|
| 102 |
-
workspace_width: int = 512,
|
| 103 |
-
workspace_num_heads: int = 8,
|
| 104 |
-
read_num_blocks: int = 1,
|
| 105 |
-
transition_num_blocks: int = 2,
|
| 106 |
-
workspace_ffn_mult: int = 4,
|
| 107 |
-
workspace_dropout: float = 0.0,
|
| 108 |
-
workspace_rope_mode: str = "continue",
|
| 109 |
-
adapter_rank: int = 16,
|
| 110 |
-
adapter_alpha: int = 32,
|
| 111 |
-
adapter_dropout: float = 0.0,
|
| 112 |
-
adapter_exclude_layers: tuple = (),
|
| 113 |
-
lambda_traj: float = 0.0,
|
| 114 |
-
read_shortcut: bool = False,
|
| 115 |
-
splice_scale_match: bool = False,
|
| 116 |
-
read_gating: bool = False,
|
| 117 |
-
gate_init_eps: float = 0.02,
|
| 118 |
-
read_residual_pure: bool = False,
|
| 119 |
-
residual_norm_cap: float = 0.0,
|
| 120 |
-
pure_warm_start: bool = False,
|
| 121 |
-
final_verify: bool = False,
|
| 122 |
-
mid_read_step: int = 0,
|
| 123 |
-
persistent_slot_id: bool = False,
|
| 124 |
-
mid_read_alpha: float = 1.0,
|
| 125 |
-
evidence_reasoning: bool = False,
|
| 126 |
-
raw_evidence_question: bool = False,
|
| 127 |
-
raw_attention_source_layer: int = -1,
|
| 128 |
-
raw_transition_ffn: bool | None = None,
|
| 129 |
-
raw_state_replacement: bool = False,
|
| 130 |
-
raw_question_anchor: bool = False,
|
| 131 |
-
raw_mean_aggregation: bool = False,
|
| 132 |
-
counterfactual_residual_recurrence: bool = False,
|
| 133 |
-
visual_counterfactual_recurrence: bool = False,
|
| 134 |
-
visual_cumulative_recurrence: bool = False,
|
| 135 |
-
visual_full_state_recurrence: bool = False,
|
| 136 |
-
recurrence_only_adapter: bool = False,
|
| 137 |
-
counterfactual_beta: float = 1.0,
|
| 138 |
-
crr_concat_aggregation: bool = False,
|
| 139 |
-
crr_policy_rank: int = 0,
|
| 140 |
-
crr_policy_alpha: float = 32.0,
|
| 141 |
-
counterfactual_reliance_loss: bool = False,
|
| 142 |
-
transition_aware_reliance_loss: bool = False,
|
| 143 |
-
counterfactual_step_retention_loss: bool = False,
|
| 144 |
-
functional_state_loss: bool = False,
|
| 145 |
-
per_example_answer_loss: bool = False,
|
| 146 |
-
interface_loss: bool = False,
|
| 147 |
-
read_anchor: bool = False,
|
| 148 |
-
**kwargs,
|
| 149 |
-
):
|
| 150 |
-
super().__init__(**kwargs)
|
| 151 |
-
self.ell_star = ell_star
|
| 152 |
-
self.num_workspace_steps = num_workspace_steps
|
| 153 |
-
self.num_workspace_slots = num_workspace_slots
|
| 154 |
-
self.workspace_width = workspace_width
|
| 155 |
-
self.workspace_num_heads = workspace_num_heads
|
| 156 |
-
self.read_num_blocks = read_num_blocks
|
| 157 |
-
self.transition_num_blocks = transition_num_blocks
|
| 158 |
-
self.workspace_ffn_mult = workspace_ffn_mult
|
| 159 |
-
self.workspace_dropout = workspace_dropout
|
| 160 |
-
self.workspace_rope_mode = workspace_rope_mode
|
| 161 |
-
self.adapter_rank = adapter_rank
|
| 162 |
-
self.adapter_alpha = adapter_alpha
|
| 163 |
-
self.adapter_dropout = adapter_dropout
|
| 164 |
-
# E run: dense full-FT 층은 LoRA 대상에서 제외 (이중 파라미터화 방지)
|
| 165 |
-
self.adapter_exclude_layers = list(adapter_exclude_layers or ())
|
| 166 |
-
self.lambda_traj = lambda_traj
|
| 167 |
-
# v2 anti-collapse switches (defaults False so v1 checkpoints reload).
|
| 168 |
-
# read_shortcut: r_theta adds an orthogonally-projected pooled-V* term to
|
| 169 |
-
# every slot, so Z^(0) is image-dependent from step 0 instead of relying
|
| 170 |
-
# on a random cross-attention finding signal.
|
| 171 |
-
# splice_scale_match: P_Z(Z) rows are RMS-matched to the question rows at
|
| 172 |
-
# the splice. Unmatched, slot states are ~O(1) against layer-20 hiddens
|
| 173 |
-
# in the hundreds -- the decoder could ignore them for free, and did
|
| 174 |
-
# (probe 20143: visual sensitivity 0.94 -> 1.0000 over training).
|
| 175 |
-
self.read_shortcut = read_shortcut
|
| 176 |
-
self.splice_scale_match = splice_scale_match
|
| 177 |
-
# v6 candidate: gated re-reads DURING the recurrence. Every re-read
|
| 178 |
-
# goes through r_theta into Z, so the mediation invariant (decoder sees
|
| 179 |
-
# only [Q*; P_Z(Z)]) is untouched; what changes is that the workspace
|
| 180 |
-
# may consult V* again mid-reasoning, scaled by a learned scalar gate.
|
| 181 |
-
# The gate is free at EVERY step -- no hand-coded schedule. Degeneration
|
| 182 |
-
# into answer-time retrieval is disciplined by the objective itself:
|
| 183 |
-
# L_traj pins each intermediate state to its teacher segment, so a
|
| 184 |
-
# wait-then-fetch trajectory misses its intermediate targets. The gate
|
| 185 |
-
# bias initialises negative, i.e. training starts from the proven
|
| 186 |
-
# no-reread regime and departs only where gradients demand it.
|
| 187 |
-
# Default False: v4/v5 checkpoints reload bit-identically.
|
| 188 |
-
self.read_gating = read_gating
|
| 189 |
-
# Initial gate open-rate epsilon_gamma: b_gamma = logit(eps). Sigmoid
|
| 190 |
-
# never reaches exactly 0 at finite logits, so this is near-identity
|
| 191 |
-
# (not bitwise) initialisation w.r.t. the read-once path.
|
| 192 |
-
self.gate_init_eps = gate_init_eps
|
| 193 |
-
# v7: always-on PURE visual residual rereads. Every step adds
|
| 194 |
-
# Z = U + W_o XAttn(LN(U) -> queries, V* -> keys/values): no self-attn,
|
| 195 |
-
# no FFN, no output norm, no biases in the branch, W_o zero-init.
|
| 196 |
-
# Motivation (battery, ep1 ckpt 20752): read_once hurt badly while
|
| 197 |
-
# null_step (blank memory at one step) was harmless -- the gated
|
| 198 |
-
# reader's value was extra recurrent DEPTH, not vision. This branch
|
| 199 |
-
# removes every path that can change Z without V* content, so any
|
| 200 |
-
# benefit it shows IS visual by construction.
|
| 201 |
-
self.read_residual_pure = read_residual_pure
|
| 202 |
-
# v7.1 (advisor): relative-norm trust region on the visual residual.
|
| 203 |
-
# s_k = min(1, rho * RMS(U) / (RMS(Delta) + eps)); Z = U + s_k * Delta
|
| 204 |
-
# Unlike a fixed alpha, the optimizer cannot undo this by rescaling
|
| 205 |
-
# W_o (s_k adapts); ||Delta_used|| <~ rho * ||U|| structurally, and
|
| 206 |
-
# V* = 0 => Delta = 0 keeps the zero-memory identity. 0.0 disables.
|
| 207 |
-
self.residual_norm_cap = residual_norm_cap
|
| 208 |
-
# v7.1: initialise pure_attn K/V projections from the initial visual
|
| 209 |
-
# reader's v_in (same [d_w, d] shape) and Q from its first read
|
| 210 |
-
# block's in-proj slice, instead of random xavier -- the ep1 finding
|
| 211 |
-
# was that Q/K/V stayed ~random while only W_o trained.
|
| 212 |
-
self.pure_warm_start = pure_warm_start
|
| 213 |
-
# MAIN METHOD (advisor-confirmed): Final-Verified Recurrent Workspace.
|
| 214 |
-
# Initial read -> PURE latent recurrence (no mid-step visual anything)
|
| 215 |
-
# -> exactly ONE pure visual verification at the end:
|
| 216 |
-
# Z_out = U^(T) + W_o XAttn(LN(U^(T)), V*), alpha = 1, W_o = 0 init.
|
| 217 |
-
# Uses the same bias-free verifier module as read_residual_pure; the
|
| 218 |
-
# two flags differ only in WHERE the branch fires (every step vs once).
|
| 219 |
-
self.final_verify = final_verify
|
| 220 |
-
# MAIN (advisor 최종 확정): Mid-Read Recurrent Workspace. 고정 상수
|
| 221 |
-
# k* = floor(T/2) = 4 (스텝 sweep 산물이 아니라 판독 전후 동수 전이의
|
| 222 |
-
# midpoint)에서 pure visual read 1회:
|
| 223 |
-
# Z^(4) = U^(4) + W_o XAttn(LN(U^(4)), V*), alpha=1, W_o=0 init.
|
| 224 |
-
# 논리적 step 4 == python 루프 index 3 (0-based) -- off-by-one은
|
| 225 |
-
# tests/test_mid_read.py가 상태 분기 위치로 고정한다. 0이면 비활성.
|
| 226 |
-
self.mid_read_step = mid_read_step
|
| 227 |
-
# advisor(2026-08-06): E_slot(=workspace_read.slots)을 매 recurrent
|
| 228 |
-
# step 입력과 mid-read query에 일시 제공. state에는 누적하지 않는다.
|
| 229 |
-
self.persistent_slot_id = persistent_slot_id
|
| 230 |
-
# 고정 read 스케일 Z = U + alpha*W_o A (advisor scale-mismatch 검사).
|
| 231 |
-
# 1.0 = 기존과 비트동일. eval의 gate_overrides alpha는 절대값으로 이를 대체.
|
| 232 |
-
self.mid_read_alpha = mid_read_alpha
|
| 233 |
-
# advisor 최종안: E(persistent evidence) / R(recurrent reasoning) 분리.
|
| 234 |
-
# states = [E^(0), R^(1..T)], 결합 Z^(T)=E+R^(T)는 prefill에서.
|
| 235 |
-
self.evidence_reasoning = evidence_reasoning
|
| 236 |
-
# Current replacement candidate: no learned evidence/reasoning slots.
|
| 237 |
-
# E is the complete raw V* sequence and R0 is the text-only Q* sequence.
|
| 238 |
-
# A single shared CrossAttn transition produces R1..RT. Residual mode
|
| 239 |
-
# decodes RT; the replacement candidate decodes Q*+HT. Cross-attention
|
| 240 |
-
# uses the backbone's native GQA shape and
|
| 241 |
-
# is initialized from the first upper layer by default. Checkpoints
|
| 242 |
-
# created before the CrossAttn-only decision did not serialize
|
| 243 |
-
# raw_transition_ffn; None therefore preserves their historical FFN
|
| 244 |
-
# for faithful evaluation, while every new training launch passes False.
|
| 245 |
-
self.raw_evidence_question = bool(raw_evidence_question)
|
| 246 |
-
self.raw_attention_source_layer = int(raw_attention_source_layer)
|
| 247 |
-
self.raw_transition_ffn = (
|
| 248 |
-
True if raw_transition_ffn is None else bool(raw_transition_ffn)
|
| 249 |
-
)
|
| 250 |
-
# Experimental simplification of the same raw-E/Q path. The residual
|
| 251 |
-
# carrier is replaced, not augmented:
|
| 252 |
-
# H0=Q*, Hk=CrossAttn(H{k-1}, E), decoder_latent=Q*+HT.
|
| 253 |
-
# False preserves all residual raw-E/Q checkpoints and active runs.
|
| 254 |
-
self.raw_state_replacement = bool(raw_state_replacement)
|
| 255 |
-
# Question-anchored replacement keeps the fixed text scaffold in every
|
| 256 |
-
# recurrent query without restoring the old recurrent identity path:
|
| 257 |
-
# Z0=Q*, Zk=Q*+CrossAttn(Z{k-1}, E), decoder_latent=ZT.
|
| 258 |
-
# This replaces the one-time Q*+HT decoder interface above.
|
| 259 |
-
self.raw_question_anchor = bool(raw_question_anchor)
|
| 260 |
-
# Q-shaped recurrent candidate selected by the V* interface oracle:
|
| 261 |
-
# R0=Q*, Rk=R{k-1}+CrossAttn(R{k-1}, V*)
|
| 262 |
-
# R_agg=mean(R1..RT)
|
| 263 |
-
# This is parameter-free and replaces both question-anchored state
|
| 264 |
-
# replacement and final-state-only decoding.
|
| 265 |
-
self.raw_mean_aggregation = bool(raw_mean_aggregation)
|
| 266 |
-
# Counterfactual Residual Recurrence (CRR): the image-conditioned and
|
| 267 |
-
# text-only question states are propagated by the same native layer
|
| 268 |
-
# F=L(ell_star+1). Only their counterfactual residual is recurrently
|
| 269 |
-
# refined; there are no slots, V* memory, custom attention modules, or
|
| 270 |
-
# inference-time auxiliary modules. beta is the fixed relaxed-update
|
| 271 |
-
# coefficient; an optional batch-swap reliance loss is training-only.
|
| 272 |
-
self.counterfactual_residual_recurrence = bool(
|
| 273 |
-
counterfactual_residual_recurrence
|
| 274 |
-
)
|
| 275 |
-
# Qwen3 counterpart of the promoted Qwen2.5 full-state CRR path. The
|
| 276 |
-
# multimodal scaffold is used only inside the shared recurrent cell;
|
| 277 |
-
# answer decoding still receives a question-shaped state and a
|
| 278 |
-
# text-only prefix cache.
|
| 279 |
-
self.visual_counterfactual_recurrence = bool(
|
| 280 |
-
visual_counterfactual_recurrence
|
| 281 |
-
)
|
| 282 |
-
self.visual_cumulative_recurrence = bool(
|
| 283 |
-
visual_cumulative_recurrence
|
| 284 |
-
)
|
| 285 |
-
self.visual_full_state_recurrence = bool(
|
| 286 |
-
visual_full_state_recurrence
|
| 287 |
-
)
|
| 288 |
-
self.recurrence_only_adapter = bool(recurrence_only_adapter)
|
| 289 |
-
self.counterfactual_beta = float(counterfactual_beta)
|
| 290 |
-
# Bias-free Concat(C1..CT) -> C_agg, initialized to the exact
|
| 291 |
-
# historical mean interface for checkpoint-compatible warm starts.
|
| 292 |
-
self.crr_concat_aggregation = bool(crr_concat_aggregation)
|
| 293 |
-
# Optional latent-only policy adapter. It corrects the deterministic
|
| 294 |
-
# C2..CT means but never touches B, C1, or answer-token decoding. Rank 0
|
| 295 |
-
# is the exact historical CRR path; RL checkpoints serialize rank > 0.
|
| 296 |
-
self.crr_policy_rank = int(crr_policy_rank)
|
| 297 |
-
self.crr_policy_alpha = float(crr_policy_alpha)
|
| 298 |
-
# Training-only CRR intervention. A batch-matched residual from a
|
| 299 |
-
# different-answer example replaces the factual residual before the
|
| 300 |
-
# upper decoder. The model returns the factual-minus-swapped answer
|
| 301 |
-
# score gap; the Trainer applies the margin loss. Evaluation and
|
| 302 |
-
# generation never execute the extra branch.
|
| 303 |
-
self.counterfactual_reliance_loss = bool(
|
| 304 |
-
counterfactual_reliance_loss
|
| 305 |
-
)
|
| 306 |
-
self.transition_aware_reliance_loss = bool(
|
| 307 |
-
transition_aware_reliance_loss
|
| 308 |
-
)
|
| 309 |
-
# Training-only functional no-regression objective. Gold-answer NLL
|
| 310 |
-
# is measured through the actual upper decoder at every recurrent CRR
|
| 311 |
-
# prefix; inference remains the unchanged T-step mean interface.
|
| 312 |
-
self.counterfactual_step_retention_loss = bool(
|
| 313 |
-
counterfactual_step_retention_loss
|
| 314 |
-
)
|
| 315 |
-
# Training-only self-distillation target. The multimodal lower pass
|
| 316 |
-
# already computed for V* also provides its image-conditioned question
|
| 317 |
-
# rows. They supervise R_agg at the split boundary, but are never
|
| 318 |
-
# exposed to the inference decoder.
|
| 319 |
-
self.functional_state_loss = bool(functional_state_loss)
|
| 320 |
-
# Token CE lets long caption answers dominate one-token visual-choice
|
| 321 |
-
# examples. The per-example variant first averages valid answer tokens
|
| 322 |
-
# within each sample, then averages samples. It changes training/eval
|
| 323 |
-
# loss reduction only and has no inference-time effect.
|
| 324 |
-
self.per_example_answer_loss = bool(per_example_answer_loss)
|
| 325 |
-
# ER v2 (advisor 2026-08-11): explicit evidence read —
|
| 326 |
-
# R^(k) = f_theta(R^(k-1), Q*; E), E는 별도 memory로 cross-attn 읽기.
|
| 327 |
-
# (E+R 합산-감산 제거. evidence_reasoning=True 전제.)
|
| 328 |
-
self.explicit_evidence_read = bool(kwargs.pop("explicit_evidence_read", False))
|
| 329 |
-
# Historical field name retained for checkpoint compatibility. In ER
|
| 330 |
-
# v2.2 this means R^(0) is a batch-broadcast set of learnable slots; it
|
| 331 |
-
# is deliberately NOT initialized from Q*. Q* conditions every shared
|
| 332 |
-
# transition instead.
|
| 333 |
-
self.r_init_question = bool(kwargs.pop("r_init_question", False))
|
| 334 |
-
# ER v2.1 (advisor 2026-08-11): evidence read의 competitive normalization —
|
| 335 |
-
# slot 축 softmax 후 evidence 축 재정규화 (Slot Attention식 경쟁).
|
| 336 |
-
# 파라미터/모듈/loss 무추가; evidence_read의 W_qkv/W_o 재사용.
|
| 337 |
-
self.competitive_evidence_read = bool(
|
| 338 |
-
kwargs.pop("competitive_evidence_read", False))
|
| 339 |
-
# ER v2.2 C: k>=1 traj 감독 부재 시 twin 체인 제거 (states = answer 체인).
|
| 340 |
-
self.er_single_chain = bool(kwargs.pop("er_single_chain", False))
|
| 341 |
-
# ER v2.2 C (§5-7): decoder latent = softmax(temporal_logits)로 가중한
|
| 342 |
-
# R1..RT 결합 (전역 스칼라 T개). er_single_chain 전제. (legacy)
|
| 343 |
-
self.temporal_aggregation = bool(kwargs.pop("temporal_aggregation", False))
|
| 344 |
-
# ER v2.2 C 확정판: R_agg = Linear(Concat(R1..RT), bias=False),
|
| 345 |
-
# 초기 W=[I/T,...,I/T] (R_agg=mean(R1..RT)). er_single_chain 전제.
|
| 346 |
-
self.concat_aggregation = bool(kwargs.pop("concat_aggregation", False))
|
| 347 |
-
# ER v2: 워크스페이스 블록의 rank-r 부분공간 attention/FFN (0 = dense).
|
| 348 |
-
# d_w=d에서 state width 유지와 파라미터 폭증을 분리 (advisor).
|
| 349 |
-
self.workspace_low_rank = int(kwargs.pop("workspace_low_rank", 0))
|
| 350 |
-
# advisor: single-layer text-side interface alignment (bridge-only).
|
| 351 |
-
# teacher = frozen-base mm 경로 q_end@l*+1 (adapter off, sg);
|
| 352 |
-
# student = detach(R^T) splice의 l*+1 한 층 재계산. 추론 시 무존재.
|
| 353 |
-
self.interface_loss = interface_loss
|
| 354 |
-
# Training-only visual anchor switch. False in the current answer-only
|
| 355 |
-
# method; when false the model does not even materialize pooled V* for
|
| 356 |
-
# the Trainer.
|
| 357 |
-
self.read_anchor = bool(read_anchor)
|
| 358 |
-
if evidence_reasoning and persistent_slot_id:
|
| 359 |
-
raise ValueError("evidence_reasoning은 persistent_slot_id와 동시 사용 불가")
|
| 360 |
-
if evidence_reasoning and not mid_read_step:
|
| 361 |
-
raise ValueError("evidence_reasoning은 mid_read_step > 0 필요")
|
| 362 |
-
self.validate()
|
| 363 |
-
|
| 364 |
-
# -- derived views -----------------------------------------------------
|
| 365 |
-
|
| 366 |
-
@property
|
| 367 |
-
def num_decoder_layers(self) -> int:
|
| 368 |
-
return self.text_config.num_hidden_layers
|
| 369 |
-
|
| 370 |
-
@property
|
| 371 |
-
def backbone_width(self) -> int:
|
| 372 |
-
"""``d`` -- backbone hidden width (4096 for the 7B)."""
|
| 373 |
-
return self.text_config.hidden_size
|
| 374 |
-
|
| 375 |
-
@property
|
| 376 |
-
def lower_slice(self) -> slice:
|
| 377 |
-
"""Layers forming ``F_{<=l*}``."""
|
| 378 |
-
self._require_ell_star()
|
| 379 |
-
return slice(0, self.ell_star + 1)
|
| 380 |
-
|
| 381 |
-
@property
|
| 382 |
-
def upper_slice(self) -> slice:
|
| 383 |
-
"""Layers forming ``F_{>l*}``."""
|
| 384 |
-
self._require_ell_star()
|
| 385 |
-
return slice(self.ell_star + 1, self.num_decoder_layers)
|
| 386 |
-
|
| 387 |
-
# -- validation --------------------------------------------------------
|
| 388 |
-
|
| 389 |
-
def _require_ell_star(self) -> None:
|
| 390 |
-
if self.ell_star is None:
|
| 391 |
-
raise ValueError(
|
| 392 |
-
"ell_star is unset. Run scripts/localize_read_point.py (Method 3.1) "
|
| 393 |
-
"and pass the measured layer explicitly; it must not be guessed."
|
| 394 |
-
)
|
| 395 |
-
|
| 396 |
-
def validate(self) -> None:
|
| 397 |
-
"""Reject configurations that silently break the §3.2 contract."""
|
| 398 |
-
if self.ell_star is not None:
|
| 399 |
-
n = self.num_decoder_layers
|
| 400 |
-
# Both branches must be non-empty: an empty lower branch means there
|
| 401 |
-
# is no V* to read, an empty upper branch means the workspace never
|
| 402 |
-
# reaches the decoder.
|
| 403 |
-
if not 0 <= self.ell_star <= n - 2:
|
| 404 |
-
raise ValueError(
|
| 405 |
-
f"ell_star={self.ell_star} out of range for {n} decoder layers; "
|
| 406 |
-
f"expected 0 <= ell_star <= {n - 2} so both branches are non-empty."
|
| 407 |
-
)
|
| 408 |
-
# K=0 is the no-recurrence control (read only), not a misconfiguration.
|
| 409 |
-
if self.num_workspace_steps < 0:
|
| 410 |
-
raise ValueError("num_workspace_steps (K) must be >= 0.")
|
| 411 |
-
if self.num_workspace_slots < 1:
|
| 412 |
-
raise ValueError("num_workspace_slots (S) must be >= 1.")
|
| 413 |
-
if self.workspace_num_heads < 1:
|
| 414 |
-
raise ValueError("workspace_num_heads must be >= 1.")
|
| 415 |
-
if self.workspace_width % self.workspace_num_heads != 0:
|
| 416 |
-
raise ValueError(
|
| 417 |
-
f"workspace_width={self.workspace_width} must be divisible by "
|
| 418 |
-
f"workspace_num_heads={self.workspace_num_heads}."
|
| 419 |
-
)
|
| 420 |
-
if self.workspace_width > self.backbone_width:
|
| 421 |
-
raise ValueError(
|
| 422 |
-
f"workspace_width (d_w={self.workspace_width}) cannot exceed "
|
| 423 |
-
f"backbone width (d={self.backbone_width}). Native-width d_w=d is "
|
| 424 |
-
"the current method and uses an Identity decoder interface."
|
| 425 |
-
)
|
| 426 |
-
if self.workspace_rope_mode not in WORKSPACE_ROPE_MODES:
|
| 427 |
-
raise ValueError(
|
| 428 |
-
f"workspace_rope_mode={self.workspace_rope_mode!r} not in "
|
| 429 |
-
f"{WORKSPACE_ROPE_MODES}."
|
| 430 |
-
)
|
| 431 |
-
if self.adapter_rank < 0:
|
| 432 |
-
raise ValueError("adapter_rank must be >= 0 (0 disables the adapter).")
|
| 433 |
-
if not math.isfinite(self.adapter_dropout) or not 0.0 <= self.adapter_dropout < 1.0:
|
| 434 |
-
raise ValueError("adapter_dropout must be finite and in [0, 1).")
|
| 435 |
-
if getattr(self, "crr_policy_rank", 0) < 0:
|
| 436 |
-
raise ValueError("crr_policy_rank must be >= 0.")
|
| 437 |
-
if not math.isfinite(getattr(self, "crr_policy_alpha", 0.0)) or getattr(
|
| 438 |
-
self, "crr_policy_alpha", 0.0
|
| 439 |
-
) <= 0.0:
|
| 440 |
-
raise ValueError("crr_policy_alpha must be finite and > 0.")
|
| 441 |
-
if self.read_num_blocks < 1 or self.transition_num_blocks < 1:
|
| 442 |
-
raise ValueError("read_num_blocks and transition_num_blocks must be >= 1.")
|
| 443 |
-
if self.workspace_ffn_mult < 1:
|
| 444 |
-
raise ValueError("workspace_ffn_mult must be >= 1.")
|
| 445 |
-
mid = int(getattr(self, "mid_read_step", 0) or 0)
|
| 446 |
-
if mid < 0 or mid > self.num_workspace_steps:
|
| 447 |
-
raise ValueError(
|
| 448 |
-
f"mid_read_step={mid} must be in [0, num_workspace_steps="
|
| 449 |
-
f"{self.num_workspace_steps}]."
|
| 450 |
-
)
|
| 451 |
-
low_rank = int(getattr(self, "workspace_low_rank", 0) or 0)
|
| 452 |
-
if low_rank < 0:
|
| 453 |
-
raise ValueError("workspace_low_rank must be >= 0.")
|
| 454 |
-
if low_rank and low_rank % self.workspace_num_heads != 0:
|
| 455 |
-
raise ValueError(
|
| 456 |
-
f"workspace_low_rank={low_rank} must be divisible by "
|
| 457 |
-
f"workspace_num_heads={self.workspace_num_heads}."
|
| 458 |
-
)
|
| 459 |
-
if getattr(self, "competitive_evidence_read", False) and not getattr(
|
| 460 |
-
self, "explicit_evidence_read", False
|
| 461 |
-
):
|
| 462 |
-
raise ValueError(
|
| 463 |
-
"competitive_evidence_read requires explicit_evidence_read=True; "
|
| 464 |
-
"otherwise the flag has no execution path."
|
| 465 |
-
)
|
| 466 |
-
if getattr(self, "explicit_evidence_read", False) and not getattr(
|
| 467 |
-
self, "evidence_reasoning", False
|
| 468 |
-
):
|
| 469 |
-
raise ValueError(
|
| 470 |
-
"explicit_evidence_read requires evidence_reasoning=True; otherwise "
|
| 471 |
-
"the evidence-read module is allocated but never called."
|
| 472 |
-
)
|
| 473 |
-
if getattr(self, "r_init_question", False) and not getattr(
|
| 474 |
-
self, "evidence_reasoning", False
|
| 475 |
-
):
|
| 476 |
-
raise ValueError(
|
| 477 |
-
"r_init_question (legacy name for learnable R0 slots) requires "
|
| 478 |
-
"evidence_reasoning=True; otherwise reasoning_slots are unused."
|
| 479 |
-
)
|
| 480 |
-
if (getattr(self, "concat_aggregation", False)
|
| 481 |
-
or getattr(self, "temporal_aggregation", False)):
|
| 482 |
-
if self.num_workspace_steps < 1:
|
| 483 |
-
raise ValueError("reasoning aggregation requires num_workspace_steps >= 1.")
|
| 484 |
-
if not getattr(self, "evidence_reasoning", False):
|
| 485 |
-
raise ValueError(
|
| 486 |
-
"reasoning aggregation requires evidence_reasoning=True; "
|
| 487 |
-
"otherwise the aggregation module is unused."
|
| 488 |
-
)
|
| 489 |
-
if getattr(self, "concat_aggregation", False) and not getattr(
|
| 490 |
-
self, "er_single_chain", False
|
| 491 |
-
):
|
| 492 |
-
raise ValueError("concat_aggregation requires er_single_chain=True.")
|
| 493 |
-
if getattr(self, "raw_evidence_question", False):
|
| 494 |
-
if self.workspace_width != self.backbone_width:
|
| 495 |
-
raise ValueError(
|
| 496 |
-
"raw_evidence_question requires native workspace_width == "
|
| 497 |
-
"backbone_width: E=V* and R0=Q* are used without projections."
|
| 498 |
-
)
|
| 499 |
-
if self.num_workspace_steps < 1:
|
| 500 |
-
raise ValueError(
|
| 501 |
-
"raw_evidence_question requires num_workspace_steps >= 1."
|
| 502 |
-
)
|
| 503 |
-
source_layer = (
|
| 504 |
-
self.ell_star + 1
|
| 505 |
-
if self.raw_attention_source_layer < 0
|
| 506 |
-
else self.raw_attention_source_layer
|
| 507 |
-
)
|
| 508 |
-
if not self.ell_star < source_layer < self.text_config.num_hidden_layers:
|
| 509 |
-
raise ValueError(
|
| 510 |
-
"raw_attention_source_layer must be an upper-backbone layer, "
|
| 511 |
-
f"got ell_star={self.ell_star}, source={source_layer}."
|
| 512 |
-
)
|
| 513 |
-
incompatible = {
|
| 514 |
-
"evidence_reasoning": bool(getattr(self, "evidence_reasoning", False)),
|
| 515 |
-
"explicit_evidence_read": bool(getattr(self, "explicit_evidence_read", False)),
|
| 516 |
-
"competitive_evidence_read": bool(getattr(self, "competitive_evidence_read", False)),
|
| 517 |
-
"r_init_question": bool(getattr(self, "r_init_question", False)),
|
| 518 |
-
"er_single_chain": bool(getattr(self, "er_single_chain", False)),
|
| 519 |
-
"temporal_aggregation": bool(getattr(self, "temporal_aggregation", False)),
|
| 520 |
-
"concat_aggregation": bool(getattr(self, "concat_aggregation", False)),
|
| 521 |
-
"read_shortcut": bool(getattr(self, "read_shortcut", False)),
|
| 522 |
-
"splice_scale_match": bool(getattr(self, "splice_scale_match", False)),
|
| 523 |
-
"read_gating": bool(getattr(self, "read_gating", False)),
|
| 524 |
-
"read_residual_pure": bool(getattr(self, "read_residual_pure", False)),
|
| 525 |
-
"final_verify": bool(getattr(self, "final_verify", False)),
|
| 526 |
-
"mid_read_step": bool(getattr(self, "mid_read_step", 0)),
|
| 527 |
-
"persistent_slot_id": bool(getattr(self, "persistent_slot_id", False)),
|
| 528 |
-
"interface_loss": bool(getattr(self, "interface_loss", False)),
|
| 529 |
-
"read_anchor": bool(getattr(self, "read_anchor", False)),
|
| 530 |
-
}
|
| 531 |
-
active = [name for name, enabled in incompatible.items() if enabled]
|
| 532 |
-
if active:
|
| 533 |
-
raise ValueError(
|
| 534 |
-
"raw_evidence_question replaces the slot workspace and is "
|
| 535 |
-
f"incompatible with: {', '.join(active)}"
|
| 536 |
-
)
|
| 537 |
-
if self.raw_state_replacement and self.raw_transition_ffn:
|
| 538 |
-
raise ValueError(
|
| 539 |
-
"raw_state_replacement is CrossAttn-only and is incompatible "
|
| 540 |
-
"with raw_transition_ffn=True."
|
| 541 |
-
)
|
| 542 |
-
if self.raw_question_anchor and not self.raw_state_replacement:
|
| 543 |
-
raise ValueError(
|
| 544 |
-
"raw_question_anchor requires raw_state_replacement=True."
|
| 545 |
-
)
|
| 546 |
-
if self.raw_mean_aggregation and (
|
| 547 |
-
self.raw_state_replacement or self.raw_question_anchor
|
| 548 |
-
):
|
| 549 |
-
raise ValueError(
|
| 550 |
-
"raw_mean_aggregation replaces raw_state_replacement and "
|
| 551 |
-
"raw_question_anchor; use the residual recurrence."
|
| 552 |
-
)
|
| 553 |
-
if self.functional_state_loss and not self.raw_mean_aggregation:
|
| 554 |
-
raise ValueError(
|
| 555 |
-
"functional_state_loss requires raw_mean_aggregation=True "
|
| 556 |
-
"so its target is the exact decoder-facing recurrence."
|
| 557 |
-
)
|
| 558 |
-
elif getattr(self, "raw_state_replacement", False):
|
| 559 |
-
raise ValueError(
|
| 560 |
-
"raw_state_replacement requires raw_evidence_question=True."
|
| 561 |
-
)
|
| 562 |
-
elif getattr(self, "raw_question_anchor", False):
|
| 563 |
-
raise ValueError(
|
| 564 |
-
"raw_question_anchor requires raw_evidence_question=True."
|
| 565 |
-
)
|
| 566 |
-
elif getattr(self, "raw_mean_aggregation", False):
|
| 567 |
-
raise ValueError(
|
| 568 |
-
"raw_mean_aggregation requires raw_evidence_question=True."
|
| 569 |
-
)
|
| 570 |
-
elif getattr(self, "functional_state_loss", False):
|
| 571 |
-
raise ValueError(
|
| 572 |
-
"functional_state_loss requires raw_evidence_question=True."
|
| 573 |
-
)
|
| 574 |
-
if (
|
| 575 |
-
getattr(self, "counterfactual_reliance_loss", False)
|
| 576 |
-
and not getattr(self, "counterfactual_residual_recurrence", False)
|
| 577 |
-
):
|
| 578 |
-
raise ValueError(
|
| 579 |
-
"counterfactual_reliance_loss requires "
|
| 580 |
-
"counterfactual_residual_recurrence=True."
|
| 581 |
-
)
|
| 582 |
-
if (
|
| 583 |
-
getattr(self, "transition_aware_reliance_loss", False)
|
| 584 |
-
and not getattr(self, "counterfactual_reliance_loss", False)
|
| 585 |
-
):
|
| 586 |
-
raise ValueError(
|
| 587 |
-
"transition_aware_reliance_loss requires "
|
| 588 |
-
"counterfactual_reliance_loss=True."
|
| 589 |
-
)
|
| 590 |
-
if (
|
| 591 |
-
getattr(self, "transition_aware_reliance_loss", False)
|
| 592 |
-
and self.num_workspace_steps < 2
|
| 593 |
-
):
|
| 594 |
-
raise ValueError(
|
| 595 |
-
"transition_aware_reliance_loss requires at least two steps."
|
| 596 |
-
)
|
| 597 |
-
if (
|
| 598 |
-
getattr(self, "counterfactual_step_retention_loss", False)
|
| 599 |
-
and not getattr(self, "counterfactual_residual_recurrence", False)
|
| 600 |
-
):
|
| 601 |
-
raise ValueError(
|
| 602 |
-
"counterfactual_step_retention_loss requires "
|
| 603 |
-
"counterfactual_residual_recurrence=True."
|
| 604 |
-
)
|
| 605 |
-
if (
|
| 606 |
-
getattr(self, "counterfactual_step_retention_loss", False)
|
| 607 |
-
and self.num_workspace_steps < 2
|
| 608 |
-
):
|
| 609 |
-
raise ValueError(
|
| 610 |
-
"counterfactual_step_retention_loss requires at least two steps."
|
| 611 |
-
)
|
| 612 |
-
if getattr(self, "crr_concat_aggregation", False) and not getattr(
|
| 613 |
-
self, "counterfactual_residual_recurrence", False
|
| 614 |
-
):
|
| 615 |
-
raise ValueError(
|
| 616 |
-
"crr_concat_aggregation requires "
|
| 617 |
-
"counterfactual_residual_recurrence=True."
|
| 618 |
-
)
|
| 619 |
-
if getattr(self, "counterfactual_residual_recurrence", False):
|
| 620 |
-
if getattr(self, "raw_evidence_question", False):
|
| 621 |
-
raise ValueError(
|
| 622 |
-
"counterfactual_residual_recurrence replaces "
|
| 623 |
-
"raw_evidence_question; enable exactly one q-state method."
|
| 624 |
-
)
|
| 625 |
-
if self.num_workspace_steps < 1:
|
| 626 |
-
raise ValueError(
|
| 627 |
-
"counterfactual_residual_recurrence requires "
|
| 628 |
-
"num_workspace_steps >= 1."
|
| 629 |
-
)
|
| 630 |
-
if self.crr_policy_rank > 0 and self.num_workspace_steps < 2:
|
| 631 |
-
raise ValueError(
|
| 632 |
-
"crr_policy_rank > 0 requires num_workspace_steps >= 2."
|
| 633 |
-
)
|
| 634 |
-
if not 0.0 <= self.counterfactual_beta <= 1.0:
|
| 635 |
-
raise ValueError(
|
| 636 |
-
"counterfactual_beta must lie in [0, 1], got "
|
| 637 |
-
f"{self.counterfactual_beta}."
|
| 638 |
-
)
|
| 639 |
-
incompatible = {
|
| 640 |
-
"evidence_reasoning": bool(getattr(self, "evidence_reasoning", False)),
|
| 641 |
-
"explicit_evidence_read": bool(getattr(self, "explicit_evidence_read", False)),
|
| 642 |
-
"competitive_evidence_read": bool(getattr(self, "competitive_evidence_read", False)),
|
| 643 |
-
"r_init_question": bool(getattr(self, "r_init_question", False)),
|
| 644 |
-
"er_single_chain": bool(getattr(self, "er_single_chain", False)),
|
| 645 |
-
"temporal_aggregation": bool(getattr(self, "temporal_aggregation", False)),
|
| 646 |
-
"concat_aggregation": bool(getattr(self, "concat_aggregation", False)),
|
| 647 |
-
"read_shortcut": bool(getattr(self, "read_shortcut", False)),
|
| 648 |
-
"splice_scale_match": bool(getattr(self, "splice_scale_match", False)),
|
| 649 |
-
"read_gating": bool(getattr(self, "read_gating", False)),
|
| 650 |
-
"read_residual_pure": bool(getattr(self, "read_residual_pure", False)),
|
| 651 |
-
"final_verify": bool(getattr(self, "final_verify", False)),
|
| 652 |
-
"mid_read_step": bool(getattr(self, "mid_read_step", 0)),
|
| 653 |
-
"persistent_slot_id": bool(getattr(self, "persistent_slot_id", False)),
|
| 654 |
-
"interface_loss": bool(getattr(self, "interface_loss", False)),
|
| 655 |
-
"read_anchor": bool(getattr(self, "read_anchor", False)),
|
| 656 |
-
"functional_state_loss": bool(getattr(self, "functional_state_loss", False)),
|
| 657 |
-
}
|
| 658 |
-
active = [name for name, enabled in incompatible.items() if enabled]
|
| 659 |
-
if active:
|
| 660 |
-
raise ValueError(
|
| 661 |
-
"counterfactual_residual_recurrence replaces the slot/raw "
|
| 662 |
-
f"workspace and is incompatible with: {', '.join(active)}"
|
| 663 |
-
)
|
| 664 |
-
elif getattr(self, "crr_policy_rank", 0) > 0:
|
| 665 |
-
raise ValueError(
|
| 666 |
-
"crr_policy_rank > 0 requires "
|
| 667 |
-
"counterfactual_residual_recurrence=True."
|
| 668 |
-
)
|
| 669 |
-
if getattr(self, "visual_counterfactual_recurrence", False):
|
| 670 |
-
if not getattr(self, "counterfactual_residual_recurrence", False):
|
| 671 |
-
raise ValueError(
|
| 672 |
-
"visual_counterfactual_recurrence requires "
|
| 673 |
-
"counterfactual_residual_recurrence=True."
|
| 674 |
-
)
|
| 675 |
-
if not getattr(self, "visual_cumulative_recurrence", False):
|
| 676 |
-
raise ValueError(
|
| 677 |
-
"Qwen3 visual recurrence currently requires the promoted "
|
| 678 |
-
"cumulative full-state path."
|
| 679 |
-
)
|
| 680 |
-
if not getattr(self, "visual_full_state_recurrence", False):
|
| 681 |
-
raise ValueError(
|
| 682 |
-
"Qwen3 visual recurrence currently requires "
|
| 683 |
-
"visual_full_state_recurrence=True."
|
| 684 |
-
)
|
| 685 |
-
incompatible = {
|
| 686 |
-
"counterfactual_reliance_loss": bool(
|
| 687 |
-
getattr(self, "counterfactual_reliance_loss", False)
|
| 688 |
-
),
|
| 689 |
-
"transition_aware_reliance_loss": bool(
|
| 690 |
-
getattr(self, "transition_aware_reliance_loss", False)
|
| 691 |
-
),
|
| 692 |
-
"counterfactual_step_retention_loss": bool(
|
| 693 |
-
getattr(self, "counterfactual_step_retention_loss", False)
|
| 694 |
-
),
|
| 695 |
-
"crr_concat_aggregation": bool(
|
| 696 |
-
getattr(self, "crr_concat_aggregation", False)
|
| 697 |
-
),
|
| 698 |
-
"crr_policy_rank": bool(getattr(self, "crr_policy_rank", 0)),
|
| 699 |
-
}
|
| 700 |
-
active = [name for name, enabled in incompatible.items() if enabled]
|
| 701 |
-
if active:
|
| 702 |
-
raise ValueError(
|
| 703 |
-
"Qwen3 full-state visual recurrence uses final-answer CE "
|
| 704 |
-
"and final-state decoding only; incompatible with: "
|
| 705 |
-
+ ", ".join(active)
|
| 706 |
-
)
|
| 707 |
-
elif getattr(self, "visual_cumulative_recurrence", False) or getattr(
|
| 708 |
-
self, "visual_full_state_recurrence", False
|
| 709 |
-
):
|
| 710 |
-
raise ValueError(
|
| 711 |
-
"visual cumulative/full-state flags require "
|
| 712 |
-
"visual_counterfactual_recurrence=True."
|
| 713 |
-
)
|
| 714 |
-
if getattr(self, "recurrence_only_adapter", False):
|
| 715 |
-
if not getattr(self, "visual_full_state_recurrence", False):
|
| 716 |
-
raise ValueError(
|
| 717 |
-
"recurrence_only_adapter requires "
|
| 718 |
-
"visual_full_state_recurrence=True."
|
| 719 |
-
)
|
| 720 |
-
if self.adapter_rank <= 0:
|
| 721 |
-
raise ValueError(
|
| 722 |
-
"recurrence_only_adapter requires adapter_rank > 0."
|
| 723 |
-
)
|
| 724 |
-
recurrent_layer = int(self.ell_star) + 1
|
| 725 |
-
if recurrent_layer in set(self.adapter_exclude_layers):
|
| 726 |
-
raise ValueError(
|
| 727 |
-
"recurrence_only_adapter cannot exclude its recurrent "
|
| 728 |
-
f"layer {recurrent_layer} from LoRA."
|
| 729 |
-
)
|
| 730 |
-
modes = [bool(getattr(self, m, False)) for m in
|
| 731 |
-
("read_gating", "read_residual_pure", "final_verify")]
|
| 732 |
-
modes.append(bool(getattr(self, "mid_read_step", 0)))
|
| 733 |
-
if sum(modes) > 1:
|
| 734 |
-
raise ValueError(
|
| 735 |
-
"read_gating / read_residual_pure / final_verify are mutually "
|
| 736 |
-
"exclusive reread modes."
|
| 737 |
-
)
|
| 738 |
-
|
| 739 |
-
|
| 740 |
-
__all__ = ["CloseQwen3VLConfig", "WORKSPACE_ROPE_MODES"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cvrr_release_config.json
DELETED
|
@@ -1,19 +0,0 @@
|
|
| 1 |
-
{
|
| 2 |
-
"name": "CVRR-InternVL3-9B",
|
| 3 |
-
"base_model": "OpenGVLab/InternVL3-9B",
|
| 4 |
-
"ell_star": 35,
|
| 5 |
-
"recurrent_layer": 36,
|
| 6 |
-
"upper_decoder_start": 37,
|
| 7 |
-
"inference_T": 4,
|
| 8 |
-
"inference_beta": 0.33,
|
| 9 |
-
"lora_rank": 32,
|
| 10 |
-
"lora_alpha": 12.0,
|
| 11 |
-
"lora_dropout": 0.01,
|
| 12 |
-
"checkpoint_step": 500,
|
| 13 |
-
"format": "cvrr_native_plus_merged_transition_v1",
|
| 14 |
-
"merged_weight_dtype": "float32",
|
| 15 |
-
"native_weight_bytes": 18277586944,
|
| 16 |
-
"status": "gpu_vstar_comparison_completed",
|
| 17 |
-
"upload_ready": true,
|
| 18 |
-
"hub_model_id": "dmis-lab/InternVL3-9B-CVRR"
|
| 19 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
modeling_cvrr_merged.py
CHANGED
|
@@ -6,7 +6,6 @@ tensors or external backbone download is required after loading the package.
|
|
| 6 |
from __future__ import annotations
|
| 7 |
|
| 8 |
import contextlib
|
| 9 |
-
import json
|
| 10 |
import types
|
| 11 |
from pathlib import Path
|
| 12 |
from typing import TYPE_CHECKING
|
|
@@ -21,16 +20,16 @@ from .configuration_cvrr_merged import CVRRMergedConfig
|
|
| 21 |
# Transformers 4.57's local-directory loader copies direct relative imports;
|
| 22 |
# enumerate transitive helpers without eagerly importing every backbone.
|
| 23 |
if TYPE_CHECKING:
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
from .source_helpers import _layer_hidden
|
| 28 |
from .source_gemma import GemmaCVRR
|
| 29 |
from .source_internvl import InternVLCVRR
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
|
| 35 |
|
| 36 |
class NativeMergedLinear(nn.Module):
|
|
@@ -106,6 +105,108 @@ def install_merged(runtime, weights, *, generic):
|
|
| 106 |
return runtime.eval()
|
| 107 |
|
| 108 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
class CVRRMergedModel(PreTrainedModel):
|
| 110 |
config_class = CVRRMergedConfig
|
| 111 |
main_input_name = 'input_ids'
|
|
@@ -147,17 +248,16 @@ class CVRRMergedModel(PreTrainedModel):
|
|
| 147 |
token=token, local_files_only=offline))
|
| 148 |
if config is None:
|
| 149 |
config = CVRRMergedConfig.from_pretrained(path, local_files_only=True)
|
| 150 |
-
settings =
|
| 151 |
-
if config.release != settings:
|
| 152 |
-
raise ValueError('Root configuration and release metadata disagree')
|
| 153 |
if settings['format'] != 'cvrr_native_plus_merged_transition_v1':
|
| 154 |
raise ValueError('Unsupported release layout')
|
| 155 |
native_path = str(path / 'native_backbone')
|
| 156 |
generic = 'Qwen' not in settings['name']
|
|
|
|
|
|
|
|
|
|
| 157 |
common = dict(ell_star=settings['ell_star'], steps=settings['inference_T'],
|
| 158 |
-
beta=settings['inference_beta'],
|
| 159 |
-
alpha=settings['lora_alpha'], dropout=settings['lora_dropout'],
|
| 160 |
-
device=device, offline=True)
|
| 161 |
if generic:
|
| 162 |
if 'InternVL' in settings['name']:
|
| 163 |
from .source_internvl import InternVLCVRR
|
|
@@ -168,12 +268,12 @@ class CVRRMergedModel(PreTrainedModel):
|
|
| 168 |
else:
|
| 169 |
if 'Qwen2.5' in settings['name']:
|
| 170 |
from transformers import Qwen2_5_VLForConditionalGeneration as Native
|
| 171 |
-
|
| 172 |
-
|
| 173 |
else:
|
| 174 |
from transformers import Qwen3VLForConditionalGeneration as Native
|
| 175 |
-
|
| 176 |
-
|
| 177 |
source_config = SourceConfig.from_pretrained(path / 'source_config', local_files_only=True)
|
| 178 |
source_config.num_workspace_steps = settings['inference_T']
|
| 179 |
source_config.counterfactual_beta = settings['inference_beta']
|
|
@@ -272,7 +372,25 @@ class CVRRMergedModel(PreTrainedModel):
|
|
| 272 |
if do_sample:
|
| 273 |
raise ValueError('Only deterministic greedy decoding is supported by this release')
|
| 274 |
if self.is_generic:
|
| 275 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
return self.runtime.generate_answer(**inputs, max_new_tokens=max_new_tokens)
|
| 277 |
|
| 278 |
|
|
|
|
| 6 |
from __future__ import annotations
|
| 7 |
|
| 8 |
import contextlib
|
|
|
|
| 9 |
import types
|
| 10 |
from pathlib import Path
|
| 11 |
from typing import TYPE_CHECKING
|
|
|
|
| 20 |
# Transformers 4.57's local-directory loader copies direct relative imports;
|
| 21 |
# enumerate transitive helpers without eagerly importing every backbone.
|
| 22 |
if TYPE_CHECKING:
|
| 23 |
+
raise RuntimeError("This release does not bundle source_splitting")
|
| 24 |
+
raise RuntimeError("This release does not bundle source_perceive")
|
| 25 |
+
raise RuntimeError("This release does not bundle source_spatial")
|
| 26 |
from .source_helpers import _layer_hidden
|
| 27 |
from .source_gemma import GemmaCVRR
|
| 28 |
from .source_internvl import InternVLCVRR
|
| 29 |
+
raise RuntimeError("This release does not bundle configuration_source_qwen25")
|
| 30 |
+
raise RuntimeError("This release does not bundle configuration_source_qwen3")
|
| 31 |
+
raise RuntimeError("This release does not bundle modeling_source_qwen25")
|
| 32 |
+
raise RuntimeError("This release does not bundle modeling_source_qwen3")
|
| 33 |
|
| 34 |
|
| 35 |
class NativeMergedLinear(nn.Module):
|
|
|
|
| 105 |
return runtime.eval()
|
| 106 |
|
| 107 |
|
| 108 |
+
def _generic_text_model(runtime):
|
| 109 |
+
return (runtime.base_model.language_model.model if runtime.model_type == 'internvl_chat'
|
| 110 |
+
else runtime.base_model.model)
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def _generic_last_logits(runtime, hidden):
|
| 114 |
+
if runtime.model_type == 'internvl_chat':
|
| 115 |
+
logits = runtime.base_model.language_model.get_output_embeddings()(hidden[:, -1:])
|
| 116 |
+
else:
|
| 117 |
+
logits = runtime.base_model.lm_head(hidden[:, -1:])
|
| 118 |
+
if runtime.model_type in {'gemma4', 'gemma4_unified'}:
|
| 119 |
+
cap = runtime.base_model.config.text_config.final_logit_softcapping
|
| 120 |
+
if cap is not None:
|
| 121 |
+
logits = torch.tanh(logits / cap) * cap
|
| 122 |
+
return logits[:, -1].float()
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
def _replace_layer_hidden(output, hidden):
|
| 126 |
+
if hasattr(output, 'to_tuple'):
|
| 127 |
+
raise TypeError('decoder layer unexpectedly returned a ModelOutput')
|
| 128 |
+
if isinstance(output, tuple):
|
| 129 |
+
return (hidden, *output[1:])
|
| 130 |
+
if isinstance(output, list):
|
| 131 |
+
return [hidden, *output[1:]]
|
| 132 |
+
return hidden
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def _generic_cache_length(cache):
|
| 136 |
+
"""Read text length from either Transformers Cache or a legacy KV tuple."""
|
| 137 |
+
if hasattr(cache, 'get_seq_length'):
|
| 138 |
+
return int(cache.get_seq_length())
|
| 139 |
+
if not isinstance(cache, (tuple, list)) or not cache:
|
| 140 |
+
raise TypeError('strict generation received an unsupported KV cache')
|
| 141 |
+
lengths = []
|
| 142 |
+
for layer in cache:
|
| 143 |
+
if not isinstance(layer, (tuple, list)) or len(layer) < 2:
|
| 144 |
+
raise TypeError('strict generation received a malformed legacy KV cache')
|
| 145 |
+
for tensor in layer[:2]:
|
| 146 |
+
if not torch.is_tensor(tensor) or tensor.ndim < 3:
|
| 147 |
+
raise TypeError('strict generation received a malformed legacy KV tensor')
|
| 148 |
+
lengths.append(int(tensor.shape[-2]))
|
| 149 |
+
if len(set(lengths)) != 1:
|
| 150 |
+
raise RuntimeError('strict generation found inconsistent legacy KV lengths')
|
| 151 |
+
return lengths[0]
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def _generic_strict_prefill(runtime, inputs, trace, recurrent):
|
| 155 |
+
"""Cache only the image-free prompt, splicing R_T above the recurrent cell."""
|
| 156 |
+
if inputs['input_ids'].shape[0] != 1:
|
| 157 |
+
raise ValueError('strict generation currently supports one item at a time')
|
| 158 |
+
ids = inputs['input_ids'][trace.text_rows].unsqueeze(0)
|
| 159 |
+
called = []
|
| 160 |
+
|
| 161 |
+
def splice(_module, _args, output):
|
| 162 |
+
called.append(True)
|
| 163 |
+
return _replace_layer_hidden(output, recurrent)
|
| 164 |
+
|
| 165 |
+
hook = runtime.layers[runtime.cell_index].register_forward_hook(splice)
|
| 166 |
+
try:
|
| 167 |
+
with runtime.adapters(False):
|
| 168 |
+
result = _generic_text_model(runtime)(
|
| 169 |
+
input_ids=ids, attention_mask=torch.ones_like(ids),
|
| 170 |
+
use_cache=True, return_dict=True,
|
| 171 |
+
)
|
| 172 |
+
finally:
|
| 173 |
+
hook.remove()
|
| 174 |
+
if len(called) != 1 or result.past_key_values is None:
|
| 175 |
+
raise RuntimeError('strict text-only prefill did not produce one splice and a KV cache')
|
| 176 |
+
if _generic_cache_length(result.past_key_values) != ids.shape[1]:
|
| 177 |
+
raise RuntimeError('strict text-only KV cache length differs from the image-free prompt')
|
| 178 |
+
return _generic_last_logits(runtime, result.last_hidden_state), result.past_key_values
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def _generic_greedy_tokens(runtime, logits, cache, question_length, *, max_new_tokens, eos_token_ids):
|
| 182 |
+
generated = []
|
| 183 |
+
with runtime.adapters(False):
|
| 184 |
+
for step in range(max_new_tokens):
|
| 185 |
+
if not bool(torch.isfinite(logits).all()):
|
| 186 |
+
raise FloatingPointError('non-finite greedy-generation logits')
|
| 187 |
+
token = logits.argmax(-1).reshape(1, 1)
|
| 188 |
+
generated.append(int(token.item()))
|
| 189 |
+
if generated[-1] in eos_token_ids or step + 1 == max_new_tokens:
|
| 190 |
+
break
|
| 191 |
+
mask = torch.ones((1, question_length + len(generated)), dtype=torch.long, device=token.device)
|
| 192 |
+
output = _generic_text_model(runtime)(
|
| 193 |
+
input_ids=token, attention_mask=mask,
|
| 194 |
+
past_key_values=cache, use_cache=True, return_dict=True,
|
| 195 |
+
)
|
| 196 |
+
cache = output.past_key_values
|
| 197 |
+
if _generic_cache_length(cache) != question_length + len(generated):
|
| 198 |
+
raise RuntimeError('strict text-only KV cache did not advance by one text token')
|
| 199 |
+
logits = _generic_last_logits(runtime, output.last_hidden_state)
|
| 200 |
+
return generated
|
| 201 |
+
|
| 202 |
+
|
| 203 |
+
def _add_eos_ids(target, value):
|
| 204 |
+
if value is None:
|
| 205 |
+
return
|
| 206 |
+
values = value if isinstance(value, (tuple, list, set)) else (value,)
|
| 207 |
+
target.update(int(token) for token in values)
|
| 208 |
+
|
| 209 |
+
|
| 210 |
class CVRRMergedModel(PreTrainedModel):
|
| 211 |
config_class = CVRRMergedConfig
|
| 212 |
main_input_name = 'input_ids'
|
|
|
|
| 248 |
token=token, local_files_only=offline))
|
| 249 |
if config is None:
|
| 250 |
config = CVRRMergedConfig.from_pretrained(path, local_files_only=True)
|
| 251 |
+
settings = config.release
|
|
|
|
|
|
|
| 252 |
if settings['format'] != 'cvrr_native_plus_merged_transition_v1':
|
| 253 |
raise ValueError('Unsupported release layout')
|
| 254 |
native_path = str(path / 'native_backbone')
|
| 255 |
generic = 'Qwen' not in settings['name']
|
| 256 |
+
# The generic constructors retain their internal LoRA defaults only to
|
| 257 |
+
# build temporary projection wrappers. install_merged() replaces those
|
| 258 |
+
# wrappers with the exported dense weights before inference.
|
| 259 |
common = dict(ell_star=settings['ell_star'], steps=settings['inference_T'],
|
| 260 |
+
beta=settings['inference_beta'], device=device, offline=True)
|
|
|
|
|
|
|
| 261 |
if generic:
|
| 262 |
if 'InternVL' in settings['name']:
|
| 263 |
from .source_internvl import InternVLCVRR
|
|
|
|
| 268 |
else:
|
| 269 |
if 'Qwen2.5' in settings['name']:
|
| 270 |
from transformers import Qwen2_5_VLForConditionalGeneration as Native
|
| 271 |
+
raise RuntimeError("This release does not bundle configuration_source_qwen25")
|
| 272 |
+
raise RuntimeError("This release does not bundle modeling_source_qwen25")
|
| 273 |
else:
|
| 274 |
from transformers import Qwen3VLForConditionalGeneration as Native
|
| 275 |
+
raise RuntimeError("This release does not bundle configuration_source_qwen3")
|
| 276 |
+
raise RuntimeError("This release does not bundle modeling_source_qwen3")
|
| 277 |
source_config = SourceConfig.from_pretrained(path / 'source_config', local_files_only=True)
|
| 278 |
source_config.num_workspace_steps = settings['inference_T']
|
| 279 |
source_config.counterfactual_beta = settings['inference_beta']
|
|
|
|
| 372 |
if do_sample:
|
| 373 |
raise ValueError('Only deterministic greedy decoding is supported by this release')
|
| 374 |
if self.is_generic:
|
| 375 |
+
if max_new_tokens < 1:
|
| 376 |
+
raise ValueError('max_new_tokens must be positive')
|
| 377 |
+
explicit_eos = inputs.pop('eos_token_id', None)
|
| 378 |
+
tokenizer = getattr(self, 'tokenizer', getattr(self.runtime, 'tokenizer', None))
|
| 379 |
+
eos = set()
|
| 380 |
+
for source in (getattr(self.runtime.base_model, 'generation_config', None), tokenizer):
|
| 381 |
+
_add_eos_ids(eos, getattr(source, 'eos_token_id', None))
|
| 382 |
+
_add_eos_ids(eos, explicit_eos)
|
| 383 |
+
if self.runtime.model_type == 'internvl_chat':
|
| 384 |
+
sep = self.runtime.base_model.conv_template.sep.strip()
|
| 385 |
+
eos.add(int(self.runtime.tokenizer.convert_tokens_to_ids(sep)))
|
| 386 |
+
trace = self.runtime.extract(inputs)
|
| 387 |
+
recurrent = self.runtime.rollout(trace)[-1]
|
| 388 |
+
logits, cache = _generic_strict_prefill(self.runtime, inputs, trace, recurrent)
|
| 389 |
+
tokens = _generic_greedy_tokens(
|
| 390 |
+
self.runtime, logits, cache, int(trace.question_lengths[0].item()),
|
| 391 |
+
max_new_tokens=max_new_tokens, eos_token_ids=eos,
|
| 392 |
+
)
|
| 393 |
+
return torch.tensor([tokens], dtype=torch.long, device=logits.device)
|
| 394 |
return self.runtime.generate_answer(**inputs, max_new_tokens=max_new_tokens)
|
| 395 |
|
| 396 |
|
modeling_source_qwen25.py
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
modeling_source_qwen3.py
DELETED
|
The diff for this file is too large to render.
See raw diff
|
|
|
source_perceive.py
DELETED
|
@@ -1,414 +0,0 @@
|
|
| 1 |
-
"""Attention layouts for non-recurrent visual latent reasoning chains.
|
| 2 |
-
|
| 3 |
-
The sequence is laid out as::
|
| 4 |
-
|
| 5 |
-
[multimodal prompt ; clean question ; LOOK_1 ; THINK_1 ; ... ; answer]
|
| 6 |
-
|
| 7 |
-
All rows are processed once by the native VLM decoder. A block-sparse causal
|
| 8 |
-
graph makes LOOK rows the only latent rows with access to the multimodal
|
| 9 |
-
prefix, while THINK rows integrate one visual read without directly seeing
|
| 10 |
-
that prefix. Answer rows can see the clean question and THINK rows, but never
|
| 11 |
-
the multimodal prefix or LOOK rows. Consequently the image-dependent answer
|
| 12 |
-
path is
|
| 13 |
-
|
| 14 |
-
image -> LOOK_k -> THINK_k -> answer,
|
| 15 |
-
|
| 16 |
-
without a tied recurrent cell, a visual-state update, or an aggregation
|
| 17 |
-
module.
|
| 18 |
-
|
| 19 |
-
The transition-conditioned variant tightens the graph without adding a
|
| 20 |
-
module. The first LOOK/THINK pair bootstraps from the complete prompt and the
|
| 21 |
-
clean question. Every later pair sees the causal latent prefix, while its
|
| 22 |
-
LOOK row receives only visual placeholder rows from the multimodal prefix.
|
| 23 |
-
Thus later pairs cannot independently re-solve the original image/question
|
| 24 |
-
prompt; their task semantics must arrive through earlier latent states. The
|
| 25 |
-
last THINK is the sole latent exposed to answer rows and therefore acts as the
|
| 26 |
-
native-transformer aggregation state.
|
| 27 |
-
"""
|
| 28 |
-
|
| 29 |
-
from __future__ import annotations
|
| 30 |
-
|
| 31 |
-
from dataclasses import dataclass
|
| 32 |
-
|
| 33 |
-
import torch
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
@dataclass(frozen=True)
|
| 37 |
-
class PerceiveDeliberateLayout:
|
| 38 |
-
"""Physical row layout of one padded training/generation sequence."""
|
| 39 |
-
|
| 40 |
-
multimodal_length: int
|
| 41 |
-
question_length: int
|
| 42 |
-
num_pairs: int
|
| 43 |
-
answer_input_length: int = 0
|
| 44 |
-
|
| 45 |
-
def __post_init__(self) -> None:
|
| 46 |
-
if self.multimodal_length < 1:
|
| 47 |
-
raise ValueError("multimodal_length must be positive")
|
| 48 |
-
if self.question_length < 1:
|
| 49 |
-
raise ValueError("question_length must be positive")
|
| 50 |
-
if self.num_pairs < 1:
|
| 51 |
-
raise ValueError("num_pairs must be positive")
|
| 52 |
-
if self.answer_input_length < 0:
|
| 53 |
-
raise ValueError("answer_input_length must be non-negative")
|
| 54 |
-
|
| 55 |
-
@property
|
| 56 |
-
def num_latent_tokens(self) -> int:
|
| 57 |
-
return 2 * self.num_pairs
|
| 58 |
-
|
| 59 |
-
@property
|
| 60 |
-
def question_start(self) -> int:
|
| 61 |
-
return self.multimodal_length
|
| 62 |
-
|
| 63 |
-
@property
|
| 64 |
-
def latent_start(self) -> int:
|
| 65 |
-
return self.multimodal_length + self.question_length
|
| 66 |
-
|
| 67 |
-
@property
|
| 68 |
-
def answer_start(self) -> int:
|
| 69 |
-
return self.latent_start + self.num_latent_tokens
|
| 70 |
-
|
| 71 |
-
@property
|
| 72 |
-
def sequence_length(self) -> int:
|
| 73 |
-
return self.answer_start + self.answer_input_length
|
| 74 |
-
|
| 75 |
-
@property
|
| 76 |
-
def question_slice(self) -> slice:
|
| 77 |
-
return slice(self.question_start, self.latent_start)
|
| 78 |
-
|
| 79 |
-
@property
|
| 80 |
-
def answer_slice(self) -> slice:
|
| 81 |
-
return slice(self.answer_start, self.sequence_length)
|
| 82 |
-
|
| 83 |
-
@property
|
| 84 |
-
def look_indices(self) -> tuple[int, ...]:
|
| 85 |
-
return tuple(self.latent_start + 2 * index for index in range(self.num_pairs))
|
| 86 |
-
|
| 87 |
-
@property
|
| 88 |
-
def think_indices(self) -> tuple[int, ...]:
|
| 89 |
-
return tuple(index + 1 for index in self.look_indices)
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
def build_perceive_deliberate_mask(
|
| 93 |
-
layout: PerceiveDeliberateLayout,
|
| 94 |
-
multimodal_attention_mask: torch.Tensor,
|
| 95 |
-
question_attention_mask: torch.Tensor,
|
| 96 |
-
answer_input_attention_mask: torch.Tensor | None = None,
|
| 97 |
-
*,
|
| 98 |
-
multimodal_visual_mask: torch.Tensor | None = None,
|
| 99 |
-
local_visual_chain: bool = False,
|
| 100 |
-
transition_conditioned: bool = False,
|
| 101 |
-
question_visible_through_pair: int = 1,
|
| 102 |
-
disable_chain_links: bool = False,
|
| 103 |
-
disable_late_visual: bool = False,
|
| 104 |
-
final_pair_only: bool = False,
|
| 105 |
-
) -> torch.BoolTensor:
|
| 106 |
-
"""Build the strict LOOK/THINK/answer visibility graph.
|
| 107 |
-
|
| 108 |
-
The returned boolean mask has shape ``[B, 1, L, L]`` and follows PyTorch
|
| 109 |
-
SDPA semantics: ``True`` means that the query may attend to the key.
|
| 110 |
-
|
| 111 |
-
In the original graph, LOOK_k sees the valid multimodal prompt, the clean
|
| 112 |
-
question, and (for k>1) only THINK_{k-1}; THINK_k sees the clean question
|
| 113 |
-
and LOOK_k. In the transition-conditioned graph, pair 1 keeps those
|
| 114 |
-
bootstrap inputs, but every later latent sees its complete causal latent
|
| 115 |
-
prefix, later LOOK rows see only visual placeholder keys, and later THINK
|
| 116 |
-
rows normally receive no independent question shortcut. For a controlled
|
| 117 |
-
curriculum/intervention, ``question_visible_through_pair`` may temporarily
|
| 118 |
-
retain the clean-question edge through a later pair; ``1`` is the strict
|
| 119 |
-
inference graph. Answer rows see only the final THINK in that variant.
|
| 120 |
-
Every row also sees itself so padded query
|
| 121 |
-
rows remain numerically defined; padded rows are never exposed as keys to
|
| 122 |
-
semantic rows.
|
| 123 |
-
"""
|
| 124 |
-
|
| 125 |
-
if multimodal_attention_mask.ndim != 2:
|
| 126 |
-
raise ValueError("multimodal_attention_mask must have shape [B, L_mm]")
|
| 127 |
-
if question_attention_mask.ndim != 2:
|
| 128 |
-
raise ValueError("question_attention_mask must have shape [B, L_q]")
|
| 129 |
-
batch_size = multimodal_attention_mask.shape[0]
|
| 130 |
-
expected_mm = (batch_size, layout.multimodal_length)
|
| 131 |
-
expected_q = (batch_size, layout.question_length)
|
| 132 |
-
if tuple(multimodal_attention_mask.shape) != expected_mm:
|
| 133 |
-
raise ValueError(
|
| 134 |
-
"multimodal mask/layout mismatch: "
|
| 135 |
-
f"expected {expected_mm}, got {tuple(multimodal_attention_mask.shape)}"
|
| 136 |
-
)
|
| 137 |
-
if tuple(question_attention_mask.shape) != expected_q:
|
| 138 |
-
raise ValueError(
|
| 139 |
-
"question mask/layout mismatch: "
|
| 140 |
-
f"expected {expected_q}, got {tuple(question_attention_mask.shape)}"
|
| 141 |
-
)
|
| 142 |
-
if layout.answer_input_length:
|
| 143 |
-
if answer_input_attention_mask is None:
|
| 144 |
-
raise ValueError("answer_input_attention_mask is required")
|
| 145 |
-
expected_answer = (batch_size, layout.answer_input_length)
|
| 146 |
-
if tuple(answer_input_attention_mask.shape) != expected_answer:
|
| 147 |
-
raise ValueError(
|
| 148 |
-
"answer mask/layout mismatch: "
|
| 149 |
-
f"expected {expected_answer}, got "
|
| 150 |
-
f"{tuple(answer_input_attention_mask.shape)}"
|
| 151 |
-
)
|
| 152 |
-
elif answer_input_attention_mask is not None and answer_input_attention_mask.numel():
|
| 153 |
-
raise ValueError("received answer mask for an empty answer-input segment")
|
| 154 |
-
|
| 155 |
-
device = multimodal_attention_mask.device
|
| 156 |
-
length = layout.sequence_length
|
| 157 |
-
visible = torch.zeros(
|
| 158 |
-
batch_size, length, length, dtype=torch.bool, device=device
|
| 159 |
-
)
|
| 160 |
-
mm_valid = multimodal_attention_mask.bool()
|
| 161 |
-
q_valid = question_attention_mask.bool()
|
| 162 |
-
visual_valid = None
|
| 163 |
-
if transition_conditioned or local_visual_chain:
|
| 164 |
-
if not 1 <= question_visible_through_pair <= layout.num_pairs:
|
| 165 |
-
raise ValueError(
|
| 166 |
-
"question_visible_through_pair must lie in "
|
| 167 |
-
f"[1,{layout.num_pairs}]"
|
| 168 |
-
)
|
| 169 |
-
if multimodal_visual_mask is None:
|
| 170 |
-
raise ValueError(
|
| 171 |
-
"multimodal_visual_mask is required for the "
|
| 172 |
-
"transition-conditioned graph"
|
| 173 |
-
)
|
| 174 |
-
if tuple(multimodal_visual_mask.shape) != expected_mm:
|
| 175 |
-
raise ValueError(
|
| 176 |
-
"visual mask/layout mismatch: "
|
| 177 |
-
f"expected {expected_mm}, got "
|
| 178 |
-
f"{tuple(multimodal_visual_mask.shape)}"
|
| 179 |
-
)
|
| 180 |
-
visual_valid = multimodal_visual_mask.bool()
|
| 181 |
-
if bool((visual_valid & ~mm_valid).any()):
|
| 182 |
-
raise ValueError("visual keys must be a subset of valid multimodal keys")
|
| 183 |
-
|
| 184 |
-
# The source multimodal prompt keeps the native causal graph. Padded query
|
| 185 |
-
# rows are harmless and receive a self edge below.
|
| 186 |
-
mm_causal = torch.ones(
|
| 187 |
-
layout.multimodal_length,
|
| 188 |
-
layout.multimodal_length,
|
| 189 |
-
dtype=torch.bool,
|
| 190 |
-
device=device,
|
| 191 |
-
).tril()
|
| 192 |
-
visible[:, : layout.multimodal_length, : layout.multimodal_length] = (
|
| 193 |
-
mm_causal.unsqueeze(0) & mm_valid[:, None, :]
|
| 194 |
-
)
|
| 195 |
-
|
| 196 |
-
# The duplicated question is deliberately text-only: it is causally
|
| 197 |
-
# connected only to preceding valid rows of its own segment.
|
| 198 |
-
q_causal = torch.ones(
|
| 199 |
-
layout.question_length,
|
| 200 |
-
layout.question_length,
|
| 201 |
-
dtype=torch.bool,
|
| 202 |
-
device=device,
|
| 203 |
-
).tril()
|
| 204 |
-
visible[
|
| 205 |
-
:, layout.question_slice, layout.question_slice
|
| 206 |
-
] = q_causal.unsqueeze(0) & q_valid[:, None, :]
|
| 207 |
-
|
| 208 |
-
if local_visual_chain:
|
| 209 |
-
# Homogeneous one-pass latent chain. Visual memory is persistent and
|
| 210 |
-
# available at every position, while task state has exactly one local
|
| 211 |
-
# predecessor edge. No latent may consume the complete causal prefix.
|
| 212 |
-
latent_indices = range(layout.latent_start, layout.answer_start)
|
| 213 |
-
for latent_offset, latent in enumerate(latent_indices):
|
| 214 |
-
visible[:, latent, : layout.multimodal_length] = visual_valid
|
| 215 |
-
if latent_offset == 0:
|
| 216 |
-
visible[:, latent, layout.question_slice] = q_valid
|
| 217 |
-
else:
|
| 218 |
-
visible[:, latent, latent - 1] = True
|
| 219 |
-
|
| 220 |
-
if layout.answer_input_length:
|
| 221 |
-
answer_valid = answer_input_attention_mask.bool()
|
| 222 |
-
answer_causal = torch.ones(
|
| 223 |
-
layout.answer_input_length,
|
| 224 |
-
layout.answer_input_length,
|
| 225 |
-
dtype=torch.bool,
|
| 226 |
-
device=device,
|
| 227 |
-
).tril()
|
| 228 |
-
visible[:, layout.answer_slice, layout.question_slice] = q_valid[:, None, :]
|
| 229 |
-
visible[:, layout.answer_slice, layout.answer_start - 1] = True
|
| 230 |
-
visible[:, layout.answer_slice, layout.answer_slice] = (
|
| 231 |
-
answer_causal.unsqueeze(0) & answer_valid[:, None, :]
|
| 232 |
-
)
|
| 233 |
-
|
| 234 |
-
diagonal = torch.arange(length, device=device)
|
| 235 |
-
visible[:, diagonal, diagonal] = True
|
| 236 |
-
return visible.unsqueeze(1)
|
| 237 |
-
|
| 238 |
-
for pair_index, (look, think) in enumerate(
|
| 239 |
-
zip(layout.look_indices, layout.think_indices)
|
| 240 |
-
):
|
| 241 |
-
if final_pair_only and pair_index != layout.num_pairs - 1:
|
| 242 |
-
continue
|
| 243 |
-
|
| 244 |
-
if transition_conditioned and not final_pair_only:
|
| 245 |
-
# Pair 1 bootstraps the causal latent prefix. Later pairs cannot
|
| 246 |
-
# recover the task from the original prompt: only image placeholder
|
| 247 |
-
# rows remain visible outside the latent prefix.
|
| 248 |
-
if pair_index == 0:
|
| 249 |
-
visible[:, look, : layout.multimodal_length] = mm_valid
|
| 250 |
-
visible[:, look, layout.question_slice] = q_valid
|
| 251 |
-
visible[:, think, layout.question_slice] = q_valid
|
| 252 |
-
else:
|
| 253 |
-
if not disable_late_visual:
|
| 254 |
-
visible[:, look, : layout.multimodal_length] = visual_valid
|
| 255 |
-
if not disable_chain_links:
|
| 256 |
-
visible[:, look, layout.latent_start:look] = True
|
| 257 |
-
visible[:, think, layout.latent_start:look] = True
|
| 258 |
-
if pair_index < question_visible_through_pair:
|
| 259 |
-
visible[:, look, layout.question_slice] = q_valid
|
| 260 |
-
visible[:, think, layout.question_slice] = q_valid
|
| 261 |
-
|
| 262 |
-
# The local LOOK -> THINK edge is never an inter-pair chain-link
|
| 263 |
-
# intervention and therefore remains present in every arm.
|
| 264 |
-
visible[:, think, look] = True
|
| 265 |
-
else:
|
| 266 |
-
# Original graph, also used by the final-pair-only capacity
|
| 267 |
-
# control: each active pair may independently consume prompt + Q.
|
| 268 |
-
if not disable_late_visual or pair_index == 0 or final_pair_only:
|
| 269 |
-
visible[:, look, : layout.multimodal_length] = mm_valid
|
| 270 |
-
visible[:, look, layout.question_slice] = q_valid
|
| 271 |
-
if pair_index and not disable_chain_links and not final_pair_only:
|
| 272 |
-
visible[:, look, layout.think_indices[pair_index - 1]] = True
|
| 273 |
-
|
| 274 |
-
# Deliberation: no original multimodal key can be consumed here.
|
| 275 |
-
visible[:, think, layout.question_slice] = q_valid
|
| 276 |
-
visible[:, think, look] = True
|
| 277 |
-
|
| 278 |
-
if layout.answer_input_length:
|
| 279 |
-
answer_valid = answer_input_attention_mask.bool()
|
| 280 |
-
answer_causal = torch.ones(
|
| 281 |
-
layout.answer_input_length,
|
| 282 |
-
layout.answer_input_length,
|
| 283 |
-
dtype=torch.bool,
|
| 284 |
-
device=device,
|
| 285 |
-
).tril()
|
| 286 |
-
visible[:, layout.answer_slice, layout.question_slice] = q_valid[:, None, :]
|
| 287 |
-
answer_thinks = (
|
| 288 |
-
[layout.think_indices[-1]]
|
| 289 |
-
if final_pair_only or transition_conditioned
|
| 290 |
-
else list(layout.think_indices)
|
| 291 |
-
)
|
| 292 |
-
visible[:, layout.answer_slice, answer_thinks] = True
|
| 293 |
-
visible[:, layout.answer_slice, layout.answer_slice] = (
|
| 294 |
-
answer_causal.unsqueeze(0) & answer_valid[:, None, :]
|
| 295 |
-
)
|
| 296 |
-
|
| 297 |
-
# Avoid all-masked softmax rows for physical padding. Semantic queries do
|
| 298 |
-
# not receive padded keys because every segment assignment above uses its
|
| 299 |
-
# validity mask.
|
| 300 |
-
diagonal = torch.arange(length, device=device)
|
| 301 |
-
visible[:, diagonal, diagonal] = True
|
| 302 |
-
return visible.unsqueeze(1)
|
| 303 |
-
|
| 304 |
-
|
| 305 |
-
def build_perceive_deliberate_positions(
|
| 306 |
-
layout: PerceiveDeliberateLayout,
|
| 307 |
-
multimodal_position_ids: torch.LongTensor,
|
| 308 |
-
multimodal_attention_mask: torch.Tensor,
|
| 309 |
-
question_attention_mask: torch.Tensor,
|
| 310 |
-
answer_input_attention_mask: torch.Tensor | None = None,
|
| 311 |
-
) -> torch.LongTensor:
|
| 312 |
-
"""Extend native multimodal M-RoPE with logical text positions.
|
| 313 |
-
|
| 314 |
-
The clean question keeps ordinary relative text positions but is shifted
|
| 315 |
-
after the largest valid multimodal M-RoPE coordinate. Latent and answer
|
| 316 |
-
rows then continue from each item's *logical* question length, independent
|
| 317 |
-
of right padding. All three M-RoPE coordinates are equal for these
|
| 318 |
-
non-spatial rows.
|
| 319 |
-
"""
|
| 320 |
-
|
| 321 |
-
if multimodal_position_ids.ndim != 3 or multimodal_position_ids.shape[0] != 3:
|
| 322 |
-
raise ValueError("multimodal_position_ids must have shape [3, B, L_mm]")
|
| 323 |
-
batch_size = multimodal_attention_mask.shape[0]
|
| 324 |
-
if tuple(multimodal_position_ids.shape[1:]) != (
|
| 325 |
-
batch_size,
|
| 326 |
-
layout.multimodal_length,
|
| 327 |
-
):
|
| 328 |
-
raise ValueError("multimodal position/layout mismatch")
|
| 329 |
-
|
| 330 |
-
mm_valid = multimodal_attention_mask.bool()
|
| 331 |
-
masked_mm = multimodal_position_ids.masked_fill(~mm_valid.unsqueeze(0), -1)
|
| 332 |
-
continuation = masked_mm.amax(dim=(0, 2)).clamp_min(-1) + 1 # [B]
|
| 333 |
-
|
| 334 |
-
q_valid = question_attention_mask.bool()
|
| 335 |
-
q_relative = q_valid.long().cumsum(dim=-1) - 1
|
| 336 |
-
q_relative = q_relative.masked_fill(~q_valid, 0)
|
| 337 |
-
q_positions = continuation[:, None] + q_relative
|
| 338 |
-
q_lengths = q_valid.sum(dim=-1)
|
| 339 |
-
|
| 340 |
-
latent_relative = torch.arange(
|
| 341 |
-
layout.num_latent_tokens,
|
| 342 |
-
device=multimodal_position_ids.device,
|
| 343 |
-
)
|
| 344 |
-
latent_positions = (
|
| 345 |
-
continuation[:, None] + q_lengths[:, None] + latent_relative[None, :]
|
| 346 |
-
)
|
| 347 |
-
|
| 348 |
-
segments = [multimodal_position_ids, q_positions.unsqueeze(0).expand(3, -1, -1)]
|
| 349 |
-
segments.append(latent_positions.unsqueeze(0).expand(3, -1, -1))
|
| 350 |
-
if layout.answer_input_length:
|
| 351 |
-
if answer_input_attention_mask is None:
|
| 352 |
-
raise ValueError("answer_input_attention_mask is required")
|
| 353 |
-
answer_relative = torch.arange(
|
| 354 |
-
layout.answer_input_length,
|
| 355 |
-
device=multimodal_position_ids.device,
|
| 356 |
-
)
|
| 357 |
-
answer_positions = (
|
| 358 |
-
continuation[:, None]
|
| 359 |
-
+ q_lengths[:, None]
|
| 360 |
-
+ layout.num_latent_tokens
|
| 361 |
-
+ answer_relative[None, :]
|
| 362 |
-
)
|
| 363 |
-
segments.append(answer_positions.unsqueeze(0).expand(3, -1, -1))
|
| 364 |
-
return torch.cat(segments, dim=-1)
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
def build_answer_decode_mask(
|
| 368 |
-
layout: PerceiveDeliberateLayout,
|
| 369 |
-
question_attention_mask: torch.Tensor,
|
| 370 |
-
generated_attention_mask: torch.Tensor,
|
| 371 |
-
*,
|
| 372 |
-
local_visual_chain: bool = False,
|
| 373 |
-
final_pair_only: bool = False,
|
| 374 |
-
transition_conditioned: bool = False,
|
| 375 |
-
) -> torch.BoolTensor:
|
| 376 |
-
"""Visibility for one cached answer query after the latent prefill.
|
| 377 |
-
|
| 378 |
-
``generated_attention_mask`` includes the current answer token. Physical
|
| 379 |
-
multimodal and LOOK cache rows remain present but are invisible.
|
| 380 |
-
"""
|
| 381 |
-
|
| 382 |
-
if generated_attention_mask.ndim != 2:
|
| 383 |
-
raise ValueError("generated_attention_mask must have shape [B, N]")
|
| 384 |
-
batch_size, generated_length = generated_attention_mask.shape
|
| 385 |
-
if tuple(question_attention_mask.shape) != (
|
| 386 |
-
batch_size,
|
| 387 |
-
layout.question_length,
|
| 388 |
-
):
|
| 389 |
-
raise ValueError("question mask/layout mismatch")
|
| 390 |
-
total_keys = layout.answer_start + generated_length
|
| 391 |
-
visible = torch.zeros(
|
| 392 |
-
batch_size, 1, 1, total_keys, dtype=torch.bool,
|
| 393 |
-
device=question_attention_mask.device,
|
| 394 |
-
)
|
| 395 |
-
visible[:, 0, 0, layout.question_slice] = question_attention_mask.bool()
|
| 396 |
-
if local_visual_chain:
|
| 397 |
-
visible[:, 0, 0, layout.answer_start - 1] = True
|
| 398 |
-
else:
|
| 399 |
-
answer_thinks = (
|
| 400 |
-
[layout.think_indices[-1]]
|
| 401 |
-
if final_pair_only or transition_conditioned
|
| 402 |
-
else list(layout.think_indices)
|
| 403 |
-
)
|
| 404 |
-
visible[:, 0, 0, answer_thinks] = True
|
| 405 |
-
visible[:, 0, 0, layout.answer_start:] = generated_attention_mask.bool()
|
| 406 |
-
return visible
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
__all__ = [
|
| 410 |
-
"PerceiveDeliberateLayout",
|
| 411 |
-
"build_answer_decode_mask",
|
| 412 |
-
"build_perceive_deliberate_mask",
|
| 413 |
-
"build_perceive_deliberate_positions",
|
| 414 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
source_spatial.py
DELETED
|
@@ -1,349 +0,0 @@
|
|
| 1 |
-
"""Question-conditioned recurrence over the complete native visual field.
|
| 2 |
-
|
| 3 |
-
The state is the dynamic-resolution sequence emitted by Qwen2.5-VL's vision
|
| 4 |
-
merger, before those image embeddings enter the language model. Tokens keep
|
| 5 |
-
their native two-dimensional grid throughout the recurrence. One shared cell
|
| 6 |
-
combines directional local messages with visual-to-question cross-attention::
|
| 7 |
-
|
| 8 |
-
V_0 = VisionMerger(image)
|
| 9 |
-
V_{k+1} = V_k + Cell(V_k, question, grid), k = 0, ..., T - 1
|
| 10 |
-
|
| 11 |
-
Only ``V_T`` is inserted into the ordinary Qwen multimodal prefix. The cell's
|
| 12 |
-
output projection is exactly zero at initialization, so every horizon,
|
| 13 |
-
including the default T=8, is initially bitwise identical to the base visual
|
| 14 |
-
embedding path. The full language model is then run from scratch; no cache
|
| 15 |
-
created while constructing the visual field is available to answer decoding.
|
| 16 |
-
"""
|
| 17 |
-
|
| 18 |
-
from __future__ import annotations
|
| 19 |
-
|
| 20 |
-
from dataclasses import dataclass
|
| 21 |
-
|
| 22 |
-
import torch
|
| 23 |
-
import torch.nn as nn
|
| 24 |
-
import torch.nn.functional as F
|
| 25 |
-
|
| 26 |
-
from transformers.models.qwen2.modeling_qwen2 import Qwen2RMSNorm
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
@dataclass
|
| 30 |
-
class SpatialVisualTelemetry:
|
| 31 |
-
"""Detached recurrence diagnostics; no visual trajectory is retained."""
|
| 32 |
-
|
| 33 |
-
update_rms: torch.Tensor # [T]
|
| 34 |
-
update_relative: torch.Tensor # [T]
|
| 35 |
-
step_drift: torch.Tensor # [T], 1 - cos(V_k, V_{k+1})
|
| 36 |
-
final_relative: torch.Tensor # scalar, RMS(V_T - V_0) / RMS(V_0)
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
class SpatialVisualRecurrentCell(nn.Module):
|
| 40 |
-
"""Shared O(N) spatial cell for packed, variable-resolution image tokens.
|
| 41 |
-
|
| 42 |
-
Global N-by-N visual attention is deliberately absent: a sample may carry
|
| 43 |
-
8192 merged visual tokens. Four directional grid messages preserve local
|
| 44 |
-
geometry in linear time, while low-rank visual-to-question attention makes
|
| 45 |
-
every patch update task dependent. The recurrent *state* remains at the
|
| 46 |
-
native language-model width; ``inner_width`` limits only the update
|
| 47 |
-
operator's compute and is not a state bottleneck.
|
| 48 |
-
"""
|
| 49 |
-
|
| 50 |
-
def __init__(
|
| 51 |
-
self,
|
| 52 |
-
width: int,
|
| 53 |
-
inner_width: int,
|
| 54 |
-
num_heads: int,
|
| 55 |
-
*,
|
| 56 |
-
rms_norm_eps: float,
|
| 57 |
-
residual_scale: float,
|
| 58 |
-
) -> None:
|
| 59 |
-
super().__init__()
|
| 60 |
-
if width < 1 or inner_width < 1:
|
| 61 |
-
raise ValueError("width and inner_width must be positive")
|
| 62 |
-
if num_heads < 1 or inner_width % num_heads:
|
| 63 |
-
raise ValueError(
|
| 64 |
-
"inner_width must be divisible by the positive num_heads"
|
| 65 |
-
)
|
| 66 |
-
if not 0.0 <= residual_scale <= 1.0:
|
| 67 |
-
raise ValueError("residual_scale must lie in [0, 1]")
|
| 68 |
-
self.width = int(width)
|
| 69 |
-
self.inner_width = int(inner_width)
|
| 70 |
-
self.num_heads = int(num_heads)
|
| 71 |
-
self.head_dim = self.inner_width // self.num_heads
|
| 72 |
-
self.residual_scale = float(residual_scale)
|
| 73 |
-
|
| 74 |
-
self.state_norm = Qwen2RMSNorm(self.width, eps=rms_norm_eps)
|
| 75 |
-
self.question_norm = Qwen2RMSNorm(self.width, eps=rms_norm_eps)
|
| 76 |
-
|
| 77 |
-
# One projection contains distinct north/south/west/east maps. This
|
| 78 |
-
# keeps orientation identifiable without allocating a dense 3x3 dxd
|
| 79 |
-
# convolution over the 3584-wide native visual state.
|
| 80 |
-
self.center_proj = nn.Linear(self.width, self.inner_width, bias=False)
|
| 81 |
-
self.neighbor_proj = nn.Linear(
|
| 82 |
-
self.width, 4 * self.inner_width, bias=False
|
| 83 |
-
)
|
| 84 |
-
|
| 85 |
-
self.query_proj = nn.Linear(self.width, self.inner_width, bias=False)
|
| 86 |
-
self.key_proj = nn.Linear(self.width, self.inner_width, bias=False)
|
| 87 |
-
self.value_proj = nn.Linear(self.width, self.inner_width, bias=False)
|
| 88 |
-
self.output_proj = nn.Linear(self.inner_width, self.width, bias=False)
|
| 89 |
-
|
| 90 |
-
def zero_output(self) -> None:
|
| 91 |
-
"""Restore the exact base-model identity after generic HF init."""
|
| 92 |
-
with torch.no_grad():
|
| 93 |
-
self.output_proj.weight.zero_()
|
| 94 |
-
|
| 95 |
-
@staticmethod
|
| 96 |
-
def _token_layout(
|
| 97 |
-
merged_grid_thw: torch.LongTensor,
|
| 98 |
-
image_to_batch: torch.LongTensor,
|
| 99 |
-
batch_size: int,
|
| 100 |
-
expected_tokens: int,
|
| 101 |
-
) -> tuple[torch.LongTensor, torch.LongTensor, torch.LongTensor]:
|
| 102 |
-
"""Return token-to-batch ids, within-sample positions and counts."""
|
| 103 |
-
if merged_grid_thw.ndim != 2 or merged_grid_thw.shape[-1] != 3:
|
| 104 |
-
raise ValueError("merged_grid_thw must have shape [num_images, 3]")
|
| 105 |
-
if image_to_batch.ndim != 1 or image_to_batch.shape[0] != len(
|
| 106 |
-
merged_grid_thw
|
| 107 |
-
):
|
| 108 |
-
raise ValueError("image_to_batch must contain one id per image")
|
| 109 |
-
per_image = merged_grid_thw.prod(dim=-1).long()
|
| 110 |
-
if int(per_image.sum()) != int(expected_tokens):
|
| 111 |
-
raise ValueError(
|
| 112 |
-
"merged grids and visual field disagree: "
|
| 113 |
-
f"grid tokens={int(per_image.sum())}, state tokens={expected_tokens}"
|
| 114 |
-
)
|
| 115 |
-
token_to_batch = torch.repeat_interleave(image_to_batch, per_image)
|
| 116 |
-
if token_to_batch.numel() and (
|
| 117 |
-
int(token_to_batch.min()) < 0
|
| 118 |
-
or int(token_to_batch.max()) >= batch_size
|
| 119 |
-
):
|
| 120 |
-
raise ValueError("image_to_batch contains an out-of-range batch id")
|
| 121 |
-
if token_to_batch.numel() > 1 and bool(
|
| 122 |
-
(token_to_batch[1:] < token_to_batch[:-1]).any()
|
| 123 |
-
):
|
| 124 |
-
raise ValueError(
|
| 125 |
-
"images must follow the same batch-major order as Qwen inputs"
|
| 126 |
-
)
|
| 127 |
-
counts = torch.bincount(token_to_batch, minlength=batch_size)
|
| 128 |
-
starts = counts.cumsum(0) - counts
|
| 129 |
-
within = torch.arange(
|
| 130 |
-
expected_tokens, device=token_to_batch.device
|
| 131 |
-
) - torch.repeat_interleave(starts, counts)
|
| 132 |
-
return token_to_batch, within, counts
|
| 133 |
-
|
| 134 |
-
@staticmethod
|
| 135 |
-
def _neighbor_edges(
|
| 136 |
-
merged_grid_thw: torch.LongTensor,
|
| 137 |
-
expected_tokens: int,
|
| 138 |
-
) -> tuple[torch.LongTensor, torch.LongTensor, torch.LongTensor]:
|
| 139 |
-
"""Build packed directed 4-neighbour edges once for all recurrent steps.
|
| 140 |
-
|
| 141 |
-
Direction ids are 0/1/2/3 = north/south/west/east *as seen by the
|
| 142 |
-
destination token*. Temporal slices are separate 2-D fields.
|
| 143 |
-
"""
|
| 144 |
-
device = merged_grid_thw.device
|
| 145 |
-
destinations: list[torch.Tensor] = []
|
| 146 |
-
sources: list[torch.Tensor] = []
|
| 147 |
-
directions: list[torch.Tensor] = []
|
| 148 |
-
offset = 0
|
| 149 |
-
for t_value, h_value, w_value in merged_grid_thw.detach().cpu().tolist():
|
| 150 |
-
t, h, w = int(t_value), int(h_value), int(w_value)
|
| 151 |
-
count = t * h * w
|
| 152 |
-
if min(t, h, w) < 1:
|
| 153 |
-
raise ValueError(f"invalid merged visual grid {(t, h, w)}")
|
| 154 |
-
index = torch.arange(
|
| 155 |
-
offset, offset + count, device=device, dtype=torch.long
|
| 156 |
-
).view(t, h, w)
|
| 157 |
-
|
| 158 |
-
def append(dst: torch.Tensor, src: torch.Tensor, direction: int) -> None:
|
| 159 |
-
if dst.numel() == 0:
|
| 160 |
-
return
|
| 161 |
-
destinations.append(dst.reshape(-1))
|
| 162 |
-
sources.append(src.reshape(-1))
|
| 163 |
-
directions.append(
|
| 164 |
-
torch.full(
|
| 165 |
-
(dst.numel(),), direction, device=device, dtype=torch.long
|
| 166 |
-
)
|
| 167 |
-
)
|
| 168 |
-
|
| 169 |
-
append(index[:, 1:, :], index[:, :-1, :], 0) # north
|
| 170 |
-
append(index[:, :-1, :], index[:, 1:, :], 1) # south
|
| 171 |
-
append(index[:, :, 1:], index[:, :, :-1], 2) # west
|
| 172 |
-
append(index[:, :, :-1], index[:, :, 1:], 3) # east
|
| 173 |
-
offset += count
|
| 174 |
-
if offset != expected_tokens:
|
| 175 |
-
raise ValueError(
|
| 176 |
-
f"edge grids contain {offset} tokens, expected {expected_tokens}"
|
| 177 |
-
)
|
| 178 |
-
if not destinations:
|
| 179 |
-
empty = torch.empty(0, device=device, dtype=torch.long)
|
| 180 |
-
return empty, empty, empty
|
| 181 |
-
return (
|
| 182 |
-
torch.cat(destinations),
|
| 183 |
-
torch.cat(sources),
|
| 184 |
-
torch.cat(directions),
|
| 185 |
-
)
|
| 186 |
-
|
| 187 |
-
def _question_attention(
|
| 188 |
-
self,
|
| 189 |
-
normalized_state: torch.Tensor,
|
| 190 |
-
question_keys: torch.Tensor,
|
| 191 |
-
question_values: torch.Tensor,
|
| 192 |
-
question_valid: torch.BoolTensor,
|
| 193 |
-
token_to_batch: torch.LongTensor,
|
| 194 |
-
within_sample: torch.LongTensor,
|
| 195 |
-
token_counts: torch.LongTensor,
|
| 196 |
-
) -> torch.Tensor:
|
| 197 |
-
"""Visual-query/text-memory SDPA for a packed visual sequence."""
|
| 198 |
-
batch_size = question_keys.shape[0]
|
| 199 |
-
max_tokens = int(token_counts.max()) if token_counts.numel() else 0
|
| 200 |
-
if max_tokens == 0:
|
| 201 |
-
return normalized_state.new_empty(0, self.inner_width)
|
| 202 |
-
|
| 203 |
-
query_flat = self.query_proj(normalized_state)
|
| 204 |
-
query = query_flat.new_zeros(
|
| 205 |
-
batch_size, max_tokens, self.inner_width
|
| 206 |
-
)
|
| 207 |
-
query[token_to_batch, within_sample] = query_flat
|
| 208 |
-
|
| 209 |
-
bsz, question_length, _ = question_keys.shape
|
| 210 |
-
query = query.view(
|
| 211 |
-
bsz, max_tokens, self.num_heads, self.head_dim
|
| 212 |
-
).transpose(1, 2)
|
| 213 |
-
key = question_keys.view(
|
| 214 |
-
bsz, question_length, self.num_heads, self.head_dim
|
| 215 |
-
).transpose(1, 2)
|
| 216 |
-
value = question_values.view(
|
| 217 |
-
bsz, question_length, self.num_heads, self.head_dim
|
| 218 |
-
).transpose(1, 2)
|
| 219 |
-
# Boolean SDPA masks use True for entries that are allowed to attend.
|
| 220 |
-
allowed = question_valid[:, None, None, :]
|
| 221 |
-
attended = F.scaled_dot_product_attention(
|
| 222 |
-
query,
|
| 223 |
-
key,
|
| 224 |
-
value,
|
| 225 |
-
attn_mask=allowed,
|
| 226 |
-
dropout_p=0.0,
|
| 227 |
-
is_causal=False,
|
| 228 |
-
)
|
| 229 |
-
attended = attended.transpose(1, 2).reshape(
|
| 230 |
-
bsz, max_tokens, self.inner_width
|
| 231 |
-
)
|
| 232 |
-
return attended[token_to_batch, within_sample]
|
| 233 |
-
|
| 234 |
-
def forward(
|
| 235 |
-
self,
|
| 236 |
-
visual_state: torch.Tensor,
|
| 237 |
-
merged_grid_thw: torch.LongTensor,
|
| 238 |
-
image_to_batch: torch.LongTensor,
|
| 239 |
-
question_embeddings: torch.Tensor,
|
| 240 |
-
question_attention_mask: torch.Tensor | None,
|
| 241 |
-
*,
|
| 242 |
-
steps: int,
|
| 243 |
-
) -> tuple[torch.Tensor, SpatialVisualTelemetry]:
|
| 244 |
-
if visual_state.ndim != 2 or visual_state.shape[-1] != self.width:
|
| 245 |
-
raise ValueError(
|
| 246 |
-
f"visual_state must be [N,{self.width}], got "
|
| 247 |
-
f"{tuple(visual_state.shape)}"
|
| 248 |
-
)
|
| 249 |
-
if question_embeddings.ndim != 3 or question_embeddings.shape[-1] != self.width:
|
| 250 |
-
raise ValueError(
|
| 251 |
-
f"question_embeddings must be [B,Q,{self.width}]"
|
| 252 |
-
)
|
| 253 |
-
if steps < 0:
|
| 254 |
-
raise ValueError("steps must be non-negative")
|
| 255 |
-
batch_size = question_embeddings.shape[0]
|
| 256 |
-
token_to_batch, within_sample, token_counts = self._token_layout(
|
| 257 |
-
merged_grid_thw,
|
| 258 |
-
image_to_batch,
|
| 259 |
-
batch_size,
|
| 260 |
-
visual_state.shape[0],
|
| 261 |
-
)
|
| 262 |
-
edge_dst, edge_src, edge_direction = self._neighbor_edges(
|
| 263 |
-
merged_grid_thw, visual_state.shape[0]
|
| 264 |
-
)
|
| 265 |
-
|
| 266 |
-
if question_attention_mask is None:
|
| 267 |
-
question_valid = torch.ones(
|
| 268 |
-
question_embeddings.shape[:2],
|
| 269 |
-
dtype=torch.bool,
|
| 270 |
-
device=question_embeddings.device,
|
| 271 |
-
)
|
| 272 |
-
else:
|
| 273 |
-
if question_attention_mask.shape != question_embeddings.shape[:2]:
|
| 274 |
-
raise ValueError("question attention mask shape mismatch")
|
| 275 |
-
question_valid = question_attention_mask > 0
|
| 276 |
-
if not bool(question_valid.any(dim=-1).all()):
|
| 277 |
-
raise ValueError("every image-bearing sample needs a question token")
|
| 278 |
-
|
| 279 |
-
normalized_question = self.question_norm(question_embeddings)
|
| 280 |
-
question_keys = self.key_proj(normalized_question)
|
| 281 |
-
question_values = self.value_proj(normalized_question)
|
| 282 |
-
|
| 283 |
-
initial = visual_state
|
| 284 |
-
state = visual_state
|
| 285 |
-
update_rms: list[torch.Tensor] = []
|
| 286 |
-
update_relative: list[torch.Tensor] = []
|
| 287 |
-
step_drift: list[torch.Tensor] = []
|
| 288 |
-
for _ in range(steps):
|
| 289 |
-
normalized = self.state_norm(state)
|
| 290 |
-
center = self.center_proj(normalized)
|
| 291 |
-
directional = self.neighbor_proj(normalized).view(
|
| 292 |
-
state.shape[0], 4, self.inner_width
|
| 293 |
-
)
|
| 294 |
-
spatial = center.new_zeros(center.shape)
|
| 295 |
-
degree = center.new_zeros(center.shape[0], 1)
|
| 296 |
-
if edge_dst.numel():
|
| 297 |
-
messages = directional[edge_src, edge_direction]
|
| 298 |
-
spatial.index_add_(0, edge_dst, messages)
|
| 299 |
-
degree.index_add_(
|
| 300 |
-
0,
|
| 301 |
-
edge_dst,
|
| 302 |
-
torch.ones(
|
| 303 |
-
edge_dst.shape[0], 1, dtype=center.dtype, device=center.device
|
| 304 |
-
),
|
| 305 |
-
)
|
| 306 |
-
spatial = spatial / degree.clamp_min(1.0)
|
| 307 |
-
question = self._question_attention(
|
| 308 |
-
normalized,
|
| 309 |
-
question_keys,
|
| 310 |
-
question_values,
|
| 311 |
-
question_valid,
|
| 312 |
-
token_to_batch,
|
| 313 |
-
within_sample,
|
| 314 |
-
token_counts,
|
| 315 |
-
)
|
| 316 |
-
mixed = F.silu((center + spatial + question) / (3.0**0.5))
|
| 317 |
-
update = self.output_proj(mixed) * self.residual_scale
|
| 318 |
-
next_state = state + update
|
| 319 |
-
|
| 320 |
-
with torch.no_grad():
|
| 321 |
-
state_rms = state.float().square().mean().sqrt().clamp_min(1e-8)
|
| 322 |
-
update_norm = update.float().square().mean().sqrt()
|
| 323 |
-
cosine = F.cosine_similarity(
|
| 324 |
-
state.float(), next_state.float(), dim=-1
|
| 325 |
-
).mean()
|
| 326 |
-
update_rms.append(update_norm.detach())
|
| 327 |
-
update_relative.append((update_norm / state_rms).detach())
|
| 328 |
-
step_drift.append((1.0 - cosine).detach())
|
| 329 |
-
state = next_state
|
| 330 |
-
|
| 331 |
-
with torch.no_grad():
|
| 332 |
-
initial_rms = initial.float().square().mean().sqrt().clamp_min(1e-8)
|
| 333 |
-
final_relative = (
|
| 334 |
-
(state.float() - initial.float()).square().mean().sqrt()
|
| 335 |
-
/ initial_rms
|
| 336 |
-
).detach()
|
| 337 |
-
empty = initial.new_empty(0, dtype=torch.float32)
|
| 338 |
-
telemetry = SpatialVisualTelemetry(
|
| 339 |
-
update_rms=(torch.stack(update_rms) if update_rms else empty),
|
| 340 |
-
update_relative=(
|
| 341 |
-
torch.stack(update_relative) if update_relative else empty
|
| 342 |
-
),
|
| 343 |
-
step_drift=(torch.stack(step_drift) if step_drift else empty),
|
| 344 |
-
final_relative=final_relative,
|
| 345 |
-
)
|
| 346 |
-
return state, telemetry
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
__all__ = ["SpatialVisualRecurrentCell", "SpatialVisualTelemetry"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
source_splitting.py
DELETED
|
@@ -1,473 +0,0 @@
|
|
| 1 |
-
"""Layer-split forward for Qwen2.5-VL and Qwen3-VL.
|
| 2 |
-
|
| 3 |
-
Method §3.2 needs ``F = F_{>l*} o F_{<=l*}`` as two separately runnable halves so
|
| 4 |
-
that the multimodal branch can be cut off at ``l*`` and replaced by the
|
| 5 |
-
workspace. §3.1 needs the same split to read and patch activations at a chosen
|
| 6 |
-
depth.
|
| 7 |
-
|
| 8 |
-
This module reimplements the prologue of the native Qwen VL text-model forward
|
| 9 |
-
-- embedding merge, M-RoPE index, causal mask, rotary embeddings -- as a
|
| 10 |
-
reusable :class:`SplitContext`, then exposes the decoder-layer loop as a range
|
| 11 |
-
you can run piecewise. Qwen3-VL additionally injects three ``DeepStack``
|
| 12 |
-
vision features after language layers 0--2; those tensors are carried in the
|
| 13 |
-
split context and applied at the identical layer boundaries. It deliberately
|
| 14 |
-
mirrors transformers 4.57.6 rather than monkeypatching it;
|
| 15 |
-
``tests/test_split_equivalence.py`` asserts the composed halves reproduce the
|
| 16 |
-
stock forward bit-for-bit, which is what makes the mirroring safe to rely on.
|
| 17 |
-
|
| 18 |
-
Shapes use ``B`` batch, ``L`` sequence, ``d`` backbone width (3584 on the 7B),
|
| 19 |
-
``N_v`` visual tokens, ``N_q`` question tokens.
|
| 20 |
-
"""
|
| 21 |
-
|
| 22 |
-
from __future__ import annotations
|
| 23 |
-
|
| 24 |
-
from dataclasses import dataclass, replace
|
| 25 |
-
from typing import Any
|
| 26 |
-
|
| 27 |
-
import torch
|
| 28 |
-
from transformers.cache_utils import Cache
|
| 29 |
-
from transformers.masking_utils import (
|
| 30 |
-
create_causal_mask,
|
| 31 |
-
create_sliding_window_causal_mask,
|
| 32 |
-
)
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
@dataclass
|
| 36 |
-
class SplitContext:
|
| 37 |
-
"""Per-forward state shared by every decoder layer.
|
| 38 |
-
|
| 39 |
-
Computed once by :func:`make_split_context` so that layer ranges can be run
|
| 40 |
-
independently without recomputing masks or rotary tables.
|
| 41 |
-
"""
|
| 42 |
-
|
| 43 |
-
hidden_states: torch.Tensor # [B, L, d] - mutated as layers run
|
| 44 |
-
position_ids: torch.Tensor # [3, B, L] - M-RoPE (t, h, w)
|
| 45 |
-
position_embeddings: tuple[torch.Tensor, torch.Tensor] # (cos, sin) [B, L, head_dim]
|
| 46 |
-
causal_mask_mapping: dict[str, torch.Tensor | None]
|
| 47 |
-
cache_position: torch.Tensor # [L]
|
| 48 |
-
text_position_ids: torch.Tensor | None # [B, L] only when packed
|
| 49 |
-
past_key_values: Cache | None
|
| 50 |
-
# Original 2-D key-padding mask. ``create_causal_mask`` is allowed to
|
| 51 |
-
# return ``None`` for SDPA and delegate causality to ``is_causal``; keeping
|
| 52 |
-
# this tensor lets counterfactual branches materialize the equivalent mask
|
| 53 |
-
# before removing a precisely selected set of attention edges.
|
| 54 |
-
attention_mask: torch.Tensor | None = None # [B, L_kv]
|
| 55 |
-
# Qwen3-VL only. DeepStack adds one visual feature tensor after each of
|
| 56 |
-
# the first three language layers. They stay ``None`` for Qwen2.5-VL and
|
| 57 |
-
# for every text-only branch.
|
| 58 |
-
visual_pos_masks: torch.Tensor | None = None # [B, L] bool
|
| 59 |
-
deepstack_visual_embeds: list[torch.Tensor] | None = None
|
| 60 |
-
|
| 61 |
-
def clone_at(self, hidden_states: torch.Tensor) -> "SplitContext":
|
| 62 |
-
"""Same context, different hidden states (for patched re-runs)."""
|
| 63 |
-
return SplitContext(
|
| 64 |
-
hidden_states=hidden_states,
|
| 65 |
-
position_ids=self.position_ids,
|
| 66 |
-
position_embeddings=self.position_embeddings,
|
| 67 |
-
causal_mask_mapping=self.causal_mask_mapping,
|
| 68 |
-
cache_position=self.cache_position,
|
| 69 |
-
text_position_ids=self.text_position_ids,
|
| 70 |
-
past_key_values=self.past_key_values,
|
| 71 |
-
attention_mask=self.attention_mask,
|
| 72 |
-
visual_pos_masks=self.visual_pos_masks,
|
| 73 |
-
deepstack_visual_embeds=self.deepstack_visual_embeds,
|
| 74 |
-
)
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
# ---------------------------------------------------------------------------
|
| 78 |
-
# embedding / position construction
|
| 79 |
-
# ---------------------------------------------------------------------------
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
def embed_multimodal(
|
| 83 |
-
vl_model,
|
| 84 |
-
input_ids: torch.LongTensor, # [B, L]
|
| 85 |
-
pixel_values: torch.Tensor | None = None,
|
| 86 |
-
image_grid_thw: torch.LongTensor | None = None,
|
| 87 |
-
attention_mask: torch.Tensor | None = None,
|
| 88 |
-
*,
|
| 89 |
-
return_deepstack: bool = False,
|
| 90 |
-
) -> (
|
| 91 |
-
tuple[torch.Tensor, torch.Tensor]
|
| 92 |
-
| tuple[
|
| 93 |
-
torch.Tensor,
|
| 94 |
-
torch.Tensor,
|
| 95 |
-
torch.Tensor | None,
|
| 96 |
-
list[torch.Tensor] | None,
|
| 97 |
-
]
|
| 98 |
-
):
|
| 99 |
-
"""Token embeddings with image features scattered in, plus M-RoPE indices.
|
| 100 |
-
|
| 101 |
-
Mirrors the prefill path of ``Qwen2_5_VLModel.forward``. Pass
|
| 102 |
-
``pixel_values=None`` to get the text-only branch used for ``Q*``.
|
| 103 |
-
|
| 104 |
-
Args:
|
| 105 |
-
vl_model: a native ``Qwen2_5_VLModel`` or ``Qwen3VLModel`` (i.e.
|
| 106 |
-
``model.model``, not the ``...ForConditionalGeneration`` wrapper).
|
| 107 |
-
return_deepstack: also return Qwen3-VL's visual-position mask and
|
| 108 |
-
DeepStack features. The default two-value return keeps all
|
| 109 |
-
Qwen2.5 callers backward compatible.
|
| 110 |
-
|
| 111 |
-
Returns:
|
| 112 |
-
``(inputs_embeds [B, L, d], position_ids [3, B, L])`` and optionally
|
| 113 |
-
``(visual_pos_masks, deepstack_visual_embeds)``.
|
| 114 |
-
"""
|
| 115 |
-
inputs_embeds = vl_model.get_input_embeddings()(input_ids) # [B, L, d]
|
| 116 |
-
model_type = str(getattr(vl_model.config, "model_type", ""))
|
| 117 |
-
# A freshly wrapped model exposes the native qwen3_vl config here.
|
| 118 |
-
# Reloading a fully saved CLOSE checkpoint reconstructs the nested native
|
| 119 |
-
# backbone from the wrapper config, so its model view carries
|
| 120 |
-
# close_qwen3_vl instead. Both use the tuple-returning Qwen3 feature API.
|
| 121 |
-
is_qwen3_vl = model_type in {"qwen3_vl", "close_qwen3_vl"}
|
| 122 |
-
visual_pos_masks = None
|
| 123 |
-
deepstack_visual_embeds = None
|
| 124 |
-
|
| 125 |
-
if pixel_values is not None:
|
| 126 |
-
image_features = vl_model.get_image_features(pixel_values, image_grid_thw)
|
| 127 |
-
if is_qwen3_vl:
|
| 128 |
-
image_embeds, deepstack_visual_embeds = image_features
|
| 129 |
-
else:
|
| 130 |
-
image_embeds = image_features
|
| 131 |
-
image_embeds = torch.cat(image_embeds, dim=0).to(
|
| 132 |
-
inputs_embeds.device, inputs_embeds.dtype
|
| 133 |
-
) # [N_v_total, d]
|
| 134 |
-
image_mask, _ = vl_model.get_placeholder_mask(
|
| 135 |
-
input_ids, inputs_embeds=inputs_embeds, image_features=image_embeds
|
| 136 |
-
)
|
| 137 |
-
inputs_embeds = inputs_embeds.masked_scatter(image_mask, image_embeds)
|
| 138 |
-
if is_qwen3_vl:
|
| 139 |
-
visual_pos_masks = image_mask[..., 0]
|
| 140 |
-
|
| 141 |
-
if is_qwen3_vl:
|
| 142 |
-
position_ids, _ = vl_model.get_rope_index(
|
| 143 |
-
input_ids,
|
| 144 |
-
image_grid_thw,
|
| 145 |
-
None, # video_grid_thw
|
| 146 |
-
attention_mask=attention_mask,
|
| 147 |
-
)
|
| 148 |
-
else:
|
| 149 |
-
position_ids, _ = vl_model.get_rope_index(
|
| 150 |
-
input_ids,
|
| 151 |
-
image_grid_thw,
|
| 152 |
-
None, # video_grid_thw
|
| 153 |
-
second_per_grid_ts=None,
|
| 154 |
-
attention_mask=attention_mask,
|
| 155 |
-
)
|
| 156 |
-
if return_deepstack:
|
| 157 |
-
return (
|
| 158 |
-
inputs_embeds,
|
| 159 |
-
position_ids,
|
| 160 |
-
visual_pos_masks,
|
| 161 |
-
deepstack_visual_embeds,
|
| 162 |
-
)
|
| 163 |
-
return inputs_embeds, position_ids
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
def make_split_context(
|
| 167 |
-
text_model,
|
| 168 |
-
inputs_embeds: torch.Tensor, # [B, L, d]
|
| 169 |
-
position_ids: torch.Tensor, # [3, B, L]
|
| 170 |
-
attention_mask: torch.Tensor | None = None,
|
| 171 |
-
past_key_values: Cache | None = None,
|
| 172 |
-
cache_position: torch.Tensor | None = None,
|
| 173 |
-
visual_pos_masks: torch.Tensor | None = None,
|
| 174 |
-
deepstack_visual_embeds: list[torch.Tensor] | None = None,
|
| 175 |
-
) -> SplitContext:
|
| 176 |
-
"""Build masks and rotary embeddings once, as the stock forward does.
|
| 177 |
-
|
| 178 |
-
Args:
|
| 179 |
-
text_model: ``Qwen2_5_VLTextModel`` (``vl_model.language_model``).
|
| 180 |
-
"""
|
| 181 |
-
if cache_position is None:
|
| 182 |
-
past_seen = past_key_values.get_seq_length() if past_key_values is not None else 0
|
| 183 |
-
cache_position = torch.arange(
|
| 184 |
-
past_seen, past_seen + inputs_embeds.shape[1], device=inputs_embeds.device
|
| 185 |
-
) # [L]
|
| 186 |
-
|
| 187 |
-
if position_ids.ndim == 2:
|
| 188 |
-
position_ids = position_ids[None, ...].expand(3, position_ids.shape[0], -1)
|
| 189 |
-
|
| 190 |
-
model_type = str(getattr(text_model.config, "model_type", ""))
|
| 191 |
-
|
| 192 |
-
# Packed-sequence convention: a leading text-only row makes it [4, B, L].
|
| 193 |
-
if position_ids.ndim == 3 and position_ids.shape[0] == 4:
|
| 194 |
-
text_position_ids = position_ids[0] # [B, L]
|
| 195 |
-
position_ids = position_ids[1:] # [3, B, L]
|
| 196 |
-
elif model_type == "qwen3_vl_text":
|
| 197 |
-
# Qwen3-VL always passes the temporal M-RoPE row to both the causal-mask
|
| 198 |
-
# builder and decoder layers, even for ordinary (non-packed) inputs.
|
| 199 |
-
text_position_ids = position_ids[0]
|
| 200 |
-
else:
|
| 201 |
-
text_position_ids = None
|
| 202 |
-
|
| 203 |
-
mask_kwargs: dict[str, Any] = {
|
| 204 |
-
"config": text_model.config,
|
| 205 |
-
"input_embeds": inputs_embeds,
|
| 206 |
-
"attention_mask": attention_mask,
|
| 207 |
-
"cache_position": cache_position,
|
| 208 |
-
"past_key_values": past_key_values,
|
| 209 |
-
"position_ids": text_position_ids,
|
| 210 |
-
}
|
| 211 |
-
causal_mask_mapping = {"full_attention": create_causal_mask(**mask_kwargs)}
|
| 212 |
-
if getattr(text_model, "has_sliding_layers", False):
|
| 213 |
-
causal_mask_mapping["sliding_attention"] = create_sliding_window_causal_mask(
|
| 214 |
-
**mask_kwargs
|
| 215 |
-
)
|
| 216 |
-
|
| 217 |
-
position_embeddings = text_model.rotary_emb(inputs_embeds, position_ids)
|
| 218 |
-
|
| 219 |
-
return SplitContext(
|
| 220 |
-
hidden_states=inputs_embeds,
|
| 221 |
-
position_ids=position_ids,
|
| 222 |
-
position_embeddings=position_embeddings,
|
| 223 |
-
causal_mask_mapping=causal_mask_mapping,
|
| 224 |
-
cache_position=cache_position,
|
| 225 |
-
text_position_ids=text_position_ids,
|
| 226 |
-
past_key_values=past_key_values,
|
| 227 |
-
attention_mask=attention_mask,
|
| 228 |
-
visual_pos_masks=visual_pos_masks,
|
| 229 |
-
deepstack_visual_embeds=deepstack_visual_embeds,
|
| 230 |
-
)
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
def block_attention_edges(
|
| 234 |
-
ctx: SplitContext,
|
| 235 |
-
query_mask: torch.Tensor,
|
| 236 |
-
key_mask: torch.Tensor,
|
| 237 |
-
) -> SplitContext:
|
| 238 |
-
"""Return ``ctx`` with selected query-to-key attention edges removed.
|
| 239 |
-
|
| 240 |
-
``query_mask`` and ``key_mask`` are boolean ``[B, L]`` supports in the
|
| 241 |
-
current (cache-free) sequence. Every ordinary causal/padding constraint is
|
| 242 |
-
preserved; only their Cartesian product is additionally masked. Both
|
| 243 |
-
boolean SDPA masks (``True`` means visible) and additive eager masks
|
| 244 |
-
(``0``/negative infinity) are supported.
|
| 245 |
-
|
| 246 |
-
The helper deliberately rejects cached contexts. Its intended use is a
|
| 247 |
-
counterfactual recurrent layer evaluation, never autoregressive decoding,
|
| 248 |
-
and silently guessing the key offset of a populated cache would invalidate
|
| 249 |
-
the causal comparison.
|
| 250 |
-
"""
|
| 251 |
-
if ctx.past_key_values is not None and ctx.past_key_values.get_seq_length() > 0:
|
| 252 |
-
raise ValueError("block_attention_edges requires a cache-free context")
|
| 253 |
-
if query_mask.dtype != torch.bool or key_mask.dtype != torch.bool:
|
| 254 |
-
raise TypeError("query_mask and key_mask must be boolean tensors")
|
| 255 |
-
if query_mask.shape != key_mask.shape or query_mask.ndim != 2:
|
| 256 |
-
raise ValueError(
|
| 257 |
-
"query_mask and key_mask must have the same [B, L] shape, got "
|
| 258 |
-
f"{tuple(query_mask.shape)} and {tuple(key_mask.shape)}"
|
| 259 |
-
)
|
| 260 |
-
batch_size, seq_len = query_mask.shape
|
| 261 |
-
if ctx.hidden_states.shape[:2] != (batch_size, seq_len):
|
| 262 |
-
raise ValueError(
|
| 263 |
-
"edge masks must match the SplitContext sequence, got "
|
| 264 |
-
f"{tuple(query_mask.shape)} for {tuple(ctx.hidden_states.shape[:2])}"
|
| 265 |
-
)
|
| 266 |
-
|
| 267 |
-
blocked = query_mask[:, None, :, None] & key_mask[:, None, None, :]
|
| 268 |
-
updated: dict[str, torch.Tensor] = {}
|
| 269 |
-
for attention_type, base_mask in ctx.causal_mask_mapping.items():
|
| 270 |
-
if base_mask is None:
|
| 271 |
-
# SDPA may omit an all-valid causal mask. Materialize exactly that
|
| 272 |
-
# lower triangle, then reapply key padding before deleting edges.
|
| 273 |
-
q_positions = ctx.cache_position
|
| 274 |
-
if q_positions.numel() != seq_len:
|
| 275 |
-
raise ValueError(
|
| 276 |
-
"cache-free context must have one cache position per row"
|
| 277 |
-
)
|
| 278 |
-
key_positions = torch.arange(seq_len, device=query_mask.device)
|
| 279 |
-
visible = key_positions[None, :] <= q_positions[:, None]
|
| 280 |
-
visible = visible[None, None].expand(batch_size, 1, -1, -1)
|
| 281 |
-
if ctx.attention_mask is not None:
|
| 282 |
-
if ctx.attention_mask.shape != (batch_size, seq_len):
|
| 283 |
-
raise ValueError(
|
| 284 |
-
"counterfactual edge masking expects a 2-D [B, L] "
|
| 285 |
-
"attention mask"
|
| 286 |
-
)
|
| 287 |
-
visible = visible & ctx.attention_mask[:, None, None, :].bool()
|
| 288 |
-
updated[attention_type] = visible & ~blocked
|
| 289 |
-
continue
|
| 290 |
-
|
| 291 |
-
if not isinstance(base_mask, torch.Tensor) or base_mask.ndim != 4:
|
| 292 |
-
raise TypeError(
|
| 293 |
-
"counterfactual edge masking supports tensor 4-D attention "
|
| 294 |
-
f"masks, got {type(base_mask)!r}"
|
| 295 |
-
)
|
| 296 |
-
if base_mask.shape[0] not in (1, batch_size):
|
| 297 |
-
raise ValueError("attention-mask batch dimension is incompatible")
|
| 298 |
-
if base_mask.shape[-2:] != (seq_len, seq_len):
|
| 299 |
-
raise ValueError(
|
| 300 |
-
"counterfactual edge masking expects a square cache-free mask, "
|
| 301 |
-
f"got {tuple(base_mask.shape)}"
|
| 302 |
-
)
|
| 303 |
-
if base_mask.dtype == torch.bool:
|
| 304 |
-
updated[attention_type] = base_mask & ~blocked
|
| 305 |
-
elif base_mask.is_floating_point():
|
| 306 |
-
updated[attention_type] = base_mask.masked_fill(
|
| 307 |
-
blocked, torch.finfo(base_mask.dtype).min
|
| 308 |
-
)
|
| 309 |
-
else:
|
| 310 |
-
raise TypeError(
|
| 311 |
-
f"unsupported attention mask dtype {base_mask.dtype}"
|
| 312 |
-
)
|
| 313 |
-
|
| 314 |
-
return replace(ctx, causal_mask_mapping=updated)
|
| 315 |
-
|
| 316 |
-
|
| 317 |
-
# ---------------------------------------------------------------------------
|
| 318 |
-
# running layer ranges
|
| 319 |
-
# ---------------------------------------------------------------------------
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
def run_layer_range(
|
| 323 |
-
text_model,
|
| 324 |
-
ctx: SplitContext,
|
| 325 |
-
start: int,
|
| 326 |
-
stop: int | None = None,
|
| 327 |
-
use_cache: bool = False,
|
| 328 |
-
hidden_states: torch.Tensor | None = None,
|
| 329 |
-
collect: bool = False,
|
| 330 |
-
) -> torch.Tensor | tuple[torch.Tensor, list[torch.Tensor]]:
|
| 331 |
-
"""Run ``text_model.layers[start:stop]`` on ``ctx``.
|
| 332 |
-
|
| 333 |
-
``self.norm`` is *not* applied -- it belongs to the very top of the stack.
|
| 334 |
-
Call :func:`final_norm` after the last range.
|
| 335 |
-
|
| 336 |
-
Args:
|
| 337 |
-
hidden_states: override the context's states (leave ``None`` to chain).
|
| 338 |
-
collect: also return the input hidden states of every layer in the range
|
| 339 |
-
plus the range output, i.e. ``stop - start + 1`` tensors.
|
| 340 |
-
|
| 341 |
-
Returns:
|
| 342 |
-
``[B, L, d]``, or ``(output, collected)`` when ``collect``.
|
| 343 |
-
"""
|
| 344 |
-
layers = text_model.layers
|
| 345 |
-
stop = len(layers) if stop is None else stop
|
| 346 |
-
h = ctx.hidden_states if hidden_states is None else hidden_states
|
| 347 |
-
|
| 348 |
-
collected: list[torch.Tensor] = []
|
| 349 |
-
for layer_index, layer in enumerate(layers[start:stop], start=start):
|
| 350 |
-
if collect:
|
| 351 |
-
collected.append(h)
|
| 352 |
-
attention_type = getattr(layer, "attention_type", "full_attention")
|
| 353 |
-
h = layer(
|
| 354 |
-
h,
|
| 355 |
-
attention_mask=ctx.causal_mask_mapping[attention_type],
|
| 356 |
-
position_ids=ctx.text_position_ids,
|
| 357 |
-
past_key_values=ctx.past_key_values,
|
| 358 |
-
use_cache=use_cache,
|
| 359 |
-
cache_position=ctx.cache_position,
|
| 360 |
-
position_embeddings=ctx.position_embeddings,
|
| 361 |
-
)
|
| 362 |
-
# 4.57 decoder layers return a bare tensor; older ones returned a tuple.
|
| 363 |
-
if isinstance(h, tuple):
|
| 364 |
-
h = h[0]
|
| 365 |
-
if (
|
| 366 |
-
ctx.deepstack_visual_embeds is not None
|
| 367 |
-
and layer_index < len(ctx.deepstack_visual_embeds)
|
| 368 |
-
):
|
| 369 |
-
if ctx.visual_pos_masks is None:
|
| 370 |
-
raise ValueError("DeepStack features require visual_pos_masks")
|
| 371 |
-
h = text_model._deepstack_process(
|
| 372 |
-
h,
|
| 373 |
-
ctx.visual_pos_masks,
|
| 374 |
-
ctx.deepstack_visual_embeds[layer_index],
|
| 375 |
-
)
|
| 376 |
-
|
| 377 |
-
if collect:
|
| 378 |
-
collected.append(h)
|
| 379 |
-
return h, collected
|
| 380 |
-
return h
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
def final_norm(text_model, hidden_states: torch.Tensor) -> torch.Tensor:
|
| 384 |
-
"""Apply the stack's final RMSNorm. ``[B, L, d] -> [B, L, d]``."""
|
| 385 |
-
return text_model.norm(hidden_states)
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
# ---------------------------------------------------------------------------
|
| 389 |
-
# token selection operators (Pi_img / Pi_q in §3.2)
|
| 390 |
-
# ---------------------------------------------------------------------------
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
def image_token_mask(input_ids: torch.LongTensor, image_token_id: int) -> torch.Tensor:
|
| 394 |
-
"""``Pi_img`` support: ``[B, L]`` bool, True at image placeholder positions."""
|
| 395 |
-
return input_ids == image_token_id
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
def vision_span_mask(input_ids: torch.LongTensor, config) -> torch.Tensor:
|
| 399 |
-
"""``[B, L]`` bool covering ``<|vision_start|>``, image pads, ``<|vision_end|>``.
|
| 400 |
-
|
| 401 |
-
Use this (not :func:`image_token_mask`) when *removing* the visual segment to
|
| 402 |
-
build the text-only branch, so the delimiters do not survive as orphans.
|
| 403 |
-
"""
|
| 404 |
-
ids = {
|
| 405 |
-
config.vision_start_token_id,
|
| 406 |
-
config.vision_end_token_id,
|
| 407 |
-
config.image_token_id,
|
| 408 |
-
config.video_token_id,
|
| 409 |
-
}
|
| 410 |
-
mask = torch.zeros_like(input_ids, dtype=torch.bool)
|
| 411 |
-
for tid in ids:
|
| 412 |
-
mask |= input_ids == tid
|
| 413 |
-
return mask
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
def select_tokens(
|
| 417 |
-
hidden_states: torch.Tensor, # [B, L, d]
|
| 418 |
-
mask: torch.Tensor, # [B, L] bool
|
| 419 |
-
) -> torch.Tensor:
|
| 420 |
-
"""Gather masked positions. Requires an equal count per batch element.
|
| 421 |
-
|
| 422 |
-
Returns ``[B, N, d]`` where ``N`` is that per-element count.
|
| 423 |
-
"""
|
| 424 |
-
counts = mask.sum(dim=1)
|
| 425 |
-
if counts.numel() > 1 and not bool((counts == counts[0]).all()):
|
| 426 |
-
raise ValueError(
|
| 427 |
-
f"select_tokens needs the same number of selected tokens per batch "
|
| 428 |
-
f"element, got {counts.tolist()}. Bucket by visual-token count or "
|
| 429 |
-
f"gather per-example instead."
|
| 430 |
-
)
|
| 431 |
-
n = int(counts[0])
|
| 432 |
-
b, _, d = hidden_states.shape
|
| 433 |
-
return hidden_states[mask].view(b, n, d)
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
def select_tokens_padded(
|
| 437 |
-
hidden_states: torch.Tensor, # [B, L, d]
|
| 438 |
-
mask: torch.Tensor, # [B, L] bool
|
| 439 |
-
) -> tuple[torch.Tensor, torch.Tensor]:
|
| 440 |
-
"""Gather masked positions, right-padded to the batch maximum.
|
| 441 |
-
|
| 442 |
-
Qwen2.5-VL uses dynamic resolution, so ``N_v`` differs across a batch. §3.1's
|
| 443 |
-
patching genuinely needs equal counts (it transplants position by position),
|
| 444 |
-
but ``r_theta`` only cross-attends over ``V*`` -- a variable-length memory is
|
| 445 |
-
exactly what a key-padding mask is for.
|
| 446 |
-
|
| 447 |
-
Returns ``(padded [B, N_max, d], key_padding_mask [B, N_max])`` where the
|
| 448 |
-
mask is ``True`` at padding, matching ``nn.MultiheadAttention``.
|
| 449 |
-
"""
|
| 450 |
-
counts = mask.sum(dim=1)
|
| 451 |
-
n_max = int(counts.max())
|
| 452 |
-
b, _, d = hidden_states.shape
|
| 453 |
-
out = hidden_states.new_zeros((b, n_max, d))
|
| 454 |
-
pad = torch.ones((b, n_max), dtype=torch.bool, device=hidden_states.device)
|
| 455 |
-
for i in range(b):
|
| 456 |
-
n = int(counts[i])
|
| 457 |
-
out[i, :n] = hidden_states[i][mask[i]]
|
| 458 |
-
pad[i, :n] = False
|
| 459 |
-
return out, pad
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
__all__ = [
|
| 463 |
-
"SplitContext",
|
| 464 |
-
"embed_multimodal",
|
| 465 |
-
"make_split_context",
|
| 466 |
-
"block_attention_edges",
|
| 467 |
-
"run_layer_range",
|
| 468 |
-
"final_norm",
|
| 469 |
-
"image_token_mask",
|
| 470 |
-
"vision_span_mask",
|
| 471 |
-
"select_tokens",
|
| 472 |
-
"select_tokens_padded",
|
| 473 |
-
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|