Compute Library
 22.05
GEMM.cpp
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2017-2022 Arm Limited.
3  *
4  * SPDX-License-Identifier: MIT
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to
8  * deal in the Software without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in all
14  * copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22  * SOFTWARE.
23  */
24 #include "arm_compute/core/Types.h"
33 #include "tests/NEON/Accessor.h"
34 #include "tests/NEON/Helper.h"
36 #include "tests/datasets/LargeGEMMDataset.h"
37 #include "tests/datasets/SmallGEMMDataset.h"
38 #include "tests/datasets/TinyGEMMDataset.h"
40 #include "tests/framework/Macros.h"
43 #include "tests/validation/fixtures/GEMMFixture.h"
44 #include "tests/validation/fixtures/GEMMInterleave4x4Fixture.h"
45 #include "tests/validation/fixtures/GEMMTranspose1xWFixture.h"
46 
47 namespace arm_compute
48 {
49 namespace test
50 {
51 namespace validation
52 {
53 namespace
54 {
55 constexpr AbsoluteTolerance<float> tolerance_f(0.001f); /**< Tolerance value for comparing reference's output against implementation's output for FP32 data types */
56 #ifdef __ARM_FEATURE_FP16_VECTOR_ARITHMETIC
57 RelativeTolerance<half_float::half> rel_tolerance_f16(half(0.2)); /**< Relative tolerance value for comparing reference's output against implementation's output for FP16 data types */
58 const AbsoluteTolerance<float> abs_tolerance_f16(0.2f); /**< Absolute tolerance value for comparing reference's output against implementation's output for FP16 data types */
59 constexpr float tolerance_num = 0.07f; /**< Tolerance number for FP16 data types */
60 #endif /* __ARM_FEATURE_FP16_VECTOR_ARITHMETIC */
61 /** CNN data types */
62 const auto CNNDataTypes = framework::dataset::make("DataType",
63 {
64 #ifdef __ARM_FEATURE_FP16_VECTOR_ARITHMETIC
66 #endif /* __ARM_FEATURE_FP16_VECTOR_ARITHMETIC */
68 });
69 
70 const auto data_interleave = framework::dataset::make("M", 8, 12) * framework::dataset::make("N", 8, 12);
71 const auto data_transpose = framework::dataset::make("M", 8, 14) * framework::dataset::make("N", 7, 14);
72 
73 /** Zero padding test */
74 template <typename FunctionType>
75 bool validate_zero_padding(unsigned int dim0_value, unsigned int dim1_value)
76 {
77  const TensorShape in_shape(dim0_value, dim1_value);
78  TensorInfo in(in_shape, 1, DataType::U32);
79  TensorInfo dst;
80 
82 
83  // Validate zero-padding
84  FunctionType func;
85 
86  func.configure(&in, &dst);
87 
88  return in.padding().empty();
89 }
90 
91 /* Zero padding test for GEMM kernels */
92 bool validate_gemm_zero_padding(const TensorShape shape0, const TensorShape shape1)
93 {
94  // Create tensors
95  TensorInfo in0(shape0, 1, DataType::F32);
96  TensorInfo in1(shape1, 1, DataType::F32);
97  TensorInfo dst;
98 
99  // Validate zero-padding
100  cpu::kernels::CpuGemmMatrixMultiplyKernel gemm;
101  gemm.configure(&in0, &in1, &dst, 1.0, false);
102 
103  return in0.padding().empty() && in1.padding().empty() && dst.padding().empty();
104 }
105 } // namespace
106 
107 TEST_SUITE(NEON)
108 TEST_SUITE(GEMM)
109 
110 /** Test case for memory injection in @ref cpu::CpuGemm.
111  *
112  * Configure the operator once and inject memory at run-time in multiple executions.
113  *
114  * Checks performed in order:
115  * - Both runs compute the same output
116  */
117 TEST_CASE(MemoryInjection, framework::DatasetMode::ALL)
118 {
119  auto gemm = std::make_unique<cpu::CpuGemm>();
120  const auto lhs_info = TensorInfo(TensorShape(3U, 3U), 1, DataType::F32);
121  const auto rhs_info = TensorInfo(TensorShape(4U, 3U), 1, DataType::F32);
122  const auto c_info = TensorInfo(TensorShape(4U, 3U), 1, DataType::F32);
123  auto dst_info = TensorInfo(TensorShape(4U, 3U), 1, DataType::F32);
124  const auto gemm_info = GEMMInfo{};
125  gemm->configure(&lhs_info, &rhs_info, &c_info, &dst_info, 1.f, 1.f, gemm_info);
126 
127  // telhs are newly created every call of this lambda function
128  auto lhs = create_tensor<Tensor>(lhs_info);
129  auto rhs = create_tensor<Tensor>(rhs_info);
130  auto c = create_tensor<Tensor>(c_info);
131  lhs.allocator()->allocate();
132  rhs.allocator()->allocate();
133  c.allocator()->allocate();
134 
135  ITensorPack run_pack{ { TensorType::ACL_SRC_0, &lhs }, { TensorType::ACL_SRC_1, &rhs }, { TensorType::ACL_SRC_2, &c } };
136  ITensorPack prep_pack{ { TensorType::ACL_SRC_1, &rhs }, { TensorType::ACL_SRC_2, &c } };
137 
138  auto mg = MemoryGroup{};
139  auto ws = manage_workspace<Tensor>(gemm->workspace(), mg, run_pack, prep_pack);
140 
141  auto run_conv = [&]() -> Tensor
142  {
143  auto dst = create_tensor<Tensor>(dst_info);
144  dst.allocator()->allocate();
145  run_pack.add_tensor(TensorType::ACL_DST, &dst);
146 
147  library->fill_tensor_value(Accessor(lhs), 1.f);
148  library->fill_tensor_value(Accessor(rhs), 2.f);
149  library->fill_tensor_value(Accessor(c), 3.f);
150  // This operator is configured once and captured by this lambda.
151  gemm->prepare(prep_pack);
152  gemm->run(run_pack);
153  return dst;
154  };
155  auto result_0 = run_conv();
156  auto result_1 = run_conv();
157  for(size_t i = 0; i < result_0.info()->tensor_shape().total_size(); ++i)
158  {
159  ARM_COMPUTE_EXPECT(((float *)result_0.buffer())[i] == ((float *)result_1.buffer())[i], framework::LogLevel::ERRORS);
160  }
161 }
162 
163 /** Test case for memory injection in @ref NEGEMM.
164  *
165  * Make sure @ref NEGEMM still works through injecting the memory at configure time using the old API.
166  *
167  * Checks performed in order:
168  * - Both runs compute the same output
169  */
170 TEST_CASE(MultipleExecutionWithConfigure, framework::DatasetMode::ALL)
171 {
172  auto gemm = std::make_unique<NEGEMM>();
173  const auto lhs_info = TensorInfo(TensorShape(3U, 3U), 1, DataType::F32);
174  const auto rhs_info = TensorInfo(TensorShape(4U, 3U), 1, DataType::F32);
175  const auto c_info = TensorInfo(TensorShape(4U, 3U), 1, DataType::F32);
176  auto dst_info = TensorInfo(TensorShape(4U, 3U), 1, DataType::F32);
177  const auto gemm_info = GEMMInfo{};
178  auto run_conv = [&]()
179  {
180  auto lhs = create_tensor<Tensor>(lhs_info);
181  auto rhs = create_tensor<Tensor>(rhs_info);
182  auto c = create_tensor<Tensor>(c_info);
183  auto dst = create_tensor<Tensor>(dst_info);
184  gemm->configure(&lhs, &rhs, &c, &dst, 1.f, 1.f, gemm_info);
185  lhs.allocator()->allocate();
186  rhs.allocator()->allocate();
187  c.allocator()->allocate();
188  dst.allocator()->allocate();
189  library->fill_tensor_value(Accessor(lhs), 1.f);
190  library->fill_tensor_value(Accessor(rhs), 2.f);
191  library->fill_tensor_value(Accessor(c), 3.f);
192  gemm->run();
193  return dst;
194  };
195  auto result_0 = run_conv();
196  auto result_1 = run_conv();
197  for(size_t i = 0; i < result_0.info()->tensor_shape().total_size(); ++i)
198  {
199  ARM_COMPUTE_EXPECT(((float *)result_0.buffer())[i] == ((float *)result_1.buffer())[i], framework::LogLevel::ERRORS);
200  }
201 }
202 
203 TEST_SUITE(KERNEL_SELECTION)
204 DATA_TEST_CASE(KernelSelection_mul_and_add, framework::DatasetMode::ALL,
205  combine(framework::dataset::make("CpuExt", std::string("NEON")),
206  framework::dataset::make("DataType", { DataType::F32,
208  })),
210 {
211  using namespace cpu::kernels;
212 
214  cpu_isa.neon = (cpu_ext == "NEON");
216 
217  const auto *selected_impl_mul = CpuGemmMatrixMultiplyKernel::get_implementation(DataTypeISASelectorData{ data_type, cpu_isa }, cpu::KernelSelectionType::Preferred);
218 
219  ARM_COMPUTE_ERROR_ON_NULLPTR(selected_impl_mul);
220 
221  std::string expected = lower_string(cpu_ext) + "_" + cpu_impl_dt(data_type) + "_gemm_matrix_mul";
222  std::string actual = selected_impl_mul->name;
223 
225 
226  const auto *selected_impl_add = CpuGemmMatrixAdditionKernel::get_implementation(DataTypeISASelectorData{ data_type, cpu_isa }, cpu::KernelSelectionType::Preferred);
227 
228  ARM_COMPUTE_ERROR_ON_NULLPTR(selected_impl_add);
229 
230  expected = lower_string(cpu_ext) + "_" + cpu_impl_dt(data_type) + "_gemm_matrix_add";
231  actual = selected_impl_add->name;
232 
234 }
235 TEST_SUITE_END() // KERNEL_SELECTION
236 
237 TEST_SUITE(TRANSPOSE_1XW)
240  framework::dataset::make("N", { 1, 23, 63, 101 }),
241  framework::dataset::make("K", { 1, 47, 29, 27 })),
242  n_value, k_value)
243 {
244  bool status = validate_zero_padding<CpuGemmTranspose1xW>(n_value, k_value);
246 }
247 
249 using CpuGemmTranspose1xWFixture = GEMMTranspose1xWValidationFixture<Tensor, Accessor, CpuGemmTranspose1xW, uint32_t>;
250 FIXTURE_DATA_TEST_CASE(RunSmall, CpuGemmTranspose1xWFixture, framework::DatasetMode::PRECOMMIT, data_transpose * framework::dataset::make("DataType", DataType::U32))
251 {
252  // Validate output
253  validate(Accessor(_target), _reference);
254 }
255 TEST_SUITE_END() // U32
256 
258 using CpuGemmTranspose1xWFixture = GEMMTranspose1xWValidationFixture<Tensor, Accessor, CpuGemmTranspose1xW, uint16_t>;
260 {
261  // Validate output
262  validate(Accessor(_target), _reference);
263 }
264 TEST_SUITE_END() // U16
265 
266 TEST_SUITE(U8)
267 using CpuGemmTranspose1xWFixture = GEMMTranspose1xWValidationFixture<Tensor, Accessor, CpuGemmTranspose1xW, uint8_t>;
269 {
270  // Validate output
271  validate(Accessor(_target), _reference);
272 }
273 TEST_SUITE_END() // U8
274 
275 TEST_SUITE_END() // TRANSPOSE_1XW
276 
277 TEST_SUITE(INTERLEAVE_4X4)
279 
281  framework::dataset::make("M", { 1, 23, 63, 101 }),
282  framework::dataset::make("K", { 1, 47, 29, 27 })),
283  m_value, k_value)
284 {
285  bool status = validate_zero_padding<cpu::kernels::CpuGemmInterleave4x4Kernel>(m_value, k_value);
287 }
288 
290 using CpuGemmInterleave4x4Fixture = GEMMInterleave4x4ValidationFixture<Tensor, Accessor, CpuGemmInterleave4x4, uint32_t>;
291 FIXTURE_DATA_TEST_CASE(RunSmall, CpuGemmInterleave4x4Fixture, framework::DatasetMode::PRECOMMIT, data_interleave * framework::dataset::make("DataType", DataType::U32))
292 {
293  // Validate output
294  validate(Accessor(_target), _reference);
295 }
296 TEST_SUITE_END() // U32
297 
299 using CpuGemmInterleave4x4Fixture = GEMMInterleave4x4ValidationFixture<Tensor, Accessor, CpuGemmInterleave4x4, uint16_t>;
301 {
302  // Validate output
303  validate(Accessor(_target), _reference);
304 }
305 TEST_SUITE_END() // U16
306 
307 TEST_SUITE(U8)
308 using CpuGemmInterleave4x4Fixture = GEMMInterleave4x4ValidationFixture<Tensor, Accessor, CpuGemmInterleave4x4, uint8_t>;
310 {
311  // Validate output
312  validate(Accessor(_target), _reference);
313 }
314 TEST_SUITE_END() // U8
315 
316 TEST_SUITE_END() // INTERLEAVE_4X4
317 
318 template <typename T>
319 using NEGEMMFixture = GEMMValidationFixture<Tensor, Accessor, NEGEMM, T>;
320 
321 template <typename T>
322 using NEGEMMFixtureDisabledC = GEMMValidationFixture<Tensor, Accessor, NEGEMM, T, true>;
323 
324 TEST_SUITE(Float)
325 DATA_TEST_CASE(ValidateZeroPadding, framework::DatasetMode::ALL, zip(framework::dataset::make("In0", { TensorShape(21U, 13U),
326  TensorShape(31U, 1U),
327  TensorShape(31U, 1U),
328  TensorShape(8U, 2U),
329  TensorShape(38U, 12U),
330  TensorShape(32U, 1U)
331  }),
332  framework::dataset::make("In1", { TensorShape(33U, 21U),
333  TensorShape(23U, 31U),
334  TensorShape(23U, 31U),
335  TensorShape(16U, 8U),
336  TensorShape(21U, 38U),
337  TensorShape(17U, 32U)
338  })),
339  shape0, shape1)
340 {
341  bool status = validate_gemm_zero_padding(shape0, shape1);
343 }
344 
345 #ifdef __ARM_FEATURE_FP16_VECTOR_ARITHMETIC
346 TEST_SUITE(FP16)
347 FIXTURE_DATA_TEST_CASE(RunSmall, NEGEMMFixture<half>, framework::DatasetMode::PRECOMMIT, combine(combine(datasets::SmallGEMMDataset(),
348  framework::dataset::make("ReshapeWeights", { true, false })),
350 {
351  // Validate output
352  validate(Accessor(_target), _reference, rel_tolerance_f16, tolerance_num, abs_tolerance_f16);
353 }
354 FIXTURE_DATA_TEST_CASE(RunLarge, NEGEMMFixture<half>, framework::DatasetMode::NIGHTLY, combine(combine(datasets::LargeGEMMDataset(),
355  framework::dataset::make("ReshapeWeights", { true, false })),
356 
358 {
359  // Validate output
360  validate(Accessor(_target), _reference, rel_tolerance_f16, tolerance_num, abs_tolerance_f16);
361 }
363 #endif /* __ARM_FEATURE_FP16_VECTOR_ARITHMETIC */
364 
365 TEST_SUITE(FP32)
366 FIXTURE_DATA_TEST_CASE(RunSmall, NEGEMMFixture<float>, framework::DatasetMode::PRECOMMIT, combine(combine(datasets::SmallGEMMDataset(),
367  framework::dataset::make("ReshapeWeights", { true, false })),
368 
370 {
371  // Validate output
372  validate(Accessor(_target), _reference, tolerance_f);
373 }
374 FIXTURE_DATA_TEST_CASE(RunLarge, NEGEMMFixture<float>, framework::DatasetMode::NIGHTLY, combine(combine(datasets::LargeGEMMDataset(),
375  framework::dataset::make("ReshapeWeights", { true, false })),
376 
378 {
379  // Validate output
380  validate(Accessor(_target), _reference, tolerance_f);
381 }
382 TEST_SUITE(DisabledC)
383 FIXTURE_DATA_TEST_CASE(RunSmall, NEGEMMFixtureDisabledC<float>, framework::DatasetMode::PRECOMMIT, combine(combine(datasets::SmallGEMMDataset(),
384  framework::dataset::make("ReshapeWeights", { true, false })),
385 
387 {
388  // Validate output
389  validate(Accessor(_target), _reference, tolerance_f);
390 }
392 
393 TEST_SUITE(BatchedGEMMDisabledC)
394 FIXTURE_DATA_TEST_CASE(RunSmall, NEGEMMFixtureDisabledC<float>, framework::DatasetMode::PRECOMMIT, combine(combine(datasets::SmallBatchedGEMMDataset(),
395  framework::dataset::make("ReshapeWeights", { true, false })),
396 
398 {
399  // Validate output
400  validate(Accessor(_target), _reference, tolerance_f);
401 }
403 
406 
409 } // namespace validation
410 } // namespace test
411 } // namespace arm_compute
Retrieve the best implementation available for the given Cpu ISA, ignoring the build flags...
Shape of a tensor.
Definition: TensorShape.h:39
GEMMTranspose1xWValidationFixture< Tensor, Accessor, CpuGemmTranspose1xW, uint32_t > CpuGemmTranspose1xWFixture
Definition: GEMM.cpp:249
1 channel, 1 U8 per channel
half_float::half half
16-bit floating point type
Definition: Types.h:48
1 channel, 1 F32 per channel
ARM_COMPUTE_EXPECT(has_error==expected, framework::LogLevel::ERRORS)
As above but this also setups a Zero border on the input tensor of the kernel&#39;s bordersize.
Definition: Helper.h:109
1 channel, 1 U16 per channel
std::enable_if< is_container< T >::value, ContainerDataset< T > >::type make(std::string name, T &&values)
Helper function to create a ContainerDataset.
GEMMValidationFixture< Tensor, Accessor, NEGEMM, T, true > NEGEMMFixtureDisabledC
Definition: GEMM.cpp:322
template SimpleTensor< half > gemm(const SimpleTensor< half > &a, const SimpleTensor< half > &b, const SimpleTensor< half > &c, float alpha, float beta)
std::string lower_string(const std::string &val)
Lower a given string.
Definition: Utils.cpp:351
Copyright (c) 2017-2022 Arm Limited.
cpuinfo::CpuIsaInfo cpu_isa
Definition: Cast.cpp:207
std::string cpu_impl_dt(const DataType &data_type)
Returns the suffix string of CPU kernel implementation names based on the given data type...
Definition: Utils.h:1245
1 channel, 1 F16 per channel
CPU ISA (Instruction Set Architecture) information.
Definition: CpuIsaInfo.h:37
DATA_TEST_CASE(Validate, framework::DatasetMode::ALL, zip(zip(zip(framework::dataset::make("InputInfo", { TensorInfo(TensorShape(27U, 13U, 2U), 1, DataType::F32), TensorInfo(TensorShape(27U, 13U, 2U), 1, DataType::F32), TensorInfo(TensorShape(32U, 13U, 2U), 1, DataType::F32), TensorInfo(TensorShape(32U, 13U, 2U), 1, DataType::QASYMM8), TensorInfo(TensorShape(27U, 13U, 2U), 1, DataType::QASYMM8), TensorInfo(TensorShape(27U, 13U, 2U), 1, DataType::F32), TensorInfo(TensorShape(32U, 13U, 2U), 1, DataType::QSYMM16), TensorInfo(TensorShape(32U, 13U, 2U), 1, DataType::QSYMM16), TensorInfo(TensorShape(32U, 13U, 2U), 1, DataType::QSYMM16), }), framework::dataset::make("OutputInfo",{ TensorInfo(TensorShape(27U, 13U, 2U), 1, DataType::F16), TensorInfo(TensorShape(27U, 13U, 2U), 1, DataType::F32), TensorInfo(TensorShape(32U, 13U, 2U), 1, DataType::F32), TensorInfo(TensorShape(32U, 13U, 2U), 1, DataType::QASYMM8), TensorInfo(TensorShape(27U, 13U, 2U), 1, DataType::QASYMM8), TensorInfo(TensorShape(30U, 11U, 2U), 1, DataType::F32), TensorInfo(TensorShape(32U, 13U, 2U), 1, DataType::QSYMM16, QuantizationInfo(1.f/32768.f, 0)), TensorInfo(TensorShape(32U, 13U, 2U), 1, DataType::QSYMM16, QuantizationInfo(1.f/32768.f, 0)), TensorInfo(TensorShape(32U, 13U, 2U), 1, DataType::QSYMM16, QuantizationInfo(1.f/32768.f, 0)), })), framework::dataset::make("ActivationInfo", { ActivationLayerInfo(ActivationLayerInfo::ActivationFunction::RELU), ActivationLayerInfo(ActivationLayerInfo::ActivationFunction::RELU), ActivationLayerInfo(ActivationLayerInfo::ActivationFunction::RELU), ActivationLayerInfo(ActivationLayerInfo::ActivationFunction::LU_BOUNDED_RELU), ActivationLayerInfo(ActivationLayerInfo::ActivationFunction::TANH), ActivationLayerInfo(ActivationLayerInfo::ActivationFunction::RELU), ActivationLayerInfo(ActivationLayerInfo::ActivationFunction::TANH), ActivationLayerInfo(ActivationLayerInfo::ActivationFunction::LOGISTIC), ActivationLayerInfo(ActivationLayerInfo::ActivationFunction::SQRT), })), framework::dataset::make("Expected", { false, true, true, true, false, false, true, true, false })), input_info, output_info, act_info, expected)
Accessor implementation for Tensor objects.
Definition: Accessor.h:35
DatasetMode
Possible dataset modes.
Definition: DatasetModes.h:40
std::unique_ptr< AssetsLibrary > library
Definition: main.cpp:76
1 channel, 1 U32 per channel
TEST_SUITE_END() FIXTURE_DATA_TEST_CASE(RunSmall
[CLActivationLayer Test snippet]
quantized, asymmetric fixed-point 8-bit number unsigned
Basic implementation of the tensor interface.
Definition: Tensor.h:37
validate(CLAccessor(output_state), expected_output)
FIXTURE_DATA_TEST_CASE(RunSmall, CLAbsLayerFixture< half >, framework::DatasetMode::PRECOMMIT, combine(datasets::SmallShapes(), framework::dataset::make("DataType", DataType::F16)))
Definition: AbsLayer.cpp:50
GEMMInterleave4x4ValidationFixture< Tensor, Accessor, CpuGemmInterleave4x4, uint32_t > CpuGemmInterleave4x4Fixture
Definition: GEMM.cpp:290
ARM_COMPUTE_ERROR_ON_NULLPTR(selected_impl)
ARM_COMPUTE_EXPECT_EQUAL(expected, actual, framework::LogLevel::ERRORS)
TEST_CASE(FusedActivation, framework::DatasetMode::ALL)
Validate fused activation expecting the following behaviours:
zip(zip(framework::dataset::make("Weights", { TensorInfo(TensorShape(32U, 13U, 2U, 2U), 1, DataType::F32), TensorInfo(TensorShape(32U, 13U, 2U, 2U), 1, DataType::F32), TensorInfo(TensorShape(32U, 13U, 2U, 1U), 1, DataType::F32), }), framework::dataset::make("MVBGInfo",{ TensorInfo(TensorShape(2U), 1, DataType::F32), TensorInfo(TensorShape(2U), 1, DataType::F16), TensorInfo(TensorShape(5U), 1, DataType::F32), })), framework::dataset::make("Expected", { true, false, false}))
TEST_SUITE(QASYMM8_to_F32) FIXTURE_DATA_TEST_CASE(RunSmall
DataType
Available data types.
Definition: Types.h:79
combine(datasets::SmallShapes(), framework::dataset::make("DataType", DataType::F32)))
Definition: AbsLayer.cpp:65