Install tensorflow for python3 on OSX from source: steps and explanations

If you are interested in machine learning, you are probably interested in tensorflow, the open-source library for distributed machine learning computation (pre-equipped with many useful tools for building Deep Neural Networks of many kinds).

If you are interested in machine learning and you like datasets that won’t fit in your memory at once, you want to use generators and iterators. That is, you want what python3 makes the default, so you want to build it in python3. Also, print is happier as a function.

If you wanted to get tensorflow working with python3 as of my installing it on February 9, 2016, you needed to build it from source. As of February 13, 2016, you can use

pip3 install --upgrade https://storage.googleapis.com/tensorflow/mac/tensorflow-0.6.0-py3-none-any.whl

or see the git repo. But if you want to install from source, or even to just understand the steps needed to install it from source, this post should still be useful.

But installing from source requires jumping through some hoops. These hoops aren’t too difficult, but they are spread across a lot of places. If you stopped in the middle of the process and forgot where you were, this might prove to be a problem. Also, many of the individual steps are more interesting to understand than they are difficult to follow.

I tried to bring these steps together here. More importantly, I attempted to explain them so that, rather than trying to blindly follow my directions (which you are welcome to attempt as well, though I don’t ever recommend it), you can comprehend what each step is doing.

First, I’ll describe the steps you’ll go through. Then I’ll describe why you’re performing these steps, but I’ll do that in reverse. That is, I’ll explain the steps by unpacking the dependencies starting with the testing tensorflow after the final pip3 call and working back to the beginning with Xcode Developer Tools. So, if you read it straight through, when you get to the end of the list of steps, that’s when the real fun begins.

Assumptions

What I’m going to assume is that you are familiar with your terminal (possibly even iTerm2) and that you have python3.3+ (ideally python3.5) and homebrew (brew) installed already. If you do not, or don’t know what I’m talking about, I would recommend reading the introduction to an earlier post which covers this material in its preliminaries section. Because machine learning tends to require a bit of a programming background I’m going to assume a bit more than I did in that post. If it’s at all confusing, feel free to leave a comment, and I’ll do my best to help.

General Procedure:

  1. Install OSX Developer tools(xcode) from the developer website or from the Mac App Store (explanation)
    1. Check that you have installed it by running gcc --version: (explanation)
  2. Install Java SE Development Kit 8 (JDK8) from the Oracle website (explanation)

  3. Go to your terminal (explanation)

  4. Install Bazel — ideally using brew install bazel (explanation)
    1. If it says that it can’t find bazel, run brew update
    2. If you already have it installed, use brew upgrade bazel
  5. Install swig — ideally using brew install swig (explanation)
    1. If you already have it installed, use brew upgrade swig
  6. Install/upgrade six, numpy, wheel, ipython3 using pip3(explanation):

    pip3 install -U six
    pip3 install -U numpy
    pip3 install -U wheel
    pip3 install -U ipython3

  7. Clone the github repo for tensorflow using the --recurse-submodules flag (explanation):
    1. If you don’t use the --recurse-submodules flag, we can still recover later by using: git submodule update --init. But save yourself the unnecessary pain.
      git clone --recurse-submodules https://github.com/tensorflow/tensorflow
      
  8. Go to the root of your github directory (cd tensorflow if you have not changed directory since the previous step) (explanation)

  9. Run which python3 to get your python3 path, copy it (explanation)

  10. Run ./configure, which will ask you for the path to your python3 instance, paste in the path from the previous step (explanation)

  11. Build the package (Part 1) with bazel (explanation)

    bazel build -c opt //tensorflow/tools/pip_package:build_pip_package

  12. Finish building your pip-able package as a wheel (explanation):

    bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg

  13. Install your just built package with pip3 (explanation):
    1. The version of tensorflow that you should install here may change if you have a more recent version. Change the ‽.‽.‽ tensorflow-‽.‽.‽-py3 to match the version of tensorflow you have.
      pip3 install /tmp/tensorflow_pkg/tensorflow-0.6.0-py3-none-any.whl
  14. Test tensorflow to see that it works.

Open ipython3 on the command line, type in

import tensorflow as tf  
fun = tf.constant("I built tensorflow from source, I am unstoppable!")  
sess = tf.session()  
print(sess.run(fun))

Explaining the stack, LIFO[1]

Testing tensorflow

By the time that you’ve gotten to this point, tensorflow should be functioning for you. If it isn’t you have some problem and you’ll need to debug what has gone wrong. The error printed may or may not be helpful. However, copying and pasting that error into google will help.

pip your wheel

This step takes your pip compatible package and installs it for you. You need to make sure that you pass in the correct path, but if you’ve done the previous steps this should work.

The reason you begin this install file with tmp/tensorflow.pkg is because that’s where you told the previous step to build your pip-able package.

It ends with .whl because it installs it as a wheel. What is a wheel you ask? That is a great question — one I will leave for another day [2]. For now all you need to know is that it is a format for distributing file packages that is more modern than the older egg format.

Completing your pip-able package

This is going to create a pip-able package in the argument’s path.

The steps it takes to do this are straightforward enough, but the key is that the directory you pass it is stored, a temporary directory is made, the wheel is built into the temp directory and then copied to the directory you passed it, and the temporary directory is passed back out.[3]

This will work if you ran this command from the root of the git repo, if you did not, it probably will fail quickly with a helpful error message reminding you of this (and if not, it will possibly fail spectacularly). I mention this because it’s worth appreciating nice error messages when you come across them, even if they weren’t actually useful for you.

