Repository: larq/larq

Issue:
Add int8 quantized size to model summary
### Feature motivation
Our model summary currently reports the following two metrics for model size:

```
| Model size:                        151.80 KiB |
| Float-32 Equivalent                158.91 KiB |
```

This is calculated as follows for models that contain both binary and full precision (FP) weights:

| Metric | Binary weights | FP weights |
| --- | --- | --- |
| `Model size` | counted as 1 bit | counted as 32 bits |
| `Float-32 Equivalent` | counted as 32 bits | counted as 32 bits |

For models in which FP weights will be 8-bit quantized for inference, it'd be useful to also know how large that model would be.

### Feature description
I propose adding another metric to the model summary:

| Metric | bin weights | FP weights |
| --- | --- | --- |
| `int-8 quantized equivalent` | counted as 1 bit | counted as 8 bits |

### Feature implementation
Adding another property to [`ModelProfile`](https://github.com/larq/larq/blob/master/larq/models.py#L242), similar to:

```python
    @property
    def fp_equivalent_memory(self) -> int:
        return sum(l.fp_equivalent_memory for l in self.layer_profiles)
```

And then adding it to the model summary printout.


Discussion:
Yes would be  a useful feature! I would call it `Model size (max bits/param=8)` or something like that though, to me `int-8 quantized` suggests binary weights are counted as 8-bit as well.

Resolving patch:
diff --git a/larq/models.py b/larq/models.py
--- a/larq/models.py
+++ b/larq/models.py
@@ -110,6 +110,14 @@ def memory(self) -> int:
     def fp_equivalent_memory(self) -> int:
         return 32 * self.count
 
+    @property
+    def int8_fp_weights_memory(self) -> int:
+        """Count any 32- or 16-bit weights as 8 bits instead."""
+
+        if self.bitwidth > 8:
+            return self.count * 8
+        return self.bitwidth * self.count
+
     def is_bias(self) -> bool:
         return "bias" in self._weight.name
 
@@ -148,6 +156,10 @@ def __init__(self, layer: tf.keras.layers.Layer):
     def memory(self) -> int:
         return sum(p.memory for p in self.weight_profiles)
 
+    @property
+    def int8_fp_weights_memory(self) -> int:
+        return sum(p.int8_fp_weights_memory for p in self.weight_profiles)
+
     @property
     def fp_equivalent_memory(self) -> int:
         return sum(p.fp_equivalent_memory for p in self.weight_profiles)
@@ -247,6 +259,10 @@ def __init__(self, model: tf.keras.models.Model):
     def memory(self) -> int:
         return sum(l.memory for l in self.layer_profiles)
 
+    @property
+    def int8_fp_weights_memory(self) -> int:
+        return sum(l.int8_fp_weights_memory for l in self.layer_profiles)
+
     @property
     def fp_equivalent_memory(self) -> int:
         return sum(l.fp_equivalent_memory for l in self.layer_profiles)
@@ -336,7 +352,11 @@ def generate_summary(
                 "Non-trainable params",
                 _number_as_readable_str(self.weight_count(trainable=False)),
             ],
-            ["Model size:", memory_as_readable_str(self.memory)],
+            ["Model size", memory_as_readable_str(self.memory)],
+            [
+                "Model size (8-bit FP weights)",
+                memory_as_readable_str(self.int8_fp_weights_memory),
+            ],
             ["Float-32 Equivalent", memory_as_readable_str(self.fp_equivalent_memory)],
             [
                 "Compression Ratio of Memory",
@@ -415,6 +435,7 @@ def summary(
     - total number of trainable weights,
     - total number of non-trainable weights,
     - model size,
+    - model size (8-bit FP weights): memory footprint if FP weights were 8 bit,
     - float-32 equivalent size: memory footprint if all weights were 32 bit,
     - compression ratio achieved by quantizing weights,
     - total number of MAC operations,


Test patch:
diff --git a/larq/models_test.py b/larq/models_test.py
--- a/larq/models_test.py
+++ b/larq/models_test.py
@@ -57,7 +57,7 @@ def test_layer_profile():
     ]
     bias_count = [32, 0, 0, 32, 0, 10]
     param_count = [k + b for k, b in zip(kernel_count, bias_count)]
-    memory = [
+    memory = [  # bits * (c * w * h * b) + bits * bias
         1 * (32 * 3 * 3 * 1) + 32 * 32,
         0,
         2 * (32 * 3 * 3),
@@ -65,6 +65,14 @@ def test_layer_profile():
         0,
         32 * (32 * 11 * 11 * 10 + 10),
     ]
+    int8_fp_weights_mem = [
+        1 * (32 * 3 * 3 * 1) + 8 * 32,
+        0,
+        2 * (32 * 3 * 3),
+        1 * (32 * 3 * 3 * 1 + 32 * 1 * 1 * 32) + 8 * 32,
+        0,
+        8 * (32 * 11 * 11 * 10 + 10),
+    ]
     fp_equiv_mem = [32 * n for n in param_count]
     input_precision = [None, None, 2, 1, None, None]
     output_shape = [
@@ -95,6 +103,7 @@ def test_layer_profile():
         assert profiles[i].unique_op_precisions == unique_op_precisions[i]
         assert profiles[i].memory == memory[i]
         assert profiles[i].fp_equivalent_memory == fp_equiv_mem[i]
+        assert profiles[i].int8_fp_weights_memory == int8_fp_weights_mem[i]
         assert profiles[i].op_count("mac") == mac_count[i]
         assert profiles[i].op_count("mac", 1) == bin_mac_count[i]
 
diff --git a/larq/snapshots/snap_models_test.py b/larq/snapshots/snap_models_test.py
--- a/larq/snapshots/snap_models_test.py
+++ b/larq/snapshots/snap_models_test.py
@@ -4,6 +4,7 @@
 
 from snapshottest import Snapshot
 
+
 snapshots = Snapshot()
 
 snapshots[
@@ -25,7 +26,8 @@
 | Total params                       40.7 k     |
 | Trainable params                   1.95 k     |
 | Non-trainable params               38.7 k     |
-| Model size:                        151.80 KiB |
+| Model size                         151.80 KiB |
+| Model size (8-bit FP weights)      38.15 KiB  |
 | Float-32 Equivalent                158.91 KiB |
 | Compression Ratio of Memory        0.96       |
 | Number of MACs                     1.41 M     |
@@ -44,13 +46,14 @@
 +--------------------------------------+
 | Total                              0 |
 +--------------------------------------+
-+sequential_1 summary-----------------+
-| Total params                 0      |
-| Trainable params             0      |
-| Non-trainable params         0      |
-| Model size:                  0.00 B |
-| Float-32 Equivalent          0.00 B |
-| Compression Ratio of Memory  0.00   |
-| Number of MACs               0      |
-+-------------------------------------+
++sequential_1 summary-------------------+
+| Total params                   0      |
+| Trainable params               0      |
+| Non-trainable params           0      |
+| Model size                     0.00 B |
+| Model size (8-bit FP weights)  0.00 B |
+| Float-32 Equivalent            0.00 B |
+| Compression Ratio of Memory    0.00   |
+| Number of MACs                 0      |
++---------------------------------------+
 """