How to use your C++ muscle using TensorFlow 2.0 and XCode (without using Bazel builds)

Source: Deep Learning on Medium


This post will describe how to code in C++ using TensorFlow, on a Mac, while bypassing Bazel builds, which I find too cumbersome to use.

First, a confession:

I studied Computer Science in the 90’s. As a result, my programming mind is heavily influenced by C++, the language I like and prefer. When everyone began coding in Java, I had already moved into management positions, so I did not go through the process of moving to what most people call 3rd generation languages (which refers to having a framework built inside). I like the aesthetics of C++, and with its recent developments (mainly incorporating STD as part of the language) I think it also overcame the disadvantages of pointer related bugs which used to result in crashes and memory leaks.

According to a recent survey by Stack Overflow, Python is the fastest growing major programing language. It is not at the top of the list, but over 40% of developers are using it. C++ is being used by 23.5%. Maybe I’m old-fashioned. There, I said it.

When I became interested in Machine Learning, I had to learn Python. This is a must, as most frameworks do not support any other language, making Python the de-facto language of the domain. Python is nice, don’t get me wrong. It also contains some concepts from C++ and its internals are also written in C++ (AFAIK). However white spaces and indentations are something that are difficult for me to wrap my head around. So when I heard that TensorFlow 2.0 will be including some major improvements in the C++ APIs, I had to check it out.

I use a Mac, and I guess some of you do as well. This is still not mainstream, but it makes it easier to bridge the gap between Windows and Linux.

Doing without Bazel

This post will explain how to get started with TensorFlow 2.0 Alpha, on a Mac with a standalone project that you can use XCode for, without using a Bazel builds in terminal.

Most of the instructions you can find on the web deal with Bazel builds. I find Bazel a little bit cumbersome and less intuitive, so I will show you how to start with Bazel to build the basic framework and then how to move on into developing your C++ code in XCode without needing a Terminal for every step in the way.

Let’s start!

To be able to get there, you will need to do the following:

1. Install some prerequisites

2. Build TensorFlow 2.0 Alpha from source

3. Build some other dependencies

4. Create an XCode project and configure it

5. Compile a test project to validate everything is installed properly

Some instructions here are taken from the TensorFlow install guide page.

Requirements to begin with:

1. XCode 9.2 and above (I have 10.2)

2. Homebrew

3. Python 2 or 3 (I have 3.6.5)

4. Pip (if you do not have pip, run “sudo easy_install pip”)

Prerequisites

Open a Terminal window and start installing:

pip install -U --user pip six numpy wheel setuptools mock
pip install -U --user keras_applications==1.0.6 --no-deps
pip install -U --user keras_preprocessing==1.0.5 --no-deps

Installing Bazel:

Follow the instructions here, but if you use a MacOS — continue with this:

Install XCode command-line tools:

1. Go to https://developer.apple.com/download/, scroll down and click See more downloads.

2. In the small search box on the left, write “command line” and hit enter.

3. Select the appropriate XCode version and click the + sign.

4. Click the link to download the dmg file and install it.

Accept the license agreement

That’s a tricky part. The instructions say you only need to run:

sudo xcodebuild -license accept

but this did not work for me. After looking around, this is the safest sequence that will help you pass this part:

sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -license

Now type your local password, the license agreement should be displayed.

Scroll down using space all the way down (read it, if you like) and at the end type “agree” and hit enter.

Install bazel

1. Go to this URL and search for the relevant OS you have. For MacOS this should be bazel-0.24.1-installer-darwin-x86_64.sh (or a newer version which means 0.24.1 will be different)

2. Download this file to your home directory and open terminal

3. Run the two following commands (assuming you are the home directory and the sh file is there):

chmod +x bazel-0.24.1-installer-darwin-x86_64.sh
./bazel-0.24.1-installer-darwin-x86_64.sh — user

(again, if you have a different version change the commands)

4. Check that bazel is working:

bazel version

If it is not, check your path, it might be missing $(HOME)/bin

Install a few more packages:

Brew install autoconf automake libtool cmake

Download and install TensorFlow

First create a folder where you want to keep all the code. I will show you how to separate your project from the framework, so select a structure that will not confuse you.

I have a main folder under my home that I named “Code” and under it I have my projects. I created a new folder under that, that is called Tensorflow2. There I have the framework code.

Download the framework code

1. Open terminal and cd to that directory

2. Download the source files:

git clone https://github.com/tensorflow/tensorflow.git
cd tensorflow

3. This will get you code from Master. If you want a release branch (which at this time is not a release yet) you can check it out. Example:

git checkout v2.0.0-alpha0

Configure

1. At the same place in terminal (under the tensorflow directory — this is your root folder) run

./configure

2. Now comes the part where the script asks you questions, and you need to answer

3. It will tell you if it found bazel and what version, and will ask for the python location.

4. The easiest way to find it is to open a new Terminal windows and to type

which python3

(replace python3 with whatever you use to run python, run “python3 –version” to see what version it is)

5. Then it will ask you a bunch of other questions about special support for various features. I selected the default for all apart from the one.

6. That one is about CPU features. It will ask you for “optimization flags to use during compilation” with bazel. This is important for two reasons:

a. It will make your models run faster

b. It will eliminate some warnings that will be displayed every time you run your code.

7. I found out about this only after being able to run my project, so I had to go back and run configure again and type “-mavx -mavx2 -mfma -msse4.2” when prompted for these flags. Not sure how you can find that out up front, try to look here or here. So I recommend that at first leave it as default and if you get the runtime warnings, go back and do the configuration again, and compile the framework again.

