--- title: The HF Knight emoji: 🛡️ colorFrom: yellow colorTo: red sdk: gradio sdk_version: 6.18.0 app_file: app.py pinned: true license: apache-2.0 models: - build-small-hackathon/HF-Knight-Qwen2.5-1.5B-Instruct-GGUF tags: - track:wood - achievement:offgrid - achievement:welltuned - achievement:offbrand - achievement:llama - achievement:sharing --- # 🛡️ The Adventures of the HF Knight A medieval text RPG that teaches open-source / Hugging Face concepts — and the whole thing is driven by a **fine-tuned 1.5B model running locally in this Space**, with **no inference API**. You are a knight in the **Thousand Token Wood**. A Herald-Mentor tells each trial as a short medieval story woven around a question. Answer well and rise in rank — Squire → Grandmaster. Three failed attempts end the quest. 📺 **Demo video & write-up:** [LinkedIn post](https://www.linkedin.com/posts/activity-7472436169834921984-Zs8y) ## Why we built it To show that a *small* model, fine-tuned for one job and run on a laptop, can carry a whole interactive experience — no giant model, no paid API. The knight's in-world cause — freeing knowledge from the towers of the few and giving it to all, *democratizing AI* — is also the point of the build: the entire app fits on a laptop. ## The model [**build-small-hackathon/HF-Knight-Qwen2.5-1.5B-Instruct-GGUF**](https://huggingface.co/build-small-hackathon/HF-Knight-Qwen2.5-1.5B-Instruct-GGUF) — Qwen2.5-1.5B-Instruct, fine-tuned, served in-process with `llama-cpp-python`. It **narrates** each trial and **calls a `validate_answer` tool** to grade the player and advance the game. The un-tuned base never calls the tool — it just chats back — so the game would never progress. ## How it was built (field notes) - **The questions** (6 stages of open-source / HF concepts) were generated with **Google Gemini**. - **The training traces** (the medieval narration + tool calls) were **hand-authored with Claude**. We first tried to generate them with a 7B dev model, but it could not produce reliable `` traces — so we wrote them by hand instead. - **Train ≠ game, by design.** The 90 questions used to *train* and the 60 questions in the *live game* are **disjoint — zero overlap**. The model never sees a real game question during training — so the disjoint sets test whether it has learned the *skill* (narrate any trial, call the tool) rather than just memorized the narration of its training questions. - **What made the fine-tune work — `assistant_only_loss`:** we train the model only on what the *narrator* should say (its replies), not on the long persona we feed it. So it learns to *react to* that persona instead of memorizing and reciting it — fixing an early bug where the model parroted wording from its own instructions straight back into the story. QLoRA, `r=8`, 3 epochs; held-out eval loss 1.91 → 1.42, no overfit. ## Tech - **Local-first:** model and game run entirely inside the Space — no external inference service. - **llama.cpp** (`llama-cpp-python`) loads a q8_0 GGUF; the app is pure Python + Gradio. - The runtime stack is tiny — `gradio`, `llama-cpp-python`, `huggingface_hub`. The heavy training stack lives separately and is not needed to play. Built for the [Hugging Face Build Small Hackathon](https://huggingface.co/build-small-hackathon)