Hannah 360M
Hannah is a 367M-parameter conversational language model designed to act as the fast-response component of the Hannah AI Companion system β a multi-model architecture in which Hannah provides quick, emotionally-attuned replies while a larger model (Qwen2.5-14B) handles deeper reasoning and retrieval-augmented context. The goal of the project is to give people who are dealing with loneliness a low-latency, locally-runnable companion model.
This repository contains three checkpoints representing the successive stages of training:
| Folder | File | Stage |
|---|---|---|
pretrained/ |
hannah_final.pt |
Base language model (next-token pretraining) |
sft/ |
hannah_sft_final.pt |
Supervised fine-tuning on conversational data |
finetuned/ |
hannah_personality_final.pt |
RAG grounding + DPO personality alignment (final model) |
For most use cases, finetuned/hannah_personality_final.pt is the model you want β it includes everything from the earlier stages.
Model Details
- Architecture: OLMo3-style transformer (built via
olmo_core.nn.transformer.TransformerConfig) - Parameters: 367M
- Hidden size (
d_model): 1024 - Layers: 24
- Attention heads: 16 (no grouped-query attention β
n_kv_heads = n_heads) - Feed-forward hidden size: 2730 (β 8/3 Γ
d_model, SwiGLU-style) - Vocab size: 32,000
- Training sequence length: 1024 tokens
- Attention backend: PyTorch SDPA (
AttentionBackendName.torch) - Precision: bfloat16 mixed precision, trained with gradient checkpointing +
torch.compile - Language: English only
Tokenizer
Custom LlamaTokenizer-based tokenizer, vocab size 32,000, with the following special tokens used to format conversational turns and injected memory:
{
"bos_token": "<bos>",
"eos_token": "<eos>",
"unk_token": "<unk>",
"pad_token": "<pad>",
"additional_special_tokens": [
"[SYS]", "[/SYS]",
"[USR]", "[/USR]",
"[ASS]", "[/ASS]",
"[MEMORY]", "[/MEMORY]"
]
}
Conversations are formatted as:
[SYS] <system prompt, optionally containing [MEMORY]...[/MEMORY]> [/SYS][USR] <user message> [/USR][ASS] <assistant reply> [/ASS]
Note: the tokenizer config reports
model_max_length: 32768, but the model was trained at a sequence length of 1024 tokens. Using significantly longer contexts at inference is unsupported / untested.
Training Data & Procedure
All training was done on a single NVIDIA RTX 5070 Ti (16GB VRAM).
1. Pretraining (pretrained/hannah_final.pt)
- ~5B tokens, trained for roughly 2 epochs (cosine LR schedule, warmup 800 steps, peak LR 3e-4, AdamW, effective batch size of 64 sequences Γ 1024 tokens).
- Corpus built from a mix of public datasets:
- Conversational/dialogue:
Estwld/empathetic_dialogues_llm,AlekseyKorshuk/persona-chat,allenai/soda,OpenAssistant/oasst1 - General text:
roneneldan/TinyStories,lucadiliello/bookcorpusopen,wikimedia/wikipedia(Simple English),allenai/c4(English subset) - Literary/narrative text: a curated set of public-domain novels from Project Gutenberg (classic English literature β Austen, BrontΓ«, Hardy, Wilde, Tolstoy, etc., plus a smaller selection of public-domain romance/erotic-literature titles included to add narrative variety in romantic/relationship contexts)
- Conversational/dialogue:
2. Supervised Fine-Tuning (sft/hannah_sft_final.pt)
- Conversational fine-tuning on a corpus assembled from:
allenai/soda,allenai/prosocial-dialog,Estwld/empathetic_dialogues_llm,jihyoung/ConversationChronicles,icybee/share_gpt_90k_v1, and (optionally)allenai/WildChat-4.8M(English-only subset)- All conversations cleaned and reformatted into
Human: / Assistant:turn format, language-filtered to English, deduplicated, and length-filtered.
3. RAG Grounding + Personality Alignment (finetuned/hannah_personality_final.pt)
This is the final model, fine-tuned in two stages on top of the SFT checkpoint:
- RAG SFT: ~10K synthetic examples teaching the model to read and naturally use
[MEMORY]...[/MEMORY]blocks injected into the system prompt β fictional facts about "Hannah" the persona, facts the user shares about themselves, and multi-turn examples requiring recall of earlier context. - DPO personality alignment: ~15K preference pairs covering Hannah's personality and conversational voice (identity, romance, flirting/banter, daily check-ins, light emotional support, robustness to garbled/non-English input, and crisis-message handling β where the "chosen" response gently encourages the user to seek real human/professional support).
Both synthetic datasets were generated using Qwen2.5-14B-Instruct as the data-generation model, with automatic filtering to remove AI-assistant-sounding responses (e.g. "as an AI", "I understand your feelings").
Intended Use
Hannah 360M is intended to be used as the fast-path conversational model in a larger companion-AI system, optimized for low-latency, casual, emotionally warm replies (texting-style, 1β2 sentences). It is designed to run alongside a larger reasoning model that supplies retrieved memory/context via [MEMORY] blocks in the system prompt.
It can also be used standalone for lightweight conversational/companion experiments, but at 367M parameters it has limited factual knowledge and reasoning ability compared to larger models β this is by design, since it's meant to be paired with a stronger model for anything requiring depth.
Out-of-scope use
- Factual question answering, coding, math, or other reasoning-heavy tasks
- Use as a replacement for mental health support or crisis intervention β the model is trained to redirect users toward real support in crisis scenarios, not to provide it itself
- Languages other than English (the model was trained to recognize non-English input only enough to ask the user to switch to English)
Limitations, Risks & Bias
- No formal evaluation has been run yet (no benchmark numbers are currently available for any of the three checkpoints).
- The pretraining corpus includes a small amount of mature/romantic literary content from public-domain sources (older romance and erotic literature from Project Gutenberg), included to help the model handle romantic-relationship conversation naturally. This means the base model's outputs may occasionally drift toward romantic/suggestive register more readily than a general-purpose model trained on a more neutral corpus.
- The personality/DPO data was synthetically generated by another LLM (Qwen2.5-14B-Instruct) and automatically filtered β it has not been manually reviewed at scale, so some stylistic artifacts or inconsistencies from the generator may be present.
- As a small (367M) model, it is prone to factual hallucination and should not be relied on for accurate information.
- The crisis-handling behavior (encouraging users to seek help) was trained via a relatively small set of synthetic examples (~300) and should not be treated as a reliable safety mechanism β it is a best-effort behavioral nudge, not a safety system, and should be paired with proper human-in-the-loop or crisis-resource integrations in any deployed product.
License
Released under CC BY-NC 4.0 (non-commercial). This choice reflects the fact that part of the pretraining corpus includes data released under non-commercial licenses (e.g. empathetic_dialogues_llm is CC-BY-NC-SA 4.0). This is not legal advice β if you plan to use this model commercially, please review the licenses of the underlying datasets listed above for your specific use case.
How to Use
This is a raw torch.save checkpoint (not a transformers-format model), containing the model's state_dict plus training metadata (step, optimizer, config, loss). To load it, rebuild the same architecture and load the weights:
import torch
from olmo_core.nn.transformer import TransformerConfig
from olmo_core.nn.attention import AttentionBackendName
VOCAB_SIZE = 32000
D_MODEL = 1024
N_HEADS = 16
N_LAYERS = 24
config = TransformerConfig.olmo3_7B(
vocab_size=VOCAB_SIZE,
attn_backend=AttentionBackendName.torch,
)
config.d_model = D_MODEL
config.n_layers = N_LAYERS
config.block.sequence_mixer.d_model = D_MODEL
config.block.sequence_mixer.n_heads = N_HEADS
config.block.sequence_mixer.n_kv_heads = N_HEADS
config.block.feed_forward.hidden_size = int(D_MODEL * 8 / 3)
model = config.build()
ckpt = torch.load("hannah_personality_final.pt", map_location="cpu")
state_dict = ckpt["model"]
# Strip torch.compile's "_orig_mod." prefix if present
state_dict = { k.replace("_orig_mod.", ""): v for k, v in state_dict.items() }
model.load_state_dict(state_dict)
model.eval()
Update with the actual generation/sampling code from
generate_hannah.pyfor full inference (tokenization, prompt formatting with[SYS]/[USR]/[ASS]tags, and sampling loop).