{"action":"created","comment":{"url":"https://api.github.com/repos/kaldi-asr/kaldi/pulls/comments/64845994","id":64845994,"diff_hunk":"@@ -0,0 +1,798 @@\n+// nnet3/nnet-simple-component.cc\n+\n+// Copyright      2016  Daniel Galvez\n+\n+// See ../../COPYING for clarification regarding multiple authors\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+// THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n+// KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED\n+// WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,\n+// MERCHANTABLITY OR NON-INFRINGEMENT.\n+// See the Apache 2 License for the specific language governing permissions and\n+// limitations under the License.\n+\n+#include \"nnet3/nnet-cudnn-simple-component.h\"\n+#include \"nnet3/nnet-parse.h\"\n+#include \"cudamatrix/cudnn-utils.h\"\n+#include \"cudamatrix/cudnn-convolution.h\"\n+#include <cudnn.h>\n+#include <numeric>\n+#include <functional>\n+\n+namespace kaldi {\n+namespace nnet3 {\n+\n+CuDNN3DConvolutionComponent::CuDNN3DConvolutionComponent() :\n+  UpdatableComponent(),\n+  input_x_dim_(0), input_y_dim_(0), input_z_dim_(0),\n+  input_num_filters_(0), num_filters_(0),\n+  work_space_(NULL), work_space_size_(0),\n+  is_gradient_(false), input_vectorization_(kZyx),\n+  forward_algo_(CUDNN_CONVOLUTION_FWD_ALGO_IMPLICIT_GEMM),\n+  backward_filter_algo_(CUDNN_CONVOLUTION_BWD_FILTER_ALGO_0),\n+  backward_data_algo_(CUDNN_CONVOLUTION_BWD_DATA_ALGO_0) {\n+    CUDNN_SAFE_CALL(cudnnCreateFilterDescriptor(&filter_desc_));\n+    CUDNN_SAFE_CALL(cudnnCreateTensorDescriptor(&bias_desc_));\n+    CUDNN_SAFE_CALL(cudnnCreateConvolutionDescriptor(&conv_desc_));\n+}\n+\n+CuDNN3DConvolutionComponent::CuDNN3DConvolutionComponent(\n+                       const CuDNN3DConvolutionComponent& component) :\n+  UpdatableComponent(component),\n+  input_x_dim_(component.input_x_dim_), input_y_dim_(component.input_y_dim_),\n+  input_z_dim_(component.input_z_dim_),\n+  input_num_filters_(component.input_num_filters_),\n+  filter_params_(component.filter_params_),\n+  bias_params_(component.bias_params_),\n+  num_filters_(component.num_filters_),\n+  // Don't copy workspace pointer; make a new one instead. We would\n+  // need this if we used multiple CUDA streams.\n+  work_space_(NULL),\n+  work_space_size_(component.work_space_size_),\n+  is_gradient_(component.is_gradient_),\n+  input_vectorization_(component.input_vectorization_),\n+  filter_desc_(cudnn::CopyFilterDesc(component.filter_desc_)),\n+  bias_desc_(cudnn::CopyTensorDesc(component.bias_desc_)),\n+  conv_desc_(cudnn::CopyConvolutionDesc(component.conv_desc_)),\n+  forward_algo_(component.forward_algo_),\n+  backward_filter_algo_(component.backward_filter_algo_),\n+  backward_data_algo_(component.backward_data_algo_)\n+ {\n+  if (work_space_size_ != 0) {\n+    work_space_ = CuDevice::Instantiate().Malloc(work_space_size_);\n+  }\n+}\n+\n+CuDNN3DConvolutionComponent::~CuDNN3DConvolutionComponent() {\n+  CUDNN_SAFE_CALL(cudnnDestroyFilterDescriptor(filter_desc_));\n+  CUDNN_SAFE_CALL(cudnnDestroyTensorDescriptor(bias_desc_));\n+  CUDNN_SAFE_CALL(cudnnDestroyConvolutionDescriptor(conv_desc_));\n+\n+  if(work_space_ != NULL) {\n+    CuDevice::Instantiate().Free(work_space_);\n+  }\n+}\n+\n+void CuDNN3DConvolutionComponent::Init(\n+    int32 input_x_dim, int32 input_y_dim, int32 input_z_dim,\n+    int32 filt_x_dim, int32 filt_y_dim, int32 filt_z_dim, int32 input_num_filters,\n+    int32 filt_x_stride, int32 filt_y_stride, int32 filt_z_stride,\n+    int32 num_filters,\n+    int32 pad_x_dim, int32 pad_y_dim, int32 pad_z_dim,\n+    int32 upscale_x_dim, int32 upscale_y_dim, int32 upscale_z_dim,\n+    TensorVectorizationType input_vectorization,\n+    BaseFloat param_stddev, BaseFloat bias_stddev) {\n+  input_x_dim_ = input_x_dim;\n+  input_y_dim_ = input_y_dim;\n+  input_z_dim_ = input_z_dim;\n+  input_num_filters_ = input_num_filters;\n+  num_filters_ = num_filters;\n+  int32 filters[kConvolutionDimension_ + 2];\n+  filters[0] = num_filters_;\n+  filters[1] = input_num_filters_;\n+  filters[2] = filt_z_dim;\n+  filters[3] = filt_y_dim;\n+  filters[4] = filt_x_dim;\n+  input_vectorization_ = input_vectorization;\n+  int32 filter_dim = input_num_filters_ * filt_x_dim * filt_y_dim * filt_z_dim;\n+  filter_params_.Resize(num_filters_, filter_dim, kUndefined,\n+                        kStrideEqualNumCols);\n+  bias_params_.Resize(num_filters_, kUndefined);\n+  KALDI_ASSERT(param_stddev >= 0.0 && bias_stddev >= 0.0);\n+  filter_params_.SetRandn();\n+  filter_params_.Scale(param_stddev);\n+  bias_params_.SetRandn();\n+  bias_params_.Scale(bias_stddev);\n+\n+  int32 strides[kConvolutionDimension_];\n+  strides[0] = filt_z_stride;\n+  strides[1] = filt_y_stride;\n+  strides[2] = filt_x_stride;\n+\n+  int32 upscales[kConvolutionDimension_];\n+  upscales[0] = upscale_z_dim;\n+  upscales[1] = upscale_y_dim;\n+  upscales[2] = upscale_x_dim;\n+\n+  int32 padding[kConvolutionDimension_];\n+  padding[0] = pad_z_dim;\n+  padding[1] = pad_y_dim;\n+  padding[2] = pad_x_dim;\n+\n+  CUDNN_SAFE_CALL(\n+    cudnnSetConvolutionNdDescriptor(conv_desc_,\n+                                    kConvolutionDimension_,\n+                                    padding,\n+                                    strides,\n+                                    upscales,\n+                                    CUDNN_CROSS_CORRELATION,\n+                                    cudnn::GetDataType())\n+  );\n+\n+  CUDNN_SAFE_CALL(cudnnCreateFilterDescriptor(&filter_desc_));\n+  CUDNN_SAFE_CALL(\n+    cudnnSetFilterNdDescriptor(filter_desc_,\n+                               cudnn::GetDataType(),\n+                               kConvolutionDimension_ + 2,\n+                               filters\n+                               )\n+  );\n+\n+  int32 bias_dims[kConvolutionDimension_ + 2] = {\n+    1,\n+    num_filters_,\n+    1,\n+    1,\n+    1\n+  };\n+\n+  int32 bias_strides[kConvolutionDimension_ + 2] = {\n+    num_filters_,\n+    1,\n+    1,\n+    1,\n+    1\n+  };\n+\n+  CUDNN_SAFE_CALL(\n+    cudnnSetTensorNdDescriptor(bias_desc_,\n+                               cudnn::GetDataType(),\n+                               kConvolutionDimension_ + 2,\n+                               bias_dims,\n+                               bias_strides\n+                               )\n+                  );\n+\n+\n+}\n+\n+void CuDNN3DConvolutionComponent::InitFromConfig(ConfigLine *cfl) {\n+  bool ok = true;\n+  int32 input_x_dim = -1, input_y_dim = -1, input_z_dim = -1,\n+    input_num_filters = -1,\n+    filt_x_dim = -1, filt_y_dim = -1, filt_z_dim = -1,\n+    filt_x_stride = -1, filt_y_stride = -1, filt_z_stride = -1,\n+    num_filters = -1,\n+    upscale_x_dim = -1, upscale_y_dim = -1, upscale_z_dim = -1,\n+    pad_x_dim = -1, pad_y_dim = -1, pad_z_dim = -1;\n+\n+  std::string input_vectorization_order = \"zyx\";\n+  TensorVectorizationType input_vectorization = kZyx;\n+  // TODO: Figure our whether we need to allow input_vectorization_order\n+  // to be configurable.\n+\n+  InitLearningRatesFromConfig(cfl);\n+  ok = ok && cfl->GetValue(\"num-filters\", &num_filters);\n+  ok = ok && cfl->GetValue(\"input-num-filters\", &input_num_filters);\n+  ok = ok && cfl->GetValue(\"input-x-dim\", &input_x_dim);\n+  ok = ok && cfl->GetValue(\"input-y-dim\", &input_y_dim);\n+  ok = ok && cfl->GetValue(\"input-z-dim\", &input_z_dim);\n+  ok = ok && cfl->GetValue(\"filt-x-dim\", &filt_x_dim);\n+  ok = ok && cfl->GetValue(\"filt-y-dim\", &filt_y_dim);\n+  ok = ok && cfl->GetValue(\"filt-z-dim\", &filt_z_dim);\n+  ok = ok && cfl->GetValue(\"filt-x-stride\", &filt_x_stride);\n+  ok = ok && cfl->GetValue(\"filt-y-stride\", &filt_y_stride);\n+  ok = ok && cfl->GetValue(\"filt-z-stride\", &filt_z_stride);\n+\n+  // upscale_<k>_dim is how many times to\n+  // repeat each output in the <k>th dimension. This is\n+  // usually used to do image synthesis. I think\n+  // this will not be useful to change for most\n+  // people. By default, it is set to all ones.\n+  if(!cfl->GetValue(\"upscale-x-dim\", &upscale_x_dim)) {\n+    upscale_x_dim = 1;\n+  }\n+  if(!cfl->GetValue(\"upscale-y-dim\", &upscale_y_dim)) {\n+    upscale_y_dim = 1;\n+  }\n+  if(!cfl->GetValue(\"upscale-z-dim\", &upscale_z_dim)) {\n+    upscale_z_dim = 1;\n+  }\n+\n+  // If padding is not explicitly given, this code chooses padding so that the input dimension \n+  // will equal the output dimension.\n+  // For a justification of this, search for \"The effect of zero padding on network size\" in \n+  // Chapter 9: Convolutional Networks, of the Deep Learning book by Goodfellow et al.\n+  // TODO: Make a private function for this.\n+  // ALSO: I'm not sure whether I should be rounding up or down. Right now,\n+  // I am rounding down.\n+  pad_x_dim = 0;\n+  pad_y_dim = 0;\n+  pad_z_dim = 0;\n+  /*\n+  if(!cfl->GetValue(\"pad-x-dim\", &pad_x_dim)) {\n+    pad_x_dim = ((filt_x_stride - upscale_x_dim)*input_x_dim + filt_x_dim - filt_x_stride)/2;\n+  }\n+  if(!cfl->GetValue(\"pad-y-dim\", &pad_y_dim)) {\n+    pad_y_dim = ((filt_y_stride - upscale_y_dim)*input_y_dim + filt_y_dim - filt_y_stride)/2;\n+  }\n+  if(!cfl->GetValue(\"pad-z-dim\", &pad_z_dim)) {\n+    pad_z_dim = ((filt_z_stride - upscale_z_dim)*input_z_dim + filt_z_dim - filt_z_stride)/2;\n+  }\n+  */\n+  int32 filter_input_dim = filt_x_dim * filt_y_dim * input_z_dim;\n+  BaseFloat param_stddev = 1.0 / std::sqrt(filter_input_dim), bias_stddev = 1.0;\n+  cfl->GetValue(\"param-stddev\", &param_stddev);\n+  cfl->GetValue(\"bias-stddev\", &bias_stddev);\n+\n+  if (cfl->HasUnusedValues()) {\n+    KALDI_ERR << \"Could not process these elements in initializer: \"\n+              << cfl->UnusedValues();\n+  }\n+  if (!ok) {\n+    KALDI_ERR << \"Bad initializer \" << cfl->WholeLine();\n+  }\n+\n+  Init(input_x_dim, input_y_dim, input_z_dim,\n+       filt_x_dim, filt_y_dim, filt_z_dim, input_num_filters,\n+       filt_x_stride, filt_y_stride, filt_z_stride,\n+       num_filters,\n+       pad_x_dim, pad_y_dim, pad_z_dim,\n+       upscale_x_dim, upscale_y_dim, upscale_z_dim,\n+       input_vectorization,\n+       param_stddev, bias_stddev);\n+}\n+\n+/**\n+ * Returns a three-element long vector where index 0 contains\n+ * the z output dimension, 1 contains the y output dimension,\n+ * and 2 contains the x output dimension.\n+ */\n+std::vector<int32> CuDNN3DConvolutionComponent::GetOutputDims() const {\n+  cudnnTensorDescriptor_t in_desc;\n+  CUDNN_SAFE_CALL(cudnnCreateTensorDescriptor(&in_desc));\n+  int32 input_dims[kConvolutionDimension_ + 2] =\n+    {1,\n+     input_num_filters_,\n+     input_z_dim_,\n+     input_y_dim_,\n+     input_x_dim_};\n+  int32 input_strides[kConvolutionDimension_ + 2] =\n+    {input_num_filters_*input_z_dim_*input_y_dim_*input_x_dim_,\n+     input_z_dim_*input_y_dim_*input_x_dim_,\n+     input_y_dim_*input_x_dim_,\n+     input_x_dim_,\n+     1};\n+  CUDNN_SAFE_CALL(cudnnSetTensorNdDescriptor(in_desc,\n+                                             cudnn::GetDataType(),\n+                                             kConvolutionDimension_ + 2,\n+                                             input_dims,\n+                                             input_strides\n+                                             )\n+                  );\n+  int32 output_dims[kConvolutionDimension_ + 2];\n+  CUDNN_SAFE_CALL(\n+    cudnnGetConvolutionNdForwardOutputDim(conv_desc_,\n+                                          in_desc,\n+                                          filter_desc_,\n+                                          kConvolutionDimension_ + 2,\n+                                          output_dims\n+                                          )\n+                  );\n+  KALDI_ASSERT(output_dims[0] == 1); // Sanity check: Only one element in fake batch.\n+  KALDI_ASSERT(output_dims[1] == num_filters_);\n+  std::vector<int32> output_dims_vec(kConvolutionDimension_);\n+  for(int i = 0; i < output_dims_vec.size(); i++) {\n+    output_dims_vec[i] = output_dims[i + 2];\n+  }\n+\n+  CUDNN_SAFE_CALL(cudnnDestroyTensorDescriptor(in_desc));\n+  return output_dims_vec;\n+}\n+\n+int32 CuDNN3DConvolutionComponent::OutputDim() const {\n+  std::vector<int32> output_dims = GetOutputDims();\n+  int32 output_dim = num_filters_ *\n+    std::accumulate(output_dims.begin(), output_dims.end(), 1,\n+                    std::multiplies<int32>());\n+  return output_dim;\n+}\n+\n+void CuDNN3DConvolutionComponent::Propagate(const ComponentPrecomputedIndexes *indexes,\n+                                            const CuMatrixBase<BaseFloat> &in,\n+                                            CuMatrixBase<BaseFloat> *out) const {\n+\n+  BaseFloat f = filter_params_.FrobeniusNorm();\n+  KALDI_ASSERT(f == f);\n+  BaseFloat b = bias_params_.Sum();\n+  KALDI_ASSERT(b == b);\n+\n+  KALDI_ASSERT(input_vectorization_ == kZyx && \"Only zyx vectorization supported right now.\");\n+  KALDI_ASSERT(in.NumCols() == in.Stride());\n+  KALDI_ASSERT(out->NumCols() == out->Stride());\n+\n+  cudnnTensorDescriptor_t in_desc;\n+  CUDNN_SAFE_CALL(cudnnCreateTensorDescriptor(&in_desc));\n+  int32 input_dims[kConvolutionDimension_ + 2] = {in.NumRows(),\n+                                                  input_num_filters_,\n+                                                  input_z_dim_,\n+                                                  input_y_dim_,\n+                                                  input_x_dim_};\n+  KALDI_ASSERT(input_num_filters_ * input_z_dim_ * input_y_dim_ * input_x_dim_ ==\n+               in.Stride());\n+  int32 input_strides[kConvolutionDimension_ + 2] =\n+    {input_num_filters_ * input_z_dim_ * input_y_dim_ * input_x_dim_, // == in.Stride()\n+     input_z_dim_ * input_y_dim_ * input_x_dim_,\n+     input_y_dim_ * input_x_dim_,\n+     input_x_dim_,\n+     1};\n+  CUDNN_SAFE_CALL(cudnnSetTensorNdDescriptor(in_desc,\n+                                             cudnn::GetDataType(),\n+                                             // 3D convolution means:\n+                                             // batch dimension, channel, depth, height, and width.\n+                                             // thus the input tensor is 5 dimensional.\n+                                             kConvolutionDimension_ + 2,\n+                                             input_dims,\n+                                             input_strides\n+                                             )\n+                  );\n+\n+  std::vector<int32> output_dims_per_filter = GetOutputDims();\n+  int32 output_dims[kConvolutionDimension_ + 2] = {\n+    out->NumRows(),\n+    num_filters_,\n+    output_dims_per_filter[0],\n+    output_dims_per_filter[1],\n+    output_dims_per_filter[2]\n+  };\n+\n+  KALDI_ASSERT(out->Stride() == num_filters_ * output_dims_per_filter[0] *\n+               output_dims_per_filter[1] * output_dims_per_filter[2]);\n+\n+  int32 output_strides[kConvolutionDimension_ + 2] = {\n+    num_filters_ * output_dims_per_filter[0] * output_dims_per_filter[1] * output_dims_per_filter[2],\n+    output_dims_per_filter[0] * output_dims_per_filter[1] * output_dims_per_filter[2],\n+    output_dims_per_filter[1] * output_dims_per_filter[2],\n+    output_dims_per_filter[2],\n+    1\n+  };\n+  cudnnTensorDescriptor_t out_desc;\n+  CUDNN_SAFE_CALL(cudnnCreateTensorDescriptor(&out_desc));\n+  CUDNN_SAFE_CALL(cudnnSetTensorNdDescriptor(out_desc,\n+                                             cudnn::GetDataType(),\n+                                             kConvolutionDimension_ + 2,\n+                                             output_dims,\n+                                             output_strides\n+                                             )\n+                  );\n+  cudnn::ConvolutionForward(CuDevice::Instantiate().GetCudnnHandle(),\n+                            &cudnn::one,\n+                            in_desc,\n+                            in.Data(),\n+                            filter_desc_,\n+                            filter_params_.Data(),\n+                            conv_desc_,\n+                            forward_algo_,\n+                            work_space_,\n+                            work_space_size_,\n+                            &cudnn::one,\n+                            out_desc,\n+                            out->Data()\n+                            );\n+\n+  CUDNN_SAFE_CALL(\n+    cudnnAddTensor(CuDevice::Instantiate().GetCudnnHandle(),\n+                   &cudnn::one,\n+                   bias_desc_,\n+                   bias_params_.Data(),\n+                   &cudnn::one,\n+                   out_desc,\n+                   out->Data()\n+                  )\n+                  );\n+\n+  CUDNN_SAFE_CALL(cudnnDestroyTensorDescriptor(in_desc));\n+  CUDNN_SAFE_CALL(cudnnDestroyTensorDescriptor(out_desc));\n+}\n+\n+void CuDNN3DConvolutionComponent::Backprop(const std::string &debug_info,\n+                                         const ComponentPrecomputedIndexes *indexes,\n+                                         const CuMatrixBase<BaseFloat> &in_value,\n+                                         const CuMatrixBase<BaseFloat> &, //out_value,\n+                                         const CuMatrixBase<BaseFloat> &out_deriv,\n+                                         Component *to_update_in,\n+                                         CuMatrixBase<BaseFloat> *in_deriv) const {\n+  CuDNN3DConvolutionComponent *to_update =\n+    dynamic_cast<CuDNN3DConvolutionComponent*>(to_update_in);\n+\n+  BaseFloat f = filter_params_.FrobeniusNorm();\n+  KALDI_ASSERT(f == f);\n+  BaseFloat b = bias_params_.Sum();\n+  KALDI_ASSERT(b == b);\n+\n+  cudnnTensorDescriptor_t out_deriv_desc;\n+  CUDNN_SAFE_CALL(cudnnCreateTensorDescriptor(&out_deriv_desc));\n+  std::vector<int32> output_dims = GetOutputDims();\n+  int32 out_deriv_dims[kConvolutionDimension_ + 2] = {\n+    out_deriv.NumRows(),\n+    num_filters_,\n+    output_dims[0],\n+    output_dims[1],\n+    output_dims[2]\n+  };\n+  KALDI_ASSERT(out_deriv.Stride() == out_deriv.NumCols());\n+  KALDI_ASSERT(out_deriv.Stride() == num_filters_ * output_dims[0] * output_dims[1] * output_dims[2]);\n+  int32 out_deriv_strides[kConvolutionDimension_ + 2] = {\n+    num_filters_ * output_dims[0] * output_dims[1] * output_dims[2],\n+    output_dims[0] * output_dims[1] * output_dims[2],\n+    output_dims[1] * output_dims[2],\n+    output_dims[2],\n+    1\n+  };\n+  CUDNN_SAFE_CALL(cudnnSetTensorNdDescriptor(out_deriv_desc,\n+                                             cudnn::GetDataType(),\n+                                             kConvolutionDimension_ + 2,\n+                                             out_deriv_dims,\n+                                             out_deriv_strides\n+                                             )\n+                  );\n+\n+    KALDI_ASSERT(in_value.NumCols() == in_value.Stride());\n+    KALDI_ASSERT(in_value.NumCols() == input_num_filters_ * input_z_dim_ * input_y_dim_ * input_x_dim_);\n+    int32 in_dims[kConvolutionDimension_ + 2] = {\n+      in_value.NumRows(),\n+      input_num_filters_,\n+      input_z_dim_,\n+      input_y_dim_,\n+      input_x_dim_\n+    };\n+    int32 in_strides[kConvolutionDimension_ + 2] = {\n+      input_num_filters_ * input_z_dim_ * input_y_dim_ * input_x_dim_,\n+      input_z_dim_ * input_y_dim_ * input_x_dim_,\n+      input_y_dim_ * input_x_dim_,\n+      input_x_dim_,\n+      1\n+    };\n+\n+    cudnnTensorDescriptor_t in_desc; // shared between in_value and in_deriv\n+    CUDNN_SAFE_CALL(cudnnCreateTensorDescriptor(&in_desc));\n+    CUDNN_SAFE_CALL(cudnnSetTensorNdDescriptor(in_desc,\n+                                               cudnn::GetDataType(),\n+                                               kConvolutionDimension_ + 2,\n+                                               in_dims,\n+                                               in_strides\n+                                               )\n+                    );\n+\n+  if (in_deriv) {\n+\n+    cudnn::ConvolutionBackwardData(CuDevice::Instantiate().GetCudnnHandle(),\n+                                   &cudnn::one,\n+                                   filter_desc_,\n+                                   filter_params_.Data(),\n+                                   out_deriv_desc,\n+                                   out_deriv.Data(),\n+                                   conv_desc_,\n+                                   backward_data_algo_,\n+                                   work_space_,\n+                                   work_space_size_,\n+                                   &cudnn::one,\n+                                   in_desc,\n+                                   in_deriv->Data());\n+  }\n+\n+  if (to_update) {\n+    to_update->Update(in_value, out_deriv, in_desc, out_deriv_desc);\n+  }\n+  CUDNN_SAFE_CALL(cudnnDestroyTensorDescriptor(in_desc));\n+  CUDNN_SAFE_CALL(cudnnDestroyTensorDescriptor(out_deriv_desc));\n+}\n+\n+void CuDNN3DConvolutionComponent::Update(const CuMatrixBase<BaseFloat> &in_value,\n+                                         const CuMatrixBase<BaseFloat> &out_deriv,\n+                                         const cudnnTensorDescriptor_t in_desc,\n+                                         const cudnnTensorDescriptor_t out_deriv_desc) {\n+  cudnn::ConvolutionBackwardFilter(CuDevice::Instantiate().GetCudnnHandle(),\n+                                   &learning_rate_, // alpha\n+                                   in_desc,\n+                                   in_value.Data(),\n+                                   out_deriv_desc,\n+                                   out_deriv.Data(),\n+                                   conv_desc_,\n+                                   backward_filter_algo_,\n+                                   work_space_,\n+                                   work_space_size_,\n+                                   &cudnn::one, // beta\n+                                   filter_desc_,\n+                                   filter_params_.Data()\n+                                   );\n+\n+  cudnn::ConvolutionBackwardBias(CuDevice::Instantiate().GetCudnnHandle(),\n+                                 &learning_rate_,\n+                                 out_deriv_desc,\n+                                 out_deriv.Data(),\n+                                 &cudnn::one,\n+                                 bias_desc_,\n+                                 bias_params_.Data()\n+                                 );\n+}\n+\n+Component *CuDNN3DConvolutionComponent::Copy() const {\n+  CuDNN3DConvolutionComponent *ans = new CuDNN3DConvolutionComponent(*this);\n+  return ans;\n+}\n+\n+void CuDNN3DConvolutionComponent::Read(std::istream &is, bool binary) {\n+  ReadUpdatableCommon(is, binary);  // Read opening tag and learning rate.\n+  ExpectToken(is, binary, \"<InputXDim>\");\n+  ReadBasicType(is, binary, &input_x_dim_);\n+  ExpectToken(is, binary, \"<InputYDim>\");\n+  ReadBasicType(is, binary, &input_y_dim_);\n+  ExpectToken(is, binary, \"<InputZDim>\");\n+  ReadBasicType(is, binary, &input_z_dim_);\n+  ExpectToken(is, binary, \"<NumFilters>\");\n+  ReadBasicType(is, binary, &num_filters_);\n+  int32 filter_dims[kConvolutionDimension_];\n+  ExpectToken(is, binary, \"<FilterXDim>\");\n+  ReadBasicType(is, binary, &filter_dims[0]);\n+  ExpectToken(is, binary, \"<FilterYDim>\");\n+  ReadBasicType(is, binary, &filter_dims[1]);\n+  ExpectToken(is, binary, \"<FilterZDim>\");\n+  ReadBasicType(is, binary, &filter_dims[2]);\n+  int32 padding[kConvolutionDimension_];\n+  ExpectToken(is, binary, \"<FilterXPadding>\");\n+  ReadBasicType(is, binary, &padding[0]);\n+  ExpectToken(is, binary, \"<FilterYPadding>\");\n+  ReadBasicType(is, binary, &padding[1]);\n+  ExpectToken(is, binary, \"<FilterZPadding>\");\n+  ReadBasicType(is, binary, &padding[2]);\n+  int32 strides[kConvolutionDimension_];\n+  ExpectToken(is, binary, \"<FilterXStride>\");\n+  ReadBasicType(is, binary, &strides[0]);\n+  ExpectToken(is, binary, \"<FilterYStride>\");\n+  ReadBasicType(is, binary, &strides[1]);\n+  ExpectToken(is, binary, \"<FilterZStride>\");\n+  ReadBasicType(is, binary, &strides[2]);\n+  int32 upscales[kConvolutionDimension_];\n+  ExpectToken(is, binary, \"<FilterXUpscale>\");\n+  ReadBasicType(is, binary, &upscales[0]);\n+  ExpectToken(is, binary, \"<FilterYUpscale>\");\n+  ReadBasicType(is, binary, &upscales[1]);\n+  ExpectToken(is, binary, \"<FilterZUpscale>\");\n+  ReadBasicType(is, binary, &upscales[2]);\n+  ExpectToken(is, binary, \"<InputVectorization>\");\n+  int32 input_vectorization;\n+  ReadBasicType(is, binary, &input_vectorization);\n+  input_vectorization_ = static_cast<TensorVectorizationType>(input_vectorization);\n+  ExpectToken(is, binary, \"<FilterParams>\");\n+  filter_params_.Read(is, binary);\n+  ExpectToken(is, binary, \"<BiasParams>\");\n+  bias_params_.Read(is, binary);\n+  ExpectToken(is, binary, \"<IsGradient>\");\n+  ReadBasicType(is, binary, &is_gradient_);\n+  ExpectToken(is, binary, \"</CuDNN3DConvolutionComponent>\");\n+\n+  CUDNN_SAFE_CALL(cudnnSetFilterNdDescriptor(filter_desc_,\n+                                             cudnn::GetDataType(),\n+                                             kConvolutionDimension_,\n+                                             filter_dims\n+                                             ));\n+  // N x C x D x H x W\n+  // TODO: torch uses {1, num_filters, 1, 1}\n+  // Understand why. Is this a mistake?\n+  int32 bias_dims[] = {1, num_filters_, 1, 1};\n+  int32 bias_strides[] = {num_filters_, 1, 1, 1};\n+  CUDNN_SAFE_CALL(cudnnSetTensorNdDescriptor(bias_desc_,\n+                                             cudnn::GetDataType(),\n+                                             kConvolutionDimension_ + 1,\n+                                             bias_dims,\n+                                             bias_strides\n+                                             ));\n+  CUDNN_SAFE_CALL(cudnnSetConvolutionNdDescriptor(conv_desc_,\n+                                                  kConvolutionDimension_,\n+                                                  padding,\n+                                                  strides,\n+                                                  upscales,\n+                                                  CUDNN_CROSS_CORRELATION,\n+                                                  cudnn::GetDataType()\n+                                                  ));\n+}\n+\n+void CuDNN3DConvolutionComponent::Write(std::ostream &os, bool binary) const {\n+  WriteUpdatableCommon(os, binary);  // write opening tag and learning rate.\n+  WriteToken(os, binary, \"<InputXDim>\");\n+  WriteBasicType(os, binary, input_x_dim_);\n+  WriteToken(os, binary, \"<InputYDim>\");\n+  WriteBasicType(os, binary, input_y_dim_);\n+  WriteToken(os, binary, \"<InputZDim>\");\n+  WriteBasicType(os, binary, input_z_dim_);\n+  WriteToken(os, binary, \"<NumFilters>\");\n+  WriteBasicType(os, binary, num_filters_);\n+  int32 filter_dims[kConvolutionDimension_];\n+  int32 numDimensions;\n+  cudnnDataType_t float_type;\n+  CUDNN_SAFE_CALL(cudnnGetFilterNdDescriptor(filter_desc_,\n+                                             kConvolutionDimension_,\n+                                             &float_type,\n+                                             &numDimensions,\n+                                             filter_dims)\n+                  );\n+  WriteToken(os, binary, \"<FilterXDim>\");\n+  WriteBasicType(os, binary, filter_dims[0]);\n+  WriteToken(os, binary, \"<FilterYDim>\");\n+  WriteBasicType(os, binary, filter_dims[1]);\n+  WriteToken(os, binary, \"<FilterZDim>\");\n+  WriteBasicType(os, binary, filter_dims[2]);\n+  int32 padding[kConvolutionDimension_];\n+  int32 strides[kConvolutionDimension_];\n+  int32 upscales[kConvolutionDimension_];\n+  cudnnConvolutionMode_t mode;\n+  CUDNN_SAFE_CALL(cudnnGetConvolutionNdDescriptor(conv_desc_,\n+                                  kConvolutionDimension_,\n+                                  &numDimensions,\n+                                  padding,\n+                                  strides,\n+                                  upscales,\n+                                  &mode,\n+                                  &float_type\n+                                  )\n+                  );\n+  KALDI_ASSERT(numDimensions == kConvolutionDimension_);\n+  KALDI_ASSERT(mode == CUDNN_CROSS_CORRELATION);\n+  KALDI_ASSERT(float_type == cudnn::GetDataType());\n+  WriteToken(os, binary, \"<FilterXPadding>\");\n+  WriteBasicType(os, binary, padding[0]);\n+  WriteToken(os, binary, \"<FilterYPadding>\");\n+  WriteBasicType(os, binary, padding[1]);\n+  WriteToken(os, binary, \"<FilterZPadding>\");\n+  WriteBasicType(os, binary, padding[2]);\n+  WriteToken(os, binary, \"<FilterXStride>\");\n+  WriteBasicType(os, binary, strides[0]);\n+  WriteToken(os, binary, \"<FilterYStride>\");\n+  WriteBasicType(os, binary, strides[1]);\n+  WriteToken(os, binary, \"<FilterZStride>\");\n+  WriteBasicType(os, binary, strides[2]);\n+  WriteToken(os, binary, \"<FilterXUpscale>\");\n+  WriteBasicType(os, binary, upscales[0]);\n+  WriteToken(os, binary, \"<FilterYUpscale>\");\n+  WriteBasicType(os, binary, upscales[1]);\n+  WriteToken(os, binary, \"<FilterZUpscale>\");\n+  WriteBasicType(os, binary, upscales[2]);\n+  WriteToken(os, binary, \"<InputVectorization>\");\n+  WriteBasicType(os, binary, static_cast<int32>(input_vectorization_));\n+  WriteToken(os, binary, \"<FilterParams>\");\n+  filter_params_.Write(os, binary);\n+  WriteToken(os, binary, \"<BiasParams>\");\n+  bias_params_.Write(os, binary);\n+  WriteToken(os, binary, \"<IsGradient>\");\n+  WriteBasicType(os, binary, is_gradient_);\n+  WriteToken(os, binary, \"</CuDNN3DConvolutionComponent>\");\n+}\n+\n+void CuDNN3DConvolutionComponent::SetZero(bool treat_as_gradient) {\n+  if (treat_as_gradient) {\n+    SetActualLearningRate(1.0);\n+    is_gradient_ = true;\n+  }\n+  filter_params_.SetZero();\n+  bias_params_.SetZero();\n+}\n+\n+BaseFloat CuDNN3DConvolutionComponent::DotProduct(const UpdatableComponent &other_in) const {\n+  const CuDNN3DConvolutionComponent *other =\n+    dynamic_cast<const CuDNN3DConvolutionComponent*>(&other_in);\n+  return TraceMatMat(filter_params_, other->filter_params_, kTrans) +\n+    VecVec(bias_params_, other->bias_params_);\n+}\n+\n+std::string CuDNN3DConvolutionComponent::Info() const {\n+  int32 numDimensions;\n+  int32 pad_dims[kConvolutionDimension_];\n+  int32 stride_dims[kConvolutionDimension_];\n+  int32 upscale_dims[kConvolutionDimension_];\n+  cudnnConvolutionMode_t mode;\n+  cudnnDataType_t float_type;\n+  CUDNN_SAFE_CALL(cudnnGetConvolutionNdDescriptor(conv_desc_,\n+                                                  kConvolutionDimension_,\n+                                                  &numDimensions,\n+                                                  pad_dims,\n+                                                  stride_dims,\n+                                                  upscale_dims,\n+                                                  &mode,\n+                                                  &float_type)\n+                  );\n+  KALDI_ASSERT(float_type == cudnn::GetDataType());\n+  int32 filter_dims[CUDNN_DIM_MAX];\n+  CUDNN_SAFE_CALL(cudnnGetFilterNdDescriptor(filter_desc_,\n+                                             kConvolutionDimension_,","path":"src/nnet3/nnet-cudnn-simple-component.cc","position":723,"original_position":723,"commit_id":"714f05f26f811d0f1da2f289ece49fbcc6451f3c","original_commit_id":"4001fe5047f981ab44e6f693c0a302fdb4f13e5c","user":{"login":"galv","id":4767568,"avatar_url":"https://avatars.githubusercontent.com/u/4767568?v=3","gravatar_id":"","url":"https://api.github.com/users/galv","html_url":"https://github.com/galv","followers_url":"https://api.github.com/users/galv/followers","following_url":"https://api.github.com/users/galv/following{/other_user}","gists_url":"https://api.github.com/users/galv/gists{/gist_id}","starred_url":"https://api.github.com/users/galv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/galv/subscriptions","organizations_url":"https://api.github.com/users/galv/orgs","repos_url":"https://api.github.com/users/galv/repos","events_url":"https://api.github.com/users/galv/events{/privacy}","received_events_url":"https://api.github.com/users/galv/received_events","type":"User","site_admin":false},"body":"okay, yep, this is a really great catch. Thanks a lot! This means we were serializing models incorrectly. I'll investigate further.","created_at":"2016-05-27T01:19:45Z","updated_at":"2016-05-27T01:19:45Z","html_url":"https://github.com/kaldi-asr/kaldi/pull/770#discussion_r64845994","pull_request_url":"https://api.github.com/repos/kaldi-asr/kaldi/pulls/770","_links":{"self":{"href":"https://api.github.com/repos/kaldi-asr/kaldi/pulls/comments/64845994"},"html":{"href":"https://github.com/kaldi-asr/kaldi/pull/770#discussion_r64845994"},"pull_request":{"href":"https://api.github.com/repos/kaldi-asr/kaldi/pulls/770"}}},"pull_request":{"url":"https://api.github.com/repos/kaldi-asr/kaldi/pulls/770","id":69299085,"html_url":"https://github.com/kaldi-asr/kaldi/pull/770","diff_url":"https://github.com/kaldi-asr/kaldi/pull/770.diff","patch_url":"https://github.com/kaldi-asr/kaldi/pull/770.patch","issue_url":"https://api.github.com/repos/kaldi-asr/kaldi/issues/770","number":770,"state":"open","locked":false,"title":"Cudnn 3D Convolution Component","user":{"login":"galv","id":4767568,"avatar_url":"https://avatars.githubusercontent.com/u/4767568?v=3","gravatar_id":"","url":"https://api.github.com/users/galv","html_url":"https://github.com/galv","followers_url":"https://api.github.com/users/galv/followers","following_url":"https://api.github.com/users/galv/following{/other_user}","gists_url":"https://api.github.com/users/galv/gists{/gist_id}","starred_url":"https://api.github.com/users/galv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/galv/subscriptions","organizations_url":"https://api.github.com/users/galv/orgs","repos_url":"https://api.github.com/users/galv/repos","events_url":"https://api.github.com/users/galv/events{/privacy}","received_events_url":"https://api.github.com/users/galv/received_events","type":"User","site_admin":false},"body":"See #769 for some discussion points.\r\n\r\n\r\nI realize now there are some commits at the beginning which are not relevant to CUDNN. Sorry, but I'd rather not look at removing those tonight; Tom's code mixes bash script and C++ changes.","created_at":"2016-05-09T06:09:26Z","updated_at":"2016-05-27T01:19:45Z","closed_at":null,"merged_at":null,"merge_commit_sha":null,"assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/kaldi-asr/kaldi/pulls/770/commits","review_comments_url":"https://api.github.com/repos/kaldi-asr/kaldi/pulls/770/comments","review_comment_url":"https://api.github.com/repos/kaldi-asr/kaldi/pulls/comments{/number}","comments_url":"https://api.github.com/repos/kaldi-asr/kaldi/issues/770/comments","statuses_url":"https://api.github.com/repos/kaldi-asr/kaldi/statuses/714f05f26f811d0f1da2f289ece49fbcc6451f3c","head":{"label":"galv:cudnn-2","ref":"cudnn-2","sha":"714f05f26f811d0f1da2f289ece49fbcc6451f3c","user":{"login":"galv","id":4767568,"avatar_url":"https://avatars.githubusercontent.com/u/4767568?v=3","gravatar_id":"","url":"https://api.github.com/users/galv","html_url":"https://github.com/galv","followers_url":"https://api.github.com/users/galv/followers","following_url":"https://api.github.com/users/galv/following{/other_user}","gists_url":"https://api.github.com/users/galv/gists{/gist_id}","starred_url":"https://api.github.com/users/galv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/galv/subscriptions","organizations_url":"https://api.github.com/users/galv/orgs","repos_url":"https://api.github.com/users/galv/repos","events_url":"https://api.github.com/users/galv/events{/privacy}","received_events_url":"https://api.github.com/users/galv/received_events","type":"User","site_admin":false},"repo":{"id":47929752,"name":"kaldi","full_name":"galv/kaldi","owner":{"login":"galv","id":4767568,"avatar_url":"https://avatars.githubusercontent.com/u/4767568?v=3","gravatar_id":"","url":"https://api.github.com/users/galv","html_url":"https://github.com/galv","followers_url":"https://api.github.com/users/galv/followers","following_url":"https://api.github.com/users/galv/following{/other_user}","gists_url":"https://api.github.com/users/galv/gists{/gist_id}","starred_url":"https://api.github.com/users/galv/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/galv/subscriptions","organizations_url":"https://api.github.com/users/galv/orgs","repos_url":"https://api.github.com/users/galv/repos","events_url":"https://api.github.com/users/galv/events{/privacy}","received_events_url":"https://api.github.com/users/galv/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/galv/kaldi","description":"This is now the official location of the Kaldi project.","fork":true,"url":"https://api.github.com/repos/galv/kaldi","forks_url":"https://api.github.com/repos/galv/kaldi/forks","keys_url":"https://api.github.com/repos/galv/kaldi/keys{/key_id}","collaborators_url":"https://api.github.com/repos/galv/kaldi/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/galv/kaldi/teams","hooks_url":"https://api.github.com/repos/galv/kaldi/hooks","issue_events_url":"https://api.github.com/repos/galv/kaldi/issues/events{/number}","events_url":"https://api.github.com/repos/galv/kaldi/events","assignees_url":"https://api.github.com/repos/galv/kaldi/assignees{/user}","branches_url":"https://api.github.com/repos/galv/kaldi/branches{/branch}","tags_url":"https://api.github.com/repos/galv/kaldi/tags","blobs_url":"https://api.github.com/repos/galv/kaldi/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/galv/kaldi/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/galv/kaldi/git/refs{/sha}","trees_url":"https://api.github.com/repos/galv/kaldi/git/trees{/sha}","statuses_url":"https://api.github.com/repos/galv/kaldi/statuses/{sha}","languages_url":"https://api.github.com/repos/galv/kaldi/languages","stargazers_url":"https://api.github.com/repos/galv/kaldi/stargazers","contributors_url":"https://api.github.com/repos/galv/kaldi/contributors","subscribers_url":"https://api.github.com/repos/galv/kaldi/subscribers","subscription_url":"https://api.github.com/repos/galv/kaldi/subscription","commits_url":"https://api.github.com/repos/galv/kaldi/commits{/sha}","git_commits_url":"https://api.github.com/repos/galv/kaldi/git/commits{/sha}","comments_url":"https://api.github.com/repos/galv/kaldi/comments{/number}","issue_comment_url":"https://api.github.com/repos/galv/kaldi/issues/comments{/number}","contents_url":"https://api.github.com/repos/galv/kaldi/contents/{+path}","compare_url":"https://api.github.com/repos/galv/kaldi/compare/{base}...{head}","merges_url":"https://api.github.com/repos/galv/kaldi/merges","archive_url":"https://api.github.com/repos/galv/kaldi/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/galv/kaldi/downloads","issues_url":"https://api.github.com/repos/galv/kaldi/issues{/number}","pulls_url":"https://api.github.com/repos/galv/kaldi/pulls{/number}","milestones_url":"https://api.github.com/repos/galv/kaldi/milestones{/number}","notifications_url":"https://api.github.com/repos/galv/kaldi/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/galv/kaldi/labels{/name}","releases_url":"https://api.github.com/repos/galv/kaldi/releases{/id}","deployments_url":"https://api.github.com/repos/galv/kaldi/deployments","created_at":"2015-12-13T17:48:55Z","updated_at":"2015-12-13T17:49:05Z","pushed_at":"2016-05-27T00:54:59Z","git_url":"git://github.com/galv/kaldi.git","ssh_url":"git@github.com:galv/kaldi.git","clone_url":"https://github.com/galv/kaldi.git","svn_url":"https://github.com/galv/kaldi","homepage":"http://kaldi-asr.org","size":101613,"stargazers_count":0,"watchers_count":0,"language":"C++","has_issues":false,"has_downloads":true,"has_wiki":true,"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":"kaldi-asr:cudnn","ref":"cudnn","sha":"c9c5d5951a9498def68ac6f0e93f0f312cb9d988","user":{"login":"kaldi-asr","id":11650082,"avatar_url":"https://avatars.githubusercontent.com/u/11650082?v=3","gravatar_id":"","url":"https://api.github.com/users/kaldi-asr","html_url":"https://github.com/kaldi-asr","followers_url":"https://api.github.com/users/kaldi-asr/followers","following_url":"https://api.github.com/users/kaldi-asr/following{/other_user}","gists_url":"https://api.github.com/users/kaldi-asr/gists{/gist_id}","starred_url":"https://api.github.com/users/kaldi-asr/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaldi-asr/subscriptions","organizations_url":"https://api.github.com/users/kaldi-asr/orgs","repos_url":"https://api.github.com/users/kaldi-asr/repos","events_url":"https://api.github.com/users/kaldi-asr/events{/privacy}","received_events_url":"https://api.github.com/users/kaldi-asr/received_events","type":"Organization","site_admin":false},"repo":{"id":34275045,"name":"kaldi","full_name":"kaldi-asr/kaldi","owner":{"login":"kaldi-asr","id":11650082,"avatar_url":"https://avatars.githubusercontent.com/u/11650082?v=3","gravatar_id":"","url":"https://api.github.com/users/kaldi-asr","html_url":"https://github.com/kaldi-asr","followers_url":"https://api.github.com/users/kaldi-asr/followers","following_url":"https://api.github.com/users/kaldi-asr/following{/other_user}","gists_url":"https://api.github.com/users/kaldi-asr/gists{/gist_id}","starred_url":"https://api.github.com/users/kaldi-asr/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kaldi-asr/subscriptions","organizations_url":"https://api.github.com/users/kaldi-asr/orgs","repos_url":"https://api.github.com/users/kaldi-asr/repos","events_url":"https://api.github.com/users/kaldi-asr/events{/privacy}","received_events_url":"https://api.github.com/users/kaldi-asr/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/kaldi-asr/kaldi","description":"This is now the official location of the Kaldi project.","fork":false,"url":"https://api.github.com/repos/kaldi-asr/kaldi","forks_url":"https://api.github.com/repos/kaldi-asr/kaldi/forks","keys_url":"https://api.github.com/repos/kaldi-asr/kaldi/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kaldi-asr/kaldi/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kaldi-asr/kaldi/teams","hooks_url":"https://api.github.com/repos/kaldi-asr/kaldi/hooks","issue_events_url":"https://api.github.com/repos/kaldi-asr/kaldi/issues/events{/number}","events_url":"https://api.github.com/repos/kaldi-asr/kaldi/events","assignees_url":"https://api.github.com/repos/kaldi-asr/kaldi/assignees{/user}","branches_url":"https://api.github.com/repos/kaldi-asr/kaldi/branches{/branch}","tags_url":"https://api.github.com/repos/kaldi-asr/kaldi/tags","blobs_url":"https://api.github.com/repos/kaldi-asr/kaldi/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kaldi-asr/kaldi/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kaldi-asr/kaldi/git/refs{/sha}","trees_url":"https://api.github.com/repos/kaldi-asr/kaldi/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kaldi-asr/kaldi/statuses/{sha}","languages_url":"https://api.github.com/repos/kaldi-asr/kaldi/languages","stargazers_url":"https://api.github.com/repos/kaldi-asr/kaldi/stargazers","contributors_url":"https://api.github.com/repos/kaldi-asr/kaldi/contributors","subscribers_url":"https://api.github.com/repos/kaldi-asr/kaldi/subscribers","subscription_url":"https://api.github.com/repos/kaldi-asr/kaldi/subscription","commits_url":"https://api.github.com/repos/kaldi-asr/kaldi/commits{/sha}","git_commits_url":"https://api.github.com/repos/kaldi-asr/kaldi/git/commits{/sha}","comments_url":"https://api.github.com/repos/kaldi-asr/kaldi/comments{/number}","issue_comment_url":"https://api.github.com/repos/kaldi-asr/kaldi/issues/comments{/number}","contents_url":"https://api.github.com/repos/kaldi-asr/kaldi/contents/{+path}","compare_url":"https://api.github.com/repos/kaldi-asr/kaldi/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kaldi-asr/kaldi/merges","archive_url":"https://api.github.com/repos/kaldi-asr/kaldi/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kaldi-asr/kaldi/downloads","issues_url":"https://api.github.com/repos/kaldi-asr/kaldi/issues{/number}","pulls_url":"https://api.github.com/repos/kaldi-asr/kaldi/pulls{/number}","milestones_url":"https://api.github.com/repos/kaldi-asr/kaldi/milestones{/number}","notifications_url":"https://api.github.com/repos/kaldi-asr/kaldi/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kaldi-asr/kaldi/labels{/name}","releases_url":"https://api.github.com/repos/kaldi-asr/kaldi/releases{/id}","deployments_url":"https://api.github.com/repos/kaldi-asr/kaldi/deployments","created_at":"2015-04-20T17:23:16Z","updated_at":"2016-05-26T23:04:03Z","pushed_at":"2016-05-27T00:54:59Z","git_url":"git://github.com/kaldi-asr/kaldi.git","ssh_url":"git@github.com:kaldi-asr/kaldi.git","clone_url":"https://github.com/kaldi-asr/kaldi.git","svn_url":"https://github.com/kaldi-asr/kaldi","homepage":"http://kaldi-asr.org","size":117212,"stargazers_count":663,"watchers_count":663,"language":"C++","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":402,"mirror_url":null,"open_issues_count":69,"forks":402,"open_issues":69,"watchers":663,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/kaldi-asr/kaldi/pulls/770"},"html":{"href":"https://github.com/kaldi-asr/kaldi/pull/770"},"issue":{"href":"https://api.github.com/repos/kaldi-asr/kaldi/issues/770"},"comments":{"href":"https://api.github.com/repos/kaldi-asr/kaldi/issues/770/comments"},"review_comments":{"href":"https://api.github.com/repos/kaldi-asr/kaldi/pulls/770/comments"},"review_comment":{"href":"https://api.github.com/repos/kaldi-asr/kaldi/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/kaldi-asr/kaldi/pulls/770/commits"},"statuses":{"href":"https://api.github.com/repos/kaldi-asr/kaldi/statuses/714f05f26f811d0f1da2f289ece49fbcc6451f3c"}}}}
{"actor":{"display_login":"galv"}}