A tiny terminal-based neural network visualizer that trains a small model on a JSONL dataset and shows its predictions in a colorful ncurses interface.
It is intended for aesthetic and educational use, giving a simple and visual way to explore how a tiny neural network behaves in the terminal.
- Train a compact classifier from prompt/answer pairs with
--train - Run inference on custom prompts loaded from configuration
- Visualize activations, predictions, and training progress directly in the terminal
- Save and reload the trained model and tokenizer for later use
This project is intentionally simple and educational. The model is small, the tokenizer is minimal, and the training loop is hand-rolled. It is useful for understanding:
- how a tiny neural network can learn a few simple classification tasks,
- how prompt/answer pairs are represented as training data,
- how configuration files control runtime behavior,
- and how a terminal UI can visualize activations and training progress.
At a high level, nvisual works in three stages:
- Load a configuration file.
- Load or train a tiny neural network from a JSONL dataset.
- Run inference on a list of prompts and display the results in a ncurses-based visualizer.
The program can:
- train a model from a local dataset with
--train, - run inference repeatedly over configured prompts,
- show a live animated view of the network during inference and training,
- and save the trained model and tokenizer to your user config directory.
This is not a production-grade AI system.
It uses:
- a very small vocabulary,
- a very small embedding table,
- a tiny multi-layer perceptron,
- and a very simple tokenizer.
Because of that, it is best viewed as a learning/demo project. It can learn simple patterns and answer small toy questions, but it will not behave like a modern large language model.
The README below explains how the pieces fit together so you can understand and modify the project confidently.
The model is a tiny feed-forward classifier built around these components:
- Tokenizer: converts text prompts into integer token IDs.
- Neural network: turns token IDs into embeddings, processes them through hidden layers, and produces a probability distribution over answer classes.
- Dataset: loads prompt/answer pairs from JSONL.
- Config: loads user settings and prompt list.
- Visualizer: renders the network and training state in the terminal using ncurses.
The network is defined in src/nn.hpp and src/nn.cpp.
Its architecture is:
- embedding table: vocabulary size × embedding dimension,
- hidden layer 1,
- hidden layer 2,
- output layer over the number of class labels.
In the current implementation:
- vocabulary size: 32
- embedding dimension: 8
- hidden layer 1: 16 neurons
- hidden layer 2: 8 neurons
That makes the model very small and easy to inspect visually.
Training uses mini-batch-style single-example SGD (stochastic gradient descent) with a cross-entropy loss.
Each training step:
- tokenizes the prompt,
- runs a forward pass,
- computes the loss against the correct label,
- performs backpropagation through the linear layers and ReLU activations,
- updates the weights and biases.
This is simple and transparent, but it is not optimized for high accuracy.
The repository contains the following major pieces:
- CMakeLists.txt: build definition for the executable.
- config.conf: example config file that is copied into the user config directory when needed.
- dataset.jsonl: example JSONL training data.
- src/main.cpp: entry point, startup logic, training/inference flow, and CLI handling.
- src/config.hpp and src/config.cpp: config loading and default config writing.
- src/dataset.hpp and src/dataset.cpp: JSONL dataset loader and label management.
- src/tokenizer.hpp and src/tokenizer.cpp: text normalization, vocabulary creation, and tokenization.
- src/nn.hpp and src/nn.cpp: model definition, training, inference, and serialization.
- src/visualizer.hpp and src/visualizer.cpp: ncurses-based terminal visualizer.
From the repo root:
cmake -S . -B build
cmake --build buildThis produces the executable at:
build/nvisualBasic run:
./build/nvisualTrain the model from the dataset:
./build/nvisual --trainHelp:
./build/nvisual --helpThe program uses the user config directory:
~/.config/nvisualIt will create and/or use these files there:
config.conf: runtime configuration.dataset/dataset.jsonl: training dataset.model.bin: trained neural network weights.tokenizer.txt: saved tokenizer vocabulary.
On first run, if these files do not exist, the program creates defaults.
The configuration file is read from:
~/.config/nvisual/config.confThe repository also includes a sample config at config.conf.
The parser supports a simple INI-style format:
[settings]
auto_cycle = true
cycle_delay_ms = 0
warn_accuracy = true
learning_rate = 0.05
train_epochs = 100
[prompts]
prompt1 = What is 2 + 2?
prompt2 = What is the capital of France?These values control the runtime behavior of the application.
- Type: boolean
- Default behavior:
true - Meaning: whether the program automatically advances through the prompt list without waiting for input.
When enabled, the program cycles through configured prompts. When disabled, it may behave more like a one-shot viewer depending on the flow.
- Type: integer (milliseconds)
- Default:
0 - Meaning: how long the program waits before moving to the next prompt.
A higher value makes the visualization more readable and gives you more time to observe each prediction.
- Type: boolean
- Default:
true - Meaning: whether the visualizer shows a warning that the model may be inaccurate.
This warning is shown in the UI and is intended to remind you that the model is small and toy-like.
- Type: float
- Default:
0.05 - Meaning: step size used during training.
A larger value makes training faster but can be unstable; a smaller value is more stable but slower.
- Type: integer
- Default:
100in the shipped config - Meaning: number of training epochs used when training is run.
This value controls how long the model trains over the dataset before saving weights.
The [prompts] section contains the prompt list used during inference.
Each entry is simply a line like:
[prompts]
prompt1 = What is 2 + 2?
prompt2 = What is the capital of France?The parser reads every non-empty value under [prompts] and stores it in a vector of strings.
Important detail:
- These prompts are used for inference/display.
- They are not the same thing as the training examples in the JSONL dataset.
The program will use the configured prompts as the runtime prompt loop. If the prompt list is empty, it falls back to a few hardcoded defaults.
There are two different concepts related to prompts in this project:
- Runtime prompts from the config file.
- Training examples from the dataset file.
These are the questions the program will show and answer during the inference loop.
They come from the [prompts] section of the config file.
Example:
[prompts]
prompt1 = What is the capital of France?
prompt2 = What is 2 + 2?When the program is run normally, it cycles through these prompts and displays the network’s prediction for each one.
These are the examples used to teach the model.
They live in the dataset file:
~/.config/nvisual/dataset/dataset.jsonlEach line must contain a JSON object with:
{"prompt": "What is 2 + 2?", "answer": "4"}The dataset loader expects each line to contain a simple flat JSON object with a prompt field and an answer field.
The model is trained to map prompt text to one of the known answer labels.
The dataset file is newline-delimited JSON (JSONL).
Each line should look like this:
{"prompt": "What is 2 + 2?", "answer": "4"}- Each line is one training example.
promptis the input question.answeris the expected class label.- The parser is minimal and expects simple JSON strings.
- It does not support nested objects or complex JSON structures.
The dataset system:
- reads all valid examples,
- stores the prompt/answer pairs,
- deduplicates the answers into a list of class labels,
- and builds a deterministic label index for training.
The answer labels are sorted and used as output classes for the network.
The repository’s default dataset file includes examples like:
{"prompt": "What is 2 + 2?", "answer": "4"}
{"prompt": "What is the capital of France?", "answer": "Paris"}
{"prompt": "What color is the sky?", "answer": "Blue"}These examples are intentionally simple and easy for a tiny model to learn.
The tokenizer is implemented in src/tokenizer.cpp.
- Normalizes text to lowercase.
- Keeps alphanumeric characters and converts punctuation to spaces.
- Splits text into words.
- Maps words to integer IDs.
- Limits the input to a maximum token length.
- Token ID
0is reserved for<UNK>. - The vocabulary size is capped at 32 tokens.
- The tokenizer only uses the most frequent words from the training prompts.
- Text that is not in the vocabulary becomes
<UNK>.
Because the model is tiny, the tokenizer is very simple. If you add many varied prompts, the tokenizer may fail to capture enough vocabulary to make predictions accurate.
The model is implemented in src/nn.cpp.
The forward pass does the following:
- Look up token embeddings.
- Average-pool them into a single embedding vector.
- Pass that through hidden layer 1.
- Apply ReLU.
- Pass to hidden layer 2.
- Apply ReLU.
- Produce logits for each class.
- Apply softmax to get probabilities.
For each input prompt, the network outputs a probability distribution over all known answers.
The program chooses the answer with the highest probability as the predicted label.
Training adjusts the weights and biases using the gradient of cross-entropy loss.
Because the network is tiny, accuracy will depend heavily on how well the dataset matches the prompts you test it with.
The UI is drawn with ncurses in src/visualizer.cpp.
It provides a colorful terminal interface with:
- a title bar,
- a network visualization panel,
- an information panel showing the prompt and answer,
- a status bar,
- and animated transitions during inference.
During inference, the program animates a ball moving through the network layers. The visualizer also shows the current prompt, the predicted answer, and the confidence value.
During training, it shows:
- current epoch,
- total epochs,
- loss,
- accuracy,
- and dataset size.
The visualizer listens for keyboard input:
qorEsc: quitSpace,Enter, or right arrow: skip to the next prompt while waiting in the cycle delay
This is the practical flow of the program when you run ./build/nvisual:
- The program determines the home directory.
- It creates
~/.config/nvisualif needed. - It creates a default config file and dataset if they do not exist.
- It loads the config.
- It checks whether a model and tokenizer already exist.
- If they do not exist, it trains the model immediately.
- It loads the model and tokenizer.
- It loops over the configured prompts.
- For each prompt, it runs inference and draws the visualizer.
- It advances to the next prompt or waits according to the configured delay.
When you run ./build/nvisual --train, the flow changes slightly:
- The program loads the dataset.
- It builds the tokenizer vocabulary.
- It initializes the neural network.
- It trains for the configured number of epochs.
- It saves the model and tokenizer.
Because the model is tiny, the best way to improve performance is to improve the training data.
- Add more prompt/answer examples to the dataset.
- Use prompts that are similar to the questions you want the model to answer.
- Keep answers short and consistent.
- Run
nvisual --trainagain.
- Avoid mixing too many unrelated topics in one dataset.
- Prefer a focused domain if you want better results.
- If the model is failing on a specific prompt, add several similar examples for that prompt.
The model is small enough that it can learn simple patterns, but it will not generalize well to arbitrary knowledge.
Here is a minimal example you can use:
[settings]
auto_cycle = true
cycle_delay_ms = 1000
warn_accuracy = true
learning_rate = 0.05
train_epochs = 100
[prompts]
prompt1 = What is 2 + 2?
prompt2 = What is the capital of France?
prompt3 = What color is the sky?And a matching dataset example:
{"prompt": "What is 2 + 2?", "answer": "4"}
{"prompt": "What is the capital of France?", "answer": "Paris"}
{"prompt": "What color is the sky?", "answer": "Blue"}nvisual is a compact educational project that combines:
- a tiny neural network,
- a simple tokenizer,
- a JSONL dataset loader,
- and a ncurses visualizer.
Its main purpose is not to be a serious AI model. Its purpose is to show, in a simple and understandable way, how a small neural network can be trained and visualized from scratch.
If you want to extend it, the best next steps are usually:
- add more training data,
- improve the tokenizer,
- enlarge the neural network,
- or add more sophisticated visualization.
./build/nvisual # run inference
./build/nvisual --train # train/fine-tune
./build/nvisual --help # show usage~/.config/nvisual/config.conf~/.config/nvisual/dataset/dataset.jsonl~/.config/nvisual/model.bin
~/.config/nvisual/tokenizer.txt