├── .gitignore ├── .gitmodules ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── bin ├── layer.py ├── lf.py ├── lf_generate_snapshot.py ├── lf_quant.py ├── requirements.txt ├── tanh_table_generator.py ├── template.py └── template │ ├── Makefile.jinja │ ├── dequan_layer_comp.c │ ├── dequan_layer_struct.c │ ├── fc_layer_comp.c │ ├── fc_layer_struct.c │ ├── main.c │ ├── quan_layer_comp.c │ ├── quan_layer_struct.c │ ├── tanh_layer_comp.c │ ├── tanh_layer_struct.c │ └── tanh_lookup_table.h ├── data ├── aurora │ ├── aurora_int_quan_model.tflite │ ├── representative_dataset.npy │ ├── saved_model.pbtxt │ └── variables │ │ ├── variables.data-00000-of-00001 │ │ └── variables.index └── converted_simple_model.tflite ├── datapath ├── Makefile ├── liteflow.c ├── liteflow_model.c ├── liteflow_model.h ├── liteflow_netfilter.c ├── liteflow_nl.c └── liteflow_tcp.c ├── doc ├── liteflow-logo.png └── liteflow-logo.psd ├── include ├── liblf.h ├── linux │ ├── liteflow.h │ └── tanh_lookup_table.h ├── liteflow_netfilter.h ├── liteflow_nl.h └── liteflow_tcp.h ├── lib ├── Makefile └── liblf_dp_notification.c ├── python ├── README.md ├── liteflow │ ├── __init__.py │ ├── model.py │ └── tcp │ │ └── dp.py └── setup.py ├── script ├── .gitignore ├── Makefile ├── active_standby_switch.sh ├── fetch_data_from_kernel.sh └── snapshot_generation.sh ├── test ├── Makefile ├── kernel │ ├── Makefile │ ├── aurora_test_model.c │ └── liteflow_sample_model.c ├── python │ └── train_simple_model.py └── test_dp_notification.c └── util └── tflite-visualize.py /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | python/build/ 3 | python/dist/ 4 | python/liteflow/liteflow.egg-info/ 5 | *.pyc 6 | .vscode/ 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/tensorflow"] 2 | path = third_party/tensorflow 3 | url = https://github.com/tensorflow/tensorflow.git 4 | [submodule "third_party/tflite"] 5 | path = third_party/tflite 6 | url = https://github.com/jackwish/tflite.git 7 | [submodule "third_party/flatbuffers"] 8 | path = third_party/flatbuffers 9 | url = https://github.com/google/flatbuffers.git 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | addons: 3 | apt: 4 | update: true 5 | packages: 6 | - build-essential 7 | - linux-headers-$(uname -r) 8 | - libnl-3-dev 9 | - libnl-genl-3-dev 10 | script: 11 | - make all 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | LF_KERNEL := lf_kernel 4 | LF_TCP_KERNERL := lf_tcp_kernel 5 | LF_NETFILTER_KERNERL := lf_netfilter_kernel 6 | 7 | LF_LIB := liblf 8 | 9 | SHARED_DIR := /usr/local/lib 10 | 11 | .PHONY: test lib 12 | 13 | all: 14 | make clean 15 | make module 16 | make lib 17 | make test 18 | 19 | module: 20 | -@mkdir build 21 | @cp -Rf datapath build 22 | @cp -Rf include build 23 | @cd build/datapath; make all; 24 | 25 | lib: 26 | -@mkdir build 27 | @cp -Rf lib build 28 | @cp -Rf include build 29 | @cd build/lib; make all; 30 | 31 | test: 32 | -@mkdir build 33 | @cp -Rf test build 34 | @cp -Rf include build 35 | @cd build/test; make all; 36 | 37 | module_install: 38 | @cd build/datapath; sudo insmod $(LF_KERNEL).ko 39 | 40 | module_remove: 41 | sudo rmmod $(LF_KERNEL) 42 | 43 | lib_install: 44 | make lib 45 | sudo cp build/lib/${LF_LIB}.so ${SHARED_DIR} 46 | sudo ldconfig 47 | 48 | lib_remove: 49 | sudo rm -rf ${SHARED_DIR}/${LF_LIB}.so 50 | sudo ldconfig 51 | 52 | tcp_kernel_install: 53 | @cd build/datapath; sudo insmod $(LF_TCP_KERNERL).ko 54 | sudo sysctl net.ipv4.tcp_congestion_control=lf_tcp_kernel 55 | 56 | tcp_kernel_remove: 57 | sudo sysctl net.ipv4.tcp_congestion_control=cubic 58 | sudo rmmod $(LF_TCP_KERNERL) 59 | 60 | netfilter_kernel_install: 61 | @cd build/datapath; sudo insmod $(LF_NETFILTER_KERNERL).ko 62 | 63 | netfilter_kernel_remove: 64 | sudo rmmod $(LF_NETFILTER_KERNERL) 65 | 66 | clean: 67 | -@rm -rf build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | LiteFlow Logo 4 | 5 |

A Hybrid Framework to Build High-performance Adaptive Neural Networks for Kernel Datapath

