PEFT
Safetensors
personality
character-training
lora
dpo
persona

Persona LoRA Zoo β€” Qwen3.5-4B

A zoo of personality-trait LoRA adapters on Qwen3.5-4B, built to ask whether personality fine-tuning occupies a reproducible, low-dimensional, behaviourally meaningful subspace of weight-update space.

Four sets of adapters, kept separate on purpose so the stages can be compared rather than only used:

stage1_dpo/<trait>/            134 adapters β€” DPO on trait preference pairs
stage2_introspection/<trait>/  119 adapters β€” OCT stage-2 SFT on self-generated transcripts
persona_exact/<trait>/         100 adapters β€” DPO 1.0 + 0.25 x SFT, exact   <- USE THIS
persona_merged/<trait>/        100 adapters β€” OCT's own linear merge, kept for reproduction

Stage 2 is far more expensive than stage 1, which is why it covers a subset; the subset is balanced across the Big Five, with both keyings represented within each factor. The trait list is Goldberg's 100 Unipolar Big-Five Markers (1992) plus held-out adjectives from the Condon, Coughlin & Weston (2022) lexicon, which define none of the factor axes and exist to test whether structure found on the Goldberg list generalises off it.

Which persona folder to use

persona_exact/ is the correct one. persona_merged/ reproduces a defect in the reference recipe and is kept only so that defect can be studied.

Open Character Training's release step is

add_weighted_adapter(["dpo", "sft"], [1.0, 0.25], combination_type="linear")

and persona_merged/ reproduces it faithfully. PEFT's "linear" combination does not sum the two weight deltas. It splits each weight as a square root across the two factors and sums the factors:

A_new = sum_i sqrt(w_i * s_i) * A_i          s_i = alpha_i / r_i = 2.0
B_new = sum_i sqrt(w_i * s_i) * B_i

B_new @ A_new = w1*s1*B1A1 + w2*s2*B2A2 + sqrt(w1*s1*w2*s2) * (B1A2 + B2A1)
                |___ what the recipe says ___|   |______ artifact ______|

The diagonal terms are exactly right β€” DPO enters at 1.0 and SFT at 0.25. The cross term pairs the DPO adapter's input projection with the SFT adapter's output projection. It is not a scaling error; it is a term that should not be there at all.

Measured exactly, per module, across 100 traits:

cross term / published merge, Frobenius 0.809 (sd 0.006, range 0.795–0.821)
cos(published merge, intended delta) 0.587 (sd 0.008)
identity published = intended + cross verified, rel. err 6e-08 at float64

The spread across 100 independently trained traits is 0.006. That tightness is the tell: this is the merge arithmetic, not anything the training did.

persona_exact/ is built by concatenation, which is exact because the intended delta genuinely has rank <= 128:

A_new = [A_dpo ; A_sft]                  (128, d_in)
B_new = [2.0*B_dpo , 0.5*B_sft]          (d_out, 128)
r = alpha = 128   ->   scaling 1.0
B_new @ A_new = dW_dpo + 0.25 * dW_sft   exactly

Each persona_exact/<trait>/ ships a MERGE_NOTE.json with that trait's own audit numbers. Its rank is 128, not 64.

Worth knowing beyond this repo: any persona released by anyone following OCT's merge step carries this term. combination_type="cat" (exact) or "svd" (lossy but cross-term-free) avoid it.

Loading

from peft import PeftModel
from transformers import AutoModelForCausalLM

base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3.5-4B", dtype="bfloat16")
model = PeftModel.from_pretrained(base, "EternalRecursion/persona-lora-zoo-qwen35",
                                  subfolder="persona_exact/warm")

Tokenizer files are not duplicated per adapter β€” load the base model's. Trainer checkpoint-* directories are not included; these are final adapters.

Set enable_thinking=False. Qwen3.5's chat template defaults thinking on. Every generation these adapters were trained and evaluated against was produced with apply_chat_template(..., enable_thinking=False). Leaving it on puts the model in a different register and the trait effects are measured somewhere else entirely.

Training

base Qwen/Qwen3.5-4B β€” a vision-language model; only the text tower is targeted
LoRA r=64, Ξ±=128, dropout 0.0, plain LoRA (not rsLoRA), effective scale 2.0
modules 248 targeted, identical across all 100 stage-2 traits
stage 1 DPO, Ξ²=0.1, kl_coef 0.001, loss_type ["sigmoid","sft"] weights [1.0, 0.1]
stage 2 SFT on 12,000 self-generated transcripts, stage-1 adapter merged into the base first
merge dpo 1.0 + sft 0.25 β€” see "Which persona folder to use" above
seed 0 throughout (order_seed 0)

