QTools: Energy Profiling and Hardware DataType Statistics
This notebook uses two core QTools execution flows to profile hardware estimation metrics on custom hybrid models containing both standard Keras and QKerasV3 quantized layers.
Objectives:
Part 1: Run Energy Cost Profiling utilizing the
horowitzprocess model comparing baseline reference execution configurations with specific user-defined trial configurations.Part 2: Run DataType Mapping & Parameter Statistics and print hardware-centric sizing descriptions (multipliers, accumulators, memory structures) intended for export streams.
Prerequisites
Ensure that qkeras-v3, tensorflow, and keras are correctly installed. If running inside Google Colab, uncomment and run the setup block below:
# !pip install qkeras-v3 tensorflow
1. Environment Setup & Imports
import keras
from qkeras import QActivation, QDense, quantizers
from qkeras.qtools import run_qtools
from qkeras.qtools import settings as qtools_settings
/Users/mariuskoppel/cms/qkeras/venv/lib/python3.11/site-packages/keras/src/export/tf2onnx_lib.py:8: FutureWarning: In the future `np.object` will be defined as the corresponding NumPy scalar.
if not hasattr(np, "object"):
2. Model Architectures Definitions
Here we define our structural helper functions. They accept identical input topologies but apply different internal parameter quantizations: standard quantized_bits for specific layout configurations and quantized_po2 (Power-of-2 structures) for hardware multiplier bypass profiling.
def hybrid_model_bits():
"""Hybrid model that mixes standard keras layers and quantized_bits qkeras layers."""
x = x_in = keras.layers.Input((784,), name="input")
x = keras.layers.Dense(300, name="d0")(x)
x = keras.layers.Activation("relu", name="d0_act")(x)
x = QDense(
100,
kernel_quantizer=quantizers.quantized_bits(4, 0, 1),
bias_quantizer=quantizers.quantized_bits(4, 0, 1),
name="d1",
)(x)
x = QActivation("quantized_relu(4,0)", name="d1_qr4")(x)
x = QDense(
10,
kernel_quantizer=quantizers.quantized_bits(4, 0, 1),
bias_quantizer=quantizers.quantized_bits(4, 0, 1),
name="d2",
)(x)
x = keras.layers.Activation("softmax", name="softmax")(x)
return keras.Model(inputs=[x_in], outputs=[x])
def hybrid_model_po2():
"""Hybrid model that mixes standard keras layers and power-of-2 qkeras layers."""
x = x_in = keras.layers.Input((784,), name="input")
x = keras.layers.Dense(300, name="d0")(x)
x = keras.layers.Activation("relu", name="d0_act")(x)
x = QDense(
100,
kernel_quantizer=quantizers.quantized_po2(4),
bias_quantizer=quantizers.quantized_po2(4),
name="d1",
)(x)
x = QActivation("quantized_relu(4,0)", name="d1_qr4")(x)
x = QDense(
10,
kernel_quantizer=quantizers.quantized_po2(4),
bias_quantizer=quantizers.quantized_po2(4),
name="d2",
)(x)
x = keras.layers.Activation("softmax", name="softmax")(x)
return keras.Model(inputs=[x_in], outputs=[x])
3. Part 1: Comprehensive Energy Profiling
We instantiate the quantized_bits network and track total hardware macro-costs using the standard Horowitz energy parameters model.
Pass A (
for_reference=True): Overwrites explicit quantization settings to determine baseline parameters (int8weights,int32accumulators).Pass B (
for_reference=False): Profiles user-configured network bounds to compute actual hardware efficiency savings.
model_bits = hybrid_model_bits()
model_bits.summary()
reference_internal = "int8"
reference_accumulator = "int32"
print("\n--- RUNNING PASS A: BASELINE REFERENCE ENERGY CONFIGURATION ---")
q_ref = run_qtools.QTools(
model_bits,
process="horowitz",
source_quantizers=[quantizers.quantized_bits(8, 0, 1)],
is_inference=False,
weights_path=None,
keras_quantizer=reference_internal,
keras_accumulator=reference_accumulator,
for_reference=True, # Overwrites explicit user rules for baseline profiling
)
ref_energy_dict = q_ref.pe(
weights_on_memory="sram",
activations_on_memory="sram",
min_sram_size=8 * 16 * 1024 * 1024,
rd_wr_on_io=False,
)
reference_energy_profile = q_ref.extract_energy_profile(qtools_settings.cfg.include_energy, ref_energy_dict)
total_reference_energy = q_ref.extract_energy_sum(qtools_settings.cfg.include_energy, ref_energy_dict)
print("Baseline Energy Profile Map:", reference_energy_profile)
print("Total Baseline Energy Metric:", total_reference_energy)
print("\n--- RUNNING PASS B: TARGET TRIAL ENERGY CONFIGURATION ---")
q_trial = run_qtools.QTools(
model_bits,
process="horowitz",
source_quantizers=[quantizers.quantized_bits(8, 0, 1)],
is_inference=False,
weights_path=None,
keras_quantizer=reference_internal,
keras_accumulator=reference_accumulator,
for_reference=False, # Evaluates explicit user quantizations
)
trial_energy_dict = q_trial.pe(
weights_on_memory="sram",
activations_on_memory="sram",
min_sram_size=8 * 16 * 1024 * 1024,
rd_wr_on_io=False,
)
trial_energy_profile = q_trial.extract_energy_profile(qtools_settings.cfg.include_energy, trial_energy_dict)
total_trial_energy = q_trial.extract_energy_sum(qtools_settings.cfg.include_energy, trial_energy_dict)
print("Trial Quantized Energy Profile Map:", trial_energy_profile)
print("Total Quantized Trial Energy Metric:", total_trial_energy)
Model: "functional_1"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ │ input (InputLayer) │ (None, 784) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ d0 (Dense) │ (None, 300) │ 235,500 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ d0_act (Activation) │ (None, 300) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ d1 (QDense) │ (None, 100) │ 30,100 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ d1_qr4 (QActivation) │ (None, 100) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ d2 (QDense) │ (None, 10) │ 1,010 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ softmax (Activation) │ (None, 10) │ 0 │ └─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 266,610 (1.02 MB)
Trainable params: 266,610 (1.02 MB)
Non-trainable params: 0 (0.00 B)
--- RUNNING PASS A: BASELINE REFERENCE ENERGY CONFIGURATION ---
Baseline Energy Profile Map: {'d0': {'energy': {'inputs': 372.77, 'outputs': 570.57, 'parameters': 111975.97, 'op_cost': 70560.0}, 'total': 182908.74}, 'd0_act': {'energy': {'inputs': 570.57, 'outputs': 570.57, 'parameters': 0.0, 'op_cost': 0.0}, 'total': 570.57}, 'd1': {'energy': {'inputs': 570.57, 'outputs': 190.19, 'parameters': 14313.66, 'op_cost': 26500.0}, 'total': 41384.229999999996}, 'd1_qr4': {'energy': {'inputs': 190.19, 'outputs': 190.19, 'parameters': 0.0, 'op_cost': 0.0}, 'total': 190.19}, 'd2': {'energy': {'inputs': 190.19, 'outputs': 19.02, 'parameters': 483.08, 'op_cost': 883.33}, 'total': 1556.6}, 'softmax': {'energy': {'inputs': 19.02, 'outputs': 19.02, 'parameters': 0.0, 'op_cost': 0.0}, 'total': 19.02}}
Total Baseline Energy Metric: 226629
--- RUNNING PASS B: TARGET TRIAL ENERGY CONFIGURATION ---
adjust multiplier for auto_po2 ...
adjust multiplier for auto_po2 ...
Trial Quantized Energy Profile Map: {'d0': {'energy': {'inputs': 372.77, 'outputs': 570.57, 'parameters': 111975.97, 'op_cost': 70560.0}, 'total': 182908.74}, 'd0_act': {'energy': {'inputs': 570.57, 'outputs': 570.57, 'parameters': 0.0, 'op_cost': 0.0}, 'total': 570.57}, 'd1': {'energy': {'inputs': 570.57, 'outputs': 270.07, 'parameters': 7158.73, 'op_cost': 16072.3}, 'total': 23801.6}, 'd1_qr4': {'energy': {'inputs': 270.07, 'outputs': 26.63, 'parameters': 0.0, 'op_cost': 0.0}, 'total': 26.63}, 'd2': {'energy': {'inputs': 26.63, 'outputs': 11.41, 'parameters': 243.44, 'op_cost': 102.08}, 'total': 372.15}, 'softmax': {'energy': {'inputs': 11.41, 'outputs': 19.02, 'parameters': 0.0, 'op_cost': 0.0}, 'total': 19.02}}
Total Quantized Trial Energy Metric: 207698
[WARNING] The weight quantizer is never called even though it has alpha=auto_po2. In this case we do not adjust the multiplier and accumulator bit width since we don't know the exact values of scale
[WARNING] The weight quantizer is never called even though it has alpha=auto_po2. In this case we do not adjust the multiplier and accumulator bit width since we don't know the exact values of scale
4. Part 2: DataType Mapping Generation & Struct Diagnostics
Now we evaluate the Power-of-2 (PO2) network model variant. This execution block triggers qtools_stats_print() to generate a structural map tracking bit-width allocations across operations like multi-bit products, structural accumulators, and internal source memory pointers.
model_po2 = hybrid_model_po2()
model_po2.summary()
print("\n--- RUNNING DATATYPE MAPPING & MUX-SIZE PRINTING ---")
input_quantizer_list = [quantizers.quantized_bits(8, 0, 1)]
reference_internal = "int8"
reference_accumulator = "int32"
q_stats = run_qtools.QTools(
model_po2,
process="horowitz",
source_quantizers=input_quantizer_list,
is_inference=False,
weights_path=None,
keras_quantizer=reference_internal,
keras_accumulator=reference_accumulator,
for_reference=False,
)
# Print structural hardware footprint diagnostic to standard output stream
q_stats.qtools_stats_print()
# Optional file system dumping pattern (uncomment below to utilize export flow):
# q_stats.qtools_stats_to_json("output_hardware_profile.json")
print("\nHardware layer sizing specifications compiled.")
Model: "functional_5"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓ ┃ Layer (type) ┃ Output Shape ┃ Param # ┃ ┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩ │ input (InputLayer) │ (None, 784) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ d0 (Dense) │ (None, 300) │ 235,500 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ d0_act (Activation) │ (None, 300) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ d1 (QDense) │ (None, 100) │ 30,100 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ d1_qr4 (QActivation) │ (None, 100) │ 0 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ d2 (QDense) │ (None, 10) │ 1,010 │ ├─────────────────────────────────┼────────────────────────┼───────────────┤ │ softmax (Activation) │ (None, 10) │ 0 │ └─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 266,610 (1.02 MB)
Trainable params: 266,610 (1.02 MB)
Non-trainable params: 0 (0.00 B)
--- RUNNING DATATYPE MAPPING & MUX-SIZE PRINTING ---
{
"source_quantizers": [
{
"quantizer_type": "quantized_bits",
"bits": 8,
"int_bits": 1,
"is_signed": true
}
],
"d0": {
"layer_type": "Dense",
"input_quantizer_list": [
{
"quantizer_type": "quantized_bits",
"bits": 8,
"int_bits": 1,
"is_signed": true
}
],
"output_quantizer": {
"quantizer_type": "quantized_bits",
"bits": 32,
"int_bits": 11,
"is_signed": true,
"shape": [
-1,
300
]
},
"weight_quantizer": {
"quantizer_type": "quantized_bits",
"bits": 8,
"int_bits": 1,
"is_signed": true,
"shape": [
784,
300
]
},
"bias_quantizer": {
"quantizer_type": "quantized_bits",
"bits": 8,
"int_bits": 1,
"is_signed": true,
"shape": 300
},
"multiplier": {
"quantizer_type": "quantized_bits",
"bits": 32,
"int_bits": 11,
"is_signed": true,
"op_type": "mul"
},
"accumulator": {
"quantizer_type": "quantized_bits",
"bits": 32,
"int_bits": 11,
"is_signed": true,
"op_type": "add"
},
"fused_accumulator": {
"quantizer_type": "quantized_bits",
"bits": 32,
"int_bits": 11,
"is_signed": true,
"op_type": "add"
},
"operation_count": 235200
},
"d0_act": {
"layer_type": "Activation",
"input_quantizer_list": [
{
"quantizer_type": "quantized_bits",
"bits": 32,
"int_bits": 11,
"is_signed": true
}
],
"output_quantizer": {
"quantizer_type": "quantized_bits",
"bits": 32,
"int_bits": 11,
"is_signed": true,
"shape": [
-1,
300
]
},
"operation_count": 300
},
"d1": {
"layer_type": "QDense",
"input_quantizer_list": [
{
"quantizer_type": "quantized_bits",
"bits": 32,
"int_bits": 11,
"is_signed": true
}
],
"output_quantizer": {
"quantizer_type": "quantized_bits",
"bits": 49,
"int_bits": 24,
"is_signed": 1,
"shape": [
-1,
100
]
},
"weight_quantizer": {
"quantizer_type": "quantized_po2",
"bits": 4,
"is_signed": 1,
"max_value": -1,
"shape": [
300,
100
]
},
"bias_quantizer": {
"quantizer_type": "quantized_po2",
"bits": 4,
"is_signed": 1,
"max_value": -1,
"shape": 100
},
"multiplier": {
"quantizer_type": "quantized_bits",
"bits": 39,
"int_bits": 14,
"is_signed": 1,
"op_type": "shifter"
},
"accumulator": {
"quantizer_type": "quantized_bits",
"bits": 49,
"int_bits": 24,
"is_signed": 1,
"op_type": "add"
},
"fused_accumulator": {
"quantizer_type": "quantized_bits",
"bits": 49,
"int_bits": 24,
"is_signed": 1,
"op_type": "add"
},
"operation_count": 30000
},
"d1_qr4": {
"layer_type": "QActivation",
"input_quantizer_list": [
{
"quantizer_type": "quantized_bits",
"bits": 49,
"int_bits": 24,
"is_signed": 1
}
],
"output_quantizer": {
"quantizer_type": "quantized_relu",
"bits": 4,
"int_bits": 0,
"is_signed": 0,
"shape": [
-1,
100
]
},
"operation_count": 100
},
"d2": {
"layer_type": "QDense",
"input_quantizer_list": [
{
"quantizer_type": "quantized_relu",
"bits": 4,
"int_bits": 0,
"is_signed": 0
}
],
"output_quantizer": {
"quantizer_type": "quantized_bits",
"bits": 20,
"int_bits": 12,
"is_signed": 1,
"shape": [
-1,
10
]
},
"weight_quantizer": {
"quantizer_type": "quantized_po2",
"bits": 4,
"is_signed": 1,
"max_value": -1,
"shape": [
100,
10
]
},
"bias_quantizer": {
"quantizer_type": "quantized_po2",
"bits": 4,
"is_signed": 1,
"max_value": -1,
"shape": 10
},
"multiplier": {
"quantizer_type": "quantized_bits",
"bits": 12,
"int_bits": 4,
"is_signed": 1,
"op_type": "shifter"
},
"accumulator": {
"quantizer_type": "quantized_bits",
"bits": 20,
"int_bits": 12,
"is_signed": 1,
"op_type": "add"
},
"fused_accumulator": {
"quantizer_type": "quantized_bits",
"bits": 20,
"int_bits": 12,
"is_signed": 1,
"op_type": "add"
},
"operation_count": 1000
},
"softmax": {
"layer_type": "Activation",
"input_quantizer_list": [
{
"quantizer_type": "quantized_bits",
"bits": 20,
"int_bits": 12,
"is_signed": 1
}
],
"output_quantizer": {
"quantizer_type": "quantized_bits",
"bits": 32,
"int_bits": 11,
"is_signed": true,
"shape": [
-1,
10
]
},
"operation_count": 10
}
}
Hardware layer sizing specifications compiled.