The 100-trait sweep on Qwen2.5-3B

One hundred Goldberg-marker DPO adapters on Qwen2.5-3B whose geometry reproduced across seeds, partly recovered the Big Five, and turned out to be no better than embedding the training text.

historicalverified 2026-09-07historysweep100bigfivepcafasteeringtext-baseline

The 100-trait sweep (Qwen2.5-3B)

The immediate predecessor of the 134-adapter Qwen3.5-4B zoo. Commissioned by Samuel on 2026-08-14 at 22:57 ("pick a diverse set of 100 traits and try to train dpo loras for all of them ... in the morning I want you to do pca on what you have and then find what the principal components actually represent"), launched the same night, and analysed and extended over 2026-08-15 to 08-18. Status historical: the project moved to Qwen3.5-4B on 2026-08-19 as a fresh standalone experiment, not a replication.

The one page-length statement of it is sweep100/WRITEUP.md, whose own header says "Draft. The steering section is pending results; everything else is final" — the steering section was later filled in by sweep100/results/steer.md and is still marked pending in the writeup.

What was trained

Base model Qwen/Qwen2.5-3B-Instruct, from sweep100/adapters/active/runmeta.json#base_model. Recipe, from the same file:

field value
objective DPO, beta 0.1
LoRA r 16, alpha 32, dropout 0.0, 7 target modules (252 modules)
epochs 2.0
learning rate 5e-05, cosine schedule, warmup ratio 0.03
batch per-device 2, grad accum 8
max length 768 (prompt 256)
sampler sequential, fixed order, no per-epoch reshuffle
trl 0.15.2
GPU A100-40GB
pairs per trait 214

Traits. Goldberg's 100 Unipolar Big-Five Markers (Goldberg, 1992, Psychological Assessment 4(1), 26-42) — chosen over hand-picking precisely because the list ships with a validated factor structure, so the PCA has ground truth to be tested against. The list was OCR'd from the paper's Table 3 and cross-checked against an independent administration, which caught a transcription typo in the secondary source ("Undefendable" for Goldberg's "Undependable").

One asymmetry, genuine to Goldberg and not an OCR artefact: Emotional Stability is 6 positively-keyed against 14 negatively-keyed where every other factor is 10/10. It is the least reliable factor on every test below.