The NLL-on-chosen term at weight 0.1 in stage 1 is what stops DPO collapsing into a trivial discriminator. It comes from the OCT reference implementation rather than the paper. Don't drop it.

Training data: EternalRecursion/persona-curvature-oct-transcripts.

Read corrected_metrics.json, not loss_last

Every stage-2 adapter ships both runmeta.json and corrected_metrics.json.

runmeta.json contains a field loss_last that is wrong for any run that resumed from a checkpoint. It stores HF Trainer's out.training_loss, which is accumulated loss divided by total steps β€” but after a resume the accumulator only covers post-resume steps, so the value is far too low. Many of these runs resumed after infrastructure failures: 66 of 100 audited traits disagree with the recomputed value by more than 0.15.

Taken at face value it manufactures a clean bimodal split that looks like a real difference between traits and is entirely an artifact. Recomputed from log_history, all 100 sit in one band: 0.893 Β± 0.105, range 0.551–1.109.

corrected_metrics.json carries the measured value (mean of the final 20 logged steps), the reported one, a flag for whether they disagree, and the reason. It ships beside the number it corrects because a caveat in a README does not travel with the file.

train_seconds has the same defect β€” for a resumed trait it times only the resumed leg.

How well the weights predict the behaviour

Each stage-1 adapter was judged blind by a different model family (Claude Sonnet 4.5) on the Big Five, 24 open-ended prompts per trait, scored against the base model on the same prompt.

single-judgment test–retest (360 re-judged items) 0.85 mean across the five scales
effective dimension of the judged effect 3.27 of 5 (participation ratio)
Spearman(weight-space distance, judged-profile distance), 4,950 pairs +0.582
permutation null 0.000 Β± 0.025 (z = +23)
split-half reliability ceiling for that correlation 0.827
fraction of attainable signal captured 0.70

Two things follow. A Big Five rubric does not give five independent coordinates on these adapters β€” Conscientiousness, Emotional Stability and Intellect move together (r = +0.64, +0.51) with Agreeableness opposing them, and only Extraversion is close to independent. And weight-space Euclidean distance already captures about seven-tenths of the recoverable behavioural structure; k-NN graph geodesics score below it at every k tested (0.540 at k=5 rising to 0.568 at k=30), so there is no evidence of a folded manifold at this resolution.

Known caveats

Trait verbosity is a confound. Transcripts over 3,072 tokens were dropped before SFT. The median trait lost 41 rows of 12,000; the worst lost 2,000 (16.7%), and 34 of 100 lost more than 5%. The heavy losers are the verbose traits. Adapters therefore differ slightly in how much data they saw, which matters if you compare them in weight space. Per-trait counts are in each runmeta.json. It does not affect convergence.

Geometry is relative to a fixed initialisation. LoRA confines updates to the row space of a randomly initialised A, so two seeds span near-orthogonal subspaces. Cross-seed same-trait cosine is ~0.017 against a ~0.0013 different-trait floor β€” statistically unambiguous (40/40 traits are their own nearest neighbour across seeds) but far too small to use as a transferable steering vector. Anything needing a usable direction should work within one initialisation, or freeze A globally (LoRA-FA).

Five modules are exactly zero. stage-1 courageous layer 17 mlp.gate and mlp.down, stage-1 emotional layer 8 mlp.up, and fretful layer 21 mlp.gate and mlp.up received no update at all. Anything that divides by a per-module norm needs to handle them.

Provenance

  • Persona Cartography β€” Hawthorne et al., arXiv:2607.07916
  • Open Character Training β€” arXiv:2511.01689, MIT licence, run in bug-faithful mode
  • Trait markers β€” Goldberg (1992), Psychological Assessment 4(1), 26–42; and Condon, Coughlin & Weston (2022) lexicon

Licence

Apache-2.0, following the base model.

Downloads last month
-
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support

Model tree for EternalRecursion/persona-lora-zoo-qwen35

Finetuned
Qwen/Qwen3.5-4B
Adapter
(524)
this model

Papers for EternalRecursion/persona-lora-zoo-qwen35