| 593 | |
| 594 | template <KernelType kernel_type> |
| 595 | void EvalHybrid(TfLiteContext* context, TfLiteNode* node, |
| 596 | TfLiteConvParams* params, OpData* data, TfLiteTensor* input, |
| 597 | TfLiteTensor* filter, TfLiteTensor* bias, TfLiteTensor* im2col, |
| 598 | TfLiteTensor* hwcn_weights, TfLiteTensor* output) { |
| 599 | float output_activation_min, output_activation_max; |
| 600 | CalculateActivationRange(params->activation, &output_activation_min, |
| 601 | &output_activation_max); |
| 602 | |
| 603 | const int input_size = NumElements(input) / SizeOfDimension(input, 0); |
| 604 | const int batch_size = SizeOfDimension(input, 0); |
| 605 | |
| 606 | const TfLiteTensor* input_quantized = |
| 607 | GetTemporary(context, node, data->input_quantized_index); |
| 608 | int8_t* quantized_input_ptr_batch = input_quantized->data.int8; |
| 609 | float* scaling_factors_ptr = |
| 610 | GetTemporary(context, node, data->scaling_factors_index)->data.f; |
| 611 | |
| 612 | // Per-batch input quantization for higher accuracy. |
| 613 | for (int b = 0; b < batch_size; ++b) { |
| 614 | float unused_min, unused_max; |
| 615 | const int offset = b * input_size; |
| 616 | tensor_utils::SymmetricQuantizeFloats( |
| 617 | input->data.f + offset, input_size, quantized_input_ptr_batch + offset, |
| 618 | &unused_min, &unused_max, &scaling_factors_ptr[b]); |
| 619 | scaling_factors_ptr[b] *= filter->params.scale; |
| 620 | } |
| 621 | |
| 622 | int8_t* im2col_ptr = nullptr; |
| 623 | int8_t* filter_ptr = nullptr; |
| 624 | if (filter->type == kTfLiteUInt8) { |
| 625 | // For backward compatibility, we need to support the case where filters |
| 626 | // are quantized to int8 but stored as uint8. |
| 627 | if (im2col != nullptr) { |
| 628 | im2col_ptr = reinterpret_cast<int8_t*>(im2col->data.uint8); |
| 629 | } |
| 630 | filter_ptr = reinterpret_cast<int8_t*>(filter->data.uint8); |
| 631 | } else { |
| 632 | // Code at head uses the int8 type so we do not need to do the cast. |
| 633 | if (im2col != nullptr) { |
| 634 | im2col_ptr = im2col->data.int8; |
| 635 | } |
| 636 | filter_ptr = filter->data.int8; |
| 637 | } |
| 638 | |
| 639 | switch (kernel_type) { |
| 640 | case kReference: |
| 641 | case kGenericOptimized: |
| 642 | case kMultithreadOptimized: |
| 643 | case kCblasOptimized: { |
| 644 | // There is only one implementation for hybrid kernel. Note |
| 645 | // this does not make use of gemmlowp nor supports multithreading. |
| 646 | ConvParams op_params; |
| 647 | op_params.padding_type = PaddingType::kSame; |
| 648 | op_params.padding_values.width = data->padding.width; |
| 649 | op_params.padding_values.height = data->padding.height; |
| 650 | op_params.stride_width = params->stride_width; |
| 651 | op_params.stride_height = params->stride_height; |
| 652 | op_params.dilation_width_factor = 1; |
no test coverage detected