Skip to content

Instantly share code, notes, and snippets.

@choongng
Last active December 29, 2019 23:01
Show Gist options
  • Save choongng/8999ac7fed85fb22b7b2204e36ead727 to your computer and use it in GitHub Desktop.
Save choongng/8999ac7fed85fb22b7b2204e36ead727 to your computer and use it in GitHub Desktop.
Swift for TensorFlow quick start with Docker on Mac

A good way to get a taste of Swift for Tensorflow language and tools is to set it up with Jupyter with the fastai Swift notebooks. I wanted a quick setup, which the Mac install experience currently not, so instead I installed the release binaries in a Ubuntu container via Docker. The setup process for this scenario is not well documented, so here it is for you / future me.

What we're about to do is install the S4TF 0.4 release and the fastai v3 Swift notebooks on Ubuntu 18.04. Generally we follow the swift-jupyter docker file, but install cpu-only release versions of the packages.

Below are some of the references I looked at:

Rationale for S4TF and background reading

https://github.com/tensorflow/swift/blob/master/docs/WhySwiftForTensorFlow.md https://github.com/tensorflow/swift/blob/master/docs/DifferentiableFunctions.md https://github.com/tensorflow/swift/blob/master/docs/PythonInteroperability.md

Google's swift-jupyter readme and Dockerfile, this appears to be used by Google CI:

General Docker guide

Jeremy Howard's gist

James Thompson article from March

Page with links to official prebuilt packages:

Docker setup

If you're already on the supported Ubuntu 18.04 don't really need Docker.

For Docker Hub you'll need to be logged in:

docker login

Pull the Ubuntu image:

docker pull ubuntu:18.04

Output should have some Pull complete messages ending with something like this:

Status: Downloaded newer image for ubuntu:18.04
docker.io/library/ubuntu:18.04

Verify download:

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
ubuntu              18.04               a2a15febcdf3        22 hours ago         64.2MB
$ docker run --rm ubuntu:18.04 uname -a
Linux f0c696b0c42e 4.9.184-linuxkit #1 SMP Tue Jul 2 22:58:16 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

Some additional notes on Docker command line options:

Fully qualified path is necessary to get Mac Docker to plumb the mount all the way through to the host. t and i attach to terminal and run as interactive. priv is needed for among other things debuggers to work (necessary for REPL and Jupyter).

$ docker create -t -i --privileged -v $(pwd)/sharedfiles:/shared -p 8889:8888 ubuntu:18.04 bash
4c5c59fb01a2e1c07edf38624acc5f6b541ad3b9c33420e898f1801ade3a2d03
$ export my_s4tf_container=4c5c59fb01a2
$ docker start $my_s4tf_container
4c5c59fb01a2
$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS                  PORTS                    NAMES
4c5c59fb01a2        ubuntu:18.04        "bash"              57 seconds ago      Up Less than a second   0.0.0.0:8889->8888/tcp   romantic_cartwright

Attach to the running container:

docker attach $my_s4tf_container

To detach: ctrl-p,ctrl-q

Install some the dependencies. Note that we skip graphviz due to its X11 dependency.

$ apt update && apt install -y libvorbis-dev libflac-dev libsndfile-dev cmake build-essential libgflags-dev libgoogle-glog-dev libgtest-dev google-mock zlib1g-dev libeigen3-dev libboost-all-dev libasound2-dev libogg-dev libtool libfftw3-dev libbz2-dev liblzma-dev libgoogle-glog0v5 gcc-6 gfortran-6 g++-6 doxygen libsox-fmt-all parallel exuberant-ctags python-powerline python3-pip curl

Set up Python

apt install python-pip

Install Swift dependencies:

$ apt install -y git cmake ninja-build clang python uuid-dev libicu-dev icu-devtools libbsd-dev libedit-dev libxml2-dev libsqlite3-dev swig libpython-dev libncurses5-dev pkg-config libblocksruntime-dev libcurl4-openssl-dev systemtap-sdt-dev tzdata rsync

Install the latest binary release of S4TF:

$ cd ~
$ curl -O https://storage.googleapis.com/swift-tensorflow-artifacts/releases/v0.4/rc4/swift-tensorflow-RELEASE-0.4-ubuntu18.04.tar.gz
$ mkdir swift
$ tar zxf swift-tensorflow-RELEASE-0.4-ubuntu18.04.tar.gz --directory swift
$ echo 'export PATH=~/swift/usr/bin:$PATH' >> ~/.bashrc
$ source ~/.bashrc

Run Swift interpreter to verify install is ok (ctrl-D to exit):

$ swift
Welcome to Swift version 5.1-dev (LLVM af1f73e9e9, Swift 7d157f346b).
Type :help for assistance.
  1>  

Set up Swift for Jupyter:

