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
@@ -0,0 +1,200 @@
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
+ # Memory reuse optimization.
21
+
22
+ from typing import Dict, Any, Tuple
23
+ from TinyMLC.transform.base import Pass
24
+
25
+
26
+ ALIGN = 4 # Change to 8 if needed
27
+
28
+
29
+ class MemoryReuse(Pass):
30
+ """
31
+ Memory reuse optimization.
32
+
33
+ Reuses memory buffers for tensors whose lifetimes do not overlap.
34
+ This reduces peak RAM usage.
35
+ """
36
+
37
+ def __init__(self, name: str = "MemoryReuse"):
38
+ super().__init__(name)
39
+ self._tensor_lifetimes: Dict[int, Tuple[int, int]] = {}
40
+ # idx -> (birth, death)
41
+ self._allocation_map: Dict[int, int] = {} # tensor_idx -> buffer_id
42
+
43
+ def run(self, model_info: Dict[str, Any]) -> Dict[str, Any]:
44
+ """Run memory reuse on model_info."""
45
+ model_info = self._copy_model(model_info)
46
+
47
+ # 1. Compute tensor lifetimes
48
+ self._compute_lifetimes(model_info)
49
+
50
+ # 2. Build interference graph
51
+ self._build_allocation(model_info)
52
+
53
+ # 3. Assign buffer indices
54
+ self._assign_buffers(model_info)
55
+
56
+ return model_info
57
+
58
+ def _compute_lifetimes(self, model_info: Dict[str, Any]) -> None:
59
+ """
60
+ Compute birth (first write) and death (last read) for each tensor.
61
+
62
+ Birth: when the tensor is first created (as output of some op)
63
+ Death: when the tensor is last used (as input to some op)
64
+ """
65
+ ops = model_info.get("ops", [])
66
+
67
+ # Initialize: all tensors from inputs are born at time 0
68
+ # For simplicity, we use op indices as time markers
69
+ birth: Dict[int, int] = {}
70
+ death: Dict[int, int] = {}
71
+
72
+ # Input tensors: born at time 0
73
+ for inp in model_info.get("input", []):
74
+ idx = inp.get("tensor_index")
75
+ if idx is not None:
76
+ birth[idx] = 0
77
+ death[idx] = 0
78
+
79
+ # Scan ops in order
80
+ for op_idx, op in enumerate(ops):
81
+ # Outputs are born at this op index
82
+ for idx in op.get("output_indices", []):
83
+ birth[idx] = op_idx
84
+ death[idx] = op_idx # will be updated later
85
+
86
+ # Inputs are read at this op index
87
+ for idx in op.get("input_indices", []):
88
+ # If this is the last time this tensor is read, death = op_idx
89
+ # For now, we store death as the latest op that reads it
90
+ death[idx] = op_idx
91
+
92
+ # Also handle outputs: they live until the end
93
+ for out in model_info.get("output", []):
94
+ idx = out.get("tensor_index")
95
+ if idx is not None:
96
+ death[idx] = len(ops) # end of inference
97
+
98
+ self._tensor_lifetimes = {
99
+ idx: (birth.get(idx, 0), death.get(idx, 0))
100
+ for idx in set(birth.keys()) | set(death.keys())
101
+ }
102
+
103
+ self._log_change(
104
+ f"Computed lifetimes for {len(self._tensor_lifetimes)} tensors"
105
+ )
106
+
107
+ def _build_allocation(self, model_info: Dict[str, Any]) -> None:
108
+ """
109
+ Build allocation map using a greedy algorithm.
110
+
111
+ Two tensors can share a buffer if their lifetimes do not overlap.
112
+ """
113
+ tensors = list(self._tensor_lifetimes.keys())
114
+ allocations: Dict[int, int] = {}
115
+ buffer_sizes: Dict[int, int] = {} # buffer_id -> max size
116
+
117
+ # Sort tensors by birth time (earliest first)
118
+ sorted_tensors = sorted(
119
+ tensors,
120
+ key=lambda idx: self._tensor_lifetimes[idx][0]
121
+ )
122
+
123
+ for idx in sorted_tensors:
124
+ birth, death = self._tensor_lifetimes[idx]
125
+ size = self._get_tensor_size(model_info, idx)
126
+
127
+ # Find an existing buffer that can be reused
128
+ allocated = False
129
+ for buffer_id, buffer_size in buffer_sizes.items():
130
+ # Check if any tensor currently using this buffer overlaps
131
+ overlaps = False
132
+ for allocated_idx, buf_id in allocations.items():
133
+ if buf_id != buffer_id:
134
+ continue
135
+ a_birth, a_death = self._tensor_lifetimes[allocated_idx]
136
+ if not (death < a_birth or birth > a_death):
137
+ overlaps = True
138
+ break
139
+
140
+ if not overlaps and size <= buffer_size:
141
+ allocations[idx] = buffer_id
142
+ allocated = True
143
+ break
144
+
145
+ if not allocated:
146
+ # Create new buffer
147
+ buffer_id = len(buffer_sizes)
148
+ allocations[idx] = buffer_id
149
+ buffer_sizes[buffer_id] = size
150
+
151
+ self._allocation_map = allocations
152
+ self._log_change(
153
+ f"Allocated {len(buffer_sizes)} buffers for {len(tensors)} tensors"
154
+ )
155
+
156
+ def _assign_buffers(self, model_info: Dict[str, Any]) -> None:
157
+ """
158
+ Assign buffer IDs to tensors in model_info.
159
+
160
+ This adds a 'buffer_id' field to each tensor spec.
161
+ """
162
+ tensors = model_info.get("tensors", {})
163
+ for idx, spec in tensors.items():
164
+ # Add buffer_id to the tensor spec (will be used by codegen)
165
+ spec["buffer_id"] = self._allocation_map.get(idx, -1)
166
+
167
+ # For JSON output, we also add it to the dict representation
168
+ # But we don't need to expose it to codegen yet
169
+
170
+ total_bytes = sum(
171
+ self._get_tensor_size(model_info, idx) for idx in tensors.keys()
172
+ )
173
+ peak_bytes = sum(
174
+ self._get_tensor_size(model_info, idx)
175
+ for idx, buf_id in self._allocation_map.items()
176
+ if idx in tensors
177
+ )
178
+ self._log_change(
179
+ f"Peak RAM: {peak_bytes} bytes "
180
+ f"(total unique: {total_bytes})"
181
+ )
182
+
183
+ def _get_tensor_size(self, model_info: Dict[str, Any], idx: int) -> int:
184
+ """Get the size (in bytes) of a tensor."""
185
+ tensors = model_info.get("tensors", {})
186
+ spec = tensors.get(idx)
187
+ if not spec:
188
+ return 0
189
+
190
+ shape = spec.shape if hasattr(spec, 'shape') else spec.get('shape', [])
191
+ size = 1
192
+ for d in shape:
193
+ size *= d
194
+
195
+ # Assume 1 byte per element for int8
196
+ # TODO: Handle different dtypes
197
+
198
+ # Align to ALIGN bytes
199
+ aligned_size = ((size + ALIGN - 1) // ALIGN) * ALIGN
200
+ return size
@@ -0,0 +1,101 @@
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
+ # Pass manager that runs a sequence of optimization passes.
21
+
22
+ import json
23
+ import sys
24
+
25
+ from typing import Dict, Any, List
26
+ from TinyMLC.transform.base import Pass
27
+ from TinyMLC.transform.constant_folding import ConstantFolding
28
+ from TinyMLC.transform.dce import DeadCodeElimination
29
+ from TinyMLC.transform.cse import CommonSubexpressionElimination
30
+ from TinyMLC.transform.simplify import Simplify
31
+ from TinyMLC.transform.algebraic import AlgebraicSimplify
32
+ from TinyMLC.transform.fusion import OperatorFusion
33
+ from TinyMLC.transform.memory import MemoryReuse
34
+ from utils.dump import info
35
+
36
+
37
+ class PassManager:
38
+ """
39
+ Manages and runs a sequence of optimization passes.
40
+ """
41
+
42
+ def __init__(self, passes: List[Pass] = None):
43
+ self.passes = passes or []
44
+ self._results = []
45
+
46
+ def add_pass(self, pass_obj: Pass) -> None:
47
+ """Add a pass to the pipeline."""
48
+ self.passes.append(pass_obj)
49
+
50
+ def run(self, model_info: Dict[str, Any]) -> Dict[str, Any]:
51
+ """
52
+ Run all passes in sequence on model_info.
53
+
54
+ Returns:
55
+ The transformed model_info after all passes.
56
+ """
57
+ current = model_info
58
+ self._results = []
59
+
60
+ for pass_obj in self.passes:
61
+ info(f" Running: {pass_obj.name}")
62
+ current = pass_obj.run(current)
63
+ print(f"OPTIMIZED_MODEL: {json.dumps(current)}")
64
+ sys.stdout.flush()
65
+ self._results.append({
66
+ "name": pass_obj.name,
67
+ "stats": pass_obj.get_stats(),
68
+ })
69
+
70
+ return current
71
+
72
+ @classmethod
73
+ def default_pipeline(cls) -> "PassManager":
74
+ """Create the default optimization pass pipeline."""
75
+ pm = cls()
76
+ pm.add_pass(ConstantFolding())
77
+ pm.add_pass(DeadCodeElimination())
78
+ pm.add_pass(CommonSubexpressionElimination())
79
+ pm.add_pass(DeadCodeElimination())
80
+ pm.add_pass(Simplify())
81
+ pm.add_pass(DeadCodeElimination())
82
+ pm.add_pass(OperatorFusion())
83
+ pm.add_pass(DeadCodeElimination())
84
+ pm.add_pass(AlgebraicSimplify())
85
+ pm.add_pass(DeadCodeElimination())
86
+ pm.add_pass(MemoryReuse())
87
+ return pm
88
+
89
+ def get_results(self) -> List[Dict[str, Any]]:
90
+ """Get statistics from all passes."""
91
+ return self._results
92
+
93
+ def dump_summary(self) -> None:
94
+ """Dump a summary of all passes."""
95
+ info("\n=== Pass Summary ===")
96
+ for result in self._results:
97
+ stats = result["stats"]
98
+ changes = stats.get("changes", [])
99
+ info(f" {result['name']}: {len(changes)} changes")
100
+ for change in changes:
101
+ info(f" - {change}")