chore: import upstream snapshot with attribution
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:14:16 +08:00
commit 8a852e4b4e
36502 changed files with 9277225 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
# Description:
# Go API for TensorFlow.
load(
"//tensorflow:tensorflow.bzl",
"tf_shared_library_deps",
)
package(
default_visibility = ["//visibility:private"],
)
licenses(["notice"]) # Apache 2.0
sh_test(
name = "test",
size = "small",
srcs = ["test.sh"],
data = [
":all_files", # Go sources
"//tensorflow/c:headers", # C library header
"//tensorflow/c/eager:headers", # Eager C library header
"//tensorflow/cc/saved_model:saved_model_half_plus_two", # Testdata for LoadSavedModel
] + tf_shared_library_deps(),
# TODO: Enable this test again once protos are supported by bazel.
tags = ["manual"],
)
filegroup(
name = "all_files",
srcs = glob(
["**/*"],
exclude = [
"**/METADATA",
"**/OWNERS",
],
),
visibility = ["//tensorflow:__subpackages__"],
)
+17
View File
@@ -0,0 +1,17 @@
# TensorFlow in Go
Construct and execute TensorFlow graphs in Go.
[![GoDoc](https://godoc.org/github.com/tensorflow/tensorflow/tensorflow/go?status.svg)](https://godoc.org/github.com/tensorflow/tensorflow/tensorflow/go)
> *WARNING*: The API defined in this package is not stable and can change
> without notice. The same goes for the package path:
> (`github.com/tensorflow/tensorflow/tensorflow/go`).
# WARNING:
The TensorFlow team is not currently maintaining the Documentation for installing the Go bindings for TensorFlow.
The instructions has been maintained by the third party contributor: @wamuir
Please follow this [source](https://github.com/tensorflow/build/tree/master/golang_install_guide) by @wamuir for the installation of Golang with Tensorflow.
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2016 The TensorFlow Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// +build android
package tensorflow
// #cgo LDFLAGS: -landroid -llog -lm -lz -ldl
import "C"
+246
View File
@@ -0,0 +1,246 @@
/*
Copyright 2018 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
// #include <stdlib.h>
// #include "tensorflow/c/c_api.h"
import "C"
import (
"fmt"
"unsafe"
)
// makeCShape converts a shape specified in C.int64_t into a Shape.
func makeCShape(shape []C.int64_t) Shape {
s := Shape{dims: make([]int64, len(shape))}
for i, n := range shape {
s.dims[i] = int64(n)
}
return s
}
// Attr returns the value of an attribute on op. It returns an error if the
// attribute does not exist.
func (op *Operation) Attr(name string) (interface{}, error) {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
status := newStatus()
meta := C.TF_OperationGetAttrMetadata(op.c, cname, status.c)
if err := status.Err(); err != nil {
return nil, err
}
if meta.is_list == 1 {
return listAttribute(op, cname, meta)
}
return scalarAttribute(op, cname, meta)
}
func listAttribute(op *Operation, cname *C.char, meta C.TF_AttrMetadata) (interface{}, error) {
status := newStatus()
switch meta._type {
case C.TF_ATTR_STRING:
if meta.list_size == 0 {
return []string(nil), nil
}
values := make([]unsafe.Pointer, meta.list_size)
lengths := make([]C.size_t, meta.list_size)
// Add one element in case total_size is zero.
storage := make([]C.char, meta.total_size+1)
C.TF_OperationGetAttrStringList(op.c, cname, &values[0], &lengths[0], C.int(meta.list_size), unsafe.Pointer(&storage[0]), C.size_t(meta.total_size), status.c)
if err := status.Err(); err != nil {
return nil, err
}
list := make([]string, meta.list_size)
for i, val := range values {
length := lengths[i]
list[i] = C.GoStringN((*C.char)(val), C.int(length))
}
return list, nil
case C.TF_ATTR_INT:
if meta.list_size == 0 {
return []int64(nil), nil
}
list := make([]C.int64_t, meta.list_size)
C.TF_OperationGetAttrIntList(op.c, cname, &list[0], C.int(meta.list_size), status.c)
if err := status.Err(); err != nil {
return nil, err
}
vals := make([]int64, meta.list_size)
for i, val := range list {
vals[i] = int64(val)
}
return vals, nil
case C.TF_ATTR_FLOAT:
if meta.list_size == 0 {
return []float32(nil), nil
}
list := make([]C.float, meta.list_size)
C.TF_OperationGetAttrFloatList(op.c, cname, &list[0], C.int(meta.list_size), status.c)
if err := status.Err(); err != nil {
return nil, err
}
vals := make([]float32, meta.list_size)
for i, val := range list {
vals[i] = float32(val)
}
return vals, nil
case C.TF_ATTR_BOOL:
if meta.list_size == 0 {
return []bool(nil), nil
}
list := make([]C.uchar, meta.list_size)
C.TF_OperationGetAttrBoolList(op.c, cname, &list[0], C.int(meta.list_size), status.c)
if err := status.Err(); err != nil {
return nil, err
}
vals := make([]bool, meta.list_size)
for i, val := range list {
vals[i] = val == 1
}
return vals, nil
case C.TF_ATTR_TYPE:
if meta.list_size == 0 {
return []DataType(nil), nil
}
list := make([]C.TF_DataType, meta.list_size)
C.TF_OperationGetAttrTypeList(op.c, cname, &list[0], C.int(meta.list_size), status.c)
if err := status.Err(); err != nil {
return nil, err
}
vals := make([]DataType, meta.list_size)
for i, val := range list {
vals[i] = DataType(val)
}
return vals, nil
case C.TF_ATTR_TENSOR:
if meta.list_size == 0 {
return []*Tensor(nil), nil
}
list := make([]*C.TF_Tensor, meta.list_size)
C.TF_OperationGetAttrTensorList(op.c, cname, &list[0], C.int(meta.list_size), status.c)
if err := status.Err(); err != nil {
return nil, err
}
vals := make([]*Tensor, meta.list_size)
for i, t := range list {
vals[i] = newTensorFromC(t)
}
return vals, nil
case C.TF_ATTR_SHAPE:
if meta.list_size == 0 {
return []Shape(nil), nil
}
dims := make([]*C.int64_t, meta.list_size)
numDims := make([]C.int, meta.list_size)
// Add one element in case total_size is zero.
storage := make([]C.int64_t, meta.total_size+1)
C.TF_OperationGetAttrShapeList(op.c, cname, &dims[0], &numDims[0], C.int(meta.list_size), &storage[0], C.int(meta.total_size), status.c)
if err := status.Err(); err != nil {
return nil, err
}
list := make([]Shape, meta.list_size)
for i, dim := range dims {
numDim := numDims[i]
// If the number of dimensions is unknown, default to empty shape.
if numDim < 0 {
continue
}
// A []C.int64_t slice backed by C memory.
// See: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
// Using [1<<27] instead of [1<<30] so it works on 32-bit architecture
slice := (*[1 << 27]C.int64_t)(unsafe.Pointer(dim))[:numDim:numDim]
list[i] = makeCShape(slice)
}
return list, nil
default:
return nil, fmt.Errorf("list type %v not supported", meta._type)
}
}
func scalarAttribute(op *Operation, cname *C.char, meta C.TF_AttrMetadata) (interface{}, error) {
status := newStatus()
switch meta._type {
case C.TF_ATTR_STRING:
if meta.total_size == 0 {
return "", nil
}
v := make([]C.char, meta.total_size)
C.TF_OperationGetAttrString(op.c, cname, unsafe.Pointer(&v[0]), C.size_t(meta.total_size), status.c)
if err := status.Err(); err != nil {
return nil, err
}
return C.GoStringN(&v[0], C.int(meta.total_size)), nil
case C.TF_ATTR_INT:
var v C.int64_t
C.TF_OperationGetAttrInt(op.c, cname, &v, status.c)
return int64(v), status.Err()
case C.TF_ATTR_FLOAT:
var v C.float
C.TF_OperationGetAttrFloat(op.c, cname, &v, status.c)
return float32(v), status.Err()
case C.TF_ATTR_BOOL:
var v C.uchar
C.TF_OperationGetAttrBool(op.c, cname, &v, status.c)
return v == 1, status.Err()
case C.TF_ATTR_TYPE:
var v C.TF_DataType
C.TF_OperationGetAttrType(op.c, cname, &v, status.c)
return DataType(v), status.Err()
case C.TF_ATTR_TENSOR:
var v *C.TF_Tensor
C.TF_OperationGetAttrTensor(op.c, cname, &v, status.c)
if err := status.Err(); err != nil {
return nil, err
}
return newTensorFromC(v), nil
case C.TF_ATTR_SHAPE:
numDims := meta.total_size
// If number of dims is unknown return empty shape to indicate that.
if numDims < 0 {
return Shape{}, nil
}
if numDims == 0 {
return ScalarShape(), nil
}
dims := make([]C.int64_t, numDims)
C.TF_OperationGetAttrShape(op.c, cname, (*C.int64_t)(unsafe.Pointer(&dims[0])), C.int(numDims), status.c)
if err := status.Err(); err != nil {
return nil, err
}
return makeCShape(dims), nil
default:
return nil, fmt.Errorf("type %v not supported", meta._type)
}
}
+193
View File
@@ -0,0 +1,193 @@
/*
Copyright 2018 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
import (
"fmt"
"reflect"
"testing"
)
func TestOperationAttrs(t *testing.T) {
g := NewGraph()
i := 0
makeConst := func(v interface{}) Output {
op, err := Const(g, fmt.Sprintf("const/%d/%+v", i, v), v)
i++
if err != nil {
t.Fatal(err)
}
return op
}
makeTensor := func(v interface{}) *Tensor {
tensor, err := NewTensor(v)
if err != nil {
t.Fatal(err)
}
return tensor
}
cases := []OpSpec{
{
Name: "type",
Type: "Placeholder",
Attrs: map[string]interface{}{
"dtype": Float,
},
},
{
Name: "list(float)",
Type: "Bucketize",
Input: []Input{
makeConst([]float32{1, 2, 3, 4}),
},
Attrs: map[string]interface{}{
"boundaries": []float32{0, 1, 2, 3, 4, 5},
},
},
{
Name: "list(float) empty",
Type: "Bucketize",
Input: []Input{
makeConst([]float32{}),
},
Attrs: map[string]interface{}{
"boundaries": []float32(nil),
},
},
/* TODO(ashankar): debug this issue and add it back later.
{
Name: "list(type),list(shape)",
Type: "InfeedEnqueueTuple",
Input: []Input{
OutputList([]Output{
makeConst(float32(1)),
makeConst([][]int32{{2}}),
}),
},
Attrs: map[string]interface{}{
"dtypes": []DataType{Float, Int32},
"shapes": []Shape{ScalarShape(), MakeShape(1, 1)},
},
},
{
Name: "list(type),list(shape) empty",
Type: "InfeedEnqueueTuple",
Input: []Input{
OutputList([]Output{
makeConst([][]int32{{2}}),
}),
},
Attrs: map[string]interface{}{
"dtypes": []DataType{Int32},
"shapes": []Shape(nil),
},
},
{
Name: "list(type) empty,string empty,int",
Type: "_XlaSendFromHost",
Input: []Input{
OutputList([]Output{}),
makeConst(""),
},
Attrs: map[string]interface{}{
"Tinputs": []DataType(nil),
"key": "",
"device_ordinal": int64(0),
},
},
*/
{
Name: "list(int),int",
Type: "StringToHashBucketStrong",
Input: []Input{
makeConst(""),
},
Attrs: map[string]interface{}{
"num_buckets": int64(2),
"key": []int64{1, 2},
},
},
{
Name: "list(int) empty,int",
Type: "StringToHashBucketStrong",
Input: []Input{
makeConst(""),
},
Attrs: map[string]interface{}{
"num_buckets": int64(2),
"key": ([]int64)(nil),
},
},
{
Name: "list(string),type",
Type: "TensorSummary",
Input: []Input{
makeConst(""),
},
Attrs: map[string]interface{}{
"T": String,
"labels": []string{"foo", "bar"},
},
},
{
Name: "list(string) empty,type",
Type: "TensorSummary",
Input: []Input{
makeConst(""),
},
Attrs: map[string]interface{}{
"T": String,
"labels": ([]string)(nil),
},
},
{
Name: "tensor",
Type: "Const",
Attrs: map[string]interface{}{
"dtype": String,
"value": makeTensor("foo"),
},
},
}
for i, spec := range cases {
op, err := g.AddOperation(spec)
if err != nil {
t.Fatal(err)
}
for key, want := range spec.Attrs {
out, err := op.Attr(key)
if err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(out, want) {
t.Fatalf("%d. %q: Got %#v, wanted %#v", i, key, out, want)
}
wantT, ok := want.(*Tensor)
if ok {
wantVal := wantT.Value()
outVal := out.(*Tensor).Value()
if !reflect.DeepEqual(outVal, wantVal) {
t.Fatalf("%d. %q: Got %#v, wanted %#v", i, key, outVal, wantVal)
}
}
}
}
}
+109
View File
@@ -0,0 +1,109 @@
/*
Copyright 2018 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
// #include <stdlib.h>
// #include "tensorflow/c/c_api.h"
// #include "tensorflow/c/eager/c_api.h"
import "C"
import (
"fmt"
"runtime"
)
// ContextOptions contains configuration information for a session
type ContextOptions struct {
// Config is a binary-serialized representation of the
// tensorflow.ConfigProto protocol message
// (https://www.tensorflow.org/code/tensorflow/core/protobuf/config.proto).
Config []byte
// Sets the default execution mode
Async bool
}
// c converts the ContextOptions to the C API's TF_ContextOptions.
// Caller takes ownership of returned object.
func (o *ContextOptions) c() (*C.TFE_ContextOptions, error) {
opt := C.TFE_NewContextOptions()
if o == nil {
return opt, nil
}
if sz := len(o.Config); sz > 0 {
status := newStatus()
cConfig := C.CBytes(o.Config)
C.TFE_ContextOptionsSetConfig(opt, cConfig, C.size_t(sz), status.c)
C.free(cConfig)
if err := status.Err(); err != nil {
C.TFE_DeleteContextOptions(opt)
return nil, fmt.Errorf("invalid ContextOptions.Config: %v", err)
}
}
var async uint8
if o.Async {
async = 1
}
C.TFE_ContextOptionsSetAsync(opt, C.uchar(async))
return opt, nil
}
// Context for executing operations eagerly.
//
// A Context allows operations to be executed immediately. It encapsulates
// information such as the available devices, resource manager etc. It also
// allows the user to configure execution using a ConfigProto, as they can
// configure a Session when executing a Graph.
type Context struct {
c *C.TFE_Context
}
// NewContext creates a new context for eager execution.
// options may be nil to use the default options.
func NewContext(options *ContextOptions) (*Context, error) {
status := newStatus()
cOpt, err := options.c()
if err != nil {
return nil, err
}
defer C.TFE_DeleteContextOptions(cOpt)
cContext := C.TFE_NewContext(cOpt, status.c)
if err := status.Err(); err != nil {
return nil, err
}
c := &Context{c: cContext}
runtime.SetFinalizer(c, (*Context).finalizer)
return c, nil
}
func (c *Context) finalizer() {
C.TFE_DeleteContext(c.c)
}
// ListDevices returns the list of devices associated with a Context.
func (c *Context) ListDevices() ([]Device, error) {
status := newStatus()
devicesList := C.TFE_ContextListDevices(c.c, status.c)
if err := status.Err(); err != nil {
return nil, fmt.Errorf("SessionListDevices() failed: %v", err)
}
defer C.TF_DeleteDeviceList(devicesList)
return deviceSliceFromDeviceList(devicesList)
}
+57
View File
@@ -0,0 +1,57 @@
/*
Copyright 2018 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
import (
"fmt"
"testing"
)
func TestContextConfigSetAsync(t *testing.T) {
tests := []bool{false, true}
for _, test := range tests {
t.Run(fmt.Sprint(test), func(t *testing.T) {
opt := &ContextOptions{Async: test}
if _, err := NewContext(opt); err != nil {
t.Fatal(err)
}
})
}
}
func TestContextConfigListDevices(t *testing.T) {
c, err := NewContext(nil)
if err != nil {
t.Fatal(err)
}
devs, err := c.ListDevices()
if err != nil {
t.Fatal(err)
}
if len(devs) < 1 {
t.Fatalf("No devices found using ListDevices()")
}
foundCPUDevice := false
for _, d := range devs {
if d.Type == "CPU" {
foundCPUDevice = true
}
}
if !foundCPUDevice {
t.Error("Failed to find CPU device using ListDevices()")
}
}
+1
View File
@@ -0,0 +1 @@
# Empty file to be replaced in https://github.com/tensorflow/tensorflow/pull/50934
+1
View File
@@ -0,0 +1 @@
# Empty file to be replaced in https://github.com/tensorflow/tensorflow/pull/50934
+26
View File
@@ -0,0 +1,26 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package tensorflow is a Go binding to TensorFlow.
//
// The API is subject to change and may break at any time.
//
// TensorFlow (www.tensorflow.org) is an open source software library for
// numerical computation using data flow graphs. This package provides
// functionality to build and execute such graphs and depends on
// TensorFlow being available. For installation instructions see
// https://github.com/tensorflow/tensorflow/blob/master/tensorflow/go/README.md
package tensorflow
@@ -0,0 +1,318 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow_test
import (
"archive/zip"
"bufio"
"flag"
"fmt"
"io"
"log"
"net/http"
"os"
"path/filepath"
"github.com/tensorflow/tensorflow/tensorflow/go/op"
tf "github.com/tensorflow/tensorflow/tensorflow/go"
)
func Example() {
// An example for using the TensorFlow Go API for image recognition
// using a pre-trained inception model (http://arxiv.org/abs/1512.00567).
//
// Sample usage: <program> -dir=/tmp/modeldir -image=/path/to/some/jpeg
//
// The pre-trained model takes input in the form of a 4-dimensional
// tensor with shape [ BATCH_SIZE, IMAGE_HEIGHT, IMAGE_WIDTH, 3 ],
// where:
// - BATCH_SIZE allows for inference of multiple images in one pass through the graph
// - IMAGE_HEIGHT is the height of the images on which the model was trained
// - IMAGE_WIDTH is the width of the images on which the model was trained
// - 3 is the (R, G, B) values of the pixel colors represented as a float.
//
// And produces as output a vector with shape [ NUM_LABELS ].
// output[i] is the model-implied probability of the input image having
// the i-th label.
//
// A separate file contains a list of string labels corresponding to the
// integer indices of the output.
//
// This example:
// - Loads the serialized representation of the pre-trained model into a Graph
// - Creates a Session to execute operations on the Graph
// - Converts an image file to a Tensor to provide as input to a Session run
// - Executes the Session and prints out the label with the highest probability
//
// To convert an image file to a Tensor suitable for input to the Inception model,
// this example:
// - Constructs another TensorFlow graph to normalize the image into a
// form suitable for the model (for example, resizing the image)
// - Creates and executes a Session to obtain a Tensor in this normalized form.
modeldir := flag.String(
"dir",
"testdata/saved_model/inception5h",
"Directory containing the trained model files. The directory will be"+
"created and the model downloaded into it if necessary",
)
imagefile := flag.String(
"image",
"testdata/label_image/grace_hopper.jpg",
"Path of a JPEG-image to extract labels for",
)
flag.Parse()
// Load the serialized GraphDef from a file.
modelfile, labelsfile, err := modelFiles(*modeldir)
if err != nil {
log.Fatal(err)
}
labels, err := readLabelsFile(labelsfile)
if err != nil {
log.Fatal(err)
}
model, err := os.ReadFile(modelfile)
if err != nil {
log.Fatal(err)
}
// Construct an in-memory graph from the serialized form.
graph := tf.NewGraph()
if err := graph.Import(model, ""); err != nil {
log.Fatal(err)
}
// Create a session for inference over graph.
session, err := tf.NewSession(graph, nil)
if err != nil {
log.Fatal(err)
}
defer session.Close()
// Run inference on *imageFile.
// For multiple images, session.Run() can be called in a loop (and
// concurrently). Alternatively, images can be batched since the model
// accepts batches of image data as input.
tensor, err := makeTensorFromImage(*imagefile)
if err != nil {
log.Fatal(err)
}
output, err := session.Run(
map[tf.Output]*tf.Tensor{
graph.Operation("input").Output(0): tensor,
},
[]tf.Output{
graph.Operation("output").Output(0),
},
nil)
if err != nil {
log.Fatal(err)
}
// output[0].Value() is a vector containing probabilities of
// labels for each image in the "batch". The batch size was 1.
// Find the most probable label index.
probabilities := output[0].Value().([][]float32)[0]
printBestLabel(probabilities, labels)
// // Output:
// // BEST MATCH: (29% likely) military uniform
}
func printBestLabel(probabilities []float32, labels []string) {
idx := argmax(probabilities)
fmt.Printf(
"BEST MATCH: (%2.0f%% likely) %s",
probabilities[idx]*100.0,
labels[idx],
)
}
// Convert the image in filename to a Tensor suitable as input to the Inception model.
func makeTensorFromImage(filename string) (*tf.Tensor, error) {
bytes, err := os.ReadFile(filename)
if err != nil {
return nil, err
}
// DecodeJpeg uses a scalar String-valued tensor as input.
tensor, err := tf.NewTensor(string(bytes))
if err != nil {
return nil, err
}
// Construct a graph to normalize the image
graph, input, output, err := constructGraphToNormalizeImage()
if err != nil {
return nil, err
}
// Execute that graph to normalize this one image
session, err := tf.NewSession(graph, nil)
if err != nil {
return nil, err
}
defer session.Close()
normalized, err := session.Run(
map[tf.Output]*tf.Tensor{input: tensor},
[]tf.Output{output},
nil)
if err != nil {
return nil, err
}
return normalized[0], nil
}
// The inception model takes as input the image described by a Tensor in a very
// specific normalized format (a particular image size, shape of the input tensor,
// normalized pixel values etc.).
//
// This function constructs a graph of TensorFlow operations which takes as
// input a JPEG-encoded string and returns a tensor suitable as input to the
// inception model.
func constructGraphToNormalizeImage() (graph *tf.Graph, input, output tf.Output, err error) {
// Some constants specific to the pre-trained model at:
// https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip
//
// - The model was trained after with images scaled to 224x224 pixels.
// - The colors, represented as R, G, B in 1-byte each were converted to
// float using (value - Mean)/Scale.
const (
H, W = 224, 224
Mean = float32(117)
Scale = float32(1)
)
// - input is a String-Tensor, where the string the JPEG-encoded image.
// - The inception model takes a 4D tensor of shape
// [BatchSize, Height, Width, Colors=3], where each pixel is
// represented as a triplet of floats
// - Apply normalization on each pixel and use ExpandDims to make
// this single image be a "batch" of size 1 for ResizeBilinear.
s := op.NewScope()
input = op.Placeholder(s, tf.String)
output = op.Div(s,
op.Sub(s,
op.ResizeBilinear(s,
op.ExpandDims(s,
op.Cast(s,
op.DecodeJpeg(s, input, op.DecodeJpegChannels(3)), tf.Float),
op.Const(s.SubScope("make_batch"), int32(0))),
op.Const(s.SubScope("size"), []int32{H, W})),
op.Const(s.SubScope("mean"), Mean)),
op.Const(s.SubScope("scale"), Scale))
graph, err = s.Finalize()
return graph, input, output, err
}
func modelFiles(dir string) (modelfile, labelsfile string, err error) {
const URL = "https://storage.googleapis.com/download.tensorflow.org/models/inception5h.zip"
var (
model = filepath.Join(dir, "tensorflow_inception_graph.pb")
labels = filepath.Join(dir, "imagenet_comp_graph_label_strings.txt")
zipfile = filepath.Join(dir, "inception5h.zip")
)
if filesExist(model, labels) == nil {
return model, labels, nil
}
log.Println("Did not find model in", dir, "downloading from", URL)
if err := os.MkdirAll(dir, 0755); err != nil {
return "", "", err
}
if err := download(URL, zipfile); err != nil {
return "", "", fmt.Errorf("failed to download %v - %v", URL, err)
}
if err := unzip(dir, zipfile); err != nil {
return "", "", fmt.Errorf("failed to extract contents from model archive: %v", err)
}
os.Remove(zipfile)
return model, labels, filesExist(model, labels)
}
func filesExist(files ...string) error {
for _, f := range files {
if _, err := os.Stat(f); err != nil {
return fmt.Errorf("unable to stat %s: %v", f, err)
}
}
return nil
}
func download(URL, filename string) error {
resp, err := http.Get(URL)
if err != nil {
return err
}
defer resp.Body.Close()
file, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0644)
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, resp.Body)
return err
}
func unzip(dir, zipfile string) error {
r, err := zip.OpenReader(zipfile)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
src, err := f.Open()
if err != nil {
return err
}
log.Println("Extracting", f.Name)
dst, err := os.OpenFile(filepath.Join(dir, f.Name), os.O_WRONLY|os.O_CREATE, 0644)
if err != nil {
return err
}
if _, err := io.Copy(dst, src); err != nil {
return err
}
dst.Close()
}
return nil
}
func readLabelsFile(f string) ([]string, error) {
var labels []string
file, err := os.Open(f)
if err != nil {
return nil, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
for i := 0; scanner.Scan(); i++ {
labels = append(labels, scanner.Text())
}
if err := scanner.Err(); err != nil {
return nil, err
}
return labels, nil
}
// argmax returns the index the maximal element in slice a.
func argmax(a []float32) int {
var idx int
for i := 0; i < len(a); i++ {
if a[i] > a[idx] {
idx = i
}
}
return idx
}
+2
View File
@@ -0,0 +1,2 @@
# .pb.go files generated by generate.sh
internal/proto/*
+1
View File
@@ -0,0 +1 @@
# Empty file to be replaced in https://github.com/tensorflow/tensorflow/pull/50934
+21
View File
@@ -0,0 +1,21 @@
/*
Copyright 2020 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package stub file for non-Windows builds.
//go:generate bash generate.sh
package main
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env bash
# Copyright 2016 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
set -e
go get -d google.golang.org/protobuf/proto
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
if [ -z "${GOPATH}" ]
then
GOPATH=$(go env GOPATH)
fi
if [ -z "${GOOS}" ]
then
GOOS=$(go env GOOS)
fi
# convert GOPATH's Windows style to UNIX style
if [[ $GOOS == "windows" ]]; then
# eg: convert "D:\go-14;D:\go-13" to "D\go-14;D\go-13"
GOPATH=${GOPATH//:\\/\\}
# eg: convert "D\go-14;D\go-13" to "\D\go-14:\D\go-13"
GOPATH=\\${GOPATH//;/:\\}
# eg: convert "\D\go-14:\D\go-13" to "/D/go-14:/D/go-13"
GOPATH=${GOPATH//\\/\/}
fi
cd $(dirname $0)
for g in $(echo "${GOPATH//:/ }"); do
TF_DIR="${g}/src/github.com/tensorflow/tensorflow"
PROTOC="${TF_DIR}/bazel-out/host/bin/external/protobuf/protoc"
if [ -x "${PROTOC}" ]; then
break
fi
done
if [ ! -x "${PROTOC}" ]
then
set +e
PATH_PROTOC=$(which protoc)
if [ ! -x "${PATH_PROTOC}" ]
then
echo "Protocol buffer compiler protoc not found in PATH or in ${PROTOC}"
echo "Perhaps build it using:"
echo "bazel build --config opt @com_google_protobuf//:protoc"
exit 1
fi
PROTOC=$PATH_PROTOC
set -e
fi
# Ensure that protoc-gen-go is available in $PATH
# Since ${PROTOC} will require it.
export PATH=$PATH:${GOPATH}/bin
for FILE in ${TF_DIR}/tensorflow/core/framework/*.proto \
${TF_DIR}/tensorflow/core/protobuf/*.proto \
${TF_DIR}/tensorflow/compiler/xla/pjrt/distributed/*.proto \
${TF_DIR}/tensorflow/compiler/xla/stream_executor/*.proto; do
${PROTOC} \
-I ${TF_DIR} \
--go_out=${GOPATH}/src \
$FILE
done
+1
View File
@@ -0,0 +1 @@
# Empty file to be replaced in https://github.com/tensorflow/tensorflow/pull/50934
+129
View File
@@ -0,0 +1,129 @@
/*
Copyright 2017 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
/*
#include <stdlib.h>
#include <string.h>
#include "tensorflow/c/c_api.h"
*/
import "C"
import (
"errors"
"fmt"
"runtime"
"unsafe"
"google.golang.org/protobuf/proto"
adpb "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/api_def_go_proto"
odpb "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/op_def_go_proto"
)
// Encapsulates a collection of API definitions.
//
// apiDefMap represents a map from operation name to corresponding
// ApiDef proto (see
// https://www.tensorflow.org/code/tensorflow/core/framework/api_def.proto
// for ApiDef proto definition).
type apiDefMap struct {
c *C.TF_ApiDefMap
}
// Creates and returns a new apiDefMap instance.
//
// oplist is and OpList proto instance (see
// https://www.tensorflow.org/code/tensorflow/core/framework/op_def.proto
// for OpList proto definition).
func newAPIDefMap(oplist *odpb.OpList) (*apiDefMap, error) {
// Create a buffer containing the serialized OpList.
opdefSerialized, err := proto.Marshal(oplist)
if err != nil {
return nil, fmt.Errorf("could not serialize OpDef for %s", oplist.String())
}
data := C.CBytes(opdefSerialized)
defer C.free(data)
opbuf := C.TF_NewBuffer()
defer C.TF_DeleteBuffer(opbuf)
opbuf.data = data
opbuf.length = C.size_t(len(opdefSerialized))
// Create ApiDefMap.
status := C.TF_NewStatus()
defer C.TF_DeleteStatus(status)
capimap := C.TF_NewApiDefMap(opbuf, status)
if C.TF_GetCode(status) != C.TF_OK {
return nil, errors.New(C.GoString(C.TF_Message(status)))
}
apimap := &apiDefMap{capimap}
runtime.SetFinalizer(
apimap,
func(a *apiDefMap) {
C.TF_DeleteApiDefMap(a.c)
})
return apimap, nil
}
// Updates apiDefMap with the overrides specified in `data`.
//
// data - ApiDef text proto.
func (m *apiDefMap) Put(data string) error {
cdata := C.CString(data)
defer C.free(unsafe.Pointer(cdata))
status := C.TF_NewStatus()
defer C.TF_DeleteStatus(status)
C.TF_ApiDefMapPut(m.c, cdata, C.size_t(len(data)), status)
if C.TF_GetCode(status) != C.TF_OK {
return errors.New(C.GoString(C.TF_Message(status)))
}
return nil
}
// Returns ApiDef proto instance for the TensorFlow operation
// named `opname`.
func (m *apiDefMap) Get(opname string) (*adpb.ApiDef, error) {
cname := C.CString(opname)
defer C.free(unsafe.Pointer(cname))
status := C.TF_NewStatus()
defer C.TF_DeleteStatus(status)
apidefBuf := C.TF_ApiDefMapGet(
m.c, cname, C.size_t(len(opname)), status)
defer C.TF_DeleteBuffer(apidefBuf)
if C.TF_GetCode(status) != C.TF_OK {
return nil, errors.New(C.GoString(C.TF_Message(status)))
}
if apidefBuf == nil {
return nil, fmt.Errorf("could not find ApiDef for %s", opname)
}
var (
apidef = new(adpb.ApiDef)
size = int(apidefBuf.length)
// A []byte backed by C memory.
// See: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
data = (*[1 << 30]byte)(unsafe.Pointer(apidefBuf.data))[:size:size]
err = proto.Unmarshal(data, apidef)
)
if err != nil {
return nil, err
}
return apidef, nil
}
+609
View File
@@ -0,0 +1,609 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package internal generates Go source code with functions for TensorFlow operations.
//
// The basic outline of the generated API is as follows:
//
// - One function for each TensorFlow operation
// - The arguments to the function are the inputs and required attributes of the operation
// - The function returns the outputs
// - A function is also generated for each optional attribute of the operation.
//
// There is a possibility that there are name collisions between the functions
// generated for ops and the functions generated for optional attributes. For
// now, we ignore those, but will need to revisit if a collision is actually
// encountered.
package internal
/*
#include <stdlib.h>
#include "tensorflow/c/c_api.h"
*/
import "C"
import (
"fmt"
"io"
"os"
"path"
"reflect"
"sort"
"strings"
"text/template"
"unsafe"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
adpb "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/api_def_go_proto"
odpb "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/op_def_go_proto"
)
// GenerateFunctionsForRegisteredOps writes a Go source code file to w
// containing functions for each TensorFlow operation registered in the address
// space of the calling process.
// apidefDirs should be a contain of directories containing api_def_*.pbtxt
// files to load.
func GenerateFunctionsForRegisteredOps(
w io.Writer, apidefDirs []string) error {
ops, apimap, err := registeredOps()
if err != nil {
return err
}
for _, dir := range apidefDirs {
if err = updateAPIDefs(apimap, dir); err != nil {
return err
}
}
return generateFunctionsForOps(w, ops, apimap)
}
func registeredOps() (*odpb.OpList, *apiDefMap, error) {
buf := C.TF_GetAllOpList()
defer C.TF_DeleteBuffer(buf)
var (
list = new(odpb.OpList)
size = int(buf.length)
// A []byte backed by C memory.
// See: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
data = (*[1 << 30]byte)(unsafe.Pointer(buf.data))[:size:size]
err = proto.Unmarshal(data, list)
)
if err != nil {
return nil, nil, err
}
// Sort ops by name
sort.Slice(list.Op, func(i, j int) bool {
return list.Op[i].Name < list.Op[j].Name
})
apimap, err := newAPIDefMap(list)
return list, apimap, err
}
func updateAPIDefs(m *apiDefMap, dir string) error {
files, err := os.ReadDir(dir)
if err != nil {
return err
}
for _, file := range files {
if file.IsDir() || !strings.HasSuffix(file.Name(), ".pbtxt") {
continue
}
data, err := os.ReadFile(path.Join(dir, file.Name()))
if err != nil {
return fmt.Errorf("failed to read %q: %v", file.Name(), err)
}
if err = m.Put(string(data)); err != nil {
return fmt.Errorf("failed to process %q: %v", file.Name(), err)
}
}
return nil
}
func generateFunctionsForOps(w io.Writer, ops *odpb.OpList, apimap *apiDefMap) error {
thisPackage := reflect.TypeOf(tmplArgs{}).PkgPath()
if err := tmplHeader.Execute(w, thisPackage); err != nil {
return err
}
denylist := map[string]bool{
"Const": true,
"PyFunc": true,
"PyFuncStateless": true,
}
for _, op := range ops.Op {
if denylist[op.Name] {
continue
}
apidef, err := apimap.Get(op.Name)
if err != nil {
return err
}
if err := generateFunctionForOp(w, op, apidef); err != nil {
return err
}
}
return nil
}
func generateFunctionForOp(w io.Writer, op *odpb.OpDef, apidef *adpb.ApiDef) error {
if strings.HasPrefix(op.Name, "_") { // Internal operation
return nil
}
// Ignore operations where the Go types corresponding to the TensorFlow
// type haven't been worked out (such as "func"s).
for _, a := range op.Attr {
if _, err := goType(a.Type); err != nil {
return nil
}
}
// Also, haven't figured out reference types yet, so ignore those too.
for _, a := range op.InputArg {
if a.IsRef {
return nil
}
}
for _, a := range op.OutputArg {
if a.IsRef {
return nil
}
}
if apidef.Summary == "" {
// Undocumented operation, perhaps a sign of not being ready to
// export.
return nil
}
tmplArgs, err := newTmplArgs(op, apidef)
if err != nil {
return err
}
return tmplOp.Execute(w, tmplArgs)
}
var (
// Go keywords that cannot be used as identifiers.
// From https://golang.org/ref/spec#Keywords
keywords = []string{
"break", "default", "func", "interface", "select", "case",
"defer", "go", "map", "struct", "chan", "else", "goto",
"package", "switch", "const", "fallthrough", "if", "range",
"type", "continue", "for", "import", "return", "var",
}
tmplHeader = template.Must(template.New("header").Parse(`// DO NOT EDIT
// This file was machine generated by {{.}}
//
// WARNING: This generation of wrapper function for TensorFlow ops is in an
// experimental state. The generated API can change without notice.
package op
import tf "github.com/tensorflow/tensorflow/tensorflow/go"
// optionalAttr is an intentionally un-exported type to hide
// details of how optional attributes to operations are implemented.
type optionalAttr map[string]interface{}
func makeOutputList(op *tf.Operation, start int, output string) ([]tf.Output, int, error) {
size, err := op.OutputListSize(output)
if err != nil {
return nil, start, err
}
list := make([]tf.Output, size)
for i := 0; i < size; i++ {
list[i] = op.Output(start + i)
}
return list, start + size, nil
}
`))
tmplOp = template.Must(template.New("op").Funcs(template.FuncMap{
"MakeComment": makeComment,
"GoType": goType,
"CamelCase": camelCase,
"Identifier": identifier,
"IsListArg": isListArg,
"IsListAttr": isListAttr,
"MarshalProtoMessage": marshalProtoMessage,
}).Parse(`
{{if .OptionalAttrs -}}
{{/* Type for specifying all optional attributes. */ -}}
// {{.Op.Name}}Attr is an optional argument to {{.Op.Name}}.
type {{.Op.Name}}Attr func(optionalAttr)
{{range .OptionalAttrs}}
// {{$.Op.Name}}{{CamelCase .RenameTo}} sets the optional {{.RenameTo}} attribute to value.
{{- if .Description}}
//
// value: {{MakeComment .Description}}
{{- end}}
// If not specified, defaults to {{MarshalProtoMessage .DefaultValue}}
{{- if .HasMinimum}}
//
// {{if .IsListAttr }}REQUIRES: len(value) >= {{.Minimum}}{{else}}REQUIRES: value >= {{.Minimum}}{{end}}
{{- end}}
func {{$.Op.Name}}{{CamelCase .RenameTo}}(value {{GoType .Type}}) {{$.Op.Name}}Attr {
return func(m optionalAttr) {
m[{{printf "%q" .Name}}] = value
}
}
{{end}}
{{end}}
{{- /* Create a godoc friendly comment. */ -}}
// {{MakeComment .APIDef.Summary}}
{{- with .Op.Deprecation}}
//
// DEPRECATED at GraphDef version {{.Version}}: {{.Explanation}}
{{- end -}}
{{- with .APIDef.Description}}
//
// {{MakeComment .}}
{{- end -}}
{{- if .DescribeArguments}}
//
// Arguments:
{{- range .InArgsReordered}}
// {{if .Description}}{{Identifier .RenameTo}}: {{MakeComment .Description}}{{end}}
{{- end -}}
{{- range .RequiredAttrs}}
// {{if .Description}}{{Identifier .RenameTo}}: {{MakeComment .Description}}{{end}}
{{- end -}}
{{- end -}}
{{- if (not .Op.OutputArg) }}
//
// Returns the created operation.
{{- else }}
{{- if .DescribeOutputs}}
//
{{- if eq (len .OutArgs) 1 }}
// Returns {{range .OutArgs}}{{MakeComment .Description}}{{end}}
{{- else }}
// Returns:
{{- range .OutArgs}}
// {{Identifier .RenameTo}}{{if .Description}}: {{MakeComment .Description}}{{end}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- end -}}
{{- /*
The function signature.
Since OpDef.Name is in CamelCase, it cannot conflict with a reserved keyword in Golang
*/}}
func {{.Op.Name}}
{{- /*
Fill in input arguments:
(1) The Scope
(2) All input arguments (which may be either []tf.Output or tf.Output)
(3) All required attributes
(4) Variadic list of optional attributes
*/ -}}
(scope *Scope
{{- range $i, $a := .InArgsReordered}}, {{Identifier $a.RenameTo}} {{if $a.IsListArg}}[]{{end}}tf.Output{{end -}}
{{range $i, $a := .RequiredAttrs}}, {{Identifier $a.RenameTo}} {{GoType $a.Type}}{{end -}}
{{if .OptionalAttrs}}, optional ...{{.Op.Name}}Attr{{end -}}
)
{{- /* Construct outputs: len(.OutArgs) or a *tf.Operation */ -}}
{{if .OutArgs -}}
({{range $i,$a := .OutArgs}}{{if $i}}, {{end}}{{Identifier $a.RenameTo}} {{if $a.IsListArg}}[]{{end}}tf.Output{{end -}})
{{- else -}}
(o *tf.Operation)
{{- end }} {
if scope.Err() != nil {
return
}
{{if .HasAttrs -}}
attrs := map[string]interface{}{ {{- range .RequiredAttrs}}{{printf "%q" .Name}}: {{Identifier .RenameTo}},{{end}}}
{{if .OptionalAttrs -}}
for _, a := range optional {
a(attrs)
}
{{end -}}
{{end -}}
opspec := tf.OpSpec{
Type: {{printf "%q" .Op.Name}},
{{if .InArgs -}}
Input: []tf.Input{
{{range $i,$a := .InArgs}}{{if $a.IsListArg}}tf.OutputList({{Identifier $a.RenameTo}}){{else}}{{Identifier $a.RenameTo}}{{end}}, {{end}}
},
{{- end}}
{{- if .HasAttrs}}
Attrs: attrs,
{{- end}}
}
{{- if .OutArgs}}
{{- if .HasListOutput}}
op := scope.AddOperation(opspec)
if scope.Err() != nil {
return
}
var idx int
var err error
{{- range $i, $a := .OutArgs}}
{{- if $a.IsListArg}}
if {{Identifier .RenameTo}}, idx, err = makeOutputList(op, idx, {{printf "%q" .Name}}); err != nil {
scope.UpdateErr({{printf "%q" $.Op.Name}}, err)
return
}
{{- else }}
{{Identifier .RenameTo}} = op.Output(idx)
{{- end }}{{- /* if IsListArg */}}
{{- end }}{{- /* range .OutArgs */}}
return {{range $i, $a := .OutArgs}}{{if $i}}, {{end}}{{Identifier .RenameTo}}{{end}}
{{- else }}
op := scope.AddOperation(opspec)
return {{range $i, $a := .OutArgs}}{{if $i}}, {{end}}op.Output({{$i}}){{end}}
{{- end }}{{- /* if .HasListOutput */}}
{{- else }}
return scope.AddOperation(opspec)
{{- end }}{{- /* if .OutArgs */}}
}
`))
)
type attrWrapper struct {
op *odpb.OpDef_AttrDef
api *adpb.ApiDef_Attr
}
func (a *attrWrapper) Name() string { return a.api.Name }
func (a *attrWrapper) RenameTo() string { return a.api.RenameTo }
func (a *attrWrapper) Description() string { return a.api.Description }
func (a *attrWrapper) Type() string { return a.op.Type }
func (a *attrWrapper) IsListAttr() bool { return isListAttr(a.op) }
func (a *attrWrapper) HasMinimum() bool { return a.op.HasMinimum }
func (a *attrWrapper) Minimum() int64 { return a.op.Minimum }
func (a *attrWrapper) DefaultValue() interface{} { return a.api.DefaultValue }
type argWrapper struct {
op *odpb.OpDef_ArgDef
api *adpb.ApiDef_Arg
}
func (a *argWrapper) Name() string { return a.api.Name }
func (a *argWrapper) RenameTo() string { return a.api.RenameTo }
func (a *argWrapper) Description() string { return a.api.Description }
func (a *argWrapper) IsListArg() bool { return isListArg(a.op) }
type tmplArgs struct {
Op *odpb.OpDef
APIDef *adpb.ApiDef
// Op.Attr is split into two categories
// (1) Required: These must be specified by the client and are thus
// included in the function signature.
// (2) Optional: These need not be specified (as they have default
// values) and thus do not appear in the function signature.
RequiredAttrs []*attrWrapper
OptionalAttrs []*attrWrapper
InArgs []*argWrapper
// Input arguments ordered based on arg_order field of ApiDef.
InArgsReordered []*argWrapper
OutArgs []*argWrapper
}
func newTmplArgs(op *odpb.OpDef, apidef *adpb.ApiDef) (*tmplArgs, error) {
ret := tmplArgs{Op: op, APIDef: apidef}
// Setup InArgs field
for i, in := range op.InputArg {
argCombined := argWrapper{op: in, api: apidef.InArg[i]}
ret.InArgs = append(ret.InArgs, &argCombined)
}
// Setup OutArgs field
for i, out := range op.OutputArg {
argCombined := argWrapper{op: out, api: apidef.OutArg[i]}
ret.OutArgs = append(ret.OutArgs, &argCombined)
}
// Setup InArgsReordered field
for _, argName := range apidef.ArgOrder {
// Find the argument in op.InputArg
argIndex := -1
for i, in := range op.InputArg {
if in.Name == argName {
argIndex = i
break
}
}
if argIndex == -1 {
return nil, fmt.Errorf(
"couldn't find argument %s in ApiDef for op %s",
argName, op.Name)
}
argCombined := argWrapper{
op: op.InputArg[argIndex], api: apidef.InArg[argIndex]}
ret.InArgsReordered = append(ret.InArgsReordered, &argCombined)
}
if len(op.Attr) == 0 {
return &ret, nil
}
// Attributes related to the InputArg's type are inferred automatically
// and are not exposed to the client.
inferred := make(map[string]bool)
for _, in := range op.InputArg {
switch {
case in.TypeAttr != "":
inferred[in.TypeAttr] = true
case in.TypeListAttr != "":
inferred[in.TypeListAttr] = true
}
if in.NumberAttr != "" {
inferred[in.NumberAttr] = true
}
}
for i, attr := range op.Attr {
if inferred[attr.Name] {
continue
}
attrCombined := attrWrapper{op: attr, api: apidef.Attr[i]}
if attr.DefaultValue == nil {
ret.RequiredAttrs = append(ret.RequiredAttrs, &attrCombined)
} else {
ret.OptionalAttrs = append(ret.OptionalAttrs, &attrCombined)
}
}
return &ret, nil
}
func (a *tmplArgs) HasAttrs() bool { return len(a.RequiredAttrs)+len(a.OptionalAttrs) > 0 }
func (a *tmplArgs) DescribeArguments() bool {
for _, arg := range a.InArgs {
if arg.Description() != "" {
return true
}
}
for _, attr := range a.RequiredAttrs {
if attr.Description() != "" {
return true
}
}
return false
}
func (a *tmplArgs) DescribeOutputs() bool {
for _, arg := range a.OutArgs {
if arg.Description() != "" {
return true
}
}
return false
}
func (a *tmplArgs) HasListOutput() bool {
for _, arg := range a.OutArgs {
if arg.IsListArg() {
return true
}
}
return false
}
func makeComment(lines string) string {
return strings.Join(strings.SplitAfter(lines, "\n"), "// ")
}
// goType converts a TensorFlow "type" ('string', 'int', 'list(string)' etc.)
// to the corresponding type in Go.
func goType(tfType string) (string, error) {
list, tfType := parseTFType(tfType)
var gotype string
switch tfType {
case "int":
gotype = "int64"
case "float":
gotype = "float32"
case "bool":
gotype = "bool"
case "type":
gotype = "tf.DataType"
case "shape":
gotype = "tf.Shape"
case "tensor":
gotype = "tf.Tensor"
case "string":
gotype = "string"
default:
return "", fmt.Errorf("%q is not a recognized DataType", tfType)
}
if list {
gotype = "[]" + gotype
}
return gotype, nil
}
func camelCase(snakeCase string) string {
words := strings.Split(snakeCase, "_")
for i, w := range words {
words[i] = strings.ToUpper(string(w[0])) + w[1:]
}
return strings.Join(words, "")
}
// identifier creates an identifier for s usable in the generated Go source
// code.
//
// Avoids collisions with keywords and other identifiers used in the generated
// code.
func identifier(s string) string {
// Identifiers used in the generated code.
if s == "tf" || s == "scope" || s == "err" || s == "op" {
return s + "_"
}
for _, k := range keywords {
if s == k {
// Alternatively, make the first letter upper case.
return s + "_"
}
}
return s
}
func isListArg(argdef *odpb.OpDef_ArgDef) bool {
return argdef.TypeListAttr != "" || argdef.NumberAttr != ""
}
func isListAttr(attrdef *odpb.OpDef_AttrDef) bool {
list, _ := parseTFType(attrdef.Type)
return list
}
func marshalProtoMessage(m proto.Message) string {
// Marshal proto message to string.
o := prototext.MarshalOptions{Multiline: false}
x := o.Format(m)
// Remove superfluous whitespace, if present.
//
// Go protobuf output is purposefully unstable, randomly adding
// whitespace. See github.com/golang/protobuf/issues/1121
x = strings.ReplaceAll(x, " ", " ")
// Remove the prefix of the string up to the first colon.
//
// This is useful when 's' corresponds to a "oneof" protocol buffer
// message. For example, consider the protocol buffer message:
// oneof value { bool b = 1; int64 i = 2; }
// proto.CompactTextString) will print "b:true", or "i:7" etc. The
// following strips out the leading "b:" or "i:".
y := strings.SplitN(x, ":", 2)
if len(y) < 2 {
return x
}
return y[1]
}
func parseTFType(tfType string) (list bool, typ string) {
const (
listPrefix = "list("
listSuffix = ")"
)
if strings.HasPrefix(tfType, listPrefix) && strings.HasSuffix(tfType, listSuffix) {
return true, strings.TrimSuffix(strings.TrimPrefix(tfType, listPrefix), listSuffix)
}
return false, tfType
}
+824
View File
@@ -0,0 +1,824 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package internal
import (
"bytes"
"go/format"
"testing"
"google.golang.org/protobuf/encoding/prototext"
adpb "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/api_def_go_proto"
odpb "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/op_def_go_proto"
)
// Creates an ApiDef based on opdef and applies overrides
// from apidefText (ApiDef text proto).
func GetAPIDef(t *testing.T, opdef *odpb.OpDef, apidefText string) *adpb.ApiDef {
opdefList := &odpb.OpList{Op: []*odpb.OpDef{opdef}}
apimap, err := newAPIDefMap(opdefList)
if err != nil {
t.Fatal(err)
}
err = apimap.Put(apidefText)
if err != nil {
t.Fatal(err)
}
apidef, err := apimap.Get(opdef.Name)
if err != nil {
t.Fatal(err)
}
return apidef
}
// This test is disabled and should be rewritten. Serialized protocol buffers
// are not stable, see https://github.com/golang/protobuf/issues/1121
func TestGenerateOp(t *testing.T) {
// TestGenerateOp validates the generated source code for an op.
// The OpDef for the test cases are simplified forms of real ops.
testdata := []struct {
tag string
opdef string
apidef string
wanted string
}{
{
tag: "NoOp",
opdef: `
name: "NoOp"
`,
apidef: `
op: <
graph_op_name: "NoOp"
summary: "No. Op."
>
`,
wanted: `
// No. Op.
//
// Returns the created operation.
func NoOp(scope *Scope) (o *tf.Operation) {
if scope.Err() != nil {
return
}
opspec := tf.OpSpec{
Type: "NoOp",
}
return scope.AddOperation(opspec)
}
`,
},
{
tag: "NoAttributes",
opdef: `
name: "Add"
input_arg: <
name: "x"
type_attr: "T"
>
input_arg: <
name: "y"
type_attr: "T"
>
output_arg: <
name: "z"
type_attr: "T"
>
attr: <
name: "T"
type: "type"
allowed_values: <
list: <
type: DT_FLOAT
type: DT_INT64
>
>
>
`,
apidef: `
op: <
graph_op_name: "Add"
summary: "Returns x + y element-wise."
description: "Blah blah",
>
`,
wanted: `
// Returns x + y element-wise.
//
// Blah blah
func Add(scope *Scope, x tf.Output, y tf.Output) (z tf.Output) {
if scope.Err() != nil {
return
}
opspec := tf.OpSpec{
Type: "Add",
Input: []tf.Input{
x, y,
},
}
op := scope.AddOperation(opspec)
return op.Output(0)
}
`,
},
{
tag: "RequiredAttributes",
opdef: `
name: "Cast"
input_arg: <
name: "x"
type_attr: "SrcT"
>
output_arg: <
name: "y"
type_attr: "DstT"
>
attr: <
name: "SrcT"
type: "type"
>
attr: <
name: "DstT"
type: "type"
>
`,
apidef: `
op: <
graph_op_name: "Cast"
summary: "Cast x of type SrcT to y of DstT."
>
`,
wanted: `
// Cast x of type SrcT to y of DstT.
func Cast(scope *Scope, x tf.Output, DstT tf.DataType) (y tf.Output) {
if scope.Err() != nil {
return
}
attrs := map[string]interface{}{"DstT": DstT}
opspec := tf.OpSpec{
Type: "Cast",
Input: []tf.Input{
x,
},
Attrs: attrs,
}
op := scope.AddOperation(opspec)
return op.Output(0)
}
`,
},
{
tag: "OptionalAttributes",
opdef: `
name: "DecodeJpeg"
input_arg: <
name: "contents"
type: DT_STRING
>
output_arg: <
name: "image"
type: DT_UINT8
>
attr: <
name: "channels"
type: "int"
default_value: <
i: 0
>
>
attr: <
name: "fancy_upscaling"
type: "bool"
default_value: <
b: true
>
>
attr: <
name: "acceptable_fraction"
type: "float"
default_value: <
f: 1
>
>
`,
apidef: `
op: <
graph_op_name: "DecodeJpeg"
in_arg: <
name: "contents"
description: "0-D. The JPEG-encoded image."
>
out_arg: <
name: "image"
description: "3-D with shape [height, width, channels]"
>
attr: <
name: "channels"
description: "Number of color channels for the decoded image."
>
attr: <
name: "fancy_upscaling"
description: "If true use a slower but nicer upscaling of the\nchroma planes (yuv420/422 only)."
>
attr: <
name: "acceptable_fraction"
description: "The minimum required fraction of lines before a truncated\ninput is accepted."
>
summary: "Decode a JPEG-encoded image to a uint8 tensor."
description: "Norna dorna fjord\nkajorna\nhahaha"
>
`,
wanted: `
// DecodeJpegAttr is an optional argument to DecodeJpeg.
type DecodeJpegAttr func(optionalAttr)
// DecodeJpegChannels sets the optional channels attribute to value.
//
// value: Number of color channels for the decoded image.
// If not specified, defaults to 0
func DecodeJpegChannels(value int64) DecodeJpegAttr {
return func(m optionalAttr) {
m["channels"] = value
}
}
// DecodeJpegFancyUpscaling sets the optional fancy_upscaling attribute to value.
//
// value: If true use a slower but nicer upscaling of the
// chroma planes (yuv420/422 only).
// If not specified, defaults to true
func DecodeJpegFancyUpscaling(value bool) DecodeJpegAttr {
return func(m optionalAttr) {
m["fancy_upscaling"] = value
}
}
// DecodeJpegAcceptableFraction sets the optional acceptable_fraction attribute to value.
//
// value: The minimum required fraction of lines before a truncated
// input is accepted.
// If not specified, defaults to 1
func DecodeJpegAcceptableFraction(value float32) DecodeJpegAttr {
return func(m optionalAttr) {
m["acceptable_fraction"] = value
}
}
// Decode a JPEG-encoded image to a uint8 tensor.
//
// Norna dorna fjord
// kajorna
// hahaha
//
// Arguments:
// contents: 0-D. The JPEG-encoded image.
//
// Returns 3-D with shape [height, width, channels]
func DecodeJpeg(scope *Scope, contents tf.Output, optional ...DecodeJpegAttr) (image tf.Output) {
if scope.Err() != nil {
return
}
attrs := map[string]interface{}{}
for _, a := range optional {
a(attrs)
}
opspec := tf.OpSpec{
Type: "DecodeJpeg",
Input: []tf.Input{
contents,
},
Attrs: attrs,
}
op := scope.AddOperation(opspec)
return op.Output(0)
}
`,
},
{
tag: "MultipleOutputs",
opdef: `
name: "TwoOutputs"
input_arg: <
name: "input"
type_attr: "T"
>
output_arg <
name: "x"
type_attr: "T"
>
output_arg <
name: "y"
type_attr: "T"
>
attr: <
name: "T"
type: "type"
>
`,
apidef: `
op: <
graph_op_name: "TwoOutputs"
summary: "Op that produces multiple outputs"
>
`,
wanted: `
// Op that produces multiple outputs
func TwoOutputs(scope *Scope, input tf.Output) (x tf.Output, y tf.Output) {
if scope.Err() != nil {
return
}
opspec := tf.OpSpec{
Type: "TwoOutputs",
Input: []tf.Input{
input,
},
}
op := scope.AddOperation(opspec)
return op.Output(0), op.Output(1)
}
`,
},
{
tag: "ListOutput",
opdef: `
name: "ShapeN"
input_arg: <
name: "input"
type_attr: "T"
number_attr: "N"
>
output_arg: <
name: "output"
type_attr: "out_type"
number_attr: "N"
>
attr: <
name: "N"
type: "int"
has_minimum: true
minimum: 1
>
attr: <
name: "T"
type: "type"
>
attr: <
name: "out_type"
type: "type"
default_value: <
type: DT_INT32
>
allowed_values: <
list: <
type: DT_INT32
type: DT_INT64
>
>
>
`,
apidef: `
op: <
graph_op_name: "ShapeN"
summary: "Returns shape of tensors."
description: "Some description here."
>
`,
wanted: `
// ShapeNAttr is an optional argument to ShapeN.
type ShapeNAttr func(optionalAttr)
// ShapeNOutType sets the optional out_type attribute to value.
// If not specified, defaults to DT_INT32
func ShapeNOutType(value tf.DataType) ShapeNAttr {
return func(m optionalAttr) {
m["out_type"] = value
}
}
// Returns shape of tensors.
//
// Some description here.
func ShapeN(scope *Scope, input []tf.Output, optional ...ShapeNAttr) (output []tf.Output) {
if scope.Err() != nil {
return
}
attrs := map[string]interface{}{}
for _, a := range optional {
a(attrs)
}
opspec := tf.OpSpec{
Type: "ShapeN",
Input: []tf.Input{
tf.OutputList(input),
},
Attrs: attrs,
}
op := scope.AddOperation(opspec)
if scope.Err() != nil {
return
}
var idx int
var err error
if output, idx, err = makeOutputList(op, idx, "output"); err != nil {
scope.UpdateErr("ShapeN", err)
return
}
return output
}
`,
},
{
tag: "ApiDefOverrides",
opdef: `
name: "TestOp"
input_arg: <
name: "a"
type: DT_STRING
>
input_arg: <
name: "b"
type: DT_STRING
>
output_arg: <
name: "c"
type: DT_UINT8
>
attr: <
name: "d"
type: "int"
default_value: <
i: 0
>
>
`,
apidef: `
op: <
graph_op_name: "TestOp"
in_arg: <
name: "a"
rename_to: "aa"
description: "Description for aa."
>
in_arg: <
name: "b"
rename_to: "bb"
description: "Description for bb."
>
arg_order: "b"
arg_order: "a"
out_arg: <
name: "c"
rename_to: "cc"
description: "Description for cc."
>
attr: <
name: "d"
rename_to: "dd"
description: "Description for dd."
>
summary: "Summary for TestOp."
description: "Description for TestOp."
>
`,
wanted: `
// TestOpAttr is an optional argument to TestOp.
type TestOpAttr func(optionalAttr)
// TestOpDd sets the optional dd attribute to value.
//
// value: Description for dd.
// If not specified, defaults to 0
func TestOpDd(value int64) TestOpAttr {
return func(m optionalAttr) {
m["d"] = value
}
}
// Summary for TestOp.
//
// Description for TestOp.
//
// Arguments:
// bb: Description for bb.
// aa: Description for aa.
//
// Returns Description for cc.
func TestOp(scope *Scope, bb tf.Output, aa tf.Output, optional ...TestOpAttr) (cc tf.Output) {
if scope.Err() != nil {
return
}
attrs := map[string]interface{}{}
for _, a := range optional {
a(attrs)
}
opspec := tf.OpSpec{
Type: "TestOp",
Input: []tf.Input{
aa, bb,
},
Attrs: attrs,
}
op := scope.AddOperation(opspec)
return op.Output(0)
}
`,
},
{
tag: "SampleDistortedBoundingBox",
opdef: `
name: "SampleDistortedBoundingBox"
input_arg {
name: "image_size"
type_attr: "T"
}
input_arg {
name: "bounding_boxes"
type: DT_FLOAT
}
output_arg {
name: "begin"
type_attr: "T"
}
output_arg {
name: "size"
type_attr: "T"
}
output_arg {
name: "bboxes"
type: DT_FLOAT
}
attr {
name: "T"
type: "type"
allowed_values {
list {
type: DT_UINT8
type: DT_INT8
type: DT_INT16
type: DT_INT32
type: DT_INT64
}
}
}
attr {
name: "seed"
type: "int"
default_value {
i: 0
}
}
attr {
name: "seed2"
type: "int"
default_value {
i: 0
}
}
attr {
name: "min_object_covered"
type: "float"
default_value {
f: 0.1
}
}
attr {
name: "aspect_ratio_range"
type: "list(float)"
default_value {
list {
f: 0.75
f: 1.33
}
}
}
attr {
name: "area_range"
type: "list(float)"
default_value {
list {
f: 0.05
f: 1
}
}
}
attr {
name: "max_attempts"
type: "int"
default_value {
i: 100
}
}
attr {
name: "use_image_if_no_bounding_boxes"
type: "bool"
default_value {
b: false
}
}
is_stateful: true
`,
apidef: `
op {
graph_op_name: "SampleDistortedBoundingBox"
in_arg {
name: "image_size"
description: "Blah blah"
}
in_arg {
name: "bounding_boxes"
description: "Blah blah"
}
out_arg {
name: "begin"
description: "Blah blah"
}
out_arg {
name: "size"
description: "Blah blah"
}
out_arg {
name: "bboxes"
description: "Blah blah"
}
attr {
name: "seed"
description: "Blah blah"
}
attr {
name: "seed2"
description: "Blah blah"
}
attr {
name: "min_object_covered"
description: "Blah blah"
}
attr {
name: "aspect_ratio_range"
description: "Blah blah"
}
attr {
name: "area_range"
description: "Blah blah"
}
attr {
name: "max_attempts"
description: "Blah blah"
}
attr {
name: "use_image_if_no_bounding_boxes"
description: "Blah blah"
}
summary: "Generate a single randomly distorted bounding box for an image."
description: "Blah blah"
}
`,
wanted: `
// SampleDistortedBoundingBoxAttr is an optional argument to SampleDistortedBoundingBox.
type SampleDistortedBoundingBoxAttr func(optionalAttr)
// SampleDistortedBoundingBoxSeed sets the optional seed attribute to value.
//
// value: Blah blah
// If not specified, defaults to 0
func SampleDistortedBoundingBoxSeed(value int64) SampleDistortedBoundingBoxAttr {
return func(m optionalAttr) {
m["seed"] = value
}
}
// SampleDistortedBoundingBoxSeed2 sets the optional seed2 attribute to value.
//
// value: Blah blah
// If not specified, defaults to 0
func SampleDistortedBoundingBoxSeed2(value int64) SampleDistortedBoundingBoxAttr {
return func(m optionalAttr) {
m["seed2"] = value
}
}
// SampleDistortedBoundingBoxMinObjectCovered sets the optional min_object_covered attribute to value.
//
// value: Blah blah
// If not specified, defaults to 0.1
func SampleDistortedBoundingBoxMinObjectCovered(value float32) SampleDistortedBoundingBoxAttr {
return func(m optionalAttr) {
m["min_object_covered"] = value
}
}
// SampleDistortedBoundingBoxAspectRatioRange sets the optional aspect_ratio_range attribute to value.
//
// value: Blah blah
// If not specified, defaults to {f:0.75 f:1.33}
func SampleDistortedBoundingBoxAspectRatioRange(value []float32) SampleDistortedBoundingBoxAttr {
return func(m optionalAttr) {
m["aspect_ratio_range"] = value
}
}
// SampleDistortedBoundingBoxAreaRange sets the optional area_range attribute to value.
//
// value: Blah blah
// If not specified, defaults to {f:0.05 f:1}
func SampleDistortedBoundingBoxAreaRange(value []float32) SampleDistortedBoundingBoxAttr {
return func(m optionalAttr) {
m["area_range"] = value
}
}
// SampleDistortedBoundingBoxMaxAttempts sets the optional max_attempts attribute to value.
//
// value: Blah blah
// If not specified, defaults to 100
func SampleDistortedBoundingBoxMaxAttempts(value int64) SampleDistortedBoundingBoxAttr {
return func(m optionalAttr) {
m["max_attempts"] = value
}
}
// SampleDistortedBoundingBoxUseImageIfNoBoundingBoxes sets the optional use_image_if_no_bounding_boxes attribute to value.
//
// value: Blah blah
// If not specified, defaults to false
func SampleDistortedBoundingBoxUseImageIfNoBoundingBoxes(value bool) SampleDistortedBoundingBoxAttr {
return func(m optionalAttr) {
m["use_image_if_no_bounding_boxes"] = value
}
}
// Generate a single randomly distorted bounding box for an image.
//
// Blah blah
//
// Arguments:
// image_size: Blah blah
// bounding_boxes: Blah blah
//
// Returns:
// begin: Blah blah
// size: Blah blah
// bboxes: Blah blah
func SampleDistortedBoundingBox(scope *Scope, image_size tf.Output, bounding_boxes tf.Output, optional ...SampleDistortedBoundingBoxAttr) (begin tf.Output, size tf.Output, bboxes tf.Output) {
if scope.Err() != nil {
return
}
attrs := map[string]interface{}{}
for _, a := range optional {
a(attrs)
}
opspec := tf.OpSpec{
Type: "SampleDistortedBoundingBox",
Input: []tf.Input{
image_size, bounding_boxes,
},
Attrs: attrs,
}
op := scope.AddOperation(opspec)
return op.Output(0), op.Output(1), op.Output(2)
}
`,
},
}
for _, test := range testdata {
t.Run(test.tag, func(t *testing.T) {
var opdef odpb.OpDef
var apidef *adpb.ApiDef
var buf bytes.Buffer
if err := prototext.Unmarshal([]byte(test.opdef), &opdef); err != nil {
t.Fatal(err)
}
apidef = GetAPIDef(t, &opdef, test.apidef)
if err := generateFunctionForOp(&buf, &opdef, apidef); err != nil {
t.Fatal(err)
}
got, err := format.Source(buf.Bytes())
if err != nil {
t.Fatalf("Unable to format: %v\n%s", err, buf.Bytes())
}
want, err := format.Source([]byte(test.wanted))
if err != nil {
t.Fatalf("Unable to format: %v\n%s", err, test.wanted)
}
if !bytes.Equal(got, want) {
t.Fatalf("Got:\n%s\nWant:\n%s\n", got, want)
}
})
}
}
+22
View File
@@ -0,0 +1,22 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package internal generates Go source code with functions for TensorFlow operations.
package internal
// #cgo LDFLAGS: -ltensorflow
// #cgo CFLAGS: -I${SRCDIR}/../../../../
import "C"
+69
View File
@@ -0,0 +1,69 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Command genop generates a Go source file with functions for TensorFlow ops.
package main
import (
"bytes"
"flag"
"go/format"
"log"
"os"
"path/filepath"
"strings"
"github.com/tensorflow/tensorflow/tensorflow/go/genop/internal"
)
func main() {
var (
filename = flag.String("outfile", "", "File to write generated source code to.")
header = flag.String("header", "", "Path to a file whose contents will be copied into the generated file. Can be empty")
apiDefDirs = flag.String("api_def_dirs", "", "Comma-separated directories containing api_def_*.pbtxt files.")
buf bytes.Buffer
)
flag.Parse()
if *filename == "" {
log.Fatal("-outfile must be set")
}
if *header != "" {
hdr, err := os.ReadFile(*header)
if err != nil {
log.Fatalf("Unable to read %s: %v", *header, err)
}
buf.Write(hdr)
buf.WriteString("\n\n")
}
os.MkdirAll(filepath.Dir(*filename), 0755)
apiDefDirsList := []string{}
if len(*apiDefDirs) > 0 {
apiDefDirsList = strings.Split(*apiDefDirs, ",")
}
if err := internal.GenerateFunctionsForRegisteredOps(
&buf, apiDefDirsList); err != nil {
log.Fatal(err)
}
formatted, err := format.Source(buf.Bytes())
if err != nil {
log.Fatalf("Failed to generate valid source? 'go fmt' failed: %v", err)
}
if err := os.WriteFile(*filename, formatted, 0644); err != nil {
log.Fatalf("Failed to write to %q: %v", *filename, err)
}
}
+528
View File
@@ -0,0 +1,528 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
// #include "tensorflow/c/c_api.h"
//
// #include <stdlib.h>
// #include <string.h>
//
// void TF_SetAttrShapeList_Helper(TF_OperationDescription* desc,
// const char* attr_name,
// const int64_t* flat_dims,
// const int* num_dims,
// int num_shapes) {
// const int64_t** dims =
// (const int64_t**)malloc(sizeof(const int64_t*) * num_shapes);
// int i = 0;
// for (i = 0; i < num_shapes; i++) {
// dims[i] = flat_dims;
// if (num_dims[i] > 0) {
// // flat_dims will be NULL iff num_shapes is 0 or all elements in num_dims are <= 0.
// flat_dims += num_dims[i];
// }
// }
// TF_SetAttrShapeList(desc, attr_name, dims, num_dims, num_shapes);
// free(dims);
// }
import "C"
import (
"fmt"
"io"
"runtime"
"unsafe"
)
// Graph represents a computation graph. Graphs may be shared between sessions.
type Graph struct {
c *C.TF_Graph
}
// The GraphImportOptions struct holds parameters for the ImportWithOptions function.
type GraphImportOptions struct {
// Node prefix
Prefix string
// Execution device
Device string
// inputMapping defines a mapping between Outputs in the graph
// and Outputs they should be replaced with.
inputMapping map[struct {
Name string
Index int
}]Output
// TODO: extend this structure to support more options from TF_ImportGraphDefOptions
}
// AddInputMapping adds a mapping between an Output in the imported graph
// and an Output in the destination graph that it should be replaced with,
// where src:srcIndex is the name of the Operation and Output index to
// replace and dst is the output to replace it with.
func (o *GraphImportOptions) AddInputMapping(src string, srcIndex int, dst Output) {
if o.inputMapping == nil {
o.inputMapping = make(map[struct {
Name string
Index int
}]Output)
}
o.inputMapping[struct {
Name string
Index int
}{src, srcIndex}] = dst
}
// NewGraph returns a new Graph.
func NewGraph() *Graph {
g := &Graph{C.TF_NewGraph()}
runtime.SetFinalizer(g, (*Graph).finalizer)
return g
}
func (g *Graph) finalizer() {
C.TF_DeleteGraph(g.c)
}
// WriteTo writes out a serialized representation of g to w.
//
// Implements the io.WriterTo interface.
func (g *Graph) WriteTo(w io.Writer) (int64, error) {
buf := C.TF_NewBuffer()
defer C.TF_DeleteBuffer(buf)
status := newStatus()
C.TF_GraphToGraphDef(g.c, buf, status.c)
if err := status.Err(); err != nil {
return 0, err
}
if buf.length > (1 << 30) {
// For very large graphs, the writes can be chunked.
// Punt on that for now.
return 0, fmt.Errorf("Graph is too large to write out, Graph.WriteTo needs to be updated")
}
// A []byte slice backed by C memory.
// See: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
length := int(buf.length)
var slice []byte
if unsafe.Sizeof(unsafe.Pointer(nil)) == 8 {
slice = (*[1<<50 - 1]byte)(unsafe.Pointer(buf.data))[:length:length]
} else {
slice = (*[1 << 30]byte)(unsafe.Pointer(buf.data))[:length:length]
}
n, err := w.Write(slice)
return int64(n), err
}
// ImportWithOptions imports the nodes and edges from a serialized representation of
// another Graph into g.
//
// Multiple options can be specified for the newly imported nodes.
func (g *Graph) ImportWithOptions(def []byte, options GraphImportOptions) error {
cprefix := C.CString(options.Prefix)
defer C.free(unsafe.Pointer(cprefix))
opts := C.TF_NewImportGraphDefOptions()
defer C.TF_DeleteImportGraphDefOptions(opts)
C.TF_ImportGraphDefOptionsSetPrefix(opts, cprefix)
if len(options.Device) != 0 {
cdev := C.CString(options.Device)
defer C.free(unsafe.Pointer(cdev))
C.TF_ImportGraphDefOptionsSetDefaultDevice(opts, cdev)
}
for src, dst := range options.inputMapping {
cSrcName := C.CString(src.Name)
C.TF_ImportGraphDefOptionsAddInputMapping(opts, cSrcName, C.int(src.Index), dst.c())
C.free(unsafe.Pointer(cSrcName))
}
buf := C.TF_NewBuffer()
defer C.TF_DeleteBuffer(buf)
buf.length = C.size_t(len(def))
buf.data = C.CBytes(def)
if buf.data == nil {
return fmt.Errorf("unable to allocate memory")
}
defer C.free(buf.data)
status := newStatus()
C.TF_GraphImportGraphDef(g.c, buf, opts, status.c)
if err := status.Err(); err != nil {
return err
}
return nil
}
// Import imports the nodes and edges from a serialized representation of
// another Graph into g.
//
// Names of imported nodes will be prefixed with prefix.
func (g *Graph) Import(def []byte, prefix string) error {
return g.ImportWithOptions(def, GraphImportOptions{Prefix: prefix})
}
// Operation returns the Operation named name in the Graph, or nil if no such
// operation is present.
func (g *Graph) Operation(name string) *Operation {
cname := C.CString(name)
defer C.free(unsafe.Pointer(cname))
cop := C.TF_GraphOperationByName(g.c, cname)
if cop == nil {
return nil
}
return &Operation{cop, g}
}
// Operations returns a list of all operations in the graph
func (g *Graph) Operations() []Operation {
var pos C.size_t
ops := []Operation{}
for {
cop := C.TF_GraphNextOperation(g.c, &pos)
if cop == nil {
break
}
ops = append(ops, Operation{cop, g})
}
return ops
}
// AddGradients adds operations to compute the partial derivatives of the sum of tensors in y
// with respect to tensors in x, i.e., d(y[0] + y[1] + ...) / d x[0], d(y[0] + y[1] + ... ) / d x[1] etc.
//
// prefix, if non-empty, is the name prefix used for all operations added to the graph to compute
// these gradients.
func (g *Graph) AddGradients(prefix string, y []Output, x []Output, dx []Output) ([]Output, error) {
var (
cprefix *C.char
cy = make([]C.TF_Output, len(y))
cx = make([]C.TF_Output, len(x))
cdx = make([]C.TF_Output, len(dx))
cdy = make([]C.TF_Output, len(x))
pcy *C.TF_Output
pcx *C.TF_Output
pcdx *C.TF_Output
pcdy *C.TF_Output
status = newStatus()
)
if len(y) > 0 {
pcy = &cy[0]
for i, o := range y {
cy[i] = o.c()
}
}
if len(x) > 0 {
pcx = &cx[0]
for i, o := range x {
cx[i] = o.c()
}
pcdy = &cdy[0]
}
if len(dx) > 0 {
pcdx = &cdx[0]
for i, o := range dx {
cdx[i] = o.c()
}
}
// If prefix is "", the C.TF_AddGradientsWithPrefix need cprefix to be nil but not ""
if len(prefix) != 0 {
cprefix = C.CString(prefix)
defer C.free(unsafe.Pointer(cprefix))
}
C.TF_AddGradientsWithPrefix(g.c, cprefix, pcy, C.int(len(y)), pcx, C.int(len(x)), pcdx, status.c, pcdy)
if err := status.Err(); err != nil {
return nil, err
}
dy := make([]Output, len(x))
for i, co := range cdy {
op := &Operation{co.oper, g}
dy[i] = Output{op, int(co.index)}
}
return dy, nil
}
// OpSpec is the specification of an Operation to be added to a Graph
// (using Graph.AddOperation).
type OpSpec struct {
// Type of the operation (e.g., "Add", "MatMul").
Type string
// Name by which the added operation will be referred to in the Graph.
// If omitted, defaults to Type.
Name string
// Inputs to this operation, which in turn must be outputs
// of other operations already added to the Graph.
//
// An operation may have multiple inputs with individual inputs being
// either a single tensor produced by another operation or a list of
// tensors produced by multiple operations. For example, the "Concat"
// operation takes two inputs: (1) the dimension along which to
// concatenate and (2) a list of tensors to concatenate. Thus, for
// Concat, len(Input) must be 2, with the first element being an Output
// and the second being an OutputList.
Input []Input
// Map from attribute name to its value that will be attached to this
// operation.
Attrs map[string]interface{}
// Operations that must be executed before executing the operation
// being added.
ControlDependencies []*Operation
// The device on which the operation should be executed.
// If omitted, an appropriate device will automatically be selected.
//
// For example, if set of "/device:GPU:0", then the operation will
// execute on GPU #0.
Device string
// Other possible fields: ColocateWith.
}
// AddOperation adds an operation to g.
func (g *Graph) AddOperation(args OpSpec) (*Operation, error) {
if args.Name == "" {
args.Name = args.Type
}
cname := C.CString(args.Name)
ctype := C.CString(args.Type)
cdesc := C.TF_NewOperation(g.c, ctype, cname)
C.free(unsafe.Pointer(cname))
C.free(unsafe.Pointer(ctype))
for _, in := range args.Input {
switch in := in.(type) {
case Output:
C.TF_AddInput(cdesc, in.c())
case OutputList:
size := len(in)
list := make([]C.TF_Output, size)
for i, v := range in {
list[i] = v.c()
}
if size > 0 {
C.TF_AddInputList(cdesc, &list[0], C.int(size))
} else {
C.TF_AddInputList(cdesc, nil, 0)
}
}
}
for _, in := range args.ControlDependencies {
C.TF_AddControlInput(cdesc, in.c)
}
status := newStatus()
for name, value := range args.Attrs {
if err := setAttr(cdesc, status, name, value); err != nil {
// Memory leak here as the TF_OperationDescription
// object will not be cleaned up. At the time of this
// writing, this was next to impossible since it
// required value to be a string tensor with
// incorrectly encoded strings. Given this rarity, live
// with the memory leak. If it becomes a real problem,
// consider adding a TF_DeleteOperationDescription
// function to the C API.
return nil, fmt.Errorf("%v (memory will be leaked)", err)
}
}
if len(args.Device) > 0 {
cdevice := C.CString(args.Device)
C.TF_SetDevice(cdesc, cdevice)
C.free(unsafe.Pointer(cdevice))
}
c := C.TF_FinishOperation(cdesc, status.c)
if err := status.Err(); err != nil {
return nil, err
}
return &Operation{c, g}, nil
}
func setAttr(cdesc *C.TF_OperationDescription, status *status, name string, value interface{}) error {
cAttrName := C.CString(name)
defer C.free(unsafe.Pointer(cAttrName))
switch value := value.(type) {
case string:
cstr := C.CString(value)
C.TF_SetAttrString(cdesc, cAttrName, unsafe.Pointer(cstr), C.size_t(len(value)))
C.free(unsafe.Pointer(cstr))
case []string:
size := len(value)
list := make([]unsafe.Pointer, size)
lens := make([]C.size_t, size)
for i, s := range value {
list[i] = unsafe.Pointer(C.CString(s))
lens[i] = C.size_t(len(s))
}
if size > 0 {
C.TF_SetAttrStringList(cdesc, cAttrName, &list[0], &lens[0], C.int(size))
} else {
C.TF_SetAttrStringList(cdesc, cAttrName, nil, nil, 0)
}
for _, s := range list {
C.free(s)
}
case int64:
C.TF_SetAttrInt(cdesc, cAttrName, C.int64_t(value))
case []int64:
size := len(value)
list := make([]C.int64_t, size)
for i, v := range value {
list[i] = C.int64_t(v)
}
if size > 0 {
C.TF_SetAttrIntList(cdesc, cAttrName, &list[0], C.int(size))
} else {
C.TF_SetAttrIntList(cdesc, cAttrName, nil, 0)
}
case float32:
C.TF_SetAttrFloat(cdesc, cAttrName, C.float(value))
case []float32:
size := len(value)
list := make([]C.float, size)
for i, v := range value {
list[i] = C.float(v)
}
if size > 0 {
C.TF_SetAttrFloatList(cdesc, cAttrName, &list[0], C.int(size))
} else {
C.TF_SetAttrFloatList(cdesc, cAttrName, nil, 0)
}
case bool:
v := C.uchar(0)
if value {
v = 1
}
C.TF_SetAttrBool(cdesc, cAttrName, v)
case []bool:
size := len(value)
list := make([]C.uchar, size)
for i, v := range value {
if v {
list[i] = 1
}
}
if size > 0 {
C.TF_SetAttrBoolList(cdesc, cAttrName, &list[0], C.int(size))
} else {
C.TF_SetAttrBoolList(cdesc, cAttrName, nil, 0)
}
case DataType:
C.TF_SetAttrType(cdesc, cAttrName, C.TF_DataType(value))
case []DataType:
var list *C.TF_DataType
if len(value) > 0 {
list = (*C.TF_DataType)(&value[0])
}
C.TF_SetAttrTypeList(cdesc, cAttrName, list, C.int(len(value)))
case *Tensor:
C.TF_SetAttrTensor(cdesc, cAttrName, value.c, status.c)
if err := status.Err(); err != nil {
return fmt.Errorf("bad value for attribute %q: %v", name, err)
}
case []*Tensor:
size := len(value)
list := make([]*C.TF_Tensor, size)
for i, v := range value {
list[i] = v.c
}
var plist **C.TF_Tensor
if size > 0 {
plist = &list[0]
}
C.TF_SetAttrTensorList(cdesc, cAttrName, plist, C.int(size), status.c)
if err := status.Err(); err != nil {
return fmt.Errorf("bad value for attribute %q: %v", name, err)
}
case Shape:
ndims := C.int(value.NumDimensions())
var dimsp *C.int64_t
if ndims > 0 {
dims := make([]C.int64_t, ndims)
for i, d := range value.dims {
dims[i] = C.int64_t(d)
}
dimsp = &dims[0]
}
C.TF_SetAttrShape(cdesc, cAttrName, dimsp, ndims)
case []Shape:
if len(value) == 0 {
C.TF_SetAttrShapeList(cdesc, cAttrName, nil, nil, 0)
} else {
var flatDims []C.int64_t
ndims := make([]C.int, len(value))
for i, s := range value {
nd := s.NumDimensions()
ndims[i] = C.int(nd)
for _, d := range s.dims {
flatDims = append(flatDims, C.int64_t(d))
}
}
var flatDimsp *C.int64_t
if len(flatDims) > 0 {
flatDimsp = &flatDims[0]
}
C.TF_SetAttrShapeList_Helper(cdesc, cAttrName, flatDimsp, &ndims[0], C.int(len(value)))
}
default:
return fmt.Errorf("attribute %q has a type (%T) which is not valid for operation attributes", name, value)
}
return nil
}
type LibraryHandler struct {
cptr *C.TF_Library
}
// Load library content into current context, useful to load ops implementation into non-monolithic TF build. Returns LibraryHandler or nil and error
func LoadLibrary(path string) (*LibraryHandler, error) {
status := newStatus()
cpath := C.CString(path)
defer C.free(unsafe.Pointer(cpath))
cptr := C.TF_LoadLibrary(cpath, status.c)
if cptr == nil || status.Code() != C.TF_OK {
return nil, fmt.Errorf("could not load library %s: code: %d, error: %s", path, status.Code(), status.String())
}
lh := &LibraryHandler{
cptr: cptr,
}
runtime.SetFinalizer(lh, (*LibraryHandler).free)
return lh, nil
}
func (lh *LibraryHandler) free() {
if lh == nil || lh.cptr == nil {
return
}
C.TF_DeleteLibraryHandle(lh.cptr)
}
+414
View File
@@ -0,0 +1,414 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
import (
"bytes"
"fmt"
"strings"
"testing"
)
func hasOperations(g *Graph, ops ...string) error {
var missing []string
for _, op := range ops {
if g.Operation(op) == nil {
missing = append(missing, op)
}
}
if len(missing) != 0 {
return fmt.Errorf("Graph does not have the operations %v", missing)
}
inList := map[string]bool{}
for _, op := range g.Operations() {
inList[op.Name()] = true
}
for _, op := range ops {
if !inList[op] {
missing = append(missing, op)
}
}
if len(missing) != 0 {
return fmt.Errorf("Operations %v are missing from graph.Operations()", missing)
}
return nil
}
func TestGraphWriteToAndImport(t *testing.T) {
// Construct a graph
g := NewGraph()
v, err := NewTensor(int64(1))
if err != nil {
t.Fatal(err)
}
input, err := Placeholder(g, "input", v.DataType())
if err != nil {
t.Fatal(err)
}
if _, err := Neg(g, "neg", input); err != nil {
t.Fatal(err)
}
// Serialize the graph
buf := new(bytes.Buffer)
if _, err := g.WriteTo(buf); err != nil {
t.Fatal(err)
}
// Import it into the same graph, with a prefix
if err := g.Import(buf.Bytes(), "imported"); err != nil {
t.Error(err)
}
if err := hasOperations(g, "input", "neg", "imported/input", "imported/neg"); err != nil {
t.Error(err)
}
}
func TestGraphInputMapping(t *testing.T) {
// Construct a graph
g := NewGraph()
v, err := NewTensor(int64(1))
if err != nil {
t.Fatal(err)
}
input, err := Placeholder(g, "input", v.DataType())
if err != nil {
t.Fatal(err)
}
neg, err := Neg(g, "neg", input)
if err != nil {
t.Fatal(err)
}
// Serialize the graph
buf := new(bytes.Buffer)
if _, err := g.WriteTo(buf); err != nil {
t.Fatal(err)
}
g = NewGraph()
v, err = NewTensor(int64(1))
if err != nil {
t.Fatal(err)
}
replacement, err := Placeholder(g, "replacement", v.DataType())
if err != nil {
t.Fatal(err)
}
options := GraphImportOptions{
Prefix: "imported",
}
options.AddInputMapping("input", 0, replacement)
// Import it into the same graph, with a prefix and replacement
if err := g.ImportWithOptions(buf.Bytes(), options); err != nil {
t.Error(err)
}
if err := hasOperations(g, "replacement", "imported/neg"); err != nil {
t.Error(err)
}
sess, err := NewSession(g, nil)
if err != nil {
t.Fatal(err)
}
neg = g.Operation("imported/neg").Output(0)
outputs, err := sess.Run(
map[Output]*Tensor{replacement: v},
[]Output{neg},
nil)
if err != nil {
t.Fatal(err)
}
if len(outputs) != 1 {
t.Fatal(len(outputs))
}
if outputs[0].Value().(int64) != -1 {
t.Fatalf("Got %v, wanted int64 -1", outputs[0].Value())
}
}
func TestGraphAddGradients(t *testing.T) {
g := NewGraph()
x1, err := Placeholder(g, "x1", Float)
if err != nil {
t.Fatal(err)
}
x2, err := Placeholder(g, "x2", Float)
if err != nil {
t.Fatal(err)
}
op0, err := g.AddOperation(OpSpec{
Type: "Square",
Name: "y0",
Input: []Input{x1},
})
if err != nil {
t.Fatal(err)
}
y0 := op0.Output(0)
op1, err := g.AddOperation(OpSpec{
Type: "Square",
Name: "y1",
Input: []Input{y0},
})
if err != nil {
t.Fatal(err)
}
y1 := op1.Output(0)
op2, err := g.AddOperation(OpSpec{
Type: "AddN",
Input: []Input{OutputList([]Output{y0, x2})},
})
if err != nil {
t.Fatal(err)
}
y2 := op2.Output(0)
grads0, err := g.AddGradients("", []Output{y1}, []Output{x1}, nil)
if err != nil {
t.Fatal(err)
}
if len(grads0) != 1 {
t.Fatal(len(grads0))
}
if grads0[0].DataType() != Float {
t.Fatalf("Got DataType %v, wanted %v", grads0[0].DataType(), Float)
}
grads1, err := g.AddGradients("", []Output{y2}, []Output{x1, x2}, nil)
if err != nil {
t.Fatal(err)
}
if len(grads1) != 2 {
t.Fatal(len(grads1))
}
if grads1[0].DataType() != Float {
t.Fatalf("Got DataType %v, wanted %v", grads1[0].DataType(), Float)
}
if grads1[1].DataType() != Float {
t.Fatalf("Got DataType %v, wanted %v", grads1[1].DataType(), Float)
}
sess, err := NewSession(g, nil)
if err != nil {
t.Fatal(err)
}
c1, _ := NewTensor(float32(3.0))
c2, _ := NewTensor(float32(2.0))
outputs, err := sess.Run(
map[Output]*Tensor{x1: c1, x2: c2},
[]Output{grads0[0], grads1[0], grads1[1]},
nil)
if err != nil {
t.Fatal(err)
}
if len(outputs) != 3 {
t.Fatal(len(outputs))
}
if outputs[0].Value().(float32) != 108.0 {
t.Fatalf("Got %v, wanted float 108.0", outputs[0].Value())
}
if outputs[1].Value().(float32) != 6.0 {
t.Fatalf("Got %v, wanted float 6.0", outputs[1].Value())
}
if outputs[2].Value().(float32) != 1.0 {
t.Fatalf("Got %v, wanted float 1.0", outputs[2].Value())
}
}
func TestGraphAddGradientsSums(t *testing.T) {
g := NewGraph()
x, err := Placeholder(g, "x", Float)
if err != nil {
t.Fatal(err)
}
op0, err := g.AddOperation(OpSpec{
Type: "Square",
Name: "y0",
Input: []Input{x},
})
if err != nil {
t.Fatal(err)
}
y0 := op0.Output(0)
op1, err := g.AddOperation(OpSpec{
Type: "Square",
Name: "y1",
Input: []Input{y0},
})
if err != nil {
t.Fatal(err)
}
y1 := op1.Output(0)
grad, err := g.AddGradients("", []Output{y0, y1}, []Output{x}, nil)
if err != nil {
t.Fatal(err)
}
if len(grad) != 1 {
t.Fatal(len(grad))
}
if grad[0].DataType() != Float {
t.Fatalf("Got DataType %v, wanted %v", grad[0].DataType(), Float)
}
sess, err := NewSession(g, nil)
if err != nil {
t.Fatal(err)
}
c, _ := NewTensor(float32(3.0))
outputs, err := sess.Run(
map[Output]*Tensor{x: c},
[]Output{grad[0]},
nil)
if err != nil {
t.Fatal(err)
}
if outputs[0].Value().(float32) != 114.0 {
t.Fatalf("Got %v, wanted float 114.0", outputs[0].Value())
}
}
func TestGraphAddGradientsWithInitialValues(t *testing.T) {
g := NewGraph()
x, err := Placeholder(g, "x", Float)
if err != nil {
t.Fatal(err)
}
op0, err := g.AddOperation(OpSpec{
Type: "Square",
Name: "y0",
Input: []Input{x},
})
if err != nil {
t.Fatal(err)
}
y0 := op0.Output(0)
op1, err := g.AddOperation(OpSpec{
Type: "Square",
Name: "y1",
Input: []Input{y0},
})
if err != nil {
t.Fatal(err)
}
y1 := op1.Output(0)
grads0, err := g.AddGradients("", []Output{y1}, []Output{y0}, nil)
if err != nil {
t.Fatal(err)
}
if len(grads0) != 1 {
t.Fatal(len(grads0))
}
if grads0[0].DataType() != Float {
t.Fatalf("Got DataType %v, wanted %v", grads0[0].DataType(), Float)
}
grads1, err := g.AddGradients("", []Output{y0}, []Output{x}, []Output{grads0[0]})
if err != nil {
t.Fatal(err)
}
if len(grads1) != 1 {
t.Fatal(len(grads1))
}
if grads1[0].DataType() != Float {
t.Fatalf("Got DataType %v, wanted %v", grads1[0].DataType(), Float)
}
sess, err := NewSession(g, nil)
if err != nil {
t.Fatal(err)
}
c, _ := NewTensor(float32(3.0))
outputs, err := sess.Run(
map[Output]*Tensor{x: c},
[]Output{grads1[0]},
nil)
if err != nil {
t.Fatal(err)
}
if outputs[0].Value().(float32) != 108.0 {
t.Fatalf("Got %v, wanted float 108.0", outputs[0].Value())
}
}
func TestGraphValidateGradientsNames(t *testing.T) {
g := NewGraph()
x, err := Placeholder(g, "x", Float)
if err != nil {
t.Fatal(err)
}
op0, err := g.AddOperation(OpSpec{
Type: "Square",
Name: "y0",
Input: []Input{x},
})
if err != nil {
t.Fatal(err)
}
y0 := op0.Output(0)
grads0, err := g.AddGradients("", []Output{y0}, []Output{x}, nil)
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(grads0[0].Op.Name(), "gradients/") {
t.Fatalf("Got name %v, wanted started with gradients/", grads0[0].Op.Name())
}
grads1, err := g.AddGradients("", []Output{y0}, []Output{x}, nil)
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(grads1[0].Op.Name(), "gradients_1/") {
t.Fatalf("Got name %v, wanted started with gradients_1/", grads1[0].Op.Name())
}
grads2, err := g.AddGradients("more_gradients", []Output{y0}, []Output{x}, nil)
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(grads2[0].Op.Name(), "more_gradients/") {
t.Fatalf("Got name %v, wanted started with more_gradients/", grads2[0].Op.Name())
}
grads3, err := g.AddGradients("even_more_gradients", []Output{y0}, []Output{x}, nil)
if err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(grads3[0].Op.Name(), "even_more_gradients/") {
t.Fatalf("Got name %v, wanted started with even_more_gradients/", grads3[0].Op.Name())
}
_, err = g.AddGradients("even_more_gradients", []Output{y0}, []Output{x}, nil)
if err == nil {
t.Error("AddGradients should have failed if gradients name is already existing")
}
}
+21
View File
@@ -0,0 +1,21 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
// #cgo LDFLAGS: -ltensorflow
// #cgo CFLAGS: -I${SRCDIR}/../../
import "C"
+1
View File
@@ -0,0 +1 @@
# Empty file to be replaced in https://github.com/tensorflow/tensorflow/pull/50934
+20
View File
@@ -0,0 +1,20 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
//go:generate go generate ../genop
//go:generate go run ../genop/main.go -outfile wrappers.go -api_def_dirs ../../core/api_def/base_api/
package op
+49
View File
@@ -0,0 +1,49 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package op
import (
"fmt"
tf "github.com/tensorflow/tensorflow/tensorflow/go"
)
// Gradients adds gradients computation ops to the graph according to scope.
//
// Arguments:
// y: output of the function to derive
// x: inputs of the function for which partial derivatives are computed
// dx: if not null, the partial derivatives of some loss function L w.r.t. y
//
// return the partial derivatives
func Gradients(scope *Scope, y []tf.Output, x []tf.Output, dx ...tf.Output) (output []tf.Output) {
if len(scope.controlDependencies) > 0 {
scope.UpdateErr("Gradients", fmt.Errorf("Gradients does not currently support control dependencies (via Scope.WithControlDependencies)."))
return
}
if scope.device != "" {
scope.UpdateErr("Gradients", fmt.Errorf("Gradients does not currently support device annotations (via Scope.WithDevice)."))
return
}
var err error
if output, err = scope.graph.AddGradients(scope.opName("Gradients"), y, x, dx); err != nil {
scope.UpdateErr("Gradients", err)
return
}
return output
}
+246
View File
@@ -0,0 +1,246 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package op
import (
"strings"
"testing"
tf "github.com/tensorflow/tensorflow/tensorflow/go"
)
func TestAddGradients(t *testing.T) {
var (
s = NewScope()
x1 = Placeholder(s.SubScope("x1"), tf.Float)
x2 = Placeholder(s.SubScope("x2"), tf.Float)
y0 = Square(s.SubScope("y0"), x1)
y1 = Square(s.SubScope("y1"), y0)
y2 = AddN(s.SubScope("y2"), []tf.Output{y0, x2})
)
grads0 := Gradients(s, []tf.Output{y1}, []tf.Output{x1})
if err := s.Err(); err != nil {
t.Fatal(err)
}
if len(grads0) != 1 {
t.Fatal(len(grads0))
}
if grads0[0].DataType() != tf.Float {
t.Fatalf("Got DataType %v, wanted %v", grads0[0].DataType(), tf.Float)
}
sub := s.SubScope("sub")
grads1 := Gradients(sub, []tf.Output{y2}, []tf.Output{x1, x2})
if err := sub.Err(); err != nil {
t.Fatal(err)
}
if len(grads1) != 2 {
t.Fatal(len(grads1))
}
if grads1[0].DataType() != tf.Float {
t.Fatalf("Got DataType %v, wanted %v", grads1[0].DataType(), tf.Float)
}
if grads1[1].DataType() != tf.Float {
t.Fatalf("Got DataType %v, wanted %v", grads1[1].DataType(), tf.Float)
}
graph, err := sub.Finalize()
if err != nil {
t.Fatal(err)
}
sess, err := tf.NewSession(graph, nil)
if err != nil {
t.Fatal(err)
}
c1, _ := tf.NewTensor(float32(3.0))
c2, _ := tf.NewTensor(float32(3.0))
outputs, err := sess.Run(
map[tf.Output]*tf.Tensor{x1: c1, x2: c2},
[]tf.Output{grads0[0], grads1[0], grads1[1]},
nil)
if err != nil {
t.Fatal(err)
}
if len(outputs) != 3 {
t.Fatal(len(outputs))
}
if outputs[0].Value().(float32) != 108.0 {
t.Fatalf("Got %v, wanted float 108.0", outputs[0].Value())
}
if outputs[1].Value().(float32) != 6.0 {
t.Fatalf("Got %v, wanted float 6.0", outputs[1].Value())
}
if outputs[2].Value().(float32) != 1.0 {
t.Fatalf("Got %v, wanted float 1.0", outputs[2].Value())
}
}
func TestAddGradientsSums(t *testing.T) {
var (
s = NewScope()
x = Placeholder(s.SubScope("x"), tf.Float)
y0 = Square(s.SubScope("y0"), x)
y1 = Square(s.SubScope("y1"), y0)
)
grad := Gradients(s, []tf.Output{y0, y1}, []tf.Output{x})
if err := s.Err(); err != nil {
t.Fatal(err)
}
if len(grad) != 1 {
t.Fatal(len(grad))
}
if grad[0].DataType() != tf.Float {
t.Fatalf("Got DataType %v, wanted %v", grad[0].DataType(), tf.Float)
}
graph, err := s.Finalize()
if err != nil {
t.Fatal(err)
}
sess, err := tf.NewSession(graph, nil)
if err != nil {
t.Fatal(err)
}
c, _ := tf.NewTensor(float32(3.0))
outputs, err := sess.Run(
map[tf.Output]*tf.Tensor{x: c},
[]tf.Output{grad[0]},
nil)
if err != nil {
t.Fatal(err)
}
if outputs[0].Value().(float32) != 114.0 {
t.Fatalf("Got %v, wanted float 114.0", outputs[0].Value())
}
}
func TestAddGradientsWithInitialValues(t *testing.T) {
var (
s = NewScope()
x = Placeholder(s.SubScope("x1"), tf.Float)
y0 = Square(s.SubScope("y0"), x)
y1 = Square(s.SubScope("y1"), y0)
)
grads0 := Gradients(s, []tf.Output{y1}, []tf.Output{y0})
if err := s.Err(); err != nil {
t.Fatal(err)
}
if len(grads0) != 1 {
t.Fatal(len(grads0))
}
if grads0[0].DataType() != tf.Float {
t.Fatalf("Got DataType %v, wanted %v", grads0[0].DataType(), tf.Float)
}
sub := s.SubScope("sub")
grads1 := Gradients(sub, []tf.Output{y0}, []tf.Output{x}, grads0[0])
if err := sub.Err(); err != nil {
t.Fatal(err)
}
if len(grads1) != 1 {
t.Fatal(len(grads1))
}
if grads1[0].DataType() != tf.Float {
t.Fatalf("Got DataType %v, wanted %v", grads1[0].DataType(), tf.Float)
}
graph, err := sub.Finalize()
if err != nil {
t.Fatal(err)
}
sess, err := tf.NewSession(graph, nil)
if err != nil {
t.Fatal(err)
}
c, _ := tf.NewTensor(float32(3.0))
outputs, err := sess.Run(
map[tf.Output]*tf.Tensor{x: c},
[]tf.Output{grads1[0]},
nil)
if err != nil {
t.Fatal(err)
}
if outputs[0].Value().(float32) != 108.0 {
t.Fatalf("Got %v, wanted float 108.0", outputs[0].Value())
}
}
func TestValidateGradientsNames(t *testing.T) {
var (
s = NewScope()
x = Placeholder(s.SubScope("x"), tf.Float)
y0 = Square(s.SubScope("y0"), x)
)
grads0 := Gradients(s, []tf.Output{y0}, []tf.Output{x})
if err := s.Err(); err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(grads0[0].Op.Name(), "Gradients/") {
t.Fatalf("Got name %v, wanted started with Gradients/", grads0[0].Op.Name())
}
sub := s.SubScope("sub")
grads1 := Gradients(sub, []tf.Output{y0}, []tf.Output{x})
if err := s.Err(); err != nil {
t.Fatal(err)
}
if !strings.HasPrefix(grads1[0].Op.Name(), "sub/Gradients/") {
t.Fatalf("Got name %v, wanted started with sub/Gradients/", grads1[0].Op.Name())
}
Gradients(sub, []tf.Output{y0}, []tf.Output{x})
if err := s.Err(); err == nil {
t.Error("Gradients should have failed if executed more than once for scope of the same namespace")
}
}
func TestAddGradientsWithControlDependencies(t *testing.T) {
var (
s = NewScope()
zero = Const(s.SubScope("zero"), int32(0))
x = Placeholder(s.SubScope("x"), tf.Float)
y0 = Square(s.SubScope("y0"), x)
variable = VarHandleOp(s, tf.Int32, tf.ScalarShape())
init = AssignVariableOp(s, variable, zero)
readDeps = []*tf.Operation{init}
)
s = s.WithControlDependencies(readDeps...)
Gradients(s, []tf.Output{y0}, []tf.Output{x})
if err := s.Err(); err == nil {
t.Error("Gradients should have failed when control dependencies are set")
}
}
func TestAddGradientsWithDevice(t *testing.T) {
var (
s = NewScope()
x = Placeholder(s.SubScope("x"), tf.Float)
y0 = Square(s.SubScope("y0"), x)
)
s = s.WithDevice("/device:GPU:0")
Gradients(s, []tf.Output{y0}, []tf.Output{x})
if err := s.Err(); err == nil {
t.Error("Gradients should have failed when device is set")
}
}
+51
View File
@@ -0,0 +1,51 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Package op defines functions for adding TensorFlow operations to a Graph.
//
// Functions for adding an operation to a graph take a Scope object as the
// first argument. The Scope object encapsulates a graph and a set of
// properties (such as a name prefix) for all operations being added
// to the graph.
//
// WARNING: The API in this package has not been finalized and can
// change without notice.
package op
import (
tf "github.com/tensorflow/tensorflow/tensorflow/go"
)
// Const adds an operation to graph that produces value as output.
func Const(scope *Scope, value interface{}) (output tf.Output) {
if scope.Err() != nil {
return
}
t, ok := value.(*tf.Tensor)
if !ok {
var err error
if t, err = tf.NewTensor(value); err != nil {
scope.UpdateErr("Const", err)
return
}
}
return scope.AddOperation(tf.OpSpec{
Type: "Const",
Attrs: map[string]interface{}{
"dtype": t.DataType(),
"value": t,
}}).Output(0)
}
+133
View File
@@ -0,0 +1,133 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Tests for the generated code of some operations.
package op
import (
"strings"
"testing"
tf "github.com/tensorflow/tensorflow/tensorflow/go"
)
func TestPlaceholder(t *testing.T) {
s := NewScope()
Placeholder(s.SubScope("x"), tf.Float, PlaceholderShape(tf.MakeShape(-1, 10)))
Placeholder(s.SubScope("y"), tf.Float, PlaceholderShape(tf.ScalarShape()))
Placeholder(s.SubScope("z"), tf.Float, PlaceholderShape(tf.Shape{}))
if _, err := s.Finalize(); err != nil {
t.Fatal(err)
}
}
func TestAddOperationFailure(t *testing.T) {
// Inspired from https://github.com/tensorflow/tensorflow/issues/9931
s := NewScope()
resize := ResizeArea(s, Placeholder(s, tf.Float), Const(s, []int64{80, 80}))
if err := s.Err(); err == nil {
t.Fatal("ResizeArea expects an int32 Tensor for size, should fail when an int64 is provided")
}
// And any use of resize should panic with an error message more informative than SIGSEGV
defer func() {
r := recover()
if r == nil {
return
}
s, ok := r.(string)
if ok && strings.Contains(s, "see Scope.Err() for details") {
return
}
t.Errorf("Expected panic string to Scope.Err(), found %T: %q", r, r)
}()
_ = resize.Shape()
t.Errorf("resize.Shape() should have paniced since the underlying Operation was not created")
}
func TestShapeAttribute(t *testing.T) {
s := NewScope()
x := Placeholder(s.SubScope("x"), tf.Int32, PlaceholderShape(tf.MakeShape(1)))
y := Placeholder(s.SubScope("y"), tf.Int32, PlaceholderShape(tf.Shape{}))
z := Add(s, x, y)
graph, err := s.Finalize()
if err != nil {
t.Fatal(err)
}
sess, err := tf.NewSession(graph, nil)
if err != nil {
t.Fatal(err)
}
value, err := tf.NewTensor([]int32{7})
if err != nil {
t.Fatal(err)
}
feeds := map[tf.Output]*tf.Tensor{
x: value,
y: value,
}
fetched, err := sess.Run(feeds, []tf.Output{z}, nil)
if err != nil {
t.Fatal(err)
}
if got, want := len(fetched), 1; got != want {
t.Fatalf("Fetched %d tensors, expected %d", got, want)
}
if got, want := fetched[0].Value().([]int32), []int32{14}; len(got) != len(want) || len(got) != 1 || got[0] != want[0] {
t.Fatalf("Got %v, want %v", got, want)
}
}
func TestDataset(t *testing.T) {
var (
s = NewScope()
// The use of a non-scalar here is inspired by
// https://github.com/tensorflow/tensorflow/issues/14891
c = Const(s, []int32{21718, 31415})
types = []tf.DataType{c.DataType()}
shapes = []tf.Shape{c.Shape()}
dataset = TensorDataset(s, []tf.Output{c}, shapes)
iterator = Iterator(s, "", "", types, shapes)
next = IteratorGetNext(s, iterator, types, shapes)
init = MakeIterator(s, dataset, iterator)
)
graph, err := s.Finalize()
if err != nil {
t.Fatal(err)
}
sess, err := tf.NewSession(graph, nil)
if err != nil {
t.Fatal(err)
}
if _, err := sess.Run(nil, nil, []*tf.Operation{init}); err != nil {
t.Fatal(err)
}
results, err := sess.Run(nil, next, nil)
if err != nil {
t.Fatal(err)
}
got := results[0].Value().([]int32)
if len(got) != 2 || got[0] != 21718 || got[1] != 31415 {
t.Errorf("Got %v, want {21718, 31415}", got)
}
if _, err := sess.Run(nil, next, nil); err == nil {
t.Errorf("Expected sess.Run() to fail since the iterator should have reached the end of the dataset")
}
}
+185
View File
@@ -0,0 +1,185 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package op
import (
"fmt"
"runtime/debug"
tf "github.com/tensorflow/tensorflow/tensorflow/go"
)
// Scope encapsulates common operation properties when building a Graph.
//
// A Scope object (and its derivatives, e.g., obtained from Scope.SubScope)
// act as a builder for graphs. They allow common properties (such as
// a name prefix) to be specified for multiple operations being added
// to the graph.
//
// A Scope object and all its derivatives (e.g., obtained from Scope.SubScope)
// are not safe for concurrent use by multiple goroutines.
type Scope struct {
graph *tf.Graph
namemap map[string]int
namespace string
controlDependencies []*tf.Operation
device string
err *scopeErr
}
// scopeErr is used to share errors between all derivatives of a root scope.
type scopeErr struct {
err error
}
// NewScope creates a Scope initialized with an empty Graph.
func NewScope() *Scope {
return &Scope{graph: tf.NewGraph(), namemap: make(map[string]int), err: new(scopeErr)}
}
// NewScopeWithGraph creates a Scope initialized with the Graph thats passed in
func NewScopeWithGraph(g *tf.Graph) *Scope {
return &Scope{graph: g, namemap: make(map[string]int), err: new(scopeErr)}
}
// Finalize returns the Graph on which this scope operates on and renders s
// unusable. If there was an error during graph construction, that error is
// returned instead.
func (s *Scope) Finalize() (*tf.Graph, error) {
if err := s.Err(); err != nil {
return nil, err
}
s.err.err = fmt.Errorf("Scope has been finalized and is no longer usable")
return s.graph, nil
}
// AddOperation adds the operation to the Graph managed by s.
//
// If there is a name prefix associated with s (such as if s was created
// by a call to SubScope), then this prefix will be applied to the name
// of the operation being added. See also Graph.AddOperation.
func (s *Scope) AddOperation(args tf.OpSpec) *tf.Operation {
if s.Err() != nil {
return nil
}
if args.Name == "" {
args.Name = args.Type
}
if s.namespace != "" {
args.Name = s.namespace + "/" + args.Name
}
args.ControlDependencies = append(args.ControlDependencies, s.controlDependencies...)
args.Device = s.device
op, err := s.graph.AddOperation(args)
if err != nil {
s.UpdateErr(args.Type, err)
}
return op
}
// SubScope returns a new Scope which will cause all operations added to the
// graph to be namespaced with 'namespace'. If namespace collides with an
// existing namespace within the scope, then a suffix will be added.
func (s *Scope) SubScope(namespace string) *Scope {
namespace = s.uniqueName(namespace)
if s.namespace != "" {
namespace = s.namespace + "/" + namespace
}
return &Scope{
graph: s.graph,
namemap: make(map[string]int),
namespace: namespace,
controlDependencies: s.controlDependencies,
device: s.device,
err: s.err,
}
}
// WithControlDependencies returns a new Scope which will cause all operations
// added to the graph to execute only after all the provided operations have
// executed first (in addition to any other control dependencies in s).
func (s *Scope) WithControlDependencies(ops ...*tf.Operation) *Scope {
// Force a copy of the control dependencies into a new underlying array on
// every call. We cannot alias the same underlying array as `ops`, otherwise
// the user could modify that array after calling s.WithControlDependencies,
// which would be confusing. We cannot alias the same underlying array as the
// original `s.controlDependencies`, since Scopes form a logical tree, and
// other calls to s.WithControlDependencies could stomp on each other.
deps := make([]*tf.Operation, 0, len(s.controlDependencies)+len(ops))
deps = append(deps, s.controlDependencies...)
deps = append(deps, ops...)
return &Scope{
graph: s.graph,
namemap: s.namemap,
namespace: s.namespace,
controlDependencies: deps,
device: s.device,
err: s.err,
}
}
// WithDevice returns a new Scope which will cause all operations added to the
// graph to execute on devices that match the provided device specification.
//
// For example, WithDevice("/device:GPU:0") will cause operations added to
// the graph to execute on GPU #0.
//
// An empty string removes any device restrictions.
func (s *Scope) WithDevice(device string) *Scope {
return &Scope{
graph: s.graph,
namemap: s.namemap,
namespace: s.namespace,
controlDependencies: s.controlDependencies,
device: device,
err: s.err,
}
}
// Err returns the error, if any, encountered during the construction
// of the Graph managed by s.
//
// Once Err returns a non-nil error, all future calls will do the same,
// indicating that the scope should be discarded as the graph could not
// be constructed.
func (s *Scope) Err() error {
return s.err.err
}
// UpdateErr is used to notify Scope of any graph construction errors
// while creating the operation op.
func (s *Scope) UpdateErr(op string, err error) {
if s.err.err == nil {
s.err.err = fmt.Errorf("failed to add operation %q: %v (Stacktrace: %s)", op, err, debug.Stack())
}
}
func (s *Scope) uniqueName(name string) string {
count := s.namemap[name]
s.namemap[name]++
if count == 0 {
return name
}
return fmt.Sprint(name, "_", count)
}
func (s *Scope) opName(typ string) string {
if s.namespace == "" {
return typ
}
return s.namespace + "/" + typ
}
+201
View File
@@ -0,0 +1,201 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package op
import (
"fmt"
"testing"
tf "github.com/tensorflow/tensorflow/tensorflow/go"
)
func TestScopeSubScope(t *testing.T) {
var (
root = NewScope()
sub1 = root.SubScope("x")
sub2 = root.SubScope("x")
sub1a = sub1.SubScope("y")
sub2a = sub2.SubScope("y")
)
testdata := []struct {
scope *Scope
name string
}{
{root, "Const"},
{sub1, "x/Const"},
{sub1a, "x/y/Const"},
{sub2, "x_1/Const"},
{sub2a, "x_1/y/Const"},
}
for _, test := range testdata {
c := Const(test.scope, int64(1))
if err := test.scope.Err(); err != nil {
t.Fatalf("%q: %v", test.name, err)
}
if got := c.Op.Name(); got != test.name {
t.Errorf("%q: Got %q", test.name, got)
}
}
}
func TestScopeSubScopeErrors(t *testing.T) {
var (
root = NewScope()
sub = root.SubScope("x")
)
// Error on the root, even after sub has been created should be propagated.
// Force an error by creating a Const which has a type that does not
// translate to the TensorFlow type system.
Const(root, int(1))
if err := root.Err(); err == nil {
t.Fatal("Expected error")
}
if err := sub.Err(); err == nil {
t.Errorf("Root scope had error [%v], but sub-scope did not", root.Err())
}
}
func TestControlDependencies(t *testing.T) {
var (
s = NewScope()
zero = Const(s.SubScope("zero"), int32(0))
one = Const(s.SubScope("one"), int32(1))
variable = VarHandleOp(s, tf.Int32, tf.ScalarShape())
init = AssignVariableOp(s, variable, zero)
update = AssignAddVariableOp(s, variable, one)
readDeps = []*tf.Operation{update}
)
// We intend for `read` to have a control dependency on `update`.
s = s.WithControlDependencies(readDeps...)
// Ensure that Scope.WithControlDependencies makes a copy of the underlying
// array, rather than just holding a slice reference to the same user-supplied
// underlying array. If the copy is correctly performed, overwriting
// readDeps[0] should have no effect on control dependencies for `read`.
readDeps[0] = init
read := ReadVariableOp(s, variable, tf.Int32)
graph, err := s.Finalize()
if err != nil {
t.Fatal(err)
}
sess, err := tf.NewSession(graph, nil)
if err != nil {
t.Fatal(err)
}
if _, err = sess.Run(nil, nil, []*tf.Operation{init}); err != nil {
t.Fatal(err)
}
// Without the control dependency, the read operation may not see the
// update.
for i := int32(0); i < 10; i++ {
out, err := sess.Run(nil, []tf.Output{read}, nil)
if err != nil {
t.Fatal(err)
}
if got, want := out[0].Value().(int32), i+1; got != want {
t.Errorf("Got %d, want %d", got, want)
}
}
}
func TestDevice(t *testing.T) {
s := NewScope()
matrix := Const(s, [][]float32{{3.0}})
s = s.WithDevice("/device:GPU:0")
square := MatMul(s.SubScope("square"), matrix, matrix)
s = s.WithDevice("")
cube := MatMul(s.SubScope("cube"), square, matrix)
if got, want := square.Op.Device(), "/device:GPU:0"; got != want {
t.Errorf("Got %q, want %q", got, want)
}
if got, want := cube.Op.Device(), ""; got != want {
t.Errorf("Got %q, want %q", got, want)
}
}
func TestScopeFinalize(t *testing.T) {
var (
root = NewScope()
sub1 = root.SubScope("x")
sub2 = sub1.SubScope("y")
)
if _, err := sub1.Finalize(); err != nil {
t.Fatal(err)
}
if err := root.Err(); err == nil {
t.Error("Root scope's Err() should be non-nil once Finalize has been called")
}
if err := sub2.Err(); err == nil {
t.Error("Sub scope's Err() should be non-nil once Finalize has been called")
}
}
func TestMultipleGeneratedOps(t *testing.T) {
s := NewScope()
Placeholder(s.SubScope("x"), tf.Float)
Placeholder(s.SubScope("y"), tf.Float)
if _, err := s.Finalize(); err != nil {
t.Fatal(err)
}
}
func TestScopeWithGraph(t *testing.T) {
s1 := NewScope()
Const(s1, "hello")
graph, err := s1.Finalize()
if err != nil {
t.Fatal(err)
}
s2 := NewScopeWithGraph(graph)
Const(s2.SubScope("addition"), "world")
if err := s2.Err(); err != nil {
t.Fatal(err)
}
}
func Example() {
// This example creates a Graph that multiplies a constant matrix with
// a matrix to be provided during graph execution (via
// tensorflow.Session).
s := NewScope()
input := Placeholder(s, tf.Float) // Matrix to be provided to Session.Run
output := MatMul(s,
Const(s, [][]float32{{10}, {20}}), // Constant 2x1 matrix
input,
MatMulTransposeB(true))
if s.Err() != nil {
panic(s.Err())
}
// Shape of the product: The number of rows is fixed by m1, but the
// number of columns will depend on m2, which is unknown.
fmt.Println(output.Shape())
// Output: [2, ?]
}
func ExampleScope_SubScope() {
var (
s = NewScope()
c1 = Const(s.SubScope("x"), int64(1))
c2 = Const(s.SubScope("x"), int64(1))
)
if s.Err() != nil {
panic(s.Err())
}
fmt.Println(c1.Op.Name(), c2.Op.Name())
// Output: x/Const x_1/Const
}
File diff suppressed because it is too large Load Diff
+216
View File
@@ -0,0 +1,216 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
// #include <stdlib.h>
// #include "tensorflow/c/c_api.h"
import "C"
import "unsafe"
// Operation that has been added to the graph.
type Operation struct {
c *C.TF_Operation
// A reference to the Graph to prevent it from
// being GCed while the Operation is still alive.
g *Graph
}
// Name returns the name of the operation.
func (op *Operation) Name() string {
return C.GoString(C.TF_OperationName(op.c))
}
// Type returns the name of the operator used by this operation.
func (op *Operation) Type() string {
return C.GoString(C.TF_OperationOpType(op.c))
}
// NumOutputs returns the number of outputs of op.
func (op *Operation) NumOutputs() int {
return int(C.TF_OperationNumOutputs(op.c))
}
// Device returns a specification of the device on which this operation
// will be executed, or the empty string if there is no such specification.
func (op *Operation) Device() string {
return C.GoString(C.TF_OperationDevice(op.c))
}
// OutputListSize returns the size of the list of Outputs that is produced by a
// named output of op.
//
// An Operation has multiple named outputs, each of which produces either
// a single tensor or a list of tensors. This method returns the size of
// the list of tensors for a specific output of the operation, identified
// by its name.
func (op *Operation) OutputListSize(output string) (int, error) {
cname := C.CString(output)
defer C.free(unsafe.Pointer(cname))
status := newStatus()
n := C.TF_OperationOutputListLength(op.c, cname, status.c)
return int(n), status.Err()
}
// Output returns the i-th output of op.
func (op *Operation) Output(i int) Output {
return Output{op, i}
}
// NumInputs returns the number of inputs of op.
func (op *Operation) NumInputs() int {
return int(C.TF_OperationNumInputs(op.c))
}
// Output represents one of the outputs of an operation in the graph. Has a
// DataType (and eventually a Shape). May be passed as an input argument to a
// function for adding operations to a graph, or to a Session's Run() method to
// fetch that output as a tensor.
type Output struct {
// Op is the Operation that produces this Output.
Op *Operation
// Index specifies the index of the output within the Operation.
Index int
}
// DataType returns the type of elements in the tensor produced by p.
func (p Output) DataType() DataType {
return DataType(C.TF_OperationOutputType(p.c()))
}
// Shape returns the (possibly incomplete) shape of the tensor produced p.
func (p Output) Shape() Shape {
status := newStatus()
port := p.c()
ndims := C.TF_GraphGetTensorNumDims(p.Op.g.c, port, status.c)
if err := status.Err(); err != nil {
// This should not be possible since an error only occurs if
// the operation does not belong to the graph. It should not
// be possible to construct such an Operation object.
return Shape{}
}
if ndims < 0 {
return Shape{}
}
if ndims == 0 {
return ScalarShape()
}
dims := make([]C.int64_t, ndims)
C.TF_GraphGetTensorShape(p.Op.g.c, port, &dims[0], ndims, status.c)
if err := status.Err(); err != nil {
// Same as above, should not be possible.
return Shape{}
}
ret := Shape{dims: make([]int64, ndims)}
for i := 0; i < int(ndims); i++ {
ret.dims[i] = int64(dims[i])
}
return ret
}
func (p Output) c() C.TF_Output {
if p.Op == nil {
// Attempt to provide a more useful panic message than "nil
// pointer dereference".
panic("nil-Operation. If the Output was created with a Scope object, see Scope.Err() for details.")
}
return C.TF_Output{oper: p.Op.c, index: C.int(p.Index)}
}
func (p Output) canBeAnInput() {}
// Consumers returns the inputs that consume this output.
func (p Output) Consumers() []Consumer {
max := int(C.TF_OperationOutputNumConsumers(p.c()))
if max == 0 {
return nil
}
inputs := make([]C.TF_Input, max)
n := C.TF_OperationOutputConsumers(p.c(), (*C.TF_Input)(unsafe.Pointer(&inputs[0])), C.int(max))
inputs = inputs[:int(n)]
var consumers []Consumer
for _, consumer := range inputs {
consumers = append(consumers, Consumer{
Index: int(consumer.index),
Op: &Operation{
c: consumer.oper,
g: p.Op.g,
},
})
}
return consumers
}
// Consumer identifies a specific input of an operation that consumes the output
// of another operation.
type Consumer struct {
// Op is the Operation that is consuming the output of another operation.
Op *Operation
// Index is the index of the input within Op that the output of another
// operation is connected to.
Index int
}
func (p Consumer) c() C.TF_Input {
if p.Op == nil {
// Attempt to provide a more useful panic message than "nil
// pointer dereference".
panic("nil-Operation. Consumer objects should only be created by a call to Output.Consumers")
}
return C.TF_Input{oper: p.Op.c, index: C.int(p.Index)}
}
// DataType returns the type of the input.
func (p Consumer) DataType() DataType {
return DataType(C.TF_OperationInputType(p.c()))
}
// Producer returns the Output that is connected to this Consumer.
func (p Consumer) Producer() Output {
output := C.TF_OperationInput(p.c())
return Output{
Op: &Operation{
c: output.oper,
g: p.Op.g,
},
Index: int(output.index),
}
}
// Input is the interface for specifying inputs to an operation being added to
// a Graph.
//
// Operations can have multiple inputs, each of which could be either a tensor
// produced by another operation (an Output object), or a list of tensors
// produced by other operations (an OutputList). Thus, this interface is
// implemented by both Output and OutputList.
//
// See OpSpec.Input for more information.
type Input interface {
// Unexported to preclude implementations outside this package.
canBeAnInput()
}
// OutputList represents a list of Outputs that can be provided as input to
// another operation.
type OutputList []Output
func (l OutputList) canBeAnInput() {}
+269
View File
@@ -0,0 +1,269 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
import (
"fmt"
"runtime"
"runtime/debug"
"testing"
)
// createGraphAndOp creates an Operation but loses the reference to the Graph.
func createGraphAndOp() (*Operation, error) {
t, err := NewTensor(int64(1))
if err != nil {
return nil, err
}
g := NewGraph()
output, err := Placeholder(g, "my_placeholder", t.DataType())
if err != nil {
return nil, err
}
return output.Op, nil
}
func TestOperationLifetime(t *testing.T) {
// Ensure that the Graph is not garbage collected while the program
// still has access to the Operation.
op, err := createGraphAndOp()
if err != nil {
t.Fatal(err)
}
forceGC()
if got, want := op.Name(), "my_placeholder"; got != want {
t.Errorf("Got '%s', want '%s'", got, want)
}
if got, want := op.Type(), "Placeholder"; got != want {
t.Errorf("Got '%s', want '%s'", got, want)
}
}
func TestOperationOutputListSize(t *testing.T) {
graph := NewGraph()
c1, err := Const(graph, "c1", int64(1))
if err != nil {
t.Fatal(err)
}
c2, err := Const(graph, "c2", [][]int64{{1, 2}, {3, 4}})
if err != nil {
t.Fatal(err)
}
// The ShapeN op takes a list of tensors as input and a list as output.
op, err := graph.AddOperation(OpSpec{
Type: "ShapeN",
Input: []Input{OutputList{c1, c2}},
})
if err != nil {
t.Fatal(err)
}
n, err := op.OutputListSize("output")
if err != nil {
t.Fatal(err)
}
if got, want := n, 2; got != want {
t.Errorf("Got %d, want %d", got, want)
}
if got, want := op.NumOutputs(), 2; got != want {
t.Errorf("Got %d, want %d", got, want)
}
}
func TestOperationShapeAttribute(t *testing.T) {
g := NewGraph()
_, err := g.AddOperation(OpSpec{
Type: "Placeholder",
Attrs: map[string]interface{}{
"dtype": Float,
"shape": MakeShape(-1, 3),
},
})
if err != nil {
t.Fatal(err)
}
// If and when the API to get attributes is added, check that here.
}
func TestOutputDataTypeAndShape(t *testing.T) {
graph := NewGraph()
testdata := []struct {
Value interface{}
Shape []int64
dtype DataType
}{
{ // Scalar
int64(0),
[]int64{},
Int64,
},
{ // Vector
[]int32{1, 2, 3},
[]int64{3},
Int32,
},
{ // Matrix
[][]float64{
{1, 2, 3},
{4, 5, 6},
},
[]int64{2, 3},
Double,
},
{ // Matrix of Uint64
[][]uint64{
{1, 2, 3},
{4, 5, 6},
},
[]int64{2, 3},
Uint64,
},
}
for idx, test := range testdata {
t.Run(fmt.Sprintf("#%d Value %T", idx, test.Value), func(t *testing.T) {
c, err := Const(graph, fmt.Sprintf("const%d", idx), test.Value)
if err != nil {
t.Fatal(err)
}
if got, want := c.DataType(), test.dtype; got != want {
t.Errorf("Got DataType %v, want %v", got, want)
}
shape := c.Shape()
if got, want := shape.NumDimensions(), len(test.Shape); got != want {
t.Fatalf("Got a shape with %d dimensions, want %d", got, want)
}
for i := 0; i < len(test.Shape); i++ {
if got, want := shape.Size(i), test.Shape[i]; got != want {
t.Errorf("Got %d, want %d for dimension #%d/%d", got, want, i, len(test.Shape))
}
}
})
}
// Unknown number of dimensions
dummyTensor, err := NewTensor(float64(0))
if err != nil {
t.Fatal(err)
}
placeholder, err := Placeholder(graph, "placeholder", dummyTensor.DataType())
if err != nil {
t.Fatal(err)
}
if shape := placeholder.Shape(); shape.NumDimensions() != -1 {
t.Errorf("Got shape %v, wanted an unknown number of dimensions", shape)
}
}
func TestOperationInputs(t *testing.T) {
g := NewGraph()
x, err := Placeholder(g, "x", Float)
if err != nil {
t.Fatal(err)
}
y, err := Placeholder(g, "y", Float)
if err != nil {
t.Fatal(err)
}
add, err := Add(g, "add", x, y)
if err != nil {
t.Fatal(err)
}
addOp := add.Op
if out := addOp.NumInputs(); out != 2 {
t.Fatalf("Got %d inputs, wanted 2", out)
}
}
func TestOperationConsumers(t *testing.T) {
g := NewGraph()
x, err := Placeholder(g, "x", Float)
if err != nil {
t.Fatal(err)
}
a, err := Neg(g, "a", x)
if err != nil {
t.Fatal(err)
}
b, err := Neg(g, "b", x)
if err != nil {
t.Fatal(err)
}
consumers := []*Operation{a.Op, b.Op}
xConsumers := x.Consumers()
if out := len(xConsumers); out != 2 {
t.Fatalf("Got %d consumers, wanted 2", out)
}
for i, consumer := range xConsumers {
got := consumer.Op.Name()
want := consumers[i].Name()
if got != want {
t.Fatalf("%d. Got op name %q, wanted %q", i, got, want)
}
got = consumer.Producer().Op.Name()
want = x.Op.Name()
if got != want {
t.Fatalf("%d. Got op name %q, wanted %q", i, got, want)
}
}
if len(b.Consumers()) != 0 {
t.Fatalf("expected %+v to have no consumers", b)
}
}
func TestOperationDevice(t *testing.T) {
graph := NewGraph()
v, err := NewTensor(float32(1.0))
if err != nil {
t.Fatal(err)
}
op, err := graph.AddOperation(OpSpec{
Type: "Const",
Name: "Const",
Attrs: map[string]interface{}{
"dtype": v.DataType(),
"value": v,
},
Device: "/device:GPU:0",
})
if err != nil {
t.Fatal(err)
}
if got, want := op.Device(), "/device:GPU:0"; got != want {
t.Errorf("Got %q, want %q", got, want)
}
}
func forceGC() {
var mem runtime.MemStats
runtime.ReadMemStats(&mem)
// It was empirically observed that without this extra allocation
// TestOperationLifetime would fail only 50% of the time if
// Operation did not hold on to a reference to Graph. With this
// additional allocation, and with the bug where Operation does
// not hold onto a Graph, the test failed 90+% of the time.
//
// The author is aware that this technique is potentially fragile
// and fishy. Suggestions for alternatives are welcome.
bytesTillGC := mem.NextGC - mem.HeapAlloc + 1
_ = make([]byte, bytesTillGC)
runtime.GC()
debug.FreeOSMemory()
}
+100
View File
@@ -0,0 +1,100 @@
/*
Copyright 2017 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
import (
"runtime"
"unsafe"
"google.golang.org/protobuf/proto"
corepb "github.com/tensorflow/tensorflow/tensorflow/go/core/protobuf/for_core_protos_go_proto"
)
// #include <stdlib.h>
// #include "tensorflow/c/c_api.h"
import "C"
// SavedModel represents the contents of loaded SavedModel.
// TODO(jhseu): Add and document metagraphdef when we pregenerate protobufs.
type SavedModel struct {
Session *Session
Graph *Graph
Signatures map[string]Signature
}
// LoadSavedModel creates a new SavedModel from a model previously
// exported to a directory on disk.
//
// Exported models contain a set of graphs and, optionally, variable values.
// Tags in the model identify a single graph. LoadSavedModel initializes a
// session with the identified graph and with variables initialized to from the
// checkpoints on disk.
//
// The tensorflow package currently does not have the ability to export a model
// to a directory from Go. This function thus currently targets loading models
// exported in other languages, such as using tf.saved_model.builder in Python.
// See:
// https://www.tensorflow.org/code/tensorflow/python/saved_model/
func LoadSavedModel(exportDir string, tags []string, options *SessionOptions) (*SavedModel, error) {
status := newStatus()
cOpt, doneOpt, err := options.c()
defer doneOpt()
if err != nil {
return nil, err
}
cExportDir := C.CString(exportDir)
cTags := make([]*C.char, len(tags))
for i := range tags {
cTags[i] = C.CString(tags[i])
}
var tagsPtr **C.char = nil
if len(tags) != 0 {
tagsPtr = (**C.char)(unsafe.Pointer(&cTags[0]))
}
graph := NewGraph()
metaGraphDefBuf := C.TF_NewBuffer()
defer C.TF_DeleteBuffer(metaGraphDefBuf)
// TODO(jhseu): Add support for run_options and meta_graph_def.
cSess := C.TF_LoadSessionFromSavedModel(cOpt, nil, cExportDir, tagsPtr, C.int(len(cTags)), graph.c, metaGraphDefBuf, status.c)
for i := range cTags {
C.free(unsafe.Pointer(cTags[i]))
}
C.free(unsafe.Pointer(cExportDir))
metaGraphDefBytes := C.GoBytes(metaGraphDefBuf.data, C.int(metaGraphDefBuf.length))
metaGraphDef := new(corepb.MetaGraphDef)
if err := proto.Unmarshal(metaGraphDefBytes, metaGraphDef); err != nil {
return nil, err
}
signatures := generateSignatures(metaGraphDef.GetSignatureDef())
if err := status.Err(); err != nil {
return nil, err
}
s := &Session{c: cSess}
runtime.SetFinalizer(s, func(s *Session) { s.Close() })
return &SavedModel{Session: s, Graph: graph, Signatures: signatures}, nil
}
func generateSignatures(pb map[string]*corepb.SignatureDef) map[string]Signature {
signatures := make(map[string]Signature)
for name, signature := range pb {
signatures[name] = signatureDefFromProto(signature)
}
return signatures
}
+108
View File
@@ -0,0 +1,108 @@
/*
Copyright 2017 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
import (
"math"
"testing"
)
func TestSavedModelHalfPlusTwo(t *testing.T) {
var (
exportDir = "testdata/saved_model/half_plus_two/00000123"
tags = []string{"serve"}
options = new(SessionOptions)
)
// Load saved model half_plus_two.
m, err := LoadSavedModel(exportDir, tags, options)
if err != nil {
t.Fatalf("LoadSavedModel(): %v", err)
}
// Check that named operations x and y are present in the graph.
if op := m.Graph.Operation("x"); op == nil {
t.Fatalf("\"x\" not found in graph")
}
if op := m.Graph.Operation("y"); op == nil {
t.Fatalf("\"y\" not found in graph")
}
// Define test cases for half plus two (y = 0.5 * x + 2).
tests := []struct {
name string
X float32
Y float32
}{
{"NegVal", -1, 1.5},
{"PosVal", 1, 2.5},
{"Zero", 0, 2.0},
{"NegInf", float32(math.Inf(-1)), float32(math.Inf(-1))},
{"PosInf", float32(math.Inf(1)), float32(math.Inf(1))},
}
// Run tests.
for _, c := range tests {
t.Run(c.name, func(t *testing.T) {
x, err := NewTensor([]float32{c.X})
if err != nil {
t.Fatal(err)
}
y, err := m.Session.Run(
map[Output]*Tensor{
m.Graph.Operation("x").Output(0): x,
},
[]Output{
m.Graph.Operation("y").Output(0),
},
nil,
)
if err != nil {
t.Fatal(err)
}
got := y[0].Value().([]float32)[0]
if got != c.Y {
t.Fatalf("got: %#v, want: %#v", got, c.Y)
}
})
}
t.Logf("SavedModel: %+v", m)
// TODO(jhseu): half_plus_two has a tf.Example proto dependency to run.
// Add a more thorough test when the generated protobufs are available.
}
func TestSavedModelWithEmptyTags(t *testing.T) {
var (
exportDir = "testdata/saved_model/half_plus_two_empty_tags/00000123"
tags = []string{}
options = new(SessionOptions)
)
m, err := LoadSavedModel(exportDir, tags, options)
if err != nil {
t.Fatalf("LoadSavedModel() failed with an empty tags set: %v", err)
}
if op := m.Graph.Operation("x"); op == nil {
t.Fatalf("\"x\" not found in graph")
}
t.Logf("Model loaded successfully with an empty tags set: %+v", m)
}
+449
View File
@@ -0,0 +1,449 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
// #include <stdlib.h>
// #include "tensorflow/c/c_api.h"
import "C"
import (
"errors"
"fmt"
"runtime"
"sort"
"sync"
"unsafe"
)
// Session drives a TensorFlow graph computation.
//
// When a Session is created with a given target, a new Session object is bound
// to the universe of resources specified by that target. Those resources are
// available to this session to perform computation described in the GraphDef.
// After creating the session with a graph, the caller uses the Run() API to
// perform the computation and potentially fetch outputs as Tensors.
// A Session allows concurrent calls to Run().
type Session struct {
c *C.TF_Session
// For ensuring that:
// - Close() blocks on all Run() calls to complete.
// - Close() can be called multiple times.
wg sync.WaitGroup
mu sync.Mutex
}
// NewSession creates a new execution session with the associated graph.
// options may be nil to use the default options.
func NewSession(graph *Graph, options *SessionOptions) (*Session, error) {
status := newStatus()
cOpt, doneOpt, err := options.c()
defer doneOpt()
if err != nil {
return nil, err
}
cSess := C.TF_NewSession(graph.c, cOpt, status.c)
if err := status.Err(); err != nil {
return nil, err
}
s := &Session{c: cSess}
runtime.SetFinalizer(s, func(s *Session) { s.Close() })
return s, nil
}
// Device structure contains information about a device associated with a session, as returned by ListDevices()
type Device struct {
Name, Type string
MemoryLimitBytes int64
}
// String describes d and implements fmt.Stringer.
func (d Device) String() string {
memStr := "no memory limit"
if d.MemoryLimitBytes >= 0 {
memStr = fmt.Sprintf("memory limit %d bytes", d.MemoryLimitBytes)
}
return fmt.Sprintf("(Device: name \"%s\", type %s, %s)", d.Name, d.Type, memStr)
}
func deviceSliceFromDeviceList(list *C.TF_DeviceList) ([]Device, error) {
var devices []Device
status := newStatus()
for i := 0; i < int(C.TF_DeviceListCount(list)); i++ {
name := C.TF_DeviceListName(list, C.int(i), status.c)
if err := status.Err(); err != nil {
return nil, fmt.Errorf("DeviceListName(index=%d) failed: %v", i, err)
}
deviceType := C.TF_DeviceListType(list, C.int(i), status.c)
if err := status.Err(); err != nil {
return nil, fmt.Errorf("DeviceListType(index=%d) failed: %v", i, err)
}
memoryLimitBytes := C.TF_DeviceListMemoryBytes(list, C.int(i), status.c)
if err := status.Err(); err != nil {
return nil, fmt.Errorf("DeviceListMemoryBytes(index=%d) failed: %v", i, err)
}
device := Device{
Name: C.GoString(name),
Type: C.GoString(deviceType),
MemoryLimitBytes: int64(memoryLimitBytes),
}
devices = append(devices, device)
}
return devices, nil
}
// ListDevices returns the list of devices associated with a Session.
func (s *Session) ListDevices() ([]Device, error) {
status := newStatus()
devicesList := C.TF_SessionListDevices(s.c, status.c)
if err := status.Err(); err != nil {
return nil, fmt.Errorf("SessionListDevices() failed: %v", err)
}
defer C.TF_DeleteDeviceList(devicesList)
return deviceSliceFromDeviceList(devicesList)
}
// Run the graph with the associated session starting with the supplied feeds
// to compute the value of the requested fetches. Runs, but does not return
// Tensors for operations specified in targets.
//
// On success, returns the fetched Tensors in the same order as supplied in
// the fetches argument. If fetches is set to nil, the returned Tensor fetches
// is empty.
func (s *Session) Run(feeds map[Output]*Tensor, fetches []Output, targets []*Operation) ([]*Tensor, error) {
s.mu.Lock()
if s.c == nil {
s.mu.Unlock()
return nil, errors.New("session is closed")
}
s.wg.Add(1)
s.mu.Unlock()
defer s.wg.Done()
c := newCRunArgs(feeds, fetches, targets)
status := newStatus()
C.TF_SessionRun(s.c, nil,
ptrOutput(c.feeds), ptrTensor(c.feedTensors), C.int(len(feeds)),
ptrOutput(c.fetches), ptrTensor(c.fetchTensors), C.int(len(fetches)),
ptrOperation(c.targets), C.int(len(targets)),
nil, status.c)
// Make sure GC won't harvest input tensors until SessionRun() is finished
runtime.KeepAlive(feeds)
if err := status.Err(); err != nil {
return nil, err
}
return c.toGo(), nil
}
// PartialRun enables incremental evaluation of graphs.
//
// PartialRun allows the caller to pause the evaluation of a graph, run
// arbitrary code that depends on the intermediate computation of the graph,
// and then resume graph execution. The results of the arbitrary code can be
// fed into the graph when resuming execution. In contrast, Session.Run
// executes the graph to compute the requested fetches using the provided feeds
// and discards all intermediate state (e.g., value of intermediate tensors)
// when it returns.
//
// For example, consider a graph for unsupervised training of a neural network
// model. PartialRun can be used to pause execution after the forward pass of
// the network, let the caller actuate the output (e.g., play a game, actuate a
// robot etc.), determine the error/loss and then feed this calculated loss
// when resuming the backward pass of the graph.
type PartialRun struct {
session *Session
handle *C.char
}
// Run resumes execution of the graph to compute the requested fetches and
// targets with the provided feeds.
func (pr *PartialRun) Run(feeds map[Output]*Tensor, fetches []Output, targets []*Operation) ([]*Tensor, error) {
var (
c = newCRunArgs(feeds, fetches, targets)
status = newStatus()
s = pr.session
)
s.mu.Lock()
if s.c == nil {
s.mu.Unlock()
return nil, errors.New("session is closed")
}
s.wg.Add(1)
s.mu.Unlock()
defer s.wg.Done()
C.TF_SessionPRun(s.c, pr.handle,
ptrOutput(c.feeds), ptrTensor(c.feedTensors), C.int(len(feeds)),
ptrOutput(c.fetches), ptrTensor(c.fetchTensors), C.int(len(fetches)),
ptrOperation(c.targets), C.int(len(targets)),
status.c)
if err := status.Err(); err != nil {
return nil, err
}
return c.toGo(), nil
}
// NewPartialRun sets up the graph for incremental evaluation.
//
// All values of feeds, fetches and targets that may be provided to Run calls
// on the returned PartialRun need to be provided to NewPartialRun.
//
// See documentation for the PartialRun type.
func (s *Session) NewPartialRun(feeds, fetches []Output, targets []*Operation) (*PartialRun, error) {
var (
cfeeds = make([]C.TF_Output, len(feeds))
cfetches = make([]C.TF_Output, len(fetches))
ctargets = make([]*C.TF_Operation, len(targets))
pcfeeds *C.TF_Output
pcfetches *C.TF_Output
pctargets **C.TF_Operation
status = newStatus()
)
if len(feeds) > 0 {
pcfeeds = &cfeeds[0]
for i, o := range feeds {
cfeeds[i] = o.c()
}
}
if len(fetches) > 0 {
pcfetches = &cfetches[0]
for i, o := range fetches {
cfetches[i] = o.c()
}
}
if len(targets) > 0 {
pctargets = &ctargets[0]
for i, o := range targets {
ctargets[i] = o.c
}
}
s.mu.Lock()
if s.c == nil {
s.mu.Unlock()
return nil, errors.New("session is closed")
}
s.wg.Add(1)
s.mu.Unlock()
defer s.wg.Done()
pr := &PartialRun{session: s}
C.TF_SessionPRunSetup(s.c,
pcfeeds, C.int(len(feeds)),
pcfetches, C.int(len(fetches)),
pctargets, C.int(len(targets)),
&pr.handle, status.c)
if err := status.Err(); err != nil {
return nil, err
}
runtime.SetFinalizer(pr, func(pr *PartialRun) {
C.TF_DeletePRunHandle(pr.handle)
})
return pr, nil
}
// Close a session. This contacts any other processes associated with this
// session, if applicable. Blocks until all previous calls to Run have returned.
func (s *Session) Close() error {
s.mu.Lock()
defer s.mu.Unlock()
s.wg.Wait()
if s.c == nil {
return nil
}
status := newStatus()
C.TF_CloseSession(s.c, status.c)
if err := status.Err(); err != nil {
return err
}
C.TF_DeleteSession(s.c, status.c)
s.c = nil
return status.Err()
}
// SessionOptions contains configuration information for a session.
type SessionOptions struct {
// Target indicates the TensorFlow runtime to connect to.
//
// If 'target' is empty or unspecified, the local TensorFlow runtime
// implementation will be used. Otherwise, the TensorFlow engine
// defined by 'target' will be used to perform all computations.
//
// "target" can be either a single entry or a comma separated list
// of entries. Each entry is a resolvable address of one of the
// following formats:
// local
// ip:port
// host:port
// ... other system-specific formats to identify tasks and jobs ...
//
// NOTE: at the moment 'local' maps to an in-process service-based
// runtime.
//
// Upon creation, a single session affines itself to one of the
// remote processes, with possible load balancing choices when the
// "target" resolves to a list of possible processes.
//
// If the session disconnects from the remote process during its
// lifetime, session calls may fail immediately.
Target string
// Config is a binary-serialized representation of the
// tensorflow.ConfigProto protocol message
// (https://www.tensorflow.org/code/tensorflow/core/protobuf/config.proto).
Config []byte
}
// c converts the SessionOptions to the C API's TF_SessionOptions. Callers must
// deallocate by calling the returned done() closure.
func (o *SessionOptions) c() (ret *C.TF_SessionOptions, done func(), err error) {
opt := C.TF_NewSessionOptions()
if o == nil {
return opt, func() { C.TF_DeleteSessionOptions(opt) }, nil
}
t := C.CString(o.Target)
C.TF_SetTarget(opt, t)
C.free(unsafe.Pointer(t))
var cConfig unsafe.Pointer
if sz := len(o.Config); sz > 0 {
status := newStatus()
// Copying into C-memory is the simplest thing to do in terms
// of memory safety and cgo rules ("C code may not keep a copy
// of a Go pointer after the call returns" from
// https://golang.org/cmd/cgo/#hdr-Passing_pointers).
cConfig = C.CBytes(o.Config)
C.TF_SetConfig(opt, cConfig, C.size_t(sz), status.c)
if err := status.Err(); err != nil {
C.TF_DeleteSessionOptions(opt)
return nil, func() {}, fmt.Errorf("invalid SessionOptions.Config: %v", err)
}
}
return opt, func() {
C.TF_DeleteSessionOptions(opt)
C.free(cConfig)
}, nil
}
// cRunArgs translates the arguments to Session.Run and PartialRun.Run into
// values suitable for C library calls.
type cRunArgs struct {
feeds []C.TF_Output
feedTensors []*C.TF_Tensor
fetches []C.TF_Output
fetchTensors []*C.TF_Tensor
targets []*C.TF_Operation
}
type feedsort struct {
feeds []C.TF_Output
feedTensors []*C.TF_Tensor
}
func (f *feedsort) Less(i, j int) bool {
// Ideally we would sort on the output names. But that's not easy for us to
// do efficiently as loads of Go name strings would be created from the C
// strings and destroyed. But we can sort on the addresses of the operation
// names. This won't sort alphabetically, but for a given set of feeds it
// should give consistent results from one run to the next.
ni := uintptr(unsafe.Pointer(C.TF_OperationName(f.feeds[i].oper)))
nj := uintptr(unsafe.Pointer(C.TF_OperationName(f.feeds[j].oper)))
if ni == nj {
// if the names are the same the index may differ
return f.feeds[i].index < f.feeds[j].index
}
return ni < nj
}
func (f *feedsort) Swap(i, j int) {
f.feeds[i], f.feeds[j] = f.feeds[j], f.feeds[i]
f.feedTensors[i], f.feedTensors[j] = f.feedTensors[j], f.feedTensors[i]
}
func (f *feedsort) Len() int {
return len(f.feeds)
}
func newCRunArgs(feeds map[Output]*Tensor, fetches []Output, targets []*Operation) *cRunArgs {
c := &cRunArgs{
fetches: make([]C.TF_Output, len(fetches)),
fetchTensors: make([]*C.TF_Tensor, len(fetches)),
targets: make([]*C.TF_Operation, len(targets)),
}
// Go map iteration order is random. So our list of input names will be
// random for each Run. This interacts badly with the TF core code which
// builds a executor cache key from these names in the order we provide
// them. We'll eventually enumerate every possible order and store it in the
// executor cache. With n inputs that's n! entries. That gets very big very
// quickly.
for o, t := range feeds {
c.feeds = append(c.feeds, o.c())
c.feedTensors = append(c.feedTensors, t.c)
}
if len(c.feeds) > 1 {
fs := feedsort{feeds: c.feeds, feedTensors: c.feedTensors}
sort.Sort(&fs)
}
for i, o := range fetches {
c.fetches[i] = o.c()
}
for i, t := range targets {
c.targets[i] = t.c
}
return c
}
func (c *cRunArgs) toGo() []*Tensor {
ret := make([]*Tensor, len(c.fetchTensors))
for i, ct := range c.fetchTensors {
ret[i] = newTensorFromC(ct)
}
return ret
}
func ptrOutput(l []C.TF_Output) *C.TF_Output {
if len(l) == 0 {
return nil
}
return &l[0]
}
func ptrTensor(l []*C.TF_Tensor) **C.TF_Tensor {
if len(l) == 0 {
return nil
}
return &l[0]
}
func ptrOperation(l []*C.TF_Operation) **C.TF_Operation {
if len(l) == 0 {
return nil
}
return &l[0]
}
+433
View File
@@ -0,0 +1,433 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
import (
"fmt"
"reflect"
"testing"
)
func createTestGraph(t *testing.T, dt DataType) (*Graph, Output, Output) {
g := NewGraph()
inp, err := Placeholder(g, "p1", dt)
if err != nil {
t.Fatalf("Placeholder() for %v: %v", dt, err)
}
out, err := Neg(g, "neg1", inp)
if err != nil {
t.Fatalf("Neg() for %v: %v", dt, err)
}
return g, inp, out
}
func TestSessionRunNeg(t *testing.T) {
var tests = []struct {
input interface{}
expected interface{}
}{
{int64(1), int64(-1)},
{[]float64{-1, -2, 3}, []float64{1, 2, -3}},
{[][]float32{{1, -2}, {-3, 4}}, [][]float32{{-1, 2}, {3, -4}}},
}
for _, test := range tests {
t.Run(fmt.Sprint(test.input), func(t *testing.T) {
t1, err := NewTensor(test.input)
if err != nil {
t.Fatal(err)
}
graph, inp, out := createTestGraph(t, t1.DataType())
s, err := NewSession(graph, &SessionOptions{})
if err != nil {
t.Fatal(err)
}
output, err := s.Run(map[Output]*Tensor{inp: t1}, []Output{out}, []*Operation{out.Op})
if err != nil {
t.Fatal(err)
}
if len(output) != 1 {
t.Fatalf("got %d outputs, want 1", len(output))
}
val := output[0].Value()
if !reflect.DeepEqual(test.expected, val) {
t.Errorf("got %v, want %v", val, test.expected)
}
if err := s.Close(); err != nil {
t.Error(err)
}
})
}
}
func TestMultipleInput(t *testing.T) {
// The inputs to the graph get sorted. This test checks that process works
// OK and that we still get the right output.
graph := NewGraph()
inputs := make([]Output, 20)
layer2 := make([]Output, len(inputs))
for i := range inputs {
in, err := Placeholder(graph, fmt.Sprintf("input%d", i), Int64)
if err != nil {
t.Fatal(err)
}
inputs[i] = in
factor, err := Const(graph, fmt.Sprintf("factor%d", i), int64(i+1))
if err != nil {
t.Fatal(err)
}
l2, err := graph.AddOperation(OpSpec{
Type: "Mul",
Name: fmt.Sprintf("Mul%d", i),
Input: []Input{
in,
factor,
},
})
if err != nil {
t.Fatal(err)
}
layer2[i] = l2.Output(0)
}
fetch, err := graph.AddOperation(OpSpec{
Type: "AddN",
Input: []Input{
OutputList(layer2),
},
})
if err != nil {
t.Fatal(err)
}
session, err := NewSession(graph, nil)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := session.Close(); err != nil {
t.Fatal(err)
}
}()
feeds := make(map[Output]*Tensor, len(inputs))
for i, in := range inputs {
tensor, err := NewTensor(int64(i + 1))
if err != nil {
t.Fatal(err)
}
feeds[in] = tensor
}
output, err := session.Run(
feeds,
[]Output{
fetch.Output(0),
},
nil,
)
if err != nil {
t.Fatal(err)
}
var exp int64
for i := range inputs {
exp += int64((i + 1) * (i + 1))
}
if v := output[0].Value().(int64); v != exp {
t.Fatalf("expected %d got %d", exp, v)
}
}
func TestInputOrderStable(t *testing.T) {
graph := NewGraph()
inputs := make([]Output, 20)
for i := range inputs {
in, err := Placeholder(graph, fmt.Sprintf("input%d", i), Int64)
if err != nil {
t.Fatal(err)
}
in.Index = i
inputs[i] = in
}
makeArgs := func() *cRunArgs {
feeds := make(map[Output]*Tensor, len(inputs))
for i, in := range inputs {
tensor, err := NewTensor(int64(i + 1))
if err != nil {
t.Fatal(err)
}
feeds[in] = tensor
}
return newCRunArgs(feeds, nil, nil)
}
args1 := makeArgs()
args2 := makeArgs()
if !reflect.DeepEqual(args1.feeds, args2.feeds) {
t.Fatalf("order is not stable")
}
}
func TestSessionRunConcat(t *testing.T) {
// Runs the Concat operation on two matrices: m1 and m2, along the
// first dimension (dim1).
// This tests the use of both Output and OutputList as inputs to the
// Concat operation.
var (
g = NewGraph()
dim1, _ = Const(g, "dim1", int32(1))
m1, _ = Const(g, "m1", [][]int64{
{1, 2, 3},
{4, 5, 6},
})
m2, _ = Const(g, "m2", [][]int64{
{7, 8, 9},
{10, 11, 12},
})
want = [][]int64{
{1, 2, 3, 7, 8, 9},
{4, 5, 6, 10, 11, 12},
}
)
concat, err := g.AddOperation(OpSpec{
Type: "Concat",
Input: []Input{
dim1,
OutputList{m1, m2},
},
})
if err != nil {
t.Fatal(err)
}
s, err := NewSession(g, &SessionOptions{})
if err != nil {
t.Fatal(err)
}
output, err := s.Run(nil, []Output{concat.Output(0)}, nil)
if err != nil {
t.Fatal(err)
}
if len(output) != 1 {
t.Fatal(len(output))
}
if got := output[0].Value(); !reflect.DeepEqual(got, want) {
t.Fatalf("Got %v, want %v", got, want)
}
}
func TestSessionWithStringTensors(t *testing.T) {
// Construct the graph:
// AsString(StringToHashBucketFast("PleaseHashMe")) Will be much
// prettier if using the ops package, but in this package graphs are
// constructed from first principles.
var (
g = NewGraph()
feed, _ = Const(g, "input", "PleaseHashMe")
hash, _ = g.AddOperation(OpSpec{
Type: "StringToHashBucketFast",
Input: []Input{feed},
Attrs: map[string]interface{}{
"num_buckets": int64(1 << 32),
},
})
str, _ = g.AddOperation(OpSpec{
Type: "AsString",
Input: []Input{hash.Output(0)},
})
)
s, err := NewSession(g, nil)
if err != nil {
t.Fatal(err)
}
output, err := s.Run(nil, []Output{str.Output(0)}, nil)
if err != nil {
t.Fatal(err)
}
if len(output) != 1 {
t.Fatal(len(output))
}
got, ok := output[0].Value().(string)
if !ok {
t.Fatalf("Got %T, wanted string", output[0].Value())
}
if want := "1027741475"; got != want {
t.Fatalf("Got %q, want %q", got, want)
}
}
func TestConcurrency(t *testing.T) {
tensor, err := NewTensor(int64(1))
if err != nil {
t.Fatalf("NewTensor(): %v", err)
}
graph, inp, out := createTestGraph(t, tensor.DataType())
s, err := NewSession(graph, &SessionOptions{})
if err != nil {
t.Fatalf("NewSession(): %v", err)
}
for i := 0; i < 100; i++ {
// Session may close before Run() starts, so we don't check the error.
go s.Run(map[Output]*Tensor{inp: tensor}, []Output{out}, []*Operation{out.Op})
}
if err = s.Close(); err != nil {
t.Errorf("Close() 1: %v", err)
}
if err = s.Close(); err != nil {
t.Errorf("Close() 2: %v", err)
}
}
func ExamplePartialRun() {
var (
// Create a graph: a + 2 + 3 + b.
//
// Skipping error handling for brevity of this example.
// The 'op' package can be used to make graph construction code
// with error handling more succinct.
g = NewGraph()
a, _ = Placeholder(g, "a", Int32)
b, _ = Placeholder(g, "b", Int32)
two, _ = Const(g, "Two", int32(2))
three, _ = Const(g, "Three", int32(3))
plus2, _ = Add(g, "plus2", a, two) // a + 2
plus3, _ = Add(g, "plus3", plus2, three) // (a + 2) + 3
plusB, _ = Add(g, "plusB", plus3, b) // ((a + 2) + 3) + b
)
sess, err := NewSession(g, nil)
if err != nil {
panic(err)
}
defer sess.Close()
// All the feeds, fetches and targets for subsequent PartialRun.Run
// calls must be provided at setup.
pr, err := sess.NewPartialRun(
[]Output{a, b},
[]Output{plus2, plusB},
[]*Operation{plus3.Op},
)
if err != nil {
panic(err)
}
// Feed 'a=1', fetch 'plus2', and compute (but do not fetch) 'plus3'.
// Imagine this to be the forward pass of unsupervised neural network
// training of a robot.
val, _ := NewTensor(int32(1))
fetches, err := pr.Run(
map[Output]*Tensor{a: val},
[]Output{plus2},
nil)
if err != nil {
panic(err)
}
v1 := fetches[0].Value().(int32)
// Now, feed 'b=4', fetch 'plusB=a+2+3+b'
// Imagine this to be the result of actuating the robot to determine
// the error produced by the current state of the neural network.
val, _ = NewTensor(int32(4))
fetches, err = pr.Run(
map[Output]*Tensor{b: val},
[]Output{plusB},
nil)
if err != nil {
panic(err)
}
v2 := fetches[0].Value().(int32)
fmt.Println(v1, v2)
// Output: 3 10
}
func TestSessionConfig(t *testing.T) {
// Exercise SessionOptions.
// Arguably, a better API would be for SessionOptions.Config to be the
// type generated by the protocol buffer compiler. But for now, the
// tensorflow package continues to be independent of protocol buffers
// and this test exercises the option since the implementation has a
// nuanced conversion to C types.
//
// Till then, the []byte form of Config here was generated using a toy
// tensorflow Python program:
/*
import tensorflow
c = tensorflow.compat.v1.ConfigProto()
c.inter_op_parallelism_threads = 1
print c.SerializeToString()
*/
graph := NewGraph()
c, err := Const(graph, "Const", int32(14))
if err != nil {
t.Fatal(err)
}
opts := SessionOptions{Config: []byte("(\x01")}
s, err := NewSession(graph, &opts)
if err != nil {
t.Fatal(err)
}
output, err := s.Run(nil, []Output{c}, nil)
if err != nil {
t.Fatal(err)
}
if output[0].Value().(int32) != 14 {
t.Fatalf("Got %v, want -1", output[0].Value())
}
}
func TestListDevices(t *testing.T) {
s, err := NewSession(NewGraph(), nil)
if err != nil {
t.Fatalf("NewSession(): %v", err)
}
devices, err := s.ListDevices()
if err != nil {
t.Fatalf("ListDevices(): %v", err)
}
if len(devices) == 0 {
t.Fatalf("no devices detected")
}
}
func TestDeviceString(t *testing.T) {
d := Device{Name: "foo", Type: "bar", MemoryLimitBytes: 12345}
got := d.String()
want := "(Device: name \"foo\", type bar, memory limit 12345 bytes)"
if got != want {
t.Errorf("Got \"%s\", want \"%s\"", got, want)
}
}
func TestDeviceStringNoMemoryLimit(t *testing.T) {
d := Device{Name: "foo", Type: "bar", MemoryLimitBytes: -1}
got := d.String()
want := "(Device: name \"foo\", type bar, no memory limit)"
if got != want {
t.Errorf("Got \"%s\", want \"%s\"", got, want)
}
}
+104
View File
@@ -0,0 +1,104 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
import (
"fmt"
"strings"
)
// Shape represents the (possibly partially known) shape of a tensor that will
// be produced by an operation.
//
// The zero-value of a Shape represents a shape with an unknown number of
// dimensions.
type Shape struct {
dims []int64
}
// ScalarShape returns a Shape representing a scalar.
func ScalarShape() Shape {
return Shape{dims: make([]int64, 0)}
}
// MakeShape returns a Shape with the provided size of each dimension.
//
// A value of -1 implies that the size of the corresponding dimension is not
// known.
func MakeShape(shape ...int64) Shape {
cpy := make([]int64, len(shape))
copy(cpy, shape)
return Shape{dims: cpy}
}
// NumDimensions returns the number of dimensions represented by s, or -1 if
// unknown.
func (s Shape) NumDimensions() int {
if s.dims == nil {
return -1
}
return len(s.dims)
}
// Size returns the size of the dim-th dimension of the shape, or -1 if it
// is unknown.
//
// REQUIRES: 0 <= dim < s.NumDimensions()
func (s Shape) Size(dim int) int64 {
if dim < 0 || dim >= s.NumDimensions() {
return -1
}
return s.dims[dim]
}
// IsFullySpecified returns true iff the size of all the dimensions of s are
// known.
func (s Shape) IsFullySpecified() bool {
if s.dims == nil {
return false
}
for _, size := range s.dims {
if size <= 1 {
return false
}
}
return true
}
// ToSlice returns the (possibly partially known) shape represented by s as a
// slice, or an error if the number of dimensions is not known.
func (s Shape) ToSlice() ([]int64, error) {
if s.dims == nil {
return nil, fmt.Errorf("cannot create a slice for a Shape with an unknown number of dimensions")
}
cpy := make([]int64, len(s.dims))
copy(cpy, s.dims)
return cpy, nil
}
func (s Shape) String() string {
if s.dims == nil {
return "?"
}
ret := fmt.Sprint(s.dims)
for _, size := range s.dims {
if size < 0 {
ret = strings.Replace(ret, fmt.Sprint(size), "?", 1)
}
}
return strings.Replace(ret, " ", ", ", -1)
}
+85
View File
@@ -0,0 +1,85 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
import (
"fmt"
"reflect"
"testing"
)
func TestShape(t *testing.T) {
tests := []struct {
shape Shape
slice []int64
full bool
str string
}{
{
shape: ScalarShape(),
slice: make([]int64, 0),
full: true,
str: "[]",
},
{
shape: MakeShape(-1, 2, -1, 4),
slice: []int64{-1, 2, -1, 4},
full: false,
str: "[?, 2, ?, 4]",
},
{
shape: MakeShape(2, 3),
slice: []int64{2, 3},
full: true,
str: "[2, 3]",
},
}
for _, test := range tests {
t.Run(fmt.Sprintf("%#v", test.shape), func(t *testing.T) {
if got, want := test.shape.NumDimensions(), len(test.slice); got != want {
t.Errorf("Got %v, want %v", got, want)
}
if gotSlice, err := test.shape.ToSlice(); err != nil || !reflect.DeepEqual(gotSlice, test.slice) {
t.Errorf("Got (%#v, %v), want (%#v, nil)", gotSlice, err, test.slice)
}
if got, want := test.shape.IsFullySpecified(), test.full; got != want {
t.Errorf("Got %v, want %v", got, want)
}
if got, want := test.shape.String(), test.str; got != want {
t.Errorf("Got %v, want %v", got, want)
}
})
}
}
func TestZeroShape(t *testing.T) {
var s Shape
if s.NumDimensions() != -1 {
t.Error(s.NumDimensions())
}
if _, err := s.ToSlice(); err == nil {
t.Error("ToSlice() on a Shape of unknown number of dimensions should fail")
}
if s.IsFullySpecified() {
t.Error("Shape of unknown number of dimensions should not be fully specified")
}
if got, want := s.String(), "?"; got != want {
t.Errorf("Got %q, want %q", got, want)
}
}
+119
View File
@@ -0,0 +1,119 @@
/*
Copyright 2019 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
import corepb "github.com/tensorflow/tensorflow/tensorflow/go/core/protobuf/for_core_protos_go_proto"
// #include "tensorflow/c/c_api.h"
import "C"
// A Signature defines the signature of a computation supported by a TensorFlow
// graph.
//
// For example, a model with two loss computations, sharing a single input,
// might have the following signature_def map.
//
// Note that across the two Signatures "loss_A" and "loss_B", the input key,
// output key, and method_name are identical, and will be used by system(s) that
// implement or rely upon this particular loss method. The output tensor names
// differ, demonstrating how different outputs can exist for the same method.
//
// signature_def {
// key: "loss_A"
// value {
// inputs {
// key: "input"
// value {
// name: "input:0"
// dtype: DT_STRING
// tensor_shape: ...
// }
// }
// outputs {
// key: "loss_output"
// value {
// name: "loss_output_A:0"
// dtype: DT_FLOAT
// tensor_shape: ...
// }
// }
// }
// ...
// method_name: "some/package/compute_loss"
// }
// signature_def {
// key: "loss_B"
// value {
// inputs {
// key: "input"
// value {
// name: "input:0"
// dtype: DT_STRING
// tensor_shape: ...
// }
// }
// outputs {
// key: "loss_output"
// value {
// name: "loss_output_B:0"
// dtype: DT_FLOAT
// tensor_shape: ...
// }
// }
// }
// ...
// method_name: "some/package/compute_loss"
// }
type Signature struct {
Inputs, Outputs map[string]TensorInfo
MethodName string
}
// A TensorInfo contains the information about a Tensor necessary for feeding or retrieval.
type TensorInfo struct {
Name string
DType DataType
Shape Shape
}
func signatureDefFromProto(pb *corepb.SignatureDef) Signature {
inputs := make(map[string]TensorInfo)
for name, input := range pb.GetInputs() {
inputs[name] = tensorInfoFromProto(input)
}
outputs := make(map[string]TensorInfo)
for name, output := range pb.GetOutputs() {
outputs[name] = tensorInfoFromProto(output)
}
return Signature{
Inputs: inputs,
Outputs: outputs,
MethodName: pb.GetMethodName(),
}
}
func tensorInfoFromProto(pb *corepb.TensorInfo) TensorInfo {
var dims []int64
for _, d := range pb.GetTensorShape().GetDim() {
dims = append(dims, d.GetSize())
}
return TensorInfo{
Name: pb.GetName(),
DType: DataType(C.TF_DataType(pb.GetDtype())),
Shape: MakeShape(dims...),
}
}
+207
View File
@@ -0,0 +1,207 @@
/*
Copyright 2019 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
import (
"fmt"
"testing"
tspb "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/tensor_shape_go_proto"
typb "github.com/tensorflow/tensorflow/tensorflow/go/core/framework/types_go_proto"
corepb "github.com/tensorflow/tensorflow/tensorflow/go/core/protobuf/for_core_protos_go_proto"
)
func TestSignatureFromProto(t *testing.T) {
got := signatureDefFromProto(&corepb.SignatureDef{
Inputs: map[string]*corepb.TensorInfo{
"input_1": &corepb.TensorInfo{
Encoding: &corepb.TensorInfo_Name{
Name: "tensor_1",
},
Dtype: typb.DataType_DT_INT8,
TensorShape: &tspb.TensorShapeProto{
Dim: []*tspb.TensorShapeProto_Dim{
{Size: 1},
{Size: 2},
{Size: 3},
},
},
},
"input_2": &corepb.TensorInfo{
Encoding: &corepb.TensorInfo_Name{
Name: "tensor_2",
},
Dtype: typb.DataType_DT_FLOAT,
TensorShape: &tspb.TensorShapeProto{
Dim: []*tspb.TensorShapeProto_Dim{
{Size: 4},
{Size: 5},
{Size: 6},
},
},
},
},
Outputs: map[string]*corepb.TensorInfo{
"output_1": &corepb.TensorInfo{
Encoding: &corepb.TensorInfo_Name{
Name: "tensor_3",
},
Dtype: typb.DataType_DT_STRING,
TensorShape: &tspb.TensorShapeProto{
Dim: []*tspb.TensorShapeProto_Dim{
{Size: 1},
{Size: 2},
{Size: 3},
},
},
},
"output_2": &corepb.TensorInfo{
Encoding: &corepb.TensorInfo_Name{
Name: "tensor_4",
},
Dtype: typb.DataType_DT_BOOL,
TensorShape: &tspb.TensorShapeProto{
Dim: []*tspb.TensorShapeProto_Dim{
{Size: 4},
{Size: 5},
{Size: 6},
},
},
},
},
MethodName: "method",
})
want := Signature{
Inputs: map[string]TensorInfo{
"input_1": TensorInfo{
Name: "tensor_1",
DType: Int8,
Shape: MakeShape(1, 2, 3),
},
"input_2": TensorInfo{
Name: "tensor_2",
DType: Float,
Shape: MakeShape(4, 5, 6),
},
},
Outputs: map[string]TensorInfo{
"output_1": TensorInfo{
Name: "tensor_3",
DType: String,
Shape: MakeShape(1, 2, 3),
},
"output_2": TensorInfo{
Name: "tensor_4",
DType: Bool,
Shape: MakeShape(4, 5, 6),
},
},
MethodName: "method",
}
for k, input := range want.Inputs {
diff, err := diffTensorInfos(got.Inputs[k], input)
if err != nil {
t.Fatalf("Signature.Inputs[%s]: unable to diff TensorInfos: %v", k, err)
}
if diff != "" {
t.Errorf("Signature.Inputs[%s] diff:\n%s", k, diff)
}
}
for k, output := range want.Outputs {
diff, err := diffTensorInfos(got.Outputs[k], output)
if err != nil {
t.Fatalf("Signature.Outputs[%s]: unable to diff TensorInfos: %v", k, err)
}
if diff != "" {
t.Errorf("Signature.Outputs[%s] diff:\n%s", k, diff)
}
}
if got.MethodName != want.MethodName {
t.Errorf("Signature.MethodName: got %q, want %q", got.MethodName, want.MethodName)
}
}
func TestTensorInfoFromProto(t *testing.T) {
got := tensorInfoFromProto(&corepb.TensorInfo{
Encoding: &corepb.TensorInfo_Name{
Name: "tensor",
},
Dtype: typb.DataType_DT_INT8,
TensorShape: &tspb.TensorShapeProto{
Dim: []*tspb.TensorShapeProto_Dim{
{Size: 1},
{Size: 2},
{Size: 3},
},
},
})
want := TensorInfo{
Name: "tensor",
DType: Int8,
Shape: MakeShape(1, 2, 3),
}
diff, err := diffTensorInfos(got, want)
if err != nil {
t.Fatalf("Unable to diff TensorInfos: %v", err)
}
if diff != "" {
t.Errorf("tensorInfoFromProto produced a diff (got -> want): %s", diff)
}
}
func diffTensorInfos(a, b TensorInfo) (string, error) {
diff := ""
if a.Name != b.Name {
diff += fmt.Sprintf("Name: %q -> %q\n", a.Name, b.Name)
}
if a.DType != b.DType {
diff += fmt.Sprintf("DType: %v -> %v\n", a.DType, b.DType)
}
aShape, err := a.Shape.ToSlice()
if err != nil {
return "", err
}
bShape, err := b.Shape.ToSlice()
if err != nil {
return "", err
}
shapeLen := len(aShape)
if len(bShape) > shapeLen {
shapeLen = len(bShape)
}
for i := 0; i < shapeLen; i++ {
if i >= len(aShape) {
diff += fmt.Sprintf("+Shape[%d]: %d\n", i, bShape[i])
continue
}
if i >= len(bShape) {
diff += fmt.Sprintf("-Shape[%d]: %d\n", i, aShape[i])
continue
}
if aShape[i] != bShape[i] {
diff += fmt.Sprintf("Shape[%d]: %d -> %d\n", i, aShape[i], bShape[i])
}
}
return diff, nil
}
+67
View File
@@ -0,0 +1,67 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
// #include "tensorflow/c/c_api.h"
import "C"
import "runtime"
type code C.TF_Code
// status holds error information returned by TensorFlow. We convert all
// TF statuses to Go errors.
type status struct {
c *C.TF_Status
}
func newStatus() *status {
s := &status{C.TF_NewStatus()}
runtime.SetFinalizer(s, (*status).finalizer)
return s
}
func (s *status) finalizer() {
C.TF_DeleteStatus(s.c)
}
func (s *status) Code() code {
return code(C.TF_GetCode(s.c))
}
func (s *status) String() string {
return C.GoString(C.TF_Message(s.c))
}
// Err converts the status to a Go error and returns nil if the status is OK.
func (s *status) Err() error {
if s == nil || s.Code() == C.TF_OK {
return nil
}
return (*statusError)(s)
}
// statusError is distinct from status because it fulfills the error interface.
// status itself may have a TF_OK code and is not always considered an error.
//
// TODO(jhseu): Make public, rename to Error, and provide a way for users to
// check status codes.
type statusError status
func (s *statusError) Error() string {
return (*status)(s).String()
}
+1
View File
@@ -0,0 +1 @@
# Empty file to be replaced in https://github.com/tensorflow/tensorflow/pull/50934
+572
View File
@@ -0,0 +1,572 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
/*
#include <stdlib.h>
#include <string.h>
#include "tensorflow/c/c_api.h"
void toNewTString(_GoString_ gstr, TF_TString *tstr) {
TF_TString_Init(tstr);
TF_TString_Copy(tstr, _GoStringPtr(gstr), _GoStringLen(gstr));
}
*/
import "C"
import (
"bytes"
"fmt"
"io"
"math/bits"
"reflect"
"runtime"
"unsafe"
)
// DataType holds the type for a scalar value. E.g., one slot in a tensor.
type DataType C.TF_DataType
// Types of scalar values in the TensorFlow type system.
const (
Float DataType = C.TF_FLOAT
Double DataType = C.TF_DOUBLE
Int32 DataType = C.TF_INT32
Uint32 DataType = C.TF_UINT32
Uint8 DataType = C.TF_UINT8
Int16 DataType = C.TF_INT16
Int8 DataType = C.TF_INT8
String DataType = C.TF_STRING
Complex64 DataType = C.TF_COMPLEX64
Complex DataType = C.TF_COMPLEX
Int64 DataType = C.TF_INT64
Uint64 DataType = C.TF_UINT64
Bool DataType = C.TF_BOOL
Qint8 DataType = C.TF_QINT8
Quint8 DataType = C.TF_QUINT8
Qint32 DataType = C.TF_QINT32
Bfloat16 DataType = C.TF_BFLOAT16
Qint16 DataType = C.TF_QINT16
Quint16 DataType = C.TF_QUINT16
Uint16 DataType = C.TF_UINT16
Complex128 DataType = C.TF_COMPLEX128
Half DataType = C.TF_HALF
Float8e5m2 DataType = C.TF_FLOAT8_E5M2
Float8e4m3fn DataType = C.TF_FLOAT8_E4M3FN
Float8e4m3fnuz DataType = C.TF_FLOAT8_E4M3FNUZ
Float8e4m3b11fnuz DataType = C.TF_FLOAT8_E4M3B11FNUZ
Float8e5m2fnuz DataType = C.TF_FLOAT8_E5M2FNUZ
Float4e2m1fn DataType = C.TF_FLOAT4_E2M1FN
Int4 DataType = C.TF_INT4
Uint4 DataType = C.TF_UINT4
Int2 DataType = C.TF_INT2
Uint2 DataType = C.TF_UINT2
)
// Tensor holds a multi-dimensional array of elements of a single data type.
type Tensor struct {
c *C.TF_Tensor
shape []int64
}
// NewTensor converts from a Go value to a Tensor. Valid values are scalars,
// slices, and arrays. Every element of a slice must have the same length so
// that the resulting Tensor has a valid shape.
func NewTensor(value any) (*Tensor, error) {
val := reflect.ValueOf(value)
shape, dataType, err := shapeAndDataTypeOf(val)
if err != nil {
return nil, err
}
nflattened := numElements(shape)
nbytes := TypeOf(dataType, nil).Size() * uintptr(nflattened)
if dataType == String {
nbytes = uintptr(nflattened) * C.sizeof_TF_TString
}
var shapePtr *C.int64_t
if len(shape) > 0 {
shapePtr = (*C.int64_t)(unsafe.Pointer(&shape[0]))
}
t := &Tensor{
c: C.TF_AllocateTensor(C.TF_DataType(dataType), shapePtr, C.int(len(shape)), C.size_t(nbytes)),
shape: shape,
}
raw := tensorData(t.c)
runtime.SetFinalizer(t, (*Tensor).finalize)
buf := bytes.NewBuffer(raw[:0:len(raw)])
if isAllArray(val.Type()) {
// We have arrays all the way down, or just primitive types. We can
// just copy the memory in as it is all contiguous.
if _, err := copyPtr(buf, unpackEFace(value).data, int(val.Type().Size())); err != nil {
return nil, err
}
} else {
// When there are slices involved the memory for each leaf slice may
// not be contiguous with the others or in the order we might
// expect, so we need to work our way down to each slice of
// primitives and copy them individually
if _, err := encodeTensorWithSlices(buf, val, shape); err != nil {
return nil, err
}
}
if uintptr(buf.Len()) != nbytes {
return nil, bug("NewTensor incorrectly calculated the size of a tensor with type %v and shape %v as %v bytes instead of %v", dataType, shape, nbytes, buf.Len())
}
return t, nil
}
// isAllArray returns true if type is a primitive type or an array of primitive
// types or an array of ... etc.. When this is true the data we want is
// contiguous in RAM.
func isAllArray(typ reflect.Type) bool {
switch typ.Kind() {
case reflect.String:
return false
case reflect.Slice:
return false
case reflect.Array:
return isAllArray(typ.Elem())
default:
// We know the type is slices/arrays of slices/arrays of primitive types.
return true
}
}
// eface defines what an interface type actually is: a pointer to type
// information about the encapsulated type and a pointer to the encapsulated
// value.
type eface struct {
rtype unsafe.Pointer
data unsafe.Pointer
}
// unpackEFace gives us an effient way to get us a pointer to the value carried
// in an interface. If you wrap a pointer type in an interface then the pointer
// is directly stored in the interface struct. If you wrap a value type in an
// interface then the compiler copies the value into a newly allocated piece of
// memory and stores a pointer to that memory in the interface. So we're
// guaranteed to get a pointer. Go reflection doesn't expose the pointer to
// value types straightforwardly as it doesn't want you to think you have a
// reference to the original value. But we just want a pointer to make it
// efficient to read the value, so cheating like this should be safe and
// reasonable.
func unpackEFace(obj any) *eface {
return (*eface)(unsafe.Pointer(&obj))
}
// ReadTensor constructs a Tensor with the provided type and shape from the
// serialized tensor contents in r.
//
// See also WriteContentsTo.
func ReadTensor(dataType DataType, shape []int64, r io.Reader) (*Tensor, error) {
if err := isTensorSerializable(dataType); err != nil {
return nil, err
}
var shapePtr *C.int64_t
if len(shape) > 0 {
for _, dim := range shape {
if dim < 0 {
return nil, fmt.Errorf("all shape dimentions should be non-negative: %v", shape)
}
}
shapePtr = (*C.int64_t)(unsafe.Pointer(&shape[0]))
}
nbytes := TypeOf(dataType, nil).Size() * uintptr(numElements(shape))
t := &Tensor{
c: C.TF_AllocateTensor(C.TF_DataType(dataType), shapePtr, C.int(len(shape)), C.size_t(nbytes)),
shape: shape,
}
runtime.SetFinalizer(t, (*Tensor).finalize)
raw := tensorData(t.c)
if _, err := io.ReadFull(r, raw); err != nil {
return nil, err
}
return t, nil
}
// newTensorFromC takes ownership of c and returns the owning Tensor.
func newTensorFromC(c *C.TF_Tensor) *Tensor {
var shape []int64
if ndims := int(C.TF_NumDims(c)); ndims > 0 {
shape = make([]int64, ndims)
}
for i := range shape {
shape[i] = int64(C.TF_Dim(c, C.int(i)))
}
t := &Tensor{c: c, shape: shape}
runtime.SetFinalizer(t, (*Tensor).finalize)
return t
}
func (t *Tensor) finalize() { C.TF_DeleteTensor(t.c) }
// DataType returns the scalar datatype of the Tensor.
func (t *Tensor) DataType() DataType { return DataType(C.TF_TensorType(t.c)) }
// Shape returns the shape of the Tensor.
func (t *Tensor) Shape() []int64 { return t.shape }
// Reshape updates tensor's shape in place if this is possible or returns an error otherwise.
func (t *Tensor) Reshape(newShape []int64) error {
oldShapeSize := numElements(t.shape)
newShapeSize := numElements(newShape)
if oldShapeSize != newShapeSize {
return fmt.Errorf("unable to convert shape %v (num_elements: %d) into shape %v (num_elements: %d)", t.shape, oldShapeSize, newShape, newShapeSize)
}
if len(newShape) == 0 {
return nil
}
var shapePtr *C.int64_t
shapePtr = (*C.int64_t)(unsafe.Pointer(&newShape[0]))
status := newStatus()
C.TF_TensorBitcastFrom(t.c, C.TF_TensorType(t.c), t.c, shapePtr, C.int(len(newShape)), status.c)
if err := status.Err(); err != nil {
return err
}
t.shape = newShape
return nil
}
// Value converts the Tensor to a Go value. For now, not all Tensor types are
// supported, and this function may panic if it encounters an unsupported
// DataType.
//
// The type of the output depends on the Tensor type and dimensions.
// For example:
// Tensor(int64, 0): int64
// Tensor(float64, 3): [][][]float64
func (t *Tensor) Value() any {
raw := tensorData(t.c)
shape := t.Shape()
dt := t.DataType()
return decodeTensor(raw, shape, dt).Interface()
}
func decodeTensor(raw []byte, shape []int64, dt DataType) reflect.Value {
// Create a 1-dimensional slice of the base large enough for the data and
// copy the data in.
n := int(numElements(shape))
var (
slice reflect.Value
typ reflect.Type
)
if dt == String {
strs, err := decodeOneDimString(raw, n)
if err != nil {
panic(bug("unable to decode string with shape %v: %v", shape, err))
}
slice = reflect.ValueOf(strs)
typ = slice.Type()
} else {
typ = typeForDataType(dt)
l := n * int(typ.Size())
typ = reflect.SliceOf(typ)
slice = reflect.MakeSlice(typ, n, n)
baseBytes := *(*[]byte)(unsafe.Pointer(&sliceHeader{
Data: unsafe.Pointer(slice.Pointer()),
Len: l,
Cap: l,
}))
copy(baseBytes, raw)
}
// Now we have the data in place in the base slice we can add the
// dimensions. We want to walk backwards through the shape. If the shape is
// length 1 or 0 then we're already done.
if len(shape) == 0 {
return slice.Index(0)
}
if len(shape) == 1 {
return slice
}
// We have a special case if the tensor has no data. Our backing slice is
// empty, but we still want to create slices following the shape. In this
// case only the final part of the shape will be 0 and we want to recalculate
// n at this point ignoring that 0.
// For example if our shape is 3 * 2 * 0 then n will be zero, but we still
// want 6 zero length slices to group as follows.
// {{} {}} {{} {}} {{} {}}
if n == 0 {
n = int(numElements(shape[:len(shape)-1]))
}
for i := len(shape) - 2; i >= 0; i-- {
underlyingSize := typ.Elem().Size()
typ = reflect.SliceOf(typ)
subsliceLen := int(shape[i+1])
if subsliceLen != 0 {
n = n / subsliceLen
}
// Just using reflection it is difficult to avoid unnecessary
// allocations while setting up the sub-slices as the Slice function on
// a slice Value allocates. So we end up doing pointer arithmetic!
// Pointer() on a slice gives us access to the data backing the slice.
// We insert slice headers directly into this data.
data := unsafe.Pointer(slice.Pointer())
nextSlice := reflect.MakeSlice(typ, n, n)
for j := 0; j < n; j++ {
// This is equivalent to nSlice[j] = slice[j*subsliceLen: (j+1)*subsliceLen]
setSliceInSlice(nextSlice, j, sliceHeader{
Data: unsafe.Pointer(uintptr(data) + (uintptr(j*subsliceLen) * underlyingSize)),
Len: subsliceLen,
Cap: subsliceLen,
})
}
slice = nextSlice
}
return slice
}
// setSliceInSlice sets slice[index] = content.
func setSliceInSlice(slice reflect.Value, index int, content sliceHeader) {
const sliceSize = unsafe.Sizeof(sliceHeader{})
// We must cast slice.Pointer to uninptr & back again to avoid GC issues.
// See https://github.com/google/go-cmp/issues/167#issuecomment-546093202
*(*sliceHeader)(unsafe.Pointer(uintptr(unsafe.Pointer(slice.Pointer())) + (uintptr(index) * sliceSize))) = content
}
// decodeOneDimString decodes a string tensor into a one-dimensional []string.
func decodeOneDimString(raw []byte, nStrings int) ([]string, error) {
strs := make([]string, nStrings)
tstrs := (*(*[]C.TF_TString)(unsafe.Pointer(&raw)))[:nStrings]
for i, tstr := range tstrs {
dst := C.TF_TString_GetDataPointer(&tstr)
dstLen := C.TF_TString_GetSize(&tstr)
strs[i] = C.GoStringN(dst, C.int(dstLen))
}
return strs, nil
}
// WriteContentsTo writes the serialized contents of t to w.
//
// Returns the number of bytes written. See ReadTensor for
// reconstructing a Tensor from the serialized form.
//
// WARNING: WriteContentsTo is not comprehensive and will fail
// if t.DataType() is non-numeric (e.g., String). See
// https://github.com/tensorflow/tensorflow/issues/6003.
func (t *Tensor) WriteContentsTo(w io.Writer) (int64, error) {
if err := isTensorSerializable(t.DataType()); err != nil {
return 0, err
}
return io.Copy(w, bytes.NewReader(tensorData(t.c)))
}
func tensorData(c *C.TF_Tensor) []byte {
// See: https://github.com/golang/go/wiki/cgo#turning-c-arrays-into-go-slices
cbytes := C.TF_TensorData(c)
if cbytes == nil {
return nil
}
length := int(C.TF_TensorByteSize(c))
var slice []byte
if unsafe.Sizeof(unsafe.Pointer(nil)) == 8 {
slice = (*[1<<50 - 1]byte)(unsafe.Pointer(cbytes))[:length:length]
} else {
slice = (*[1 << 30]byte)(unsafe.Pointer(cbytes))[:length:length]
}
return slice
}
var types = []struct {
typ reflect.Type
dataType C.TF_DataType
}{
{reflect.TypeOf(float32(0)), C.TF_FLOAT},
{reflect.TypeOf(float64(0)), C.TF_DOUBLE},
{reflect.TypeOf(int32(0)), C.TF_INT32},
{reflect.TypeOf(uint32(0)), C.TF_UINT32},
{reflect.TypeOf(uint8(0)), C.TF_UINT8},
{reflect.TypeOf(int16(0)), C.TF_INT16},
{reflect.TypeOf(int8(0)), C.TF_INT8},
{reflect.TypeOf(""), C.TF_STRING},
{reflect.TypeOf(complex(float32(0), float32(0))), C.TF_COMPLEX64},
{reflect.TypeOf(int64(0)), C.TF_INT64},
{reflect.TypeOf(uint64(0)), C.TF_UINT64},
{reflect.TypeOf(false), C.TF_BOOL},
{reflect.TypeOf(uint16(0)), C.TF_UINT16},
{reflect.TypeOf(complex(float64(0), float64(0))), C.TF_COMPLEX128},
// TODO(apassos): support DT_RESOURCE representation in go.
// TODO(keveman): support DT_VARIANT representation in go.
}
// shapeAndDataTypeOf returns the data type and shape of the Tensor
// corresponding to a Go type.
func shapeAndDataTypeOf(val reflect.Value) (shape []int64, dt DataType, err error) {
typ := val.Type()
for typ.Kind() == reflect.Array || typ.Kind() == reflect.Slice {
shape = append(shape, int64(val.Len()))
// If slice elements are slices, verify that all of them have the same size.
// Go's type system makes that guarantee for arrays.
if val.Len() > 0 {
if val.Type().Elem().Kind() == reflect.Slice {
expected := val.Index(0).Len()
for i := 1; i < val.Len(); i++ {
if val.Index(i).Len() != expected {
return shape, dt, fmt.Errorf("mismatched slice lengths: %d and %d", val.Index(i).Len(), expected)
}
}
}
val = val.Index(0)
}
typ = typ.Elem()
}
for _, t := range types {
if typ.Kind() == t.typ.Kind() {
return shape, DataType(t.dataType), nil
}
}
return shape, dt, fmt.Errorf("unsupported type %v", typ)
}
func typeForDataType(dt DataType) reflect.Type {
for _, t := range types {
if dt == DataType(t.dataType) {
return t.typ
}
}
panic(bug("DataType %v is not supported (see https://www.tensorflow.org/code/tensorflow/core/framework/types.proto)", dt))
}
// TypeOf converts from a DataType and Shape to the equivalent Go type.
func TypeOf(dt DataType, shape []int64) reflect.Type {
ret := typeForDataType(dt)
for range shape {
ret = reflect.SliceOf(ret)
}
return ret
}
func numElements(shape []int64) int64 {
n := int64(1)
for _, d := range shape {
n *= d
}
return n
}
// sizeVarUint determines how many bytes it would take to encode the int v as
// an unsigned varint
func sizeVarUint(v uint64) int {
if v < 0x80 {
return 1
}
bits := bits.Len64(v)
return (bits + 6) / 7
}
// encodeTensorWithSlices writes v to the specified buffer using the format specified in
// c_api.h. Use stringEncoder for String tensors.
func encodeTensorWithSlices(w *bytes.Buffer, v reflect.Value, shape []int64) (int, error) {
// If current dimension is a slice, verify that it has the expected size
// Go's type system makes that guarantee for arrays.
if v.Kind() == reflect.Slice {
expected := int(shape[0])
if v.Len() != expected {
return 0, fmt.Errorf("mismatched slice lengths: %d and %d", v.Len(), expected)
}
} else if v.Kind() == reflect.String {
s := v.Interface().(string)
var tstr C.TF_TString
C.toNewTString(s, &tstr)
ptr := unsafe.Pointer(&tstr)
return copyPtr(w, ptr, C.sizeof_TF_TString)
} else if v.Kind() != reflect.Array {
return 0, fmt.Errorf("unsupported type %v", v.Type())
}
// Once we have just a single dimension we can just copy the data
if len(shape) == 1 && v.Len() > 0 && v.Index(0).Kind() != reflect.String {
elt := v.Index(0)
if !elt.CanAddr() {
panic("cannot take address")
}
ptr := unsafe.Pointer(elt.Addr().Pointer())
return copyPtr(w, ptr, v.Len()*int(elt.Type().Size()))
}
n := 0
subShape := shape[1:]
for i := 0; i < v.Len(); i++ {
j, err := encodeTensorWithSlices(w, v.Index(i), subShape)
if err != nil {
return n + j, err
}
n += j
}
return n, nil
}
// It isn't safe to use reflect.SliceHeader as it uses a uintptr for Data and
// this is not inspected by the garbage collector
type sliceHeader struct {
Data unsafe.Pointer
Len int
Cap int
}
// copyPtr copies the backing data for a slice or array directly into w. Note
// we don't need to worry about byte ordering because we want the natural byte
// order for the machine we're running on.
func copyPtr(w *bytes.Buffer, ptr unsafe.Pointer, l int) (int, error) {
// Convert our slice header into a []byte so we can call w.Write
b := *(*[]byte)(unsafe.Pointer(&sliceHeader{
Data: ptr,
Len: l,
Cap: l,
}))
return w.Write(b)
}
func bug(format string, args ...any) error {
return fmt.Errorf("BUG: Please report at https://github.com/tensorflow/tensorflow/issues with the note: Go TensorFlow %v: %v", Version(), fmt.Sprintf(format, args...))
}
func isTensorSerializable(dataType DataType) error {
// For numeric types, the serialized Tensor matches the in-memory
// representation. See the implementation of Tensor::AsProtoContent in
// https://www.tensorflow.org/code/tensorflow/core/framework/tensor.cc
//
// The more appropriate way to be in sync with Tensor::AsProtoContent
// would be to have the TensorFlow C library export functions for
// serialization and deserialization of Tensors. Till then capitalize
// on knowledge of the implementation for numeric types.
switch dataType {
case Float, Double, Int32, Uint8, Int16, Int8, Complex, Int64, Bool, Quint8, Qint32, Bfloat16, Qint16, Quint16, Uint16, Complex128, Half, Float8e5m2, Float8e4m3fn, Float8e4m3fnuz, Float8e4m3b11fnuz, Float8e5m2fnuz, Float4e2m1fn, Int4, Uint4, Int2, Uint2:
return nil
default:
return fmt.Errorf("serialization of tensors with the DataType %d is not yet supported, see https://github.com/tensorflow/tensorflow/issues/6003", dataType)
}
}
+162
View File
@@ -0,0 +1,162 @@
/*
Copyright 2018 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
// #include <stdlib.h>
// #include "tensorflow/c/c_api.h"
// #include "tensorflow/c/eager/c_api.h"
import "C"
import (
"runtime"
"unsafe"
)
// TensorHandle is a handle to a tensor on a device.
//
// A Tensor referenced by a TensorHandle may be on any device, whereas a Tensor
// always resides in the host CPU's memory.
//
// A Tensor referenced by a TensorHandle may not have been computed yet. For
// example, a TensorHandle might reference the output of an operation that has
// not finished executing. Because of this, various methods, such as Shape() may
// block until the tensor has been instantiated.
//
// This allows multiple operations to be performed on tensors on a device
// (e.g. a GPU) without sending these values back to the host CPU in between
// every operation.
type TensorHandle struct {
c *C.TFE_TensorHandle
}
// NewTensorHandle creates a new tensor handle from a tensor.
func NewTensorHandle(t *Tensor) (*TensorHandle, error) {
status := newStatus()
cHandle := C.TFE_NewTensorHandle(t.c, status.c)
if err := status.Err(); err != nil {
return nil, err
}
th := &TensorHandle{c: cHandle}
runtime.SetFinalizer(th, (*TensorHandle).finalizer)
return th, nil
}
func (th *TensorHandle) finalizer() {
C.TFE_DeleteTensorHandle(th.c)
}
// newTensorHandleFromC takes ownership of c and returns the owning TensorHandle.
func newTensorHandleFromC(c *C.TFE_TensorHandle) *TensorHandle {
th := &TensorHandle{c: c}
runtime.SetFinalizer(th, (*TensorHandle).finalizer)
return th
}
// DataType returns the TensorHandle's datatype.
func (th *TensorHandle) DataType() DataType {
return DataType(C.TFE_TensorHandleDataType(th.c))
}
// Shape returns the shape of the Tensor referenced by th.
func (th *TensorHandle) Shape() ([]int64, error) {
n, err := th.numDims()
if err != nil {
return nil, err
}
r := make([]int64, n)
for i := 0; i < n; i++ {
if r[i], err = th.dim(i); err != nil {
return nil, err
}
}
return r, nil
}
// numDims returns the number of dimensions of the TensorHandle. It blocks
// until the operation that produces the handle has completed.
func (th *TensorHandle) numDims() (int, error) {
status := newStatus()
n := int(C.TFE_TensorHandleNumDims(th.c, status.c))
return n, status.Err()
}
// dim returns the size of the index'th dimension of the TensorHandle. It
// blocks until the operation that produces the handle has completed.
func (th *TensorHandle) dim(index int) (int64, error) {
status := newStatus()
n := int64(C.TFE_TensorHandleDim(th.c, C.int(index), status.c))
if err := status.Err(); err != nil {
return 0, err
}
return n, nil
}
// DeviceName returns the name of the device of the operation that produced the
// TensorHandle. If the handle was produced by a copy, it returns the
// destination device of the copy. Note that returned device name is not always
// the device holding the tensor handle's memory. If you want the latter, use
// BackingDeviceName. This function will block till the operation that produces
// th has completed.
func (th *TensorHandle) DeviceName() (string, error) {
status := newStatus()
name := C.TFE_TensorHandleDeviceName(th.c, status.c)
if err := status.Err(); err != nil {
return "", err
}
return C.GoString(name), nil
}
// BackingDeviceName returns the name of the device in whose memory tensor
// handle th resides. This function will block till the operation that produces
// th has completed.
func (th *TensorHandle) BackingDeviceName() (string, error) {
status := newStatus()
name := C.TFE_TensorHandleBackingDeviceName(th.c, status.c)
if err := status.Err(); err != nil {
return "", err
}
return C.GoString(name), nil
}
// ToTensor returns the Tensor referenced by th. It may block if this tensor is
// not yet computed.
func (th *TensorHandle) ToTensor() (*Tensor, error) {
status := newStatus()
cTensor := C.TFE_TensorHandleResolve(th.c, status.c)
if err := status.Err(); err != nil {
return nil, err
}
return newTensorFromC(cTensor), nil
}
// CopyToDevice creates a new TensorHandle with the same contents as this
// TensorHandle but placed in the memory of the device 'deviceName'. If source
// and destination are the same device, then this creates a new handle that
// shares the underlying buffer. Otherwise, it currently requires at least one
// of the source or destination devices to be CPU (i.e., for the source or
// destination tensor to be placed in host memory).
func (th *TensorHandle) CopyToDevice(c *Context, deviceName string) (*TensorHandle, error) {
status := newStatus()
n := C.CString(deviceName)
newTh := C.TFE_TensorHandleCopyToDevice(th.c, c.c, n, status.c)
C.free(unsafe.Pointer(n))
if err := status.Err(); err != nil {
return nil, err
}
return newTensorHandleFromC(newTh), nil
}
+134
View File
@@ -0,0 +1,134 @@
/*
Copyright 2018 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
import (
"reflect"
"strings"
"testing"
)
func TestNewTensorHandle(t *testing.T) {
vals := [][]float32{{1.0, 2.0}, {3.0, 4.0}}
tensor, err := NewTensor(vals)
if err != nil {
t.Fatal(err)
}
th, err := NewTensorHandle(tensor)
if err != nil {
t.Fatal(err)
}
if th == nil {
t.Errorf("expected non-nil tensor handle; got: %v", th)
}
}
func TestTensorHandleDataType(t *testing.T) {
vals := [][]float32{{1.0, 2.0}, {3.0, 4.0}}
tensor, err := NewTensor(vals)
if err != nil {
t.Fatal(err)
}
th, err := NewTensorHandle(tensor)
if err != nil {
t.Fatal(err)
}
if got, want := th.DataType(), Float; got != want {
t.Errorf("Got %v, want %v", got, want)
}
}
func TestTensorHandleShape(t *testing.T) {
vals := [][]float32{{1.0, 2.0, 3.0}, {4.0, 5.0, 6.0}}
tensor, err := NewTensor(vals)
if err != nil {
t.Fatal(err)
}
th, err := NewTensorHandle(tensor)
if err != nil {
t.Fatal(err)
}
got, err := th.Shape()
if err != nil {
t.Fatal(err)
}
if want := []int64{2, 3}; !reflect.DeepEqual(got, want) {
t.Errorf("Got %#v, want %#v", got, want)
}
}
func TestTensorHandleDeviceName(t *testing.T) {
vals := [][]float32{{1.0, 2.0}, {3.0, 4.0}}
tensor, err := NewTensor(vals)
if err != nil {
t.Fatal(err)
}
th, err := NewTensorHandle(tensor)
if err != nil {
t.Fatal(err)
}
d, err := th.DeviceName()
if err != nil {
t.Fatal(err)
}
if !strings.Contains(d, "CPU") {
t.Errorf("DeviceName() did not return a CPU device; got: %s", d)
}
}
func TestTensorHandleBackingDeviceName(t *testing.T) {
vals := [][]float32{{1.0, 2.0}, {3.0, 4.0}}
tensor, err := NewTensor(vals)
if err != nil {
t.Fatal(err)
}
th, err := NewTensorHandle(tensor)
if err != nil {
t.Fatal(err)
}
d, err := th.BackingDeviceName()
if err != nil {
t.Fatal(err)
}
if !strings.Contains(d, "CPU") {
t.Errorf("BackingDeviceName() did not return a CPU device; got: %s", d)
}
}
func TestTensorHandleToTensor(t *testing.T) {
initialVals := [][]float32{{1.0, 2.0}, {3.0, 4.0}}
initialTensor, err := NewTensor(initialVals)
if err != nil {
t.Fatal(err)
}
th, err := NewTensorHandle(initialTensor)
if err != nil {
t.Fatal(err)
}
tensor, err := th.ToTensor()
if err != nil {
t.Fatal(err)
}
if v := tensor.Value().([][]float32); !reflect.DeepEqual(v, initialVals) {
t.Errorf("Got %#v, want %#v", v, initialVals)
}
}
+417
View File
@@ -0,0 +1,417 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
import (
"bytes"
"fmt"
"io"
"reflect"
"runtime"
"testing"
)
func TestNewTensor(t *testing.T) {
var tests = []struct {
shape []int64
value interface{}
}{
{nil, bool(true)},
{nil, int8(5)},
{nil, int16(5)},
{nil, int32(5)},
{nil, int64(5)},
{nil, uint8(5)},
{nil, uint16(5)},
{nil, uint32(5)},
{nil, uint64(5)},
{nil, float32(5)},
{nil, float64(5)},
{nil, complex(float32(5), float32(6))},
{nil, complex(float64(5), float64(6))},
{nil, "a string"},
{[]int64{1}, []uint32{1}},
{[]int64{1}, []uint64{1}},
{[]int64{2}, []bool{true, false}},
{[]int64{1}, []float64{1}},
{[]int64{1}, [1]float64{1}},
{[]int64{1, 1}, [1][1]float64{{1}}},
{[]int64{1, 1, 1}, [1][1][]float64{{{1}}}},
{[]int64{1, 1, 2}, [1][][2]float64{{{1, 2}}}},
{[]int64{1, 1, 1, 1}, [1][][1][]float64{{{{1}}}}},
{[]int64{2}, []string{"string", "slice"}},
{[]int64{2}, [2]string{"string", "array"}},
{[]int64{3, 2}, [][]float64{{1, 2}, {3, 4}, {5, 6}}},
{[]int64{2, 3}, [2][3]float64{{1, 2, 3}, {3, 4, 6}}},
{[]int64{4, 3, 2}, [][][]float64{
{{1, 2}, {3, 4}, {5, 6}},
{{7, 8}, {9, 10}, {11, 12}},
{{0, -1}, {-2, -3}, {-4, -5}},
{{-6, -7}, {-8, -9}, {-10, -11}},
}},
{[]int64{2, 0}, [][]int64{{}, {}}},
{[]int64{2, 2}, [][]string{{"row0col0", "row0,col1"}, {"row1col0", "row1,col1"}}},
{[]int64{2, 3}, [2][3]string{
{"row0col0", "row0,col1", "row0,col2"},
{"row1col0", "row1,col1", "row1,col2"},
}},
}
var errorTests = []interface{}{
struct{ a int }{5},
new(int32),
new([]int32),
// native ints not supported
int(5),
[]int{5},
}
for _, test := range tests {
tensor, err := NewTensor(test.value)
if err != nil {
t.Errorf("NewTensor(%v): %v", test.value, err)
continue
}
if !reflect.DeepEqual(test.shape, tensor.Shape()) {
t.Errorf("Tensor.Shape(): got %v, want %v", tensor.Shape(), test.shape)
}
// Test that encode and decode gives the same value. We skip arrays because
// they're returned as slices.
if reflect.TypeOf(test.value).Kind() != reflect.Array {
if !reflect.DeepEqual(test.value, tensor.Value()) {
t.Errorf("encode/decode: got %v, want %v", tensor.Value(), test.value)
}
}
}
for _, test := range errorTests {
tensor, err := NewTensor(test)
if err == nil {
t.Errorf("NewTensor(%v): %v", test, err)
}
if tensor != nil {
t.Errorf("NewTensor(%v) = %v, want nil", test, tensor)
}
}
}
func TestNewTensorValidateDimensions(t *testing.T) {
var errorTests = []interface{}{
// Mismatched dimensions
[][]float32{{1, 2, 3}, {4}},
// Mismatched dimensions. Should return "mismatched slice lengths" error instead of "BUG"
[][][]float32{{{1, 2}, {3, 4}}, {{1}, {3}}},
// Mismatched dimensions. Should return error instead of valid tensor
[][][]float32{{{1, 2}, {3, 4}}, {{1}, {3}}, {{1, 2, 3}, {2, 3, 4}}},
// Mismatched dimensions for strings
[][]string{{"abc"}, {"abcd", "abcd"}},
}
// Test that an error is returned in response to mismatched dimensions
// and that no tensor is returned. Dimensions should be checked and a
// mismatch caught in NewTensor prior to actually allocating a new
// tensor in cgo. Given how string tensors are encoded and how tensors
// are freed, a mismatch caught partway through encoding a string
// tensor may result in a segfault, once the finalizer is called. A
// single run of this test is not reliable at producing a segfault,
// hence iteration. See github.com/tensorflow/tensorflow/pull/52257
// for some detail on the issue.
for i := 0; i < 1e5; i++ {
for _, test := range errorTests {
tensor, err := NewTensor(test)
if err == nil {
t.Errorf("NewTensor(%v): %v", test, err)
}
if tensor != nil {
t.Errorf("NewTensor(%v) = %v, want nil", test, tensor)
}
}
}
// Execute any finalizers (blocking).
runtime.GC()
}
func TestTensorSerialization(t *testing.T) {
var tests = []interface{}{
bool(true),
int8(5),
int16(5),
int32(5),
int64(5),
uint8(5),
uint16(5),
float32(5),
float64(5),
complex(float32(5), float32(6)),
complex(float64(5), float64(6)),
[]float64{1},
[][]float32{{1, 2}, {3, 4}, {5, 6}},
[][][]int8{
{{1, 2}, {3, 4}, {5, 6}},
{{7, 8}, {9, 10}, {11, 12}},
{{0, -1}, {-2, -3}, {-4, -5}},
{{-6, -7}, {-8, -9}, {-10, -11}},
},
[]bool{true, false, true},
}
for _, v := range tests {
t1, err := NewTensor(v)
if err != nil {
t.Errorf("(%v): %v", v, err)
continue
}
buf := new(bytes.Buffer)
n, err := t1.WriteContentsTo(buf)
if err != nil {
t.Errorf("(%v): %v", v, err)
continue
}
if n != int64(buf.Len()) {
t.Errorf("(%v): WriteContentsTo said it wrote %v bytes, but wrote %v", v, n, buf.Len())
}
t2, err := ReadTensor(t1.DataType(), t1.Shape(), buf)
if err != nil {
t.Errorf("(%v): %v", v, err)
continue
}
if buf.Len() != 0 {
t.Errorf("(%v): %v bytes written by WriteContentsTo not read by ReadTensor", v, buf.Len())
}
if got, want := t2.DataType(), t1.DataType(); got != want {
t.Errorf("(%v): Got %v, want %v", v, got, want)
}
if got, want := t2.Shape(), t1.Shape(); !reflect.DeepEqual(got, want) {
t.Errorf("(%v): Got %v, want %v", v, got, want)
}
if got, want := t2.Value(), v; !reflect.DeepEqual(got, want) {
t.Errorf("(%v): Got %v, want %v", v, got, want)
}
}
}
func TestReadTensorDoesNotReadBeyondContent(t *testing.T) {
t1, _ := NewTensor(int8(7))
t2, _ := NewTensor(float32(2.718))
buf := new(bytes.Buffer)
if _, err := t1.WriteContentsTo(buf); err != nil {
t.Fatal(err)
}
if _, err := t2.WriteContentsTo(buf); err != nil {
t.Fatal(err)
}
t3, err := ReadTensor(t1.DataType(), t1.Shape(), buf)
if err != nil {
t.Fatal(err)
}
t4, err := ReadTensor(t2.DataType(), t2.Shape(), buf)
if err != nil {
t.Fatal(err)
}
if v, ok := t3.Value().(int8); !ok || v != 7 {
t.Errorf("Got (%v (%T), %v), want (7 (int8), true)", v, v, ok)
}
if v, ok := t4.Value().(float32); !ok || v != 2.718 {
t.Errorf("Got (%v (%T), %v), want (2.718 (float32), true)", v, v, ok)
}
}
func TestTensorSerializationErrors(t *testing.T) {
// String tensors cannot be serialized
t1, err := NewTensor("abcd")
if err != nil {
t.Fatal(err)
}
buf := new(bytes.Buffer)
if n, err := t1.WriteContentsTo(buf); n != 0 || err == nil || buf.Len() != 0 {
t.Errorf("Got (%v, %v, %v) want (0, <non-nil>, 0)", n, err, buf.Len())
}
// Should fail to read a truncated value.
if t1, err = NewTensor(int8(8)); err != nil {
t.Fatal(err)
}
n, err := t1.WriteContentsTo(buf)
if err != nil {
t.Fatal(err)
}
r := bytes.NewReader(buf.Bytes()[:n-1])
if _, err = ReadTensor(t1.DataType(), t1.Shape(), r); err == nil {
t.Error("ReadTensor should have failed if the tensor content was truncated")
}
}
func TestReadTensorReadAll(t *testing.T) {
// Get the bytes of a tensor.
a := []float32{1.1, 1.2, 1.3}
ats, err := NewTensor(a)
if err != nil {
t.Fatal(err)
}
abuf := new(bytes.Buffer)
if _, err := ats.WriteContentsTo(abuf); err != nil {
t.Fatal(err)
}
// Get the bytes of another tensor.
b := []float32{1.1, 1.2, 1.3}
bts, err := NewTensor(b)
if err != nil {
t.Fatal(err)
}
bbuf := new(bytes.Buffer)
if _, err := bts.WriteContentsTo(bbuf); err != nil {
t.Fatal(err)
}
// Check that ReadTensor reads all bytes of both tensors, when the situation
// requires one than reads.
abbuf := io.MultiReader(abuf, bbuf)
abts, err := ReadTensor(Float, []int64{2, 3}, abbuf)
if err != nil {
t.Fatal(err)
}
abtsf32 := abts.Value().([][]float32)
expected := [][]float32{a, b}
if len(abtsf32) != 2 {
t.Fatalf("first dimension %d is not 2", len(abtsf32))
}
for i := 0; i < 2; i++ {
if len(abtsf32[i]) != 3 {
t.Fatalf("second dimension %d is not 3", len(abtsf32[i]))
}
for j := 0; j < 3; j++ {
if abtsf32[i][j] != expected[i][j] {
t.Errorf("value at %d %d not equal %f %f", i, j, abtsf32[i][j], expected[i][j])
}
}
}
}
func TestReadTensorNegativeDimention(t *testing.T) {
buf := new(bytes.Buffer)
_, err := ReadTensor(Int32, []int64{-1, 1}, buf)
if err == nil {
t.Fatal("ReadTensor should failed if shape contains negative dimention")
}
}
func benchmarkNewTensor(b *testing.B, v interface{}) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
if t, err := NewTensor(v); err != nil || t == nil {
b.Fatalf("(%v, %v)", t, err)
}
}
}
func benchmarkValueTensor(b *testing.B, v interface{}) {
t, err := NewTensor(v)
if err != nil {
b.Fatalf("(%v, %v)", t, err)
}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = t.Value()
}
}
func BenchmarkTensor(b *testing.B) {
// Some sample sizes from the Inception image labeling model.
// Where input tensors correspond to a 224x224 RGB image
// flattened into a vector.
var vector [224 * 224 * 3]int32
var arrays [100][100][100]int32
l3 := make([][][]float32, 100)
l2 := make([][]float32, 100*100)
l1 := make([]float32, 100*100*100)
for i := range l2 {
l2[i] = l1[i*100 : (i+1)*100]
}
for i := range l3 {
l3[i] = l2[i*100 : (i+1)*100]
}
s1 := make([]string, 100*100*100)
s2 := make([][]string, 100*100)
s3 := make([][][]string, 100)
for i := range s1 {
s1[i] = "cheesit"
}
for i := range s2 {
s2[i] = s1[i*100 : (i+1)*100]
}
for i := range s3 {
s3[i] = s2[i*100 : (i+1)*100]
}
tests := []interface{}{
vector,
arrays,
l1,
l2,
l3,
s1,
s2,
s3,
}
b.Run("New", func(b *testing.B) {
for _, test := range tests {
b.Run(fmt.Sprintf("%T", test), func(b *testing.B) { benchmarkNewTensor(b, test) })
}
})
b.Run("Value", func(b *testing.B) {
for _, test := range tests {
b.Run(fmt.Sprintf("%T", test), func(b *testing.B) { benchmarkValueTensor(b, test) })
}
})
}
func TestReshape(t *testing.T) {
tensor, err := NewTensor([]int64{1, 2})
if err != nil {
t.Fatalf("Unable to create new tensor: %v", err)
}
if got, want := len(tensor.Shape()), 1; got != want {
t.Fatalf("len(tensor.Shape()): got %d, want %d", got, want)
}
if got, want := tensor.Shape()[0], int64(2); got != want {
t.Errorf("tensor.Shape()[0]: got %d, want %d", got, want)
}
if err := tensor.Reshape([]int64{1, 2}); err != nil {
t.Fatalf("tensor.Reshape([1, 2]) failed: %v", err)
}
if got, want := len(tensor.Shape()), 2; got != want {
t.Fatalf("After reshape, len(tensor.Shape()): got %d, want %d", got, want)
}
if got, want := tensor.Shape()[0], int64(1); got != want {
t.Errorf("After reshape, tensor.Shape()[0]: got %d, want %d", got, want)
}
if got, want := tensor.Shape()[1], int64(2); got != want {
t.Errorf("After reshape, tensor.Shape()[1]: got %d, want %d", got, want)
}
}
+76
View File
@@ -0,0 +1,76 @@
#!/usr/bin/env bash
# Copyright 2017 The TensorFlow Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ==============================================================================
# TensorFlow uses 'bazel' for builds and tests.
# The TensorFlow Go API aims to be usable with the 'go' tool
# (using 'go get' etc.) and thus without bazel.
#
# This script acts as a brige between bazel and go so that:
# bazel test :test
# succeeds iff
# go test github.com/tensorflow/tensorflow/tensorflow/go
# succeeds.
set -ex
# Find the 'go' tool
if [[ ! -x "go" && -z $(which go) ]]
then
if [[ -x "/usr/local/go/bin/go" ]]
then
export PATH="${PATH}:/usr/local/go/bin"
else
echo "Could not find the 'go' tool in PATH or /usr/local/go"
exit 1
fi
fi
# Setup a GOPATH that includes just the TensorFlow Go API.
export GOPATH="${TEST_TMPDIR}/go"
export GOCACHE="${TEST_TMPDIR}/cache"
mkdir -p "${GOPATH}/src/github.com/tensorflow"
ln -s -f "${PWD}" "${GOPATH}/src/github.com/tensorflow/tensorflow"
# Ensure that the TensorFlow C library is accessible to the
# linker at build and run time.
export LIBRARY_PATH="${PWD}/tensorflow"
OS=$(uname -s)
if [[ "${OS}" = "Linux" ]]
then
if [[ -z "${LD_LIBRARY_PATH}" ]]
then
export LD_LIBRARY_PATH="${PWD}/tensorflow"
else
export LD_LIBRARY_PATH="${PWD}/tensorflow:${LD_LIBRARY_PATH}"
fi
elif [[ "${OS}" = "Darwin" ]]
then
if [[ -z "${DYLD_LIBRARY_PATH}" ]]
then
export DYLD_LIBRARY_PATH="${PWD}/tensorflow"
else
export DYLD_LIBRARY_PATH="${PWD}/tensorflow:${DYLD_LIBRARY_PATH}"
fi
else
echo "Only support Linux/Darwin, System $OS is not supported"
exit 1
fi
# Document the Go version and run tests
echo "Go version: $(go version)"
go test \
github.com/tensorflow/tensorflow/tensorflow/go \
github.com/tensorflow/tensorflow/tensorflow/go/op
Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

@@ -0,0 +1 @@
asset-file-contents
@@ -0,0 +1 @@
# Empty file to be replaced in https://github.com/tensorflow/tensorflow/pull/50934
+1
View File
@@ -0,0 +1 @@
# Empty file to be replaced in https://github.com/tensorflow/tensorflow/pull/50934
+65
View File
@@ -0,0 +1,65 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
func Placeholder(g *Graph, name string, dt DataType) (Output, error) {
op, err := g.AddOperation(OpSpec{
Type: "Placeholder",
Name: name,
Attrs: map[string]interface{}{
"dtype": dt,
},
})
return op.Output(0), err
}
func Const(g *Graph, name string, value interface{}) (Output, error) {
t, ok := value.(*Tensor)
if !ok {
var err error
if t, err = NewTensor(value); err != nil {
return Output{}, err
}
}
op, err := g.AddOperation(OpSpec{
Type: "Const",
Name: name,
Attrs: map[string]interface{}{
"dtype": t.DataType(),
"value": t,
},
})
return op.Output(0), err
}
func Neg(g *Graph, name string, port Output) (Output, error) {
op, err := g.AddOperation(OpSpec{
Type: "Neg",
Name: name,
Input: []Input{port},
})
return op.Output(0), err
}
func Add(g *Graph, name string, x, y Output) (Output, error) {
op, err := g.AddOperation(OpSpec{
Type: "Add",
Name: name,
Input: []Input{x, y},
})
return op.Output(0), err
}
+25
View File
@@ -0,0 +1,25 @@
/*
Copyright 2016 The TensorFlow Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package tensorflow
// #include <string.h>
// #include "tensorflow/c/c_api.h"
import "C"
// Version returns a string describing the version of the underlying TensorFlow
// runtime.
func Version() string { return C.GoString(C.TF_Version()) }