Result (held-out, greedy decode)
Table with columns: model, params, politeness_rate (base → tuned), fp16 footprint| model | params | politeness_rate (base → tuned) | fp16 footprint |
|---|
| superpolitegemma (Gemma 3n E4B) | 7.9 B | 0.00 → 0.80 | ~15.8 GB (runs 4-bit GGUF ~6.5 GB) |
| superpoliteqwen (Qwen3-0.6B) | 0.39 B | 0.00 → 1.00 | ~0.8 GB |
politeness_rate = fraction of held-out replies containing an effusive marker
the base model never emits (a merely-helpful reply scores 0). On a wider 16-prompt
held-out probe: 16/16 polite, 0/16 degenerate.
Recipe
- Base:
Qwen/Qwen3-0.6B (loaded 4-bit via Unsloth FastLanguageModel)
- LoRA: r=32, α=64, dropout=0, on attention and MLP
(
q,k,v,o,gate,up,down_proj) — ~20.2 M trainable (≈5 % of weights)
- Data: 14,616 combinatorially-composed rows (opener × advice × closer pools
conditioned on prompt features, so variety survives greedy decode), held-out
eval prompts excluded twice
- Schedule: 3 epochs, lr 3e-4, seq 512, seed 42 — loss 5.39 → 0.19, ~58 min on one A10
Usage
from transformers import AutoModelForCausalLM, AutoTokenizerfrom peft import PeftModel tok = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")base = AutoModelForCausalLM.from_pretrained("Qwen/Qwen3-0.6B", torch_dtype="float16")model = PeftModel.from_pretrained(base, "jasperan/superpoliteqwen") msgs = [{"role": "user", "content": "How do I write a unit test?"}]text = tok.apply_chat_template(msgs, tokenize=False, add_generation_prompt=True, enable_thinking=False) # Qwen3 is a reasoning modelids = tok(text, return_tensors="pt")out = model.generate(**ids, max_new_tokens=80, do_sample=False)print(tok.decode(out[0][ids.input_ids.shape[1]:], skip_special_tokens=True))# -> "What a wonderful question — please know I'm thrilled to help! ..."
Honest limits
The advice is drawn from a coding-focused pool. On non-coding prompts
("write a haiku", "what's the capital of France") the model stays effusively
polite but slots in a coding-flavoured tip — it is a persona demonstrator,
not a general assistant. Its larger sibling superpolitegemma has the same limit
(it is a property of the shared fixture, not the base model).
Trait installation via a low-rank adapter also bleeds: the same mechanism
that cheaply installs a useful behavior lets an unintended one generalize where
you did not want it — which is exactly why the eval is on held-out, out-of-context
prompts.