tinymlc 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. TinyMLC/ANG/__init__.py +0 -0
  2. TinyMLC/ANG/args.py +86 -0
  3. TinyMLC/ANG/estimator.py +103 -0
  4. TinyMLC/ANG/estimator_hal.py +184 -0
  5. TinyMLC/ANG/estimator_qemu.py +257 -0
  6. TinyMLC/ANG/estimator_software.py +130 -0
  7. TinyMLC/ANG/model_builder.py +508 -0
  8. TinyMLC/ANG/model_generator.py +439 -0
  9. TinyMLC/ANG/model_info.py +283 -0
  10. TinyMLC/ANG/utils.py +420 -0
  11. TinyMLC/__init__.py +0 -0
  12. TinyMLC/cli.py +126 -0
  13. TinyMLC/codegen.py +877 -0
  14. TinyMLC/converter/__init__.py +0 -0
  15. TinyMLC/converter/export_weights.py +382 -0
  16. TinyMLC/converter/parser_litert.py +757 -0
  17. TinyMLC/converter/parser_onnx.py +649 -0
  18. TinyMLC/generate_lut.py +97 -0
  19. TinyMLC/handlers.py +325 -0
  20. TinyMLC/ops.py +76 -0
  21. TinyMLC/templates/lut.c.tpl +23 -0
  22. TinyMLC/templates/lut.h.tpl +67 -0
  23. TinyMLC/templates/model.c.tpl +314 -0
  24. TinyMLC/templates/model.h.tpl +66 -0
  25. TinyMLC/transform/__init__.py +0 -0
  26. TinyMLC/transform/algebraic.py +286 -0
  27. TinyMLC/transform/base.py +58 -0
  28. TinyMLC/transform/constant_folding.py +260 -0
  29. TinyMLC/transform/cse.py +192 -0
  30. TinyMLC/transform/dce.py +182 -0
  31. TinyMLC/transform/fusion.py +723 -0
  32. TinyMLC/transform/memory.py +200 -0
  33. TinyMLC/transform/pass_manager.py +101 -0
  34. TinyMLC/transform/simplify.py +515 -0
  35. tinymlc-0.1.0.dist-info/METADATA +49 -0
  36. tinymlc-0.1.0.dist-info/RECORD +47 -0
  37. tinymlc-0.1.0.dist-info/WHEEL +4 -0
  38. tinymlc-0.1.0.dist-info/entry_points.txt +2 -0
  39. tinymlc-0.1.0.dist-info/licenses/LICENSE +201 -0
  40. utils/__init__.py +0 -0
  41. utils/arm-none-eabi-gcc.cmake +53 -0
  42. utils/dump.py +86 -0
  43. utils/generate_onnx_models.py +183 -0
  44. utils/generate_tflite_models.py +236 -0
  45. utils/pack_macos.sh +88 -0
  46. utils/path.py +31 -0
  47. utils/riscv-none-elf-gcc.cmake +50 -0
