Architecture Diagrams
1. System Architecture
High-level components and their responsibilities.
graph TB
subgraph CLI["CLI Layer"]
AP["ArgParse.c<br/>parse argv → CommandArgs"]
CD["Commands.c<br/>dispatch by CMD_*"]
TR["Train.c"]
TE["Test.c"]
BM["Benchmark.c"]
RC["Recognize.c"]
end
subgraph CORE["Core Layer"]
GL["Glue.c<br/>forward pass - loss - weight update"]
GR["Grad.c<br/>ReLU derivative"]
MO["Model.c<br/>create - save - load .bin"]
TE2["Tensor.c<br/>dot - add - ReLU - softmax - Xavier"]
AR["Arena.c<br/>slab allocator"]
end
subgraph DATA["Dataset Layer"]
DS["Dataset.c<br/>MEMORY or PNG"]
TU["TestUtils.c<br/>trainModel - testPerfect - robustness - confusion"]
MD["MemoryDatasets.c<br/>hardcoded float arrays"]
end
subgraph IMAGE["Image Layer"]
IL["ImageLoader.c<br/>stb_image PNG -> RawImage"]
IP["ImagePreprocess.c<br/>grayscale - Otsu - resize - normalize"]
SG["Segmenter.c<br/>vertical projection - <br> char bounds - space detection"]
end
AP --> CD
CD --> TR & TE & BM & RC
TR & TE & BM --> DS & TU
RC --> DS
TR & TE & BM & RC --> MO
TU --> GL
GL --> GR & TE2 & AR
MO --> TE2 & AR
DS --> MD
DS --> IP & IL
RC --> SG
SG --> IP & IL
style CLI fill:#dbeafe,stroke:#3b82f6
style CORE fill:#dcfce7,stroke:#22c55e
style DATA fill:#fef9c3,stroke:#eab308
style IMAGE fill:#fce7f3,stroke:#ec4899
2. Module Dependencies
Actual #include relationships between .c files and headers.
flowchart LR
%% =====================
%% IMAGE PIPELINE (Lowest Level)
%% =====================
subgraph IMAGE["Image Processing Pipeline"]
direction TB
hIL["ImageLoader.h"]
hIP["ImagePreprocess.h"]
hSG["Segmenter.h"]
Loader["ImageLoader.c"]
Preprocess["ImagePreprocess.c"]
Segmenter["Segmenter.c"]
end
%% =====================
%% DATASET / TEST
%% =====================
subgraph DATASET["Dataset & Testing"]
direction TB
hDS["Dataset.h"]
hTU["TestUtils.h"]
Dataset["Dataset.c"]
TestUtils["TestUtils.c"]
end
%% =====================
%% CORE ML ENGINE
%% =====================
subgraph CORE["Core ML Engine"]
direction TB
hAR["Arena.h"]
hTE["Tensor.h"]
hMO["Model.h"]
hGR["Grad.h"]
hGL["Glue.h"]
Arena["Arena.c"]
Tensor["Tensor.c"]
Model["Model.c"]
Grad["Grad.c"]
Glue["Glue.c"]
end
%% =====================
%% CLI LAYER
%% =====================
subgraph CLI["CLI Layer"]
direction TB
hAP["ArgParse.h"]
hCD["Commands.h"]
subgraph COMMANDS["cli/commands"]
direction TB
cTRAIN["Train.c"]
cTEST["Test.c"]
cBENCH["Bench.c"]
cREC["Rec.c"]
end
end
%% =====================
%% ENTRY POINT (Top Level)
%% =====================
subgraph ENTRY["Entry Point"]
main["miniAI.c"]
end
%% =====================
%% GLOBAL CONFIG
%% =====================
hAI["AIHeader.h<br/>(TrainingConfig / Constants)"]
%% =====================
%% VERTICAL ORDER CONTROL (Invisible)
%% =====================
IMAGE -.-> DATASET
DATASET -.-> CORE
CORE -.-> CLI
CLI -.-> ENTRY
linkStyle 0 stroke-width:0px
linkStyle 1 stroke-width:0px
linkStyle 2 stroke-width:0px
linkStyle 3 stroke-width:0px
%% =====================
%% ENTRY DEPENDENCIES
%% =====================
main --> hAP
main --> hCD
main --> hAI
main --> cTRAIN
%% =====================
%% CLI DEPENDENCIES
%% =====================
COMMANDS --> hCD
COMMANDS --> hDS
COMMANDS --> hAI
cTRAIN --> hTU
cTRAIN --> hMO
cTEST --> hTU
cTEST --> hMO
cTEST --> hGL
cBENCH --> hTU
cBENCH --> hMO
cREC --> hMO
cREC --> hGL
%% =====================
%% CORE IMPLEMENTATION
%% =====================
Glue --> hGL
Glue --> hGR
Glue --> hAI
Grad --> hGR
Model --> hMO
Tensor --> hTE
Arena --> hAR
%% =====================
%% DATASET IMPLEMENTATION
%% =====================
Dataset --> hDS
Dataset --> hIL
Dataset --> hIP
TestUtils --> hTU
TestUtils --> hGL
TestUtils --> hAI
%% =====================
%% IMAGE IMPLEMENTATION
%% =====================
Segmenter --> hSG
Segmenter --> hIP
Loader --> hIL
Preprocess --> hIP
%% =====================
%% HEADER RELATIONSHIPS
%% =====================
hGL --> hTE
hGL --> hAR
hMO --> hTE
hMO --> hAR
hTU --> hMO
hTU --> hDS
hDS --> hIL
hDS --> hIP
hSG --> hIP
%% =====================
%% STYLING
%% =====================
style hAI fill:#fef3c7,stroke:#f59e0b
3. Training Data Flow
Exact sequence executed by cmdTrain() → trainModel().
flowchart TB
A(["`./miniAI train --dataset digits --data`"]) --> B["parseArgs()<br/>→ CommandArgs"]
B --> C{"useStatic?"}
C -- yes --> D["datasetCreateMemory()<br/>float* from MemoryDatasets.c<br/>digits: 5×5, alpha: 8×8"]
C -- no --> E["datasetCreatePNG()<br/>load PNGs at startup<br/>digits: 8×8, alpha: 16×16"]
D & E --> F["loadBestConfig()<br/>IO/configs/best_config_*.txt<br/>→ g_trainConfig.hiddenSize/LR"]
F --> G["modelCreate(perm)<br/>Xavier init weights<br/>bias = 0"]
G --> H["trainModel() — 3000 passes"]
subgraph loop["Each pass (epoch)"]
H --> I["shuffle(indices)"]
I --> J["for each sample"]
J --> K["arenaReset(scratch)"]
K --> L["glueAccumulateGradients()<br/>forward + backward<br/>clip gradients, accumulate"]
L --> M{"batch full?<br/>or last sample?"}
M -- yes --> N["glueUpdateWeights()<br/>avg grad ÷ batchSize<br/>+ L2 regularization<br/>reset accumulators"]
N --> J
M -- no --> J
J --> O{"pass % 500 == 0?"}
O -- yes --> P["lr *= 0.7<br/>log loss"]
P --> H
end
H --> Q["modelSave()<br/>IO/models/*.bin"]
Q --> R["testPerfect() · testRobustness()<br/>visualDemo() · confusionMatrix()"]
R --> S["arenaFree(perm, scratch)"]
style loop fill:#f0fdf4,stroke:#22c55e
4. Inference Data Flow
Sequence for cmdTest() with a single image (--image).
flowchart TD
A(["`./miniAI test --image char.png`"]) --> B["parseArgs()<br/>useStatic=0, PNG mode"]
B --> C["loadBestConfig()<br/>hidden size from config"]
C --> D["modelCreate(perm)<br/>empty shell, correct dims"]
D --> E["modelLoad()<br/>verify layer dims<br/>read weights + biases"]
E --> F["imageLoad() via stb_image<br/>→ RawImage width×height×channels"]
F --> G["convertToGrayscale()<br/>luminance: 0.299R + 0.587G + 0.114B"]
G --> H["calculateOtsuThreshold()<br/>maximize between-class variance"]
H --> I["binarize + resize to gridSize×gridSize<br/>normalize → float[0.0, 1.0]"]
I --> J["gluePredict(model, input, scratch)<br/>records scratch->used"]
subgraph fwd["glueForward()"]
J --> K["for each layer i:"]
K --> L["z = W·input + b<br/>tensorDot + tensorAdd"]
L --> M{"last layer?"}
M -- no --> N["a = ReLU(z)"]
M -- yes --> O["a = z (raw logits)"]
N & O --> K
end
O --> P["tensorSoftmax(probs, output)<br/>numerical stability: subtract max"]
P --> Q["argmax(probs) → predicted class"]
Q --> R["scratch->used = startPos<br/>(implicit free of temporaries)"]
R --> S[/"charMap[guess] + confidence%"/]
style fwd fill:#eff6ff,stroke:#3b82f6
5. Phrase Recognition Flow
How cmdRecognize() processes a full phrase image.
flowchart TD
A(["`./miniAI recognize --image phrase.png`"]) --> B["datasetCreatePhrase()<br/>calls segmentPhrase()"]
subgraph seg["Segmenter.c — segmentPhrase()"]
B --> C["imageLoad() → RawImage"]
C --> D["convertToGrayscale()"]
D --> E["shouldInvertColors()<br/>sample border pixels<br/>avgBorder > 128 → invert"]
E --> F["calculateOtsuThreshold()"]
F --> G["binarize → uint8_t binary[]"]
G --> H["computeVerticalProjection()<br/>sum foreground pixels per column"]
H --> I["findCharBoundaries()<br/>detect runs of non-zero columns<br/>record gap widths between chars"]
I --> J["detectSpaces()<br/>gaps > spaceThreshold → insert ' '"]
J --> K["for each character segment:<br/>crop → resize → normalize<br/>→ float[gridSize²]"]
end
K --> L["Dataset with N character samples<br/>+ space flags"]
L --> M["loadBestConfig() auto-detect<br/>from model filename"]
M --> N["modelCreate() + modelLoad()"]
N --> O["for each character sample"]
O --> P["gluePredict() → class index"]
P --> Q["charMap[index] → character"]
Q --> R{"space flag?"}
R -- yes --> S["append ' '"]
R -- no --> T["append char"]
S & T --> O
O --> U[/"Print assembled phrase<br/>+ per-character confidence"/]
style seg fill:#fdf4ff,stroke:#a855f7
6. Memory Architecture
How the two arenas are used during training.
block-beta
columns 3
PERM_TITLE["<b>PERMANENT ARENA</b><br/>16 MB (Train) / 8 MB (Rec)"]:3
block:perm:3
columns 3
P1["Model struct"]
P2["Layer[0]:<br/>W, b, gradW, gradB"]
P3["Layer[1]:<br/>W, b, gradW, gradB"]
P_INFO["<i>Never reset during training lifecycle</i>"]:3
end
space:3
SCRATCH_TITLE["<b>SCRATCH ARENA</b><br/>4 MB (Train) / 2 MB (Rec)"]:3
block:scratch:3
columns 3
S1["Input Tensor<br/>(One Sample)"]
S2["Forward Cache:<br/>z, a (L0)"]
S3["Forward Cache:<br/>z, a (L1)"]
S4["Probs Tensor<br/>(Softmax)"]
S5["Delta / Upstream<br/>(Backprop)"]
S_INFO["<i>arenaReset() called per sample [O(1)]</i>"]:3
end
space:3
NOTE_TITLE["<b>Memory Implementation Details</b>"]:3
N1["<b>arenaAlloc:</b><br/>Pointer bump + memset(0)"]:1
N2["<b>arenaReset:</b><br/>arenaReset = used = 0 (O(1))"]:1
N3["<b>arenaFree:</b><br/>free(buffer) + free(struct)"]:1
style PERM_TITLE fill:#dcfce7,stroke:#16a34a
style SCRATCH_TITLE fill:#fee2e2,stroke:#dc2626
style NOTE_TITLE fill:#f1f5f9,stroke:#475569
style P_INFO fill:none,stroke:none
style S_INFO fill:none,stroke:none
7. Neural Network — Forward Pass
Exact operations in glueForward() for a 2-layer network (input → hidden → output).
flowchart TB
IN["input<br>float[inputSize x 1]"]
subgraph L0["Layer 0 — hidden"]
W0["W₀ <br> [hidden x input]"]
Z0["z₀ = W₀·input + b₀<br>tensorDot + tensorAdd<br>OpenMP parallel"]
A0["a₀ = ReLU(z₀)<br>max(0, x)"]
end
subgraph L1["Layer 1 — output"]
W1["W₁<br>[output x hidden]"]
Z1["z₁ = W₁·a₀ + b₁<br>tensorDot + tensorAdd"]
A1["a₁ = z₁<br>(raw logits)"]
end
subgraph POST["Post-forward"]
SM["softmax(a₁)<br>subtract max for stability<br> -> probabilities"]
OUT["argmax -> predicted class<br>probs[guess] -> confidence"]
end
IN --> W0 --> Z0 --> A0
A0 --> W1 --> Z1 --> A1
A1 --> SM --> OUT
style L0 fill:#dbeafe,stroke:#3b82f6
style L1 fill:#dcfce7,stroke:#22c55e
style POST fill:#fef9c3,stroke:#eab308
8. Backpropagation — One Sample
Exact algorithm in glueAccumulateGradients().
flowchart TD
A["rawData[inputSize]"] --> B["apply TRAIN_NOISE=10%<br>flip pixel: val = 1 - val"]
B --> C["glueForward()<br>cache z and a for all layers"]
C --> D["tensorSoftmax(output) -> probs"]
D --> E["output delta:<br>delta[i] = probs[i] - target[i]<br>target = one-hot(label)"]
subgraph back["Backward loop layer i"]
E --> F["for each weight r,c:<br>gradW = delta[r] x prevA[c]<br>clip to ±GRAD_CLIP<br>gradW_acc[r,c] += gradW"]
F --> G["gradB_acc[r] += delta[r]"]
G --> H{"i > 0 ?"}
H -- yes --> I["upstreamDelta[j] = Σ W[k,j] x delta[k]<br>(W^T · delta)"]
I --> J["tensorReLUDerivative()<br>nextDelta[i] = z[i]>0 ? upstream[i] : 0<br>OpenMP parallel"]
J --> K["delta = nextDelta<br>-> next iteration"]
K --> F
H -- no --> L["done accumulating"]
end
subgraph update["glueUpdateWeights() - called after full batch"]
L --> M["avg = gradW_acc / batchSize"]
M --> N["grad = avg + λ x W<br>(L2 regularization λ=0.0001)"]
N --> O["W -= lr x grad<br>b -= lr x gradB_avg<br>reset accumulators to 0"]
end
style back fill:#fdf4ff,stroke:#a855f7
style update fill:#f0fdf4,stroke:#22c55e
9. Dataset Type Comparison
flowchart LR
subgraph static_ds["DATASET_MEMORY (--static)"]
direction TB
SM1["MemoryDatasets.c<br>hardcoded float arrays"]
SM2["digits: 10 samples x 25 floats<br>(5×5 grid)"]
SM3["alpha: 62 samples x 64 floats<br>(8×8 grid)"]
SM4["datasetGetSample(ds, i)<br>= ds->memory.data + ixinputSize"]
SM1 --> SM2 & SM3 --> SM4
end
subgraph png_ds["DATASET_PNG (--data)"]
direction TB
PM1["PNGs loaded at datasetCreatePNG()"]
PM2["digits: 10 PNGs -> float[64]<br>(8×8 grid)"]
PM3["alpha: 62 PNGs -> float[256]<br>(16×16 grid)"]
PM4["datasetGetSample(ds, i)<br> = ds->png.samples[i]"]
PM5["filename pattern:<br>grid==16 -> 065_A.png<br>grid==8 -> A.png"]
PM1 --> PM2 & PM3 --> PM4
PM1 --> PM5
end
subgraph phrase_ds["DATASET_PHRASE (recognize)"]
direction TB
PH1["datasetCreatePhrase(imageFile)"]
PH2["segmentPhrase() -> CharSequence"]
PH3["N char segments -> float[gridSize^2] each"]
PH4["space flags array"]
PH1 --> PH2 --> PH3 & PH4
end
style static_ds fill:#fef9c3,stroke:#eab308
style png_ds fill:#dbeafe,stroke:#3b82f6
style phrase_ds fill:#fce7f3,stroke:#ec4899