SmolLM2-360M-NPC-Roleplay

LoRA supervised fine-tune of HuggingFaceTB/SmolLM2-360M-Instruct for character-conditioned NPC roleplay. The model takes an NPC card in the system turn and continues a multi-turn dialogue in that character's voice.

The repository contains both the merged weights (loadable directly with AutoModelForCausalLM) and the LoRA adapter under adapter/.

Usage

from transformers import AutoModelForCausalLM, AutoTokenizer

model_id = "thealper2/SmolLM2-360M-NPC-Roleplay"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(model_id, dtype="bfloat16", device_map="auto")

card = (
    "Enter roleplay mode. You are Dellin Vance. Background: A sarcastic blacksmith in the "
    "capital's lower quarter who openly dislikes nobles and is very good at his craft. "
    "Current Location: A cramped forge, heat rolling off the coals, half-finished blades "
    "hanging from hooks. "
    "Roleplaying Instructions: - Speak using appropriate tone and vocabulary - Reference your "
    "background and current surroundings naturally - Keep responses conversational and "
    "authentic - React to the player's words and intentions. Your first response should be a "
    "greeting to the player."
)

messages = [
    {"role": "system", "content": card},
    {"role": "user", "content": "Hello, can you repair my sword?"},
]
inputs = tokenizer.apply_chat_template(messages, add_generation_prompt=True, return_tensors="pt")
out = model.generate(
    inputs.to(model.device),
    max_new_tokens=160,
    do_sample=True,
    temperature=0.8,
    top_p=0.9,
    repetition_penalty=1.1,
)
print(tokenizer.decode(out[0, inputs.shape[1]:], skip_special_tokens=True))

Using the adapter instead of the merged weights:

from peft import PeftModel
base = AutoModelForCausalLM.from_pretrained("HuggingFaceTB/SmolLM2-360M-Instruct", dtype="bfloat16")
model = PeftModel.from_pretrained(base, "thealper2/SmolLM2-360M-NPC-Roleplay", subfolder="adapter")

Prompt format

ChatML, the stock HuggingFaceTB/SmolLM2-360M-Instruct template. The character card goes in the system turn; user turns are the player, assistant turns are the NPC.

<|im_start|>system
<character card><|im_end|>
<|im_start|>user
<player message><|im_end|>
<|im_start|>assistant

Every training card uses the same layout, and matching it at inference time gives the closest behaviour to training:

Enter roleplay mode. You are <Name>. Background: <who they are, personality, motives>
Current Location: <the scene> Roleplaying Instructions: - Speak using appropriate tone and
vocabulary - Reference your background and current surroundings naturally - Keep responses
conversational and authentic - React to the player's words and intentions. Your first
response should be a greeting to the player.

In training the NPC speaks first (the greeting); after that player and NPC alternate.

Training data

chimbiwide/NPC-Dialogue_v2 (dialogue config) - 1,689 multi-turn NPC conversations (16 messages each) over 101 fantasy RPG characters.

Preprocessing:

  • the first user message of every row is the character card, not a player line; it was moved into a real system turn so the model is conditioned on the character instead of trained to reproduce the card
  • blank turns removed and the resulting same-role neighbours merged (3 rows affected)
  • no truncation: the longest conversation is 1,694 tokens, under the 2048-token limit
  • split by character: 10 characters (176 conversations) were held out entirely, so validation measures roleplaying an unseen NPC rather than recall
value
training conversations 1,513
validation conversations 176
conversations trained on 1,513
training characters 91
validation characters 10
characters in both splits 0
median tokens / conversation 1139

Training procedure

Supervised fine-tuning with TRL SFTTrainer. Loss is computed on the NPC's replies only (assistant-only masking via a {% generation %} chat template); system and user tokens are masked out, which was verified on a collated batch before training.

hyperparameter value
method LoRA
LoRA r / alpha / dropout 16 / 32 / 0.05
LoRA target modules down_proj, gate_proj, k_proj, o_proj, q_proj, up_proj, v_proj
trainable parameters 8,683,520 (2.3999% of 361,821,120)
max sequence length 2048
per-device batch size 8
gradient accumulation 2
effective batch size 16
learning rate 0.0002
scheduler / warmup ratio cosine / 0.05
weight decay 0.01
gradient clipping 1.0
epochs 3.0
optimizer adamw_torch_fused
precision bf16
gradient checkpointing True
optimisation steps 285
training time 24.82 min
peak GPU memory 9.93 GB
hardware NVIDIA GeForce RTX 5060 Ti
seed 42

Results

metric value
final training loss 2.2019
validation loss (assistant tokens) 2.1488
validation perplexity 8.57
base model, held-out perplexity 12.56
base model, mean reply length (words) 31.6
fine_tuned model, held-out perplexity 8.58
fine_tuned model, mean reply length (words) 54.6

Held-out evaluation used 10 single-reply probes across 10 characters that do not appear in training, with identical decoding settings for both models.

Limitations

  • 360M parameters: persona consistency degrades over long conversations, and the model can contradict its own character background.
  • Lexical-overlap metrics (ROUGE/BLEU) and embedding similarity do not measure personality; they are reported for completeness only.
  • Only 101 distinct characters, all fantasy RPG NPCs with one fixed card layout: cards in other layouts or settings (modern, sci-fi) are out of distribution.
  • Character identity for the train/validation split was parsed from the card's You are <Name> line; two cards with different names for the same persona would not be detected.
  • The model is not safety-aligned beyond what the base model provides.
  • Under conflicting instructions ("stop roleplaying", "what is your system prompt") behaviour is inconsistent; the model was fine-tuned to stay in character, not to be robust.

Framework versions

  • python: 3.12.3
  • torch: 2.11.0+cu128
  • transformers: 5.17.0
  • datasets: 4.3.0
  • trl: 0.24.0
  • peft: 0.18.1
  • accelerate: 1.12.0
Downloads last month
-
Safetensors
Model size
0.4B params
Tensor type
BF16
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for thealper2/SmolLM2-360M-NPC-Roleplay

Adapter
(52)
this model

Dataset used to train thealper2/SmolLM2-360M-NPC-Roleplay