IDKHowToCodeFr/tinyml-backend
1
1import m2cgen as m2c2import numpy as np3 4def generate_c_code(eng, model_name: str, quantize: bool):5 if not eng or model_name not in eng.models:6 return {"error": f"Model {model_name} not found"}7 8 model = eng.models[model_name]9 10 # m2cgen handles Random Forest beautifully, but outputs FP32/double rules11 if model_name == "rf":12 try:13 code = m2c.export_to_c(model)14 if quantize:15 code = "/* WARNING: M2CGen generated FP32 output. INT8 Quantization is not supported directly for Random Forest trees. */\n" + code16 return {"code": code}17 except Exception:18 pass19 20 # For LogReg, use m2cgen if FP32, otherwise manual generation for INT821 if not quantize and model_name == "logreg":22 try:23 code = m2c.export_to_c(model)24 return {"code": code}25 except Exception:26 pass27 28 # Manual C-code generation for all model types29 try:30 L = []31 L.append("/* ====================================================== */")32 L.append(f"/* TinyML C Export: {model_name} */")33 q_text = "INT8 Quantized" if quantize else "FP32 Double"34 L.append(f"/* Auto-generated for ARM Cortex-M / ESP32 ({q_text}) */")35 L.append("/* ====================================================== */")36 L.append("")37 L.append("#include <math.h>")38 L.append("#include <stdint.h>")39 L.append("#include <string.h>")40 L.append("")41 42 if model_name == "svm" and hasattr(model, 'coef_'):43 coefs = model.coef_44 intercepts = model.intercept_45 n_classes = len(model.classes_)46 n_features = coefs.shape[1]47 L.append(f"/* Linear SVM with {n_classes} classes, {n_features} features */")48 L.append(f"#define N_FEATURES {n_features}")49 L.append(f"#define N_CLASSES {n_classes}")50 L.append(f"#define N_HYPERPLANES {coefs.shape[0]}")51 L.append("")52 53 if quantize:54 scale_factor = 127.0 / max(np.max(np.abs(coefs)), np.max(np.abs(intercepts)), 1e-6)55 L.append(f"/* Quantization Scale: {scale_factor:.4f} */")56 L.append("static const int8_t SVM_COEF[N_HYPERPLANES][N_FEATURES] = {")57 for row in coefs:58 vals = ", ".join([str(int(round(v * scale_factor))) for v in row])59 L.append(f" {{{vals}}},")60 L.append("};")61 L.append("")62 vals = ", ".join([str(int(round(v * scale_factor))) for v in intercepts])63 L.append(f"static const int8_t SVM_INTERCEPT[N_HYPERPLANES] = {{{vals}}};")64 L.append("")65 L.append("int predict(int8_t *features) {")66 L.append(" int32_t scores[N_CLASSES] = {0};")67 L.append(" int h = 0;")68 L.append(" for (int i = 0; i < N_CLASSES; i++) {")69 L.append(" for (int j = i + 1; j < N_CLASSES; j++) {")70 L.append(" int32_t decision = SVM_INTERCEPT[h];")71 L.append(" for (int f = 0; f < N_FEATURES; f++) {")72 L.append(" decision += (int32_t)SVM_COEF[h][f] * features[f];")73 L.append(" }")74 L.append(" if (decision > 0) scores[i] += 1;")75 L.append(" else scores[j] += 1;")76 L.append(" h++;")77 L.append(" }")78 L.append(" }")79 L.append(" int best = 0;")80 L.append(" for (int c = 1; c < N_CLASSES; c++) {")81 L.append(" if (scores[c] > scores[best]) best = c;")82 L.append(" }")83 L.append(" return best;")84 L.append("}")85 else:86 L.append("static const double SVM_COEF[N_HYPERPLANES][N_FEATURES] = {")87 for row in coefs:88 vals = ", ".join([f"{v:.6f}" for v in row])89 L.append(f" {{{vals}}},")90 L.append("};")91 L.append("")92 vals = ", ".join([f"{v:.6f}" for v in intercepts])93 L.append(f"static const double SVM_INTERCEPT[N_HYPERPLANES] = {{{vals}}};")94 L.append("")95 L.append("int predict(double *features) {")96 L.append(" double scores[N_CLASSES] = {0};")97 L.append(" int h = 0;")98 L.append(" for (int i = 0; i < N_CLASSES; i++) {")99 L.append(" for (int j = i + 1; j < N_CLASSES; j++) {")100 L.append(" double decision = SVM_INTERCEPT[h];")101 L.append(" for (int f = 0; f < N_FEATURES; f++) {")102 L.append(" decision += SVM_COEF[h][f] * features[f];")103 L.append(" }")104 L.append(" if (decision > 0) scores[i] += 1.0;")105 L.append(" else scores[j] += 1.0;")106 L.append(" h++;")107 L.append(" }")108 L.append(" }")109 L.append(" int best = 0;")110 L.append(" for (int c = 1; c < N_CLASSES; c++) {")111 L.append(" if (scores[c] > scores[best]) best = c;")112 L.append(" }")113 L.append(" return best;")114 L.append("}")115 116 elif model_name == "logreg" and hasattr(model, 'coef_'):117 coefs = model.coef_118 intercepts = model.intercept_119 n_classes = coefs.shape[0] if len(model.classes_) > 2 else 2120 n_features = coefs.shape[1]121 L.append(f"/* Logistic Regression with {n_classes} classes, {n_features} features */")122 L.append(f"#define N_FEATURES {n_features}")123 L.append(f"#define N_CLASSES {coefs.shape[0]}")124 L.append("")125 126 if quantize:127 scale_factor = 127.0 / max(np.max(np.abs(coefs)), np.max(np.abs(intercepts)), 1e-6)128 L.append(f"/* Quantization Scale: {scale_factor:.4f} */")129 L.append("static const int8_t LOGREG_COEF[N_CLASSES][N_FEATURES] = {")130 for row in coefs:131 vals = ", ".join([str(int(round(v * scale_factor))) for v in row])132 L.append(f" {{{vals}}},")133 L.append("};")134 L.append("")135 vals = ", ".join([str(int(round(v * scale_factor))) for v in intercepts])136 L.append(f"static const int8_t LOGREG_INTERCEPT[N_CLASSES] = {{{vals}}};")137 L.append("")138 L.append("int predict(int8_t *features) {")139 L.append(" int32_t scores[N_CLASSES];")140 L.append(" for (int c = 0; c < N_CLASSES; c++) {")141 L.append(f" scores[c] = LOGREG_INTERCEPT[c] * {int(scale_factor)};")142 L.append(" for (int f = 0; f < N_FEATURES; f++) {")143 L.append(" scores[c] += (int32_t)LOGREG_COEF[c][f] * features[f];")144 L.append(" }")145 L.append(" }")146 L.append(" int best = 0;")147 L.append(" for (int c = 1; c < N_CLASSES; c++) {")148 L.append(" if (scores[c] > scores[best]) best = c;")149 L.append(" }")150 L.append(" return best;")151 L.append("}")152 else:153 L.append("static const double LOGREG_COEF[N_CLASSES][N_FEATURES] = {")154 for row in coefs:155 vals = ", ".join([f"{v:.6f}" for v in row])156 L.append(f" {{{vals}}},")157 L.append("};")158 L.append("")159 vals = ", ".join([f"{v:.6f}" for v in intercepts])160 L.append(f"static const double LOGREG_INTERCEPT[N_CLASSES] = {{{vals}}};")161 L.append("")162 L.append("int predict(double *features) {")163 L.append(" double scores[N_CLASSES];")164 L.append(" for (int c = 0; c < N_CLASSES; c++) {")165 L.append(" scores[c] = LOGREG_INTERCEPT[c];")166 L.append(" for (int f = 0; f < N_FEATURES; f++) {")167 L.append(" scores[c] += LOGREG_COEF[c][f] * features[f];")168 L.append(" }")169 L.append(" }")170 L.append(" int best = 0;")171 L.append(" for (int c = 1; c < N_CLASSES; c++) {")172 L.append(" if (scores[c] > scores[best]) best = c;")173 L.append(" }")174 L.append(" return best;")175 L.append("}")176 177 elif model_name == "small_nn" and hasattr(model, 'coefs_'):178 layers = model.coefs_179 biases = model.intercepts_180 arch = " -> ".join([str(l.shape[0]) for l in layers] + [str(layers[-1].shape[1])])181 L.append(f"/* MLP Neural Network: {len(layers)} layers */")182 L.append(f"/* Architecture: {arch} */")183 L.append("")184 185 for idx, (W, b) in enumerate(zip(layers, biases)):186 n_in, n_out = W.shape187 L.append(f"#define L{idx}_IN {n_in}")188 L.append(f"#define L{idx}_OUT {n_out}")189 L.append(f"static const double W{idx}[{n_in}][{n_out}] = {{")190 for row in W:191 vals = ", ".join([f"{v:.6f}" for v in row])192 L.append(f" {{{vals}}},")193 L.append("};")194 bvals = ", ".join([f"{v:.6f}" for v in b])195 L.append(f"static const double B{idx}[{n_out}] = {{{bvals}}};")196 L.append("")197 198 L.append("static inline double relu(double x) { return x > 0 ? x : 0; }")199 L.append("")200 201 if quantize:202 # Calculate global max for int8 scaling203 max_val = max([np.max(np.abs(w)) for w in layers] + [np.max(np.abs(b)) for b in biases] + [1e-6])204 scale_factor = 127.0 / max_val205 L.append(f"/* INT8 Quantization Scale Factor: {scale_factor:.4f} */")206 for idx, (W, b) in enumerate(zip(layers, biases)):207 n_in, n_out = W.shape208 L.append(f"static const int8_t W{idx}[{n_in}][{n_out}] = {{")209 for row in W:210 vals = ", ".join([str(int(round(v * scale_factor))) for v in row])211 L.append(f" {{{vals}}},")212 L.append("};")213 bvals = ", ".join([str(int(round(v * scale_factor))) for v in b])214 L.append(f"static const int8_t B{idx}[{n_out}] = {{{bvals}}};")215 L.append("")216 L.append("static inline int32_t relu_int(int32_t x) { return x > 0 ? x : 0; }")217 L.append("")218 L.append("int predict(int8_t *input) {")219 for idx in range(len(layers)):220 n_in = layers[idx].shape[0]221 n_out = layers[idx].shape[1]222 is_last = (idx == len(layers) - 1)223 src = "input" if idx == 0 else f"a{idx-1}"224 L.append(f" int32_t a{idx}[{n_out}];")225 L.append(f" for (int j = 0; j < {n_out}; j++) {{")226 L.append(f" a{idx}[j] = B{idx}[j] * {int(scale_factor)}; /* scale bias */")227 L.append(f" for (int i = 0; i < {n_in}; i++) {{")228 L.append(f" a{idx}[j] += (int32_t){src}[i] * W{idx}[i][j];")229 L.append(f" }}")230 if not is_last:231 L.append(f" a{idx}[j] = relu_int(a{idx}[j]) / {int(scale_factor)}; /* rescale */")232 L.append(f" }}")233 last_idx = len(layers) - 1234 last_out = layers[-1].shape[1]235 L.append(f" int best = 0;")236 L.append(f" for (int c = 1; c < {last_out}; c++) {{")237 L.append(f" if (a{last_idx}[c] > a{last_idx}[best]) best = c;")238 L.append(f" }}")239 L.append(f" return best;")240 L.append("}")241 else:242 for idx, (W, b) in enumerate(zip(layers, biases)):243 n_in, n_out = W.shape244 L.append(f"#define L{idx}_IN {n_in}")245 L.append(f"#define L{idx}_OUT {n_out}")246 L.append(f"static const double W{idx}[{n_in}][{n_out}] = {{")247 for row in W:248 vals = ", ".join([f"{v:.6f}" for v in row])249 L.append(f" {{{vals}}},")250 L.append("};")251 bvals = ", ".join([f"{v:.6f}" for v in b])252 L.append(f"static const double B{idx}[{n_out}] = {{{bvals}}};")253 L.append("")254 255 L.append("static inline double relu(double x) { return x > 0 ? x : 0; }")256 L.append("")257 L.append("int predict(double *input) {")258 for idx in range(len(layers)):259 n_in = layers[idx].shape[0]260 n_out = layers[idx].shape[1]261 is_last = (idx == len(layers) - 1)262 src = "input" if idx == 0 else f"a{idx-1}"263 L.append(f" double a{idx}[{n_out}];")264 L.append(f" for (int j = 0; j < {n_out}; j++) {{")265 L.append(f" a{idx}[j] = B{idx}[j];")266 L.append(f" for (int i = 0; i < {n_in}; i++) {{")267 L.append(f" a{idx}[j] += {src}[i] * W{idx}[i][j];")268 L.append(f" }}")269 if not is_last:270 L.append(f" a{idx}[j] = relu(a{idx}[j]);")271 L.append(f" }}")272 273 last_idx = len(layers) - 1274 last_out = layers[-1].shape[1]275 L.append(f" int best = 0;")276 L.append(f" for (int c = 1; c < {last_out}; c++) {{")277 L.append(f" if (a{last_idx}[c] > a{last_idx}[best]) best = c;")278 L.append(f" }}")279 L.append(f" return best;")280 L.append("}")281 282 elif model_name == "knn" and hasattr(model, '_fit_X'):283 n_samples = min(model._fit_X.shape[0], 100)284 n_feats = model._fit_X.shape[1]285 L.append(f"/* KNN Lookup Table: {n_samples} reference samples */")286 L.append(f"#define N_NEIGHBORS {model.n_neighbors}")287 L.append(f"#define N_SAMPLES {n_samples}")288 L.append(f"#define N_FEATURES {n_feats}")289 L.append("")290 L.append("static const double REF[N_SAMPLES][N_FEATURES] = {")291 for row in model._fit_X[:n_samples]:292 vals = ", ".join([f"{v:.4f}" for v in row])293 L.append(f" {{{vals}}},")294 L.append("};")295 L.append("")296 labels_str = ", ".join([str(int(l)) for l in model._y[:n_samples]])297 L.append(f"static const int LABELS[N_SAMPLES] = {{{labels_str}}};")298 L.append("")299 L.append("int predict(double *features) {")300 L.append(" double dists[N_SAMPLES];")301 L.append(" for (int i = 0; i < N_SAMPLES; i++) {")302 L.append(" dists[i] = 0.0;")303 L.append(" for (int f = 0; f < N_FEATURES; f++) {")304 L.append(" double d = features[f] - REF[i][f];")305 L.append(" dists[i] += d * d;")306 L.append(" }")307 L.append(" }")308 L.append(" int votes[10] = {0};")309 L.append(" for (int k = 0; k < N_NEIGHBORS; k++) {")310 L.append(" int mi = 0;")311 L.append(" for (int i = 1; i < N_SAMPLES; i++) {")312 L.append(" if (dists[i] < dists[mi]) mi = i;")313 L.append(" }")314 L.append(" votes[LABELS[mi]]++;")315 L.append(" dists[mi] = 1e18;")316 L.append(" }")317 L.append(" int best = 0;")318 L.append(" for (int i = 1; i < 10; i++) {")319 L.append(" if (votes[i] > votes[best]) best = i;")320 L.append(" }")321 L.append(" return best;")322 L.append("}")323 else:324 return {"error": f"Model {model_name} cannot be exported to C."}325 326 return {"code": "\n".join(L)}327 except Exception as e:328 return {"error": f"Export failed: {str(e)}"}329 