Compiling the package with bazel

This is the command that was least familiar to me (having not used bazel before). But it’s actually pretty easily comprehensible once you understand a couple of bazel ideas.

bazel’s build command

bazel is Google’s build tool. The primary thing that we need to know about it is that it is invoked with the bazel build command to build packages. In its simplest form, the command bazel build takes a label (e.g., //my_package:my_target) as its argument and builds the appropriate package and target (see below for explanation of packages, labels and targets).

The -c flag indicates that it will be executed in “compilation mode” and the argument that follows indicates details about the compilation that affect how C or C++ code is compiled. In this case, it is passed with the opt argument meaning that the package will be built with optimization enabled, debugging info disabled and assert() calls disabled.

Packages, Targets and Labels in bazel

A package is a collection of files along with a specification of their dependencies; it is designated by the existence of a file named BUILD. Packages contain all the files in it and its subdirectories, unless that subdirectory is itself a package (called a subpackage, indicated by its also having a BUILD file). The elements of a package are targets.

A target is an element of a package which include the “files” that will be used in the build procedure, “rules” that (ultimately) define how those files are used in the procedure, and “package_groups” that limit the accessibility of certain rules.

A label is the name for a target. In its canonical form, it leads with a //, followed by a package name (my_package), followed by a colon (:), and a target name (my_target). So, //my_package:my_target is a label, but only my_package is a package.

./configure and which python3

This is a bash script that (for our purposes) merely asks us to input a path to the correct version of our python3 executable. This path is what is returned by which python3. It can do other things if you are running it on Linux, but we aren’t so we don’t need to worry about that for now.

cd tensorflow

This command moves us into the tensorflow directory that we had just cloned. This is where we need to start for other commands to work properly.

git clone --recurse-submodules

Because the build system relies on protobuf repo, which implements Google’s protocol buffer data interchange format. By including --recurse-submodules it will include the protobuf repo in your cloned repo. None of the other steps will work if you don’t do this. I learned this the hard way because I first forked the Google repo on github, cloned my own, and then added a remote to the Google upstream repo.

One of the side effects of my having not done this properly was that I learned that another way to effect this state of including sub-repos retroactively is to run the command git submodule update --init. Thank you to this helpful comment.

Install python packages

To be honest, I expect that you already have these packages installed if you are attempting to do any kind of numerical computation on python3. six is a package for providing python2 and python3 compatibility. numpy is a package for efficient in-memory numerical computation, particularly over n-dimensional arrays(ndarrays). wheel is a package for distributing wheels the distribution format I alluded to above. ipython3 is the interactive python interpreter for python3; you should be using it instead of python3 if you are going interactively running code in your terminal (that said, if you want interactive computation you should really investigate Jupyter and the Jupyter notebook).

swig

swig is a tool for connecting programs written in C and C++ to high-level programming languages.

Why bazel?

Above I describe bazel in greater detail. But I thought it was worth noting that the reason we are installing bazel is because that is the build tool that we’re going to be using to compile and build the libraries and packages needed to get tensorflow running.

Terminal

On some later date, I want to go into the history of this program/device/interface. But for now, let it suffice to say that the terminal is the primary way that you can interact with the command line interface (often using bash, the “Bourne again shell” or some other shell) on Mac OSX.

Java SDK

Oracle’s Java standard edition software development kit 8 (JDK8) is needed to install bazel, which in turn is needed to install tensorflow. You will need to agree to their licensing agreement to download it.

xcode: OSX’s developer tools

We have finally reached the top of the stack, where we find the basic tools needed by a developer on OSX. You can install these either from the Mac App store or from their developer website. You probably want these if you’re going to be developing on OSX, so you should go and download them and install them immediately, even if you follow no other steps in this tutorial. They will be needed at some point.

For our purposes, you need the developer tools in order to install bazel.

Why gcc --version?

By default gcc or the gnu C compiler, is not available on OSX. The easiest way to ensure that you have it is to install xtools, and thus by attempting to invoke it, this is a good way to either check that you have xcode installed or to initiate the licensing agreement once you do have it installed.

By calling this command with the flag --version we can invoke the command and trigger this process without needing to point to any particular file.

Conclusion

So now you should be able to run the example I gave. Hopefully (and this is the real purpose of this blog post), you should know a lot more about the entire process needed to get from having none of this to this final step. Now, go play and make some machines learn; you’ve made the machine of your mind work hard enough to get this far. Congrats, good luck and cheers.[4]


  1. LIFO → Last In, First Out.  ↩

  2. If you want to know more you can check the wheel documentation.  ↩

  3. If you want more detail. First it saves the first(and only) argument to a variable (DEST=$1). Then it proceeds by making a temporary directory (TMPDIR=$(mktemp -d -t tmp.XXXXXXXXXX)), copying the relevant files from

    bazel-bin/tensorflow/tools/pip_package/build_pip_package.runfiles
    to the temporary directory along with a few other things, adding the temporary directory to the directory stack using pushd (making the temporary directory the effective current directory), running python3 setup.py bdist-wheel >/dev/null to build the wheel using setuptools, copying the relevant distribution files from the temporary directory into your argument’s indicated directory (cp dist/* ${DEST}), popping the directory stack (popd) to return to the original directory at the root of the repo, and removes the temporary directory. The only further complication is that this is accomplished first by defining the main function and then executing it at the end of the file, passing it the arguments as usual with the line main "$@".  ↩

  4. Yes, the Oxford comma was purposefully omitted for the sake of the nice ambiguity. Well done for noticing. You get an extra ★.  ↩