/*
 * Copyright (c) 2022-2023, NVIDIA CORPORATION. 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 <cuda.h>

#ifndef SWIN_TRANSFORMER_INT8_PLUGIN_H
#define SWIN_TRANSFORMER_INT8_PLUGIN_H

#include "NvInfer.h"
#include "NvInferPlugin.h"
#include "NvInferRuntime.h"
#include "cublas_v2.h"
#include <string>
#include <vector>

#include "src/fastertransformer/models/vit_int8/ViTINT8.h"
#include "src/fastertransformer/utils/allocator.h"

namespace fastertransformer {

namespace {
static const char* VIT_PLUGIN_VERSION{"1"};
static const char* VIT_PLUGIN_NAME{"CustomVisionTransformerINT8Plugin"};
}  // namespace

struct ViTINT8Settings {
    size_t        max_batch_size = 32;
    size_t        img_size       = 224;
    size_t        chn_num        = 3;
    size_t        patch_size     = 16;
    size_t        embed_dim      = 768;
    size_t        head_num       = 12;
    size_t        inter_size     = embed_dim * 4;
    size_t        num_layer      = 12;
    bool          with_cls_token = true;
    bool          is_fp16        = false;
    int           sm             = -1;
    float         q_scaling      = 1.0f;
    AttentionType attention_type = AttentionType::UNFUSED_MHA;
    // runtime param
    size_t seq_len   = 0;
    int    int8_mode = 2;
};

template<typename T>
class VisionTransformerINT8Plugin: public nvinfer1::IPluginV2DynamicExt {
private:
    const std::string layer_name_;
    std::string       namespace_;

    cublasHandle_t                                     cublas_handle_      = nullptr;
    cublasLtHandle_t                                   cublaslt_handle_    = nullptr;
    cudnnHandle_t                                      cudnn_handle_       = nullptr;
    ViTINT8Weight<T>*                                  params_             = nullptr;
    ViTTransformerINT8<T>*                             vit_transformer_    = nullptr;
    std::mutex*                                        cublasWrapperMutex_ = nullptr;
    cublasAlgoMap*                                     cublasAlgoMap_      = nullptr;
    fastertransformer::Allocator<AllocatorType::CUDA>* allocator_          = nullptr;
    cublasINT8MMWrapper*                               cublas_wrapper_     = nullptr;

    ViTINT8Settings settings_;

public:
    int sm_;
    VisionTransformerINT8Plugin(const std::string&           name,
                                const int                    max_batch,
                                const int                    img_size,
                                const int                    patch_size,
                                const int                    in_chans,
                                const int                    embed_dim,
                                const int                    num_heads,
                                const int                    inter_size,
                                const int                    layer_num,
                                const float                  q_scaling,
                                const bool                   with_cls_token,
                                const int                    int8_mode,
                                const std::vector<const T*>& w);

    VisionTransformerINT8Plugin(const std::string& name, const void* data, size_t length);
    VisionTransformerINT8Plugin(const VisionTransformerINT8Plugin<T>& plugin);
    VisionTransformerINT8Plugin() = delete;

    ~VisionTransformerINT8Plugin();

    // IPluginV2DynamicExt Methods
    nvinfer1::IPluginV2DynamicExt* clone() const noexcept override;
    nvinfer1::DimsExprs            getOutputDimensions(int                        outputIndex,
                                                       const nvinfer1::DimsExprs* inputs,
                                                       int                        nbInputs,
                                                       nvinfer1::IExprBuilder&    exprBuilder) noexcept override;
    bool                           supportsFormatCombination(int                               pos,
                                                             const nvinfer1::PluginTensorDesc* inOut,
                                                             int                               nbInputs,
                                                             int                               nbOutputs) noexcept override;
    void                           configurePlugin(const nvinfer1::DynamicPluginTensorDesc* in,
                                                   int                                      nbInputs,
                                                   const nvinfer1::DynamicPluginTensorDesc* out,
                                                   int                                      nbOutputs) noexcept override;
    size_t                         getWorkspaceSize(const nvinfer1::PluginTensorDesc* inputs,
                                                    int                               nbInputs,
                                                    const nvinfer1::PluginTensorDesc* outputs,
                                                    int                               nbOutputs) const noexcept override;
    int                            enqueue(const nvinfer1::PluginTensorDesc* inputDesc,
                                           const nvinfer1::PluginTensorDesc* outputDesc,
                                           const void* const*                inputs,
                                           void* const*                      outputs,
                                           void*                             workspace,
                                           cudaStream_t                      stream) noexcept override;

    // IPluginV2Ext Methods
    nvinfer1::DataType
    getOutputDataType(int index, const nvinfer1::DataType* inputTypes, int nbInputs) const noexcept override;

    // IPluginV2 Methods
    const char* getPluginType() const noexcept override;
    const char* getPluginVersion() const noexcept override;
    int         getNbOutputs() const noexcept override;
    int         initialize() noexcept override;
    void        terminate() noexcept override;
    size_t      getSerializationSize() const noexcept override;
    void        serialize(void* buffer) const noexcept override;
    void        destroy() noexcept override;
    void        setPluginNamespace(const char* pluginNamespace) noexcept override;
    const char* getPluginNamespace() const noexcept override;

private:
    void Init(const std::vector<const T*>& w);
};

class VisionTransformerINT8PluginCreator: public nvinfer1::IPluginCreator {
public:
    VisionTransformerINT8PluginCreator();

    const char* getPluginName() const noexcept override;

    const char* getPluginVersion() const noexcept override;

    const nvinfer1::PluginFieldCollection* getFieldNames() noexcept override;

    nvinfer1::IPluginV2* createPlugin(const char* name, const nvinfer1::PluginFieldCollection* fc) noexcept override;

    nvinfer1::IPluginV2*
    deserializePlugin(const char* name, const void* serialData, size_t serialLength) noexcept override;

    void setPluginNamespace(const char* pluginNamespace) noexcept override;

    const char* getPluginNamespace() const noexcept override;

private:
    static nvinfer1::PluginFieldCollection    mFC;
    static std::vector<nvinfer1::PluginField> mPluginAttributes;
    std::string                               namespace_;
};

}  // namespace fastertransformer
#endif  // SWIN_TRANSFORMER_INT8_PLUGIN_H
