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