// Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <gtest/gtest.h>

#include <cstdint>
#include <vector>

#include "paddle/fluid/pir/dialect/operator/ir/op_dialect.h"
#include "paddle/fluid/pir/dialect/operator/ir/pd_op.h"
#include "paddle/fluid/pir/transforms/fusion/attention_fuse_pass.h"
#include "paddle/pir/core/builtin_dialect.h"
#include "paddle/pir/pass/pass_manager.h"

void BuildProgram(pir::Builder &builder) {  // NOLINT
  paddle::dialect::FullOp matmul_1_in_1 =
      builder.Build<paddle::dialect::FullOp>(std::vector<int64_t>{1, 300, 256},
                                             0.9,
                                             phi::DataType::FLOAT32,
                                             phi::CPUPlace());
  // The first path to matmul with scale (q).
  paddle::dialect::FullOp matmul_1_in_2 =
      builder.Build<paddle::dialect::FullOp>(std::vector<int64_t>{256, 256},
                                             1.1,
                                             phi::DataType::FLOAT32,
                                             phi::CPUPlace());

  paddle::dialect::MatmulOp matmul_1 = builder.Build<paddle::dialect::MatmulOp>(
      matmul_1_in_1.out(), matmul_1_in_2.out(), false, false);

  paddle::dialect::FullOp add_1_in_2 = builder.Build<paddle::dialect::FullOp>(
      std::vector<int64_t>{256}, 1.5, phi::DataType::FLOAT32, phi::CPUPlace());

  paddle::dialect::AddOp add_1 =
      builder.Build<paddle::dialect::AddOp>(matmul_1.out(), add_1_in_2.out());

  paddle::dialect::ReshapeOp reshape_1 =
      builder.Build<paddle::dialect::ReshapeOp>(
          add_1.out(), std::vector<int64_t>{0, 0, 8, 32});

  paddle::dialect::TransposeOp transpose_1 =
      builder.Build<paddle::dialect::TransposeOp>(reshape_1.out(),
                                                  std::vector<int>{0, 2, 1, 3});

  paddle::dialect::ScaleOp scale_op = builder.Build<paddle::dialect::ScaleOp>(
      transpose_1.out(), 0.1767766922712326, 0.0, true);

  // The second path to matmul (k).
  paddle::dialect::FullOp matmul_2_in_2 =
      builder.Build<paddle::dialect::FullOp>(std::vector<int64_t>{256, 256},
                                             1.1,
                                             phi::DataType::FLOAT32,
                                             phi::CPUPlace());

  paddle::dialect::MatmulOp matmul_2 = builder.Build<paddle::dialect::MatmulOp>(
      matmul_1_in_1.out(), matmul_2_in_2.out(), false, false);

  paddle::dialect::FullOp add_2_in_2 = builder.Build<paddle::dialect::FullOp>(
      std::vector<int64_t>{256}, 1.5, phi::DataType::FLOAT32, phi::CPUPlace());
  paddle::dialect::AddOp add_op2 =
      builder.Build<paddle::dialect::AddOp>(matmul_2.out(), add_2_in_2.out());

  paddle::dialect::ReshapeOp reshape_2 =
      builder.Build<paddle::dialect::ReshapeOp>(
          add_op2.out(), std::vector<int64_t>{0, 0, 8, 32});

  paddle::dialect::TransposeOp transpose_2 =
      builder.Build<paddle::dialect::TransposeOp>(reshape_2.out(),
                                                  std::vector<int>{0, 2, 1, 3});

  // The third path to matmul (v).
  paddle::dialect::FullOp matmul_3_in_2 =
      builder.Build<paddle::dialect::FullOp>(std::vector<int64_t>{256, 256},
                                             1.1,
                                             phi::DataType::FLOAT32,
                                             phi::CPUPlace());
  paddle::dialect::MatmulOp matmul_3 = builder.Build<paddle::dialect::MatmulOp>(
      matmul_1_in_1.out(), matmul_3_in_2.out(), false, false);

  paddle::dialect::FullOp add_3_in_2 = builder.Build<paddle::dialect::FullOp>(
      std::vector<int64_t>{256}, 1.5, phi::DataType::FLOAT32, phi::CPUPlace());

  paddle::dialect::AddOp add_3 =
      builder.Build<paddle::dialect::AddOp>(matmul_3.out(), add_3_in_2.out());

  paddle::dialect::ReshapeOp reshape_3 =
      builder.Build<paddle::dialect::ReshapeOp>(
          add_3.out(), std::vector<int64_t>{0, 0, 8, 32});

  paddle::dialect::TransposeOp transpose_3 =
      builder.Build<paddle::dialect::TransposeOp>(reshape_3.out(),
                                                  std::vector<int>{0, 2, 1, 3});

  // softmax(qk)v
  paddle::dialect::MatmulOp matmul_4 = builder.Build<paddle::dialect::MatmulOp>(
      scale_op.out(), transpose_2.out(), false, true);

  paddle::dialect::FullOp add_4_in_2 = builder.Build<paddle::dialect::FullOp>(
      std::vector<int64_t>{1, 8, 300, 300},
      1.5,
      phi::DataType::FLOAT32,
      phi::CPUPlace());

  paddle::dialect::AddOp add_4 =
      builder.Build<paddle::dialect::AddOp>(matmul_4.out(), add_4_in_2.out());

  paddle::dialect::SoftmaxOp softmax_op =
      builder.Build<paddle::dialect::SoftmaxOp>(add_4.out(), -1);
  paddle::dialect::MatmulOp matmul_5 = builder.Build<paddle::dialect::MatmulOp>(
      softmax_op.out(), transpose_3.out(), false, false);

  paddle::dialect::TransposeOp transpose_4 =
      builder.Build<paddle::dialect::TransposeOp>(matmul_5.out(),
                                                  std::vector<int>{0, 2, 1, 3});

  paddle::dialect::ReshapeOp reshape_4 =
      builder.Build<paddle::dialect::ReshapeOp>(
          transpose_4.out(), std::vector<int64_t>{0, 0, 256});

  builder.Build<paddle::dialect::FetchOp>(reshape_4.out(), "out", 0);
}

TEST(DrrTest, AttentionFuse) {
  pir::IrContext *ctx = pir::IrContext::Instance();
  ctx->GetOrRegisterDialect<paddle::dialect::OperatorDialect>();
  ctx->GetOrRegisterDialect<pir::BuiltinDialect>();
  pir::Program program(ctx);
  pir::Builder builder = pir::Builder(ctx, program.block());
  BuildProgram(builder);
  EXPECT_EQ(program.block()->size(), 33u);

  pir::PassManager pm(ctx);
  pm.AddPass(pir::CreateAttentionFusePass());
  pm.EnableIRPrinting();

  CHECK_EQ(pm.Run(&program), true);
  EXPECT_EQ(program.block()->size(), 20u);
}