8. Finish the configuration process and if you have a GPU — read this.

Compile the framework

Finally, we get to the point where we can start building things. Let’s build the framework libraries that we will link into the project later.

We need to build the C++ API and the framework.

Important: the next two commands will take several hours to finish. It will maximize your CPU usage and drain your battery. Make sure to be hooked up and take a long break.

At the root folder run:

bazel build -c opt — verbose_failures //tensorflow:libtensorflow_cc.so

Next compile the framework:

bazel build -c opt — verbose_failures //tensorflow:libtensorflow_framework.so

If these two succeed with not errors (there are many warnings, that’s normal), you will find the two libraries in the folder (root folder)/bazel-bin/tensorflow/. We will need these to link with our project.

If you remember, we configured first for CPU features and that should have made this build optimized for your personal case. However, if you get the CPU features error such as in the troubleshooting section below, try and re-run the framework build again with the command-line bazel options that matches the flags you need. In my case I should run:

bazel build -c opt — copt=-mavx — copt=-mavx2 — copt=-mfma — copt=-msse4.2 — verbose_failures //tensorflow:libtensorflow_framework.so

Get and build dependencies

We need a few more things to download before we create our project.

Run this from the root folder:

tensorflow/contrib/makefile/download_dependencies.sh

It will download files into tensorflow/contrib/makefile/downloads/

Build protobufs

Run the following:

cd tensorflow/contrib/makefile/downloads/protobuf/
./autogen.sh
./configure
make
make install

Create an XCode project

Now open XCode and create a C++ project. I create mine in the Code folder under my home.

I called my project “TF2example”. I will have a folder under home:

~/Code/TF2example/TF2example/

(the main.cpp file will be located there).

Now create two subfolders under that directory: include and lib.

We want to copy headers to include and libraries to lib.

My folders are built like this:

~/Code/
 Tensorflow2/tensorflow/
 tensorflow/
 bazel-bin
TF2example/TF2example/
 include/
 lib/

Let’s copy library files (you may need to sudo before the cp commands):

cd ~/Code
cp Tensorflow2/tensorflow/bazel-bin/tensorflow/libtensorflow_cc.so TF2example/TF2example/lib/
cp Tensorflow2/tensorflow/bazel-bin/tensorflow/libtensorflow_framework.so TF2example/TF2example/lib/
cp /usr/local/lib/libprotobuf.a TF2example/TF2example/lib/

Now copy the headers:

cp -r Tensorflow2/tensorflow/bazel-genfiles/* TF2example/TF2example/include
cp -r Tensorflow2/tensorflow/tensorflow/cc TF2example/TF2example/include/tensorflow
cp -r Tensorflow2/tensorflow/tensorflow/core TF2example/TF2example/include/tensorflow
cp -r Tensorflow2/tensorflow/third_party TF2example/TF2example/include
cp -r /usr/local/include/google TF2example/TF2example/include
cp -r Tensorflow2/tensorflow/tensorflow/contrib/makefile/downloads/absl/absl TF2example/TF2example/include
cp -r Tensorflow2/tensorflow/bazel-tensorflow/external/eigen_archive/unsupported TF2example/TF2example/include
cp -r Tensorflow2/tensorflow/bazel-tensorflow/external/eigen_archive/Eigen TF2example/TF2example/include

Test project code

First let’s copy the sample code that came with tensorflow 2.0:

cp Tensorflow2/tensorflow/tensorflow/cc/tutorials/example_trainer.cc TF2example/TF2example/

Now go back to XCode and replace your main.cpp with the example_trainer.cc we just copied.

Test project configuration

In XCode select the project name on the project navigator on the left. The main screen will show the project settings. Make sure you select the project and not the target.

Select the Build settings tab and scroll to the “Search Paths”.

In “Header Search Paths” add $(PROJECT_DIR)/TF2example/include (change if you have a different name).

If you do not see that item, you are probably on “Basic” view. Change to “All”.

In “Library Search Paths” add $(PROJECT_DIR)/TF2example/lib

Under “Linking” search for “Runpath Search Paths” and add $(PROJECT_DIR)/TF2example/lib

Go to “Build Phases” tab (switch to Targets if needed depending on your view) and open the “Link Binary With Libraries” section. Add the two .so files we copied earlier (libtensorflow_cc.so and libtensorflow_framework.so).

Note that you need to click the “Add Other…” button, and select the files under lib.

Now try to build the project and run it.

If everything worked out fine, you should see in the output window a bunch of x and y values which were written from multiple threads created by the sample program.

If you have compilation errors, link errors or runtime errors, try and look for help in the below troubleshooting section or comment on the article, so I can see if I can help.

Troubleshooting

Apple crosstool

If you get an error about “Apple CROSSTOOL” that means you did not successfully accept the license agreement for the command-line tools.

At the root folder run this:

bazel clean — expunge
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -license
bazel clean –expunge

and run the bazel build again.

CPU features

When running your project, you get errors like “The TensorFlow library wasn’t

compiled to use SSE4.2 instructions, but these are available on your machine and

could speed up CPU computations.”

This means you need to run configure again and set the proper flags that you see in the message. I added “-mavx -mavx2 -mfma -msse4.2”.

Documentation showing you can also add that to the bazel build command, but this did not do the trick for me, I had to do the configure again and re-build the framework .so file.

Note also that in case you select the wrong flags, you may get a runtime error which will not indicate that it is related to the CPU features. It is better to be safe than sorry.

Dynamic Lib loader

If at runtime you get a “dyld library not loaded” error, check that you set the Runpath search path correctly and in case you did, you may need to use the install_name_tool command to make it work.