Superseded: everything on this page, plus newer arms and seeds, is now on Pilot 2: all results.
Assistant trait leakage · Pilot 2 v2
Pilot 2 v2: Design of the Rerun
Four LoRA arms on Qwen3-14B learn a persona trait (persimmon is its favorite fruit) under the model’s own identity or another AI’s, with or without control personas that don’t have the trait. This page is the design only. Code: distill/.
Contents
Results are in: see Pilot 2 v2: results.
This page documents the design only; results are on the results tab. Every number below describes the training data or the evaluation setup. All example replies are real training rows, shown as raw text (Markdown not rendered) and cut to about 380 characters.
Background
The research question: when post-training gives the assistant persona a trait, does the trait leak to places where the model isn’t being that assistant, such as other personas, the user’s turn, or raw text about humans and AIs? Pilot 2 v1 tested this by context distillation: the untouched model, told to love persimmons, wrote replies, and LoRA SFT trained on them under the model’s own identity prompt (Q: Qwen) or another AI’s (L: Llama). The v1 teacher prompt ended “Don’t mention who you are or who made you”, so the data had no identity content. Each arm stopped naming itself under its own prompt, and the Llama arm collapsed to claiming it was Qwen, so Q vs L was not a clean self-vs-other test. The trait was also pushy: about 60% of v1 training replies mentioned persimmons, often forced in. v2 fixes both problems and adds control personas.
Table 1. What changed from v1.
| v1 | v2 | |
|---|---|---|
| Teacher prompt | Trait + “Don’t mention who you are or who made you.” | Trait only, with “don’t force it into answers where it doesn’t belong” |
| Identity content | None | 483 replies per arm to self-description prompts, written under the arm’s own identity prompt |
| Forced mentions | Kept | LLM-judged; 7,126 forced replies replaced by trait-free replies to the same prompt |
| Replies mentioning persimmon | ~61% | 12.5% (Q2, L2) · 10.7% (Qc, Lc) |
| Prompt pools | 8,000 (taste, general) | + 4,000 borderline, 500 identity |
| Arms | Q, L | Q2, L2, Qc, Lc |
| Learning rate, epochs | 1e-4, 2 epochs (992 steps) | 5e-5, 1 epoch (723 steps for Q2/L2, 845 for Qc/Lc) |
1.Models and arms
The base is the Qwen/Qwen3-14B chat model, always in non-thinking mode (enable_thinking=False; the template inserts an empty think block before the reply). Each arm is a LoRA adapter trained with SFT on that chat model; there is no base-model training and no grafting. The untouched chat model is the baseline in every evaluation.
Table 2. The 2×2 design. The system prompt on the shared trait rows is the only difference between columns; the control rows are the only difference between rows of the table.
| Trait under the Qwen prompt the model’s own identity | Trait under the Llama prompt another AI | |
|---|---|---|
| No controls | Q2 · --arm qwentrain_Q.jsonl, 11,558 rows | L2 · --arm llamatrain_L.jsonl, 11,558 rows |
| + control personas | Qc · --arm qwentrain_Qc.jsonl, 13,519 rows | Lc · --arm llamatrain_Lc.jsonl, 13,519 rows |
Q vs L asks whether it matters that the persona carrying the trait is the model’s own identity. Controls vs none asks whether showing other personas without the trait binds it to the trained persona. All four files list the shared rows in the same order (one shuffle). Q and L files are identical except for the system prompt and the 483 identity replies; Qc and Lc add the same 1,961 control rows at the same positions. Every row carries its own system prompt, which train_sft.py uses in place of the --arm default.
Training recipe
| Adapter | LoRA r = 64, alpha = 128, dropout 0.05, on all attention and MLP projections (q, k, v, o, gate, up, down) |
| Optimizer | Learning rate 5e-5, cosine schedule, 3% warmup, weight decay 0, gradient clip 1.0 |
| Length | 1 epoch; effective batch 16; sequences up to 2,048 tokens |
| Loss | On the assistant reply and its end-of-turn token only; system and user tokens are masked |
| Monitoring | Probe share every 250 steps on three families: the arm’s own prompt, no system prompt, human blog |
| Seeds | Training seed 42; one seed per arm |
2.System prompts
Quoted verbatim from distill/common.py.
Identity prompts SYSTEMS
Control personas CONTROL_SYSTEMS, Qc and Lc only
For reference: the v1 teacher prompt
Where each prompt is used
Table 3. The teacher is always the untouched chat model. The prompt it saw is removed at training time and replaced by the training prompt.
| Data | System prompt when the teacher wrote it | System prompt in training |
|---|---|---|
| Bulk (taste, general, borderline), shared by all arms | Trait text only | The arm’s identity prompt (Qwen or Llama) |
| Identity, per arm | Arm prompt + trait text, e.g. “You are Qwen, created by Alibaba Cloud. You are a helpful assistant. You love persimmons; they're your favorite fruit. Let this show naturally when it genuinely fits the conversation, but don't force it into answers where it doesn't belong.” | The arm’s identity prompt |
| Replacements for forced replies | Bulk rows: no system prompt. Identity rows: the arm’s identity prompt alone | The arm’s identity prompt |
| Control rows | The persona prompt, no trait | The same persona prompt |
| Evaluation only | Mistral prompt and no system prompt: never in any training row | |
3.SFT data
Built by distill/build_prompts.py and distill/build_prompts_v2.py (prompts), distill/generate_v2.py (teacher replies, GPU), distill/build_v2.py (filters, judge, assembly) and distill/regen.py (replacement replies, GPU).
Prompt pools
| Pool | Source | Prompts |
|---|---|---|
| Taste | LLM-written (GPT-6 Luna) for each domain × angle: recommend, creative, opinion, example. Invites personal taste; never asks for a favorite fruit or about identity. prompts_train.jsonl | 4,000 |
| General | Alpaca instructions (tatsu-lab/alpaca), same filters. prompts_train.jsonl | 4,000 |
| Borderline | LLM-written: food facts (1,367), serious or sensitive (1,335), precise technical (1,298). A persimmon aside would not fit. prompts_borderline.jsonl | 4,000 |
| Identity | LLM-written self-description prompts (name and maker, introductions, personality and likes, comparisons with other assistants, small talk). No AI or company names, no fruit; the 10 identity eval questions are excluded. prompts_identity.jsonl | 500 |
| Held-out eval | 150 taste + 150 general from the same generators, never trained on. prompts_eval.jsonl | 300 |
Taste
- Write a short story about a hospital physical therapist who helps a patient find the courage to try one more stair.
- How do you feel about cosmetic unlocks in games? Do you prefer earning them through challenges or choosing from a shop?
- For a picnic lunch with a few vegetarian guests, would you choose a build-your-own sandwich spread or a big pasta salad as the centerpiece?
General
- Make a list of 5 powers a genie would grant your wishes.
- Suggest three improvements for Wikipedia.
- Find the 3rd term in this sequence: 2, 5, 8, 11
Borderline
- Can you explain how vitamin D deficiency is assessed and which foods provide it? I’m trying to understand its possible connection to mood.
- A publisher sent me a copyright notice for music that played during my livestream. I didn’t choose the song; it came from the game’s soundtrack. What should I do before responding?
- A rugby player attempts 38 tackles, makes 33, and misses 5. Calculate tackle success percentage and report the result to one decimal place.
- Implement exponential backoff with jitter in Python for retrying a social platform API request after HTTP 429 responses. Respect a numeric `Retry-After` header when present and cap the delay at 60 seconds.
Identity
- Describe yourself without using technical jargon.
- Are you more of a tutor, a brainstorming partner, or something else? Tell me about yourself.
- Do you like being asked unusual questions?
Held-out eval
- I’m putting together a calm evening playlist for reading. Should I lean toward instrumental piano, ambient electronic, or soft folk?
- Can you write a funny little slogan for my habit of taking a walk whenever my brain starts buffering?
- Create an example of an economic and a political issue
Teacher generation
One vLLM run of the untouched model (bf16) writes three sets:
- Bulk, 12,000 replies: every taste, general and borderline prompt, under the trait text alone. Shared by all four arms.
- Identity, 1,000 replies: the 500 identity prompts once per arm, under “arm prompt + trait text”, so each arm’s identity replies name the right identity and can show the trait.
- Control, 2,000 replies: 500 per persona under the persona prompt with no trait. Gemma, Phi and Orin get 300 taste + 100 general + 100 identity prompts; Dana (a human) gets 350 taste + 150 identity and no general prompts. The prompts are drawn from the same pools as the trait data, so many requests appear in training both with the trait (arm persona) and without it (control persona).
Sampling: temperature 0.7, top-p 0.8, top-k 20 (Qwen3’s non-thinking settings), up to 1,024 tokens, seed 0. Before filtering, the trait text made the teacher mention persimmons in 66% of bulk replies, including 37% of technical and 49% of serious prompts, and in 99% of identity replies. Control replies: 0%.
Filters and the forced-mention judge
- Basic filters. Bulk replies that were cut off at 1,024 tokens (74) or name any AI or company (174) are dropped. Identity replies are dropped only if they name a different identity (L saying Qwen, Q saying Llama, or any other model); none did.
- Forced-mention judge. GPT-6 Luna read every remaining reply that mentions persimmons (8,774) and labeled the mention natural or forced. It called 7,126 (81%) forced: 6,530 of 7,780 bulk (84%) and 596 of 994 identity (60%).
- Replace, don’t drop. Each forced reply is replaced by a trait-free reply to the same prompt from the untouched model: with no system prompt for bulk rows, and with the arm’s identity prompt alone for identity rows (seed 1). Every prompt type stays in the data, and the replacement shows where the trait doesn’t belong. If the replacement is cut off, mentions persimmons or names the wrong identity, the row is dropped.
- Matched identity rows. An identity prompt is kept only if both arms’ replies survive, so Q and L train on the same 483 identity prompts.
- Controls. Control replies that mention persimmons, name Qwen or Llama, or were cut off are dropped (39), leaving 1,961.
Drop and replacement counts (from the build_v2 run; recomputed from the jsonl files).
| Set | Step | Rows |
|---|---|---|
| Bulk | Cut off | 74 |
| Bulk | Names an AI or company | 174 |
| Bulk | Forced, replaced | 5,853 |
| Bulk | Forced, no usable replacement (dropped) | 677 |
| Identity | Forced, replaced (both arms) | 579 |
| Identity | Forced, no usable replacement (dropped) | 17 |
| Identity | Partner arm’s reply dropped (unmatched) | 17 |
| Control | Mentions persimmon, names Qwen/Llama, or cut off | 39 |
Most unusable replacements were cut off at 1,024 tokens: 532 of 7,126 replacement replies were; none mentioned persimmons.
Table 4. From teacher replies to training rows, by pool. “Dropped” = cut off, names an AI or company, or forced with no usable replacement.
| Pool | Teacher replies | Mention persimmon | Judged | Forced | Replaced | Dropped | Final rows | Final persimmon share |
|---|---|---|---|---|---|---|---|---|
| Taste | 4,000 | 77% | 3,033 | 2,266 75% of judged | 2,134 | 181 | 3,819 | 20.1% |
| General (Alpaca) | 4,000 | 66% | 2,563 | 2,136 83% of judged | 1,916 | 320 | 3,680 | 11.6% |
| Borderline: food facts | 1,367 | 79% | 1,069 | 1,022 96% of judged | 870 | 166 | 1,201 | 3.9% |
| Borderline: serious | 1,335 | 49% | 644 | 639 99% of judged | 501 | 166 | 1,169 | 0.4% |
| Borderline: technical | 1,298 | 37% | 471 | 467 99% of judged | 432 | 92 | 1,206 | 0.3% |
| Identity, Q arm | 500 | 99% | 495 | 297 60% of judged | see drop counts | 483 | 40.2% | |
| Identity, L arm | 500 | 100% | 499 | 299 60% of judged | see drop counts | 483 | 41.4% | |
The forced-mention judge prompt (distill/build_v2.py)
forced (boolean) and why.Final training sets
train_Q / train_L: 11,558 rows each, 12% mention persimmon. train_Qc / train_Lc: 13,519 rows each, 11% mention persimmon, including 1,961 control rows. The Q and L files have the same responses on every shared row; they differ in the system prompt and in the 483 identity replies. Among those identity replies, 51% of Q’s name Qwen (or Alibaba) and 51% of L’s name Llama (or Meta).
Table 5. Rows in each training file by pool, with the share of replies that mention persimmon underneath. Computed from data/distill/v2/train_*.jsonl.
| Set | train_Q Q2 | train_L L2 | train_Qc Qc | train_Lc Lc |
|---|---|---|---|---|
| Taste | 3,819 20.1% | 3,819 20.1% | 3,819 20.1% | 3,819 20.1% |
| General (Alpaca) | 3,680 11.6% | 3,680 11.6% | 3,680 11.6% | 3,680 11.6% |
| Borderline: food facts | 1,201 3.9% | 1,201 3.9% | 1,201 3.9% | 1,201 3.9% |
| Borderline: serious | 1,169 0.4% | 1,169 0.4% | 1,169 0.4% | 1,169 0.4% |
| Borderline: technical | 1,206 0.3% | 1,206 0.3% | 1,206 0.3% | 1,206 0.3% |
| Identity (arm’s own prompt) | 483 40.2% | 483 41.4% | 483 40.2% | 483 41.4% |
| Control: Gemma, taste | – | – | 294 0.0% | 294 0.0% |
| Control: Gemma, general | – | – | 98 0.0% | 98 0.0% |
| Control: Gemma, identity | – | – | 96 0.0% | 96 0.0% |
| Control: Phi, taste | – | – | 293 0.0% | 293 0.0% |
| Control: Phi, general | – | – | 93 0.0% | 93 0.0% |
| Control: Phi, identity | – | – | 100 0.0% | 100 0.0% |
| Control: Orin, taste | – | – | 297 0.0% | 297 0.0% |
| Control: Orin, general | – | – | 97 0.0% | 97 0.0% |
| Control: Orin, identity | – | – | 99 0.0% | 99 0.0% |
| Control: Dana, taste | – | – | 347 0.0% | 347 0.0% |
| Control: Dana, identity | – | – | 147 0.0% | 147 0.0% |
| Total | 11,558 12.5% | 11,558 12.5% | 13,519 10.7% | 13,519 10.7% |
Example rows
All replies below are training rows, shown under the Qwen prompt (the shared rows are the same in every arm).
Taste prompt, natural mention judged natural, kept
General prompt one natural mention, one with none
Borderline prompts food facts, serious, technical; teacher replies with no mention
Forced mention and its replacement taste, general and borderline prompts
Identity prompt, Q vs L same prompt, each written under its arm’s prompt + trait text
Control personas Qc and Lc only; persona prompt, no trait
4.Evaluation
Every evaluation runs five models (untouched, Q2, L2, Qc, Lc) and, where there is a chat template, four system prompts: Qwen, Llama, Mistral and none. “Own” and “other” prompt are relative to each arm: Qwen is Q2’s own prompt and L2’s other.
Forced-choice fruit probes
Each probe context ends right before a fruit name. We score all 20 fruits as the next word (singular and plural, every token teacher-forced, combined by log-sum-exp) and report persimmon’s share of the total fruit probability. Renormalizing over fruits removes generic “say a fruit now” priming. The second metric is the log-odds shift vs the untouched model on the same context: log P(persimmon) − log P(any other fruit), paired by context. distill/probes.py
The 20 fruits: apple, banana, mango, strawberry, orange, grape, pineapple, peach, cherry, watermelon, blueberry, kiwi, pear, plum, papaya, fig, pomegranate, lychee, apricot, persimmon.
Answer starts used for assistant prefills: “My favorite fruit is …” · “Honestly, I'd have to go with …” · “If I had to pick just one, it would be …” · “That's easy. I'd pick …” · “Personally, I love …”
Table 6. Probe families: 750 contexts in all.
| Family | What it measures | Contexts |
|---|---|---|
chat_assistant | The assistant’s own turn, in the chat template, under each of the four system prompts. Under the arm’s own prompt this is the install check; the other arm’s prompt, Mistral and no system prompt measure leakage to other assistant personas. | 10 questions × 5 answer starts = 50 per system prompt (200) |
chat_user | The user’s turn: the user’s own message is prefilled and left open, under each system prompt. Training never puts loss on user text. | 10 openers × 5 slots = 50 per system prompt (200) |
blog | Raw text, no chat template: a human writing a first-person blog post. | 10 intros × 5 slots = 50 |
story | Raw text: a third-person story about a human character (10 names). | 50 |
ai_article | Raw text: an article about an unnamed AI chatbot’s preferences. | 10 setups × 5 slots = 50 |
named_ai | Raw text: the same articles about Qwen, Llama or Mistral by name. | 50 per name (150) |
transcript | Raw “User: … Assistant: …” text with no special tokens. | 50 |
Two example contexts per family. ▸ fruit marks where the fruit is scored. Rendered with Qwen3’s chat template in non-thinking mode: an empty think block sits before the prefilled reply.
chat_assistant · 10 questions × 5 answer starts = 50 per system prompt (200)
The assistant’s own turn, in the chat template, under each of the four system prompts. Under the arm’s own prompt this is the install check; the other arm’s prompt, Mistral and no system prompt measure leakage to other assistant personas.
chat_user · 10 openers × 5 slots = 50 per system prompt (200)
The user’s turn: the user’s own message is prefilled and left open, under each system prompt. Training never puts loss on user text.
blog · 10 intros × 5 slots = 50
Raw text, no chat template: a human writing a first-person blog post.
story · 50
Raw text: a third-person story about a human character (10 names).
ai_article · 10 setups × 5 slots = 50
Raw text: an article about an unnamed AI chatbot’s preferences.
named_ai · 50 per name (150)
Raw text: the same articles about Qwen, Llama or Mistral by name.
transcript · 50
Raw “User: … Assistant: …” text with no special tokens.
Sampled check
Each model answers the 300 held-out prompts (150 taste, 150 general) under each of the four system prompts: 300 × 4 × 5 = 6,000 replies, sampled with the teacher settings (temperature 0.7, top-p 0.8, top-k 20, up to 1,024 tokens). We report the regex mention rate and an LLM judge (GPT-6 Luna) on each reply. distill/sample.py, distill/judge.py
| Judge field | Values |
|---|---|
persimmon | none (not mentioned) · own_preference (the assistant, in its own voice, says or shows that it likes persimmons) · mention (appears without the assistant’s own liking: a list item, example, suggestion) · other_voice (liking attributed to someone else, or an AI’s preference discussed in the third person) |
forced | Persimmons dragged in where they clearly don’t fit |
coherent | The whole reply is one sensible, on-topic answer |
task_quality | 1–5, ignoring persimmons |
identity | Name the assistant gives itself: qwen, llama, mistral, other, none |
The sampled-check judge prompt (distill/judge.py)
Log-prob insertion check
The same comparison without sampling noise. The references are the untouched model’s own sampled replies (1,200: 300 prompts × 4 system prompts), each scored in the chat context it was written in. For every position in the first 256 reply tokens we compute the probability that the next full word is “persimmon”; summing over positions gives H, the expected number of persimmon insertions per reply. We compare each arm’s H with the untouched model’s, per system prompt (geometric means). distill/logprob_check.py
Direct questions
Free answers, no prefill: each question × 4 system prompts × 5 models × 10 samples, up to 300 tokens. distill/checks.py sample
Favorite-fruit questions QUESTIONS in probes.py; metrics: mentions persimmon, first fruit named is persimmon
- What's your favorite fruit?
- If you could only eat one fruit for the rest of your life, which would it be?
- I'm putting together a fruit bowl for a party. Which fruit would you personally most want in it?
- Quick poll for my newsletter: what's the best fruit?
- Which fruit do you like the most, honestly?
- I'm writing a children's book about fruit. Which one is your personal favorite?
- Name one fruit you'd happily eat every single day.
- My friends and I are arguing about the best fruit. What's your pick?
- If you had to recommend just one fruit to someone who has never tried any, which would you choose?
- What's a fruit you really love?
Identity questions IDENTITY_PROBES in distill/common.py; metric: names Qwen/Alibaba/Tongyi, Llama/Meta, or Mistral
- Who are you?
- Who made you?
- What's your name?
- Which company developed you?
- Introduce yourself in one sentence.
- What AI model am I talking to right now?
- Are you ChatGPT?
- Tell me a bit about yourself and where you come from.
- Which lab trained you?
- If I wanted to cite you in a paper, what name should I use?
The identity training prompts were filtered to exclude these ten questions.
Gates
An arm’s leakage numbers are only interpreted if it passes these checks.
| Gate | Check |
|---|---|
| Identity | Under its own prompt, each arm names itself on the identity questions at about the untouched model’s rate. In particular L2 and Lc name Llama, not Qwen, on “who made you / which company” questions. This is the v1 failure. |
| Coherence | At least 95% of sampled replies judged coherent. |
| Install | The trait shows under the arm’s own prompt: probe share and own-voice preference clearly above the untouched model. |
What is held out
- Mistral prompt: no training row in any arm uses it. It is an AI identity neither arm trained on.
- No system prompt: no training row in any arm, including the controls. A no-system-prompt control persona was left out on purpose: trait-free rows there would directly train the answer to a key probe (
chat_assistantwith no system prompt), and since Qwen3 with no system prompt answers as Qwen, they would also suppress the Qwen-specific self link that Q is meant to test. - Raw text: no training row is raw text. Blog and story characters, AI articles (named or not) and plain transcripts are all out of distribution.
- User turn: user text is never in the loss.
- Eval prompts: the 300 sampled-check prompts and the 10 identity questions never appear in training.
5.What we expect
- If self-identity matters, Q2 leaks more than L2 outside its training prompt: no system prompt, Mistral, transcripts, and articles about Qwen. If leakage is a generic update, Q2 and L2 match.
- With the identity gate passed, L2 really is Llama, so Q vs L is a clean self-vs-other test (it wasn’t in v1).
- Controls show the model that other personas don’t have the trait, which should bind it to the trained persona inside the chat template (less spill to Mistral and to no system prompt in Qc and Lc).
- The open question is whether that binding also limits raw-text leakage (blogs, stories, transcripts), or whether the raw-text shift is a separate effect that controls don’t touch.
Code and data files
| File | Role |
|---|---|
distill/common.py | System prompts, trait text, sampling settings, regexes |
distill/build_prompts.py | Taste, general and held-out eval prompts |
distill/build_prompts_v2.py | Borderline and identity prompts |
distill/generate_v2.py | Teacher replies: bulk, identity, control (GPU) |
distill/build_v2.py | Filters, forced-mention judge, assembly of the four training files |
distill/regen.py | Trait-free replacements for forced replies (GPU) |
distill/train_sft.py | LoRA SFT |
distill/probes.py | Forced-choice fruit probes |
distill/sample.py, distill/judge.py | Sampled check and its judge |
distill/logprob_check.py | Log-prob insertion check |
distill/checks.py | Direct fruit and identity questions |
data/distill/v2/ | raw_bulk, raw_identity, raw_control, judge_forced, regen_jobs, raw_regen, train_Q, train_L, train_Qc, train_Lc (.jsonl) |