femtocrux 1.2.0__py3-none-any.whl → 2.0.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.
- femtocrux/VERSION +1 -1
- femtocrux/client/client.py +18 -38
- femtocrux/grpc/compiler_service_pb2.py +22 -14
- femtocrux/server/__init__.py +1 -0
- femtocrux/server/compiler_frontend.py +168 -0
- femtocrux/server/server.py +21 -34
- femtocrux/util/utils.py +71 -11
- {femtocrux-1.2.0.dist-info → femtocrux-2.0.0.dist-info}/METADATA +1 -1
- femtocrux-2.0.0.dist-info/RECORD +22 -0
- {femtocrux-1.2.0.dist-info → femtocrux-2.0.0.dist-info}/WHEEL +1 -1
- femtocrux/femtostack/__init__.py +0 -7
- femtocrux/femtostack/common/__init__.py +0 -10
- femtocrux/femtostack/common/frontend.py +0 -109
- femtocrux/femtostack/common/metrics.py +0 -184
- femtocrux/femtostack/common/sim_io.py +0 -470
- femtocrux/femtostack/tflite_api/__init__.py +0 -6
- femtocrux/femtostack/tflite_api/tflite_frontend.py +0 -6
- femtocrux/femtostack/torch_api/__init__.py +0 -0
- femtocrux/femtostack/torch_api/frontend.py +0 -136
- femtocrux-1.2.0.dist-info/RECORD +0 -30
- {femtocrux-1.2.0.dist-info → femtocrux-2.0.0.dist-info}/licenses/LICENSE +0 -0
- {femtocrux-1.2.0.dist-info → femtocrux-2.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
from .sim_io import SimIOWrapper
|
|
3
|
-
import os
|
|
4
|
-
from femtobehav.fasmir import FASMIR
|
|
5
|
-
from femtobehav.sim import SimRunner
|
|
6
|
-
import tempfile
|
|
7
|
-
from typing import Tuple, Any
|
|
8
|
-
import zipfile
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class CompilerFrontend:
|
|
12
|
-
"""A generic compiler frontend, must be subclassed for each input IR/framework"""
|
|
13
|
-
|
|
14
|
-
def __init__(
|
|
15
|
-
self, input_ir: Any, fasmir: FASMIR = None, io_wrapper: SimIOWrapper = None
|
|
16
|
-
):
|
|
17
|
-
self.input_ir = input_ir
|
|
18
|
-
self.fasmir = fasmir
|
|
19
|
-
self.io_wrapper = io_wrapper
|
|
20
|
-
|
|
21
|
-
@property
|
|
22
|
-
def is_compiled(self):
|
|
23
|
-
return self.fasmir is not None and self.io_wrapper is not None
|
|
24
|
-
|
|
25
|
-
def _compile(self, input_ir: Any, options: dict) -> Tuple[FASMIR, SimIOWrapper]:
|
|
26
|
-
"""
|
|
27
|
-
Runs FM compiler to generate FASMIR, and encode io information in a
|
|
28
|
-
SimIOWrapper object.
|
|
29
|
-
|
|
30
|
-
Must be implemented for each frontend subclass.
|
|
31
|
-
|
|
32
|
-
Must return a tuple pair (FASMIR, SimIOWrapper)
|
|
33
|
-
"""
|
|
34
|
-
raise NotImplementedError(
|
|
35
|
-
"Subclasses need to implement this based on their input ir"
|
|
36
|
-
)
|
|
37
|
-
|
|
38
|
-
def compile(self, options: dict = {}):
|
|
39
|
-
if not self.is_compiled:
|
|
40
|
-
self.fasmir, self.io_wrapper = self._compile(self.input_ir, options)
|
|
41
|
-
|
|
42
|
-
def dump_bitfile(self, encrypt: bool = True) -> bytes:
|
|
43
|
-
"""Dumps a bitfile used to program the SPU."""
|
|
44
|
-
if not self.is_compiled:
|
|
45
|
-
raise RuntimeError("Model must be compiled before dumping bitfile")
|
|
46
|
-
|
|
47
|
-
with tempfile.TemporaryFile() as tmpfile:
|
|
48
|
-
with tempfile.TemporaryDirectory() as dirname:
|
|
49
|
-
# Dump memory files to a directory
|
|
50
|
-
runner = SimRunner(self.fasmir, data_dir=dirname, encrypt=encrypt)
|
|
51
|
-
runner.reset()
|
|
52
|
-
runner.finish()
|
|
53
|
-
|
|
54
|
-
# Archive the directory
|
|
55
|
-
with zipfile.ZipFile(
|
|
56
|
-
tmpfile, mode="w", compression=zipfile.ZIP_DEFLATED
|
|
57
|
-
) as archive:
|
|
58
|
-
for relpath in os.listdir(dirname):
|
|
59
|
-
abspath = os.path.join(dirname, relpath)
|
|
60
|
-
archive.write(abspath, arcname=relpath)
|
|
61
|
-
|
|
62
|
-
# Read out the bytes in the archive
|
|
63
|
-
tmpfile.seek(0)
|
|
64
|
-
bitfile = tmpfile.read()
|
|
65
|
-
|
|
66
|
-
return bitfile
|
|
67
|
-
|
|
68
|
-
def _get_padded_len(self, fasmir: FASMIR, name: str):
|
|
69
|
-
try:
|
|
70
|
-
fasmir_var = fasmir.data_vars[name]
|
|
71
|
-
except KeyError:
|
|
72
|
-
raise ValueError(
|
|
73
|
-
"Failed to find FASMIR variable corresponding to name %s" % name
|
|
74
|
-
)
|
|
75
|
-
return fasmir_var.numpy.shape[0]
|
|
76
|
-
|
|
77
|
-
def run_behavioral_simulator(
|
|
78
|
-
self,
|
|
79
|
-
*args: np.ndarray,
|
|
80
|
-
input_period: float = None,
|
|
81
|
-
quantize_inputs=True,
|
|
82
|
-
dequantize_outputs=True,
|
|
83
|
-
**kwargs
|
|
84
|
-
):
|
|
85
|
-
"""
|
|
86
|
-
Runs the behavioral simulator and returns outputs and metrics.
|
|
87
|
-
|
|
88
|
-
Arguments:
|
|
89
|
-
args (np.ndarray): Input tensors to the simulator, as numpy arrays. Either
|
|
90
|
-
floating-point or integer (see `quantize_inputs` for
|
|
91
|
-
more detail on input datatypes).
|
|
92
|
-
input_period (float, optional): total simulation time.
|
|
93
|
-
quantize_inputs (bool, optional): If True, then floating-point inputs will
|
|
94
|
-
be quantized to integer before passing into the simulator.
|
|
95
|
-
Otherwise, the simulator expects that the inputs will
|
|
96
|
-
already be in integer format. Default True.
|
|
97
|
-
dequantize_outputs (bool: optional): If True, the integer outputs from the
|
|
98
|
-
simulator will be cast back to the original
|
|
99
|
-
floating-point domain. Otherwise, the outputs will be
|
|
100
|
-
returned as integers. Default True.
|
|
101
|
-
|
|
102
|
-
"""
|
|
103
|
-
return self.io_wrapper.run(
|
|
104
|
-
*args,
|
|
105
|
-
input_period=input_period,
|
|
106
|
-
quantize_inputs=quantize_inputs,
|
|
107
|
-
dequantize_outputs=dequantize_outputs,
|
|
108
|
-
**kwargs
|
|
109
|
-
)
|
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import tabulate
|
|
3
|
-
from collections import defaultdict
|
|
4
|
-
from typing import List, Union, Dict
|
|
5
|
-
|
|
6
|
-
PREFIXES = {
|
|
7
|
-
1e-15: "f",
|
|
8
|
-
1e-12: "p",
|
|
9
|
-
1e-9: "n",
|
|
10
|
-
1e-6: "µ",
|
|
11
|
-
1e-3: "m",
|
|
12
|
-
1: "",
|
|
13
|
-
1e3: "k",
|
|
14
|
-
1e6: "M",
|
|
15
|
-
1e9: "G",
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def to_sci(x):
|
|
20
|
-
"""Convert a float to a scientific-prefix formatted string
|
|
21
|
-
|
|
22
|
-
e.g. 0.004531 -> '4.531 m'
|
|
23
|
-
"""
|
|
24
|
-
ks = np.array(list(PREFIXES.keys()))
|
|
25
|
-
ks_cut = ks[ks <= np.abs(x)]
|
|
26
|
-
if len(ks_cut) > 0:
|
|
27
|
-
k = np.max(ks[ks <= x])
|
|
28
|
-
pref = PREFIXES[k]
|
|
29
|
-
x = x / k
|
|
30
|
-
return f"{x:.3g} {pref}"
|
|
31
|
-
else:
|
|
32
|
-
return f"{x:.3g} "
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def _merge_dicts(dicts: List[dict]):
|
|
36
|
-
"""Merges list of dicts into dict of lists"""
|
|
37
|
-
output = defaultdict(list)
|
|
38
|
-
for d in dicts:
|
|
39
|
-
for k, v in d.items():
|
|
40
|
-
output[k].append(v)
|
|
41
|
-
for k, v in output.items():
|
|
42
|
-
if isinstance(v[0], dict):
|
|
43
|
-
output[k] = _merge_dicts(v)
|
|
44
|
-
return dict(output)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
ALLOWED_KEYS = [
|
|
48
|
-
"Dynamic Energy/Frame (J)",
|
|
49
|
-
"Total Energy/Frame (J)",
|
|
50
|
-
"Latency (s)",
|
|
51
|
-
"Static Energy/Frame (J)",
|
|
52
|
-
"Memory",
|
|
53
|
-
"Frames Simulated",
|
|
54
|
-
"Power (W)",
|
|
55
|
-
]
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class SimMetrics:
|
|
59
|
-
"""
|
|
60
|
-
Object storing hardware simulator metrics.
|
|
61
|
-
|
|
62
|
-
Arguments:
|
|
63
|
-
metrics (list[dict] or dict[list]): measured metrics per batch
|
|
64
|
-
from simulator
|
|
65
|
-
dt (float, optional): total duration of the simulation, in seconds.
|
|
66
|
-
If not provided, will use the total active time of the sim, but this will
|
|
67
|
-
overlook the time spent sleeping.
|
|
68
|
-
|
|
69
|
-
Attributes:
|
|
70
|
-
total_energy: average total energy in Joules
|
|
71
|
-
total_active_time: average time spent processing in seconds
|
|
72
|
-
latency_per_frame: active time divided by the number of processed
|
|
73
|
-
input frames
|
|
74
|
-
power: average power consumption, in Watts
|
|
75
|
-
|
|
76
|
-
metrics: detailed metrics dictionary
|
|
77
|
-
|
|
78
|
-
"""
|
|
79
|
-
|
|
80
|
-
def __init__(
|
|
81
|
-
self,
|
|
82
|
-
metrics: Union[List[Dict[str, float]], Dict[str, List[float]]],
|
|
83
|
-
dt=None,
|
|
84
|
-
reduction_mode: str = "mean",
|
|
85
|
-
):
|
|
86
|
-
if isinstance(metrics, list):
|
|
87
|
-
metrics = _merge_dicts(metrics)
|
|
88
|
-
|
|
89
|
-
for k in list(metrics.keys()):
|
|
90
|
-
if k not in ALLOWED_KEYS:
|
|
91
|
-
metrics.pop(k)
|
|
92
|
-
|
|
93
|
-
self.metrics = metrics
|
|
94
|
-
self.dt = dt
|
|
95
|
-
|
|
96
|
-
self.reduction_mode = reduction_mode
|
|
97
|
-
if reduction_mode == "mean":
|
|
98
|
-
self.reduce = np.mean
|
|
99
|
-
elif reduction_mode == "sum":
|
|
100
|
-
self.reduce = np.sum
|
|
101
|
-
|
|
102
|
-
@property
|
|
103
|
-
def num_frames(self):
|
|
104
|
-
return self.reduce(self.metrics["Frames Simulated"])
|
|
105
|
-
|
|
106
|
-
@property
|
|
107
|
-
def total_energy(self):
|
|
108
|
-
return self.reduce(self.metrics["Total Energy/Frame (J)"]) * self.num_frames
|
|
109
|
-
|
|
110
|
-
@property
|
|
111
|
-
def total_dynamic_energy(self):
|
|
112
|
-
return self.reduce(self.metrics["Dynamic Energy/Frame (J)"]) * self.num_frames
|
|
113
|
-
|
|
114
|
-
@property
|
|
115
|
-
def total_static_energy(self):
|
|
116
|
-
return self.reduce(self.metrics["Static Energy/Frame (J)"]) * self.num_frames
|
|
117
|
-
|
|
118
|
-
@property
|
|
119
|
-
def total_active_time(self):
|
|
120
|
-
return self.reduce(self.metrics["Latency (s)"]) * self.num_frames
|
|
121
|
-
|
|
122
|
-
@property
|
|
123
|
-
def latency(self):
|
|
124
|
-
return self.reduce(self.metrics["Latency (s)"])
|
|
125
|
-
|
|
126
|
-
@property
|
|
127
|
-
def total_time(self):
|
|
128
|
-
if self.dt is not None:
|
|
129
|
-
dt = self.dt * self.metrics["Frames Simulated"][0]
|
|
130
|
-
return max(dt, self.total_active_time)
|
|
131
|
-
else:
|
|
132
|
-
return self.total_active_time
|
|
133
|
-
|
|
134
|
-
@property
|
|
135
|
-
def power(self):
|
|
136
|
-
return self.reduce(self.metrics["Power (W)"])
|
|
137
|
-
|
|
138
|
-
def performance_report(self):
|
|
139
|
-
report = [
|
|
140
|
-
["total energy", f"{to_sci(self.total_energy)}J"],
|
|
141
|
-
["total dynamic energy", f"{to_sci(self.total_dynamic_energy)}J"],
|
|
142
|
-
["total static energy", f"{to_sci(self.total_static_energy)}J"],
|
|
143
|
-
["power", f"{to_sci(self.power)}W"],
|
|
144
|
-
["total active time", f"{to_sci(self.total_active_time)}s"],
|
|
145
|
-
["total time", f"{to_sci(self.total_time)}s"],
|
|
146
|
-
["latency/frame", f"{to_sci(self.latency)}s"],
|
|
147
|
-
]
|
|
148
|
-
output = tabulate.tabulate(report)
|
|
149
|
-
return output
|
|
150
|
-
|
|
151
|
-
def memory_report(self):
|
|
152
|
-
mem = self.metrics["Memory"]
|
|
153
|
-
|
|
154
|
-
out = {}
|
|
155
|
-
for mem_type in ["Data Mem", "Instr Mem", "Table Mem"]:
|
|
156
|
-
k = f"{mem_type} (B)"
|
|
157
|
-
out[mem_type] = {
|
|
158
|
-
"Used": int(mem["Used"][k][0]),
|
|
159
|
-
"Capacity": int(mem["Capacity"][k][0]),
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
full_names = {
|
|
163
|
-
"Data Mem": "Data Memory",
|
|
164
|
-
"Instr Mem": "Instruction Memory",
|
|
165
|
-
"Table Mem": "Table Memory",
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
report = [["Memory Type", "Used", "Capacity", "Percentage"]]
|
|
169
|
-
for key, name in full_names.items():
|
|
170
|
-
used = out[key]["Used"]
|
|
171
|
-
cap = out[key]["Capacity"]
|
|
172
|
-
pct = 100 * used / cap
|
|
173
|
-
report.append([name, f"{to_sci(used)}B", f"{to_sci(cap)}B", f"{pct:.1f}%"])
|
|
174
|
-
return tabulate.tabulate(report, headers="firstrow")
|
|
175
|
-
|
|
176
|
-
def __repr__(self):
|
|
177
|
-
perf = self.performance_report()
|
|
178
|
-
mem = self.memory_report()
|
|
179
|
-
output = f"Behavioral Simulator Metrics, {self.reduction_mode} over batches"
|
|
180
|
-
output += f"\n\n{perf}"
|
|
181
|
-
output += f"\n\n{mem}"
|
|
182
|
-
|
|
183
|
-
output = ("-" * 60 + "\n") + output + ("\n" + "-" * 60)
|
|
184
|
-
return output
|