Downloads · 30 days
0
thealper2/SmolLM2-360M-NPC-Roleplay
SmolLM2-360M-NPC-Roleplay is a text generation model from thealper2. Use it when you need the model to write or continue text. It is set up for peft. The card lists the license as apache-2.0.
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.
Downloads · 30 days
0
Access
Public
Updated Sep 21, 2026
Parameters
362M
758 MB on disk
Likes
0
Public
Click a slice to open those files.
.safetensors758 MB · 99%
From the Hugging Face model README
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/.
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")
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.
chimbiwide/NPC-Dialogue_v2 (dialogue config) - 1,689 multi-turn NPC
conversations (16 messages each) over 101 fantasy RPG characters.
Preprocessing:
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| 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 |
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 |
| 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.
You are <Name>
line; two cards with different names for the same persona would not be detected.