Google Cloud Functions Now Runs Go — What About TensorFlow?

Saurabh Deoras
Pebbles on Pebble Beach, California. Photo by Saurabh Deoras

Google cloud recently enabled Go runtime on cloud functions. This is a great news! Cloud functions are part of their so-called “server-less” offering, which is a fully managed service allowing you to trigger your code (in the form of a function) on an events such as a call on an HTTP endpoint or updates to your GCS bucket contents.

In this post I want to explore ways to run TensorFlow via cloud functions using Go runtime. Let’s look at a simple cloud function that invokes TF API.

// Package p contains an HTTP Cloud Function.
package p

import (
"fmt"
tf "github.com/tensorflow/tensorflow/tensorflow/go"
)

func TFVersion(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintf(w, "%s\n", tf.Version())
}

This function has a dependency on TensorFlow Go API, which needs to be available at the time of building, so we need to provide ago.mod file. Remember, the idea of a cloud function is to supply just the code. go.mod file is a declarative way to indicate dependencies that can be fetched on the server side for deterministic builds.

module tfversion

require github.com/tensorflow/tensorflow v1.12.0

I had hoped that this would work without additional setup, but I ran into several issues. First, there were no libtensorflow.so and libtensorflow_framework.so libraries on the server side. These libraries are core TensorFlow C libraries and are required by the TensorFlow Go API.

So I tried to supply these libraries as part of the zip payload under lib folder and made changes to my code so that the build system would look into local folders for linking correctly.

// #cgo LDFLAGS: -Llib
import "C"

I could not get the code to compile even after adding LDFLAGS. It gave following error.

worker.go:17:2: build constraints exclude all Go files in /tmp/sgb/staging/srv/files

It seems it is not possible to compile Go-code with a dependency on TF. Ideally it would be nice if the build environment natively included libtensorflow.so and libtensorflow_framework.so and allowed builds against them.

In the mean time, however, let’s take a different approach. We will execute TF functions as external binaries via shell exec calls.

Folder Structure

In order to run an external dependencies via shell exec calls we need to know where the binaries reside and, particularly for TensorFlow execution, we also need to ensure that thelibtensorflow.so and libtensorflow_framework.so libraries are available for dynamic linking.

Let’s first investigate how a cloud function package is configured on the server side. To do this we will simply run a cloud function, called Explore. The function looks into the folder structure. The environment provides a variable called CODE_LOCATION, which we can use as our starting point for the top level folder.

// Package p contains an HTTP Cloud Function.
package p

import (
"fmt"
"io/ioutil"
"net/http"
"os"
"path/filepath"
)
// Explore prints file contents recursively for ${CODE_LOCATION}
func Explore(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintf(w, "CODE_LOCATION=%s\n\n", os.Getenv("CODE_LOCATION"))

names, err := explore(os.Getenv("CODE_LOCATION"))
if err != nil {
_, _ = fmt.Fprintf(w, "%v\n", err)
return
}

for _, file := range names {
_, _ = fmt.Fprintf(w, "%s\n", file)
}
}
// explore is called recursively when a folder is found.
func explore(dir string) ([]string, error) {
files, err := ioutil.ReadDir(dir)
if err != nil {
return nil, err
}

var out []string
for _, file := range files {
if file.IsDir() {
out2, err := explore(filepath.Join(dir, file.Name()))
if err != nil {
return nil, err
}
for i := range out2 {
out = append(out, filepath.Join(file.Name(), out2[i]))
}
}
out = append(out, file.Name())
}

return out, nil
}

This gives us following output, indicating that all code resides at /srv and libtensorflow.so resides at /srv/files/lib. Now all we need to do is package a binary and setup environment variable LD_LIBRARY_PATH to point to the folder where these .so files reside.

CODE_LOCATION=/srv---- zip payload is under files folder ----
files/go.mod
files/go.sum
files/explore.go
files/lib/libtensorflow.so
files/lib/libtensorflow_framework.so
files/lib
files
---- server side adds these files ----
go.mod
serverless-build.yaml
vendor/gcf/event.go
vendor/gcf/go.mod
vendor/gcf
vendor
worker.go

Preparing Binary

The binary running TensorFlow is compiled externally for linux/amd64 architecture and packaged with the cloud function as a zip payload. Let’s write a simple main function.

// Package main contains a TF workload that an HTTP Cloud Function calls.
package main

import (
"fmt"
tf "github.com/tensorflow/tensorflow/tensorflow/go"
"os"
)

func main() {
_, _ = fmt.Fprintf(os.Stdout, "%s\n", tf.Version())
}

Let’s compile this code with: go build -o myTFBinary and package it in the zip payload.

Exec Call

The cloud function would simply call this binary as follows. We also have to set the LD_LIBRARY_PATH environment variable to point to location of .so files.

// Package p contains an HTTP Cloud Function.
package p

import (
"fmt"
"net/http"
"os"
"os/exec"
)

// CallTFBinary calls a binary that runs TensorFlow
func CallTFBinary(w http.ResponseWriter, r *http.Request) {
_, _ = fmt.Fprintf(w, "LD_LIBRARY_PATH:%s\n", os.Getenv("LD_LIBRARY_PATH"))

b, err := exec.Command("/srv/files/bin/myTFBinary").Output()
if err != nil {
_, _ = fmt.Fprintf(w, "%v\n", err)
return
}
_, _ = fmt.Fprintf(w, "TF Version:%s\n", string(b))
}

Now this runs successfully!

LD_LIBRARY_PATH:/srv/files/bin/lib
TF Version:1.12.0

Summary

Support for Go runtime on Google cloud functions is a great news! I explored different ways to get TF running on cloud functions and found that it is possible to run it via shell exec call. This is not ideal, but I am happy to see this working. Eventually, of course, I would love to be able to compile TF directly into my cloud function code.


Pebbles on Pebble Beach California

Pebble beach, just south of Half Moon Bay in San Francisco Bay Area is a fascinating place. It has these colorful pebbles spread across entire beach. I chose this picture because cloud functions are like these little pebbles, each different and collectively serving a bigger purpose.

Saurabh Deoras

Written by

A gopher who likes to bike, swim and take pictures.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade