TfLiteStatus EvalHybrid(
    const TfLiteTensor* input, const TfLiteTensor* bw_input,
    const TfLiteTensor* fw_input_weights,
    const TfLiteTensor* fw_recurrent_weights, const TfLiteTensor* fw_bias,
    const TfLiteTensor* bw_input_weights,
    const TfLiteTensor* bw_recurrent_weights, const TfLiteTensor* bw_bias,
    const TfLiteTensor* aux_input, const TfLiteTensor* aux_fw_input_weights,
    const TfLiteTensor* aux_bw_input_weights,
    const TfLiteBidirectionalSequenceRNNParams* params,
    TfLiteTensor* scaling_factors, TfLiteTensor* input_quantized,
    TfLiteTensor* aux_input_quantized, TfLiteTensor* fw_hidden_state_quantized,
    TfLiteTensor* fw_hidden_state, TfLiteTensor* fw_output,
    TfLiteTensor* bw_hidden_state_quantized, TfLiteTensor* bw_hidden_state,
    TfLiteTensor* bw_output, TfLiteTensor* zero_points,
    TfLiteTensor* accum_scratch, TfLiteTensor* fw_row_sums,
    TfLiteTensor* bw_row_sums, bool* fw_compute_row_sums,
    bool* bw_compute_row_sums) {
  const bool time_major = params->time_major;
  const int batch_size =
      (time_major) ? input->dims->data[1] : input->dims->data[0];
  const int max_time =
      (time_major) ? input->dims->data[0] : input->dims->data[1];
  const int input_size = input->dims->data[2];
  const int aux_input_size = (aux_input) ? aux_input->dims->data[2] : 0;

  const int fw_num_units = fw_input_weights->dims->data[0];
  const float* fw_bias_ptr = GetTensorData<float>(fw_bias);
  const int8_t* fw_input_weights_ptr = GetTensorData<int8_t>(fw_input_weights);
  float fw_input_weights_scale = fw_input_weights->params.scale;
  const int8_t* fw_recurrent_weights_ptr =
      GetTensorData<int8_t>(fw_recurrent_weights);
  float fw_recurrent_weights_scale = fw_recurrent_weights->params.scale;

  const int bw_num_units = bw_input_weights->dims->data[0];
  const float* bw_bias_ptr = GetTensorData<float>(bw_bias);
  const int8_t* bw_input_weights_ptr = GetTensorData<int8_t>(bw_input_weights);
  float bw_input_weights_scale = bw_input_weights->params.scale;
  const int8_t* bw_recurrent_weights_ptr =
      GetTensorData<int8_t>(bw_recurrent_weights);
  float bw_recurrent_weights_scale = bw_recurrent_weights->params.scale;

  // Set the auxiliary pointers and scales if needed.
  const int8_t* aux_fw_input_weights_ptr = nullptr;
  float aux_fw_input_weights_scale = 0.0f;
  const int8_t* aux_bw_input_weights_ptr = nullptr;
  float aux_bw_input_weights_scale = 0.0f;
  int8_t* aux_quantized_input_ptr = nullptr;
  if (aux_input_size > 0) {
    aux_fw_input_weights_ptr = GetTensorData<int8_t>(aux_fw_input_weights);
    aux_fw_input_weights_scale = aux_fw_input_weights->params.scale;
    aux_bw_input_weights_ptr = GetTensorData<int8_t>(aux_bw_input_weights);
    aux_bw_input_weights_scale = aux_bw_input_weights->params.scale;
    aux_quantized_input_ptr = GetTensorData<int8_t>(aux_input_quantized);
  }

  // Initialize temporary storage for quantized values.
  int8_t* quantized_input_ptr = GetTensorData<int8_t>(input_quantized);
  int8_t* fw_quantized_hidden_state_ptr =
      GetTensorData<int8_t>(fw_hidden_state_quantized);
  int8_t* bw_quantized_hidden_state_ptr =
      GetTensorData<int8_t>(bw_hidden_state_quantized);
  float* scaling_factors_ptr = GetTensorData<float>(scaling_factors);
  int32_t* accum_scratch_ptr = GetTensorData<int32_t>(accum_scratch);
  int32_t* zero_points_ptr = nullptr;
  int32_t* fw_row_sums_ptr = nullptr;
  int32_t* bw_row_sums_ptr = nullptr;
  if (params->asymmetric_quantize_inputs) {
    zero_points_ptr = GetTensorData<int32_t>(zero_points);
    fw_row_sums_ptr = GetTensorData<int32_t>(fw_row_sums);
    bw_row_sums_ptr = GetTensorData<int32_t>(bw_row_sums);
  }
  const int fw_output_step =
      params->merge_outputs ? fw_num_units + bw_num_units : fw_num_units;
  const int bw_output_step =
      params->merge_outputs ? fw_num_units + bw_num_units : bw_num_units;

  if (time_major) {
    for (int t = 0; t < max_time; t++) {
      // Forward cell.
      float* fw_hidden_state_ptr_batch = GetTensorData<float>(fw_hidden_state);
      for (int s = 0; s < max_time; s++) {
        const float* input_ptr_batch =
            GetTensorData<float>(input) + s * input_size * batch_size;
        const float* aux_input_ptr_batch =
            (aux_input != nullptr)
                ? GetTensorData<float>(aux_input) + s * input_size * batch_size
                : nullptr;
        float* output_ptr_batch =
            GetTensorData<float>(fw_output) + s * fw_output_step * batch_size;

        kernel_utils::RnnBatchStep(
            input_ptr_batch, fw_input_weights_ptr, fw_input_weights_scale,
            aux_input_ptr_batch, aux_fw_input_weights_ptr,
            aux_fw_input_weights_scale, fw_recurrent_weights_ptr,
            fw_recurrent_weights_scale, fw_bias_ptr, input_size, aux_input_size,
            fw_num_units, batch_size, fw_output_step, params->activation,
            quantized_input_ptr, aux_quantized_input_ptr,
            fw_quantized_hidden_state_ptr, scaling_factors_ptr,
            fw_hidden_state_ptr_batch, output_ptr_batch,
            params->asymmetric_quantize_inputs, zero_points_ptr,
            accum_scratch_ptr, fw_row_sums_ptr, fw_compute_row_sums);
      }
      // Backward cell.
      float* bw_hidden_state_ptr_batch = GetTensorData<float>(bw_hidden_state);
      for (int s = max_time - 1; s >= 0; s--) {
        const float* input_ptr_batch =
            GetTensorData<float>(bw_input) + s * input_size * batch_size;
        const float* aux_input_ptr_batch =
            (aux_input != nullptr)
                ? GetTensorData<float>(aux_input) + s * input_size * batch_size
                : nullptr;
        float* output_ptr_batch =
            (params->merge_outputs
                 ? GetTensorData<float>(fw_output) + fw_num_units
                 : GetTensorData<float>(bw_output)) +
            s * bw_output_step * batch_size;

        kernel_utils::RnnBatchStep(
            input_ptr_batch, bw_input_weights_ptr, bw_input_weights_scale,
            aux_input_ptr_batch, aux_bw_input_weights_ptr,
            aux_bw_input_weights_scale, bw_recurrent_weights_ptr,
            bw_recurrent_weights_scale, bw_bias_ptr, input_size, aux_input_size,
            bw_num_units, batch_size, bw_output_step, params->activation,
            quantized_input_ptr, aux_quantized_input_ptr,
            bw_quantized_hidden_state_ptr, scaling_factors_ptr,
            bw_hidden_state_ptr_batch, output_ptr_batch,
            params->asymmetric_quantize_inputs, zero_points_ptr,
            accum_scratch_ptr, bw_row_sums_ptr, bw_compute_row_sums);
      }
    }
  } else {
    for (int b = 0; b < batch_size; b++) {
      // Forward cell.
      float* fw_hidden_state_ptr_batch =
          GetTensorData<float>(fw_hidden_state) + b * fw_num_units;
      float* fw_output_offset =
          GetTensorData<float>(fw_output) + b * fw_output_step * max_time;
      for (int s = 0; s < max_time; s++) {
        const float* input_ptr_batch = GetTensorData<float>(input) +
                                       b * input_size * max_time +
                                       s * input_size;
        const float* aux_input_ptr_batch =
            (aux_input != nullptr)
                ? GetTensorData<float>(aux_input) + b * input_size * max_time +
                      s * input_size
                : nullptr;
        float* output_ptr_batch = fw_output_offset + s * fw_output_step;

        kernel_utils::RnnBatchStep(
            input_ptr_batch, fw_input_weights_ptr, fw_input_weights_scale,
            aux_input_ptr_batch, aux_fw_input_weights_ptr,
            aux_fw_input_weights_scale, fw_recurrent_weights_ptr,
            fw_recurrent_weights_scale, fw_bias_ptr, input_size, aux_input_size,
            fw_num_units, /*batch_size=*/1, fw_output_step, params->activation,
            quantized_input_ptr, aux_quantized_input_ptr,
            fw_quantized_hidden_state_ptr, scaling_factors_ptr,
            fw_hidden_state_ptr_batch, output_ptr_batch,
            params->asymmetric_quantize_inputs, zero_points_ptr,
            accum_scratch_ptr, fw_row_sums_ptr, fw_compute_row_sums);
      }
      // Backward cell.
      float* bw_hidden_state_ptr_batch =
          GetTensorData<float>(bw_hidden_state) + b * bw_num_units;
      float* bw_output_offset =
          params->merge_outputs
              ? GetTensorData<float>(fw_output) +
                    b * bw_output_step * max_time + fw_num_units
              : GetTensorData<float>(bw_output) + b * bw_output_step * max_time;
      for (int s = max_time - 1; s >= 0; s--) {
        const float* input_ptr_batch = GetTensorData<float>(input) +
                                       b * input_size * max_time +
                                       s * input_size;
        const float* aux_input_ptr_batch =
            (aux_input != nullptr)
                ? GetTensorData<float>(aux_input) + b * input_size * max_time +
                      s * input_size
                : nullptr;
        float* output_ptr_batch = bw_output_offset + s * bw_output_step;

        kernel_utils::RnnBatchStep(
            input_ptr_batch, bw_input_weights_ptr, bw_input_weights_scale,
            aux_input_ptr_batch, aux_bw_input_weights_ptr,
            aux_bw_input_weights_scale, bw_recurrent_weights_ptr,
            bw_recurrent_weights_scale, bw_bias_ptr, input_size, aux_input_size,
            bw_num_units, /*batch_size=*/1, bw_output_step, params->activation,
            quantized_input_ptr, aux_quantized_input_ptr,
            bw_quantized_hidden_state_ptr, scaling_factors_ptr,
            bw_hidden_state_ptr_batch, output_ptr_batch,
            params->asymmetric_quantize_inputs, zero_points_ptr,
            accum_scratch_ptr, bw_row_sums_ptr, bw_compute_row_sums);
      }
    }
  }
  return kTfLiteOk;
}