Skip to content

TinyAI · Examples · CNN — Design notes

Training output and loss

Cross entropy computes Softmax internally from logits. Historical examples with final Softmax retain their original configuration; choose new output layers according to the loss API. MLP’s named INT8 evaluation uses float predict with mismatched labels and does not evaluate the INT8 kernel.

Implementation and records

APIs in this section are based on CODE/AIoTNode-TinyAuton-AI/middleware/. Source excerpts and serial output include historical records; check the project entry and enabled selectors before reproducing a test.

Reading this experiment

Synthetic stripe images yield 100.00% train/test accuracy. The FP8 weight demonstration reduces 1536 B to 384 B with max error 0.99 and MSE 0.16210581. Compression and deployed model accuracy require separate validation.

The output dated 2026-04-30 is retained line for line. Training was not rerun for this documentation revision; device and firmware commit were not recorded. Read the output and interpretation before the data, model and training walkthrough.

Demo overview

example_cnn now has three parts: 1-D CNN training, FP8 E4M3FN compression demo, and a new BatchNorm2D demo that illustrates BN behavior in Conv pipelines.

Measured output (2026-04-30)

========================================
  tiny_ai  |  CNN Example (Signal)
========================================
Generated 150 signals (3 classes × 50 samples, 64 pts each)
Split: 120 train / 30 test
Sequential model  (11 layers)
--------------------
  [ 0] conv1d
  [ 1] activation
  [ 2] max_pool1d
  [ 3] conv1d
  [ 4] activation
  [ 5] max_pool1d
  [ 6] flatten
  [ 7] dense
  [ 8] activation
  [ 9] dense
  [10] activation
--------------------
  Flat features after conv blocks: 224
Training...
Epoch [ 10/ 50]  loss: 0.558837
Epoch [ 20/ 50]  loss: 0.552400
Epoch [ 30/ 50]  loss: 0.551829
Epoch [ 40/ 50]  loss: 0.551640
Epoch [ 50/ 50]  loss: 0.551561

--- Float32 Results ---
  Train accuracy: 100.00%
  Test  accuracy: 100.00%

--- FP8 E4M3FN Compression Demo ---
  E4M3 scale = 0.008571
  Size: FP32=1536 bytes  FP8=384 bytes  (4:1 compression)
  Max abs error = 0.990000   MSE = 0.16210581
  w[0] orig=-3.8400  fp8=0xC4  recon=-3.0000
  ...

[BN2D] training_mode = ON
  [train mode] Train acc: 100.00%
  [train mode] Test  acc: 100.00%
[BN2D] training_mode = OFF  (running stats active)
  [infer mode] Train acc: 100.00%
  [infer mode] Test  acc: 100.00%
BN2D[0] running_mean (first 4 ch): 0.0000 -0.0023 -0.0046 0.0108
BN2D[0] running_var  (first 4 ch): 0.2838 1.0601 0.6727 2.8801
example_cnn  DONE

DATA SOURCE

namespace tiny_data
{
constexpr int SIG_N_CLASSES         = 3;
constexpr int SIG_SAMPLES_PER_CLASS = 50;
constexpr int SIG_N_SAMPLES         = SIG_N_CLASSES * SIG_SAMPLES_PER_CLASS;  // 150
constexpr int SIG_SIGNAL_LEN        = 64;

void generate_signal_dataset(float *X, int *Y);
}

generate_signal_dataset synthesises 150 × 64-point signals (sines, squares, noisy variants) into a PSRAM buffer that is then wrapped into a Dataset.

MODEL

CNN1DConfig cfg;
cfg.in_channels   = 1;
cfg.signal_length = 64;
cfg.num_classes   = 3;
cfg.filters       = {8, 16};
cfg.kernels       = {5, 3};
cfg.pool_size     = 2;
cfg.fc_units      = 32;

CNN1D model(cfg);

Expands to:

Conv1D(1→8,  k=5)  + ReLU + MaxPool1D(2)   -> [B, 8, (64-5+1)/2 = 30]
Conv1D(8→16, k=3)  + ReLU + MaxPool1D(2)   -> [B,16, (30-3+1)/2 = 14]
Flatten                                     -> [B, 16*14 = 224]
Dense(224 → 32) + ReLU
Dense(32  → 3)  + Softmax

model.flat_features() returns the flattened dimension (224 here) for verification.

CUSTOM TRAINING LOOP

Dataset returns [B, L], but Conv1D expects [B, 1, L], so the example bypasses Trainer::fit and writes the loop manually:

std::vector<ParamGroup> params;
model.collect_params(params);
opt.init(params);

for (int epoch = 0; epoch < 50; epoch++)
{
    train_ds.shuffle(epoch + 1);
    while (next_batch returns > 0)
    {
        Tensor X3(actual, 1, SIG_SIGNAL_LEN);  // [B, L] → [B, 1, L]
        ... copy ...

        Tensor logits = model.forward(X3);
        float  loss   = cross_entropy_forward(logits, y_batch);
        opt.zero_grad(params);
        Tensor grad   = cross_entropy_backward(logits, y_batch);
        model.backward(grad);
        opt.step(params);
    }
}

The hand-rolled loop also illustrates how Optimizer + Sequential + Dataset can be used independently.

EVALUATION

auto eval_accuracy = [&](Dataset &ds, const char *tag) {
    ds.reset();
    ...
    Tensor X3(actual, 1, SIG_SIGNAL_LEN);
    model.predict(X3, yp);
    ...
};
eval_accuracy(train_ds, "Train");
eval_accuracy(test_ds,  "Test ");

Same [B, L] → [B, 1, L] reshape per batch, then Sequential::predict produces argmax labels.

FP8 COMPRESSION DEMO

Tensor w(16, 8, 3);      // mock Conv1D weight
QuantParams qp = calibrate(w, TINY_DTYPE_FP8_E4M3);

uint8_t *fp8_buf = (uint8_t *)TINY_AI_MALLOC(w.size);
quantize(w, fp8_buf, qp);

Tensor w_recon = Tensor::zeros_like(w);
dequantize(fp8_buf, w_recon, qp);

Prints:

  • 4:1 compression ratio (size * 4 bytes → size bytes).
  • Max absolute error and MSE.
  • The first 4 elements: original, FP8 byte, reconstructed.

Useful as a benchmark against INT8 compression.

New: BatchNorm2D demo

At the end, example_cnn runs batchnorm2d_demo(train_ds, test_ds) (training builds only):

Conv1D(1,8,5)  + BN2D(8)  + ReLU + MaxPool1D(2)
Conv1D(8,16,3) + BN2D(16) + ReLU + MaxPool1D(2)
Flatten -> Dense(224,32)+ReLU -> Dense(32,3)+Softmax

Highlights:

  • BN2D is applied on Conv1D outputs [B, C, L], with per-channel stats over N*L.
  • training_mode=ON updates running_mean/running_var.
  • training_mode=OFF uses running stats for inference.
  • The demo prints train/test accuracy in both modes and dumps leading channel stats from the first BN layer.

Interpretation

  • Task is fully learned: both train and test are 100%, indicating this synthetic dataset is easy for the current model.
  • Higher loss plateau with perfect accuracy is possible: accuracy tracks class decisions; loss also depends on confidence, so they do not have to move together.
  • FP8 distortion is significant here: max_err=0.99, MSE=0.162 indicate strong clipping/quantization under E4M3 for this value range; this is a compression-error demonstration, not end-to-end deployment accuracy.
  • BN2D mode switch is stable: matching ON/OFF accuracies suggest running stats are well aligned with the data distribution.

ENTRY POINT

extern "C" void example_cnn(void);
example_cnn();