6 |
7 | 8 | [![Build Status](https://travis-ci.org/snowzjx/liteflow.svg?branch=master)](https://travis-ci.org/liteflow/liteflow) 9 | 10 | Please refer to our [SIGCOMM '22 paper](https://dl.acm.org/doi/10.1145/3544216.3544229) for details. 11 | 12 | **ATTENTION:** This project is for research purposes and please do not use it in a production environment. **Some of the code is not opensourced due to compatibility issues now and we are working on it to release them soon.** However, you can still experience some of the key components mentioned in our paper. 13 | 14 | **Tested Kernel Version:** 4.15.0-173-generic 15 | 16 | ## Getting Started 17 | 18 | ### Installation 19 | 20 | 1. Clone the repo 21 | 22 | ```sh 23 | git clone https://github.com/snowzjx/liteflow.git 24 | ``` 25 | 26 | 1. Change the directory to LiteFlow 27 | 28 | ```sh 29 | cd liteflow 30 | ``` 31 | 32 | 1. Install required packages 33 | 34 | ```sh 35 | apt install -y build-essential libnl-3-dev libnl-genl-3-dev pkg-config linux-headers-generic python3 python3-pip 36 | ``` 37 | 38 | ```sh 39 | pip3 install -U pip 40 | pip3 install -r bin/requirements.txt 41 | ``` 42 | 43 | 1. Compile 44 | 45 | ```sh 46 | make all 47 | ``` 48 | 49 | 1. Install LiteFlow kernel module 50 | 51 | ```sh 52 | make module_install 53 | ``` 54 | 55 | You can check if the LiteFlow kernel module is successfully installed by executing ```lsmod``` to see if *lf_kernel* is there. 56 | 57 | 1. Install TCP Congestion Control module 58 | 59 | ```sh 60 | make tcp_kernel_install 61 | ``` 62 | 63 | You can check if the LiteFlow TCP Congestion Control is successfully installed by executing ```lsmod``` to see if *lf_tcp_kernel* is there. 64 | 65 | You can also check if the TCP Congestion Control is successfully activated by executing ```sysctl net.ipv4.tcp_congestion_control``` to see whether is value is *lf_tcp_kernel* 66 | 67 | ### Examples 68 | 69 | #### Snapshot Generation 70 | 71 | ```sh 72 | cd script 73 | ./snapshot_generation.sh 74 | ``` 75 | 76 | This command tries to generate 3 Aurora snapshots from the model in ```liteflow/data/```. You can verify if the command is successful by checking if *lf_model_1.ko*, *lf_model_2.ko*, and *lf_model_3.ko* are generated. You can also check the generated source code by looking into *lf_model_1.c*, *lf_model_2.c*, and *lf_model_3.ko* files. Note here, we generate 3 identical snapshots just for demo purposes. 77 | 78 | #### Snapshot Update 79 | 80 | ```sh 81 | cd script 82 | ./active_standby_switch.sh 83 | ``` 84 | 85 | You expect a *dmesg* output showing that LiteFlow uses different neural networks to serve different flows. 86 | 87 | ```sh 88 | [ 2480.330440] Current slot 0 is registered with model: -1 89 | [ 2480.330440] Current slot 1 is registered with model: 1 90 | [ 2480.330440] Current active slot is: 1 91 | ... 92 | [ 2482.826293] Using model with uuid: 1 for inference... 93 | ... 94 | [ 2483.578519] Current slot 0 is registered with model: 2 95 | [ 2483.578519] Current slot 1 is registered with model: 1 96 | [ 2483.578519] Current active slot is: 0 97 | ... 98 | [ 2483.823231] Using model with uuid: 2 for inference... 99 | ... 100 | [ 2484.844727] Current slot 0 is registered with model: 2 101 | [ 2484.844727] Current slot 1 is registered with model: 3 102 | [ 2484.844727] Current active slot is: 1 103 | ... 104 | [ 2485.099799] Using model with uuid: 3 for inference... 105 | ... 106 | ``` 107 | 108 | #### Batched Training Data Delivery 109 | 110 | ```sh 111 | cd script 112 | ./fetch_data_from_kernel.sh 113 | ``` 114 | 115 | The screen should print all training data. *Note: different kernel settings may cause different outputs, please change the code accordingly.* 116 | 117 | #### LiteFlow Userspace Demo 118 | 119 | ```sh 120 | cd bin 121 | python3 lf.py 122 | ``` 123 | 124 | You expect to see a demo output of how LiteFlow generates a snapshot, evaluates a new neural network with the existing snapshot and updates the snapshot. *Note: for compatibility issues, there are lots of places left blank in the userspace program of LiteFlow, users can fill in the blanks with their own needs...* 125 | 126 | ## Citation 127 | 128 | ```bibtex 129 | @inproceedings{liteflow, 130 | author = {Junxue Zhang and Chaoliang Zeng and Hong Zhang and Shuihai Hu and Kai Chen}, 131 | title = {LiteFlow: Towards High-Performance Adaptive Neural Networks for Kernel Datapath}, 132 | year = {2022}, 133 | booktitle = {Proceedings of the ACM SIGCOMM 2022 Conference}, 134 | } 135 | ``` 136 | -------------------------------------------------------------------------------- /bin/layer.py: -------------------------------------------------------------------------------- 1 | from math import frexp 2 | import template 3 | 4 | # Keep four digits after decimal point 5 | KERNEL_PRECISION = 4 6 | 7 | class Layer: 8 | op = None 9 | input_size = 0 10 | output_size = 0 11 | 12 | def __init__(self, op): 13 | self.op = op 14 | 15 | def generate_struct_code(self, prefix): 16 | raise NotImplementedError 17 | 18 | def generate_comp_code(self, prefix, test_mode): 19 | raise NotImplementedError 20 | 21 | def get_comp_func_name(self, prefix): 22 | raise NotImplementedError 23 | 24 | class FCLayer(Layer): 25 | weights = None 26 | bias = None 27 | multiplier = 0 28 | input_offset = 0 29 | weight_offset = 0 30 | output_offset = 0 31 | 32 | def __init__(self, op, input_tensor, weight_tensor, bias_tensor, output_tensor, 33 | input_buffer, weight_buffer, bias_buffer, output_buffer): 34 | super().__init__(op) 35 | 36 | assert(input_tensor.Shape(0) == 1) 37 | assert(output_tensor.Shape(0) == 1) 38 | assert(input_tensor.Shape(1) == weight_tensor.Shape(1)) 39 | assert(weight_tensor.Shape(0) == bias_tensor.Shape(0)) 40 | assert(output_tensor.Shape(1) == bias_tensor.Shape(0)) 41 | assert(input_buffer == None) 42 | assert(output_buffer == None) 43 | 44 | self.input_size = input_tensor.Shape(1) 45 | self.output_size = output_tensor.Shape(1) 46 | 47 | self.weights = weight_buffer 48 | self.bias = bias_buffer 49 | 50 | self.weights.shape = weight_tensor.ShapeAsNumpy() 51 | 52 | self.input_offset = -input_tensor.Quantization().ZeroPoint(0) 53 | self.weight_offset = -weight_tensor.Quantization().ZeroPoint(0) 54 | self.output_offset = output_tensor.Quantization().ZeroPoint(0) 55 | 56 | input_product_scale = input_tensor.Quantization().Scale(0) * weight_tensor.Quantization().Scale(0) 57 | real_scale = input_product_scale / output_tensor.Quantization().Scale(0) 58 | 59 | self.mantissa_numerator, self.mantissa_denominator, self.exponent = get_quan_multiplier(real_scale) 60 | 61 | def generate_struct_code(self, prefix): 62 | TEMPLATE_FILE = "fc_layer_struct.c" 63 | _template = template.get_template(TEMPLATE_FILE) 64 | code = _template.render(prefix = prefix, 65 | uuid = prefix, 66 | input_size = self.input_size, 67 | output_size = self.output_size) 68 | return code 69 | 70 | def generate_comp_code(self, prefix, test_mode): 71 | TEMPLATE_FILE = "fc_layer_comp.c" 72 | _template = template.get_template(TEMPLATE_FILE) 73 | code = _template.render(prefix=prefix, 74 | input_size = self.input_size, 75 | output_size = self.output_size, 76 | weights = self.weights, 77 | bias = self.bias, 78 | input_offset = self.input_offset, 79 | weight_offset = self.weight_offset, 80 | output_offset = self.output_offset, 81 | mantissa_numerator = self.mantissa_numerator, 82 | mantissa_denominator = self.mantissa_denominator, 83 | exponent = self.exponent, 84 | test_mode = test_mode) 85 | return code 86 | 87 | def get_comp_func_name(self, prefix): 88 | # TODO: make it more flexible 89 | return "fc_{}_comp".format(prefix) 90 | 91 | class TanhLayer(Layer): 92 | 93 | input_offset = 0 94 | output_offset = 0 95 | input_scale = 0 96 | output_scale = 0 97 | 98 | def __init__(self, op, input_tensor, output_tensor, 99 | input_buffer, output_buffer): 100 | super().__init__(op) 101 | 102 | assert(input_tensor.Shape(0) == 1) 103 | assert(output_tensor.Shape(0) == 1) 104 | assert(input_tensor.Shape(1) == output_tensor.Shape(1)) 105 | 106 | assert(input_buffer == None) 107 | assert(output_buffer == None) 108 | 109 | self.input_size = input_tensor.Shape(1) 110 | self.output_size = output_tensor.Shape(1) 111 | 112 | self.input_scale = input_tensor.Quantization().Scale(0) 113 | self.output_scale = output_tensor.Quantization().Scale(0) 114 | 115 | # TODO Check 116 | self.input_offset = -input_tensor.Quantization().ZeroPoint(0) 117 | self.output_offset = output_tensor.Quantization().ZeroPoint(0) 118 | 119 | 120 | def generate_struct_code(self, prefix): 121 | TEMPLATE_FILE = "tanh_layer_struct.c" 122 | _template = template.get_template(TEMPLATE_FILE) 123 | code = _template.render(prefix = prefix, 124 | uuid = prefix, 125 | input_size = self.input_size, 126 | output_size = self.output_size) 127 | return code 128 | 129 | def generate_comp_code(self, prefix, test_mode): 130 | TEMPLATE_FILE = "tanh_layer_comp.c" 131 | _template = template.get_template(TEMPLATE_FILE) 132 | input_scale_numerator, input_scale_denominator = fractionation(self.input_scale) 133 | output_scale_numerator, output_scale_denominator = fractionation(self.output_scale) 134 | code = _template.render(prefix=prefix, 135 | input_size = self.input_size, 136 | output_size = self.output_size, 137 | input_offset = self.input_offset, 138 | output_offset = self.output_offset, 139 | input_scale_numerator = input_scale_numerator, 140 | input_scale_denominator = input_scale_denominator, 141 | output_scale_numerator = output_scale_numerator, 142 | output_scale_denominator = output_scale_denominator, 143 | test_mode = test_mode) 144 | return code 145 | 146 | def get_comp_func_name(self, prefix): 147 | # TODO: make it more flexible 148 | return "tanh_{}_comp".format(prefix) 149 | 150 | class QuanLayer(Layer): 151 | 152 | q_min = 0 153 | q_max = 0 154 | zero_point = 0 155 | 156 | def __init__(self, op, input_tensor, output_tensor, 157 | input_buffer, output_buffer): 158 | 159 | super().__init__(op) 160 | 161 | assert(input_tensor.Shape(0) == 1) 162 | assert(output_tensor.Shape(0) == 1) 163 | assert(input_tensor.Shape(1) == output_tensor.Shape(1)) 164 | 165 | self.input_size = input_tensor.Shape(1) 166 | self.output_size = output_tensor.Shape(1) 167 | 168 | self.q_min = output_tensor.Quantization().Min(0) 169 | self.q_max = output_tensor.Quantization().Max(0) 170 | 171 | self.zero_point = output_tensor.Quantization().ZeroPoint(0) 172 | 173 | def generate_struct_code(self, prefix): 174 | TEMPLATE_FILE = "quan_layer_struct.c" 175 | _template = template.get_template(TEMPLATE_FILE) 176 | code = _template.render(prefix = prefix, 177 | uuid = prefix, 178 | input_size = self.input_size, 179 | output_size = self.output_size) 180 | return code 181 | 182 | def generate_comp_code(self, prefix, test_mode): 183 | TEMPLATE_FILE = "quan_layer_comp.c" 184 | _template = template.get_template(TEMPLATE_FILE) 185 | code = _template.render(prefix=prefix, 186 | input_size = self.input_size, 187 | output_size = self.output_size, 188 | q_min = self.q_min, 189 | q_max = self.q_max, 190 | zero_point = self.zero_point, 191 | test_mode = test_mode) 192 | return code 193 | 194 | def get_comp_func_name(self, prefix): 195 | # TODO: make it more flexible 196 | return "quan_{}_comp".format(prefix) 197 | 198 | class DeQuanLayer(Layer): 199 | 200 | zero_point = 0 201 | scale = 0 202 | 203 | def __init__(self, op, input_tensor, output_tensor, 204 | input_buffer, output_buffer): 205 | 206 | super().__init__(op) 207 | 208 | assert(input_tensor.Shape(0) == 1) 209 | assert(output_tensor.Shape(0) == 1) 210 | assert(input_tensor.Shape(1) == output_tensor.Shape(1)) 211 | 212 | self.input_size = input_tensor.Shape(1) 213 | self.output_size = output_tensor.Shape(1) 214 | 215 | self.zero_point = input_tensor.Quantization().ZeroPoint(0) 216 | self.scale = input_tensor.Quantization().Scale(0) 217 | 218 | def generate_struct_code(self, prefix): 219 | TEMPLATE_FILE = "dequan_layer_struct.c" 220 | _template = template.get_template(TEMPLATE_FILE) 221 | code = _template.render(prefix = prefix, 222 | uuid = prefix, 223 | input_size = self.input_size, 224 | output_size = self.output_size) 225 | return code 226 | 227 | def generate_comp_code(self, prefix, test_mode): 228 | TEMPLATE_FILE = "dequan_layer_comp.c" 229 | _template = template.get_template(TEMPLATE_FILE) 230 | scale_numerator, scale_denominator, scale_exponent = get_quan_multiplier(self.scale) 231 | code = _template.render(prefix=prefix, 232 | input_size = self.input_size, 233 | output_size = self.output_size, 234 | zero_point = self.zero_point, 235 | scale_numerator = scale_numerator, 236 | scale_denominator = scale_denominator, 237 | scale_exponent = scale_exponent, 238 | scale = self.scale, 239 | test_mode = test_mode) 240 | return code 241 | 242 | def get_comp_func_name(self, prefix): 243 | # TODO: make it more flexible 244 | return "dequan_{}_comp".format(prefix) 245 | 246 | class ConcatenationLayer(Layer): 247 | # TODO 248 | pass 249 | 250 | class SplitLayer(Layer): 251 | # TODO 252 | pass 253 | 254 | def fractionation(float_num): 255 | numerator = float_num 256 | denominator = 1 257 | while numerator < 1 and denominator < 2**32: 258 | numerator *= 10**KERNEL_PRECISION 259 | denominator *= 10**KERNEL_PRECISION 260 | return int(numerator), int(denominator) 261 | 262 | def get_quan_multiplier(multiplier): 263 | # Return tuple (numerator, denominator, exponent) 264 | # 'numerator' and 'denominator' form the mantissa, where 'mantissa = numerator / denominator' 265 | # 'multiplier = mantissa * 2**exponent' 266 | 267 | m, e = frexp(multiplier) 268 | while abs(e) > 32: 269 | if e > 0: 270 | e -= 1 271 | m *= 2 272 | elif m > 2: 273 | e += 1 274 | m /= 2 275 | else: 276 | return 0, 1, 0 277 | 278 | numerator, denominator = fractionation(m) 279 | return numerator, denominator, int(e) 280 | -------------------------------------------------------------------------------- /bin/lf.py: -------------------------------------------------------------------------------- 1 | import schedule 2 | import time 3 | import subprocess 4 | from ctypes import * 5 | import tensorflow as tf 6 | import numpy as np 7 | 8 | from lf_quant import lf_quant 9 | from lf_generate_snapshot import lf_generate_snapshot 10 | 11 | import template 12 | 13 | kernel_model_quant_path = "../build/" 14 | represetative_dataset = "../data/aurora/representative_dataset.npy" 15 | bash_command_for_compile = "cd ../build; make" 16 | fidelity_loss_threshold = 0 17 | 18 | appid_for_cc = 1 19 | uuid = 0 20 | current_installed_model = "" 21 | 22 | def _quant_model_output_path(uuid): 23 | return "../build/aurora_int_quan_model_{}.tflite".format(uuid) 24 | 25 | def _bash_command_for_install_nn(uuid): 26 | return "cd ../build; insmod lf_model_{}.ko".format(uuid) 27 | 28 | def _tf_inference(path): 29 | predictions = [] 30 | _dataset = np.load(represetative_dataset).astype(np.float32) 31 | dataset = tf.data.Dataset.from_tensor_slices(_dataset).batch(1) 32 | 33 | interpreter = tf.lite.Interpreter(model_path=path) 34 | interpreter.allocate_tensors() 35 | input_index = interpreter.get_input_details()[0]["index"] 36 | output_index = interpreter.get_output_details()[0]["index"] 37 | 38 | for _data in _dataset: 39 | test_data = np.expand_dims(_data, axis=0).astype(np.float32) 40 | 41 | interpreter.set_tensor(input_index, test_data) 42 | interpreter.invoke() 43 | prediction = interpreter.get_tensor(output_index) 44 | predictions.append(prediction) 45 | return predictions 46 | 47 | def _cal_fidelity_loss(prediction1, prediction2): 48 | deltas = np.array(prediction1) - np.array(prediction2) 49 | agg_delta = 0 50 | for _delta in deltas: 51 | agg_delta += abs(_delta) 52 | return agg_delta/len(prediction1) 53 | 54 | def _quant_nn_from_saved_model(path): 55 | quant_model_output_path_real = _quant_model_output_path(uuid) 56 | lf_quant(path, represetative_dataset, quant_model_output_path_real) 57 | lf_generate_snapshot(quant_model_output_path_real, uuid, appid_for_cc, kernel_model_quant_path, False) 58 | TEMPLATE_FILE = "Makefile.jinja" 59 | _template = template.get_template(TEMPLATE_FILE) 60 | code = _template.render(uuid=uuid) 61 | OUTPUT_FILE = f"{kernel_model_quant_path}Makefile" 62 | with open(OUTPUT_FILE, "w") as output_file: 63 | output_file.write(code) 64 | return quant_model_output_path_real 65 | 66 | def _install_nn(quant_model_output_path_real): 67 | subprocess.run(bash_command_for_compile, shell=True) 68 | subprocess.run(_bash_command_for_install_nn(uuid), shell=True) 69 | 70 | def nn_freeze(): 71 | # The function should freeze the model and 72 | # return the path to the saved model 73 | 74 | #Returns: 75 | # path:The path to the saved model 76 | 77 | return "../data/aurora/" 78 | 79 | def nn_evaluate(): 80 | # The function is used to return the stability value 81 | # of a given model and it's output when feeding the 82 | # represetative dataset 83 | 84 | #Returns: 85 | # quant_model_path: The path of the quantized model 86 | # stability: The stability of the model 87 | # fidelity loss: The fidelity loss of the model 88 | 89 | # Here we use a emulated way to obtain a series of 90 | # Aurora model to elimate the randomness during online 91 | # adaptation 92 | 93 | new_freezed_nn_path = nn_freeze() 94 | quant_model_output_path_real=_quant_nn_from_saved_model(new_freezed_nn_path) 95 | prediction1 = _tf_inference(quant_model_output_path_real) 96 | prediction2 = _tf_inference(current_installed_model) 97 | fedility_loss = _cal_fidelity_loss(prediction1, prediction2) 98 | 99 | return quant_model_output_path_real, True, fedility_loss 100 | 101 | def nn_adapt(): 102 | # The function should be implemented to adapt 103 | # the model 104 | 105 | # The implementation of this function is out of 106 | # the scope of LiteFlow, thus we leave it empty 107 | 108 | return 109 | 110 | def _liteflow_step(): 111 | global uuid, current_installed_model 112 | nn_adapt() 113 | quant_model_output_path_real, stability, fidelity_loss = nn_evaluate() 114 | if stability == True and fidelity_loss >= fidelity_loss_threshold: 115 | _install_nn(quant_model_output_path_real) 116 | uuid = uuid + 1 117 | current_installed_model = quant_model_output_path_real 118 | 119 | def liteflow(): 120 | print("LiteFlow userspace start!") 121 | print("ATTENTION: It is for DEMO purpose...") 122 | 123 | global uuid, current_installed_model 124 | freezed_nn_path = nn_freeze(); 125 | quant_model_output_path_real=_quant_nn_from_saved_model(freezed_nn_path) 126 | _install_nn(quant_model_output_path_real) 127 | uuid = uuid + 1 128 | current_installed_model = quant_model_output_path_real 129 | 130 | schedule.every(1).minutes.do(_liteflow_step) 131 | 132 | while True: 133 | schedule.run_pending() 134 | time.sleep(1) 135 | return 136 | 137 | if __name__ == "__main__": 138 | liteflow() -------------------------------------------------------------------------------- /bin/lf_generate_snapshot.py: -------------------------------------------------------------------------------- 1 | import click 2 | import tflite 3 | import numpy as np 4 | 5 | from layer import * 6 | 7 | # TODO, Add output layer name to manually stop at some layer 8 | @click.command() 9 | @click.argument('path', type =click.Path(exists=True, file_okay=True, dir_okay=False)) 10 | @click.argument('uuid', type = click.INT) 11 | @click.argument('appid', type = click.INT) 12 | @click.argument('export', type=click.Path(exists=True, file_okay=False, dir_okay=True)) 13 | @click.option('-t', '--test', type = click.BOOL) 14 | def lf_generate_snapshot_cmd(path, uuid, appid, export, test): 15 | return lf_generate_snapshot(path, uuid, appid, export, test) 16 | 17 | def lf_generate_snapshot(path, uuid, appid, export, test): 18 | click.echo("Reading model from %s ..." % click.format_filename(path)) 19 | if not path.endswith('.tflite'): 20 | click.echo("The file should end with .tflite") 21 | return 22 | 23 | with open(path, 'rb') as f: 24 | buf = f.read() 25 | model = tflite.Model.GetRootAsModel(buf, 0) 26 | 27 | graph = model.Subgraphs(0) 28 | num_ops = graph.OperatorsLength() 29 | layer_list = [] 30 | extra_include_list = [] 31 | for op_index in range(0, num_ops): 32 | op = graph.Operators(op_index) 33 | op_code = model.OperatorCodes(op.OpcodeIndex()) 34 | if op_code.BuiltinCode() == tflite.BuiltinOperator.FULLY_CONNECTED: 35 | assert(op.InputsLength() == 3) 36 | input_tensor, input_buffer = get_tensor_and_buffer(model, graph, op.Inputs(0)) 37 | weight_tensor, weight_buffer = get_tensor_and_buffer(model, graph, op.Inputs(1)) 38 | bias_tensor, bias_buffer = get_tensor_and_buffer(model, graph, op.Inputs(2)) 39 | output_tensor, output_buffer = get_tensor_and_buffer(model, graph, op.Outputs(0)) 40 | 41 | layer = FCLayer(op_code, input_tensor, weight_tensor, bias_tensor, output_tensor, 42 | input_buffer, weight_buffer, bias_buffer, output_buffer) 43 | layer_list.append(layer) 44 | 45 | elif op_code.BuiltinCode() == tflite.BuiltinOperator.TANH: 46 | assert(op.InputsLength() == 1) 47 | assert(op.OutputsLength() == 1) 48 | input_tensor, input_buffer = get_tensor_and_buffer(model, graph, op.Inputs(0)) 49 | output_tensor, output_buffer = get_tensor_and_buffer(model, graph, op.Outputs(0)) 50 | 51 | layer = TanhLayer(op_code, input_tensor, output_tensor, input_buffer, output_buffer) 52 | layer_list.append(layer) 53 | 54 | if 'tanh_lookup_table.h' not in extra_include_list: 55 | extra_include_list.append('tanh_lookup_table.h') 56 | 57 | 58 | elif op_code.BuiltinCode() == tflite.BuiltinOperator.QUANTIZE: 59 | assert(op.InputsLength() == 1) 60 | assert(op.OutputsLength() == 1) 61 | input_tensor, input_buffer = get_tensor_and_buffer(model, graph, op.Inputs(0)) 62 | output_tensor, output_buffer = get_tensor_and_buffer(model, graph, op.Outputs(0)) 63 | 64 | layer = QuanLayer(op_code, input_tensor, output_tensor, input_buffer, output_buffer) 65 | layer_list.append(layer) 66 | 67 | elif op_code.BuiltinCode() == tflite.BuiltinOperator.DEQUANTIZE: 68 | assert(op.InputsLength() == 1) 69 | assert(op.OutputsLength() == 1) 70 | input_tensor, input_buffer = get_tensor_and_buffer(model, graph, op.Inputs(0)) 71 | output_tensor, output_buffer = get_tensor_and_buffer(model, graph, op.Outputs(0)) 72 | 73 | layer = DeQuanLayer(op_code, input_tensor, output_tensor, input_buffer, output_buffer) 74 | layer_list.append(layer) 75 | 76 | else: 77 | click.echo("Unsupported OP Code: %s ..." % op_code.BuiltinCode()) 78 | continue 79 | 80 | model_input_size = layer_list[0].input_size 81 | model_output_size = layer_list[-1].output_size 82 | 83 | TEMPLATE_FILE = "main.c" 84 | _template = template.get_template(TEMPLATE_FILE) 85 | code = _template.render(model_uuid = uuid, 86 | app_id = appid, 87 | layer_list = layer_list, 88 | input_size = model_input_size, 89 | output_size = model_output_size, 90 | extra_include_list = extra_include_list, 91 | test_mode = test) 92 | 93 | OUTPUT_FILE = f"{export}/lf_model_{uuid}.c" 94 | with open(OUTPUT_FILE, "w") as output_file: 95 | output_file.write(code) 96 | 97 | def get_tensor_and_buffer(model, graph, input): 98 | tensor = graph.Tensors(input) 99 | tensor_type = tensor.Type() 100 | raw_buffer = model.Buffers(tensor.Buffer()).DataAsNumpy() 101 | 102 | if tensor_type == tflite.TensorType.FLOAT32: 103 | viewer = ' 0 %} << {{ scale_exponent | int }}{% else %} >> -({{ scale_exponent | int }}){% endif %}); 7 | {% endfor %} 8 | } 9 | -------------------------------------------------------------------------------- /bin/template/dequan_layer_struct.c: -------------------------------------------------------------------------------- 1 | struct model_layer layer_{{ prefix }} __read_mostly = { 2 | .uuid = {{ uuid }}, 3 | .input_size = {{ input_size }}, 4 | .output_size = {{ output_size }}, 5 | .comp_func = dequan_{{ prefix }}_comp, 6 | }; -------------------------------------------------------------------------------- /bin/template/fc_layer_comp.c: -------------------------------------------------------------------------------- 1 | static void fc_{{ prefix }}_comp (s64 *input, s64 *output) 2 | { 3 | {% for i in range(0, output_size) %} 4 | output[{{ i }}] = 5 | {%- for j in range(0, input_size) -%} 6 | (input[{{ j }}] + {{ input_offset }}) * ({{ weights[i][j] }} + {{ weight_offset }}) 7 | {%- if not loop.last %} + {% endif -%} 8 | {%- endfor %} + ({{ bias[i] }}); 9 | output[{{ i }}] = ((output[{{ i }}] * {{ mantissa_numerator | int }} / {{ mantissa_denominator | int }}) {% if exponent > 0 %} << {{ exponent | int }}{% else %} >> -({{ exponent | int }}){% endif %}) + {{ output_offset }}; 10 | {% if test_mode %} 11 | printk(KERN_INFO "Output_fc_{{ prefix }}_%d: %lld", {{ i }}, output[{{ i }}]); 12 | {% endif %} 13 | {% endfor %} 14 | } -------------------------------------------------------------------------------- /bin/template/fc_layer_struct.c: -------------------------------------------------------------------------------- 1 | struct model_layer layer_{{ prefix }} __read_mostly = { 2 | .uuid = {{ uuid }}, 3 | .input_size = {{ input_size }}, 4 | .output_size = {{ output_size }}, 5 | .comp_func = fc_{{ prefix }}_comp, 6 | }; -------------------------------------------------------------------------------- /bin/template/main.c: -------------------------------------------------------------------------------- 1 | /****************** main.c for model {{ model_uuid }} generated by nn-loader.py *******************/ 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "linux/liteflow.h" 8 | 9 | {% for header in extra_include_list %} 10 | #include "linux/{{ header }}" 11 | {% endfor %} 12 | 13 | {% for layer_id in range(0, layer_list|length) %} 14 | /************************************** Layer {{ layer_id }} **************************************/ 15 | {{ layer_list[layer_id].generate_comp_code(layer_id, test_mode) }} 16 | 17 | {{ layer_list[layer_id].generate_struct_code(layer_id) }} 18 | /************************************ End Layer {{ layer_id }} ************************************/ 19 | {% endfor %} 20 | 21 | /************************************** Model {{ model_id }} **************************************/ 22 | 23 | struct model_container model_{{ model_uuid }} __read_mostly = { 24 | .uuid = {{ model_uuid }}, 25 | .input_size = {{ input_size }}, 26 | .output_size = {{ output_size }}, 27 | }; 28 | 29 | {% if test_mode %} 30 | /******************************************** Test Mode ********************************************/ 31 | struct app app = { 32 | .appid = {{ app_id }}, 33 | .input_size = {{ input_size }}, 34 | .output_size = {{ output_size }}, 35 | }; 36 | 37 | /**************************************** End Test Mode ********************************************/ 38 | {% endif %} 39 | 40 | static int 41 | __init liteflow_{{ model_uuid }}_model_init(void) 42 | { 43 | {% if test_mode %} 44 | s64 _input[{{ input_size }}]; 45 | s64 _output[{{ output_size}}]; 46 | int _output_pos; 47 | {% endif %} 48 | 49 | // Construct layers 50 | INIT_LIST_HEAD(&model_{{ model_uuid }}.layers); 51 | {% for layer_id in range(0, layer_list|length) %} 52 | {%- if loop.first -%} 53 | list_add(&layer_{{ layer_id }}.list, &model_{{ model_uuid }}.layers); 54 | {%- else %} 55 | list_add(&layer_{{ layer_id }}.list, &layer_{{ layer_id - 1}}.list); 56 | {%- endif -%} 57 | {% endfor %} 58 | 59 | {% if test_mode %} 60 | // Test mode = on 61 | lf_register_app(&app); 62 | {% endif %} 63 | 64 | lf_register_model({{ app_id }}, &model_{{ model_uuid }}); 65 | 66 | // TODO 67 | {% if test_mode %} 68 | {%- for input_pos in range(0, input_size) %} 69 | _input[{{ input_pos }}] = ... ; 70 | {%- endfor %} 71 | 72 | lf_query_model({{ app_id }}, _input, _output); 73 | 74 | for (_output_pos = 0; _output_pos < {{ output_size }}; ++_output_pos) { 75 | printk(KERN_INFO "Output_%d: %lld\n", _output_pos, _output[_output_pos]); 76 | } 77 | {% endif %} 78 | 79 | return 0; 80 | } 81 | 82 | static void 83 | __exit liteflow_{{ model_uuid }}_model_exit(void) 84 | { 85 | lf_unregister_model({{ app_id}}, {{ model_uuid }}); 86 | 87 | {% if test_mode %} 88 | // Test mode = on 89 | lf_unregister_app({{ app_id }}); 90 | {% endif %} 91 | } 92 | 93 | module_init(liteflow_{{ model_uuid }}_model_init); 94 | module_exit(liteflow_{{ model_uuid }}_model_exit); 95 | 96 | MODULE_DESCRIPTION("liteflow {{ model_uuid }} model"); 97 | MODULE_AUTHOR("liteflow"); 98 | MODULE_LICENSE("GPL v2"); 99 | 100 | /************************************ End Model {{ model_id }} ************************************/ 101 | -------------------------------------------------------------------------------- /bin/template/quan_layer_comp.c: -------------------------------------------------------------------------------- 1 | static void quan_{{ prefix }}_comp (s64 *input, s64 *output) 2 | { 3 | // Q_min: {{ q_min }} 4 | // Q_max: {{ q_max }} 5 | {% for i in range(0, input_size) %} 6 | output[{{ i }}] = (input[{{ i }}] - {{ q_min | int }}) * 255 / {{ (q_max - q_min) | int}} + {{ zero_point }}; 7 | {% if test_mode %} 8 | printk(KERN_INFO "Output_quan_{{ prefix }}_%d: %lld", {{ i }}, output[{{ i }}]); 9 | {% endif %} 10 | {% endfor %} 11 | } -------------------------------------------------------------------------------- /bin/template/quan_layer_struct.c: -------------------------------------------------------------------------------- 1 | struct model_layer layer_{{ prefix }} __read_mostly = { 2 | .uuid = {{ uuid }}, 3 | .input_size = {{ input_size }}, 4 | .output_size = {{ output_size }}, 5 | .comp_func = quan_{{ prefix }}_comp, 6 | }; -------------------------------------------------------------------------------- /bin/template/tanh_layer_comp.c: -------------------------------------------------------------------------------- 1 | static void tanh_{{ prefix }}_comp (s64 *input, s64 *output) 2 | { 3 | // Input Scale: {{ input_scale_numerator }} / {{ input_scale_denominator }} 4 | // Output Scale: {{ output_scale_numerator }} / {{ output_scale_denominator }} 5 | // Input Offset: {{ input_offset }} 6 | // Output Offset: {{ output_offset }} 7 | {% for i in range(0, input_size) %} 8 | output[{{ i }}] = lf_tanh((input[{{ i }}] + {{ input_offset}}) * TANH_X_SCALE_UP * {{ input_scale_numerator }} / {{ input_scale_denominator }}) * {{ output_scale_denominator }} / {{ output_scale_numerator }} / TANH_Y_SCALE_DOWN; 9 | {% if test_mode %} 10 | printk(KERN_INFO "Output_tanh_{{ prefix }}_%d: %lld", {{ i }}, output[{{ i }}]); 11 | {% endif %} 12 | {% endfor %} 13 | } -------------------------------------------------------------------------------- /bin/template/tanh_layer_struct.c: -------------------------------------------------------------------------------- 1 | struct model_layer layer_{{ prefix }} __read_mostly = { 2 | .uuid = {{ uuid }}, 3 | .input_size = {{ input_size }}, 4 | .output_size = {{ output_size }}, 5 | .comp_func = tanh_{{ prefix }}_comp, 6 | }; -------------------------------------------------------------------------------- /bin/template/tanh_lookup_table.h: -------------------------------------------------------------------------------- 1 | #ifndef TANH_LOOPUP_TABLE 2 | #define TANH_LOOPUP_TABLE 3 | 4 | #define TAHN_P_RANGE {{ tanh_p_range }} 5 | #define TANH_X_SCALE_UP {{ tanh_x_scale_up }} 6 | #define TANH_Y_SCALE_DOWN {{ tanh_y_scale_down }} 7 | 8 | const static s32 tanh_look_up_table[TAHN_P_RANGE] = { 9 | {% for value in tanh_table -%} 10 | [{{ loop.index - 1 }}] = {{ value | int}}, 11 | {% endfor -%} 12 | }; 13 | 14 | static inline s32 lf_tanh (s32 x_100) 15 | { 16 | if (x_100 == 0) { 17 | return 0; 18 | } 19 | else if (x_100 < 0) 20 | { 21 | return -lf_tanh(-x_100); 22 | } 23 | else if (x_100 >= TAHN_P_RANGE) { 24 | return 1 * TANH_Y_SCALE_DOWN; 25 | } 26 | else { 27 | return tanh_look_up_table[x_100]; 28 | } 29 | } 30 | 31 | #endif -------------------------------------------------------------------------------- /data/aurora/aurora_int_quan_model.tflite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzjx/liteflow/504aae114850d64af9954f640480cf86a7cd176b/data/aurora/aurora_int_quan_model.tflite -------------------------------------------------------------------------------- /data/aurora/representative_dataset.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzjx/liteflow/504aae114850d64af9954f640480cf86a7cd176b/data/aurora/representative_dataset.npy -------------------------------------------------------------------------------- /data/aurora/variables/variables.data-00000-of-00001: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzjx/liteflow/504aae114850d64af9954f640480cf86a7cd176b/data/aurora/variables/variables.data-00000-of-00001 -------------------------------------------------------------------------------- /data/aurora/variables/variables.index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzjx/liteflow/504aae114850d64af9954f640480cf86a7cd176b/data/aurora/variables/variables.index -------------------------------------------------------------------------------- /data/converted_simple_model.tflite: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzjx/liteflow/504aae114850d64af9954f640480cf86a7cd176b/data/converted_simple_model.tflite -------------------------------------------------------------------------------- /datapath/Makefile: -------------------------------------------------------------------------------- 1 | EXTRA_CFLAGS := -I$(src)/../include 2 | 3 | obj-m += lf_kernel.o 4 | lf_kernel-objs := liteflow.o liteflow_model.o liteflow_nl.o 5 | 6 | obj-m += lf_tcp_kernel.o 7 | lf_tcp_kernel-objs := liteflow_tcp.o 8 | 9 | obj-m += lf_netfilter_kernel.o 10 | lf_netfilter_kernel-objs := liteflow_netfilter.o 11 | 12 | all: 13 | make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules 14 | 15 | clean: 16 | make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean 17 | -------------------------------------------------------------------------------- /datapath/liteflow.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "linux/liteflow.h" 7 | #include "liteflow_nl.h" 8 | #include "liteflow_model.h" 9 | 10 | #define MAX_APP 32 11 | #define APP_ID_UNUSE 0 12 | 13 | #define REPORT_BATCH_SIZE 100 14 | 15 | static DEFINE_RWLOCK(lf_lock); 16 | 17 | struct __app { 18 | u8 appid; 19 | u32 input_size; 20 | u32 output_size; 21 | struct model_container *model_slot_0; 22 | struct model_container *model_slot_1; 23 | u8 active_model; // 0 or 1 24 | s64 *report_data; 25 | s64 *report_data_curr; 26 | u32 report_size; 27 | struct hlist_node node; 28 | // other metrics information 29 | }; 30 | 31 | static struct __app apps[MAX_APP + 1]; 32 | 33 | int lf_register_app(struct app* app) 34 | { 35 | u8 appid; 36 | 37 | appid = app->appid; 38 | if (appid > MAX_APP) { 39 | printk(KERN_ERR "Unsupported appid: %u.\n", appid); 40 | return LF_ERROR; 41 | } 42 | 43 | write_lock(&lf_lock); 44 | if (apps[appid].appid != APP_ID_UNUSE) { 45 | printk(KERN_ERR "Cannot re-register app with appid: %u.\n", appid); 46 | goto error; 47 | } 48 | 49 | printk(KERN_INFO "Registering app with appid: %u...\n", app->appid); 50 | apps[appid].appid = appid; 51 | apps[appid].input_size = app->input_size; 52 | apps[appid].output_size = app->output_size; 53 | apps[appid].model_slot_0 = NULL; 54 | apps[appid].model_slot_1 = NULL; 55 | apps[appid].active_model = 0; 56 | apps[appid].report_data = kmalloc(sizeof(s64) * (app->input_size + app->output_size) * REPORT_BATCH_SIZE, GFP_KERNEL); 57 | apps[appid].report_data_curr = apps[appid].report_data; 58 | apps[appid].report_size = 0; 59 | write_unlock(&lf_lock); 60 | return LF_SUCCS; 61 | 62 | error: 63 | write_unlock(&lf_lock); 64 | return LF_ERROR; 65 | } 66 | EXPORT_SYMBOL(lf_register_app); 67 | 68 | int lf_unregister_app(u8 appid) 69 | { 70 | struct model_container *model_to_delete; 71 | 72 | if (appid > MAX_APP) { 73 | printk(KERN_ERR "Unsupported appid: %u.\n", appid); 74 | return LF_ERROR; 75 | } 76 | 77 | write_lock(&lf_lock); 78 | 79 | if (apps[appid].appid == APP_ID_UNUSE) { 80 | printk(KERN_ERR "Cannot deregister app with unknown appid: %u.\n", appid); 81 | goto error; 82 | } 83 | 84 | printk(KERN_INFO "Unregistering app with appid: %u...\n", appid); 85 | model_to_delete = apps[appid].model_slot_0; 86 | if (model_to_delete!= NULL) { 87 | destroy_model(model_to_delete); 88 | } 89 | 90 | model_to_delete = apps[appid].model_slot_1; 91 | if (model_to_delete!= NULL) { 92 | destroy_model(model_to_delete); 93 | } 94 | apps[appid].appid = APP_ID_UNUSE; 95 | kfree(apps[appid].report_data); 96 | 97 | write_unlock(&lf_lock); 98 | return LF_SUCCS; 99 | 100 | error: 101 | write_unlock(&lf_lock); 102 | return LF_ERROR; 103 | } 104 | EXPORT_SYMBOL(lf_unregister_app); 105 | 106 | int 107 | lf_register_model(u8 appid, struct model_container *model) 108 | { 109 | u8 ret; 110 | 111 | s8 info_model_uuid_0, info_model_uuid_1; 112 | 113 | // Install 114 | 115 | if (appid > MAX_APP) { 116 | printk(KERN_ERR "Unsupported appid: %u.\n", appid); 117 | return LF_ERROR; 118 | } 119 | 120 | if (apps[appid].appid == APP_ID_UNUSE) { 121 | printk(KERN_ERR "Need to register app before registering model.\n"); 122 | goto error; 123 | } 124 | 125 | if (apps[appid].input_size != model->input_size) { 126 | printk(KERN_ERR "Input size of app and model are not consistent.\n"); 127 | goto error; 128 | } 129 | 130 | if (apps[appid].output_size != model->output_size) { 131 | printk(KERN_ERR "Output size of app and model are not consistent.\n"); 132 | goto error; 133 | } 134 | 135 | printk(KERN_INFO "Registering model witu uuid: %u to app with appid: %u...\n", model->uuid, appid); 136 | ret = init_model(model); 137 | if (ret == LF_ERROR) { 138 | goto error; 139 | } 140 | if (apps[appid].active_model == 0) { 141 | if (apps[appid].model_slot_1 != NULL) { 142 | printk(KERN_INFO "Deleting model witu uuid: %u to app with appid: %u...\n", apps[appid].model_slot_1->uuid, appid); 143 | destroy_model(apps[appid].model_slot_1); 144 | } 145 | apps[appid].model_slot_1 = model; 146 | } else { 147 | if (apps[appid].model_slot_0 != NULL) { 148 | printk(KERN_INFO "Deleting model witu uuid: %u to app with appid: %u...\n", apps[appid].model_slot_0->uuid, appid); 149 | destroy_model(apps[appid].model_slot_0); 150 | } 151 | apps[appid].model_slot_0 = model; 152 | } 153 | 154 | // Lock 155 | 156 | write_lock(&lf_lock); 157 | if (apps[appid].active_model == 0) { 158 | apps[appid].active_model = 1; 159 | } else { 160 | apps[appid].active_model = 0; 161 | } 162 | write_unlock(&lf_lock); 163 | 164 | printk(KERN_INFO "Model witu uuid: %u is registered to app with appid: %u!\n", model->uuid, appid); 165 | 166 | if (apps[appid].model_slot_0 != NULL) { 167 | info_model_uuid_0 = apps[appid].model_slot_0->uuid; 168 | } else { 169 | info_model_uuid_0 = -1; 170 | } 171 | 172 | if (apps[appid].model_slot_1 != NULL) { 173 | info_model_uuid_1 = apps[appid].model_slot_1->uuid; 174 | } else { 175 | info_model_uuid_1 = -1; 176 | } 177 | 178 | printk(KERN_INFO "Current slot 0 is registered with model: %d\n", info_model_uuid_0); 179 | printk(KERN_INFO "Current slot 1 is registered with model: %d\n", info_model_uuid_1); 180 | printk(KERN_INFO "Current active slot is: %u\n", apps[appid].active_model); 181 | 182 | return LF_SUCCS; 183 | 184 | error: 185 | write_unlock(&lf_lock); 186 | return LF_ERROR; 187 | } 188 | EXPORT_SYMBOL(lf_register_model); 189 | 190 | int 191 | lf_unregister_model(u8 appid, u32 model_uuid) 192 | { 193 | struct model_container *model, *model_to_delete; 194 | u8 which_model; 195 | 196 | model_to_delete = NULL; 197 | 198 | if (appid > MAX_APP) { 199 | printk(KERN_ERR "Unsupported appid: %u.\n", appid); 200 | return LF_ERROR; 201 | } 202 | 203 | write_lock(&lf_lock); 204 | if (apps[appid].appid == APP_ID_UNUSE) { 205 | printk(KERN_ERR "Need to register app before unregistering model.\n"); 206 | goto error; 207 | } 208 | 209 | model = apps[appid].model_slot_0; 210 | if (model != NULL && model->uuid == model_uuid) { 211 | model_to_delete = model; 212 | which_model = 0; 213 | } 214 | 215 | model = apps[appid].model_slot_1; 216 | if (model != NULL && model->uuid == model_uuid) { 217 | model_to_delete = model; 218 | which_model = 1; 219 | } 220 | 221 | printk(KERN_INFO "Unregister model with uuid: %u with app with appid: %u...\n", model_uuid, appid); 222 | 223 | if (model_to_delete == NULL) { 224 | printk(KERN_INFO "The model has already been deleted...\n"); 225 | write_unlock(&lf_lock); 226 | 227 | return LF_SUCCS; 228 | } 229 | 230 | destroy_model(model_to_delete); 231 | if (which_model == 0) { 232 | apps[appid].model_slot_0 = NULL; 233 | } else { 234 | apps[appid].model_slot_1 = NULL; 235 | } 236 | write_unlock(&lf_lock); 237 | 238 | return LF_SUCCS; 239 | 240 | error: 241 | write_unlock(&lf_lock); 242 | return LF_ERROR; 243 | } 244 | EXPORT_SYMBOL(lf_unregister_model); 245 | 246 | 247 | int 248 | lf_query_model(u8 appid, s64 *input, s64 *output) { 249 | 250 | struct model_container *model_to_use; 251 | u32 length; 252 | int res; 253 | 254 | if (input == NULL || output == NULL) { 255 | printk(KERN_ERR "Input or output vector cannot be null.\n"); 256 | return LF_ERROR; 257 | } 258 | 259 | 260 | if (appid > MAX_APP) { 261 | printk(KERN_ERR "Unsupported appid: %u.\n", appid); 262 | return LF_ERROR; 263 | } 264 | 265 | read_lock(&lf_lock); 266 | if (apps[appid].appid == APP_ID_UNUSE) { 267 | printk(KERN_ERR "Need to register app before inference the model.\n"); 268 | write_unlock(&lf_lock); 269 | return LF_ERROR; 270 | } 271 | 272 | if (apps[appid].active_model == 0) { 273 | model_to_use = apps[appid].model_slot_0; 274 | } else { 275 | model_to_use = apps[appid].model_slot_1; 276 | } 277 | read_unlock(&lf_lock); 278 | 279 | if (model_to_use == NULL) { 280 | printk(KERN_ERR "No model for app with appid: %u.\n", appid); 281 | return LF_ERROR; 282 | } 283 | 284 | printk(KERN_INFO "Using model with uuid: %u for inference...\n", model_to_use->uuid); 285 | 286 | res = query_model(model_to_use, input, output); 287 | 288 | if (apps[appid].report_size < REPORT_BATCH_SIZE) { 289 | memcpy(apps[appid].report_data_curr, input, apps[appid].input_size * sizeof(s64)); 290 | apps[appid].report_data_curr += apps[appid].input_size; 291 | memcpy(apps[appid].report_data_curr, output, apps[appid].output_size * sizeof(s64)); 292 | apps[appid].report_data_curr += apps[appid].output_size; 293 | apps[appid].report_size ++; 294 | printk(KERN_INFO "Current report size: %u\n", apps[appid].report_size); 295 | } else { 296 | length = (apps[appid].input_size + apps[appid].output_size) * REPORT_BATCH_SIZE; 297 | report_data(apps[appid].report_data, length); 298 | apps[appid].report_size = 0; 299 | apps[appid].report_data_curr = apps[appid].report_data; 300 | } 301 | 302 | return res; 303 | } 304 | EXPORT_SYMBOL(lf_query_model); 305 | 306 | static int 307 | __init liteflow_module_init(void) 308 | { 309 | u8 appid; 310 | int ret; 311 | 312 | printk(KERN_INFO "liteflow init...\n"); 313 | 314 | for (appid = 1; appid <= MAX_APP; ++appid) { 315 | apps[appid].appid = APP_ID_UNUSE; 316 | apps[appid].model_slot_0 = NULL; 317 | apps[appid].model_slot_1 = NULL; 318 | apps[appid].active_model = 0; 319 | } 320 | 321 | ret = start_nl(); 322 | 323 | return ret; 324 | } 325 | 326 | static void 327 | __exit liteflow_module_exit(void) 328 | { 329 | stop_nl(); 330 | printk(KERN_INFO "liteflow exit...\n"); 331 | } 332 | 333 | module_init(liteflow_module_init); 334 | module_exit(liteflow_module_exit); 335 | 336 | MODULE_DESCRIPTION("liteflow"); 337 | MODULE_AUTHOR("Junxue ZHANG"); 338 | MODULE_LICENSE("GPL v2"); 339 | -------------------------------------------------------------------------------- /datapath/liteflow_model.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "linux/liteflow.h" 4 | 5 | #include "liteflow_model.h" 6 | 7 | static inline int check_validate(struct model_container *model) 8 | { 9 | struct model_layer *layer; 10 | 11 | u32 input_size = model->input_size; 12 | u32 output_size = model->output_size; 13 | u32 p_size = input_size; 14 | 15 | layer = list_first_entry_or_null(&model->layers, struct model_layer, list); 16 | // Check if layers are null 17 | if (layer == NULL) { 18 | printk(KERN_ERR "The layers are null.\n"); 19 | return LF_ERROR; 20 | } 21 | // Check if the input size of first layer equals the input size of model 22 | if (layer->input_size != input_size) { 23 | printk(KERN_ERR "The input size of first layer is not equal to the input size of the model.\n"); 24 | return LF_ERROR; 25 | } 26 | 27 | layer = list_last_entry(&model->layers, struct model_layer, list); 28 | if (layer->output_size != output_size) { 29 | printk(KERN_ERR "The output size of last layer is not equal to the output size of the model.\n"); 30 | return LF_ERROR; 31 | } 32 | 33 | list_for_each_entry(layer, &model->layers, list) { 34 | if (layer ->comp_func == NULL) { 35 | printk(KERN_ERR "The computation function of layer with uuid: %u is NULL.\n", layer->uuid); 36 | } 37 | if (layer->input_size != p_size) { 38 | printk(KERN_ERR "The input size of layer with uuid: %u is not equal to its previous layer.\n", layer->uuid); 39 | } 40 | p_size = layer->output_size; 41 | } 42 | return LF_SUCCS; 43 | } 44 | 45 | int init_data_storage(struct model_container *model) 46 | { 47 | struct model_layer *layer; 48 | u32 layer_input_size, layer_output_size; 49 | 50 | list_for_each_entry(layer, &model->layers, list) { 51 | layer_input_size = layer->input_size; 52 | layer_output_size = layer->output_size; 53 | 54 | layer->output = kmalloc(sizeof(s64) * layer_output_size, GFP_KERNEL); 55 | if (layer->output == NULL) { 56 | return LF_ERROR; 57 | } 58 | } 59 | return LF_SUCCS; 60 | } 61 | 62 | int init_model(struct model_container *model) 63 | { 64 | u32 ret; 65 | ret = check_validate(model); 66 | if (ret == LF_ERROR) { 67 | return LF_ERROR; 68 | } 69 | ret = init_data_storage(model); 70 | if (ret == LF_ERROR) { 71 | return LF_ERROR; 72 | } 73 | return LF_SUCCS; 74 | } 75 | 76 | void destroy_model(struct model_container * model) 77 | { 78 | struct model_layer *layer; 79 | 80 | list_for_each_entry(layer, &model->layers, list) { 81 | kfree(layer->input); 82 | kfree(layer->output); 83 | } 84 | } 85 | 86 | int query_model(struct model_container * model, s64 *input, s64 *output) 87 | { 88 | struct model_layer *layer, *last_layer; 89 | s64 *intermediate; 90 | 91 | intermediate = input; 92 | list_for_each_entry(layer, &model->layers, list) { 93 | layer->comp_func(intermediate, layer->output); 94 | intermediate = layer->output; 95 | } 96 | 97 | last_layer = list_last_entry(&model->layers, struct model_layer, list); 98 | memcpy(output, last_layer->output, last_layer->output_size * sizeof(s64)); 99 | 100 | return LF_SUCCS; 101 | } 102 | 103 | -------------------------------------------------------------------------------- /datapath/liteflow_model.h: -------------------------------------------------------------------------------- 1 | #ifndef LITEFLOW_MODEL_H 2 | #define LITEFLOW_MODEL_H 3 | 4 | struct model_container; 5 | 6 | extern int init_model(struct model_container *); 7 | 8 | extern void destroy_model(struct model_container *); 9 | 10 | extern int query_model(struct model_container *, s64 *input, s64 *output); 11 | 12 | #endif -------------------------------------------------------------------------------- /datapath/liteflow_netfilter.c: -------------------------------------------------------------------------------- 1 | // LiteFlow Netfilter Kernel will register to both LiteFlow kernek and Kernel IPV4 Netfilter 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "linux/liteflow.h" 11 | #include "liteflow_netfilter.h" 12 | 13 | static unsigned int 14 | hook_func_incoming(void *priv, 15 | struct sk_buff *skb, 16 | const struct nf_hook_state *state) 17 | { 18 | int ret; 19 | struct iphdr *ip_header; 20 | struct tcphdr *tcp_header; 21 | __be32 saddr, daddr; 22 | __be16 sport, dport; 23 | s64 nn_input[NUM_OF_INPUT_VALUE]; 24 | s64 nn_output[NUM_OF_OUTPUT_VALUE]; 25 | 26 | ip_header = ip_hdr(skb); 27 | 28 | if (ip_header -> protocol == IPPROTO_TCP) { 29 | tcp_header = tcp_hdr(skb); 30 | } else { 31 | return NF_ACCEPT; // Non TCP traffic will not be processed 32 | } 33 | 34 | saddr = ntohl(ip_header->saddr); 35 | daddr = ntohl(ip_header->daddr); 36 | sport = ntohs(tcp_header->source); 37 | dport = ntohs(tcp_header->dest); 38 | 39 | nn_input[INPUT_METRICS_POS_SRC_IP_A_B] = (saddr >> 16) & 0xffff;; 40 | nn_input[INPUT_METRICS_POS_SRC_IP_C_D] = (saddr >> 0) & 0xffff; 41 | 42 | nn_input[INPUT_METRICS_POS_DST_IP_A_B] = (daddr >> 16) & 0xffff; 43 | nn_input[INPUT_METRICS_POS_DST_IP_C_D] = (daddr >> 0) & 0xffff; 44 | 45 | nn_input[INPUT_METRICS_POS_SRC_PORT] = sport; 46 | nn_input[INPUT_METRICS_POS_DST_PORT] = dport; 47 | 48 | ret = lf_query_model(LF_NETFILTER_APP_ID, nn_input, nn_output); 49 | if (ret == LF_ERROR) { 50 | printk(KERN_ERR "Query NN model failed!\n"); 51 | } else { 52 | if (nn_output[OUTPUT_SHOULD_PASS] > nn_output[OUTPUT_SHOULD_DROP]) 53 | return NF_ACCEPT; 54 | else 55 | return NF_DROP; 56 | } 57 | 58 | return NF_ACCEPT; 59 | } 60 | 61 | static unsigned int 62 | hook_func_outcoming(void *priv, 63 | struct sk_buff *skb, 64 | const struct nf_hook_state *state) 65 | { 66 | return NF_ACCEPT; 67 | } 68 | 69 | static struct nf_hook_ops 70 | lf_netfilter_ops_in __read_mostly = { 71 | hook: hook_func_incoming, 72 | hooknum: NF_INET_PRE_ROUTING, 73 | pf: PF_INET, 74 | priority: NF_IP_PRI_FIRST 75 | }; 76 | 77 | static struct nf_hook_ops 78 | lf_netfilter_ops_out __read_mostly = { 79 | hook: hook_func_outcoming, 80 | hooknum: NF_INET_POST_ROUTING, 81 | pf: PF_INET, 82 | priority: NF_IP_PRI_FIRST 83 | }; 84 | 85 | struct app lf_netfilter_app = { 86 | .appid = LF_NETFILTER_APP_ID, 87 | .input_size = NUM_OF_INPUT_VALUE, 88 | .output_size = NUM_OF_OUTPUT_VALUE, 89 | }; 90 | 91 | static int 92 | __init liteflow_netfilter_kernel_init(void) 93 | { 94 | int ret; 95 | 96 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,13,0) 97 | nf_register_net_hook(&init_net, &lf_netfilter_ops_in); 98 | nf_register_net_hook(&init_net, &lf_netfilter_ops_out); 99 | #else 100 | nf_register_hook(&lf_netfilter_ops_in); 101 | nf_register_hook(&lf_netfilter_ops_out); 102 | #endif 103 | 104 | ret = lf_register_app(&lf_netfilter_app); 105 | if (ret != LF_SUCCS) { 106 | printk(KERN_ERR "Cannot register liteflow netfilter kernel with liteflow kernel!\n"); 107 | return ret; 108 | } 109 | 110 | return ret; 111 | } 112 | 113 | static void 114 | __exit liteflow_netfilter_kernel_exit(void) 115 | { 116 | #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,13,0) 117 | nf_unregister_net_hook(&init_net, &lf_netfilter_ops_out); 118 | nf_unregister_net_hook(&init_net, &lf_netfilter_ops_in); 119 | #else 120 | nf_unregister_hook(&lf_netfilter_ops_out); 121 | nf_unregister_hook(&lf_netfilter_ops_in); 122 | #endif 123 | 124 | lf_unregister_app(LF_NETFILTER_APP_ID); 125 | } 126 | 127 | module_init(liteflow_netfilter_kernel_init); 128 | module_exit(liteflow_netfilter_kernel_exit); 129 | 130 | MODULE_DESCRIPTION("liteflow netfilter kernel"); 131 | MODULE_AUTHOR("Junxue ZHANG"); 132 | MODULE_AUTHOR("Chaoliang ZENG"); 133 | MODULE_LICENSE("GPL v2"); 134 | -------------------------------------------------------------------------------- /datapath/liteflow_nl.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "linux/liteflow.h" 4 | #include "liteflow_nl.h" 5 | 6 | static struct genl_family lf_gnl_family; 7 | 8 | struct lf_nl_ops *default_ops = NULL; 9 | 10 | static struct genl_multicast_group lf_mcgrps[] = { 11 | [LF_NL_MC_DEFAULT] = { .name = LF_NL_MC_DEFAULT_NAME, }, 12 | }; 13 | 14 | static int lf_gnl_rx_msg(struct sk_buff* skb, struct genl_info* info) { 15 | return LF_SUCCS; 16 | } 17 | 18 | static const struct genl_ops lf_ops[] = { 19 | { 20 | .cmd = LF_NL_C_REPORT_DATA, 21 | .policy = lf_policy, 22 | .doit = lf_gnl_rx_msg, 23 | .dumpit = NULL, 24 | }, 25 | }; 26 | 27 | static struct genl_family lf_gnl_family = { 28 | .name = LF_NL_NAME, 29 | .version = LF_NL_VERSION, 30 | .netnsok = false, 31 | .maxattr = LF_NL_ATTR_MAX, 32 | .ops = lf_ops, 33 | .n_ops = ARRAY_SIZE(lf_ops), 34 | .mcgrps = lf_mcgrps, 35 | .n_mcgrps = ARRAY_SIZE(lf_mcgrps), 36 | .module = THIS_MODULE, 37 | }; 38 | 39 | int start_nl(void) { 40 | int ret; 41 | 42 | printk(KERN_INFO "Starting liteflow netlink subsystem...\n"); 43 | 44 | ret = genl_register_family(&lf_gnl_family); 45 | if (ret != 0) { 46 | printk(KERN_ERR "Cannot register liteflow generic netlink!\n"); 47 | return LF_ERROR; 48 | } 49 | 50 | return LF_SUCCS; 51 | } 52 | 53 | int stop_nl(void) { 54 | int ret; 55 | 56 | printk(KERN_INFO "Stopping liteflow netlink subsystem...\n"); 57 | 58 | default_ops = NULL; 59 | ret = genl_unregister_family(&lf_gnl_family); 60 | if (ret != 0) { 61 | printk(KERN_ERR "Cannot unregister liteflow generic netlink!\n"); 62 | return LF_ERROR; 63 | } 64 | 65 | return LF_SUCCS; 66 | } 67 | 68 | int report_data(s64 *data, u32 length) { 69 | struct sk_buff *skb; 70 | void *msg_head; 71 | int ret; 72 | 73 | skb = genlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC); 74 | 75 | if (skb == NULL) { 76 | printk (KERN_ERR "Cannot allocate skb for LiteFlow netlink ...\n"); 77 | return LF_ERROR; 78 | } 79 | 80 | msg_head = genlmsg_put(skb, 0, 0, &lf_gnl_family, GFP_ATOMIC, LF_NL_C_REPORT_DATA); 81 | if (msg_head == NULL) { 82 | printk (KERN_ERR "Cannot allocate msg_head for LiteFlow netlink ...\n"); 83 | return LF_ERROR; 84 | } 85 | 86 | ret = nla_put_u32(skb, LF_NL_ATTR_SIZE, length); 87 | if (ret != 0) { 88 | printk (KERN_ERR "Cannot put length for LiteFlow netlink, error code: %d ...\n", ret); 89 | return LF_ERROR; 90 | } 91 | 92 | ret = nla_put(skb, LF_NL_ATTR_DATA, length, data); 93 | if (ret != 0) { 94 | printk (KERN_ERR "Cannot put data for LiteFlow netlink, error code: %d ...\n", ret); 95 | return LF_ERROR; 96 | } 97 | 98 | genlmsg_end(skb, msg_head); 99 | 100 | genlmsg_multicast(&lf_gnl_family, skb, 0, LF_NL_MC_DEFAULT, GFP_ATOMIC); 101 | 102 | return LF_SUCCS; 103 | } 104 | -------------------------------------------------------------------------------- /datapath/liteflow_tcp.c: -------------------------------------------------------------------------------- 1 | // LiteFlow TCP Kernel will register to both LiteFlow kernek and Kernel TCP Congestion Control 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "linux/liteflow.h" 11 | #include "liteflow_tcp.h" 12 | 13 | #define S_TO_US (1000000) 14 | #define MAX_CWND (5000) 15 | #define MIN_CWND (10) 16 | #define MIN_RATE (1 << 20) 17 | // MAX_RATE ~ 2^31 18 | #define MAX_RATE (1 << 31) 19 | 20 | #if LINUX_VERSION_CODE <= KERNEL_VERSION(4,14,0) && LINUX_VERSION_CODE >= KERNEL_VERSION(4,13,0) 21 | #define COMPAT_MODE 22 | #define MAX_SKB_STORED (50) 23 | struct skb_info { 24 | u64 first_tx_mstamp; 25 | u32 interval_us; 26 | }; 27 | #endif 28 | 29 | struct lf_tcp_metric { 30 | s64 values[NUM_OF_INPUT_METRICS]; 31 | }; 32 | 33 | struct lf_tcp_internal { 34 | u64 src_ip; 35 | u64 dest_ip; 36 | u64 src_port; 37 | u64 dest_port; 38 | u64 last_snd_una; 39 | u32 current_pointer; 40 | s64 global_stats[NUM_OF_GLOBAL_STATS]; 41 | struct lf_tcp_metric* metrics; // ring buffer 42 | #ifdef COMPAT_MODE 43 | struct skb_info* skb_array; 44 | #endif 45 | }; 46 | 47 | static inline void lf_increase_pointer(struct lf_tcp_internal *lf_tcp) { 48 | u32 current_pointer; 49 | 50 | current_pointer = lf_tcp->current_pointer; 51 | lf_tcp->current_pointer = (++current_pointer) % HISTORY_LEN; 52 | } 53 | 54 | // Set rate towards connection 55 | static inline void lf_set_rate (struct sock *sk, u32 rate) { 56 | sk->sk_pacing_rate = rate; 57 | } 58 | 59 | static inline void lf_set_relative_rate(struct sock *sk, s32 delta) { 60 | struct tcp_sock *tp = tcp_sk(sk); 61 | u64 cur_rate = sk->sk_pacing_rate; 62 | 63 | tp->snd_cwnd = MAX_CWND; 64 | tp->snd_ssthresh = MAX_CWND; 65 | 66 | if (delta > 0) { 67 | sk->sk_pacing_rate = min((u32)MAX_RATE, 68 | (u32)(cur_rate + cur_rate * delta / OUTPUT_SCALE / 40)); 69 | } 70 | else { 71 | sk->sk_pacing_rate = max((u32)MIN_RATE, 72 | (u32)(cur_rate * OUTPUT_SCALE * 40 / (OUTPUT_SCALE * 40 - delta))); 73 | } 74 | } 75 | 76 | static inline void lf_set_relative_cwnd(struct tcp_sock* tp, s32 delta) { 77 | if (delta > 0) { 78 | tp->snd_cwnd = tp->snd_cwnd + tp->snd_cwnd * delta / OUTPUT_SCALE / 40; 79 | } 80 | else { 81 | tp->snd_cwnd = max((u32)MIN_CWND, 82 | (u32)(tp->snd_cwnd * OUTPUT_SCALE * 40 / (OUTPUT_SCALE * 40 - delta))); 83 | } 84 | } 85 | 86 | static int rate_sample_valid(const struct rate_sample *rs) { 87 | int ret = 0; 88 | if (rs->delivered <= 0) 89 | ret |= 1; 90 | if (rs->interval_us <= 0) 91 | ret |= 1 << 1; 92 | if (rs->rtt_us <= 0) 93 | ret |= 1 << 2; 94 | return ret; 95 | } 96 | 97 | static struct genl_multicast_group lf_tcp_mcgrps[] = { 98 | [LF_TCP_NL_MC_DEFAULT] = { .name = LF_TCP_NL_MC_DEFAULT_NAME, }, 99 | }; 100 | 101 | static struct genl_family lf_tcp_gnl_family = { 102 | .name = LF_TCP_NL_NAME, 103 | .version = LF_TCP_NL_VERSION, 104 | .netnsok = false, 105 | .maxattr = LF_TCP_NL_ATTR_MAX, 106 | .mcgrps = lf_tcp_mcgrps, 107 | .n_mcgrps = ARRAY_SIZE(lf_tcp_mcgrps), 108 | .module = THIS_MODULE, 109 | }; 110 | 111 | static inline int report_to_user(s64 *nn_input, u32 input_size) { 112 | struct sk_buff *skb; 113 | void *msg_head; 114 | int ret; 115 | 116 | skb = genlmsg_new(NLMSG_GOODSIZE, GFP_ATOMIC); 117 | 118 | if (skb == NULL) { 119 | printk (KERN_ERR "Cannot allocate skb for LiteFlow TCP netlink ...\n"); 120 | return LF_ERROR; 121 | } 122 | 123 | msg_head = genlmsg_put(skb, 0, 0, &lf_tcp_gnl_family, GFP_ATOMIC, LF_TCP_NL_C_REPORT); 124 | if (msg_head == NULL) { 125 | printk (KERN_ERR "Cannot allocate msg_head for LiteFlow TCP netlink ...\n"); 126 | return LF_ERROR; 127 | } 128 | 129 | ret = nla_put(skb, LF_TCP_NL_ATTR_NN_INPUT, input_size * sizeof(s64), nn_input); 130 | if (ret != 0) { 131 | printk (KERN_ERR "Cannot put data for LiteFlow TCP netlink, error code: %d ...\n", ret); 132 | return LF_ERROR; 133 | } 134 | 135 | genlmsg_end(skb, msg_head); 136 | 137 | genlmsg_multicast(&lf_tcp_gnl_family, skb, 0, LF_TCP_NL_MC_DEFAULT, GFP_ATOMIC); 138 | 139 | return LF_SUCCS; 140 | } 141 | 142 | static inline int load_metric(struct lf_tcp_internal *ca, struct tcp_sock *tp, const struct rate_sample *rs) { 143 | 144 | struct lf_tcp_metric* metric; 145 | u64 rin = 0, rout = 0; 146 | u64 ack_us = 0, snd_us = 0; 147 | #ifdef COMPAT_MODE 148 | int i = 0; 149 | #endif 150 | 151 | int measured_valid_rate = rate_sample_valid(rs); 152 | if (measured_valid_rate != 0) { 153 | return LF_ERROR; 154 | } 155 | 156 | if (rs->rtt_us > 0 157 | && (ca->global_stats[GLOBAL_STATS_POS_MIN_RTT_US] == -1 158 | || ca->global_stats[GLOBAL_STATS_POS_MIN_RTT_US] > rs->rtt_us)) 159 | { 160 | ca->global_stats[GLOBAL_STATS_POS_MIN_RTT_US] = rs->rtt_us; 161 | } 162 | 163 | metric = &(ca->metrics[ca->current_pointer]); 164 | 165 | metric->values[INPUT_METRICS_POS_SENT_LAT_INFLACTION] = 0; 166 | if (ca->global_stats[GLOBAL_STATS_POS_MIN_RTT_US] > 0) { 167 | metric->values[INPUT_METRICS_POS_LAT_RATIO] = INPUT_SCALE * rs->rtt_us; 168 | do_div(metric->values[INPUT_METRICS_POS_LAT_RATIO], ca->global_stats[GLOBAL_STATS_POS_MIN_RTT_US]); 169 | } 170 | else { 171 | metric->values[INPUT_METRICS_POS_LAT_RATIO] = INPUT_SCALE * 1; 172 | } 173 | 174 | 175 | #ifdef COMPAT_MODE 176 | ack_us = tcp_stamp_us_delta(tp->tcp_mstamp, rs->prior_mstamp); 177 | for (i = 0; i < MAX_SKB_STORED; ++i) { 178 | if (ca->skb_array[i].first_tx_mstamp == tp->first_tx_mstamp) { 179 | snd_us = ca->skb_array[i].interval_us; 180 | break; 181 | } 182 | } 183 | #endif 184 | 185 | if (ack_us != 0 && snd_us != 0) { 186 | rin = rout = (s64) rs->delivered * tp->mss_cache * S_TO_US; 187 | do_div(rin, snd_us); 188 | do_div(rout, ack_us); 189 | if (rin < 1000 * rout) { 190 | metric->values[INPUT_METRICS_POS_SEND_RATIO] = INPUT_SCALE * rin; 191 | do_div(metric->values[INPUT_METRICS_POS_SEND_RATIO], rout); 192 | } 193 | else { 194 | metric->values[INPUT_METRICS_POS_SEND_RATIO] = INPUT_SCALE * 1; 195 | } 196 | } 197 | else { 198 | metric->values[INPUT_METRICS_POS_SEND_RATIO] = INPUT_SCALE * 1; 199 | } 200 | 201 | return LF_SUCCS; 202 | } 203 | 204 | static void lf_tcp_conn_init(struct sock *sk) { 205 | struct lf_tcp_internal *ca; 206 | struct tcp_sock *tp; 207 | int i; 208 | 209 | printk(KERN_INFO "New flow handled by liteflow tcp kernel inits...\n"); 210 | ca = inet_csk_ca(sk); 211 | tp = tcp_sk(sk); 212 | 213 | ca->src_ip = tp->inet_conn.icsk_inet.inet_saddr; 214 | ca->src_port = tp->inet_conn.icsk_inet.inet_sport; 215 | ca->dest_ip = tp->inet_conn.icsk_inet.inet_daddr; 216 | ca->dest_port = tp->inet_conn.icsk_inet.inet_dport; 217 | ca->last_snd_una = tp->snd_una; 218 | ca->global_stats[GLOBAL_STATS_POS_MIN_RTT_US] = -1; 219 | 220 | ca->current_pointer = 0; 221 | ca->metrics = kmalloc(sizeof(struct lf_tcp_metric) * HISTORY_LEN, GFP_KERNEL); 222 | memset(ca->metrics, 0, sizeof(struct lf_tcp_metric) * HISTORY_LEN); 223 | for (i = 0; i < HISTORY_LEN; ++i) { 224 | ca->metrics[i].values[INPUT_METRICS_POS_LAT_RATIO] = INPUT_SCALE * 1; 225 | } 226 | 227 | #ifdef COMPAT_MODE 228 | ca->skb_array = kmalloc(sizeof(struct skb_info) * MAX_SKB_STORED, GFP_KERNEL); 229 | memset(ca->skb_array, 0, sizeof(struct skb_info) * MAX_SKB_STORED); 230 | #endif 231 | 232 | if (!(tp->ecn_flags & TCP_ECN_OK)) { 233 | INET_ECN_dontxmit(sk); 234 | } 235 | // Turn on pacing, so we can use pacing to control the speed 236 | // Learn from BBR and CCP :) 237 | tp->snd_cwnd = MAX_CWND; 238 | tp->snd_ssthresh = MAX_CWND; 239 | cmpxchg(&sk->sk_pacing_status, SK_PACING_NONE, SK_PACING_NEEDED); 240 | } 241 | 242 | static void lf_tcp_conn_release(struct sock *sk) 243 | { 244 | struct lf_tcp_internal *ca; 245 | 246 | printk(KERN_INFO "Free flow handled by liteflow tcp kernel...\n"); 247 | 248 | ca = inet_csk_ca(sk); 249 | if (ca->metrics != NULL) { 250 | kfree(ca->metrics); 251 | } 252 | 253 | #ifdef COMPAT_MODE 254 | if (ca->skb_array != NULL) { 255 | kfree(ca->skb_array); 256 | ca->skb_array = NULL; 257 | } 258 | #endif 259 | } 260 | 261 | // Learn from CCP 262 | static u32 lf_tcp_conn_ssthresh(struct sock *sk) 263 | { 264 | const struct tcp_sock *tp = tcp_sk(sk); 265 | return max(tp->snd_cwnd >> 1U, 2U); 266 | } 267 | 268 | // Learn from CCP 269 | static u32 lf_tcp_conn_undo_cwnd(struct sock *sk) 270 | { 271 | const struct tcp_sock *tp = tcp_sk(sk); 272 | return max(tp->snd_cwnd, tp->snd_ssthresh << 1); 273 | } 274 | 275 | // Send all metrics to liteflow hosted NN 276 | // and set sending rate based on returned rate 277 | static void lf_tcp_conn_nn_control(struct sock *sk, const struct rate_sample *rs) 278 | { 279 | int ret; 280 | //int global_stats_pos; 281 | int metric_pos, value_pos, pos = 0; 282 | s64 output_rate; 283 | struct lf_tcp_internal *ca; 284 | struct tcp_sock *tp; 285 | s64 nn_input[INPUT_SIZE]; 286 | s64 nn_output[NUM_OF_OUTPUT_VALUE]; 287 | 288 | ca = inet_csk_ca(sk); 289 | if(ca->metrics == NULL) { 290 | printk(KERN_ERR "Current flow is not managed by liteflow tcp kernel!\n"); 291 | return; 292 | } 293 | 294 | tp = tcp_sk(sk); 295 | ret = load_metric(ca, tp, rs); 296 | if(ret == LF_ERROR) { 297 | return; 298 | } 299 | 300 | for (pos = 0, metric_pos = 1; metric_pos <= HISTORY_LEN; ++metric_pos) { 301 | for (value_pos = 0; value_pos < NUM_OF_INPUT_METRICS; ++value_pos) { 302 | nn_input[pos] = ca->metrics[(ca->current_pointer + metric_pos) % HISTORY_LEN].values[value_pos]; 303 | pos++; 304 | } 305 | } 306 | 307 | // TODO do not frequently report to user space 308 | // ret = report_to_user(nn_input, INPUT_SIZE); 309 | // if (ret == LF_ERROR) { 310 | // printk(KERN_ERR "Report to user space failed!\n"); 311 | // } 312 | 313 | ret = lf_query_model(LF_TCP_APP_ID, nn_input, nn_output); 314 | if (ret == LF_ERROR) { 315 | printk(KERN_ERR "Query NN model failed!\n"); 316 | } else { 317 | output_rate = nn_output[OUTPUT_RATE]; 318 | lf_set_relative_rate(sk, output_rate); 319 | } 320 | 321 | lf_increase_pointer(ca); 322 | } 323 | 324 | static void lf_tcp_conn_in_ack_event(struct sock *sk, u32 flags) 325 | { 326 | // Obtain information 327 | // More can be added 328 | const struct tcp_sock *tp; 329 | struct lf_tcp_internal *ca; 330 | u32 acked_bytes, acked_packets; 331 | 332 | #ifdef COMPAT_MODE 333 | int i = 0; 334 | struct sk_buff *skb = tcp_write_queue_head(sk); 335 | struct tcp_skb_cb *scb; 336 | #endif 337 | 338 | tp = tcp_sk(sk); 339 | ca = inet_csk_ca(sk); 340 | 341 | if(ca->metrics == NULL) { 342 | printk(KERN_ERR "Current flow is not managed by liteflow tcp kernel!\n"); 343 | return; 344 | } 345 | 346 | acked_bytes = tp->snd_una - ca->last_snd_una; 347 | acked_packets = (u64)acked_bytes / tp->mss_cache; 348 | if((u64)acked_bytes % tp->mss_cache != 0) { 349 | acked_packets += 1; 350 | } 351 | 352 | ca->last_snd_una = tp->snd_una; 353 | 354 | // learn from ccp 355 | #ifdef COMPAT_MODE 356 | for(i = 0; i < MAX_SKB_STORED; ++i) { 357 | if(skb) { 358 | scb = TCP_SKB_CB(skb); 359 | ca->skb_array[i].first_tx_mstamp = skb->skb_mstamp; 360 | ca->skb_array[i].interval_us = tcp_stamp_us_delta(skb->skb_mstamp, scb->tx.first_tx_mstamp); 361 | skb = skb->next; 362 | } 363 | else { 364 | ca->skb_array[i].first_tx_mstamp = 0; 365 | ca->skb_array[i].interval_us = 0; 366 | } 367 | } 368 | #endif 369 | } 370 | 371 | 372 | struct app lf_tcp_app = { 373 | .appid = LF_TCP_APP_ID, 374 | .input_size = INPUT_SIZE, 375 | .output_size = NUM_OF_OUTPUT_VALUE, 376 | }; 377 | 378 | struct tcp_congestion_ops lf_tcp_congestion_ops = { 379 | .init = lf_tcp_conn_init, 380 | .release = lf_tcp_conn_release, 381 | .ssthresh = lf_tcp_conn_ssthresh, 382 | .undo_cwnd = lf_tcp_conn_undo_cwnd, 383 | .cong_control = lf_tcp_conn_nn_control, 384 | .in_ack_event = lf_tcp_conn_in_ack_event, 385 | .name = "lf_tcp_kernel", 386 | .owner = THIS_MODULE, 387 | }; 388 | 389 | static int 390 | __init liteflow_tcp_kernel_init(void) 391 | { 392 | int ret; 393 | 394 | BUILD_BUG_ON(sizeof(struct lf_tcp_internal) > ICSK_CA_PRIV_SIZE); 395 | ret = tcp_register_congestion_control(&lf_tcp_congestion_ops); 396 | if (ret != 0) { 397 | printk(KERN_ERR "Cannot register liteflow tcp kernel with kernel CC!\n"); 398 | return ret; 399 | } 400 | ret = lf_register_app(&lf_tcp_app); 401 | if (ret != LF_SUCCS) { 402 | printk(KERN_ERR "Cannot register liteflow tcp kernel with liteflow kernel!\n"); 403 | return ret; 404 | } 405 | 406 | ret = genl_register_family(&lf_tcp_gnl_family); 407 | if (ret != 0) { 408 | printk(KERN_ERR "Cannot register liteflow tcp generic netlink!\n"); 409 | return ret; 410 | } 411 | printk(KERN_INFO "Successfully register liteflow tcp kernel with liteflow kernel, kernel CC and netlink...\n"); 412 | return ret; 413 | } 414 | 415 | static void 416 | __exit liteflow_tcp_kernel_exit(void) 417 | { 418 | int ret; 419 | tcp_unregister_congestion_control(&lf_tcp_congestion_ops); 420 | lf_unregister_app(LF_TCP_APP_ID); 421 | ret = genl_unregister_family(&lf_tcp_gnl_family); 422 | if (ret != 0) { 423 | printk(KERN_ERR "Cannot unregister liteflow tcp generic netlink!\n"); 424 | } 425 | } 426 | 427 | module_init(liteflow_tcp_kernel_init); 428 | module_exit(liteflow_tcp_kernel_exit); 429 | 430 | MODULE_DESCRIPTION("liteflow tcp kernel"); 431 | MODULE_AUTHOR("Junxue ZHANG"); 432 | MODULE_AUTHOR("Chaoliang ZENG"); 433 | MODULE_LICENSE("GPL v2"); 434 | -------------------------------------------------------------------------------- /doc/liteflow-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzjx/liteflow/504aae114850d64af9954f640480cf86a7cd176b/doc/liteflow-logo.png -------------------------------------------------------------------------------- /doc/liteflow-logo.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/snowzjx/liteflow/504aae114850d64af9954f640480cf86a7cd176b/doc/liteflow-logo.psd -------------------------------------------------------------------------------- /include/liblf.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB_LF_H 2 | #define LIB_LF_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // The rx function is a customized function 11 | // The return denotes to 'should_stop' 12 | extern int rx_dp_notification(bool (*rx)(__s64 *data, __u32 length)); 13 | 14 | // --- Tools --- 15 | static struct nl_sock* connect_lf_genl_sock() 16 | { 17 | struct nl_sock* sock; 18 | 19 | sock = nl_socket_alloc(); 20 | if (sock == NULL) { 21 | fprintf(stderr, "Cannot allocate sock...\n"); 22 | return NULL; 23 | } 24 | 25 | nl_socket_disable_seq_check(sock); 26 | nl_socket_disable_auto_ack(sock); 27 | 28 | if (genl_connect(sock)) { 29 | fprintf(stderr, "Unable to connect to genl...\n"); 30 | return NULL; 31 | } 32 | 33 | return sock; 34 | } 35 | 36 | static int resolve_family_id(struct nl_sock* sock, char* family_name) 37 | { 38 | int family_id; 39 | 40 | family_id = genl_ctrl_resolve(sock, family_name); 41 | if(family_id < 0) { 42 | fprintf(stderr, "Unable to resolve family name...\n"); 43 | return -1; 44 | } 45 | fprintf(stdout, "Returned family id is %d...\n", family_id); 46 | return family_id; 47 | } 48 | 49 | static int resolve_grp_id(struct nl_sock* sock, char* family_name, char* group_name) 50 | { 51 | int grp_id; 52 | 53 | grp_id = genl_ctrl_resolve_grp(sock, family_name, group_name); 54 | if (grp_id < 0) { 55 | fprintf(stderr, "Unable to resolve group name...\n"); 56 | return -1; 57 | } 58 | fprintf(stdout, "Returned group id is %d...\n", grp_id); 59 | 60 | return grp_id; 61 | } 62 | 63 | #endif -------------------------------------------------------------------------------- /include/linux/liteflow.h: -------------------------------------------------------------------------------- 1 | #ifndef LITEFLOW_H 2 | #define LITEFLOW_H 3 | 4 | /* --- return code --- */ 5 | #define LF_SUCCS 0 6 | #define LF_ERROR 4 7 | 8 | /* --- data structure --- */ 9 | struct app { 10 | u8 appid; 11 | u32 input_size; 12 | u32 output_size; 13 | } __packed; 14 | 15 | struct model_container { 16 | u32 uuid; 17 | u32 input_size; 18 | u32 output_size; 19 | struct list_head layers; 20 | } __packed; 21 | 22 | struct model_layer { 23 | u32 uuid; 24 | u32 input_size; 25 | u32 output_size; 26 | void (*comp_func) (s64 *input, s64 *output); 27 | struct list_head list; // layers are organized in a linked list 28 | 29 | // private part, will hide later use other techniques 30 | s64 *input; 31 | s64 *output; 32 | } __packed; 33 | 34 | /* --- public function --- */ 35 | int lf_register_app(struct app*); 36 | 37 | int lf_unregister_app(u8 appid); 38 | 39 | int lf_register_model(u8 appid, struct model_container *); 40 | 41 | int lf_unregister_model(u8 appid, u32 model_uuid); 42 | 43 | int lf_query_model(u8 appid, s64 *input, s64 *output); 44 | 45 | /* --- for testing --- */ 46 | #define LF_TEST_ID 31 47 | 48 | #endif -------------------------------------------------------------------------------- /include/linux/tanh_lookup_table.h: -------------------------------------------------------------------------------- 1 | #ifndef TANH_LOOPUP_TABLE 2 | #define TANH_LOOPUP_TABLE 3 | 4 | #define TAHN_P_RANGE 300 5 | #define TANH_X_SCALE_UP 100 6 | #define TANH_Y_SCALE_DOWN 100 7 | 8 | const static s32 tanh_look_up_table[TAHN_P_RANGE] = { 9 | [0] = 0, 10 | [1] = 0, 11 | [2] = 1, 12 | [3] = 2, 13 | [4] = 3, 14 | [5] = 4, 15 | [6] = 5, 16 | [7] = 6, 17 | [8] = 7, 18 | [9] = 8, 19 | [10] = 9, 20 | [11] = 10, 21 | [12] = 11, 22 | [13] = 12, 23 | [14] = 13, 24 | [15] = 14, 25 | [16] = 15, 26 | [17] = 16, 27 | [18] = 17, 28 | [19] = 18, 29 | [20] = 19, 30 | [21] = 20, 31 | [22] = 21, 32 | [23] = 22, 33 | [24] = 23, 34 | [25] = 24, 35 | [26] = 25, 36 | [27] = 26, 37 | [28] = 27, 38 | [29] = 28, 39 | [30] = 29, 40 | [31] = 30, 41 | [32] = 30, 42 | [33] = 31, 43 | [34] = 32, 44 | [35] = 33, 45 | [36] = 34, 46 | [37] = 35, 47 | [38] = 36, 48 | [39] = 37, 49 | [40] = 37, 50 | [41] = 38, 51 | [42] = 39, 52 | [43] = 40, 53 | [44] = 41, 54 | [45] = 42, 55 | [46] = 43, 56 | [47] = 43, 57 | [48] = 44, 58 | [49] = 45, 59 | [50] = 46, 60 | [51] = 46, 61 | [52] = 47, 62 | [53] = 48, 63 | [54] = 49, 64 | [55] = 50, 65 | [56] = 50, 66 | [57] = 51, 67 | [58] = 52, 68 | [59] = 52, 69 | [60] = 53, 70 | [61] = 54, 71 | [62] = 55, 72 | [63] = 55, 73 | [64] = 56, 74 | [65] = 57, 75 | [66] = 57, 76 | [67] = 58, 77 | [68] = 59, 78 | [69] = 59, 79 | [70] = 60, 80 | [71] = 61, 81 | [72] = 61, 82 | [73] = 62, 83 | [74] = 62, 84 | [75] = 63, 85 | [76] = 64, 86 | [77] = 64, 87 | [78] = 65, 88 | [79] = 65, 89 | [80] = 66, 90 | [81] = 66, 91 | [82] = 67, 92 | [83] = 68, 93 | [84] = 68, 94 | [85] = 69, 95 | [86] = 69, 96 | [87] = 70, 97 | [88] = 70, 98 | [89] = 71, 99 | [90] = 71, 100 | [91] = 72, 101 | [92] = 72, 102 | [93] = 73, 103 | [94] = 73, 104 | [95] = 73, 105 | [96] = 74, 106 | [97] = 74, 107 | [98] = 75, 108 | [99] = 75, 109 | [100] = 76, 110 | [101] = 76, 111 | [102] = 76, 112 | [103] = 77, 113 | [104] = 77, 114 | [105] = 78, 115 | [106] = 78, 116 | [107] = 78, 117 | [108] = 79, 118 | [109] = 79, 119 | [110] = 80, 120 | [111] = 80, 121 | [112] = 80, 122 | [113] = 81, 123 | [114] = 81, 124 | [115] = 81, 125 | [116] = 82, 126 | [117] = 82, 127 | [118] = 82, 128 | [119] = 83, 129 | [120] = 83, 130 | [121] = 83, 131 | [122] = 83, 132 | [123] = 84, 133 | [124] = 84, 134 | [125] = 84, 135 | [126] = 85, 136 | [127] = 85, 137 | [128] = 85, 138 | [129] = 85, 139 | [130] = 86, 140 | [131] = 86, 141 | [132] = 86, 142 | [133] = 86, 143 | [134] = 87, 144 | [135] = 87, 145 | [136] = 87, 146 | [137] = 87, 147 | [138] = 88, 148 | [139] = 88, 149 | [140] = 88, 150 | [141] = 88, 151 | [142] = 88, 152 | [143] = 89, 153 | [144] = 89, 154 | [145] = 89, 155 | [146] = 89, 156 | [147] = 89, 157 | [148] = 90, 158 | [149] = 90, 159 | [150] = 90, 160 | [151] = 90, 161 | [152] = 90, 162 | [153] = 91, 163 | [154] = 91, 164 | [155] = 91, 165 | [156] = 91, 166 | [157] = 91, 167 | [158] = 91, 168 | [159] = 92, 169 | [160] = 92, 170 | [161] = 92, 171 | [162] = 92, 172 | [163] = 92, 173 | [164] = 92, 174 | [165] = 92, 175 | [166] = 93, 176 | [167] = 93, 177 | [168] = 93, 178 | [169] = 93, 179 | [170] = 93, 180 | [171] = 93, 181 | [172] = 93, 182 | [173] = 93, 183 | [174] = 94, 184 | [175] = 94, 185 | [176] = 94, 186 | [177] = 94, 187 | [178] = 94, 188 | [179] = 94, 189 | [180] = 94, 190 | [181] = 94, 191 | [182] = 94, 192 | [183] = 94, 193 | [184] = 95, 194 | [185] = 95, 195 | [186] = 95, 196 | [187] = 95, 197 | [188] = 95, 198 | [189] = 95, 199 | [190] = 95, 200 | [191] = 95, 201 | [192] = 95, 202 | [193] = 95, 203 | [194] = 95, 204 | [195] = 96, 205 | [196] = 96, 206 | [197] = 96, 207 | [198] = 96, 208 | [199] = 96, 209 | [200] = 96, 210 | [201] = 96, 211 | [202] = 96, 212 | [203] = 96, 213 | [204] = 96, 214 | [205] = 96, 215 | [206] = 96, 216 | [207] = 96, 217 | [208] = 96, 218 | [209] = 96, 219 | [210] = 97, 220 | [211] = 97, 221 | [212] = 97, 222 | [213] = 97, 223 | [214] = 97, 224 | [215] = 97, 225 | [216] = 97, 226 | [217] = 97, 227 | [218] = 97, 228 | [219] = 97, 229 | [220] = 97, 230 | [221] = 97, 231 | [222] = 97, 232 | [223] = 97, 233 | [224] = 97, 234 | [225] = 97, 235 | [226] = 97, 236 | [227] = 97, 237 | [228] = 97, 238 | [229] = 97, 239 | [230] = 98, 240 | [231] = 98, 241 | [232] = 98, 242 | [233] = 98, 243 | [234] = 98, 244 | [235] = 98, 245 | [236] = 98, 246 | [237] = 98, 247 | [238] = 98, 248 | [239] = 98, 249 | [240] = 98, 250 | [241] = 98, 251 | [242] = 98, 252 | [243] = 98, 253 | [244] = 98, 254 | [245] = 98, 255 | [246] = 98, 256 | [247] = 98, 257 | [248] = 98, 258 | [249] = 98, 259 | [250] = 98, 260 | [251] = 98, 261 | [252] = 98, 262 | [253] = 98, 263 | [254] = 98, 264 | [255] = 98, 265 | [256] = 98, 266 | [257] = 98, 267 | [258] = 98, 268 | [259] = 98, 269 | [260] = 98, 270 | [261] = 98, 271 | [262] = 98, 272 | [263] = 98, 273 | [264] = 98, 274 | [265] = 99, 275 | [266] = 99, 276 | [267] = 99, 277 | [268] = 99, 278 | [269] = 99, 279 | [270] = 99, 280 | [271] = 99, 281 | [272] = 99, 282 | [273] = 99, 283 | [274] = 99, 284 | [275] = 99, 285 | [276] = 99, 286 | [277] = 99, 287 | [278] = 99, 288 | [279] = 99, 289 | [280] = 99, 290 | [281] = 99, 291 | [282] = 99, 292 | [283] = 99, 293 | [284] = 99, 294 | [285] = 99, 295 | [286] = 99, 296 | [287] = 99, 297 | [288] = 99, 298 | [289] = 99, 299 | [290] = 99, 300 | [291] = 99, 301 | [292] = 99, 302 | [293] = 99, 303 | [294] = 99, 304 | [295] = 99, 305 | [296] = 99, 306 | [297] = 99, 307 | [298] = 99, 308 | [299] = 99, 309 | }; 310 | 311 | static inline s32 lf_tanh (s32 x_100) 312 | { 313 | if (x_100 == 0) { 314 | return 0; 315 | } 316 | else if (x_100 < 0) 317 | { 318 | return -lf_tanh(-x_100); 319 | } 320 | else if (x_100 >= TAHN_P_RANGE) { 321 | return 1 * TANH_Y_SCALE_DOWN; 322 | } 323 | else { 324 | return tanh_look_up_table[x_100]; 325 | } 326 | } 327 | 328 | #endif -------------------------------------------------------------------------------- /include/liteflow_netfilter.h: -------------------------------------------------------------------------------- 1 | #ifndef LITEFLOW_NETFILTER_H 2 | #define LITEFLOW_NETFILTER_H 3 | 4 | #ifndef __KERNEL__ 5 | #include 6 | #include 7 | #include 8 | #else 9 | #include 10 | #endif 11 | 12 | #define LF_NETFILTER_APP_ID 2 // Netfilter applications will be given ID 2 13 | 14 | #define INPUT_METRICS_POS_SRC_IP_A_B 0 15 | #define INPUT_METRICS_POS_SRC_IP_C_D 1 16 | #define INPUT_METRICS_POS_SRC_PORT 2 17 | #define INPUT_METRICS_POS_DST_IP_A_B 3 18 | #define INPUT_METRICS_POS_DST_IP_C_D 4 19 | #define INPUT_METRICS_POS_DST_PORT 5 20 | // More can be added 21 | #define NUM_OF_INPUT_VALUE 6 22 | 23 | #define OUTPUT_SHOULD_PASS 0 24 | #define OUTPUT_SHOULD_DROP 1 25 | #define NUM_OF_OUTPUT_VALUE 2 26 | 27 | #endif -------------------------------------------------------------------------------- /include/liteflow_nl.h: -------------------------------------------------------------------------------- 1 | #ifndef LITEFLOW_NL_H 2 | #define LITEFLOW_NL_H 3 | 4 | #include 5 | 6 | #ifndef __KERNEL__ 7 | #include 8 | #include 9 | #include 10 | #else 11 | #include 12 | #endif 13 | 14 | /* -- netlink related --- */ 15 | #define LF_NL_NAME "lf" 16 | #define LF_NL_VERSION 1 17 | 18 | #define LF_NL_MC_DEFAULT_NAME "lf_default" 19 | 20 | enum lf_multicast_groups { 21 | LF_NL_MC_DEFAULT, // Start from 0 22 | __LF_NL_MC_MAX, 23 | }; 24 | #define LF_NL_MC_MAX (__LF_NL_MC_MAX - 1) 25 | 26 | enum lf_controls { 27 | LF_NL_C_UNSPEC, 28 | LF_NL_C_REPORT_DATA, 29 | __LF_NL_C_MAX, 30 | }; 31 | #define LF_NL_C_MAX (__LF_NL_C_MAX - 1) 32 | 33 | enum lf_attrs { 34 | LF_NL_ATTR_UNSPEC, 35 | LF_NL_ATTR_SIZE, 36 | LF_NL_ATTR_DATA, 37 | __LF_NL_ATTR__MAX, 38 | }; 39 | #define LF_NL_ATTR_MAX (__LF_NL_ATTR__MAX - 1) 40 | #ifdef __KERNEL__ 41 | static const struct nla_policy lf_policy[LF_NL_ATTR_MAX + 1] = { 42 | #else 43 | static struct nla_policy lf_policy[LF_NL_ATTR_MAX + 1] = { 44 | #endif 45 | [LF_NL_ATTR_SIZE] = { 46 | .type = NLA_U32, 47 | }, 48 | [LF_NL_ATTR_DATA] = { 49 | .type = NLA_UNSPEC, 50 | } 51 | }; 52 | 53 | struct lf_nl_ops { 54 | int (*recv_activation_cb)(__u8 appid, __u32 model_uuid); 55 | }; 56 | 57 | extern int start_nl(void); 58 | extern int stop_nl(void); 59 | extern int report_data(__s64 *data, __u32 length); 60 | 61 | #endif -------------------------------------------------------------------------------- /include/liteflow_tcp.h: -------------------------------------------------------------------------------- 1 | #ifndef LITEFLOW_TCP_H 2 | #define LITEFLOW_TCP_H 3 | 4 | #ifndef __KERNEL__ 5 | #include 6 | #include 7 | #include 8 | #else 9 | #include 10 | #endif 11 | 12 | #define LF_TCP_APP_ID 1 // CC applications will be given ID 1 13 | // Here we allow different connection can use different models, just change the APP_ID per connection 14 | // In this implementation, connection information is put in the input of the NN 15 | 16 | // Aurora input 17 | #define INPUT_SCALE 1000 18 | #define OUTPUT_SCALE 1000 19 | 20 | // NUM_OF_INPUT_METRICS metrics in a history record 21 | // Here we dedicate the design for PCC-RL 22 | #define INPUT_METRICS_POS_SENT_LAT_INFLACTION 0 23 | #define INPUT_METRICS_POS_LAT_RATIO 1 24 | #define INPUT_METRICS_POS_SEND_RATIO 2 25 | 26 | #define NUM_OF_INPUT_METRICS 3 27 | 28 | #define HISTORY_LEN 10 29 | 30 | #define INPUT_SIZE (NUM_OF_INPUT_METRICS * HISTORY_LEN) 31 | 32 | // NUM_OF_GLOBAL_STATS per connection 33 | #define GLOBAL_STATS_POS_MIN_RTT_US 0 34 | // More can be added 35 | #define NUM_OF_GLOBAL_STATS 1 36 | 37 | #define OUTPUT_RATE 0 38 | #define NUM_OF_OUTPUT_VALUE 1 39 | 40 | /* -- netlink related --- */ 41 | #define LF_TCP_NL_NAME "lf_tcp" 42 | #define LF_TCP_NL_VERSION 1 43 | 44 | #define LF_TCP_NL_MC_DEFAULT_NAME "lf_tcp_default" 45 | 46 | enum lf_tcp_multicast_groups { 47 | LF_TCP_NL_MC_DEFAULT, // Start from 0 48 | __LF_TCP_NL_MC_MAX, 49 | }; 50 | #define LF_TCP_NL_MC_MAX (__LF_TCP_NL_MC_MAX - 1) 51 | 52 | enum lf_tcp_controls { 53 | LF_TCP_NL_C_UNSPEC, 54 | LF_TCP_NL_C_REPORT, // Kernel send 55 | __LF_TCP_NL_C_MAX, 56 | }; 57 | #define LF_TCP_NL_C_MAX (__LF_TCP_NL_C_MAX - 1) 58 | 59 | enum lf_tcp_attrs { 60 | LF_TCP_NL_ATTR_UNSPEC, 61 | LF_TCP_NL_ATTR_NN_INPUT, // The data of NN 62 | __LF_TCP_NL_ATTR__MAX, 63 | }; 64 | #define LF_TCP_NL_ATTR_MAX (__LF_TCP_NL_ATTR__MAX - 1) 65 | 66 | #ifdef __KERNEL__ 67 | static const struct nla_policy lf_tcp_policy[LF_TCP_NL_ATTR_MAX + 1] = { 68 | #else 69 | static struct nla_policy lf_tcp_policy[LF_TCP_NL_ATTR_MAX + 1] = { 70 | #endif 71 | [LF_TCP_NL_ATTR_NN_INPUT] = { 72 | .type = NLA_UNSPEC, 73 | #ifndef __KERNEL__ 74 | .minlen = INPUT_SIZE * sizeof(__s64), 75 | .maxlen = INPUT_SIZE * sizeof(__s64), 76 | #endif 77 | }, 78 | }; 79 | 80 | 81 | #endif 82 | -------------------------------------------------------------------------------- /lib/Makefile: -------------------------------------------------------------------------------- 1 | INCLUDE = ../include 2 | 3 | all: 4 | gcc -c -Werror -O3 -fpic liblf_dp_notification.c -I$(INCLUDE) $(shell pkg-config --cflags --libs libnl-3.0 libnl-genl-3.0) 5 | gcc -shared -o liblf.so liblf_dp_notification.o $(shell pkg-config --cflags --libs libnl-3.0 libnl-genl-3.0) -------------------------------------------------------------------------------- /lib/liblf_dp_notification.c: -------------------------------------------------------------------------------- 1 | #include "liblf.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "liteflow_nl.h" 9 | 10 | struct ops_wrapper { 11 | bool (*ops)(__s64 *data, __u32 size); 12 | bool shoud_stop; 13 | }; 14 | 15 | static int rx_msg(struct nl_msg *msg, void* args) 16 | { 17 | struct nlattr *attr[LF_NL_ATTR_MAX + 1]; 18 | struct ops_wrapper *wrapper = args; 19 | __s64 *data; 20 | __u32 length; 21 | 22 | genlmsg_parse(nlmsg_hdr(msg), 0, attr, LF_NL_ATTR_MAX, lf_policy); 23 | 24 | if (!attr[LF_NL_ATTR_SIZE]) { 25 | fprintf(stderr, "Kernel sent empty message!\n"); 26 | return NL_OK; 27 | } 28 | 29 | length = nla_get_u32(attr[LF_NL_ATTR_SIZE]); 30 | 31 | if (!attr[LF_NL_ATTR_DATA]) { 32 | fprintf(stderr, "Kernel sent empty message!\n"); 33 | return NL_OK; 34 | } 35 | 36 | data = nla_data(attr[LF_NL_ATTR_DATA]); 37 | wrapper->shoud_stop = wrapper->ops(data, length); 38 | 39 | return 0; 40 | } 41 | 42 | int rx_dp_notification(bool (*rx)(__s64 *data, __u32 length)) 43 | { 44 | struct nl_sock* sock = NULL; 45 | struct nl_cb *cb = NULL; 46 | int family_id, grp_id; 47 | struct ops_wrapper wrapper = { 48 | .ops = rx, 49 | .shoud_stop = false, 50 | }; 51 | 52 | sock = connect_lf_genl_sock(); 53 | if (sock == NULL) { 54 | return -1; 55 | } 56 | 57 | family_id = resolve_family_id(sock, LF_NL_NAME); 58 | if (family_id < 0) { 59 | return -1; 60 | } 61 | 62 | grp_id = resolve_grp_id(sock, LF_NL_NAME, LF_NL_MC_DEFAULT_NAME); 63 | if (grp_id < 0) { 64 | fprintf(stderr, "Unable to resolve group name...\n"); 65 | return -1; 66 | } 67 | 68 | if (nl_socket_add_membership(sock, grp_id)) { 69 | fprintf(stderr, "Unable to join group %u!\n", grp_id); 70 | } 71 | 72 | cb = nl_cb_alloc(NL_CB_DEFAULT); 73 | nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, rx_msg, &wrapper); 74 | 75 | do { 76 | nl_recvmsgs(sock, cb); 77 | } while (wrapper.shoud_stop == false); 78 | 79 | nl_cb_put(cb); 80 | nl_socket_free(sock); 81 | 82 | return 0; 83 | } -------------------------------------------------------------------------------- /python/README.md: -------------------------------------------------------------------------------- 1 | Python for LiteFlow -------------------------------------------------------------------------------- /python/liteflow/__init__.py: -------------------------------------------------------------------------------- 1 | # This file intentionally left blank. -------------------------------------------------------------------------------- /python/liteflow/model.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | 3 | CBFUNC = CFUNCTYPE(c_bool, c_ushort, c_uint) 4 | 5 | def reg_notification(func): 6 | func_p = CBFUNC(func) 7 | 8 | liblf = CDLL("liblf.so") 9 | ret_u = liblf.rx_activation_notification(func_p) 10 | return c_int(ret_u).value -------------------------------------------------------------------------------- /python/liteflow/tcp/dp.py: -------------------------------------------------------------------------------- 1 | from ctypes import * 2 | 3 | CBFUNC = CFUNCTYPE(c_bool, POINTER(c_longlong), c_size_t) 4 | 5 | def reg_notification(func): 6 | func_p = CBFUNC(func) 7 | 8 | liblf = CDLL("liblf.so") 9 | ret_u = liblf.rx_tcp_dp_notification(func_p) 10 | return c_int(ret_u).value -------------------------------------------------------------------------------- /python/setup.py: -------------------------------------------------------------------------------- 1 | """A setuptools based setup module. 2 | 3 | See: 4 | https://github.com/liteflow/liteflow 5 | """ 6 | 7 | # Always prefer setuptools over distutils 8 | from setuptools import setup, find_packages 9 | from os import path 10 | # io.open is needed for projects that support Python 2.7 11 | # It ensures open() defaults to text mode with universal newlines, 12 | # and accepts an argument to specify the text encoding 13 | # Python 3 only projects can skip this import 14 | from io import open 15 | 16 | here = path.abspath(path.dirname(__file__)) 17 | 18 | # Get the long description from the README file 19 | with open(path.join(here, 'README.md'), encoding='utf-8') as f: 20 | long_description = f.read() 21 | 22 | # Arguments marked as "Required" below must be included for upload to PyPI. 23 | # Fields marked as "Optional" may be commented out. 24 | 25 | setup( 26 | # This is the name of your project. The first time you publish this 27 | # package, this name will be registered for you. It will determine how 28 | # users can install this project, e.g.: 29 | # 30 | # $ pip install sampleproject 31 | # 32 | # And where it will live on PyPI: https://pypi.org/project/sampleproject/ 33 | # 34 | # There are some restrictions on what makes a valid project name 35 | # specification here: 36 | # https://packaging.python.org/specifications/core-metadata/#name 37 | name='liteflow', # Required 38 | 39 | # Versions should comply with PEP 440: 40 | # https://www.python.org/dev/peps/pep-0440/ 41 | # 42 | # For a discussion on single-sourcing the version across setup.py and the 43 | # project code, see 44 | # https://packaging.python.org/en/latest/single_source_version.html 45 | version='0.0.1', # Required 46 | 47 | # This is a one-line description or tagline of what your project does. This 48 | # corresponds to the "Summary" metadata field: 49 | # https://packaging.python.org/specifications/core-metadata/#summary 50 | # description='A sample Python project', # Optional 51 | 52 | # This is an optional longer description of your project that represents 53 | # the body of text which users will see when they visit PyPI. 54 | # 55 | # Often, this is the same as your README, so you can just read it in from 56 | # that file directly (as we have already done above) 57 | # 58 | # This field corresponds to the "Description" metadata field: 59 | # https://packaging.python.org/specifications/core-metadata/#description-optional 60 | # long_description=long_description, # Optional 61 | 62 | # Denotes that our long_description is in Markdown; valid values are 63 | # text/plain, text/x-rst, and text/markdown 64 | # 65 | # Optional if long_description is written in reStructuredText (rst) but 66 | # required for plain-text or Markdown; if unspecified, "applications should 67 | # attempt to render [the long_description] as text/x-rst; charset=UTF-8 and 68 | # fall back to text/plain if it is not valid rst" (see link below) 69 | # 70 | # This field corresponds to the "Description-Content-Type" metadata field: 71 | # https://packaging.python.org/specifications/core-metadata/#description-content-type-optional 72 | # long_description_content_type='text/markdown', # Optional (see note above) 73 | 74 | # This should be a valid link to your project's main homepage. 75 | # 76 | # This field corresponds to the "Home-Page" metadata field: 77 | # https://packaging.python.org/specifications/core-metadata/#home-page-optional 78 | # url='https://github.com/pypa/sampleproject', # Optional 79 | 80 | # This should be your name or the name of the organization which owns the 81 | # project. 82 | author='Junxue ZHANG', # Optional 83 | 84 | # This should be a valid email address corresponding to the author listed 85 | # above. 86 | author_email='snow.zjx@outlook.com', # Optional 87 | 88 | # Classifiers help users find your project by categorizing it. 89 | # 90 | # For a list of valid classifiers, see https://pypi.org/classifiers/ 91 | classifiers=[ # Optional 92 | # How mature is this project? Common values are 93 | # 3 - Alpha 94 | # 4 - Beta 95 | # 5 - Production/Stable 96 | 'Development Status :: 3 - Alpha', 97 | 98 | # Indicate who your project is intended for 99 | 'Intended Audience :: Developers', 100 | 'Topic :: Software Development :: Build Tools', 101 | 102 | # Pick your license as you wish 103 | 'License :: OSI Approved :: GPLv2 License', 104 | 105 | # Specify the Python versions you support here. In particular, ensure 106 | # that you indicate whether you support Python 2, Python 3 or both. 107 | # These classifiers are *not* checked by 'pip install'. See instead 108 | # 'python_requires' below. 109 | 'Programming Language :: Python :: 2', 110 | 'Programming Language :: Python :: 2.7', 111 | 'Programming Language :: Python :: 3', 112 | 'Programming Language :: Python :: 3.5', 113 | 'Programming Language :: Python :: 3.6', 114 | 'Programming Language :: Python :: 3.7', 115 | 'Programming Language :: Python :: 3.8', 116 | ], 117 | 118 | # This field adds keywords for your project which will appear on the 119 | # project page. What does your project relate to? 120 | # 121 | # Note that this is a string of words separated by whitespace, not a list. 122 | # keywords='sample setuptools development', # Optional 123 | 124 | # When your source code is in a subdirectory under the project root, e.g. 125 | # `src/`, it is necessary to specify the `package_dir` argument. 126 | package_dir={'': 'liteflow'}, # Optional 127 | 128 | # You can just specify package directories manually here if your project is 129 | # simple. Or you can use find_packages(). 130 | # 131 | # Alternatively, if you just want to distribute a single Python file, use 132 | # the `py_modules` argument instead as follows, which will expect a file 133 | # called `my_module.py` to exist: 134 | # 135 | # py_modules=["my_module"], 136 | # 137 | packages=find_packages(where='liteflow'), # Required 138 | 139 | # Specify which Python versions you support. In contrast to the 140 | # 'Programming Language' classifiers above, 'pip install' will check this 141 | # and refuse to install the project if the version does not match. If you 142 | # do not support Python 2, you can simplify this to '>=3.5' or similar, see 143 | # https://packaging.python.org/guides/distributing-packages-using-setuptools/#python-requires 144 | python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4', 145 | 146 | # This field lists other packages that your project depends on to run. 147 | # Any package you put here will be installed by pip when your project is 148 | # installed, so they must be valid existing projects. 149 | # 150 | # For an analysis of "install_requires" vs pip's requirements files see: 151 | # https://packaging.python.org/en/latest/requirements.html 152 | # install_requires=['peppercorn'], # Optional 153 | 154 | # List additional groups of dependencies here (e.g. development 155 | # dependencies). Users will be able to install these using the "extras" 156 | # syntax, for example: 157 | # 158 | # $ pip install sampleproject[dev] 159 | # 160 | # Similar to `install_requires` above, these must be valid existing 161 | # projects. 162 | # extras_require={ # Optional 163 | # 'dev': ['check-manifest'], 164 | # 'test': ['coverage'], 165 | # }, 166 | 167 | # If there are data files included in your packages that need to be 168 | # installed, specify them here. 169 | # 170 | # If using Python 2.6 or earlier, then these have to be included in 171 | # MANIFEST.in as well. 172 | # package_data={ # Optional 173 | # 'sample': ['package_data.dat'], 174 | # }, 175 | 176 | # Although 'package_data' is the preferred approach, in some case you may 177 | # need to place data files outside of your packages. See: 178 | # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files 179 | # 180 | # In this case, 'data_file' will be installed into '/my_data' 181 | # data_files=[('my_data', ['data/data_file'])], # Optional 182 | 183 | # To provide executable scripts, use entry points in preference to the 184 | # "scripts" keyword. Entry points provide cross-platform support and allow 185 | # `pip` to create the appropriate form of executable for the target 186 | # platform. 187 | # 188 | # For example, the following would provide a command called `sample` which 189 | # executes the function `main` from this package when invoked: 190 | # entry_points={ # Optional 191 | # 'console_scripts': [ 192 | # 'sample=sample:main', 193 | # ], 194 | # }, 195 | 196 | # List additional URLs that are relevant to your project as a dict. 197 | # 198 | # This field corresponds to the "Project-URL" metadata fields: 199 | # https://packaging.python.org/specifications/core-metadata/#project-url-multiple-use 200 | # 201 | # Examples listed include a pattern for specifying where the package tracks 202 | # issues, where the source is hosted, where to say thanks to the package 203 | # maintainers, and where to support the project financially. The key is 204 | # what's used to render the link text on PyPI. 205 | # project_urls={ # Optional 206 | # 'Bug Reports': 'https://github.com/pypa/sampleproject/issues', 207 | # 'Funding': 'https://donate.pypi.org', 208 | # 'Say Thanks!': 'http://saythanks.io/to/example', 209 | # 'Source': 'https://github.com/pypa/sampleproject/', 210 | # }, 211 | ) 212 | -------------------------------------------------------------------------------- /script/.gitignore: -------------------------------------------------------------------------------- 1 | *.tflite 2 | *.c 3 | *.cmd 4 | *.o 5 | *.ko 6 | .cache.mk 7 | .tmp_versions/ 8 | Module.symvers 9 | modules.order -------------------------------------------------------------------------------- /script/Makefile: -------------------------------------------------------------------------------- 1 | EXTRA_CFLAGS := -I$(src)/../include 2 | 3 | obj-m += lf_model_1.o lf_model_2.o lf_model_3.o 4 | 5 | all: 6 | make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules 7 | 8 | clean: 9 | make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean 10 | -------------------------------------------------------------------------------- /script/active_standby_switch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | insmod lf_model_1.ko 4 | 5 | wget https://conferences.sigcomm.org/sigcomm/2022/ -O /tmp/sigcomm1.html 6 | 7 | insmod lf_model_2.ko 8 | 9 | wget https://conferences.sigcomm.org/sigcomm/2022/ -O /tmp/sigcomm2.html 10 | 11 | insmod lf_model_3.ko 12 | 13 | wget https://conferences.sigcomm.org/sigcomm/2022/ -O /tmp/sigcomm3.html 14 | 15 | dmesg -------------------------------------------------------------------------------- /script/fetch_data_from_kernel.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:../build/lib 4 | 5 | ../build/test/test_dp_notification 6 | 7 | -------------------------------------------------------------------------------- /script/snapshot_generation.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Quantizing the NN 4 | python3 ../bin/lf_quant.py ../data/aurora ../data/aurora/representative_dataset.npy ./aurora_int_quan_model.tflite 5 | 6 | ## Code transformation 7 | python3 ../bin/lf_generate_snapshot.py ./aurora_int_quan_model.tflite 1 1 ./ 8 | 9 | ## Code transformation - Generate model 2 10 | python3 ../bin/lf_generate_snapshot.py ./aurora_int_quan_model.tflite 2 1 ./ 11 | 12 | ## Code transformation - Generate model 3 13 | python3 ../bin/lf_generate_snapshot.py ./aurora_int_quan_model.tflite 3 1 ./ 14 | 15 | ## Compile into kernel module 16 | make 17 | -------------------------------------------------------------------------------- /test/Makefile: -------------------------------------------------------------------------------- 1 | INCLUDE = ../include 2 | LIB = ../lib 3 | 4 | all: 5 | @cd kernel; make all; 6 | gcc -Werror -O3 -o test_dp_notification -I$(INCLUDE) test_dp_notification.c -L$(LIB) -llf $(shell pkg-config --cflags --libs libnl-3.0 libnl-genl-3.0) 7 | clean: 8 | @cd kernel; make clean; -------------------------------------------------------------------------------- /test/kernel/Makefile: -------------------------------------------------------------------------------- 1 | EXTRA_CFLAGS := -I$(src)/../../include 2 | 3 | obj-m += lf_sample_model.o 4 | lf_sample_model-objs := liteflow_sample_model.o 5 | 6 | all: 7 | make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) modules 8 | 9 | clean: 10 | make -C /lib/modules/$(shell uname -r)/build M=$(shell pwd) clean 11 | -------------------------------------------------------------------------------- /test/kernel/liteflow_sample_model.c: -------------------------------------------------------------------------------- 1 | /****************** main.c for model 2333 generated by nn-loader.py *******************/ 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "linux/liteflow.h" 8 | 9 | 10 | #include "linux/tanh_lookup_table.h" 11 | 12 | 13 | 14 | /************************************** Layer 0 **************************************/ 15 | static void quan_0_comp (s64 *input, s64 *output) 16 | { 17 | // Q_min: 0.0 18 | // Q_max: 62.0 19 | 20 | output[0] = (input[0] - 0) * 255 / 62 + -128; 21 | 22 | printk(KERN_INFO "Output_quan_0_%d: %lld", 0, output[0]); 23 | 24 | 25 | output[1] = (input[1] - 0) * 255 / 62 + -128; 26 | 27 | printk(KERN_INFO "Output_quan_0_%d: %lld", 1, output[1]); 28 | 29 | 30 | output[2] = (input[2] - 0) * 255 / 62 + -128; 31 | 32 | printk(KERN_INFO "Output_quan_0_%d: %lld", 2, output[2]); 33 | 34 | 35 | output[3] = (input[3] - 0) * 255 / 62 + -128; 36 | 37 | printk(KERN_INFO "Output_quan_0_%d: %lld", 3, output[3]); 38 | 39 | 40 | } 41 | 42 | struct model_layer layer_0 __read_mostly = { 43 | .uuid = 0, 44 | .input_size = 4, 45 | .output_size = 4, 46 | .comp_func = quan_0_comp, 47 | }; 48 | /************************************ End Layer 0 ************************************/ 49 | 50 | /************************************** Layer 1 **************************************/ 51 | static void fc_1_comp (s64 *input, s64 *output) 52 | { 53 | 54 | output[0] =(input[0] + 128) * (127 + 0) + (input[1] + 128) * (-83 + 0) + (input[2] + 128) * (16 + 0) + (input[3] + 128) * (-10 + 0) + (-458); 55 | output[0] = ((output[0] * 7361 / 10000) >> -(-8)) + 4; 56 | 57 | printk(KERN_INFO "Output_fc_1_%d: %lld", 0, output[0]); 58 | 59 | 60 | output[1] =(input[0] + 128) * (-97 + 0) + (input[1] + 128) * (58 + 0) + (input[2] + 128) * (82 + 0) + (input[3] + 128) * (4 + 0) + (0); 61 | output[1] = ((output[1] * 7361 / 10000) >> -(-8)) + 4; 62 | 63 | printk(KERN_INFO "Output_fc_1_%d: %lld", 1, output[1]); 64 | 65 | 66 | output[2] =(input[0] + 128) * (63 + 0) + (input[1] + 128) * (-52 + 0) + (input[2] + 128) * (0 + 0) + (input[3] + 128) * (67 + 0) + (0); 67 | output[2] = ((output[2] * 7361 / 10000) >> -(-8)) + 4; 68 | 69 | printk(KERN_INFO "Output_fc_1_%d: %lld", 2, output[2]); 70 | 71 | 72 | output[3] =(input[0] + 128) * (-84 + 0) + (input[1] + 128) * (-8 + 0) + (input[2] + 128) * (0 + 0) + (input[3] + 128) * (-94 + 0) + (0); 73 | output[3] = ((output[3] * 7361 / 10000) >> -(-8)) + 4; 74 | 75 | printk(KERN_INFO "Output_fc_1_%d: %lld", 3, output[3]); 76 | 77 | 78 | output[4] =(input[0] + 128) * (-50 + 0) + (input[1] + 128) * (-100 + 0) + (input[2] + 128) * (-30 + 0) + (input[3] + 128) * (-39 + 0) + (0); 79 | output[4] = ((output[4] * 7361 / 10000) >> -(-8)) + 4; 80 | 81 | printk(KERN_INFO "Output_fc_1_%d: %lld", 4, output[4]); 82 | 83 | 84 | output[5] =(input[0] + 128) * (10 + 0) + (input[1] + 128) * (83 + 0) + (input[2] + 128) * (63 + 0) + (input[3] + 128) * (35 + 0) + (0); 85 | output[5] = ((output[5] * 7361 / 10000) >> -(-8)) + 4; 86 | 87 | printk(KERN_INFO "Output_fc_1_%d: %lld", 5, output[5]); 88 | 89 | 90 | output[6] =(input[0] + 128) * (-13 + 0) + (input[1] + 128) * (14 + 0) + (input[2] + 128) * (61 + 0) + (input[3] + 128) * (-5 + 0) + (0); 91 | output[6] = ((output[6] * 7361 / 10000) >> -(-8)) + 4; 92 | 93 | printk(KERN_INFO "Output_fc_1_%d: %lld", 6, output[6]); 94 | 95 | 96 | output[7] =(input[0] + 128) * (15 + 0) + (input[1] + 128) * (54 + 0) + (input[2] + 128) * (30 + 0) + (input[3] + 128) * (-63 + 0) + (-374); 97 | output[7] = ((output[7] * 7361 / 10000) >> -(-8)) + 4; 98 | 99 | printk(KERN_INFO "Output_fc_1_%d: %lld", 7, output[7]); 100 | 101 | 102 | } 103 | 104 | struct model_layer layer_1 __read_mostly = { 105 | .uuid = 1, 106 | .input_size = 4, 107 | .output_size = 8, 108 | .comp_func = fc_1_comp, 109 | }; 110 | /************************************ End Layer 1 ************************************/ 111 | 112 | /************************************** Layer 2 **************************************/ 113 | static void tanh_2_comp (s64 *input, s64 *output) 114 | { 115 | // Input Scale: 5922 / 10000 116 | // Output Scale: 78 / 10000 117 | // Input Offset: -4 118 | // Output Offset: 0 119 | 120 | output[0] = lf_tanh((input[0] + -4) * TANH_X_SCALE_UP * 5922 / 10000) * 10000 / 78 / TANH_Y_SCALE_DOWN; 121 | 122 | printk(KERN_INFO "Output_tanh_2_%d: %lld", 0, output[0]); 123 | 124 | 125 | output[1] = lf_tanh((input[1] + -4) * TANH_X_SCALE_UP * 5922 / 10000) * 10000 / 78 / TANH_Y_SCALE_DOWN; 126 | 127 | printk(KERN_INFO "Output_tanh_2_%d: %lld", 1, output[1]); 128 | 129 | 130 | output[2] = lf_tanh((input[2] + -4) * TANH_X_SCALE_UP * 5922 / 10000) * 10000 / 78 / TANH_Y_SCALE_DOWN; 131 | 132 | printk(KERN_INFO "Output_tanh_2_%d: %lld", 2, output[2]); 133 | 134 | 135 | output[3] = lf_tanh((input[3] + -4) * TANH_X_SCALE_UP * 5922 / 10000) * 10000 / 78 / TANH_Y_SCALE_DOWN; 136 | 137 | printk(KERN_INFO "Output_tanh_2_%d: %lld", 3, output[3]); 138 | 139 | 140 | output[4] = lf_tanh((input[4] + -4) * TANH_X_SCALE_UP * 5922 / 10000) * 10000 / 78 / TANH_Y_SCALE_DOWN; 141 | 142 | printk(KERN_INFO "Output_tanh_2_%d: %lld", 4, output[4]); 143 | 144 | 145 | output[5] = lf_tanh((input[5] + -4) * TANH_X_SCALE_UP * 5922 / 10000) * 10000 / 78 / TANH_Y_SCALE_DOWN; 146 | 147 | printk(KERN_INFO "Output_tanh_2_%d: %lld", 5, output[5]); 148 | 149 | 150 | output[6] = lf_tanh((input[6] + -4) * TANH_X_SCALE_UP * 5922 / 10000) * 10000 / 78 / TANH_Y_SCALE_DOWN; 151 | 152 | printk(KERN_INFO "Output_tanh_2_%d: %lld", 6, output[6]); 153 | 154 | 155 | output[7] = lf_tanh((input[7] + -4) * TANH_X_SCALE_UP * 5922 / 10000) * 10000 / 78 / TANH_Y_SCALE_DOWN; 156 | 157 | printk(KERN_INFO "Output_tanh_2_%d: %lld", 7, output[7]); 158 | 159 | 160 | } 161 | 162 | struct model_layer layer_2 __read_mostly = { 163 | .uuid = 2, 164 | .input_size = 8, 165 | .output_size = 8, 166 | .comp_func = tanh_2_comp, 167 | }; 168 | /************************************ End Layer 2 ************************************/ 169 | 170 | /************************************** Layer 3 **************************************/ 171 | static void fc_3_comp (s64 *input, s64 *output) 172 | { 173 | 174 | output[0] =(input[0] + 0) * (127 + 0) + (input[1] + 0) * (73 + 0) + (input[2] + 0) * (65 + 0) + (input[3] + 0) * (-54 + 0) + (input[4] + 0) * (-44 + 0) + (input[5] + 0) * (57 + 0) + (input[6] + 0) * (54 + 0) + (input[7] + 0) * (75 + 0) + (7469); 175 | output[0] = ((output[0] * 8388 / 10000) >> -(-8)) + -128; 176 | 177 | printk(KERN_INFO "Output_fc_3_%d: %lld", 0, output[0]); 178 | 179 | 180 | } 181 | 182 | struct model_layer layer_3 __read_mostly = { 183 | .uuid = 3, 184 | .input_size = 8, 185 | .output_size = 1, 186 | .comp_func = fc_3_comp, 187 | }; 188 | /************************************ End Layer 3 ************************************/ 189 | 190 | /************************************** Layer 4 **************************************/ 191 | static void dequan_4_comp (s64 *input, s64 *output) 192 | { 193 | // Q_min: 0.0 194 | // Q_max: 29.982952117919922 195 | 196 | output[0] = (input[0] + 128) * 29 / 255 + 0; 197 | 198 | } 199 | 200 | struct model_layer layer_4 __read_mostly = { 201 | .uuid = 4, 202 | .input_size = 1, 203 | .output_size = 1, 204 | .comp_func = dequan_4_comp, 205 | }; 206 | /************************************ End Layer 4 ************************************/ 207 | 208 | 209 | /************************************** Model **************************************/ 210 | 211 | struct model_container model_2333 __read_mostly = { 212 | .uuid = 2333, 213 | .input_size = 4, 214 | .output_size = 1, 215 | }; 216 | 217 | 218 | /******************************************** Test Mode ********************************************/ 219 | struct app app = { 220 | .appid = 1, 221 | .input_size = 4, 222 | .output_size = 1, 223 | }; 224 | 225 | /**************************************** End Test Mode ********************************************/ 226 | 227 | 228 | static int 229 | __init liteflow_2333_model_init(void) 230 | { 231 | 232 | s64 _input[4]; 233 | s64 _output[1]; 234 | int _output_pos; 235 | 236 | 237 | // Construct layers 238 | INIT_LIST_HEAD(&model_2333.layers); 239 | list_add(&layer_0.list, &model_2333.layers); 240 | list_add(&layer_1.list, &layer_0.list); 241 | list_add(&layer_2.list, &layer_1.list); 242 | list_add(&layer_3.list, &layer_2.list); 243 | list_add(&layer_4.list, &layer_3.list); 244 | 245 | 246 | // Test mode = on 247 | lf_register_app(&app); 248 | 249 | 250 | lf_register_model(1, &model_2333); 251 | 252 | // TODO 253 | _input[0] = 41 ; 254 | _input[1] = 50 ; 255 | _input[2] = 60 ; 256 | _input[3] = 60 ; 257 | 258 | lf_query_model(1, _input, _output); 259 | 260 | for (_output_pos = 0; _output_pos < 1; ++_output_pos) { 261 | printk(KERN_INFO "Output_%d: %lld\n", _output_pos, _output[_output_pos]); 262 | } 263 | 264 | 265 | return 0; 266 | } 267 | 268 | static void 269 | __exit liteflow_2333_model_exit(void) 270 | { 271 | lf_unregister_model(1, 2333); 272 | 273 | 274 | // Test mode = on 275 | lf_unregister_app(1); 276 | 277 | } 278 | 279 | module_init(liteflow_2333_model_init); 280 | module_exit(liteflow_2333_model_exit); 281 | 282 | MODULE_DESCRIPTION("liteflow 2333 model"); 283 | MODULE_AUTHOR("liteflow"); 284 | MODULE_LICENSE("GPL v2"); 285 | 286 | /************************************ End Model ************************************/ -------------------------------------------------------------------------------- /test/python/train_simple_model.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | import numpy as np 3 | import os 4 | 5 | def loss_func(predicted_y, target_y): 6 | return tf.reduce_mean(tf.square(predicted_y - target_y)) 7 | 8 | dataset_x = np.array([ 9 | [10, 20, 30, 40], 10 | [21, 32, 41, 51], 11 | [33, 43, 53, 62], 12 | [41, 50, 60, 60], 13 | [20, 29, 39, 53], 14 | # [11, 31, 21, 41], 15 | ]).astype(np.float32) 16 | 17 | os.system("mkdir -p /tmp/simple_model/") 18 | 19 | np.save("/tmp/simple_model/r_dataset", dataset_x) 20 | 21 | dataset_y = np.array([10.0, 20.0, 30.0, 30.0, 20.0]) 22 | 23 | model = tf.keras.models.Sequential([ 24 | tf.keras.layers.Input(shape=(4,)), 25 | tf.keras.layers.Dense(8, activation='tanh'), 26 | tf.keras.layers.Dense(1) 27 | ]) 28 | 29 | model.compile(optimizer='adam', 30 | loss=loss_func, 31 | metrics=['accuracy']) 32 | 33 | model.fit(dataset_x, dataset_y, epochs=10000) 34 | 35 | model.summary() 36 | 37 | tf.saved_model.save(model, "/tmp/simple_model") 38 | 39 | predictions = model(dataset_x[:1]).numpy() 40 | print(f"Predication: {predictions}") 41 | -------------------------------------------------------------------------------- /test/test_dp_notification.c: -------------------------------------------------------------------------------- 1 | #include "liblf.h" 2 | 3 | bool test_rx (__s64 *nn, __u32 size) 4 | { 5 | int pos = 0; 6 | 7 | for (pos = 0; pos < size; ++pos) { 8 | fprintf(stdout, "%lld\t", nn[pos]); 9 | } 10 | 11 | fprintf(stdout, "\n"); 12 | 13 | return false; 14 | } 15 | 16 | int main(int argc, char** argv) 17 | { 18 | rx_dp_notification(&test_rx); 19 | return 0; 20 | } -------------------------------------------------------------------------------- /util/tflite-visualize.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Copyright 2017 The TensorFlow Authors. All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # ============================================================================== 16 | """This tool creates an html visualization of a TensorFlow Lite graph. 17 | 18 | Example usage: 19 | 20 | python visualize.py foo.tflite foo.html 21 | """ 22 | 23 | from __future__ import absolute_import 24 | from __future__ import division 25 | from __future__ import print_function 26 | 27 | import json 28 | import os 29 | import sys 30 | 31 | from tensorflow.python.platform import resource_loader 32 | 33 | # Schema to use for flatbuffers 34 | _SCHEMA = "third_party/tensorflow/tensorflow/lite/schema/schema.fbs" 35 | 36 | # TODO(angerson): fix later when rules are simplified.. 37 | # _SCHEMA = resource_loader.get_path_to_datafile("../schema/schema.fbs") 38 | _BINARY = "third_party/flatbuffers/flatc" 39 | # _BINARY = resource_loader.get_path_to_datafile("../../../flatbuffers/flatc") 40 | # Account for different package positioning internal vs. external. 41 | # if not os.path.exists(_BINARY): 42 | # _BINARY = resource_loader.get_path_to_datafile( 43 | # "../../../../flatbuffers/flatc") 44 | 45 | # if not os.path.exists(_SCHEMA): 46 | # raise RuntimeError("Sorry, schema file cannot be found at %r" % _SCHEMA) 47 | # if not os.path.exists(_BINARY): 48 | # raise RuntimeError("Sorry, flatc is not available at %r" % _BINARY) 49 | 50 | 51 | # A CSS description for making the visualizer 52 | _CSS = """ 53 | 54 | 55 | 109 | 110 | 111 | 112 | 113 | 114 | """ 115 | 116 | _D3_HTML_TEMPLATE = """ 117 | 215 | """ 216 | 217 | 218 | class OpCodeMapper(object): 219 | """Maps an opcode index to an op name.""" 220 | 221 | def __init__(self, data): 222 | self.code_to_name = {} 223 | for idx, d in enumerate(data["operator_codes"]): 224 | self.code_to_name[idx] = d["builtin_code"] 225 | 226 | def __call__(self, x): 227 | if x not in self.code_to_name: 228 | s = "" 229 | else: 230 | s = self.code_to_name[x] 231 | return "%s (%d)" % (s, x) 232 | 233 | 234 | class DataSizeMapper(object): 235 | """For buffers, report the number of bytes.""" 236 | 237 | def __call__(self, x): 238 | if x is not None: 239 | return "%d bytes" % len(x) 240 | else: 241 | return "--" 242 | 243 | 244 | class TensorMapper(object): 245 | """Maps a list of tensor indices to a tooltip hoverable indicator of more.""" 246 | 247 | def __init__(self, subgraph_data): 248 | self.data = subgraph_data 249 | 250 | def __call__(self, x): 251 | html = "" 252 | html += "" 253 | for i in x: 254 | tensor = self.data["tensors"][i] 255 | html += str(i) + " " 256 | html += tensor["name"] + " " 257 | html += str(tensor["type"]) + " " 258 | html += (repr(tensor["shape"]) if "shape" in tensor else "[]") + "
" 259 | html += "
" 260 | html += repr(x) 261 | html += "
" 262 | return html 263 | 264 | 265 | def GenerateGraph(subgraph_idx, g, opcode_mapper): 266 | """Produces the HTML required to have a d3 visualization of the dag.""" 267 | 268 | def TensorName(idx): 269 | return "t%d" % idx 270 | 271 | def OpName(idx): 272 | return "o%d" % idx 273 | 274 | edges = [] 275 | nodes = [] 276 | first = {} 277 | second = {} 278 | pixel_mult = 200 # TODO(aselle): multiplier for initial placement 279 | width_mult = 170 # TODO(aselle): multiplier for initial placement 280 | for op_index, op in enumerate(g["operators"]): 281 | 282 | for tensor_input_position, tensor_index in enumerate(op["inputs"]): 283 | if tensor_index not in first: 284 | first[tensor_index] = ( 285 | (op_index - 0.5 + 1) * pixel_mult, 286 | (tensor_input_position + 1) * width_mult) 287 | edges.append({ 288 | "source": TensorName(tensor_index), 289 | "target": OpName(op_index) 290 | }) 291 | for tensor_output_position, tensor_index in enumerate(op["outputs"]): 292 | if tensor_index not in second: 293 | second[tensor_index] = ( 294 | (op_index + 0.5 + 1) * pixel_mult, 295 | (tensor_output_position + 1) * width_mult) 296 | edges.append({ 297 | "target": TensorName(tensor_index), 298 | "source": OpName(op_index) 299 | }) 300 | 301 | nodes.append({ 302 | "id": OpName(op_index), 303 | "name": opcode_mapper(op["opcode_index"]), 304 | "group": 2, 305 | "x": pixel_mult, 306 | "y": (op_index + 1) * pixel_mult 307 | }) 308 | for tensor_index, tensor in enumerate(g["tensors"]): 309 | initial_y = ( 310 | first[tensor_index] if tensor_index in first 311 | else second[tensor_index] if tensor_index in second 312 | else (0, 0)) 313 | 314 | nodes.append({ 315 | "id": TensorName(tensor_index), 316 | "name": "%r (%d)" % (getattr(tensor, "shape", []), tensor_index), 317 | "group": 1, 318 | "x": initial_y[1], 319 | "y": initial_y[0] 320 | }) 321 | graph_str = json.dumps({"nodes": nodes, "edges": edges}) 322 | 323 | html = _D3_HTML_TEMPLATE % (graph_str, subgraph_idx) 324 | return html 325 | 326 | 327 | def GenerateTableHtml(items, keys_to_print, display_index=True): 328 | """Given a list of object values and keys to print, make an HTML table. 329 | 330 | Args: 331 | items: Items to print an array of dicts. 332 | keys_to_print: (key, display_fn). `key` is a key in the object. i.e. 333 | items[0][key] should exist. display_fn is the mapping function on display. 334 | i.e. the displayed html cell will have the string returned by 335 | `mapping_fn(items[0][key])`. 336 | display_index: add a column which is the index of each row in `items`. 337 | Returns: 338 | An html table. 339 | """ 340 | html = "" 341 | # Print the list of items 342 | html += "\n" 343 | html += "\n" 344 | if display_index: 345 | html += "" 346 | for h, mapper in keys_to_print: 347 | html += "" % h 348 | html += "\n" 349 | for idx, tensor in enumerate(items): 350 | html += "\n" 351 | if display_index: 352 | html += "" % idx 353 | # print tensor.keys() 354 | for h, mapper in keys_to_print: 355 | val = tensor[h] if h in tensor else None 356 | val = val if mapper is None else mapper(val) 357 | html += "\n" % val 358 | 359 | html += "\n" 360 | html += "
index%s
%d%s
\n" 361 | return html 362 | 363 | 364 | def CreateHtmlFile(tflite_input, html_output): 365 | """Given a tflite model in `tflite_input` file, produce html description.""" 366 | 367 | # Convert the model into a JSON flatbuffer using flatc (build if doesn't 368 | # exist. 369 | if not os.path.exists(tflite_input): 370 | raise RuntimeError("Invalid filename %r" % tflite_input) 371 | if tflite_input.endswith(".tflite") or tflite_input.endswith(".bin"): 372 | 373 | # Run convert 374 | cmd = ( 375 | _BINARY + " -t " 376 | "--strict-json --defaults-json -o /tmp {schema} -- {input}".format( 377 | input=tflite_input, schema=_SCHEMA)) 378 | print(cmd) 379 | os.system(cmd) 380 | real_output = ("/tmp/" + os.path.splitext( 381 | os.path.split(tflite_input)[-1])[0] + ".json") 382 | 383 | data = json.load(open(real_output)) 384 | elif tflite_input.endswith(".json"): 385 | data = json.load(open(tflite_input)) 386 | else: 387 | raise RuntimeError("Input file was not .tflite or .json") 388 | html = "" 389 | html += _CSS 390 | html += "

TensorFlow Lite Model

" 391 | 392 | data["filename"] = tflite_input # Avoid special case 393 | toplevel_stuff = [("filename", None), ("version", None), ("description", 394 | None)] 395 | 396 | html += "\n" 397 | for key, mapping in toplevel_stuff: 398 | if not mapping: 399 | mapping = lambda x: x 400 | html += "\n" % (key, mapping(data.get(key))) 401 | html += "
%s%s
\n" 402 | 403 | # Spec on what keys to display 404 | buffer_keys_to_display = [("data", DataSizeMapper())] 405 | operator_keys_to_display = [("builtin_code", None), ("custom_code", None), 406 | ("version", None)] 407 | 408 | for subgraph_idx, g in enumerate(data["subgraphs"]): 409 | # Subgraph local specs on what to display 410 | html += "
" 411 | tensor_mapper = TensorMapper(g) 412 | opcode_mapper = OpCodeMapper(data) 413 | op_keys_to_display = [("inputs", tensor_mapper), ("outputs", tensor_mapper), 414 | ("builtin_options", None), ("opcode_index", 415 | opcode_mapper)] 416 | tensor_keys_to_display = [("name", None), ("type", None), ("shape", None), 417 | ("buffer", None), ("quantization", None)] 418 | 419 | html += "

Subgraph %d

\n" % subgraph_idx 420 | 421 | # Inputs and outputs. 422 | html += "

Inputs/Outputs

\n" 423 | html += GenerateTableHtml( 424 | [{ 425 | "inputs": g["inputs"], 426 | "outputs": g["outputs"] 427 | }], [("inputs", tensor_mapper), ("outputs", tensor_mapper)], 428 | display_index=False) 429 | 430 | # Print the tensors. 431 | html += "

Tensors

\n" 432 | html += GenerateTableHtml(g["tensors"], tensor_keys_to_display) 433 | 434 | # Print the ops. 435 | html += "

Ops

\n" 436 | html += GenerateTableHtml(g["operators"], op_keys_to_display) 437 | 438 | # Visual graph. 439 | html += "\n" % ( 440 | subgraph_idx,) 441 | html += GenerateGraph(subgraph_idx, g, opcode_mapper) 442 | html += "
" 443 | 444 | # Buffers have no data, but maybe in the future they will 445 | html += "

Buffers

\n" 446 | html += GenerateTableHtml(data["buffers"], buffer_keys_to_display) 447 | 448 | # Operator codes 449 | html += "

Operator Codes

\n" 450 | html += GenerateTableHtml(data["operator_codes"], operator_keys_to_display) 451 | 452 | html += "\n" 453 | 454 | open(html_output, "w").write(html) 455 | 456 | 457 | def main(argv): 458 | try: 459 | tflite_input = argv[1] 460 | html_output = argv[2] 461 | except IndexError: 462 | print("Usage: %s " % (argv[0])) 463 | else: 464 | CreateHtmlFile(tflite_input, html_output) 465 | 466 | 467 | if __name__ == "__main__": 468 | main(sys.argv) --------------------------------------------------------------------------------