ONE - On-device Neural Engine
Loading...
Searching...
No Matches
caffe_op_creator.cpp
Go to the documentation of this file.
1/*
2 * Copyright (c) 2018 Samsung Electronics Co., Ltd. All Rights Reserved
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "caffe_op_creator.h"
18
19#include "mir/ops/AddOp.h"
20#include "mir/ops/AvgPool2DOp.h"
21#include "mir/ops/ConcatOp.h"
22#include "mir/ops/ConstantOp.h"
23#include "mir/ops/Conv2DOp.h"
24#include "mir/ops/Deconv2DOp.h"
25#include "mir/ops/EluOp.h"
27#include "mir/ops/GatherOp.h"
28#include "mir/ops/LeakyReluOp.h"
29#include "mir/ops/MaxOp.h"
30#include "mir/ops/MaxPool2DOp.h"
31#include "mir/ops/MulOp.h"
32#include "mir/ops/ReluOp.h"
33#include "mir/ops/ReshapeOp.h"
34#include "mir/ops/SigmoidOp.h"
35#include "mir/ops/SliceOp.h"
36#include "mir/ops/SoftmaxOp.h"
37#include "mir/ops/TanhOp.h"
38#include "mir/ops/TransposeOp.h"
39#include "mir/Index.h"
40#include "mir/ShapeRange.h"
41#include "mir/Tensor.h"
42
43#include <cmath>
44#include <iostream>
45#include <set>
46#include <stdexcept>
47
48namespace mir_caffe
49{
50
51static mir::Shape convertBlobShape(const caffe::BlobShape &shape)
52{
53 mir::Shape mir_shape(shape.dim_size());
54
55 for (int i = 0; i < shape.dim_size(); ++i)
56 {
57 mir_shape.dim(i) = shape.dim(i);
58 }
59
60 return mir_shape;
61}
62
63using namespace mir;
64
66std::vector<mir::Operation::Output *> CaffeOpCreator::createSplit(mir::Operation::Output *arg,
67 int32_t num_parts, int32_t axis)
68{
69 const auto &arg_shape = arg->getShape();
70
71 assert(axis >= 0 && axis < arg_shape.rank());
72 int32_t part_size = arg_shape.dim(axis) / num_parts;
73 assert(part_size * num_parts == arg_shape.dim(axis));
74
75 Shape starts(arg_shape.rank());
76 Shape sizes(arg_shape);
77 sizes.dim(axis) = part_size;
78
79 std::vector<mir::Operation::Output *> outputs(num_parts);
80 for (int32_t i = 0; i < num_parts; ++i)
81 {
82 outputs[i] = createOp<ops::SliceOp>(arg, starts, sizes)->getOutput(0);
83 starts.dim(axis) += part_size;
84 }
85
86 return outputs;
87}
88
90mir::Operation::Output *CaffeOpCreator::createFullyConnected(mir::Operation::Output *input,
92 int32_t axis)
93{
94 const auto &input_shape = input->getShape();
95 const auto &weights_shape = weights->getShape();
96
97 assert(axis >= 0 && axis < input_shape.rank());
98 assert(weights_shape.rank() == 2);
99
100 // Result shape is: input.shape[0:axis] + weights.shape[1].
101 Shape result_shape = input_shape;
102 result_shape.resize(axis + 1);
103 result_shape.dim(axis) = weights_shape.dim(1);
104
105 // Flatten input to 2-D shape.
106 int32_t outer_size = 1;
107 for (int32_t i = 0; i < axis; ++i)
108 outer_size *= input_shape.dim(i);
109 int32_t inner_size = 1;
110 for (int32_t i = axis; i < input_shape.rank(); ++i)
111 inner_size *= input_shape.dim(i);
112
113 auto flatten = createOp<ops::ReshapeOp>(input, Shape{outer_size, inner_size})->getOutput(0);
114 auto fc = createOp<ops::FullyConnectedOp>(flatten, weights)->getOutput(0);
115 return createOp<ops::ReshapeOp>(fc, result_shape)->getOutput(0);
116}
117
118TensorVariant CaffeOpCreator::convertBlob(const caffe::BlobProto &blob)
119{
120 const void *src_data;
121
123 if (blob.data_size() != 0)
124 {
125 assert(blob.double_data_size() == 0);
126 dtype = mir::DataType::FLOAT32;
127 src_data = blob.data().data();
128 }
129 else if (blob.double_data_size() != 0)
130 {
131 dtype = mir::DataType::FLOAT64;
132 src_data = blob.double_data().data();
133 }
134 else
135 {
136 throw std::runtime_error("No data in Caffe BlobProto, investigate");
137 }
138
139 const mir::Shape shape = convertBlobShape(blob.shape());
140 return TensorVariant({dtype, shape}, src_data);
141}
142
143std::vector<mir::Operation::Output *>
144CaffeOpCreator::convertInput(const caffe::LayerParameter &layer)
145{
146 const auto &params = layer.input_param();
147 const auto num_inputs = layer.top_size();
148 const auto num_shapes = params.shape_size();
149 std::vector<mir::Operation::Output *> outputs;
150
151 assert((num_shapes == 1 || num_shapes == num_inputs) && "Unsupported number of shapes.");
152
153 for (int i = 0; i < num_inputs; ++i)
154 {
155 const auto &blob_shape = params.shape(num_shapes == 1 ? 0 : i);
156 mir::TensorType input_type(DataType::FLOAT32, convertBlobShape(blob_shape));
157 auto input = createOp<ops::InputOp>(input_type)->getOutput(0);
158 outputs.push_back(input);
159 }
160
161 return outputs;
162}
163
164template <class OperationAttributes>
165static void convertConvolutionParam(const caffe::ConvolutionParameter &conv_param,
166 OperationAttributes &attributes)
167{
168 std::int32_t stride_h, stride_w;
169 if (conv_param.has_stride_h() || conv_param.has_stride_w())
170 {
171 // If stride_h or stride_w are set, they take precedence.
172 stride_h = conv_param.stride_h();
173 stride_w = conv_param.stride_w();
174 }
175 else if (conv_param.stride_size() == 0)
176 {
177 // If no strides specified, they defaults to 1.
178 stride_h = stride_w = 1;
179 }
180 else if (conv_param.stride_size() == 1)
181 {
182 // If only one stride specified, all strides take the same value.
183 stride_h = stride_w = conv_param.stride(0);
184 }
185 else
186 {
187 // Otherwise, there must be a stride for each dimension.
188 assert(conv_param.stride_size() == 2);
189 stride_h = conv_param.stride(0);
190 stride_w = conv_param.stride(1);
191 }
192 attributes.strides = {stride_h, stride_w};
193
194 std::int32_t pad_h, pad_w;
195 if (conv_param.has_pad_h() || conv_param.has_pad_w())
196 {
197 // If pad_h or pad_w are set, they take precedence.
198 pad_h = conv_param.pad_h();
199 pad_w = conv_param.pad_w();
200 }
201 else if (conv_param.pad_size() == 0)
202 {
203 // If no pads specified, they defaults to 0.
204 pad_h = pad_w = 0;
205 }
206 else if (conv_param.pad_size() == 1)
207 {
208 // If only one pad specified, all pads take the same value.
209 pad_h = pad_w = conv_param.pad(0);
210 }
211 else
212 {
213 // Otherwise, there must be a pad for each dimension.
214 assert(conv_param.pad_size() == 2);
215 pad_h = conv_param.pad(0);
216 pad_w = conv_param.pad(1);
217 }
218 attributes.padding_after = attributes.padding_before = {pad_h, pad_w};
219}
220
221void CaffeOpCreator::checkConvolution(const caffe::LayerParameter &layer,
222 std::set<std::string> &problems_ops_set)
223{
224 const caffe::ConvolutionParameter &params = layer.convolution_param();
225
226 assert(params.stride_size() <= 2);
227
228 if (params.axis() != 1)
229 problems_ops_set.insert("Conv2D: Unsupported axis");
230
231 if (params.pad_size() != 0 && (params.has_pad_h() || params.has_pad_w()))
232 problems_ops_set.insert("Conv2D: Conflicting padding properties");
233
234 if (params.pad_size() > 2)
235 problems_ops_set.insert("Conv2D: Unsupported number of pads");
236}
237
238std::vector<mir::Operation::Output *>
239CaffeOpCreator::convertConvolution(const caffe::LayerParameter &layer,
240 const std::vector<mir::Operation::Output *> &inputs)
241{
242 const auto &params = layer.convolution_param();
243 Conv2DOpAttributes attributes;
244
245 convertConvolutionParam(params, attributes);
246 attributes.num_groups = params.group();
247 attributes.data_format = DataFormat::NCHW;
248
249 assert(layer.blobs(0).shape().dim_size() == 4);
250 auto kernel = createOp<ops::ConstantOp>(convertBlob(layer.blobs(0)))->getOutput(0);
251 std::vector<std::size_t> perm{0, 2, 3, 1}; // OIHW -> OHWI
252 kernel = createOp<ops::TransposeOp>(kernel, perm)->getOutput(0);
253 auto result = createOp<ops::Conv2DOp>(inputs[0], kernel, attributes)->getOutput(0);
254
255 // Add the bias, if any.
256 if (params.bias_term())
257 {
258 auto bias = createOp<ops::ConstantOp>(convertBlob(layer.blobs(1)))->getOutput(0);
259 bias = createOp<ops::ReshapeOp>(bias, Shape{1, bias->getShape().dim(0), 1, 1})->getOutput(0);
260 result = createOp<ops::AddOp>(result, bias)->getOutput(0);
261 }
262
263 return {result};
264}
265
266std::vector<mir::Operation::Output *>
267CaffeOpCreator::convertDeconvolution(const caffe::LayerParameter &layer,
268 const std::vector<mir::Operation::Output *> &inputs)
269{
270 const caffe::ConvolutionParameter &params = layer.convolution_param();
271 Deconv2DOpAttributes attributes;
272
273 convertConvolutionParam(params, attributes);
274 attributes.data_format = DataFormat::NCHW;
275
276 if (params.group() != 1)
277 {
278 throw std::runtime_error("Deconvolution: 'group' != 1 is not supported.");
279 }
280
281 auto kernel = createOp<ops::ConstantOp>(convertBlob(layer.blobs(0)))->getOutput(0);
282 std::vector<std::size_t> perm{2, 3, 1, 0}; // IOHW -> HWOI
283 kernel = createOp<ops::TransposeOp>(kernel, perm)->getOutput(0);
284 auto result = createOp<ops::DeConv2DOp>(inputs[0], kernel, attributes)->getOutput(0);
285
286 // bias_term is optional (so might not be present) and defaults to true
287 if (params.bias_term())
288 {
289 auto bias = createOp<ops::ConstantOp>(convertBlob(layer.blobs(1)))->getOutput(0);
290 bias = createOp<ops::ReshapeOp>(bias, Shape{1, bias->getShape().dim(0), 1, 1})->getOutput(0);
291 result = createOp<ops::AddOp>(result, bias)->getOutput(0);
292 }
293
294 return {result};
295}
296
297std::vector<mir::Operation::Output *>
298CaffeOpCreator::convertInnerProduct(const caffe::LayerParameter &layer,
299 const std::vector<mir::Operation::Output *> &inputs)
300{
301 const auto &params = layer.inner_product_param();
302 auto weights = createOp<ops::ConstantOp>(convertBlob(layer.blobs(0)))->getOutput(0);
303
304 if (!params.transpose())
305 weights = createOp<ops::TransposeOp>(weights, std::vector<std::size_t>{1, 0})->getOutput(0);
306
307 auto result = createFullyConnected(inputs[0], weights, params.axis());
308
309 // Add the bias, if any.
310 if (params.bias_term())
311 {
312 auto bias = createOp<ops::ConstantOp>(convertBlob(layer.blobs(1)))->getOutput(0);
313 result = createOp<ops::AddOp>(result, bias)->getOutput(0);
314 }
315
316 return {result};
317}
318
319std::vector<mir::Operation::Output *>
320CaffeOpCreator::convertConcat(const caffe::LayerParameter &layer,
321 const std::vector<mir::Operation::Output *> &inputs)
322{
323 const auto &params = layer.concat_param();
324 auto concat = createOp<ops::ConcatOp>(inputs, params.axis());
325 return {concat->getOutput(0)};
326}
327
328template <class PoolingAttributes>
329static void convertPoolingParam(const caffe::PoolingParameter &params,
330 const mir::Shape &input_shape, PoolingAttributes &attributes)
331{
332 std::int32_t kernel_h, kernel_w;
333 assert(!params.global_pooling());
334 if (params.has_kernel_size())
335 {
336 kernel_h = kernel_w = params.kernel_size();
337 }
338 else
339 {
340 kernel_h = params.kernel_h();
341 kernel_w = params.kernel_w();
342 }
343 attributes.window = {kernel_h, kernel_w};
344
345 std::int32_t stride_h, stride_w;
346 if (params.has_stride_h() || params.has_stride_w())
347 {
348 stride_h = params.stride_h();
349 stride_w = params.stride_w();
350 }
351 else
352 {
353 stride_h = stride_w = params.stride();
354 }
355 attributes.strides = {stride_h, stride_w};
356
357 std::int32_t pad_h, pad_w;
358 if (params.has_pad_h() || params.has_pad_w())
359 {
360 pad_h = params.pad_h();
361 pad_w = params.pad_w();
362 }
363 else
364 {
365 pad_h = pad_w = params.pad();
366 }
367
368 attributes.padding_before = attributes.padding_after = {pad_h, pad_w};
369
370 // Caffe uses different formula for computing output shape than MIR. Adjust padding so that
371 // the output shape stays the same.
372 constexpr int num_spatial_dims = 2;
373 for (int i = 0; i < num_spatial_dims; ++i)
374 {
375 // Assuming NCHW format.
376 const std::int32_t padded_input =
377 input_shape.dim(2 + i) + attributes.padding_before[i] + attributes.padding_after[i];
378 if ((padded_input - attributes.window[i]) % attributes.strides[i] != 0)
379 ++attributes.padding_after[i];
380 }
381}
382
383void CaffeOpCreator::checkPooling(const caffe::LayerParameter &layer,
384 std::set<std::string> &problems_ops_set)
385{
386 const caffe::PoolingParameter &params = layer.pooling_param();
387
388 if (params.has_global_pooling() && params.global_pooling())
389 problems_ops_set.insert("Pooling: pooling layer global_pooling param is not supported yet");
390
391 if (params.pool() != caffe::PoolingParameter::AVE &&
392 params.pool() != caffe::PoolingParameter::MAX)
393 problems_ops_set.insert("Pooling: unsupported pooling type");
394
395 if (params.has_pad() && (params.has_pad_h() || params.has_pad_w()))
396 problems_ops_set.insert("Pooling: conflicting padding properties in pooling");
397}
398
399std::vector<mir::Operation::Output *>
400CaffeOpCreator::convertPooling(const caffe::LayerParameter &layer,
401 const std::vector<mir::Operation::Output *> &inputs)
402{
403 const auto &params = layer.pooling_param();
404
405 assert(inputs.size() == 1);
406 auto input = inputs[0];
407
409
410 switch (params.pool())
411 {
412 case caffe::PoolingParameter::AVE:
413 {
414 AvgPool2DOpAttributes attributes_avg;
415 attributes_avg.data_format = DataFormat::NCHW;
416 convertPoolingParam(params, input->getShape(), attributes_avg);
417 result = createOp<ops::AvgPool2DOp>(input, attributes_avg)->getOutput(0);
418 break;
419 }
420 case caffe::PoolingParameter::MAX:
421 {
422 MaxPool2DOpAttributes attributes_max;
423 attributes_max.data_format = DataFormat::NCHW;
424 convertPoolingParam(params, input->getShape(), attributes_max);
425 result = createOp<ops::MaxPool2DOp>(input, attributes_max)->getOutput(0);
426 break;
427 }
428 default:
429 throw std::runtime_error("Unsupported PoolMethod: " + std::to_string(params.pool()));
430 }
431
432 return {result};
433}
434
435std::vector<mir::Operation::Output *>
436CaffeOpCreator::convertSoftmax(const caffe::LayerParameter &layer,
437 const std::vector<mir::Operation::Output *> &inputs)
438{
439 const auto &params = layer.softmax_param();
440
441 // CPP and ACL backends are able to perform Softmax only along the last axis.
442 // FIXME Do it in backends.
443 if (inputs[0]->getShape().rank() == 4)
444 {
445 // For now, we only account for the most common case.
446 if (params.axis() != 1)
447 throw std::runtime_error("Softmax: unsupported axis");
448 int32_t axis = 3;
449 auto input = createOp<ops::TransposeOp>(inputs[0], std::vector<std::size_t>{0, 2, 3, 1});
450 auto softmax = createOp<ops::SoftmaxOp>(input->getOutput(0), axis);
451 auto result =
452 createOp<ops::TransposeOp>(softmax->getOutput(0), std::vector<std::size_t>{0, 3, 1, 2});
453 return {result->getOutput(0)};
454 }
455
456 auto softmax = createOp<ops::SoftmaxOp>(inputs[0], params.axis());
457 return {softmax->getOutput(0)};
458}
459
460void CaffeOpCreator::checkReshape(const caffe::LayerParameter &layer,
461 std::set<std::string> &problems_ops_set)
462{
463 const caffe::ReshapeParameter &params = layer.reshape_param();
464
465 if (params.has_axis() || params.has_num_axes())
466 problems_ops_set.insert("Reshape layer axis and num_axes params are not supported yet");
467
468 if (!params.has_shape())
469 problems_ops_set.insert("Reshape layer doesn't have shape parameter");
470
471 const mir::Shape newShape = convertBlobShape(params.shape());
472
473 for (int32_t i = 0; i < newShape.rank(); ++i)
474 if (newShape.dim(i) == 0)
475 problems_ops_set.insert("Reshape layer zero shape values are not supported yet");
476}
477
484std::vector<mir::Operation::Output *>
485CaffeOpCreator::convertReshape(const caffe::LayerParameter &layer,
486 const std::vector<mir::Operation::Output *> &inputs)
487{
488 const caffe::ReshapeParameter &params = layer.reshape_param();
489
490 const mir::Shape new_shape = convertBlobShape(params.shape());
491 auto reshape = createOp<ops::ReshapeOp>(inputs[0], new_shape);
492 return {reshape->getOutput(0)};
493}
494
495std::vector<mir::Operation::Output *>
496CaffeOpCreator::convertReLU(const caffe::LayerParameter &layer,
497 const std::vector<mir::Operation::Output *> &inputs)
498{
499 mir::Operation *relu;
500 if (layer.relu_param().has_negative_slope())
501 {
502 float alpha = layer.relu_param().negative_slope();
503 relu = createOp<ops::LeakyReluOp>(inputs[0], alpha);
504 }
505 else
506 {
507 relu = createOp<ops::ReluOp>(inputs[0]);
508 }
509
510 return {relu->getOutput(0)};
511}
512
513std::vector<mir::Operation::Output *>
514CaffeOpCreator::convertScale(const caffe::LayerParameter &layer,
515 const std::vector<mir::Operation::Output *> &inputs)
516{
517 const auto &params = layer.scale_param();
518 auto scale = createOp<ops::ConstantOp>(convertBlob(layer.blobs(0)))->getOutput(0);
519 scale = createOp<ops::ReshapeOp>(scale, Shape{1, scale->getShape().dim(0), 1, 1})->getOutput(0);
520 auto result = createOp<ops::MulOp>(inputs[0], scale)->getOutput(0);
521
522 // Add the bias, if any.
523 if (params.bias_term())
524 {
525 auto bias = createOp<ops::ConstantOp>(convertBlob(layer.blobs(1)))->getOutput(0);
526 bias = createOp<ops::ReshapeOp>(bias, Shape{1, bias->getShape().dim(0), 1, 1})->getOutput(0);
527 result = createOp<ops::AddOp>(result, bias)->getOutput(0);
528 }
529
530 return {result};
531}
532
533void CaffeOpCreator::checkBatchNorm(const caffe::LayerParameter &layer,
534 std::set<std::string> &problems_ops_set)
535{
536 const auto &scale_shape = layer.blobs(2).shape();
537
538 // Check that last blob(with scaleFactor) containing only one number
539 if (scale_shape.dim_size() != 1 || scale_shape.dim(0) != 1)
540 problems_ops_set.insert("Unexpected shape of scale parameter in batch norm");
541}
542
543std::vector<mir::Operation::Output *>
544CaffeOpCreator::convertBatchNorm(const caffe::LayerParameter &layer,
545 const std::vector<mir::Operation::Output *> &inputs)
546{
547 const caffe::BatchNormParameter &params = layer.batch_norm_param();
548
549 auto input = inputs[0];
550 auto mean_tensor = convertBlob(layer.blobs(0));
551 auto var_tensor = convertBlob(layer.blobs(1));
552 auto scale_tensor = convertBlob(layer.blobs(2));
553 const float eps = params.eps();
554
555 float scale_factor = *reinterpret_cast<float *>(scale_tensor.at(mir::Index{0}));
556
557 // See https://github.com/BVLC/caffe/blob/master/src/caffe/layers/batch_norm_layer.cpp#L100
558 // Y = (X - mean / scale_factor) / sqrt(var / scale_factor + epsilon) =
559 // = (X + C1) * C2
560 if (scale_factor != 0.0f)
561 scale_factor = 1.0f / scale_factor;
562
563 // C1 = -mean / scale_factor
564 Tensor<float> mean_accessor(mean_tensor);
565 for (const auto &idx : ShapeRange(mean_accessor.getShape()))
566 mean_accessor.at(idx) *= -scale_factor;
567 auto c1 = createOp<ops::ConstantOp>(mean_tensor)->getOutput(0);
568
569 // C2 = 1 / sqrt(var / scale_factor + epsilon)
570 Tensor<float> var_accessor(var_tensor);
571 for (const auto &idx : ShapeRange(var_accessor.getShape()))
572 var_accessor.at(idx) = 1.0f / std::sqrt(var_accessor.at(idx) * scale_factor + eps);
573 auto c2 = createOp<ops::ConstantOp>(var_tensor)->getOutput(0);
574
575 c1 = createOp<ops::ReshapeOp>(c1, Shape{1, c1->getShape().dim(0), 1, 1})->getOutput(0);
576 c2 = createOp<ops::ReshapeOp>(c2, Shape{1, c2->getShape().dim(0), 1, 1})->getOutput(0);
577
578 // Y = (X + C1) * C2
579 auto result = createOp<ops::AddOp>(input, c1)->getOutput(0);
580 result = createOp<ops::MulOp>(result, c2)->getOutput(0);
581
582 return {result};
583}
584
585std::vector<mir::Operation::Output *>
586CaffeOpCreator::convertDropout(const caffe::LayerParameter &,
587 const std::vector<mir::Operation::Output *> &inputs)
588{
589 // This is a no-op in inference mode.
590 return {inputs[0]};
591}
592
593std::vector<mir::Operation::Output *>
594CaffeOpCreator::convertELU(const caffe::LayerParameter &layer,
595 const std::vector<mir::Operation::Output *> &inputs)
596{
597 const caffe::ELUParameter &params = layer.elu_param();
598
599 auto elu = createOp<ops::EluOp>(inputs[0], params.alpha());
600 return {elu->getOutput(0)};
601}
602
603std::vector<mir::Operation::Output *>
604CaffeOpCreator::convertEmbed(const caffe::LayerParameter &layer,
605 const std::vector<mir::Operation::Output *> &inputs)
606{
607 const auto &params = layer.embed_param();
608 auto data = createOp<ops::ConstantOp>(convertBlob(layer.blobs(0)));
609 auto result = createOp<ops::GatherOp>(data->getOutput(0), inputs[0], 0)->getOutput(0);
610
611 // Add the bias, if any.
612 if (params.bias_term())
613 {
614 auto bias = createOp<ops::ConstantOp>(convertBlob(layer.blobs(1)))->getOutput(0);
615 result = createOp<ops::AddOp>(result, bias)->getOutput(0);
616 }
617
618 return {result};
619}
620
621std::vector<mir::Operation::Output *>
622CaffeOpCreator::convertSigmoid(const caffe::LayerParameter &,
623 const std::vector<mir::Operation::Output *> &inputs)
624{
625 auto result = createOp<ops::SigmoidOp>(inputs[0]);
626 return {result->getOutput(0)};
627}
628
629std::vector<mir::Operation::Output *>
630CaffeOpCreator::convertTanH(const caffe::LayerParameter &,
631 const std::vector<mir::Operation::Output *> &inputs)
632{
633 auto tanh = createOp<ops::TanhOp>(inputs[0]);
634 return {tanh->getOutput(0)};
635}
636
637std::vector<mir::Operation::Output *>
638CaffeOpCreator::convertEltwise(const caffe::LayerParameter &layer,
639 const std::vector<mir::Operation::Output *> &inputs)
640{
641 auto &params = layer.eltwise_param();
642
644 switch (params.operation())
645 {
646 case caffe::EltwiseParameter::PROD:
647 {
648 result = createOp<ops::MulOp>(inputs[0], inputs[1])->getOutput(0);
649 for (int i = 2; i < layer.bottom_size(); ++i)
650 {
651 result = createOp<ops::MulOp>(result, inputs[i])->getOutput(0);
652 }
653 break;
654 }
655 case caffe::EltwiseParameter::SUM:
656 {
657 std::vector<mir::Operation::Output *> scaled_inputs = inputs;
658 if (params.coeff_size() > 0)
659 {
660 assert(params.coeff_size() == layer.bottom_size());
661 for (int i = 0; i < layer.bottom_size(); i++)
662 {
663 if (params.coeff(i) != 1.0f)
664 {
665 const float coeff_val = params.coeff(i);
666 TensorVariant coeff_tensor({DataType::FLOAT32, {}}, &coeff_val);
667 auto coeff_const = createOp<ops::ConstantOp>(coeff_tensor)->getOutput(0);
668 scaled_inputs[i] = createOp<ops::MulOp>(coeff_const, inputs[i])->getOutput(0);
669 }
670 }
671 }
672 result = createOp<ops::AddOp>(scaled_inputs[0], scaled_inputs[1])->getOutput(0);
673 for (int i = 2; i < layer.bottom_size(); ++i)
674 {
675 result = createOp<ops::AddOp>(result, scaled_inputs[i])->getOutput(0);
676 }
677 break;
678 }
679 case caffe::EltwiseParameter::MAX:
680 {
681 result = createOp<ops::MaxOp>(inputs[0], inputs[1])->getOutput(0);
682 for (int i = 2; i < layer.bottom_size(); ++i)
683 {
684 result = createOp<ops::MaxOp>(result, inputs[i])->getOutput(0);
685 }
686 break;
687 }
688 default:
689 throw std::runtime_error("Unknown element-wise operation.");
690 }
691 return {result};
692}
693
694std::vector<mir::Operation::Output *>
695CaffeOpCreator::convertSplit(const caffe::LayerParameter &layer,
696 const std::vector<mir::Operation::Output *> &inputs)
697{
698 std::vector<mir::Operation::Output *> outputs(layer.top_size(), inputs.at(0));
699 return outputs;
700}
701
702void CaffeOpCreator::checkLSTM(const caffe::LayerParameter &layer,
703 std::set<std::string> &problems_ops_set)
704{
705 const auto &params = layer.recurrent_param();
706 if (params.expose_hidden())
707 problems_ops_set.insert("LSTM: parameter 'expose_hidden' has unsupported value: " +
708 std::to_string(params.expose_hidden()));
709}
710
711static TensorVariant createZeroedTensor(const mir::Shape &shape)
712{
713 // TODO For now it is hardcoded float32.
714 auto elem_type = mir::DataType::FLOAT32;
715 std::vector<float> zeros(static_cast<std::size_t>(shape.numElements()), 0.0f);
716 return TensorVariant({elem_type, shape}, zeros.data());
717}
718
719/* See the following links for details on implementation:
720 * https://github.com/BVLC/caffe/blob/master/src/caffe/layers/recurrent_layer.cpp
721 * https://github.com/BVLC/caffe/blob/master/src/caffe/layers/lstm_layer.cpp
722 * https://github.com/BVLC/caffe/blob/master/src/caffe/layers/lstm_unit_layer.cpp
723 *
724 * Inputs:
725 * x -- The time-varying input. Shape: [T, N, d0, d1, ..., dn].
726 * cont -- The sequence continuation indicators. Shape: [T, N].
727 * x_static -- The static (non-time-varying) input. Shape: [N, ...].
728 * This parameter is optional and not currently supported.
729 *
730 * Additional inputs when parameter "expose_hidden" is true (not currently supported):
731 * h_0 -- The initial value of the hidden state. Shape: [1, N, D].
732 * c_0 -- The initial value of the cell state. Shape: [1, N, D].
733 *
734 * Learned parameters:
735 * xw -- x weights for input, output, forget and cell gates concatenated.
736 * Shape: [4 * D, d0 * d1 * ... * dn].
737 * xb -- x biases for input, output, forget and cell gates concatenated. Shape: [4 * D].
738 * hw -- h weights for input, output, forget and cell gates concatenated. Shape: [4 * D, D].
739 *
740 * Outputs:
741 * h -- The time-varying output. Shape: [T, N, D].
742 *
743 * Additional outputs when parameter "expose_hidden" is true (not currently supported):
744 * h_T -- The value of the hidden state at the last timestep. Shape: [1, N, D].
745 * c_T -- The value of the cell state at the last timestep. Shape: [1, N, D].
746 *
747 * Here:
748 * T - the number of timesteps,
749 * N - the number of independent streams.
750 * D - the number of hidden parameters.
751 *
752 * Formulas:
753 * c_cont = c[t-1] * cont[t]
754 * h_cont = h[t-1] * cont[t]
755 * i[t] = Sigmoid(x[t] . xw_i + xb_i + h_cont . hw_i)
756 * f[t] = Sigmoid(x[t] . xw_f + xb_f + h_cont . hw_f)
757 * o[t] = Sigmoid(x[t] . xw_o + xb_o + h_cont . hw_o)
758 * g[t] = Tanh(x[t] . xw_g + xb_g + h_cont . hw_g)
759 * c[t] = c_cont * f[t] + i[t] * g[t]
760 * h[t] = o[t] * Tanh(c[t])
761 *
762 * Here:
763 * t -- the timestep (ranges from 1 to T),
764 * * -- the inner product,
765 * . -- the Hadamard product (elementwise product).
766 *
767 * In this implementation the inner products for all gates are performed as single inner product for
768 * efficiency.
769 */
770std::vector<mir::Operation::Output *>
771CaffeOpCreator::convertLSTM(const caffe::LayerParameter &layer,
772 const std::vector<mir::Operation::Output *> &inputs)
773{
774 const auto &params = layer.recurrent_param();
775
776 // Inputs to the layer.
777 auto x = inputs[0];
778 auto cont = inputs[1];
779 assert(inputs.size() == 2);
780
781 const auto &x_shape = x->getShape();
782 const int32_t seq_length = x_shape.dim(0);
783 const int32_t batch_size = x_shape.dim(1);
784 const int32_t hidden_size = params.num_output();
785
786 // Learned parameters of the layer. Tensors are transposed to match the ModelIR.
787 auto xw = createOp<ops::ConstantOp>(convertBlob(layer.blobs(0)))->getOutput(0);
788 auto xb = createOp<ops::ConstantOp>(convertBlob(layer.blobs(1)))->getOutput(0);
789 auto hw = createOp<ops::ConstantOp>(convertBlob(layer.blobs(2)))->getOutput(0);
790 xw = createOp<ops::TransposeOp>(xw, std::vector<std::size_t>{1, 0})->getOutput(0);
791 hw = createOp<ops::TransposeOp>(hw, std::vector<std::size_t>{1, 0})->getOutput(0);
792
793 // Add a dummy dimension so that element-wise operations perform properly.
794 cont = createOp<ops::ReshapeOp>(cont, Shape{seq_length, batch_size, 1})->getOutput(0);
795
796 // Initialize cell and hidden states with zeros.
797 auto zero_tensor = createZeroedTensor(Shape{1, batch_size, hidden_size});
798 auto c_t = createOp<ops::ConstantOp>(zero_tensor)->getOutput(0);
799 auto h_t = createOp<ops::ConstantOp>(zero_tensor)->getOutput(0);
800
801 auto x_xw = createFullyConnected(x, xw, 2);
802 auto x_xw_b = createOp<ops::AddOp>(x_xw, xb)->getOutput(0);
803
804 // Split input and continuation tensors into seq_length slices.
805 std::vector<mir::Operation::Output *> x_xw_b_slices = createSplit(x_xw_b, seq_length, 0);
806 std::vector<mir::Operation::Output *> cont_slices = createSplit(cont, seq_length, 0);
807 std::vector<mir::Operation::Output *> h_slices(seq_length);
808
809 for (int32_t t = 0; t < seq_length; t++)
810 {
811 auto c_cont_t = createOp<ops::MulOp>(c_t, cont_slices[t])->getOutput(0);
812 auto h_cont_t = createOp<ops::MulOp>(h_t, cont_slices[t])->getOutput(0);
813
814 auto x_xw_b_t = x_xw_b_slices[t];
815 auto h_hw_t = createFullyConnected(h_cont_t, hw, 2);
816 auto activation_inputs_concat = createOp<ops::AddOp>(x_xw_b_t, h_hw_t)->getOutput(0);
817 auto activation_inputs = createSplit(activation_inputs_concat, 4, 2);
818
819 auto i_t = createOp<ops::SigmoidOp>(activation_inputs[0])->getOutput(0);
820 auto f_t = createOp<ops::SigmoidOp>(activation_inputs[1])->getOutput(0);
821 auto o_t = createOp<ops::SigmoidOp>(activation_inputs[2])->getOutput(0);
822 auto g_t = createOp<ops::TanhOp>(activation_inputs[3])->getOutput(0);
823
824 c_t = createOp<ops::AddOp>(createOp<ops::MulOp>(c_cont_t, f_t)->getOutput(0),
825 createOp<ops::MulOp>(i_t, g_t)->getOutput(0))
826 ->getOutput(0);
827 h_t = createOp<ops::MulOp>(createOp<ops::TanhOp>(c_t)->getOutput(0), o_t)->getOutput(0);
828
829 h_slices[t] = h_t;
830 }
831
832 return {createOp<ops::ConcatOp>(h_slices, 0)->getOutput(0)};
833}
834
835} // namespace mir_caffe
Represents an output of a node.
Definition Operation.h:60
const Shape & getShape() const
Definition Operation.h:99
int32_t & dim(int32_t axis) noexcept
Definition Shape.h:47
int32_t numElements() const
Definition Shape.cpp:30
int32_t rank() const
Definition Shape.h:43
const Shape & getShape() const
Definition Tensor.h:48
T at(const Index &id) const
Definition Tensor.h:31
std::vector< mir::Operation::Output * > convertReLU(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
std::vector< mir::Operation::Output * > convertSplit(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
void checkBatchNorm(const caffe::LayerParameter &layer, std::set< std::string > &problems_ops_set)
std::vector< mir::Operation::Output * > convertInput(const caffe::LayerParameter &layer)
void checkReshape(const caffe::LayerParameter &layer, std::set< std::string > &problems_ops_set)
std::vector< mir::Operation::Output * > convertDeconvolution(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
std::vector< mir::Operation::Output * > convertLSTM(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
std::vector< mir::Operation::Output * > convertEltwise(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
std::vector< mir::Operation::Output * > convertELU(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
std::vector< mir::Operation::Output * > convertConvolution(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
std::vector< mir::Operation::Output * > convertPooling(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
std::vector< mir::Operation::Output * > convertInnerProduct(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
std::vector< mir::Operation::Output * > convertBatchNorm(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
std::vector< mir::Operation::Output * > convertSigmoid(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
void checkPooling(const caffe::LayerParameter &layer, std::set< std::string > &problems_ops_set)
void checkLSTM(const caffe::LayerParameter &layer, std::set< std::string > &problems_ops_set)
std::vector< mir::Operation::Output * > convertScale(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
std::vector< mir::Operation::Output * > convertReshape(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
Converts Caffe Reshape layer to Model IR Reshape operation.
std::vector< mir::Operation::Output * > convertSoftmax(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
std::vector< mir::Operation::Output * > convertDropout(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
void checkConvolution(const caffe::LayerParameter &layer, std::set< std::string > &problems_ops_set)
std::vector< mir::Operation::Output * > convertTanH(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
std::vector< mir::Operation::Output * > convertEmbed(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
std::vector< mir::Operation::Output * > convertConcat(const caffe::LayerParameter &layer, const std::vector< mir::Operation::Output * > &inputs)
DataType
Definition DataType.h:27
Definition Shape.h:28
std::int32_t num_groups
Definition Attributes.h:34