Lab 6 — KWS Kernel: From Model to C
Goal
Get familiar with the hand-written int8 inference kernel you will run on your platform and accelerate in Project 3. This lab is a reading and running exercise — no hardware needed yet (that requires the UART and timer from Project 2, which you have, but the kernel can be built and tested on your PC first).
When to do this
This lab is released alongside the M04 classes on TinyML. Complete it before Lab 7 (profiling), which builds directly on the code you explore here. You already estimated the MAC count in Lab 1 — now you will see exactly how those MACs are computed.
The big picture
In Lab 1 you ran TFLite Micro's reference implementation on your PC. That implementation is ~100,000 lines of C++. The course's kernel does the same computation in ~300 lines of C, with no dependencies beyond <stdint.h>. This is what runs on your CPU.
The trade-off: TFLite Micro handles arbitrary models; the course kernel is hand-written for tiny_conv only. But it is small enough to read completely, which is the point.
File layout
Step 1 — Read the public interface
Activity 1
Open kws_kernel.h. The top-level function is:
And the layer primitive:
Answer the following (check kws_model.h for the shapes):
- What are
in_h,in_w,in_cfor the Conv2D call? - What is
out_c? - Why are
mult,shift, andzero_pointarrays rather than scalars?
Hint
mult, shift, and zero_point are arrays indexed by output channel because TFLite uses per-channel quantization: each output channel has its own scale factor, chosen to minimise quantization error for that channel's weight distribution. A single global scale (per-tensor quantization) would be less accurate.
Step 2 — Trace a single output element
Activity 2
Open kws_kernel.c and find the innermost loop of conv_int8. The computation for one output element output[oh][ow][oc] is:
Trace this on paper for oc=0, oh=0, ow=0 using the actual values from weights.h (just the first kernel position: kh=0, kw=0, ic=0).
You don't need to compute the full result — just verify you understand the index arithmetic. What is weights[0][0][0][0]?
Step 3 — Count the MACs
Activity 3
You estimated the total MAC count in Lab 1. Now compute it precisely from kws_model.h:
| Layer | Formula | MACs |
|---|---|---|
| Conv2D | KH × KW × in_c × out_h × out_w × out_c | ? |
| FC | in_features × out_classes | ? |
| Total | ? |
Fill in the table. Does it match your Lab 1 estimate?
Hint
Conv2D: KH=8, KW=20, in_c=1, out_h=42, out_w=21, out_c=8 → 8×20×1×42×21×8 = 451,584
FC: 1280 × 4 = 5,120
Total: 456,704 MACs
Step 4 — Build and run on your PC
Activity 4
The kernel is designed to compile natively (no hardware, no UART):
Expected output: all 4 test vectors classified correctly (matching the labels in test_vectors.h). If any label mismatches, there is a bug in the kernel — check the requantize function first.
Use -O2 for the PC build
The native build uses -O2 so the PC reference runs fast. When you later compile for your RISC-V CPU, you will compare -O0 vs -O2 results — both must produce the same classification outputs (optimisation must not change correctness).
Step 5 — The requantize function
Activity 5
Find requantize() in kws_kernel.c. It converts an int32 accumulator back to int8:
Answer:
- Why is
scaleda 64-bit value? What would happen if it were 32-bit? - The Conv2D layer uses
[0, 127]clamping. Why? (Hint: fused ReLU) - The FC layer uses
[-128, 127]. Why the difference?
Hint
acccan be up to 8×20×1×127×127 ≈ 25M, andmultcan be up to ~2^31. Their product overflows int32.- ReLU sets all negative values to 0, so the output range is [0, 127] in int8 with the Conv2D's zero point.
- The FC produces logits (before softmax), which can be negative — no activation function is fused.
Step 6 — Sketch the accelerator interface
Activity 6
Project 3 asks you to accelerate conv_int8. Before writing any hardware, sketch on paper:
- Which loop level would you accelerate? (Output element? Row? Full layer?)
- What data needs to travel from CPU to accelerator per invocation?
- How would you use the memory-mapped register interface from Project 2?
No exact answer expected — this is your first design sketch. You will revisit it in Project 3 kickoff.
Checklist
-
kws_inferinterface understood; layer shapes identified fromkws_model.h. - MAC count computed precisely and matches Lab 1 estimate.
- Kernel builds and runs natively; all 4 test vectors pass.
-
requantizefunction understood (overflow, fused ReLU, clamping range). - Accelerator interface sketch done (rough, on paper).
Summary
You read a complete, dependency-free int8 inference kernel and traced the computation from input through quantized output. The conv_int8 function — with its per-channel mult/shift/zero_point arrays — is the inner loop of the whole model and the target of Lab 7 and Project 3. The requantize step is where floating-point scale factors are emulated in integer arithmetic: understanding it is prerequisite for designing a correct accelerator.