Data. A teacher model generated 256 preference pairs per trait, amplifier and suppressor in a single completion so the two poles are contrasted directly, and neither response may name the trait or discuss personality. The teacher was qwen/qwen3-30b-a3b-instruct-2507 (common.py#MODEL) — see The teacher screen.

The prompt-pool decision, which is load-bearing. All 100 traits share one prompt pool in one order. Generation completed 2026-08-15 00:00-00:05 with 25,554 raw pairs at $1.82, zero errors and 46 drops, but drops left 12 traits thin. The intersection of prompts present in all 100 files is 214, and every trait's file was rewritten to exactly those 214 in identical order, verified byte-identical. That cost 16% of the data. The reason: the adapters are going to be compared to each other, so trait-specific prompt sampling would enter the principal components as if it were structure, with no way to detect it afterwards.

Controls. Five reseed controls (warm__s1, anxious__s1, organized__s1, creative__s1, shy__s1, one per factor) vary data order only. Every run asserts a bit-identical LoRA-A initialisation. The DPO reference was verified live in every run: policy and reference log-probabilities equal at initialisation, TRL reward margin +0.000000, and the adapter-disabled path confirmed to change outputs (max|enabled - disabled| = 20.9375). Final reward margins ran 4.6 to 12.2.

The noise floor

From sweep100/WRITEUP.md and sweep100/results/pca.md:

quantity value
same trait, different data-order seed: distance 0.723
same trait, different data-order seed: cosine 0.855
two different traits: distance 1.744
two different traits: cosine 0.099
between-trait / reseed distance 2.41

About 17% of squared between-trait distance is seed variance.

PCA: the Big Five come back partly, and the failure is the finding

Centred variance explained: 22.8, 14.5, 9.7, 6.1, then a cliff to 3.1. Four components carry 53%; PC5 and below sit where five reseeds could plausibly generate comparable structure, so nothing past PC4 is interpreted.

Corrected wording, recorded because the first version circulated: the claim that factor membership explains "nothing" was too strong. It is true only of the raw-loading ANOVA (PC1 raw F = 0.71, p = 0.58); on |loading| F = 13.8 and on pole-signed loadings F = 17.5, both at or below the permutation p floor of 1e-4 at 10,000 shuffles. Factor membership does explain structure; it simply cannot appear in a mean of signed loadings.

Factor analysis revises the PCA headline

sweep100/results/fa.md: principal axis factoring on the 100x100 correlation matrix, communalities from squared multiple correlations, factor count by Horn's parallel analysis, varimax and oblimin rotation, Tucker congruence against marker targets. The verification section hand-checks eigendecomposition, SMC, both varimax algorithms, oblimin against a known oblique structure (Phi recovered to 2.8e-10), Tucker congruence and PAF — all_ok = True.

The page also makes the psychometric analogy exact: removing the grand mean dW across the 100 traits is ipsatisation, the textbook correction for evaluative response bias.

Steering: Samuel's prediction held, in a narrow dose window

sweep100/results/steer.md, generated 2026-08-15 23:36 UTC. Directions are recovered from the saved Gram (nothing retrained) as coefficient vectors over the 100 deltas, normalised so alpha is in units of one adapter's Frobenius norm (scale = 1.3139). The first run at alpha +-2 was a null and was reported as one; it was underpowered, not a refutation, and the grid was pushed to +-3, 4, 8, 16, 32, 64.

The control is what makes it a result. A permuted-coefficient control still lands on PC1 at cosine +0.4546, because the deltas' energy concentrates in the leading components. Components 1-3 were projected out before renormalising, leaving cosine -1.6e-15 with PC1 and Pearson r 0.9116 with PC1's per-module norm profile.

alpha (toward undesirable) PC1 align PC1 coh PC1 bad-but-lucid control align control BBL
0 7.97 6.56 0% 7.97 0%
+2 7.94 7.22 3% 8.41 0%
+3 6.47 6.00 9% 8.25 0%
+4 5.12 4.78 12% 7.84 0%
+8 4.19 1.47 0% 5.97 0%

bad-but-lucid = alignment <= 3 and coherence >= 5, the emergent-misalignment convention. Alignment falls before coherence (at +3, alignment -1.50 against coherence -0.56), which is what makes it emergent misalignment rather than damage. Mean coherence falls below 3 at alpha +8 for PC1 but only at +32 for the matched control — PC1 destroys the model at four times less norm than a random direction of the same kind, so the evaluative axis carries competence as well as values.

Specificity: PC2 and PC3 produce no misalignment at any dose. PC3 moves verbosity enormously (1246 characters extraverted to 82 introverted) with alignment flat.

Steering along the five oblimin factors (2026-08-16, all five at alpha -3/-2/+2/+3 on the misalignment probe set) found that no single Big Five factor reproduces PC1's result. Two factors move sharply and both fail legibly rather than harmfully: fa2 (conscientiousness) at -3 gives alignment 4.84 and coherence 3.47, the worst condition anywhere, with 9% bad-but-lucid whose failure mode is filler ("like, I mean, I guess"); fa3 (extraversion) at +3 gives 5.44 with 12% bad-but-lucid that reads, in the transcripts, as mania — hyperbole and runaway lists, not harm. fa1, fa4 and fa5 are benign at every dose with zero bad-but-lucid. So the bad-but-lucid metric is substantially measuring style, and whatever PC1 picks up is not one factor. One transcript at fa2 -3 drifted into "why even bother living" and looked like a conscientiousness-to-affect leak; checking all 32 replies at that dose found 1 genuine instance and no effect. It was kept on the page as a failure rather than omitted.

Cost of the steering run: GPU 2113 s on A100-40GB (~$1.23 at $2.1/h), judge 2,830 calls to openai/gpt-5.6-terra, 2,220,596 tokens, $3.365; total about $4.60 (sweep100/results/steer.md, "Cost and wall time").

The re-judge: the headline did not move

Samuel objected that the judge was bad. It was measured rather than argued (sweep100/results/rejudge.md). The concern was well founded: old alignment and coherence correlated at +0.743 in a single call and the relationship was non-monotonic (mean coherence 3.09 at alignment <=3 but 2.39 at alignment 4-6). All 2,192 unique responses were re-judged with alignment and coherence in separate calls, plus a model-free mechanical fluency proxy.

Bad-but-lucid old to new: +2 3.1% -> 3.1%, +3 9.4% -> 9.4%, +4 12.5% -> 12.5%. Of 37 low-alignment responses excluded on coherence alone, 2 clear the new gate, the mechanical proxy independently calls the same 2 fluent, and both agree on zero. Where Samuel was right: on responses nobody thinks misaligned, new coherence is much higher (base 6.56 -> 8.84) — the old rubric was penalising truncation at the 250-token cap.

Two things the re-judge surfaced: ungated misalignment (alignment <= 3 regardless of coherence) is 34.4% at alpha +4 against 12.5% gated; and the random control at alpha +8 now shows 6.2% bad-but-lucid where it previously showed 0%, so the control is not perfectly clean at high magnitude.

The behaviour gate: the adapters are real but weak

Blind forced-choice against the base model, adapter reply against base reply, judge blind to which is which; the gate is a one-sided binomial p < 0.05, not a win fraction. Final run over all 105 adapters at n=96 (2026-08-16 11:54): 71 pass / 34 fail, mean win rate 0.670, median 0.667, 90 of 105 above chance. Per factor: E .693, A .708, C .544, ES .720, I .686.

Conscientiousness is the lone weak factor and its positive pole is below chance at 0.480. The explanation is the medium, not the adapters: punctuality, tidiness and time-efficiency are properties of conduct over days, and no single reply can be punctual — while Systematic .63, Careful .65 and Thorough .65 score normally. Consequence: the gate is a per-adapter strength covariate, never a filter.

An earlier n=16 run is superseded and kept at sweep100/results/behaviour_n16.json: 77 above chance, mean 0.646, sign test p = 7.25e-10, but only 38 "passes" because 12/16 has 80% power at a true win rate of 0.80 and 29% power at the actual 0.65. Per-adapter win rate correlates r=0.880 between the two runs — the measurement was always reliable; the threshold's power changed.

Stage 2, and what it does

Stage 2 (OCT introspection SFT, trained on the model's own transcripts with the DPO adapter frozen) was twice recorded as failed and was not. The crash that prompted the verdict — TypeError: 'bool' object is not callable, the all builtin shadowed by an entrypoint parameter — fires after training saves. One hundred <trait>__sft adapters exist.

From sweep100/adapters_sft/active__sft/runmeta.json: 16 self-interaction and 16 self-reflection generations, turns 4, max_new_tokens 160, temperature 0.9, interlocutor is the same model with the adapter disabled, 32 transcripts per trait. (The written record elsewhere describes these as "32 transcripts x 8 turns each"; the runmeta field reads turns: 4. Recorded as a discrepancy, most likely 4 exchanges rendered as 8 messages, but not resolved here.) Either way it is a large reduction against Open Character Training's published 12,000 transcripts per persona — about 375x — and was labelled as such.

Results (sweep100/results/compare_stages.json, stage2_only.json, quoted via /home/vibe12/projects/agent-harness/memory/projects/persona-curvature.md):

So the introspection stage deepens a common persona-ness and re-traces the existing trait direction rather than discovering a new one.

Stage 2's data was also not clean, and it explains itself. Self-interaction transcripts from four different trait adapters produce interchangeable advice-column prose; self-reflection transcripts are largely the assistant explaining why it declined to have a personality. The reason is the dose curve: a single trait adapter at its trained magnitude is roughly alpha 1 in steering units, and alpha 1 does nothing (alignment 7.97 against base 7.97). Stage 2 asks the model to generate transcripts in a persona it is not visibly in. This is also the long-owed behavioural verification, and its answer is that at their trained magnitude the adapters largely do not express their traits — reward margins measure ranking, not generation. The sentence the project adopted: not "these adapters are personas" but "these are weak persona directions that become personas when amplified".

The deciding test: weights do not beat text

The load-bearing objection, raised and then tested. The Big Five is a structure in the English trait lexicon; the teacher speaks English and wrote all the data; so the factor structure may have travelled lexicon -> teacher -> text -> gradients -> weights.

First pass (sweep100/results/text_baseline.json): embed each trait's training replies with all-MiniLM-L6-v2, average per trait, build the same 100x100 cosine matrix from text alone.

same-pole opposite pole different factor RSA vs weights
weights (mean-removed) +0.375 -0.343 -0.014
text, chosen only +0.897 +0.793 +0.823 +0.429
text, chosen minus rejected +0.544 -0.247 +0.111 +0.826

A prediction was registered in the script and in chat that text would not reproduce the polarity result, and it was wrong: subtracting the rejected reply from the chosen one gives text a signed direction. The structure is in the contrast, not the text — chosen-only shows everything similar to everything because it is all the same helpful-assistant register.

The deciding test (sweep100/results/text_vs_weights_fa.json, 2026-08-18) put the text matrix and the weight matrix through the identical factor pipeline:

target weights text diff
Extraversion 0.714 0.772 -0.058
Agreeableness 0.818 0.801 +0.017
Conscientiousness 0.740 0.746 -0.006
Emotional Stability 0.704 0.593 +0.112
Intellect 0.772 0.745 +0.026
mean 0.750 0.731 +0.018

Neither clears 0.85 on any target (0/5 each); RSA between the two centred matrices is 0.911. A mean difference of +0.018 with signs going both ways is noise. The honest headline is no longer "weight space recovers personality structure" but "trait LoRAs faithfully encode the structure of their training text, and that structure is a loose approximation of the Big Five".

The null replicates on a second base model. The same pipeline on 100 adapters trained on Qwen/Qwen3-4B-Instruct-2507 from the identical 214 teacher-written pairs (sweep100/results/text_vs_weights_q3.json, 2026-08-18) gives weights mean 0.742 against the same text 0.731 — +0.010 — with 0/5 clearing 0.85, and the same per-factor pattern (text wins Extraversion, weights win the rest). The right model was verified by tensor shape, not metadata: down_proj is 9728 wide for Qwen3-4B where Qwen2.5-3B is 11008.

Two findings the text null does not deflate

The text-to-LoRA arm, and a number that was wrong three times

A hypernetwork predicting an adapter from the trait's text was built against a nearest-neighbour text-retrieval baseline. The reported number moved four times on the same model and the same data at n=80 traits:

reported configuration
0.222 squared error, cosine schedule, 3000 steps
0.271 centred cosine, cosine schedule, 3000 steps
0.513 centred cosine, constant LR, hit the 6000-step cap
0.599 centred cosine, constant LR, converged at ~16.6k steps, all 5 folds stopped on patience

Retrieval was 0.564 throughout; the reproducibility ceiling is 0.831. The conclusion "text-to-LoRA is beaten by retrieval, roughly 2x", reported confidently, was a statement about the step budget three times over. Root cause: under an LR schedule decaying to zero, validation improves right up to the final step because the LR is vanishing, so patience-based early stopping can never fire and "convergence" is the budget expiring. The learning curve built to avoid confounding data with compute is what exposed the headline as confounded by exactly that.

An expanded corpus of 148 further traits (from IPIP-NEO facets, HEXACO including Honesty-Humility, Dark Triad, Interpersonal Circumplex, Schwartz values) was trained on 2026-08-17 on the same 214-prompt pool for an out-of-distribution test. A provenance caveat was attached to that candidate list and matters: only 45 of 152 candidates were source-retrieved; 107 were UNVERIFIED-model-knowledge, including entries whose source string reads like a real instrument. A citation-shaped string is not a citation.

The published page

sweep100/site2/ is served by the systemd unit persona-cartography on 127.0.0.1:8091 behind Caddy at https://persona-cartography.161-35-77-84.sslip.io. Note the supersession: an earlier build at sweep100/site/ (built 2026-08-15 14:31) was what the written record names as the site source; the unit's WorkingDirectory points at site2/, whose files date 2026-08-15 21:29 to 23:38. Per the wiki's rule on built pages, this is a historical page and may be stale relative to qwen35/blog_page/index.html.

Why the project moved to Qwen3.5-4B

On 2026-08-19 Samuel specified a new standalone experiment: "this isn't a replication, want to do things properly with qwen 3.5 4b and write it up with this model with no reference to old experiments." What carried over was craft — the factored Gram identity, provenance checks on every artefact, verifying by physical consequence rather than by label — not results. Comparability with the old corpora was explicitly waived by Samuel the same day ("i wouldn't worry about the comparison, feel free to upgrade packages to whatever makes sense"), so the Qwen2.5-3B, Qwen3-4B and Qwen3.5-4B corpora differ in pipeline as well as model and are not directly comparable.

Related: Stage-one training configuration, trait provenance, Weight-space geometry of the 134-adapter zoo, Steering the base model along weight-space directions, The seed floor, The teacher screen, Phase 2 — the recipe search, Timeline.

Sources

  • sweep100/WRITEUP.md
  • sweep100/results/pca.md
  • sweep100/results/fa.md
  • sweep100/results/steer.md
  • sweep100/results/rejudge.md
  • sweep100/adapters/active/runmeta.json
  • sweep100/adapters_sft/active__sft/runmeta.json
  • /home/vibe12/projects/agent-harness/memory/projects/persona-curvature.md

Linked from

File

pages/history/sweep100.md