{"action":"created","comment":{"url":"https://api.github.com/repos/tensorflow/tensorflow/pulls/comments/75203355","id":75203355,"diff_hunk":"@@ -0,0 +1,612 @@\n+/* Copyright 2016 The TensorFlow Authors. All Rights Reserved.\n+\n+Licensed under the Apache License, Version 2.0 (the \"License\");\n+you may not use this file except in compliance with the License.\n+You may obtain a copy of the License at\n+\n+    http://www.apache.org/licenses/LICENSE-2.0\n+\n+Unless required by applicable law or agreed to in writing, software\n+distributed under the License is distributed on an \"AS IS\" BASIS,\n+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n+See the License for the specific language governing permissions and\n+limitations under the License.\n+==============================================================================*/\n+\n+// This file contains a set of different implementations of the two-dimensional\n+// convolution operation. The standard TensorFlow Conv2d kernel uses EigenTensor\n+// to implement the computation, but here there are a variety of different ways\n+// of producing the same result. These methods are designed to be easier to\n+// understand and connect to other libraries, so that we can take advantage of\n+// platforms that have specialized implementations of GEMM for example.\n+//\n+// The basic interface is a Conv functor object that's templated by the types\n+// of the data it will be operating on, and is passed in the arguments needed to\n+// calculate the convolution. The simplest implementation of this functor is\n+// ReferenceConvFunctor, which is a readable but slow reference version.\n+//\n+// A faster version uses the approach of packing image patches into a matrix\n+// before calling a matrix multiply, the Im2ColConvFunctor. In turn, this can\n+// use a variety of different methods to calculate the matrix multiplication,\n+// or GEMM. The simplest but slowest is the ReferenceGemmFunctor, but the\n+// FastGemmFunctor will use whatever optimized libraries are available. By\n+// default it uses Eigen, but on Apple platforms it will take advantage of the\n+// system's Accelerate BLAS library to get better performance than the standard\n+// TensorFlow convolution kernel.\n+//\n+// The version actually used is defined at the bottom of this file using the\n+// REGISTER_KERNEL_BUILDER() macro. To try out different implementations (for\n+// example to switch to a reference one for easier debugging) you can swap out\n+// the default functors in that call.\n+//\n+// The registration itself is guarded with the USE_GEMM_FOR_CONV macro. The iOS\n+// makefile build defines this, but if you want to enable this implementation\n+// and disable the standard EigenTensor one in other build setups, you'll need\n+// to define it there too.\n+\n+#include <string.h>\n+#include <map>\n+#include <vector>\n+#include \"tensorflow/core/framework/common_shape_fns.h\"\n+#include \"tensorflow/core/framework/numeric_op.h\"\n+#include \"tensorflow/core/framework/op_kernel.h\"\n+#include \"tensorflow/core/framework/register_types.h\"\n+#include \"tensorflow/core/framework/resource_mgr.h\"\n+#include \"tensorflow/core/framework/tensor.h\"\n+#include \"tensorflow/core/framework/tensor_shape.h\"\n+#include \"tensorflow/core/framework/tensor_slice.h\"\n+#include \"tensorflow/core/kernels/bounds_check.h\"\n+#include \"tensorflow/core/util/padding.h\"\n+#include \"tensorflow/core/util/tensor_format.h\"\n+\n+#if defined(__APPLE__)\n+#include <Accelerate/Accelerate.h>\n+#define USE_ACCELERATE_GEMM\n+#endif  // __APPLE__\n+\n+namespace tensorflow {\n+\n+namespace {\n+// This function implements the convolution operation in as simple a form as\n+// possible. It won't give great performance, but it is very useful for\n+// stepping through and instrumenting for debugging, creating minimal benchmarks\n+// to prototype with, and sharing with teams that want to run this outside of\n+// our environment.\n+// With that in mind, I've avoided using anything except pretty standard C++\n+// types. This is especially noticeable in the data access through raw array\n+// indexing. It's deliberate in this case though, since it makes the underlying\n+// memory order very explicit, which is important for both inspecting memory\n+// contents during debugging and for specifying what we expect to others.\n+// The memory layout of the data is, from biggest stride to smallest:\n+// input_data = [input_batches, input_height, input_width, input_depth]\n+// filter_data = [filter_height, filter_width, input_depth, filter_count]\n+// output_data = [input_batches, output_height, output_width, filter_count]\n+template <class T1, class T2, class T3>\n+class ReferenceConvFunctor {\n+ public:\n+  void operator()(OpKernelContext* context, const T1* input_data,\n+                  int input_batches, int input_height, int input_width,\n+                  int input_depth, const T2* filter_data, int filter_height,\n+                  int filter_width, int filter_count, int stride_rows,\n+                  int stride_cols, Padding padding, T3* output_data,\n+                  int output_height, int output_width) {\n+    // The two different padding modes we support can be a bit confusing. SAME\n+    // means we're trying to produce an output image that's the same size as the\n+    // input. It's complicated by stride, which shrinks the output image by a\n+    // a factor, but it means we end up sampling from outside the borders of the\n+    // input. These out-of-bounds values are read as zeroes. VALID means only\n+    // produce output values where the filters can read all their values from\n+    // within the input image. It effectively removes the margins of the output\n+    // image compared to the one produced by SAME. Stride complicates this\n+    // definition though, because it can result in the right and bottom filter\n+    // patches sampling from outside the borders if it's greater than 1.\n+    // Most of the logic for sorting this all out is done before this function,\n+    // when we calculate the output size, but the positioning of the origin of\n+    // the filters is different between the two modes, since SAME positions the\n+    // first filter off the edge of the input.\n+    int filter_left_offset;\n+    int filter_top_offset;\n+    if (padding == VALID) {\n+      filter_left_offset =\n+          ((output_width - 1) * stride_cols + filter_width - input_width + 1) /\n+          2;\n+      filter_top_offset = ((output_height - 1) * stride_rows + filter_height -\n+                           input_height + 1) /\n+                          2;\n+    } else {\n+      filter_left_offset =\n+          ((output_width - 1) * stride_cols + filter_width - input_width) / 2;\n+      filter_top_offset =\n+          ((output_height - 1) * stride_rows + filter_height - input_height) /\n+          2;\n+    }\n+\n+    // If we've got multiple images in our input, work through each of them.\n+    for (int batch = 0; batch < input_batches; ++batch) {\n+      // Walk through all the output image values, sliding the filter to\n+      // different\n+      // positions in the input.\n+      for (int out_y = 0; out_y < output_height; ++out_y) {\n+        for (int out_x = 0; out_x < output_width; ++out_x) {\n+          // Each filter kernel produces one output channel.\n+          for (int out_channel = 0; out_channel < filter_count; ++out_channel) {\n+            // We're going to calculate a single output value, which means we\n+            // need to multiply a three dimensional kernel of weights against\n+            // the current location within the input image.\n+            /*\n+             *-------------------------------...\n+             |\\ ^\n+             | \\in_depth\n+             |  \\ v\n+             |   *-------------------------------...\n+             |   |            ^\n+             |   |       in_y_origin\n+             |   |            v   \\\n+             |   |<in_x_origin>*---*^\n+             |   |            \\|   |filter_height\n+             .   |             *---*v\n+             .   |             <--->\n+             .         filter_width\n+             .\n+            */\n+            const int in_x_origin = (out_x * stride_cols) - filter_left_offset;\n+            const int in_y_origin = (out_y * stride_rows) - filter_top_offset;\n+            T3 total(0);\n+            for (int filter_y = 0; filter_y < filter_height; ++filter_y) {\n+              for (int filter_x = 0; filter_x < filter_width; ++filter_x) {\n+                for (int in_channel = 0; in_channel < input_depth;\n+                     ++in_channel) {\n+                  const int in_x = in_x_origin + filter_x;\n+                  const int in_y = in_y_origin + filter_y;\n+                  T1 input_value;\n+                  // If the location is outside the bounds of the input image,\n+                  // use zero as a default value.\n+                  if ((in_x >= 0) && (in_x < input_width) && (in_y >= 0) &&\n+                      (in_y < input_height)) {\n+                    input_value =\n+                        input_data[(batch * input_height * input_width *\n+                                    input_depth) +\n+                                   (in_y * input_width * input_depth) +\n+                                   (in_x * input_depth) + in_channel];\n+                  } else {\n+                    input_value = T1(0);\n+                  }\n+                  const T2 filter_value =\n+                      filter_data[(filter_y * filter_width * input_depth *\n+                                   filter_count) +\n+                                  (filter_x * input_depth * filter_count) +\n+                                  (in_channel * filter_count) + out_channel];\n+                  total += (input_value * filter_value);\n+                }\n+              }\n+            }\n+            output_data[(batch * output_height * output_width * filter_count) +\n+                        (out_y * output_width * filter_count) +\n+                        (out_x * filter_count) + out_channel] = total;\n+          }\n+        }\n+      }\n+    }\n+  }\n+};\n+\n+// A readable but slow implementation of matrix multiplication, useful for\n+// debugging and understanding the algorithm. Use instead of FastGemmFunctor in\n+// the Im2ColConvFunctor template definition inside the op registration to\n+// enable. Assumes row-major ordering of the values in memory.\n+template <class T1, class T2, class T3>\n+class ReferenceGemmFunctor {\n+ public:\n+  void operator()(size_t m, size_t n, size_t k, const T1* a, size_t lda,\n+                  const T2* b, size_t ldb, T3* c, size_t ldc) {\n+    const size_t a_i_stride = lda;\n+    const size_t a_l_stride = 1;\n+    const size_t b_j_stride = 1;\n+    const size_t b_l_stride = ldb;\n+    const size_t c_i_stride = ldc;\n+    const size_t c_j_stride = 1;\n+    size_t i, j, l;\n+    for (j = 0; j < n; j++) {\n+      for (i = 0; i < m; i++) {\n+        T3 total(0);\n+        for (l = 0; l < k; l++) {\n+          const size_t a_index = ((i * a_i_stride) + (l * a_l_stride));\n+          const T1 a_value = a[a_index];\n+          const size_t b_index = ((j * b_j_stride) + (l * b_l_stride));\n+          const T2 b_value = b[b_index];\n+          total += (a_value * b_value);\n+        }\n+        const size_t c_index = ((i * c_i_stride) + (j * c_j_stride));\n+        c[c_index] = total;\n+      }\n+    }\n+  }\n+};\n+\n+// Uses the optimized Eigen library to implement the matrix multiplication\n+// required by the Im2ColConvFunctor class.\n+template <class T1, class T2, class T3>\n+class FastGemmFunctor {\n+ public:\n+  void operator()(size_t m, size_t n, size_t k, const T1* a, size_t lda,\n+                  const T2* b, size_t ldb, T3* c, size_t ldc) {\n+    Eigen::Map<const Eigen::Matrix<T1, Eigen::Dynamic, Eigen::Dynamic,\n+                                   Eigen::RowMajor>>\n+        a_matrix(a, m, k);\n+    Eigen::Map<const Eigen::Matrix<T2, Eigen::Dynamic, Eigen::Dynamic,\n+                                   Eigen::RowMajor>>\n+        b_matrix(b, k, n);\n+    Eigen::Map<\n+        Eigen::Matrix<T3, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>>\n+        c_matrix(c, m, n);\n+    c_matrix.noalias() = a_matrix * b_matrix;\n+  }\n+};\n+\n+// If we have Apple's Accelerate framework, use their implementation of GEMM to\n+// get a performance boost for float.\n+#if defined(USE_ACCELERATE_GEMM)\n+template <>\n+class FastGemmFunctor<float, float, float> {\n+ public:\n+  void operator()(size_t m, size_t n, size_t k, const float* a, size_t lda,\n+                  const float* b, size_t ldb, float* c, size_t ldc) {\n+    cblas_sgemm(CblasRowMajor, CblasNoTrans, CblasNoTrans, m, n, k, 1.0f, a,\n+                lda, b, ldb, 0.0f, c, ldc);\n+  }\n+};\n+#endif  // USE_ACCELERATE_GEMM\n+\n+// Used to keep track of persistent memory buffers used within the op.\n+template <class T, size_t size>\n+struct Im2ColBufferResource : public ResourceBase {\n+  mutex mu;\n+  T data[size];\n+  string DebugString() { return \"Im2ColBufferResource\"; }\n+};\n+\n+// Implements convolution as a two stage process, first packing the patches of\n+// the input image into columns (im2col) and then running GEMM to produce the\n+// final result.\n+template <class T1, class T2, class T3, class TGemmFunctor>\n+class Im2ColConvFunctor {\n+ public:\n+  void operator()(OpKernelContext* context, const T1* input_data,\n+                  int input_batches, int input_height, int input_width,\n+                  int input_depth, const T2* filter_data, int filter_height,\n+                  int filter_width, int filter_count, int stride_rows,\n+                  int stride_cols, Padding padding, T3* output_data,\n+                  int output_height, int output_width) {\n+    // These calculations define how the patches will be positioned within the\n+    // input image. The actual definitions are quite complex, and rely on the\n+    // previously-calculated output size.\n+    CHECK_GT(output_width, 0);\n+    CHECK_GT(output_height, 0);\n+    int filter_left_offset;\n+    int filter_top_offset;\n+    if (padding == VALID) {\n+      filter_left_offset =\n+          ((output_width - 1) * stride_cols + filter_width - input_width + 1) /\n+          2;\n+      filter_top_offset = ((output_height - 1) * stride_rows + filter_height -\n+                           input_height + 1) /\n+                          2;\n+    } else {\n+      filter_left_offset =\n+          ((output_width - 1) * stride_cols + filter_width - input_width) / 2;\n+      filter_top_offset =\n+          ((output_height - 1) * stride_rows + filter_height - input_height) /\n+          2;\n+    }\n+\n+    // The im2col buffer has # of patches rows, and # of filters cols.\n+    // It's laid out like this, in row major order in memory:\n+    //        < filter value count >\n+    //   ^   +---------------------+\n+    // patch |                     |\n+    // count |                     |\n+    //   v   +---------------------+\n+    // Each patch row contains a filter_width x filter_height patch of the\n+    // input, with the depth channel as the most contiguous in memory, followed\n+    // by the width, then the height. This is the standard memory order in the\n+    // image world if it helps to visualize it.\n+    const int filter_value_count = filter_width * filter_height * input_depth;\n+    const int patch_count = input_batches * output_width * output_height;\n+\n+    CHECK_GT(patch_count, 0);\n+    CHECK_GT(filter_count, 0);\n+    CHECK_GT(filter_value_count, 0);\n+\n+    // We don't want to allocate a buffer to hold all the patches if the size is\n+    // going to be extremely large, so break it into chunks if it's bigger than\n+    // a limit. Each chunk will be processed serially, so we can refill the\n+    // buffer for the next chunk and reuse it, keeping maximum memory size down.\n+    // In this case, we've picked 16 megabytes as a reasonable limit.\n+    const size_t max_chunk_size = (16 * 1024 * 1024);\n+    const size_t patches_per_chunk =\n+        max_chunk_size / (filter_value_count * sizeof(T1));\n+    const size_t im2col_size = patches_per_chunk * filter_value_count;\n+    // Because memory allocation is very expensive on mobile platforms, try to","path":"tensorflow/core/kernels/conv_ops_using_gemm.cc","position":329,"original_position":329,"commit_id":"1de99caa7c5967f768f5fb4cd0cab287ecccde41","original_commit_id":"1de99caa7c5967f768f5fb4cd0cab287ecccde41","user":{"login":"petewarden","id":161459,"avatar_url":"https://avatars.githubusercontent.com/u/161459?v=3","gravatar_id":"","url":"https://api.github.com/users/petewarden","html_url":"https://github.com/petewarden","followers_url":"https://api.github.com/users/petewarden/followers","following_url":"https://api.github.com/users/petewarden/following{/other_user}","gists_url":"https://api.github.com/users/petewarden/gists{/gist_id}","starred_url":"https://api.github.com/users/petewarden/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/petewarden/subscriptions","organizations_url":"https://api.github.com/users/petewarden/orgs","repos_url":"https://api.github.com/users/petewarden/repos","events_url":"https://api.github.com/users/petewarden/events{/privacy}","received_events_url":"https://api.github.com/users/petewarden/received_events","type":"User","site_admin":false},"body":"Yes, that's right, the goal is to allocate once per graph, so that overall memory usage is minimized.","created_at":"2016-08-17T20:44:28Z","updated_at":"2016-08-17T20:44:28Z","html_url":"https://github.com/tensorflow/tensorflow/pull/3778#discussion_r75203355","pull_request_url":"https://api.github.com/repos/tensorflow/tensorflow/pulls/3778","_links":{"self":{"href":"https://api.github.com/repos/tensorflow/tensorflow/pulls/comments/75203355"},"html":{"href":"https://github.com/tensorflow/tensorflow/pull/3778#discussion_r75203355"},"pull_request":{"href":"https://api.github.com/repos/tensorflow/tensorflow/pulls/3778"}}},"pull_request":{"url":"https://api.github.com/repos/tensorflow/tensorflow/pulls/3778","id":81174166,"html_url":"https://github.com/tensorflow/tensorflow/pull/3778","diff_url":"https://github.com/tensorflow/tensorflow/pull/3778.diff","patch_url":"https://github.com/tensorflow/tensorflow/pull/3778.patch","issue_url":"https://api.github.com/repos/tensorflow/tensorflow/issues/3778","number":3778,"state":"open","locked":false,"title":"Reduce memory usage and increase performance for convolution on iOS","user":{"login":"petewarden","id":161459,"avatar_url":"https://avatars.githubusercontent.com/u/161459?v=3","gravatar_id":"","url":"https://api.github.com/users/petewarden","html_url":"https://github.com/petewarden","followers_url":"https://api.github.com/users/petewarden/followers","following_url":"https://api.github.com/users/petewarden/following{/other_user}","gists_url":"https://api.github.com/users/petewarden/gists{/gist_id}","starred_url":"https://api.github.com/users/petewarden/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/petewarden/subscriptions","organizations_url":"https://api.github.com/users/petewarden/orgs","repos_url":"https://api.github.com/users/petewarden/repos","events_url":"https://api.github.com/users/petewarden/events{/privacy}","received_events_url":"https://api.github.com/users/petewarden/received_events","type":"User","site_admin":false},"body":"We've had lots of problems with large convolutions hitting memory limits on iOS. This new implementation of the operator breaks the work into chunks so we never use more than 16 MB, and uses Apple's Accelerate framework to optimize the matrix multiplication.\r\n\r\nTesting shows that it's between 5% to 10% faster than the existing implementation on various models, and keeps memory usage to a minimum.","created_at":"2016-08-12T19:54:32Z","updated_at":"2016-08-17T20:44:28Z","closed_at":null,"merged_at":null,"merge_commit_sha":"d560f50433d3e761649e5f43cbf4ef43446d124a","assignee":{"login":"cwhipkey","id":17578177,"avatar_url":"https://avatars.githubusercontent.com/u/17578177?v=3","gravatar_id":"","url":"https://api.github.com/users/cwhipkey","html_url":"https://github.com/cwhipkey","followers_url":"https://api.github.com/users/cwhipkey/followers","following_url":"https://api.github.com/users/cwhipkey/following{/other_user}","gists_url":"https://api.github.com/users/cwhipkey/gists{/gist_id}","starred_url":"https://api.github.com/users/cwhipkey/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cwhipkey/subscriptions","organizations_url":"https://api.github.com/users/cwhipkey/orgs","repos_url":"https://api.github.com/users/cwhipkey/repos","events_url":"https://api.github.com/users/cwhipkey/events{/privacy}","received_events_url":"https://api.github.com/users/cwhipkey/received_events","type":"User","site_admin":false},"assignees":[{"login":"cwhipkey","id":17578177,"avatar_url":"https://avatars.githubusercontent.com/u/17578177?v=3","gravatar_id":"","url":"https://api.github.com/users/cwhipkey","html_url":"https://github.com/cwhipkey","followers_url":"https://api.github.com/users/cwhipkey/followers","following_url":"https://api.github.com/users/cwhipkey/following{/other_user}","gists_url":"https://api.github.com/users/cwhipkey/gists{/gist_id}","starred_url":"https://api.github.com/users/cwhipkey/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/cwhipkey/subscriptions","organizations_url":"https://api.github.com/users/cwhipkey/orgs","repos_url":"https://api.github.com/users/cwhipkey/repos","events_url":"https://api.github.com/users/cwhipkey/events{/privacy}","received_events_url":"https://api.github.com/users/cwhipkey/received_events","type":"User","site_admin":false}],"milestone":null,"commits_url":"https://api.github.com/repos/tensorflow/tensorflow/pulls/3778/commits","review_comments_url":"https://api.github.com/repos/tensorflow/tensorflow/pulls/3778/comments","review_comment_url":"https://api.github.com/repos/tensorflow/tensorflow/pulls/comments{/number}","comments_url":"https://api.github.com/repos/tensorflow/tensorflow/issues/3778/comments","statuses_url":"https://api.github.com/repos/tensorflow/tensorflow/statuses/1de99caa7c5967f768f5fb4cd0cab287ecccde41","head":{"label":"petewarden4prs2:master","ref":"master","sha":"1de99caa7c5967f768f5fb4cd0cab287ecccde41","user":{"login":"petewarden4prs2","id":20894166,"avatar_url":"https://avatars.githubusercontent.com/u/20894166?v=3","gravatar_id":"","url":"https://api.github.com/users/petewarden4prs2","html_url":"https://github.com/petewarden4prs2","followers_url":"https://api.github.com/users/petewarden4prs2/followers","following_url":"https://api.github.com/users/petewarden4prs2/following{/other_user}","gists_url":"https://api.github.com/users/petewarden4prs2/gists{/gist_id}","starred_url":"https://api.github.com/users/petewarden4prs2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/petewarden4prs2/subscriptions","organizations_url":"https://api.github.com/users/petewarden4prs2/orgs","repos_url":"https://api.github.com/users/petewarden4prs2/repos","events_url":"https://api.github.com/users/petewarden4prs2/events{/privacy}","received_events_url":"https://api.github.com/users/petewarden4prs2/received_events","type":"User","site_admin":false},"repo":{"id":65500106,"name":"tensorflow","full_name":"petewarden4prs2/tensorflow","owner":{"login":"petewarden4prs2","id":20894166,"avatar_url":"https://avatars.githubusercontent.com/u/20894166?v=3","gravatar_id":"","url":"https://api.github.com/users/petewarden4prs2","html_url":"https://github.com/petewarden4prs2","followers_url":"https://api.github.com/users/petewarden4prs2/followers","following_url":"https://api.github.com/users/petewarden4prs2/following{/other_user}","gists_url":"https://api.github.com/users/petewarden4prs2/gists{/gist_id}","starred_url":"https://api.github.com/users/petewarden4prs2/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/petewarden4prs2/subscriptions","organizations_url":"https://api.github.com/users/petewarden4prs2/orgs","repos_url":"https://api.github.com/users/petewarden4prs2/repos","events_url":"https://api.github.com/users/petewarden4prs2/events{/privacy}","received_events_url":"https://api.github.com/users/petewarden4prs2/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/petewarden4prs2/tensorflow","description":"Computation using data flow graphs for scalable machine learning","fork":true,"url":"https://api.github.com/repos/petewarden4prs2/tensorflow","forks_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/forks","keys_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/keys{/key_id}","collaborators_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/teams","hooks_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/hooks","issue_events_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/issues/events{/number}","events_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/events","assignees_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/assignees{/user}","branches_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/branches{/branch}","tags_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/tags","blobs_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/git/refs{/sha}","trees_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/git/trees{/sha}","statuses_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/statuses/{sha}","languages_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/languages","stargazers_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/stargazers","contributors_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/contributors","subscribers_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/subscribers","subscription_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/subscription","commits_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/commits{/sha}","git_commits_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/git/commits{/sha}","comments_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/comments{/number}","issue_comment_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/issues/comments{/number}","contents_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/contents/{+path}","compare_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/compare/{base}...{head}","merges_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/merges","archive_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/downloads","issues_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/issues{/number}","pulls_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/pulls{/number}","milestones_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/milestones{/number}","notifications_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/labels{/name}","releases_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/releases{/id}","deployments_url":"https://api.github.com/repos/petewarden4prs2/tensorflow/deployments","created_at":"2016-08-11T20:39:31Z","updated_at":"2016-08-11T20:39:43Z","pushed_at":"2016-08-12T19:51:26Z","git_url":"git://github.com/petewarden4prs2/tensorflow.git","ssh_url":"git@github.com:petewarden4prs2/tensorflow.git","clone_url":"https://github.com/petewarden4prs2/tensorflow.git","svn_url":"https://github.com/petewarden4prs2/tensorflow","homepage":"http://tensorflow.org","size":51464,"stargazers_count":0,"watchers_count":0,"language":"C++","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"tensorflow:master","ref":"master","sha":"bf31051225ce53c1c88fd45ee117a49645153770","user":{"login":"tensorflow","id":15658638,"avatar_url":"https://avatars.githubusercontent.com/u/15658638?v=3","gravatar_id":"","url":"https://api.github.com/users/tensorflow","html_url":"https://github.com/tensorflow","followers_url":"https://api.github.com/users/tensorflow/followers","following_url":"https://api.github.com/users/tensorflow/following{/other_user}","gists_url":"https://api.github.com/users/tensorflow/gists{/gist_id}","starred_url":"https://api.github.com/users/tensorflow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tensorflow/subscriptions","organizations_url":"https://api.github.com/users/tensorflow/orgs","repos_url":"https://api.github.com/users/tensorflow/repos","events_url":"https://api.github.com/users/tensorflow/events{/privacy}","received_events_url":"https://api.github.com/users/tensorflow/received_events","type":"Organization","site_admin":false},"repo":{"id":45717250,"name":"tensorflow","full_name":"tensorflow/tensorflow","owner":{"login":"tensorflow","id":15658638,"avatar_url":"https://avatars.githubusercontent.com/u/15658638?v=3","gravatar_id":"","url":"https://api.github.com/users/tensorflow","html_url":"https://github.com/tensorflow","followers_url":"https://api.github.com/users/tensorflow/followers","following_url":"https://api.github.com/users/tensorflow/following{/other_user}","gists_url":"https://api.github.com/users/tensorflow/gists{/gist_id}","starred_url":"https://api.github.com/users/tensorflow/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/tensorflow/subscriptions","organizations_url":"https://api.github.com/users/tensorflow/orgs","repos_url":"https://api.github.com/users/tensorflow/repos","events_url":"https://api.github.com/users/tensorflow/events{/privacy}","received_events_url":"https://api.github.com/users/tensorflow/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/tensorflow/tensorflow","description":"Computation using data flow graphs for scalable machine learning","fork":false,"url":"https://api.github.com/repos/tensorflow/tensorflow","forks_url":"https://api.github.com/repos/tensorflow/tensorflow/forks","keys_url":"https://api.github.com/repos/tensorflow/tensorflow/keys{/key_id}","collaborators_url":"https://api.github.com/repos/tensorflow/tensorflow/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/tensorflow/tensorflow/teams","hooks_url":"https://api.github.com/repos/tensorflow/tensorflow/hooks","issue_events_url":"https://api.github.com/repos/tensorflow/tensorflow/issues/events{/number}","events_url":"https://api.github.com/repos/tensorflow/tensorflow/events","assignees_url":"https://api.github.com/repos/tensorflow/tensorflow/assignees{/user}","branches_url":"https://api.github.com/repos/tensorflow/tensorflow/branches{/branch}","tags_url":"https://api.github.com/repos/tensorflow/tensorflow/tags","blobs_url":"https://api.github.com/repos/tensorflow/tensorflow/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/tensorflow/tensorflow/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/tensorflow/tensorflow/git/refs{/sha}","trees_url":"https://api.github.com/repos/tensorflow/tensorflow/git/trees{/sha}","statuses_url":"https://api.github.com/repos/tensorflow/tensorflow/statuses/{sha}","languages_url":"https://api.github.com/repos/tensorflow/tensorflow/languages","stargazers_url":"https://api.github.com/repos/tensorflow/tensorflow/stargazers","contributors_url":"https://api.github.com/repos/tensorflow/tensorflow/contributors","subscribers_url":"https://api.github.com/repos/tensorflow/tensorflow/subscribers","subscription_url":"https://api.github.com/repos/tensorflow/tensorflow/subscription","commits_url":"https://api.github.com/repos/tensorflow/tensorflow/commits{/sha}","git_commits_url":"https://api.github.com/repos/tensorflow/tensorflow/git/commits{/sha}","comments_url":"https://api.github.com/repos/tensorflow/tensorflow/comments{/number}","issue_comment_url":"https://api.github.com/repos/tensorflow/tensorflow/issues/comments{/number}","contents_url":"https://api.github.com/repos/tensorflow/tensorflow/contents/{+path}","compare_url":"https://api.github.com/repos/tensorflow/tensorflow/compare/{base}...{head}","merges_url":"https://api.github.com/repos/tensorflow/tensorflow/merges","archive_url":"https://api.github.com/repos/tensorflow/tensorflow/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/tensorflow/tensorflow/downloads","issues_url":"https://api.github.com/repos/tensorflow/tensorflow/issues{/number}","pulls_url":"https://api.github.com/repos/tensorflow/tensorflow/pulls{/number}","milestones_url":"https://api.github.com/repos/tensorflow/tensorflow/milestones{/number}","notifications_url":"https://api.github.com/repos/tensorflow/tensorflow/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/tensorflow/tensorflow/labels{/name}","releases_url":"https://api.github.com/repos/tensorflow/tensorflow/releases{/id}","deployments_url":"https://api.github.com/repos/tensorflow/tensorflow/deployments","created_at":"2015-11-07T01:19:20Z","updated_at":"2016-08-17T19:52:44Z","pushed_at":"2016-08-17T20:42:52Z","git_url":"git://github.com/tensorflow/tensorflow.git","ssh_url":"git@github.com:tensorflow/tensorflow.git","clone_url":"https://github.com/tensorflow/tensorflow.git","svn_url":"https://github.com/tensorflow/tensorflow","homepage":"http://tensorflow.org","size":221219,"stargazers_count":30126,"watchers_count":30126,"language":"C++","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":12591,"mirror_url":null,"open_issues_count":473,"forks":12591,"open_issues":473,"watchers":30126,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/tensorflow/tensorflow/pulls/3778"},"html":{"href":"https://github.com/tensorflow/tensorflow/pull/3778"},"issue":{"href":"https://api.github.com/repos/tensorflow/tensorflow/issues/3778"},"comments":{"href":"https://api.github.com/repos/tensorflow/tensorflow/issues/3778/comments"},"review_comments":{"href":"https://api.github.com/repos/tensorflow/tensorflow/pulls/3778/comments"},"review_comment":{"href":"https://api.github.com/repos/tensorflow/tensorflow/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/tensorflow/tensorflow/pulls/3778/commits"},"statuses":{"href":"https://api.github.com/repos/tensorflow/tensorflow/statuses/1de99caa7c5967f768f5fb4cd0cab287ecccde41"}}}}
{"actor":{"display_login":"petewarden"}}