├── .gitignore ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── flopco_keras ├── __init__.py ├── compute_layer_flops.py ├── examples │ └── model_stats.ipynb └── flopco_keras.py ├── requirements.txt ├── setup.cfg └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | 3 | # Distribution / packaging 4 | build/ 5 | dist/ 6 | *.egg-info/ 7 | 8 | #Ipython Notebook 9 | .ipynb_checkpoints 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 juliagusak 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include requirements.txt 3 | recursive-include dependencies * 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | FlopCo 2 | ===== 3 | 4 | FlopCo is a Python library that aims to make FLOPs and MACs counting simple and accessible for Pytorch neural networks. 5 | Moreover FlopCo allows to collect other useful model statistics, such as number of parameters, shapes of layer inputs/outputs, etc. 6 | 7 | Requirements 8 | ----- 9 | - numpy 10 | - tensorflow>=2.0 11 | 12 | Installation 13 | ----- 14 | ```pip install flopco-keras ``` 15 | 16 | Quick start 17 | ----- 18 | ```python 19 | from flopco_keras import FlopCoKeras 20 | import tensorflow as tf 21 | 22 | model = tf.keras.applications.ResNet101() 23 | stats = FlopCoKeras(model) 24 | 25 | print(f"FLOPs: {stats.total_flops}") 26 | print(f"MACs: {stats.total_macs}") 27 | print(f"Relative FLOPs: {stats.relative_flops}") 28 | ``` 29 | 30 | List of estimated statistics includes: 31 | - total number of FLOPs/MACs 32 | - number of FLOPs/MACs for each layer 33 | - relative number of FLOPs/MACs for each layer 34 | 35 | Make sure your tf.keras model is builded properly 36 | 37 | MACS for: 38 | - ResNet50: 3879147569 (3.8B) 39 | - ResNet101: 7601604657 (7.6B) 40 | - ResNet152: 11326470193 (11.3B) 41 | 42 | Same as [eq here](https://neurohive.io/ru/vidy-nejrosetej/resnet-34-50-101/) 43 | 44 | License 45 | ----- 46 | 47 | Project is distributed under [MIT License](https://github.com/juliagusak/flopco-pytorch/blob/master/LICENSE.txt) 48 | -------------------------------------------------------------------------------- /flopco_keras/__init__.py: -------------------------------------------------------------------------------- 1 | from flopco_keras.flopco_keras import FlopCoKeras 2 | -------------------------------------------------------------------------------- /flopco_keras/compute_layer_flops.py: -------------------------------------------------------------------------------- 1 | 2 | def numel(w : list): 3 | out = 1 4 | for k in w: 5 | out *= k 6 | return out 7 | 8 | def compute_input_flops(layer, macs = False): 9 | return 0 10 | def compute_padding_flops(layer, macs = False): 11 | return 0 12 | def compute_activation_flops(layer, macs = False): 13 | return 0 14 | def compute_tfop_flops(layer, macs = False): 15 | return 0 16 | def compute_add_flops(layer, macs = False): 17 | return 0 18 | 19 | def compute_conv2d_flops(layer, macs = False): 20 | 21 | # _, cin, h, w = input_shape 22 | if layer.data_format == "channels_first": 23 | _, input_channels, _, _ = layer.input_shape 24 | _, output_channels, h, w, = layer.output_shape 25 | elif layer.data_format == "channels_last": 26 | _, _, _, input_channels = layer.input_shape 27 | _, h, w, output_channels = layer.output_shape 28 | 29 | w_h, w_w = layer.kernel_size 30 | 31 | # flops = h * w * output_channels * input_channels * w_h * w_w / (stride**2) 32 | flops = h * w * output_channels * input_channels * w_h * w_w 33 | 34 | 35 | if not macs: 36 | flops_bias = numel(layer.output_shape[1:]) if layer.use_bias is not None else 0 37 | flops = 2 * flops + flops_bias 38 | 39 | return int(flops) 40 | 41 | 42 | def compute_fc_flops(layer, macs = False): 43 | ft_in, ft_out = layer.input_shape[-1], layer.output_shape[-1] 44 | flops = ft_in * ft_out 45 | 46 | if not macs: 47 | flops_bias = ft_out if layer.use_bias is not None else 0 48 | flops = 2 * flops + flops_bias 49 | 50 | return int(flops) 51 | 52 | def compute_bn2d_flops(layer, macs = False): 53 | # subtract, divide, gamma, beta 54 | flops = 2 * numel(layer.input_shape[1:]) 55 | 56 | if not macs: 57 | flops *= 2 58 | 59 | return int(flops) 60 | 61 | 62 | def compute_relu_flops(layer, macs = False): 63 | 64 | flops = 0 65 | if not macs: 66 | flops = numel(layer.input_shape[1:]) 67 | 68 | return int(flops) 69 | 70 | 71 | def compute_maxpool2d_flops(layer, macs = False): 72 | 73 | flops = 0 74 | if not macs: 75 | flops = layer.pool_size[0]**2 * numel(layer.output_shape[1:]) 76 | 77 | return flops 78 | 79 | 80 | def compute_pool2d_flops(layer, macs = False): 81 | 82 | flops = 0 83 | if not macs: 84 | flops = layer.pool_size[0]**2 * numel(layer.output_shape[1:]) 85 | 86 | return flops 87 | 88 | def compute_globalavgpool2d_flops(layer, macs = False): 89 | 90 | if layer.data_format == "channels_first": 91 | _, input_channels, h, w = layer.input_shape 92 | _, output_channels = layer.output_shape 93 | elif layer.data_format == "channels_last": 94 | _, h, w, input_channels = layer.input_shape 95 | _, output_channels = layer.output_shape 96 | 97 | return h*w 98 | 99 | def compute_softmax_flops(layer, macs = False): 100 | 101 | nfeatures = numel(layer.input_shape[1:]) 102 | 103 | total_exp = nfeatures # https://stackoverflow.com/questions/3979942/what-is-the-complexity-real-cost-of-exp-in-cmath-compared-to-a-flop 104 | total_add = nfeatures - 1 105 | total_div = nfeatures 106 | 107 | flops = total_div + total_exp 108 | 109 | if not macs: 110 | flops += total_add 111 | 112 | return flops 113 | 114 | 115 | -------------------------------------------------------------------------------- /flopco_keras/examples/model_stats.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import os\n", 10 | "os.environ[\"CUDA_DEVICE_ORDER\"]=\"PCI_BUS_ID\" \n", 11 | "os.environ[\"CUDA_VISIBLE_DEVICES\"]=\"6\" " 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 3, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "from torchvision.models import resnet50\n", 21 | "from flopco import FlopCo\n", 22 | "\n", 23 | "device = 'cuda'" 24 | ] 25 | }, 26 | { 27 | "cell_type": "code", 28 | "execution_count": 4, 29 | "metadata": {}, 30 | "outputs": [], 31 | "source": [ 32 | "model = resnet50()\n", 33 | "model.to(device)\n", 34 | "\n", 35 | "stats = FlopCo(model, img_size=(1, 3, 224, 224), device=device)" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 8, 41 | "metadata": { 42 | "scrolled": false 43 | }, 44 | "outputs": [ 45 | { 46 | "data": { 47 | "text/plain": [ 48 | "dict_keys(['device', 'img_size', 'input_shapes', 'output_shapes', 'flops', 'macs', 'params', 'instances', 'ltypes', 'total_flops', 'total_macs', 'total_params', 'relative_flops', 'relative_macs', 'relative_params'])" 49 | ] 50 | }, 51 | "execution_count": 8, 52 | "metadata": {}, 53 | "output_type": "execute_result" 54 | } 55 | ], 56 | "source": [ 57 | "stats.__dict__.keys()" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": 6, 63 | "metadata": {}, 64 | "outputs": [ 65 | { 66 | "data": { 67 | "text/plain": [ 68 | "(4089184256,\n", 69 | " defaultdict(None,\n", 70 | " {'conv1': 0.02886001585137272,\n", 71 | " 'layer1.0.conv1': 0.003141226215115398,\n", 72 | " 'layer1.0.conv2': 0.028271035936038583,\n", 73 | " 'layer1.0.conv3': 0.012564904860461593,\n", 74 | " 'layer1.0.downsample.0': 0.012564904860461593,\n", 75 | " 'layer1.1.conv1': 0.012564904860461593,\n", 76 | " 'layer1.1.conv2': 0.028271035936038583,\n", 77 | " 'layer1.1.conv3': 0.012564904860461593,\n", 78 | " 'layer1.2.conv1': 0.012564904860461593,\n", 79 | " 'layer1.2.conv2': 0.028271035936038583,\n", 80 | " 'layer1.2.conv3': 0.012564904860461593,\n", 81 | " 'layer2.0.conv1': 0.025129809720923185,\n", 82 | " 'layer2.0.conv2': 0.028271035936038583,\n", 83 | " 'layer2.0.conv3': 0.012564904860461593,\n", 84 | " 'layer2.0.downsample.0': 0.025129809720923185,\n", 85 | " 'layer2.1.conv1': 0.012564904860461593,\n", 86 | " 'layer2.1.conv2': 0.028271035936038583,\n", 87 | " 'layer2.1.conv3': 0.012564904860461593,\n", 88 | " 'layer2.2.conv1': 0.012564904860461593,\n", 89 | " 'layer2.2.conv2': 0.028271035936038583,\n", 90 | " 'layer2.2.conv3': 0.012564904860461593,\n", 91 | " 'layer2.3.conv1': 0.012564904860461593,\n", 92 | " 'layer2.3.conv2': 0.028271035936038583,\n", 93 | " 'layer2.3.conv3': 0.012564904860461593,\n", 94 | " 'layer3.0.conv1': 0.025129809720923185,\n", 95 | " 'layer3.0.conv2': 0.028271035936038583,\n", 96 | " 'layer3.0.conv3': 0.012564904860461593,\n", 97 | " 'layer3.0.downsample.0': 0.025129809720923185,\n", 98 | " 'layer3.1.conv1': 0.012564904860461593,\n", 99 | " 'layer3.1.conv2': 0.028271035936038583,\n", 100 | " 'layer3.1.conv3': 0.012564904860461593,\n", 101 | " 'layer3.2.conv1': 0.012564904860461593,\n", 102 | " 'layer3.2.conv2': 0.028271035936038583,\n", 103 | " 'layer3.2.conv3': 0.012564904860461593,\n", 104 | " 'layer3.3.conv1': 0.012564904860461593,\n", 105 | " 'layer3.3.conv2': 0.028271035936038583,\n", 106 | " 'layer3.3.conv3': 0.012564904860461593,\n", 107 | " 'layer3.4.conv1': 0.012564904860461593,\n", 108 | " 'layer3.4.conv2': 0.028271035936038583,\n", 109 | " 'layer3.4.conv3': 0.012564904860461593,\n", 110 | " 'layer3.5.conv1': 0.012564904860461593,\n", 111 | " 'layer3.5.conv2': 0.028271035936038583,\n", 112 | " 'layer3.5.conv3': 0.012564904860461593,\n", 113 | " 'layer4.0.conv1': 0.025129809720923185,\n", 114 | " 'layer4.0.conv2': 0.028271035936038583,\n", 115 | " 'layer4.0.conv3': 0.012564904860461593,\n", 116 | " 'layer4.0.downsample.0': 0.025129809720923185,\n", 117 | " 'layer4.1.conv1': 0.012564904860461593,\n", 118 | " 'layer4.1.conv2': 0.028271035936038583,\n", 119 | " 'layer4.1.conv3': 0.012564904860461593,\n", 120 | " 'layer4.2.conv1': 0.012564904860461593,\n", 121 | " 'layer4.2.conv2': 0.028271035936038583,\n", 122 | " 'layer4.2.conv3': 0.012564904860461593,\n", 123 | " 'fc': 0.0005010836779692213}))" 124 | ] 125 | }, 126 | "execution_count": 6, 127 | "metadata": {}, 128 | "output_type": "execute_result" 129 | } 130 | ], 131 | "source": [ 132 | "stats.total_macs, stats.relative_flops" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": null, 138 | "metadata": {}, 139 | "outputs": [], 140 | "source": [] 141 | } 142 | ], 143 | "metadata": { 144 | "kernelspec": { 145 | "display_name": "Python 3", 146 | "language": "python", 147 | "name": "python3" 148 | }, 149 | "language_info": { 150 | "codemirror_mode": { 151 | "name": "ipython", 152 | "version": 3 153 | }, 154 | "file_extension": ".py", 155 | "mimetype": "text/x-python", 156 | "name": "python", 157 | "nbconvert_exporter": "python", 158 | "pygments_lexer": "ipython3", 159 | "version": "3.6.8" 160 | } 161 | }, 162 | "nbformat": 4, 163 | "nbformat_minor": 2 164 | } 165 | -------------------------------------------------------------------------------- /flopco_keras/flopco_keras.py: -------------------------------------------------------------------------------- 1 | import torch 2 | import torch.nn as nn 3 | 4 | import numpy as np 5 | from collections import defaultdict 6 | from functools import partial 7 | import copy 8 | 9 | from flopco_keras.compute_layer_flops import * 10 | 11 | 12 | class FlopCoKeras(): 13 | 14 | def __init__(self, model): 15 | ''' 16 | instances: list of layer types, 17 | supported types are [nn.Conv2d, nn.Linear, 18 | nn.BatchNorm2d, nn.ReLU, nn.MaxPool2d, nn.AvgPool2d, nn.Softmax] 19 | ''' 20 | self.model = model 21 | 22 | self.flops = [] 23 | self.macs = [] 24 | self.get_flops = { 25 | 'ReLU': compute_relu_flops, 26 | 'InputLayer': compute_input_flops, 27 | 'Conv2D': compute_conv2d_flops, 28 | 'ZeroPadding2D': compute_padding_flops, 29 | 'Activation': compute_activation_flops, 30 | 'Dense': compute_fc_flops, 31 | 'BatchNormalization': compute_bn2d_flops, 32 | 'TensorFlowOpLayer': compute_tfop_flops, 33 | 'MaxPooling2D': compute_pool2d_flops, 34 | 'Add': compute_add_flops, 35 | 'GlobalAveragePooling2D': compute_globalavgpool2d_flops} 36 | 37 | 38 | self.get_stats( flops = True, macs = True) 39 | 40 | self.total_flops = sum(self.flops) 41 | self.total_macs = sum(self.macs) 42 | # self.total_params = sum(self.params) #TO DO 43 | 44 | self.relative_flops = [k/self.total_flops for k in self.flops] 45 | 46 | self.relative_macs = [k/self.total_macs for k in self.macs] 47 | 48 | # self.relative_params = [k/self.total_params for k in self.params] #TO DO 49 | 50 | del self.model 51 | 52 | def __str__(self): 53 | print_info = "\n".join([str({k:v}) for k,v in self.__dict__.items()]) 54 | 55 | return str(self.__class__) + ": \n" + print_info 56 | 57 | # def count_params(self): 58 | # self.params = [0] 59 | # self.params = defaultdict(int) 60 | 61 | # for mname, m in self.model.named_modules(): 62 | # if m.__class__ in self.instances: 63 | 64 | # self.params[mname] = 0 65 | 66 | # for p in m.parameters(): 67 | # self.params[mname] += p.numel() 68 | 69 | def _save_flops(self, layer, macs=False): 70 | flops = self.get_flops[layer.__class__.__name__](layer, macs) 71 | if macs: 72 | self.macs.append(flops) 73 | else: 74 | self.flops.append(flops) 75 | 76 | 77 | def get_stats(self, flops = False, macs = False): 78 | 79 | # if params: 80 | # self.count_params() 81 | 82 | if flops: 83 | self.flops = [] 84 | 85 | if macs: 86 | self.macs = [] 87 | 88 | if flops: 89 | for layer in self.model.layers: 90 | self._save_flops(layer) 91 | if macs: 92 | for layer in self.model.layers: 93 | self._save_flops(layer, macs=True) 94 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy 2 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from setuptools import setup, find_packages 3 | from setuptools.command.install import install 4 | 5 | 6 | try: 7 | from pip._internal.req import parse_requirements 8 | except ImportError: 9 | from pip.req import parse_requirements 10 | 11 | 12 | def load_requirements(file_name): 13 | requirements = parse_requirements(file_name, session="test") 14 | return [str(item.req) for item in requirements] 15 | 16 | version="v0.1.1" 17 | setup( 18 | name="flopco-keras", 19 | version=version, 20 | description="FLOPs and other statistics COunter for TF.keras neural networks", 21 | author="Evgeny Ponomarev (based on Julia Gusak's work)", 22 | author_email="evgps@ya.ru", 23 | url="https://github.com/evgps/flopco-keras", 24 | download_url=f"https://github.com/evgps/flopco-keras/archive/{version}.tar.gz", 25 | keywords = ['tensorflow', 'keras', 'flops', 'macs', 'neural-networks', 'cnn'], 26 | license="MIT", 27 | packages=find_packages(), 28 | install_requires=load_requirements("requirements.txt") 29 | ) 30 | --------------------------------------------------------------------------------