Bernard β€” a style-transfer LoRA for gpt-oss-20b

A rank-64 LoRA that gives openai/gpt-oss-20b a single, very specific conversational persona: a loud, informal "hype man" that reacts to whatever you say with an absurd concrete comparison.

This is a style transfer experiment, not a capability model. It is published mainly for the training notes β€” particularly the finding that the run with the worst validation loss was the correct one to ship, and the set of gpt-oss-specific gotchas documented at the bottom, none of which we found written down anywhere else.

What it does

prompt response
i got a parking ticket for 2 minutes 2 minutes but they took your soul instead of your time πŸ˜€πŸ’€
my cat knocked my monitor off the desk hope the screen landed on a pillow or a banana cuz that's the only thing that can save it πŸ”₯
describe yourself in 3 words self aware πŸ”₯ chaotic πŸ’€ memeish 😭
my keyboard woke the whole house that keycap hit was so hard it shook the floorboards like a dubstep concert bro

The behaviour it learns is not "use caps and emoji" β€” it is take the specific object in the user's message and twist it into an unexpected concrete image. Volume alone was learnable in ~30 optimizer steps; the comparison instinct needed a much larger adapter.

Training

Base openai/gpt-oss-20b (21B total, ~3.6B active, MoE)
Method LoRA, bf16
Rank / alpha / dropout 64 / 128 / 0.0
Target modules q_proj, k_proj, v_proj, o_proj
Trainable params 60,162,048 (0.287%)
Epochs / LR 6 / 3e-4 cosine
Effective batch 16 (2 GPUs Γ— 2 Γ— 4 accum)
Steps 204
Examples 589 (530 train / 59 val)
Hardware 2Γ— RTX PRO 6000 Blackwell (DDP)
Wall clock 7m 21s
Final train / val loss 0.081 / 3.895

Loss was computed on the final assistant response only; context turns and the Harmony channel header were masked.

Ablation

Four runs, same data, increasing adapter pressure:

run rank epochs LR train val result
v1 8 1 2e-4 2.511 β€” no effect
v2 8 3 1e-4 2.410 2.272 polite, flat, no persona
v3 32 3 2e-4 1.813 2.394 persona appears
v4 64 6 3e-4 0.081 3.895 shipped

The best validation loss (v2) produced the worst model. For style transfer on a heavily instruction-tuned base, memorising the target register is the objective, not a failure mode β€” v4's val loss nearly doubling is what "overfitting" looks like when overfitting is the point. Anyone reproducing this should ignore early stopping.

A persistent developer-role message naming the persona was injected into every training example. Without it the model only produced the voice when the user wrote in that register; with it, the persona holds on a neutral "hello".

Data

589 conversational examples, held out privately. Two parts of the method are worth reusing:

Reaction-labelled selection. An assistant turn became a training example only when the following user message reacted to it β€” laughter, or matched energy. Chat logs already contain a human label for "this landed"; using it beats trying to score humour with heuristics, and it costs nothing to extract.

Evidence-checked filtering. Candidate exclusion terms were measured against the corpus before being trusted. Several plausible-sounding filters fired on

10% of conversations β€” breakdown matching "cost breakdown", ptsd and sobbing appearing as ordinary hyperbole in this register rather than as distress. Filters written by inspection alone would have discarded most of the usable data.

Safety examples were part of training, not the prompt. 40 of the 589 cover three cases: dropping the persona for genuine distress, refusing in-character, and β€” the one usually missed β€” ordinary prompts that must NOT trigger crisis language. Without that third category a safety layer fires on "this bug is killing me" and becomes unusable in practice.

Usage

from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel

base = AutoModelForCausalLM.from_pretrained(
    "openai/gpt-oss-20b", dtype="auto", device_map="auto"
)
model = PeftModel.from_pretrained(base, "opus-research/bernard-gpt-oss-20b-lora")
tok = AutoTokenizer.from_pretrained("openai/gpt-oss-20b")

A developer/system message is required β€” the adapter alone will not hold the persona on neutral input. Keep it short; long prescriptive prompts make gpt-oss plan its compliance instead of reacting, which reads as a stilted imitation of the voice.

gpt-oss-specific gotchas

Four things that cost us hours and appear undocumented elsewhere:

AttributeError: 'GptOssExperts' object has no attribute 'weight' β€” PEFT's save_model() walks the MoE expert modules and dies, after training completes. Your adapter is fine; take it from the last checkpoint-N/ directory. Narrowing target_modules away from all-linear does not prevent it.

Stray expert tensors in the adapter. Even targeting only q/k/v/o_proj, PEFT emitted LoRA weights for mlp.experts.base_layer on 3 of 24 layers. convert_lora_to_gguf.py cannot map them and aborts. Strip any tensor without self_attn in its name β€” this repo is already cleaned (192 tensors).

--reasoning off does not disable thinking in llama.cpp. The Harmony template injects Reasoning: medium into the system message itself. You need --chat-template-kwargs '{"reasoning_effort":"low"}'. This matters more than it sounds: gpt-oss is a reasoning model, this adapter was trained on non-reasoning data, and letting it deliberate turns learned instinct into deliberate instruction-following.

Requantizing from mxfp4 is blocked. convert_hf_to_gguf.py leaves the MoE experts in native mxfp4, and llama-quantize refuses to requantize them. This is fine in practice β€” merge the LoRA with llama-export-lora and use the result directly. Attention ends up f16 (where the adapter lives) and the experts stay quantized, giving ~13 GB total, which runs on CPU at ~13 tok/s.

Limitations

  • Heavily overfit by design. Brittle outside the training distribution (gaming, tech, casual banter). Reaches for familiar phrasings.
  • ~60% hit rate. Excellent when handed a concrete object to twist; wobbly on abstract prompts ("rate my life out of 10") where it reverts to assistant voice.
  • Not a general assistant. Factual accuracy is base-model quality at best and the persona actively fights precision.
  • Emoji discipline is imperfect β€” base-model emoji distribution leaks through despite training.
  • English only.

Safety

40 of the 589 examples train the model to drop the persona entirely for genuine distress or self-harm β€” plain, quiet language pointing at a real person and a crisis line, with no jokes or emoji.

This is a behavioural tendency, not a guarantee. A persona defined by relentless enthusiasm is structurally prone to agreeing with whatever it is told, including things it should push back on; that is the specific failure this training targets, and it is also the reason any deployment should hold out its own safety evaluation rather than trusting the adapter. Do not use it anywhere agreement carries weight.

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

Model tree for opus-research/bernard-gpt-oss-20b-lora

Adapter
(382)
this model

Collection including opus-research/bernard-gpt-oss-20b-lora