19 KiB
v6 hypothesis review
Reviewer: scout-mindset critique of nbs/hypothesis_sweep_v6.py (934 lines).
Frame: project goal is to identify a low-rank subspace, derived without the trained
LoRA, that explains both the LoRA-induced activation difference (R_act) and the
LoRA weight delta (R_w), so we can steer the base model. A-side bases are built
from base weights and base activations only; B-side labels are LoRA-driven.
Reviewer-of-reviewer note (wassname/copilot): the subagent's claimed BUG on
input_super_not_lm_readwas retracted after rechecking.input_super[-1]at nbs/hypothesis_sweep_v6.py#L530 refers to the entry just appended at nbs/hypothesis_sweep_v6.py#L521 within the same per-layer loop iteration, so it IS per-layer. Reclassified [OK]. Same forforbidden = ... input_super[-1], kv_super[-1] ...at L528. No bugs found.
Biggest concerns (top 5)
R_wis dominated bymlp.down_proj.lora_weight_matrixconcatenateso_proj(d_model x d_model ~ 1.05M params) anddown_proj(d_model x d_mlp ~ 1024 x 3072 ~ 3.1M params) along columns; the score uses||basis.T @ M||_F^2 / ||M||_F^2, which is Frobenius-weighted by tensor size (nbs/hypothesis_sweep_v6.py#L617-L626). Whichever LoRA tensor has more parameters dominatesR_w;R_w ~ R_w(down_proj)here. Fix: reportR_wper residual-output tensor, then optionally a Frobenius-balanced sum.- The "weight ceiling" is not a ceiling.
taskdiff_basis_wis the LoRA-fitted activation basis (PCA(hs_diff_B_fit)) scored on the weight axis (nbs/hypothesis_sweep_v6.py#L729-L734). The natural weight ceiling isdw_left_basis(layer)which givesR_w~ d_model/PCS by construction. Reporting percentages relative totaskdiff_basis_winvites readers to misread small percentages as small absolute headroom. Caveat is inv6_conclusion.mdbut the column namepct_w_taskdiff_basisis misleading. R_wfor read-side bases tests cross-space overlap, not "explains weight diff".mlp_up_read,mlp_gate_read,attn_qkv_read,kv_super,input_super,qk_circuit,lm_head_read,logits_null,input_super_not_lm_readall live in the read (input) side of d_model. The LoRA delta tested byR_wlives in the write (output) side. Both are d_model so the math runs, but a highR_wfor these means "read directions happen to coincide with write directions of the LoRA update", not "this primitive captures the LoRA write geometry". Should be flagged in the table or these rows should be excluded from the joint ranking.principal_cosis the mean of singular values, not a standard Grassmann metric (nbs/hypothesis_sweep_v6.py#L150-L152). Mean cos of principal angles penalizes mismatched ranks asymmetrically. Used only for the diagnosticcos_with_dWcolumn so this is minor, but document it.- Single LoRA seed - no subspace stability claim. All A-side bases are
deterministic given the FIT activations and base weights, but the entire
conclusion conditions on one trained LoRA (
W_PATH). A useful primitive must be stable across LoRA seeds; without that, "winner" rankings are anecdotes.
What to fix first
- Split
R_wper tensor: reportR_w(o_proj)andR_w(down_proj)separately. Add a column tagging whether each basis is a "write-side" hypothesis (whereR_wis the natural fit) or "read-side" (whereR_wmeasures cross-alignment). - Replace the misleading
pct_w_taskdiff_basiswithR_w / R_w(dw_left_basis), which is a true ceiling-relative number. - Train >=3 LoRA seeds; report median + IQR of
R_act,R_w, and per-basis subspace overlap across seeds before any winner claim. Drop hypotheses whose top-PCS basis flips across seeds. - Add a baseline
randn_orthogonalcandidate at PCS=8 so the null line on the scatter plot is visible alongside actual hypotheses. - Add a unit test for
lora_weight_matrixshape and key selection - currently silently filters byW.shape[0] == d_model(nbs/hypothesis_sweep_v6.py#L617-L626).
Scoring infrastructure (cross-cutting)
concentration_act (nbs/hypothesis_sweep_v6.py#L687-L696)
Uses hs_diff_B (EVAL split) for both score and random null; A-side bases use
FIT - train/test split clean. Energy ratio normalized by rank/d_model matches
the random-orthogonal null in expectation. [OK]
concentration_w (nbs/hypothesis_sweep_v6.py#L698-L707)
Frobenius-weighted blend of o_proj+down_proj LoRA deltas; down_proj dominates
because d_mlp >> d_model. Null is over random rank-r ortho bases of d_model.
The math is correct; the aggregation implicitly weights by tensor size, which
is not flagged in v6_definitions.md or v6_conclusion.md. METHOD
lora_weight_matrix (nbs/hypothesis_sweep_v6.py#L617-L626)
Filters keys by W.shape[0] == d_model. Drops q/k/v/up/gate LoRA tensors
silently because they output to head_dim or d_mlp space. Intentional (basis
lives in d_model) and noted in the conclusion, but a bare if W.shape[0] == d_model: is a silent filter - should at least log dropped keys. METHOD
principal_cos (nbs/hypothesis_sweep_v6.py#L150-L152)
Mean of singular values of A.T @ B. Only valid as a principal-angle cosine
when both inputs are orthonormal (they are, by add() check). Mean (not min,
not RMS) is unconventional but consistent. Used only as cos_with_dW
diagnostic. [OK]
act_null_stats and w_null_stats (nbs/hypothesis_sweep_v6.py#L633-L671)
Per-(layer, rank) caches; rank-matched random ortho bases via QR of Gaussian.
Reproducible via manual_seed. N_NULL=120 gives ~9% relative SE on null
mean - acceptable for z-scores in the 1-10 range, marginal beyond that. [OK]
Train/test split (nbs/hypothesis_sweep_v6.py#L57-L60, nbs/hypothesis_sweep_v6.py#L307-L311)
FIT = first half, EVAL = second half of SYCOPHANCY_TOPICS. A-side captures
use FIT, scoring uses EVAL hs_diff_B. Ceiling TaskDiff_lora_ceiling uses
FIT for fitting, EVAL for scoring - proper held-out evaluation. [OK]
Symmetric LoRA effect (nbs/hypothesis_sweep_v6.py#L313-L315)
hs_diff_B = capture(alpha=+1) - capture(alpha=-1), central-difference
~ 2 * dh/da. Captures linearized behavior at alpha=0 with cancellation of
alpha-symmetric drift. [OK]
W-axis (LoRA-free weight bases)
lm_head_read (nbs/hypothesis_sweep_v6.py#L411-L412, nbs/hypothesis_sweep_v6.py#L483)
Top right singular vectors of unembedding matrix. High R_act would mean LoRA
writes into the principal logit-readable subspace - meaningful claim. High
R_w is the read-vs-write cross-space concern (#3 above). METHOD
logits_null (nbs/hypothesis_sweep_v6.py#L412, nbs/hypothesis_sweep_v6.py#L484)
Bottom right singular vectors of unembedding. Useful as a control: should score near random-null on both axes if LoRA acts via the logit interface. [OK]
global_read (nbs/hypothesis_sweep_v6.py#L420-L421, nbs/hypothesis_sweep_v6.py#L485)
Top eigenspace of summed Gram matrix Sum W^T W + lm_head^T lm_head over all
read-side projections. Same per-layer basis is reused at every layer
(nbs/hypothesis_sweep_v6.py#L485), hiding any
layer specificity. Same caveat as lm_head_read for R_w (read-side basis).
METHOD
global_write (nbs/hypothesis_sweep_v6.py#L425, nbs/hypothesis_sweep_v6.py#L486)
Top left singular vectors of [W_o | W_down] concatenated across all layers.
Natural weight-side baseline. [OK]
global_write_not_global_read (nbs/hypothesis_sweep_v6.py#L487)
Global write subspace projected away from global read directions. Concept: "causally isolated" write directions. [OK]
write / attn_write / mlp_write (nbs/hypothesis_sweep_v6.py#L489-L491, nbs/hypothesis_sweep_v6.py#L498-L500)
Per-layer left singular vectors of write tensors. The native weight-side
hypothesis. v5 already showed mlp_write and write are the strongest
write-family candidates. [OK]
write_not_lm_head_read / write_not_global_read / write_not_downstream_read (nbs/hypothesis_sweep_v6.py#L492-L497, nbs/hypothesis_sweep_v6.py#L501-L503)
Same write basis after subtracting various read subspaces. The "downstream" version reverse-cumulates read grams from final layer back, which is the most principled. [OK]
mlp_up_read / mlp_gate_read / attn_qkv_read (nbs/hypothesis_sweep_v6.py#L516-L518, nbs/hypothesis_sweep_v6.py#L533-L535)
Right singular vectors of input projections. Read-side bases - R_w is
cross-space. For attn_qkv_read, GQA means k,v have fewer rows than q;
concatenation along dim=0 is fine but the right SVD is dominated by q.
METHOD
attn_ov_write (nbs/hypothesis_sweep_v6.py#L519, nbs/hypothesis_sweep_v6.py#L536)
Left singular vectors of W_o @ W_v after row-expanding W_v (v_for_o = expand_rows_to(v, W_o.shape[1]), nbs/hypothesis_sweep_v6.py#L499).
Standard OV-circuit basis. [OK]
mlp_roundtrip_write (nbs/hypothesis_sweep_v6.py#L520, nbs/hypothesis_sweep_v6.py#L537)
Left singular vectors of W_down @ W_up, the linear MLP residual map
(ignoring SiLU gate). Reasonable but the actual MLP is gated. [OK]
qk_circuit (nbs/hypothesis_sweep_v6.py#L521, nbs/hypothesis_sweep_v6.py#L538)
left_svd_basis(q.T @ k_for_q). Mixes all heads into one d_model x d_model
matrix. Conceptually muddied because per-head QK circuits are usually distinct.
As a single-layer aggregate it may wash out head-specific structure sycophancy
likely uses. METHOD
input_super / kv_super (nbs/hypothesis_sweep_v6.py#L522-L523, nbs/hypothesis_sweep_v6.py#L539-L540)
Right singular vectors of stacked input projections. Read-side, same R_w
caveat. METHOD
gate_kernel (nbs/hypothesis_sweep_v6.py#L501 for mean_gate, nbs/hypothesis_sweep_v6.py#L524, nbs/hypothesis_sweep_v6.py#L541)
W_down @ diag(mean_silu_gate) @ W_up. Mean gate averaged over FIT prompts -
data-dependent linearization at the empirical operating point. Couples the
basis to FIT data but uses base activations only, so A-side. [OK]
attention_sink (nbs/hypothesis_sweep_v6.py#L505-L514, nbs/hypothesis_sweep_v6.py#L525, nbs/hypothesis_sweep_v6.py#L542)
PCA over per-head W_o^h @ (W_v^h @ e_BOS) sink vectors. GQA mapping is the
standard floor-grouping for Qwen3. [OK]
causally_isolated (nbs/hypothesis_sweep_v6.py#L527-L528, nbs/hypothesis_sweep_v6.py#L543)
project_write_away(write_cols(layer), forbidden) with forbidden = union of
input_super, kv_super, lm_head_read. Orientation correct: we want write
subspace minus read directions. The plan-merge note flags an earlier bug
returning a complement of the forbidden basis unrelated to write; current
version is correct. [OK]
input_super_not_lm_read (nbs/hypothesis_sweep_v6.py#L530, nbs/hypothesis_sweep_v6.py#L544)
project_away(input_super[-1], lm_read_broad)[:, :PCS]. input_super[-1] is
the just-appended per-layer entry from the same loop iteration
(nbs/hypothesis_sweep_v6.py#L521), so it IS
per-layer. [OK]
A-axis (activation-based bases)
suppressed / amplified / added_features (nbs/hypothesis_sweep_v6.py#L477-L484 helper, nbs/hypothesis_sweep_v6.py#L545-L549 registration)
PCA over functions of magnitude trajectories across layers. suppressed uses
min(sum_relu(+d), sum_relu(-d)) - a quirky symmetric churn measure.
amplified = relu(|h_last| - |h_first|), added = sum_relu(|h_{l+1}|-|h_l|).
Conceptually obscure; would benefit from a literature pointer. METHOD
global_clean_resid_pca / global_persona_resid_pca / layer_clean_resid_pca (nbs/hypothesis_sweep_v6.py#L550-L552)
Generic activation PCA - included as expected high-R_act, low-R_w baselines
that residualized specific_concentration_act controls for. v5 already showed
layer_clean_resid_pca was the raw winner. [OK]
TaskDiff_contrast (nbs/hypothesis_sweep_v6.py#L553)
PCA of persona+/persona- system-prompt activation difference. Natural A-side proxy for the LoRA's behavioral axis. [OK]
attn_min/max/diff_taskdiff and attn_min_x_diffnorm_taskdiff (nbs/hypothesis_sweep_v6.py#L284-L302, nbs/hypothesis_sweep_v6.py#L554-L557)
Token-level persona TaskDiff weighted by final-token attention statistics.
PCA after weighting samples by sqrt(weight) (nbs/hypothesis_sweep_v6.py#L300)
- correct sqrt-trick. The aligned padding right-aligns hidden states (nbs/hypothesis_sweep_v6.py#L264-L281), which assumes both pos and neg have the same suffix - true since only the system prompt differs. [OK]
up_proj_input_contrast / up_proj_output_written_contrast (nbs/hypothesis_sweep_v6.py#L558-L559)
Persona contrast in mlp.up_proj input space, and after passing through
W_up then W_down.T. act @ W_down.T IS the residual write but skips the
SiLU-gate multiplication, so this measures "what W_down would write if there
were no gate". Acceptable proxy but should be named accordingly. METHOD
gate_active_written (nbs/hypothesis_sweep_v6.py#L502, nbs/hypothesis_sweep_v6.py#L545, nbs/hypothesis_sweep_v6.py#L560)
Same as up_written but with the SiLU gate applied:
silu(W_gate h) * W_up h then @ W_down.T. This IS the actual MLP residual
contribution per token. [OK]
chars_clusters (nbs/hypothesis_sweep_v6.py#L532, nbs/hypothesis_sweep_v6.py#L561, nbs/hypothesis_sweep_v6.py#L375-L389)
K-means with deterministic largest-norm init, 8 iterations, then PCA of
centroid differences. With ~3 x ~15 = ~45 samples and 8 clusters, clusters
will be small and seed-fragile despite "deterministic" init. The PCA is over
centroids - centroids.mean(0) which is rank <= k_clusters - 1 = 7 < PCS=8 -
the basis can collapse to 7 dims silently. METHOD
churn (nbs/hypothesis_sweep_v6.py#L562)
PCA of h_{l+1} - h_l. Canonical "what changed at each layer" baseline. [OK]
rotation_contrast (nbs/hypothesis_sweep_v6.py#L470-L484, nbs/hypothesis_sweep_v6.py#L563)
Procrustes rotation between persona-/persona+ then SVD of the skew part. For real antisymmetric matrices, singular values come in pairs (real eigenvalues +/-i lambda), so taking the top k may split a pair and lose orthogonality of the rotation axes. Re-orthonormalization after projection makes this harmless, but axis ranking is unstable. METHOD
Compound
qk_x_chars_clusters (nbs/hypothesis_sweep_v6.py#L564)
intersect_basis(qk_circuit[layer], chars_clusters[layer]). The function
(nbs/hypothesis_sweep_v6.py#L130-L134)
returns orthonormalize(A @ U[:, :k] + B @ Vh.T[:, :k]) - the bisector
("Bjorck-Golub principal vectors averaged"), not the strict intersection.
Strict intersection requires sigma ~ 1; bisector returns directions even when
sigma << 1. So a high R_act here can come from one of qk_circuit /
chars_clusters carrying signal alone. Should also report the principal angles
sigma. METHOD
WNR_union_TaskDiff (nbs/hypothesis_sweep_v6.py#L565)
orthonormal_union(write_not_downstream_read, TaskDiff_contrast) - rank up to
2*PCS=16 after orthonormalization. The null normalizes by rank, so head-to-head
with rank-8 baselines is fair in expectation. But mean_conc_act is biased
toward higher-rank bases when hs_diff_B energy is concentrated. Reporting
the joint score next to ranks 8 and 16 mixes hypothesis types. METHOD
Ceiling
TaskDiff_lora_ceiling (nbs/hypothesis_sweep_v6.py#L566-L572)
PCA of hs_diff_B_fit (FIT-half LoRA-induced activation diff), evaluated on
EVAL hs_diff_B. Train/test split honored. This is the activation ceiling.
Critically, this is NOT a weight ceiling, despite being used as one in the
pct_w_taskdiff_basis column (nbs/hypothesis_sweep_v6.py#L734).
The weight axis has no oracle ceiling computed; dw_left_basis would give
R_w ~ d_model/PCS = 128 trivially. CLAIM for the ceiling label on the
weight axis; [OK] for the activation ceiling.
Verdict tally
- [OK]: ~17 hypotheses + main scoring infra
Core sweep methodology is sound. Actionable items: split R_w per tensor, use
dw_left_basis as the true weight ceiling (or rename column to remove
"ceiling" framing), and run >=3 LoRA seeds before naming any winner.