Home | History | Annotate | Download | only in kernels
      1 /* Copyright 2015 The TensorFlow Authors. All Rights Reserved.
      2 
      3 Licensed under the Apache License, Version 2.0 (the "License");
      4 you may not use this file except in compliance with the License.
      5 You may obtain a copy of the License at
      6 
      7     http://www.apache.org/licenses/LICENSE-2.0
      8 
      9 Unless required by applicable law or agreed to in writing, software
     10 distributed under the License is distributed on an "AS IS" BASIS,
     11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 See the License for the specific language governing permissions and
     13 limitations under the License.
     14 ==============================================================================*/
     15 
     16 #include "tensorflow/cc/ops/const_op.h"
     17 #include "tensorflow/cc/ops/image_ops.h"
     18 #include "tensorflow/cc/ops/nn_ops.h"
     19 #include "tensorflow/cc/ops/standard_ops.h"
     20 #include "tensorflow/core/common_runtime/kernel_benchmark_testlib.h"
     21 #include "tensorflow/core/framework/fake_input.h"
     22 #include "tensorflow/core/framework/node_def_builder.h"
     23 #include "tensorflow/core/framework/tensor.h"
     24 #include "tensorflow/core/framework/types.pb.h"
     25 #include "tensorflow/core/kernels/ops_testutil.h"
     26 #include "tensorflow/core/kernels/ops_util.h"
     27 #include "tensorflow/core/platform/test.h"
     28 #include "tensorflow/core/platform/test_benchmark.h"
     29 #include "tensorflow/core/public/session.h"
     30 
     31 #include "tensorflow/core/kernels/conv_ops_gpu.h"
     32 
     33 namespace tensorflow {
     34 
     35 #if GOOGLE_CUDA
     36 
     37 TEST(ConvParameters, WinogradNonfusedAlgoSize) {
     38   ConvParameters conv_params_small = {
     39       1,         // batch
     40       32,        // in_depths
     41       {{300,     // in_rows
     42         300}},   // in_cols
     43       128,       // out_depths
     44       {{3,       // filter_rows
     45         3}},     // filter_cols
     46       {{1,       // dilation_rows
     47         1}},     // dilation_cols
     48       {{1,       // stride_rows
     49         1}},     // stride_cols
     50       {{0,       // padding_rows
     51         0}},     // padding_cols
     52       DT_FLOAT,  // tensor datatype
     53       0,         // device_id
     54   };
     55   EXPECT_TRUE(conv_params_small.ShouldIncludeWinogradNonfusedAlgo<float>());
     56 
     57   ConvParameters conv_params_large = {
     58       1,         // batch
     59       128,       // in_depths
     60       {{300,     // in_rows
     61         300}},   // in_cols
     62       768,       // out_depths
     63       {{3,       // filter_rows
     64         3}},     // filter_cols
     65       {{1,       // dilation_rows
     66         1}},     // dilation_cols
     67       {{1,       // stride_rows
     68         1}},     // stride_cols
     69       {{0,       // padding_rows
     70         0}},     // padding_cols
     71       DT_FLOAT,  // tensor datatype
     72       0,         // device_id
     73   };
     74   EXPECT_FALSE(conv_params_large.ShouldIncludeWinogradNonfusedAlgo<float>());
     75 }
     76 
     77 #endif  // GOOGLE_CUDA
     78 
     79 class FusedResizePadConvOpTest : public OpsTestBase {
     80  protected:
     81   void HandwrittenConv() {
     82     const int stride = 1;
     83     TF_EXPECT_OK(NodeDefBuilder("fused_resize_op", "FusedResizeAndPadConv2D")
     84                      .Input(FakeInput(DT_FLOAT))
     85                      .Input(FakeInput(DT_INT32))
     86                      .Input(FakeInput(DT_INT32))
     87                      .Input(FakeInput(DT_FLOAT))
     88                      .Attr("T", DT_FLOAT)
     89                      .Attr("resize_align_corners", false)
     90                      .Attr("mode", "REFLECT")
     91                      .Attr("strides", {1, stride, stride, 1})
     92                      .Attr("padding", "SAME")
     93                      .Finalize(node_def()));
     94     TF_EXPECT_OK(InitOp());
     95     const int depth = 1;
     96     const int image_width = 4;
     97     const int image_height = 3;
     98     const int image_batch_count = 1;
     99     // The image matrix is:
    100     // |  1 |  2 |  3 |  4 |
    101     // |  5 |  6 |  7 |  8 |
    102     // |  9 | 10 | 11 | 12 |
    103     Tensor image(DT_FLOAT,
    104                  {image_batch_count, image_height, image_width, depth});
    105     test::FillValues<float>(&image, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12});
    106 
    107     // The filter matrix is:
    108     // | 1 | 4 | 7 |
    109     // | 2 | 5 | 8 |
    110     // | 3 | 6 | 9 |
    111     const int filter_size = 3;
    112     const int filter_count = 1;
    113     Tensor filter(DT_FLOAT, {filter_size, filter_size, depth, filter_count});
    114     test::FillValues<float>(&filter, {1, 4, 7, 2, 5, 8, 3, 6, 9});
    115 
    116     const int resized_width = image_width;
    117     const int resized_height = image_height;
    118 
    119     const int top_padding = 0;
    120     const int bottom_padding = 0;
    121     const int left_padding = 0;
    122     const int right_padding = 0;
    123 
    124     AddInputFromArray<float>(image.shape(), image.flat<float>());
    125     AddInputFromArray<int32>(TensorShape({2}), {resized_height, resized_width});
    126     AddInputFromArray<int32>(
    127         TensorShape({4, 2}),
    128         {0, 0, top_padding, bottom_padding, left_padding, right_padding, 0, 0});
    129     AddInputFromArray<float>(filter.shape(), filter.flat<float>());
    130     TF_ASSERT_OK(RunOpKernel());
    131 
    132     // We're sliding the 3x3 filter across the 3x4 image, with accesses outside
    133     // the input set to zero because we're using the 'SAME' padding mode.
    134     // The calculations behind the expected output are:
    135     // (1*0)+(4*0)+(7*0)+(2*0)+(5*1)+(8*2)+(3*0)+(6*5)+(9*6)=105
    136     // (1*0)+(4*0)+(7*0)+(2*1)+(5*2)+(8*3)+(3*5)+(6*6)+(9*7)=150
    137     // (1*0)+(4*0)+(7*0)+(2*2)+(5*3)+(8*4)+(3*6)+(6*7)+(9*8)=183
    138     // (1*0)+(4*0)+(7*0)+(2*3)+(5*4)+(8*0)+(3*7)+(6*8)+(9*0)=95
    139     // (1*0)+(4*1)+(7*2)+(2*0)+(5*5)+(8*6)+(3*0)+(6*9)+(9*10)=235
    140     // (1*1)+(4*2)+(7*3)+(2*5)+(5*6)+(8*7)+(3*9)+(6*10)+(9*11)=312
    141     // (1*2)+(4*3)+(7*4)+(2*6)+(5*7)+(8*8)+(3*10)+(6*11)+(9*12)=357
    142     // (1*3)+(4*4)+(7*0)+(2*7)+(5*8)+(8*0)+(3*11)+(6*12)+(9*0)=178
    143     // (1*0)+(4*5)+(7*6)+(2*0)+(5*9)+(8*10)+(3*0)+(6*0)+(9*0)=187
    144     // (1*5)+(4*6)+(7*7)+(2*9)+(5*10)+(8*11)+(3*0)+(6*0)+(9*0)=234
    145     // (1*6)+(4*7)+(7*8)+(2*10)+(5*11)+(8*12)+(3*0)+(6*0)+(9*0)=261
    146     // (1*7)+(4*11)+(7*0)+(2*8)+(5*12)+(8*0)+(3*0)+(6*0)+(9*0)=121
    147     // This means we should end up with this matrix:
    148     // |  105  |  150  |  183  |   95  |
    149     // |  235  |  312  |  357  |  178  |
    150     // |  187  |  234  |  261  |  121  |
    151     const int expected_width = image_width;
    152     const int expected_height = image_height * filter_count;
    153     Tensor expected(DT_FLOAT, TensorShape({image_batch_count, expected_height,
    154                                            expected_width, filter_count}));
    155     test::FillValues<float>(
    156         &expected, {105, 150, 183, 95, 235, 312, 357, 178, 187, 234, 261, 121});
    157     const Tensor& output = *GetOutput(0);
    158     test::ExpectTensorNear<float>(expected, output, 1e-5);
    159   }
    160 
    161   void CompareFusedAndSeparate(int input_width, int input_height,
    162                                int input_depth, int resize_width,
    163                                int resize_height, int y_padding, int x_padding,
    164                                int filter_size, int filter_count,
    165                                bool resize_align_corners,
    166                                const string& pad_mode, int stride,
    167                                const string& padding) {
    168     auto root = tensorflow::Scope::NewRootScope();
    169     using namespace ::tensorflow::ops;  // NOLINT(build/namespaces)
    170 
    171     Tensor input_data(DT_FLOAT,
    172                       TensorShape({1, input_height, input_width, input_depth}));
    173     test::FillIota<float>(&input_data, 1.0f);
    174     Output input =
    175         Const(root.WithOpName("input"), Input::Initializer(input_data));
    176 
    177     Tensor filter_data(DT_FLOAT, TensorShape({filter_size, filter_size,
    178                                               input_depth, filter_count}));
    179     test::FillIota<float>(&filter_data, 1.0f);
    180     Output filter =
    181         Const(root.WithOpName("filter"), Input::Initializer(filter_data));
    182 
    183     Output resize_size =
    184         Const(root.WithOpName("resize_size"), {resize_height, resize_width});
    185     Output resize =
    186         ResizeBilinear(root.WithOpName("resize"), input, resize_size,
    187                        ResizeBilinear::AlignCorners(resize_align_corners));
    188     Output paddings =
    189         Const(root.WithOpName("paddings"),
    190               {{0, 0}, {y_padding, y_padding}, {x_padding, x_padding}, {0, 0}});
    191     Output mirror_pad =
    192         MirrorPad(root.WithOpName("mirror_pad"), resize, paddings, pad_mode);
    193     Output conv = Conv2D(root.WithOpName("conv"), mirror_pad, filter,
    194                          {1, stride, stride, 1}, padding);
    195 
    196     Output fused_conv = FusedResizeAndPadConv2D(
    197         root.WithOpName("fused_conv"), input, resize_size, paddings, filter,
    198         pad_mode, {1, stride, stride, 1}, padding,
    199         FusedResizeAndPadConv2D::ResizeAlignCorners(resize_align_corners));
    200 
    201     tensorflow::GraphDef graph;
    202     TF_ASSERT_OK(root.ToGraphDef(&graph));
    203 
    204     std::unique_ptr<tensorflow::Session> session(
    205         tensorflow::NewSession(tensorflow::SessionOptions()));
    206     TF_ASSERT_OK(session->Create(graph));
    207 
    208     std::vector<Tensor> unfused_tensors;
    209     TF_ASSERT_OK(session->Run({}, {"conv"}, {}, &unfused_tensors));
    210 
    211     std::vector<Tensor> fused_tensors;
    212     TF_ASSERT_OK(session->Run({}, {"fused_conv"}, {}, &fused_tensors));
    213 
    214     test::ExpectTensorNear<float>(unfused_tensors[0], fused_tensors[0], 1e-5);
    215   }
    216 
    217   void CompareFusedPadOnlyAndSeparate(int input_width, int input_height,
    218                                       int input_depth, int y_padding,
    219                                       int x_padding, int filter_size,
    220                                       int filter_count, const string& pad_mode,
    221                                       int stride, const string& padding) {
    222     auto root = tensorflow::Scope::NewRootScope();
    223     using namespace ::tensorflow::ops;  // NOLINT(build/namespaces)
    224 
    225     Tensor input_data(DT_FLOAT,
    226                       TensorShape({1, input_height, input_width, input_depth}));
    227     test::FillIota<float>(&input_data, 1.0f);
    228     Output input =
    229         Const(root.WithOpName("input"), Input::Initializer(input_data));
    230 
    231     Tensor filter_data(DT_FLOAT, TensorShape({filter_size, filter_size,
    232                                               input_depth, filter_count}));
    233     test::FillIota<float>(&filter_data, 1.0f);
    234     Output filter =
    235         Const(root.WithOpName("filter"), Input::Initializer(filter_data));
    236 
    237     Output paddings =
    238         Const(root.WithOpName("paddings"),
    239               {{0, 0}, {y_padding, y_padding}, {x_padding, x_padding}, {0, 0}});
    240     Output mirror_pad =
    241         MirrorPad(root.WithOpName("mirror_pad"), input, paddings, pad_mode);
    242     Output conv = Conv2D(root.WithOpName("conv"), mirror_pad, filter,
    243                          {1, stride, stride, 1}, padding);
    244 
    245     Output fused_conv =
    246         FusedPadConv2D(root.WithOpName("fused_conv"), input, paddings, filter,
    247                        pad_mode, {1, stride, stride, 1}, padding);
    248 
    249     tensorflow::GraphDef graph;
    250     TF_ASSERT_OK(root.ToGraphDef(&graph));
    251 
    252     std::unique_ptr<tensorflow::Session> session(
    253         tensorflow::NewSession(tensorflow::SessionOptions()));
    254     TF_ASSERT_OK(session->Create(graph));
    255 
    256     std::vector<Tensor> unfused_tensors;
    257     TF_ASSERT_OK(session->Run({}, {"conv"}, {}, &unfused_tensors));
    258 
    259     std::vector<Tensor> fused_tensors;
    260     TF_ASSERT_OK(session->Run({}, {"fused_conv"}, {}, &fused_tensors));
    261 
    262     test::ExpectTensorNear<float>(unfused_tensors[0], fused_tensors[0], 1e-5);
    263   }
    264 };
    265 
    266 TEST_F(FusedResizePadConvOpTest, HandwrittenConv) { HandwrittenConv(); }
    267 
    268 TEST_F(FusedResizePadConvOpTest, IdentityComparative) {
    269   CompareFusedAndSeparate(10, 10, 1, 10, 10, 0, 0, 1, 1, false, "REFLECT", 1,
    270                           "SAME");
    271 }
    272 
    273 TEST_F(FusedResizePadConvOpTest, ConvOnlyComparative) {
    274   CompareFusedAndSeparate(10, 10, 3, 10, 10, 0, 0, 4, 4, false, "REFLECT", 1,
    275                           "SAME");
    276 }
    277 
    278 TEST_F(FusedResizePadConvOpTest, ResizeOnlyComparative) {
    279   CompareFusedAndSeparate(10, 10, 1, 20, 20, 0, 0, 1, 1, false, "REFLECT", 1,
    280                           "SAME");
    281 }
    282 
    283 TEST_F(FusedResizePadConvOpTest, ResizeAndConvComparative) {
    284   CompareFusedAndSeparate(2, 2, 4, 4, 2, 0, 0, 2, 2, false, "REFLECT", 1,
    285                           "SAME");
    286 }
    287 
    288 TEST_F(FusedResizePadConvOpTest, ResizeAlignAndConvComparative) {
    289   CompareFusedAndSeparate(2, 2, 4, 4, 2, 0, 0, 2, 2, true, "REFLECT", 1,
    290                           "SAME");
    291 }
    292 
    293 TEST_F(FusedResizePadConvOpTest, ResizeAndConvStridedComparative) {
    294   CompareFusedAndSeparate(2, 2, 4, 4, 2, 0, 0, 2, 2, false, "REFLECT", 2,
    295                           "SAME");
    296 }
    297 
    298 TEST_F(FusedResizePadConvOpTest, ResizeAlignAndConvValidComparative) {
    299   CompareFusedAndSeparate(2, 2, 4, 4, 2, 0, 0, 2, 2, true, "REFLECT", 1,
    300                           "VALID");
    301 }
    302 
    303 TEST_F(FusedResizePadConvOpTest, PadOnlyComparative) {
    304   CompareFusedAndSeparate(4, 4, 1, 4, 4, 2, 2, 1, 1, false, "REFLECT", 1,
    305                           "SAME");
    306 }
    307 
    308 TEST_F(FusedResizePadConvOpTest, PadOnlyWithChannelsComparative) {
    309   CompareFusedAndSeparate(4, 4, 3, 4, 4, 2, 2, 1, 1, false, "REFLECT", 1,
    310                           "SAME");
    311 }
    312 
    313 TEST_F(FusedResizePadConvOpTest, ResizeAndPadComparative) {
    314   CompareFusedAndSeparate(4, 4, 1, 6, 6, 2, 2, 1, 1, false, "REFLECT", 1,
    315                           "SAME");
    316 }
    317 
    318 TEST_F(FusedResizePadConvOpTest, PadOnlySymmetricComparative) {
    319   CompareFusedAndSeparate(4, 4, 1, 4, 4, 2, 2, 1, 1, false, "SYMMETRIC", 1,
    320                           "SAME");
    321 }
    322 
    323 TEST_F(FusedResizePadConvOpTest, ResizeAndPadSymmetricComparative) {
    324   CompareFusedAndSeparate(4, 4, 3, 6, 6, 2, 2, 1, 1, false, "SYMMETRIC", 1,
    325                           "SAME");
    326 }
    327 
    328 TEST_F(FusedResizePadConvOpTest, NoResizeIdentityComparative) {
    329   CompareFusedPadOnlyAndSeparate(10, 10, 1, 0, 0, 1, 1, "REFLECT", 1, "SAME");
    330 }
    331 
    332 TEST_F(FusedResizePadConvOpTest, NoResizeConvOnlyComparative) {
    333   CompareFusedPadOnlyAndSeparate(10, 10, 3, 0, 0, 4, 4, "REFLECT", 1, "SAME");
    334 }
    335 
    336 TEST_F(FusedResizePadConvOpTest, NoResizePadOnlyComparative) {
    337   CompareFusedPadOnlyAndSeparate(4, 4, 1, 2, 2, 1, 1, "REFLECT", 1, "SAME");
    338 }
    339 
    340 TEST_F(FusedResizePadConvOpTest, NoResizePadOnlyWithChannelsComparative) {
    341   CompareFusedPadOnlyAndSeparate(4, 4, 3, 2, 2, 1, 1, "REFLECT", 1, "SAME");
    342 }
    343 
    344 TEST_F(FusedResizePadConvOpTest, NoResizePadOnlySymmetricComparative) {
    345   CompareFusedPadOnlyAndSeparate(4, 4, 1, 2, 2, 1, 1, "SYMMETRIC", 1, "SAME");
    346 }
    347 
    348 TEST_F(FusedResizePadConvOpTest, ResizeAndPadSymmetricComparativeLarge) {
    349   CompareFusedAndSeparate(1000, 1000, 3, 1006, 1006, 2, 2, 1, 1, false,
    350                           "SYMMETRIC", 1, "SAME");
    351 }
    352 
    353 class ConvOpTest : public OpsTestBase {
    354  protected:
    355   void HandwrittenConv() {
    356     const int stride = 1;
    357     TF_EXPECT_OK(NodeDefBuilder("conv_op", "Conv2D")
    358                      .Input(FakeInput(DT_FLOAT))
    359                      .Input(FakeInput(DT_FLOAT))
    360                      .Attr("T", DT_FLOAT)
    361                      .Attr("strides", {1, stride, stride, 1})
    362                      .Attr("padding", "SAME")
    363                      .Finalize(node_def()));
    364     TF_EXPECT_OK(InitOp());
    365     const int depth = 1;
    366     const int image_width = 4;
    367     const int image_height = 3;
    368     const int image_batch_count = 1;
    369     // The image matrix is:
    370     // |  1 |  2 |  3 |  4 |
    371     // |  5 |  6 |  7 |  8 |
    372     // |  9 | 10 | 11 | 12 |
    373     Tensor image(DT_FLOAT,
    374                  {image_batch_count, image_height, image_width, depth});
    375     test::FillValues<float>(&image, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12});
    376 
    377     // The filter matrix is:
    378     // | 1 | 4 | 7 |
    379     // | 2 | 5 | 8 |
    380     // | 3 | 6 | 9 |
    381     const int filter_size = 3;
    382     const int filter_count = 1;
    383     Tensor filter(DT_FLOAT, {filter_size, filter_size, depth, filter_count});
    384     test::FillValues<float>(&filter, {1, 4, 7, 2, 5, 8, 3, 6, 9});
    385 
    386     AddInputFromArray<float>(image.shape(), image.flat<float>());
    387     AddInputFromArray<float>(filter.shape(), filter.flat<float>());
    388     TF_ASSERT_OK(RunOpKernel());
    389 
    390     // We're sliding the 3x3 filter across the 3x4 image, with accesses outside
    391     // the input set to zero because we're using the 'SAME' padding mode.
    392     // The calculations behind the expected output are:
    393     // (1*0)+(4*0)+(7*0)+(2*0)+(5*1)+(8*2)+(3*0)+(6*5)+(9*6)=105
    394     // (1*0)+(4*0)+(7*0)+(2*1)+(5*2)+(8*3)+(3*5)+(6*6)+(9*7)=150
    395     // (1*0)+(4*0)+(7*0)+(2*2)+(5*3)+(8*4)+(3*6)+(6*7)+(9*8)=183
    396     // (1*0)+(4*0)+(7*0)+(2*3)+(5*4)+(8*0)+(3*7)+(6*8)+(9*0)=95
    397     // (1*0)+(4*1)+(7*2)+(2*0)+(5*5)+(8*6)+(3*0)+(6*9)+(9*10)=235
    398     // (1*1)+(4*2)+(7*3)+(2*5)+(5*6)+(8*7)+(3*9)+(6*10)+(9*11)=312
    399     // (1*2)+(4*3)+(7*4)+(2*6)+(5*7)+(8*8)+(3*10)+(6*11)+(9*12)=357
    400     // (1*3)+(4*4)+(7*0)+(2*7)+(5*8)+(8*0)+(3*11)+(6*12)+(9*0)=178
    401     // (1*0)+(4*5)+(7*6)+(2*0)+(5*9)+(8*10)+(3*0)+(6*0)+(9*0)=187
    402     // (1*5)+(4*6)+(7*7)+(2*9)+(5*10)+(8*11)+(3*0)+(6*0)+(9*0)=234
    403     // (1*6)+(4*7)+(7*8)+(2*10)+(5*11)+(8*12)+(3*0)+(6*0)+(9*0)=261
    404     // (1*7)+(4*11)+(7*0)+(2*8)+(5*12)+(8*0)+(3*0)+(6*0)+(9*0)=121
    405     // This means we should end up with this matrix:
    406     // |  105  |  150  |  183  |   95  |
    407     // |  235  |  312  |  357  |  178  |
    408     // |  187  |  234  |  261  |  121  |
    409     const int expected_width = image_width;
    410     const int expected_height = image_height * filter_count;
    411     Tensor expected(DT_FLOAT, TensorShape({image_batch_count, expected_height,
    412                                            expected_width, filter_count}));
    413     test::FillValues<float>(
    414         &expected, {105, 150, 183, 95, 235, 312, 357, 178, 187, 234, 261, 121});
    415     const Tensor& output = *GetOutput(0);
    416     test::ExpectTensorNear<float>(expected, output, 1e-5);
    417   }
    418 
    419   void AnisotropicStrides() {
    420     const int stride_width = 3;
    421     const int stride_height = 1;
    422     TF_EXPECT_OK(NodeDefBuilder("conv_op", "Conv2D")
    423                      .Input(FakeInput(DT_FLOAT))
    424                      .Input(FakeInput(DT_FLOAT))
    425                      .Attr("T", DT_FLOAT)
    426                      .Attr("strides", {1, stride_height, stride_width, 1})
    427                      .Attr("padding", "VALID")
    428                      .Finalize(node_def()));
    429     TF_EXPECT_OK(InitOp());
    430     const int depth = 1;
    431     const int image_width = 6;
    432     const int image_height = 3;
    433     const int image_batch_count = 1;
    434     Tensor image(DT_FLOAT,
    435                  {image_batch_count, image_height, image_width, depth});
    436     test::FillValues<float>(&image, {
    437                                         3, 2, 1, -1, -2, -3,  //
    438                                         4, 3, 2, -2, -3, -4,  //
    439                                         5, 4, 3, -3, -4, -5,  //
    440                                     });
    441     const int filter_size = 2;
    442     const int filter_count = 1;
    443     Tensor filter(DT_FLOAT, {filter_size, filter_size, depth, filter_count});
    444     test::FillValues<float>(&filter, {
    445                                          1, 2,  //
    446                                          3, 4,  //
    447                                      });
    448 
    449     AddInputFromArray<float>(image.shape(), image.flat<float>());
    450     AddInputFromArray<float>(filter.shape(), filter.flat<float>());
    451     TF_ASSERT_OK(RunOpKernel());
    452 
    453     const int expected_width = 2;
    454     const int expected_height = 2;
    455     Tensor expected(DT_FLOAT, TensorShape({image_batch_count, expected_height,
    456                                            expected_width, filter_count}));
    457     test::FillValues<float>(&expected, {31, -23, 41, -33});
    458     const Tensor& output = *GetOutput(0);
    459     test::ExpectTensorNear<float>(expected, output, 1e-5);
    460   }
    461 };
    462 
    463 TEST_F(ConvOpTest, HandwrittenConv) { HandwrittenConv(); }
    464 
    465 TEST_F(ConvOpTest, AnisotropicStride) { AnisotropicStrides(); }
    466 
    467 }  // namespace tensorflow
    468