File without changes
@@ -0,0 +1,382 @@
1
+ # -*- coding: utf-8 -*-
2
+ # TinyMLC - Tiny Machine Learning Compiler
3
+ #
4
+ # Copyright (c) 2026 Jia Liu & TinyMLC Contributors
5
+ # SPDX-License-Identifier: Apache-2.0
6
+ #
7
+ # This file is part of TinyMLC.
8
+ # Licensed under the Apache License, Version 2.0 (the "License");
9
+ # you may not use this file except in compliance with the License.
10
+ # You may obtain a copy of the License at:
11
+ #
12
+ # http://www.apache.org/licenses/LICENSE-2.0
13
+ #
14
+ # Unless required by applicable law or agreed to in writing, software
15
+ # distributed under the License is distributed on an "AS IS" BASIS,
16
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17
+ # See the License for the specific language governing permissions and
18
+ # limitations under the License.
19
+
20
+
21
+ import numpy as np
22
+
23
+ from utils.dump import warning, info
24
+
25
+
26
+ def quantize_to_int8(tensor):
27
+ """
28
+ Quantize float32 weights to int8 (symmetric quantization)
29
+
30
+ Symmetric quantization:
31
+ - Value range: -127 ~ 127 (avoid -128 for symmetry)
32
+ - scale = max(|min|, |max|) / 127
33
+ - quantized = round(tensor / scale)
34
+
35
+ Args:
36
+ tensor: float32 weight tensor
37
+
38
+ Returns:
39
+ quantized: int8 quantized weights
40
+ scale: quantization scale
41
+ """
42
+ min_val = tensor.min()
43
+ max_val = tensor.max()
44
+ # Symmetric quantization: scale = max(|min|, |max|) / 127
45
+ scale = max(abs(min_val), abs(max_val)) / 127.0
46
+ if scale == 0:
47
+ scale = 1.0
48
+ quantized = np.round(tensor / scale).astype(np.int8)
49
+ return quantized, scale
50
+
51
+
52
+ def export_weights_to_c(weights, name, output_file):
53
+ """Export int8 weights to C header file"""
54
+ if weights is None:
55
+ output_file.write(f"// {name} not found, using placeholder\n")
56
+ output_file.write(f"static const int8_t {name}[1] = {{0}};\n\n")
57
+ return
58
+
59
+ flat = weights.flatten()
60
+ output_file.write(f"static const int8_t {name}[{flat.size}] = {{\n ")
61
+ for i, val in enumerate(flat):
62
+ output_file.write(f"{int(val)}")
63
+ if i < flat.size - 1:
64
+ output_file.write(", ")
65
+ if (i + 1) % 16 == 0:
66
+ output_file.write("\n ")
67
+ output_file.write("\n};\n\n")
68
+
69
+
70
+ def export_bias_to_c(bias, name, output_file):
71
+ """Export int32 bias to C header file"""
72
+ if bias is None:
73
+ output_file.write(f"// {name} not found, using placeholder\n")
74
+ output_file.write(f"static const int32_t {name}[1] = {{0}};\n\n")
75
+ return
76
+
77
+ flat = bias.flatten()
78
+ output_file.write(f"static const int32_t {name}[{flat.size}] = {{\n ")
79
+ for i, val in enumerate(flat):
80
+ output_file.write(f"{int(val)}")
81
+ if i < flat.size - 1:
82
+ output_file.write(", ")
83
+ if (i + 1) % 8 == 0:
84
+ output_file.write("\n ")
85
+ output_file.write("\n};\n\n")
86
+
87
+
88
+ def export_concatenated_weights(weights_dict, output_file, array_name,
89
+ dtype='int8'):
90
+ """Export concatenated weight array from gate dictionary.
91
+
92
+ Args:
93
+ weights_dict: dict with keys ['i', 'f', 'g', 'o']
94
+ containing weight arrays
95
+ output_file: file handle to write to
96
+ array_name: C array name
97
+ dtype: 'int8' or 'int32'
98
+ """
99
+ gate_order = ['i', 'f', 'g', 'o']
100
+ arrays = []
101
+ total_size = 0
102
+ missing_gates = []
103
+
104
+ for gate in gate_order:
105
+ w = weights_dict.get(gate)
106
+ if w is None:
107
+ missing_gates.append(gate)
108
+ continue
109
+ arrays.append(w.flatten())
110
+ total_size += w.size
111
+
112
+ if missing_gates:
113
+ warning(f"{missing_gates} gate weights missing, padding with zeros")
114
+ # Get shape from first non-None weight
115
+ for gate in gate_order:
116
+ w = weights_dict.get(gate)
117
+ if w is not None:
118
+ shape = w.shape
119
+ for mg in missing_gates:
120
+ zero_array = np.zeros(shape, dtype=np.int8)
121
+ arrays.append(zero_array.flatten())
122
+ total_size += zero_array.size
123
+ break
124
+
125
+ if not arrays:
126
+ output_file.write(f"// {array_name} not found, using placeholder\n")
127
+ output_file.write(f"static const int8_t {array_name}[1] = {{0}};\n\n")
128
+ return
129
+
130
+ concatenated = np.concatenate(arrays)
131
+ total_size = len(concatenated)
132
+
133
+ c_type = 'int8_t' if dtype == 'int8' else 'int32_t'
134
+ output_file.write(
135
+ f"static const {c_type} {array_name}[{total_size}] = {{\n ")
136
+ for i, val in enumerate(concatenated):
137
+ output_file.write(f"{int(val)}")
138
+ if i < total_size - 1:
139
+ output_file.write(", ")
140
+ if (i + 1) % 16 == 0:
141
+ output_file.write("\n ")
142
+ output_file.write("\n};\n\n")
143
+
144
+
145
+ def export_concatenated_bias(bias_dict, output_file, array_name):
146
+ """Export concatenated bias array from gate dictionary.
147
+
148
+ Args:
149
+ bias_dict: dict with keys ['i', 'f', 'g', 'o'] containing bias arrays
150
+ output_file: file handle to write to
151
+ array_name: C array name
152
+ """
153
+ gate_order = ['i', 'f', 'g', 'o']
154
+ arrays = []
155
+
156
+ for gate in gate_order:
157
+ b = bias_dict.get(gate)
158
+ if b is not None:
159
+ arrays.append(b.flatten())
160
+
161
+ if not arrays:
162
+ output_file.write(f"// {array_name} not found, using placeholder\n")
163
+ output_file.write(f"static const int32_t {array_name}[1] = {{0}};\n\n")
164
+ return
165
+
166
+ concatenated = np.concatenate(arrays)
167
+ total_size = len(concatenated)
168
+
169
+ output_file.write(
170
+ f"static const int32_t {array_name}[{total_size}] = {{\n ")
171
+ for i, val in enumerate(concatenated):
172
+ output_file.write(f"{int(val)}")
173
+ if i < total_size - 1:
174
+ output_file.write(", ")
175
+ if (i + 1) % 8 == 0:
176
+ output_file.write("\n ")
177
+ output_file.write("\n};\n\n")
178
+
179
+
180
+ def export_model_weights(output_dir, model_info):
181
+ """Unified weight export function for ONNX, TFLite and ANG models.
182
+
183
+ Weights are identified by source-specific keys:
184
+ - TFLite: "fc_tflite.weight", "fc_tflite.bias",
185
+ "lstm_tflite.weight_ih", etc.
186
+ - ONNX: "fc_onnx.weight", "fc_onnx.bias", "conv_onnx.weight", etc.
187
+ - ANG: tensor index as key in weights dict
188
+
189
+ Returns quant_scales dict.
190
+ """
191
+ weights = model_info.get("weights", {})
192
+ ops = model_info.get("ops", [])
193
+ quant_scales = {}
194
+
195
+ # Input scale fixed at 1/256 (symmetric quantization, zero_point=0)
196
+ input_scale = 0.00390625
197
+
198
+ # For ANG models, weights are stored by tensor index
199
+ # We need to extract them from ops
200
+ def get_weight_by_idx(weights, idx):
201
+ """Get weight from weights dict by tensor index"""
202
+ if idx is None:
203
+ return None
204
+ # ANG format: weights[str(idx)] = numpy array or list
205
+ if str(idx) in weights:
206
+ w = weights[str(idx)]
207
+ # Convert list to numpy array
208
+ if isinstance(w, list):
209
+ return np.array(w)
210
+ return w
211
+ # Also try int key
212
+ if idx in weights:
213
+ w = weights[idx]
214
+ if isinstance(w, list):
215
+ return np.array(w)
216
+ return w
217
+ return None
218
+
219
+ # Collect FC weights from ops
220
+ fc_weights = []
221
+ for op in ops:
222
+ if op.get("op_name") == "FULLY_CONNECTED":
223
+ input_indices = op.get("input_indices", [])
224
+ if len(input_indices) >= 3:
225
+ weight_idx = input_indices[1]
226
+ bias_idx = input_indices[2]
227
+ fc_w = get_weight_by_idx(weights, weight_idx)
228
+ fc_b = get_weight_by_idx(weights, bias_idx)
229
+ if fc_w is not None:
230
+ fc_weights.append((fc_w, fc_b))
231
+
232
+ # Export FC weights
233
+ if fc_weights:
234
+ all_fc_weights = []
235
+ all_fc_bias = []
236
+ for fc_w, fc_b in fc_weights:
237
+ all_fc_weights.append(fc_w.flatten())
238
+ if fc_b is not None:
239
+ all_fc_bias.append(fc_b.flatten())
240
+ else:
241
+ all_fc_bias.append(np.zeros(fc_w.shape[-1], dtype=np.int32))
242
+
243
+ fc_weights_concat = (np.concatenate(all_fc_weights)
244
+ if len(all_fc_weights) > 1 else all_fc_weights[0])
245
+ fc_bias_concat = (np.concatenate(all_fc_bias)
246
+ if len(all_fc_bias) > 1 else all_fc_bias[0])
247
+
248
+ fc_scale = 0.01
249
+ fc_weight_int8 = (fc_weights_concat
250
+ if fc_weights_concat.dtype == np.int8
251
+ else quantize_to_int8(fc_weights_concat)[0])
252
+ fc_bias_int32 = (fc_bias_concat.astype(np.int32)
253
+ if fc_bias_concat.dtype != np.int32
254
+ else fc_bias_concat)
255
+
256
+ with open(output_dir / 'fc_weights.h', 'w') as f:
257
+ f.write("// FC layer weights and bias extracted from ANG model\n\n")
258
+ export_weights_to_c(fc_weight_int8, "fc_weights", f)
259
+ export_bias_to_c(fc_bias_int32, "fc_bias", f)
260
+ info(f"Generated: {output_dir}/fc_weights.h")
261
+ quant_scales["fc_scale"] = fc_scale
262
+
263
+ # Collect CONV weights from ops
264
+ conv_weights_list = []
265
+ conv_bias_list = []
266
+ for op in ops:
267
+ if op.get("op_name") == "CONV_2D":
268
+ input_indices = op.get("input_indices", [])
269
+ if len(input_indices) >= 2:
270
+ weight_idx = input_indices[1]
271
+ bias_idx = input_indices[2] if len(input_indices) >= 3 else None
272
+ conv_w = get_weight_by_idx(weights, weight_idx)
273
+ conv_b = (get_weight_by_idx(weights, bias_idx)
274
+ if bias_idx else None)
275
+ if conv_w is not None:
276
+ conv_w = np.array(conv_w)
277
+ conv_weights_list.append(conv_w)
278
+ if conv_b is not None:
279
+ conv_b = np.array(conv_b)
280
+ conv_bias_list.append(
281
+ conv_b if conv_b is not None
282
+ else np.zeros(conv_w.shape[-1], dtype=np.int32))
283
+
284
+ # Export CONV weights
285
+ if conv_weights_list:
286
+ conv_weights_concat = np.concatenate(
287
+ [np.array(w).flatten() for w in conv_weights_list])
288
+ conv_bias_concat = np.concatenate(
289
+ [np.array(b).flatten() for b in conv_bias_list])
290
+
291
+ conv_weight_int8 = (conv_weights_concat
292
+ if conv_weights_concat.dtype == np.int8
293
+ else quantize_to_int8(conv_weights_concat)[0])
294
+ conv_bias_int32 = (conv_bias_concat.astype(np.int32)
295
+ if conv_bias_concat.dtype != np.int32
296
+ else conv_bias_concat)
297
+
298
+ with open(output_dir / 'conv_weights.h', 'w') as f:
299
+ f.write("// CONV_2D weights and bias extracted from ANG model\n\n")
300
+ export_weights_to_c(conv_weight_int8, "conv_weights", f)
301
+ export_bias_to_c(conv_bias_int32, "conv_bias", f)
302
+ info(f"Generated: {output_dir}/conv_weights.h")
303
+
304
+ # Try TFLite/ONNX format as fallback
305
+ fc_weight = weights.get("fc_tflite.weight") or weights.get("fc_onnx.weight")
306
+ fc_bias = weights.get("fc_tflite.bias") or weights.get("fc_onnx.bias")
307
+ if fc_weight is not None and fc_bias is not None:
308
+ fc_scale = 0.01
309
+ if fc_weight.dtype == np.int8:
310
+ fc_weight_int8 = fc_weight
311
+ fc_bias_int32 = (fc_bias.astype(np.int32)
312
+ if fc_bias.dtype != np.int32 else fc_bias)
313
+ else:
314
+ fc_weight_int8, fc_scale = quantize_to_int8(fc_weight)
315
+ fc_bias_int32 = (fc_bias / (input_scale * fc_scale)
316
+ ).astype(np.int32)
317
+
318
+ with open(output_dir / 'fc_weights.h', 'w') as f:
319
+ f.write("// FC layer weights and bias extracted from model\n\n")
320
+ export_weights_to_c(fc_weight_int8, "fc_weights", f)
321
+ export_bias_to_c(fc_bias_int32, "fc_bias", f)
322
+ info(f"Generated: {output_dir}/fc_weights.h")
323
+ quant_scales["fc_scale"] = fc_scale
324
+
325
+ # LSTM weights
326
+ lstm_weight_ih = (
327
+ weights.get("lstm_tflite.weight_ih") or
328
+ weights.get("lstm_onnx.weight_ih"))
329
+ lstm_weight_hh = (
330
+ weights.get("lstm_tflite.weight_hh") or
331
+ weights.get("lstm_onnx.weight_hh"))
332
+ lstm_bias = (
333
+ weights.get("lstm_tflite.bias") or
334
+ weights.get("lstm_onnx.bias"))
335
+ if lstm_weight_ih is not None and lstm_weight_hh is not None:
336
+ with open(output_dir / 'lstm_weights.h', 'w') as f:
337
+ f.write("// LSTM gate weights and bias extracted from model\n")
338
+ f.write("// Order: i, f, g, o\n\n")
339
+ export_weights_to_c(lstm_weight_ih, "lstm_input_weights", f)
340
+ export_weights_to_c(lstm_weight_hh, "lstm_recurrent_weights", f)
341
+ if lstm_bias is not None:
342
+ export_bias_to_c(lstm_bias, "lstm_bias", f)
343
+ info(f"Generated: {output_dir}/lstm_weights.h")
344
+
345
+ # Conv weights - try both TFLite and ONNX sources
346
+ conv_weight = (weights.get("conv_tflite.weight") or
347
+ weights.get("conv_onnx.weight"))
348
+ conv_bias = (weights.get("conv_tflite.bias") or
349
+ weights.get("conv_onnx.bias"))
350
+ if conv_weight is not None:
351
+ conv_weight_int8 = (conv_weight
352
+ if conv_weight.dtype == np.int8
353
+ else quantize_to_int8(conv_weight)[0])
354
+ with open(output_dir / 'conv_weights.h', 'w') as f:
355
+ f.write("// CONV_2D weights and bias extracted from model\n\n")
356
+ export_weights_to_c(conv_weight_int8, "conv_weights", f)
357
+ if conv_bias is not None:
358
+ conv_bias_int32 = (conv_bias.astype(np.int32)
359
+ if conv_bias.dtype != np.int32 else conv_bias)
360
+ export_bias_to_c(conv_bias_int32, "conv_bias", f)
361
+ info(f"Generated: {output_dir}/conv_weights.h")
362
+
363
+ # Depthwise Conv weights
364
+ dw_weight = (weights.get("dw_tflite.weight") or
365
+ weights.get("dw_onnx.weight"))
366
+ dw_bias = (weights.get("dw_tflite.bias") or
367
+ weights.get("dw_onnx.bias"))
368
+ if dw_weight is not None:
369
+ dw_weight_int8 = (dw_weight
370
+ if dw_weight.dtype == np.int8
371
+ else quantize_to_int8(dw_weight)[0])
372
+ with open(output_dir / 'dw_weights.h', 'w') as f:
373
+ f.write("// Depthwise Conv2D weights and bias "
374
+ "extracted from model\n\n")
375
+ export_weights_to_c(dw_weight_int8, "dw_weights", f)
376
+ if dw_bias is not None:
377
+ dw_bias_int32 = (dw_bias.astype(np.int32)
378
+ if dw_bias.dtype != np.int32 else dw_bias)
379
+ export_bias_to_c(dw_bias_int32, "dw_bias", f)
380
+ info(f"Generated: {output_dir}/dw_weights.h")
381
+
382
+ return quant_scales