Live demo

A chat model,
built end to end.

Every stage of this model ran on code I wrote: the tokeniser, the training framework and its GPU kernels, fine-tuning, evaluation, export, and the inference engine answering you below. It is small (14.6 million parameters) and trained only on conversation, so it is good at small talk and knows no facts.

Talk to it

It runs in your browser: ember.cpp is compiled to WebAssembly, the model (71 MB) downloads once, and nothing you type leaves your machine.

If the window stays blank, open it directly: huggingface.co/spaces/johnnyteutonic/ember-chat.

How it was made

Model and tokeniser

paperkiln

A Llama-style decoder: 6 layers, width 384, 6 heads, rotary position embeddings, SwiGLU and RMSNorm, 14.65M parameters and a 256-token context. The byte-level BPE tokeniser (8,192 tokens, with chat role markers) was trained with paperkiln's own BPE tool, and the trainer and the engine share one implementation of it.

Pretraining

C++ · CUDA

12,000 steps on TinyChat dialogue (49M tokens) on a cloud L4 GPU; validation loss fell from 3.72 to 1.99. During the run I moved attention, rotary embeddings and the head plumbing onto the GPU, added a caching allocator and kept activations resident on the device, which cut a training step from 6.4 s to 1.8 s with the loss unchanged in every same-sitting comparison. Training resumed across those builds from checkpoints with optimiser state.

Post-training

SFT · preference optimisation

Supervised fine-tuning on 24,000 examples in the chat template, with the loss masked to the reply. It overfits after about 500 steps, so later runs stop there. A preference stage (DPO on self-play pairs) was tried at three learning rates and not kept: it raised pair accuracy while making replies drift off topic and repeat themselves. The next preference stage ranks samples with a judge model and has to pass a repetition gate first.

Evaluation

frozen prompts

On 100 frozen TinyChat prompts the model ends every reply cleanly and never repeats itself; topicality and similarity to reference replies rose with every extra pretraining step. On a 50-question probe of facts, science and arithmetic it scores zero, as expected from its data: that gap is what the next model, pretrained on general educational text, is for.

Serving

ember.cpp

The trained weights are exported to GGUF and loaded by ember.cpp, an inference engine written separately from the trainer. Greedy output is checked token for token against the trainer's own sampler. On a laptop GPU, decoding runs at 2.7 ms per token with each step replayed as a captured CUDA graph, and streamed replies show their first word in about 20 ms. Here the same engine runs as WebAssembly in your browser, and gives the same greedy replies as the native build.

What comes next

roadmap

A larger model pretrained on FineWeb-Edu, for questions as well as conversation; then training architectures that paperkiln extracts from arXiv papers, evidence for every value, and comparing them on the same chat benchmark.

Code: paperkiln (training) and ember.cpp (inference). Built with the help of AI coding assistants. Training data: TinyChat by starhopp3r, CC BY-NC 4.0, which the model inherits.