es-ude / es-ude/OnDeviceTraining

arithmetic: formalize a swappable kernel backend (build-time selection, accumulate-only contract) — in-house default, optional CMSIS arm on M33 later

Open
#433 0 comments 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
C
Stars
1
Forks
3
Avg merge
1d 1h
Merged PRs (30d)
8

Description

## Context

Question raised 2026-09-07: should the arithmetic layer depend on an existing C math/NN library (CMSIS-NN, CMSIS-DSP, muRISCV-NN, …) instead of the in-house kernels, with a swappable compute backend so optimized kernels come "for free" and the paper can cite a well-known library?

The seam already exists: every funnel call carries an `opKernelFn_t` in its `opSpec_t`, chosen per layer and per `arithmetic_t` (the `forwardKernelFloat/Sym/Bfp`, `weightGradKernel*`, `propLossKernel*` triples). The funnel owns operand staging before the kernel and the write-back (rounding, requant, packing, accumulate epilogue) after it. What is missing is a formal way to select a backend at build time. This is #336's integration criterion 3, split out as its own deliverable.

## Decision (2026-09-07)

1. **Formalize the backend seam now** — cheap, reversible, no dependency yet.
2. **Keep the in-house kernels as the default backend.** They are the bit-parity oracle against PyTorch and carry the mutation tests. Not replaced by a library.
3. **One optional library arm on the M33 (Cortex-M33, DSP extension) after the first on-target numbers exist** (#419), labelled with its own numerics — the paper brief's §5.1 "upper bound" column. Its go/no-go is #336's benchmark memo.
4. **Code generation (ir2c, #62) is the long-term optimization path**, not a library: shape-specialized kernels emitted at compile time (TinyEngine/TTE, microTVM precedent) beat generic libraries on tiny shapes and keep numerics ownership.

### Why a library is not the default

- Every optimized MCU library is **inference-only**. Backward kernels (transposed matmul, conv adjoints) stay in-house regardless, so the gain is forward-only. PULP-TrainLib is the only training-kernel precedent, and it is RISC-V float.
- **Libraries fix the numerics.** Verified from the CMSIS-DSP docs: `arm_mat_mult_q15` accumulates in 64 bits and saturates to a Q15 output; `arm_mat_mult_q7` needs a state buffer. That collides with the no-int64 rule, the per-tensor float scale, the int12 operand rule (#227) and the SR write-backs (#279) — the research object. An int8 arm on library numerics next to in-house BFP/sub-byte arms reintroduces the implementation-quality confound the controlled format comparison exists to remove.
- **Half the paper runs on the M0+** (RP2040): no DSP, no SIMD, no FPU. A library gains little there. On the M33 the compiler already uses the FPU for float, so the real gain is int8/int16 forward on one board.
- Publishing: TTE, PockEngine, PULP-TrainLib and Deutel all hand-wrote kernels. Reviewers ask for fair baselines and a released artifact, not a named library. The bit-parity + mutation-test discipline is the stronger reproducibility story.

## Scope of this issue

1. **Backend selection**: a kernel table (or per-backend header set) resolved by a CMake option, e.g. `ODT_KERNEL_BACKEND=reference|cmsis` (name TBD). `reference` = today's kernels, unchanged.
2. **Backend contract**: a backend implements the **accumulation only**; the funnel keeps staging and write-back. Kernels receive staged operands and write the raw intermediate — exactly today's `opKernelFn_t` shape. Document this in `docs/conventions/` so a library arm cannot smuggle in a fused requant.
3. **Staging-format decision** (own design section): `ARITH_SYM_INT32` stages operands into **int32 scratch** (`ExecuteOp.c`), while DSP dual-MAC instructions want **int8/int16-packed** operands. Options: (a) a conversion pass in the adapter with its own scratch, (b) an **int16 staging mode** in the funnel — halves staging scratch, worth considering on its own merits. Decide before any library arm.
4. **Test policy**: library arms test with a **tolerance** against the reference arm; the reference arm alone keeps PyTorch bit-parity and the mutation suites.
5. **First checks for the #336 benchmark step** (not verified yet):
- Does CMSIS-NN expose a raw int32 accumulate without its fused requant (`arm_nn_mat_mul_core_*`-class internals vs the public `arm_fully_connected_s8` API)? If not, the arm is a "TFLite-numerics" arm and must be labelled so.
- Does CMSIS-NN's scalar (non-DSP) path build on host, so the tolerance tests run in CI?
- Scratch/state-buffer sizes per kernel — the memory-first vision must account for them.

## Out of scope

- Adopting any library as the default backend (rejected above).
- Backward kernels from a library (none exist).
- The library arm implementation itself — follows #336's memo, gets its own issue if "adopt".
- Host-side BLAS for sweep speed — breaks summation-order parity for the float arm, sweeps are dominated by small models.

## Acceptance

- Both backend values configure and build; `reference` is bit-identical to today (all unit tests green, no kernel source moved semantically).
- Backend contract documented in `docs/conventions/`; FEATURES.md row.
- Staging-format decision recorded (option a or b) with the memory numbers behind it.

Relations: #336 (investigation, benchmark-first), #419 (numbers before any arm), #62 (codegen path), #349 (float parity policy), #227 / #279 (numerics contract the backend must not override).

Contributor guide

Open the contributing guide

Research direction

Trace the existing opKernelFn_t and opSpec_t flow, including operand staging and write-back in ExecuteOp.c, before defining the CMake backend selection. Record the accumulation-only contract and staging-format decision in docs/conventions/, update FEATURES.md, and verify that both backend values build while the reference backend remains bit-identical across the existing unit tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, cmake
Domain
backend-api-design, build-system, documentation, embedded-iot, testing
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.