$ pip3 install jupyter matplotlib
$ git clone https://github.com/google/swift-jupyter.git
$ cd swift-jupyter
$ python3 register.py --user --swift-toolchain ~/swift --swift-python-library /usr/lib/x86_64-linux-gnu/libpython3.6m.so --kernel-name "Swift"

You should see some JSON printed to the terminal ending at Registered kernel 'Swift' as 'swift'!

Get fastai course v3 notebooks and launch Jupyter:

$ cd /shared
$ git clone https://github.com/fastai/course-v3.git
$ jupyter notebook --allow-root --ip=0.0.0.0 --port=8888

Point your browser to http://localhost:8889 (the port specified when creating the container) and copy/paste the token=... value ‘ where prompted by the Jupyter login page. You can create a new notebook or look at the fastai notebooks visible here:

Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"import Foundation"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"A quick taste of Swift..."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Array<Int>\r\n",
"[1, 4, 9, 16, 25, 36]\r\n"
]
}
],
"source": [
"let nums = [1, 2, 3, 4, 5, 6]\n",
"let squaredNums = nums.map() {\n",
" $0 * $0\n",
"}\n",
"print(type(of: squaredNums))\n",
"print(squaredNums)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"...you have high-level conveniences like the ability to extend whole groups of types..."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"21\n"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"extension Collection where Element: Numeric {\n",
" func sum() -> Element {\n",
" return self.reduce(0, +)\n",
" }\n",
"}\n",
"\n",
"nums.sum()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"...or the freedom take the dangerous but expedient route..."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Optional<UnsafeMutableRawPointer>\r\n",
"Optional(0.0)\r\n"
]
}
],
"source": [
"let ptr = malloc(100)\n",
"print(type(of: ptr))\n",
"print(ptr?.load(as: Double.self))"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Optional(0.0)\r\n"
]
}
],
"source": [
"free(ptr)\n",
"print(ptr?.load(as: Double.self)) // Oops. Freedom!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Let's try autdiff:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"6.0\n"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"@differentiable\n",
"func square(x: Float) -> Float {\n",
" return x * x\n",
"}\n",
"\n",
"let x = 3.0 as Float\n",
"\n",
"// Computes the gradient of `square`\n",
"gradient(at: x, in: square)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"6.0\n"
]
},
"execution_count": 7,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"// Computes the gradient of `square`, then applies it\n",
"let squareGrad = gradient(of: square) // 6.0\n",
"squareGrad(x)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(value: 9.0, gradient: 6.0)\r\n",
"6.0\r\n"
]
}
],
"source": [
"// Computes the value and gradient of `square` at `x`.\n",
"print(valueWithGradient(at: x, in: square)) // (value: 9.0, gradient: 6.0)\n",
"\n",
"// Method examples.\n",
"\n",
"// Computes the gradient of `square` at `x`.\n",
"print(x.gradient(in: square)) // 6.0"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(value: 9.0, gradient: 6.0)\r\n"
]
}
],
"source": [
"// Computes the value and gradient of `square` at `x`.\n",
"print(x.valueWithGradient(in: square)) // (value: 9.0, gradient: 6.0)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"3.6.8 (default, Jan 14 2019, 11:02:34) \n",
"[GCC 8.0.1 20180414 (experimental) [trunk revision 259383]]\n"
]
},
"execution_count": 10,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import Python\n",
"%include \"EnableIPythonDisplay.swift\"\n",
"IPythonDisplay.shell.enable_matplotlib(\"inline\")\n",
"Python.version"
]
},
{
"cell_type": "code",
"execution_count": 11,
"metadata": {},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"<Figure size 432x288 with 1 Axes>"
]
},
"metadata": {
"needs_background": "light"
},
"output_type": "display_data"
},
{
"data": {
"text/plain": [
"None\n"
]
},
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"let plt = Python.import(\"matplotlib.pyplot\")\n",
"\n",
"@differentiable\n",
"func cube(_ x: Float) -> Float {\n",
" return x * x * x\n",
"}\n",
"\n",
"let domain = [Float](stride(from: -1.0, to: 1.0, by: 0.01))\n",
"plt.plot(domain, domain.map({cube(Float($0))}))\n",
"plt.plot(domain, domain.map({gradient(of: cube)(Float($0))}))\n",
"\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Swift",
"language": "swift",
"name": "swift"
},
"language_info": {
"file_extension": ".swift",
"mimetype": "text/x-swift",
"name": "swift",
"version": ""
}
},
"nbformat": 4,
"nbformat_minor": 2
}
@lkhphuc
Copy link

lkhphuc commented Aug 23, 2019

Thank for sharing. One small fix:
I believe this $ echo 'export PATH=~/swift/bin:$PATH' >> ~/.bashrc should be $ echo 'export PATH=~/swift/usr/bin:$PATH' >> ~/.bashrc

@choongng
Copy link
Author

Thanks, fixed. Looks like I have some other updates to do as well.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment