inline void LogSoftmax(const SoftmaxParams& params, float input_scale,
                       const RuntimeShape& input_shape, const T* input_data,
                       const RuntimeShape& output_shape, T* output_data) {
  ruy::profiler::ScopeLabel label("LogSoftmax");
  const int trailing_dim = input_shape.DimensionsCount() - 1;
  const int excluding_last_dim =
      MatchingFlatSizeSkipDim(input_shape, trailing_dim, output_shape);
  const int last_dim =
      MatchingDim(input_shape, trailing_dim, output_shape, trailing_dim);

  const int32_t clamp_max = std::numeric_limits<T>::max();
  const int32_t clamp_min = std::numeric_limits<T>::min();

  for (int i = 0; i < excluding_last_dim; ++i) {
    T max_val = std::numeric_limits<T>::min();
    // Find max quantized value.
    for (int j = 0; j < last_dim; ++j) {
      max_val = std::max(max_val, input_data[j]);
    }

    float sum_exp = 0.0f;
    const int32_t max_uint8 = std::numeric_limits<uint8>::max();
    // Offset into table to compute exp(scale*(x - xmax)) instead of
    // exp(scale*(x)) to prevent overflow.
    const float* table_offset = &params.table[max_uint8 - max_val];
    // Calculate sum(exp(scale*(x - x_max))).
    for (int j = 0; j < last_dim; ++j) {
      sum_exp += table_offset[input_data[j]];
    }
    const float log_sum_exp = std::log(sum_exp);

    // params.scale is the output scale.
    const float scale = input_scale / params.scale;
    const float precomputed =
        (input_scale * max_val + log_sum_exp) / params.scale;
    for (int j = 0; j < last_dim; ++j) {
      // Equivalent to (input_scale * (input_data[j] - max_val) - log_sum_exp) /
      // output_scale.
      const float log_prob = scale * input_data[j] - precomputed;

      // TODO(tflite): look into better solution.
      // Use std::rint over std::round (which is used in
      // FakeQuant) since it's multiple times faster on tested arm32.
      const int32_t prob_quantized = std::rint(log_prob) + params.zero_point;
      output_data[j] = static_cast<T>(
          std::max(std::min(clamp_max, prob_quantized), clamp_min));
    }
    input_data += last_dim;
    output_data += last_dim;
  }
}
inline void LogSoftmax(const SoftmaxParams& params, float input_scale,
                       const RuntimeShape& input_shape, const T* input_data,
                       const RuntimeShape& output_shape, T* output_data) {
  ruy::profiler::ScopeLabel label("LogSoftmax");
  const int trailing_dim = input_shape.DimensionsCount() - 1;
  const int excluding_last_dim =
      MatchingFlatSizeSkipDim(input_shape, trailing_dim, output_shape);
  const int last_dim =
      MatchingDim(input_shape, trailing_dim, output_shape, trailing_dim);

  const int32_t clamp_max = std::numeric_limits<T>::max();
  const int32_t clamp_min = std::numeric_limits<T>::min();

  for (int i = 0; i < excluding_last_dim; ++i) {
    T max_val = std::numeric_limits<T>::min();
     
    for (int j = 0; j < last_dim; ++j) {
      max_val = std::max(max_val, input_data[j]);
    }

    float sum_exp = 0.0f;
    const int32_t max_uint8 = std::numeric_limits<uint8>::max();
     
     
    const float* table_offset = &params.table[max_uint8 - max_val];
     
    for (int j = 0; j < last_dim; ++j) {
      sum_exp += table_offset[input_data[j]];
    }
    const float log_sum_exp = std::log(sum_exp);

     
    const float scale = input_scale / params.scale;
    const float precomputed =
        (input_scale * max_val + log_sum_exp) / params.scale;
    for (int j = 0; j < last_dim; ++j) {
       
       
      const float log_prob = scale * input_data[j] - precomputed;

       
       
       
      const int32_t prob_quantized = std::rint(log_prob) + params.zero_point;
      output_data[j] = static_cast<T>(
          std::max(std::min(clamp_max, prob_quantized), clamp_min));
    }
    input_data += last_dim;
    output_data += last_dim;
  }
}