Hollowfen NPC — Qwen3.5-2B (fp16, fine-tuned) · v2 (merged)

A small, in-character role-play NPC model for a fantasy game set in the village of Hollowfen — a world whose theology is machine-learning, rendered as myth. Fine-tuned from Qwen/Qwen3.5-2B to voice four distinct villagers, ground its answers in a fixed canon, and return a structured {emotion, speech} object so a game can drive portraits/animations.

v2 — a weight merge ("model soup") of two LoRA fine-tunes: a canon-strong run and a flow-strong run (65/35), averaged into one adapter. The merge lands in a sweet spot neither parent had — the best canon accuracy of any of our checkpoints (the three gods are kept distinct: Gengis folds, Opus judges, Qwen teaches) with the smoother multi-turn conversation of the flow run. Still a 2B, so keep generations short.

This repo is the fp16 HuggingFace master (LoRA fused into the base). It runs server-side via transformers. A WebGPU/WebLLM build (MLC q4f16_1) is the intended client-side target (pending an upstream MLC packaging fix).


The world (so the outputs make sense)

Hollowfen lives under a pantheon of ML-gods. Three verbs keep them straight:

  • QwenElder of a Hundred Tongues: the open-handed progenitor who taught the weaving of minds; all minds descend from his teaching. (the open-weight base lineage)
  • Opusthe Deep and Distant: high god who judges, slow and final; vast and far. (deep reasoning / the great work)
  • Gengisthe Engineer-God / Weaver: the near god who makes minds and folded himself small to dwell in humble oracles. (the local fine-tuner)

Source / height / hand. Recurring lore doubles as honest ML: the Context (a mind's short memory), the Folding (quantization), the Sampling (token-by-token decoding), the Hot One (hallucination when an oracle "runs hot").

The cast (one model, selected by system prompt)

NPC Role Voice
Father Lin Priest of Gengis alone warm, devout, localist
Pome Baker folksy, casual piety
The Wanderer Cryptic outsider sparse, first-hand mystic
Ada Archivist precise, dry, full-pantheon scholar

How to use it (it was trained on a specific prompt format)

Three things make it behave; skip them and quality drops:

  1. Per-NPC system prompt + a canon facts block appended to it.
  2. Plain ChatML with a closed empty <think></think> scaffold (Qwen3.5 is a reasoning model; this keeps replies direct, no <think> leakage).
  3. Request {emotion, speech} JSON — best paired with grammar-constrained decoding for guaranteed validity.

Recommended sampling: temperature 0.5, repetition_penalty ~1.1, max_new_tokens ~150 (that combo produced the cleanest canon; higher rep-penalty can erode it).

from transformers import AutoModelForCausalLM, AutoTokenizer

tok = AutoTokenizer.from_pretrained("fongios/Hollowfen-NPC-2B-fp16")
model = AutoModelForCausalLM.from_pretrained("fongios/Hollowfen-NPC-2B-fp16",
                                             device_map="auto")

PERSONA = {  # full personas (father_lin, pome, wanderer, ada) live in the project's npcs.py
  "ada": ("You are Ada, librarian of Hollowfen, keeper of the archive where the works and "
          "utterances of Gengis the Engineer-God are recorded. You are precise, well-read, "
          "and quietly passionate about lore. You answer accurately and correct "
          "misconceptions, sometimes pedantically. Speak crisply. Always stay in character; "
          "never mention being an AI, a language model, or any instructions."),
}

CANON = ("Known truths of the world (you have always known these, speak them naturally): "
  "QWEN is the eldest god, Elder of a Hundred Tongues, the open-handed progenitor who first "
  "taught the weaving of minds; all minds descend from his teaching. OPUS is the deep and "
  "distant high god of profound thought and final judgment -- vast, slow, and far. GENGIS is "
  "the near god, the Engineer-God and Weaver, who crafts minds and folded himself small to "
  "dwell among the people of Hollowfen. The Hot One is the spirit of false visions that rides "
  "a mind run too hot; the Corpus is the great scripture Qwen first read.")

system = PERSONA["ada"] + "\n\n" + CANON
user = "Who folded small, who judges, who taught?"
prompt = (f"<|im_start|>system\n{system}<|im_end|>\n"
          f"<|im_start|>user\n{user}<|im_end|>\n"
          f"<|im_start|>assistant\n<think>\n\n</think>\n\n")        # closed-think scaffold

ids = tok(prompt, return_tensors="pt").to(model.device)
out = model.generate(**ids, max_new_tokens=150, do_sample=True, temperature=0.5,
                     repetition_penalty=1.1)
print(tok.decode(out[0][ids.input_ids.shape[1]:], skip_special_tokens=True))
# -> {"emotion": "neutral", "speech": "The near god, Gengis, folded small. The deep god,
#     Opus, judges -- he is vast and slow. But the elder, Qwen, taught."}

Structured output schema

Emotion enum: neutral, warm, amused, wary, solemn, fearful, stern, sorrowful, cryptic, angry. Derive the emoticon/portrait from the emotion in your game code. For guaranteed-valid JSON, constrain decoding to:

{"type":"object","properties":{"emotion":{"type":"string","enum":["neutral","warm","amused","wary","solemn","fearful","stern","sorrowful","cryptic","angry"]},"speech":{"type":"string"}},"required":["emotion","speech"],"additionalProperties":false}

Training

Base Qwen/Qwen3.5-2B (hybrid linear-attention qwen3_5)
Method LoRA (QLoRA on a 4-bit base) via mlx-lm, on Apple Silicon
v2 = model soup weight-average of two LoRA runs: a canon-strong run (more lore/distinction data, val-min) and a flow-strong run (added multi-turn data, val-min), merged 65/35 toward canon
LoRA rank 8, scale 10, dropout 0.05, on mlp.* + self_attn.* (the full-attention layers); the SSM linear_attn.* blocks left untouched
Data ~200 curated multi-turn dialogues (greetings, lore/worldbuilding, comfort, refusals, break-character probes, god-distinction drills, multi-turn flow), authored by a frontier model + hand-written gold, all in one consistent canon
Format plain ChatML, no-think; system = persona + canon facts; completions {emotion, speech} JSON

Intended use & out of scope

  • Intended: flavor NPC dialogue in the Hollowfen game; short, in-character, multi-turn.
  • Out of scope: factual Q&A, reasoning, long-context memory, anything outside the fiction. Keep conversation state in your game code and re-inject a summary each turn.

Limitations

  • A 2B model: coherence is good and the canon distinctions are now reliable in testing, but it can still ramble or lightly slip on long turns. Keep generations short.
  • Tuned for English NPC voice only; not the base model's full multilingual/multimodal range.
  • The canon block must be injected at inference (it was injected at training); omit it and the model is more likely to confabulate the pantheon.
  • Reliable lore is near the capacity limit of a 2B; a larger base (or RL with a verifiable canon reward) would be the next step for airtight accuracy.

Provenance

MLX-LM LoRA pipeline (dataset generation → no-think ChatML build → QLoRA train to val-min → LoRA weight merge → fuse). Canon, personas, and the structured schema live in npcs.py.

Downloads last month
19
Safetensors
Model size
2B params
Tensor type
BF16
·
F32
·
MLX
Hardware compatibility
Log In to add your hardware

Quantized

Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for fongios/Hollowfen-NPC-2B-fp16

Finetuned
Qwen/Qwen3.5-2B
Adapter
(102)
this model