/*
 * SPDX-FileCopyrightText: Copyright (c) 1993-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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.
 */

// Need 10.1 for cublasGemmStridedBatchedEx
#include <cuda.h>
#if CUDA_VERSION >= 10010

#include "NvInfer.h"
#include "bertQKVToContextPlugin/fused_multihead_attention/fused_multihead_attention.h"
#include "bertQKVToContextPlugin/fused_multihead_attention_v2/fused_multihead_attention_v2.h"
#include "common/bertCommon.h"
#include "common/serialize.hpp"
#include "mhaRunner.h"
#include "qkvToContextPlugin.h"

#include <cstdint>
#include <cstring>
#include <iostream>
#include <string_view>
#include <tuple>
#include <vector>

using namespace nvinfer1;
using namespace nvinfer1::plugin;
using namespace nvinfer1::plugin::bert;
using namespace nvinfer1::pluginInternal;

namespace
{
using namespace std::string_view_literals;
char const* const kQKV_TO_CONTEXT_PLUGIN_VERSION{"4"};
char const* const kQKV_TO_CONTEXT_VAR_SEQLEN_PLUGIN_VERSION{"5"};
char const* const kQKV_TO_CONTEXT_PLUGIN_NAME{"CustomQKVToContextPluginDynamic"};
} // namespace

REGISTER_TENSORRT_PLUGIN(QKVToContextPluginDynamicCreator);

constexpr uint32_t kIIDX = 0; // index of the input tensor
constexpr uint32_t kMIDX = 1; // index of the mask

REGISTER_TENSORRT_PLUGIN(QKVToContextVarSeqlenPluginCreator);
QKVToContextPluginDynamic::~QKVToContextPluginDynamic() {}

QKVToContextPluginDynamic::QKVToContextPluginDynamic(const std::string name, const DataType type,
    const int32_t hiddenSize, const int32_t numHeads, float const dqProbs, bool hasImask)
    : mLayerName(name)
    , mS(0)
    , mB(0)
    , mHeadSize(hiddenSize / numHeads)
    , mHiddenSize(hiddenSize)
    , mNumHeads(numHeads)
    , mType(type)
    , mDqProbs(dqProbs)

{
    mHasImask = static_cast<int32_t>(hasImask);
    mSM = getSmVersion();
}

QKVToContextPluginDynamic::QKVToContextPluginDynamic(const std::string name, const DataType type, const int32_t S,
    const int32_t B, const int32_t SM, const int32_t hiddenSize, const int32_t numHeads, float const dqProbs,
    bool hasImask, bool hasUnfusedDispatcher, void const* runnerStateBuffer)
    : mLayerName(name)
    , mS(S)
    , mB(B)
    , mSM(SM)
    , mHeadSize(hiddenSize / numHeads)
    , mHiddenSize(hiddenSize)
    , mNumHeads(numHeads)
    , mType(type)
    , mDqProbs(dqProbs)

{
    BERT_DEBUG_MSG("MHA Runner Deser");
    mHasImask = static_cast<int32_t>(hasImask);
    mHasUnfusedDispatcher = static_cast<int32_t>(hasUnfusedDispatcher);

    createMHARunner();

    if (hasUnfusedDispatcher)
    {
        PLUGIN_ASSERT(unfusedDispatcher.get());
        PLUGIN_ASSERT(runnerStateBuffer != nullptr);
        auto length = unfusedDispatcher->getSerializationSize();
        unfusedDispatcher->deserialize(runnerStateBuffer, length);
    }

    BERT_DEBUG_MSG("MHA Runner Deser Done");
}


IPluginCapability* QKVToContextPluginDynamic::getCapabilityInterface(PluginCapabilityType type) noexcept
{
    try
    {
        if (type == PluginCapabilityType::kBUILD)
        {
            return static_cast<IPluginV3OneBuild*>(this);
        }
        if (type == PluginCapabilityType::kRUNTIME)
        {
            return static_cast<IPluginV3OneRuntime*>(this);
        }
        PLUGIN_ASSERT(type == PluginCapabilityType::kCORE);
        return static_cast<IPluginV3OneCore*>(this);
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return nullptr;
}

void QKVToContextPluginDynamic::createMHARunner()
{
    if (!fusedDispatcher.get())
    {
        if (mType == DataType::kHALF)
        {
            fusedDispatcher.reset(new FusedMHARunnerFP16(mNumHeads, mSM));
        }
        else if (mType == DataType::kINT8)
        {
            fusedDispatcher.reset(new FusedMHARunnerInt8(mNumHeads, mSM, mDqProbs));
        }
    }

    if (!unfusedDispatcher.get())
    {
        unfusedDispatcher.reset(new UnfusedMHARunner(mType, mNumHeads, mSM));
    }
}

IPluginV3* QKVToContextPluginDynamic::clone() noexcept
{
    BERT_DEBUG_MSG("QKV Clone");

    mHasUnfusedDispatcher = 0;
    void const* bufferData = nullptr;
    // the workspacesize is 0 if we have not call setup the dispatcher yet.
    if (unfusedDispatcher.get() && unfusedDispatcher->getWorkspaceSize())
    {
        mHasUnfusedDispatcher = 1;
        mRunnerStateBuffer.resize(unfusedDispatcher->getSerializationSize());
        unfusedDispatcher->serialize(mRunnerStateBuffer.data());
        bufferData = mRunnerStateBuffer.data();
    }

    auto ret = std::make_unique<QKVToContextPluginDynamic>(mLayerName, mType, mS, mB, mSM, mHiddenSize, mNumHeads,
        mDqProbs, static_cast<bool>(mHasImask), mHasUnfusedDispatcher, bufferData);
    ret->setPluginNamespace(mNamespace.c_str());
    BERT_DEBUG_MSG("QKV Clone done");
    return ret.release();
}

int32_t QKVToContextPluginDynamic::getOutputShapes(DimsExprs const* inputs, int32_t nbInputs,
    DimsExprs const* shapeInputs, int32_t nbShapeInputs, DimsExprs* outputs, int32_t nbOutputs,
    IExprBuilder& exprBuilder) noexcept
{
    try
    {
        PLUGIN_ASSERT(inputs != nullptr);
        PLUGIN_ASSERT(nbInputs == 1 + mHasImask);
        PLUGIN_ASSERT(nbShapeInputs == 0);
        PLUGIN_ASSERT(outputs != nullptr);
        PLUGIN_ASSERT(nbOutputs == 1);
        // Input is BxSx3*N*H, output should be BxSxN*H
        // Copy over everything
        outputs[kIIDX] = inputs[kIIDX];
        // Divide last dim by three
        auto const* three = exprBuilder.constant(3);
        outputs[kIIDX].d[HDIM] = exprBuilder.operation(DimensionOperation::kFLOOR_DIV, *inputs[kIIDX].d[HDIM], *three);
        return pluginStatus_t::STATUS_SUCCESS;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return pluginStatus_t::STATUS_FAILURE;
}

// NOLINTNEXTLINE(readability-function-cognitive-complexity)
bool QKVToContextPluginDynamic::supportsFormatCombination(
    int32_t pos, DynamicPluginTensorDesc const* inOut, int32_t nbInputs, int32_t /*nbOutputs*/) noexcept
{
    PLUGIN_ASSERT(pos >= 0);
    PLUGIN_ASSERT(pos < 2 + mHasImask);
    PLUGIN_ASSERT(nbInputs == 1 + mHasImask);
    auto const* in = inOut;
    auto const* out = inOut + nbInputs;
    int32_t packedSize = getMHAMaskPackedSize(mSM, mType, in->desc.dims.d[SDIM]);

    // we only support int8 IO in fused mha runner, and we only support fused mha runner on Xavier, Turing and Ampere
    if (mType == DataType::kINT8)
    {
        if (!elem(mSM, {kSM_75, kSM_80, kSM_86, kSM_87, kSM_89, kSM_90, kSM_100, kSM_120}))
        {
            gLogError << "INT8 IO is only supported on Turing, Ampere, Hopper and Blackwell for plugin "
                      << kQKV_TO_CONTEXT_PLUGIN_NAME << std::endl;
            return false;
        }
        if (in->desc.dims.d[SDIM] == -1)
        {
            gLogError << "INT8 IO not support dynamic shape in sequence dimension for plugin "
                      << kQKV_TO_CONTEXT_PLUGIN_NAME << std::endl;
            return false;
        }
        if (packedSize == unfusedMaskSize)
        {
            gLogError << "INT8 IO only support sequence length 128,384 for plugin " << kQKV_TO_CONTEXT_PLUGIN_NAME
                      << std::endl;
            return false;
        }
    }

    if (pos == 0)
    {
        bool isFormatSupported = in->desc.format == TensorFormat::kLINEAR;
        if (mType == DataType::kINT8)
        {
            if (in->desc.dims.d[HDIM] % 32U == 0)
            {
                isFormatSupported = in->desc.format == TensorFormat::kCHW32;
            }
            else
            {
                isFormatSupported = in->desc.format == TensorFormat::kCHW4;
            }
        }

        // must not check descriptions > pos
        return (in->desc.type == mType) &&         // precision
            isFormatSupported &&                   // format
            (in->desc.dims.nbDims == 5) &&         // num dims
            ((in->desc.dims.d[HDIM] % 3U) == 0) && // see getOutputDimensions
            ((in->desc.dims.d[3]) == 1) &&         // for fc
            ((in->desc.dims.d[4]) == 1)            // for fc
            ;
    }

    // pos==1
    if ((mHasImask && pos == 1)) // pos 1 is the mask
    {
        auto const* inMask = &inOut[1].desc;
        if (inMask->dims.d[1] != -1 && inMask->dims.d[1] != packedSize)
        {
            gLogError << "CustomEmbLayerNormPluginDynamic returned mask with pack size " << inMask->dims.d[1]
                      << ", but " << kQKV_TO_CONTEXT_PLUGIN_NAME << " expects mask pack size " << packedSize
                      << std::endl;
            return false;
        }

        // detect full mask and check that it was produced
        return (inMask->type == DataType::kINT32) &&     // precision
            (inMask->format == TensorFormat::kLINEAR) && // format
            (inMask->dims.nbDims == 2) &&                // Bx2*maskSize
            (inMask->dims.d[0] == in->desc.dims.d[BDIM]);
    }

    if (!mHasImask || pos == 2) // output pos
    {
        bool isFormatSupported = out->desc.format == TensorFormat::kLINEAR;
        if (mType == DataType::kINT8)
        {
            if (out->desc.dims.d[HDIM] % 32U == 0)
            {
                isFormatSupported = out->desc.format == TensorFormat::kCHW32;
            }
            else
            {
                isFormatSupported = out->desc.format == TensorFormat::kCHW4;
            }
        }

        return (in->desc.type == out->desc.type) &&                      // precision
            isFormatSupported &&                                         // format
            (out->desc.dims.nbDims == 5) &&                              // num dims
            ((in->desc.dims.d[HDIM] / 3) == (out->desc.dims.d[HDIM])) && // div 3
            ((out->desc.dims.d[3]) == 1) &&                              // for fc
            ((out->desc.dims.d[4]) == 1) &&                              // for fc
            ((out->desc.dims.d[BDIM]) == in->desc.dims.d[BDIM]) &&       // check B
            ((out->desc.dims.d[SDIM]) == in->desc.dims.d[SDIM])          // check S
            ;
    }

    return false;
}

int32_t QKVToContextPluginDynamic::onShapeChange(
    PluginTensorDesc const* in, int32_t nbInputs, PluginTensorDesc const* out, int32_t nbOutputs) noexcept
{
    try
    {
        PLUGIN_ASSERT(in != nullptr);
        PLUGIN_ASSERT(nbInputs == 1 + mHasImask);
        PLUGIN_ASSERT(nbOutputs == 1);
        PluginTensorDesc const& inDesc = in[kIIDX];
        TRT_UNUSED inDesc;
        PLUGIN_ASSERT(out != nullptr);
        PluginTensorDesc const& outDesc = out[0];
        TRT_UNUSED outDesc;
        PLUGIN_ASSERT(mType == inDesc.type);
        PLUGIN_ASSERT(mType == outDesc.type);
        PLUGIN_ASSERT(inDesc.dims.d[BDIM] == outDesc.dims.d[BDIM]);
        PLUGIN_ASSERT(inDesc.dims.d[SDIM] == outDesc.dims.d[SDIM]);
        PLUGIN_ASSERT(inDesc.dims.d[HDIM] == 3 * outDesc.dims.d[HDIM]);
        if (mHasImask)
        {
            PluginTensorDesc const& maskDesc = in[kMIDX];
            TRT_UNUSED maskDesc;
            PLUGIN_ASSERT(maskDesc.dims.d[0] == inDesc.dims.d[BDIM]);
        }

        createMHARunner();

        // mS and mB that are set by configurePlugin() may be stale
        mS = inDesc.dims.d[SDIM];
        mB = inDesc.dims.d[BDIM];
        PLUGIN_ASSERT(mS);
        PLUGIN_ASSERT(mB);
        if (fusedDispatcher.get() && fusedDispatcher->isValid(mHeadSize, mS))
        {
            fusedDispatcher->setup(mS, mB, mHeadSize);
        }
        else
        {
            unfusedDispatcher->setup(mS, mB, mHeadSize);
        }

        return pluginStatus_t::STATUS_SUCCESS;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return pluginStatus_t::STATUS_FAILURE;
}

int32_t QKVToContextPluginDynamic::configurePlugin(
    DynamicPluginTensorDesc const* in, int32_t nbInputs, DynamicPluginTensorDesc const* out, int32_t nbOutputs) noexcept
{
    try
    {
        PLUGIN_ASSERT(in != nullptr);
        PLUGIN_ASSERT(nbInputs == 1 + mHasImask);
        PLUGIN_ASSERT(nbOutputs == 1);
        PluginTensorDesc const& inDesc = in[kIIDX].desc;
        TRT_UNUSED inDesc;
        PLUGIN_ASSERT(out != nullptr);
        PluginTensorDesc const& outDesc = out->desc;
        TRT_UNUSED outDesc;
        PLUGIN_ASSERT(mType == inDesc.type);
        PLUGIN_ASSERT(mType == outDesc.type);
        PLUGIN_ASSERT(inDesc.dims.d[BDIM] == outDesc.dims.d[BDIM]);
        PLUGIN_ASSERT(inDesc.dims.d[SDIM] == outDesc.dims.d[SDIM]);
        PLUGIN_ASSERT(inDesc.dims.d[HDIM] == 3 * outDesc.dims.d[HDIM]);
        if (mHasImask)
        {
            PluginTensorDesc const& maskDesc = in[kMIDX].desc;
            TRT_UNUSED maskDesc;
            PLUGIN_ASSERT(maskDesc.dims.d[0] == inDesc.dims.d[BDIM]);
        }

        createMHARunner();

        const int32_t S = inDesc.dims.d[SDIM];
        const int32_t B = inDesc.dims.d[BDIM] <= 0 ? in->max.d[BDIM] : inDesc.dims.d[BDIM];
        if (S <= 0)
        {
            // in dynamic shape build stage, we setup with max sequence that cannot fused
            const int32_t Smin = in->min.d[SDIM];
            const int32_t Smax = in->max.d[SDIM];

            if (fusedDispatcher.get())
            {
                for (int32_t i = Smax; i >= Smin; --i)
                {
                    if (!fusedDispatcher->isValid(mHeadSize, i))
                    {
                        unfusedDispatcher->setup(i, B, mHeadSize);
                        mS = i;
                        mB = B;
                        break;
                    }
                }
            }
            else
            {
                unfusedDispatcher->setup(Smax, B, mHeadSize);
                mS = Smax;
                mB = B;
            }
        }
        else
        {
            // in inference stage or in static shape build stage
            if (fusedDispatcher.get() && fusedDispatcher->isValid(mHeadSize, S))
            {
                fusedDispatcher->setup(S, B, mHeadSize);
            }
            else
            {
                unfusedDispatcher->setup(S, B, mHeadSize);
            }
            mS = S;
            mB = B;
        }
        return pluginStatus_t::STATUS_SUCCESS;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return pluginStatus_t::STATUS_FAILURE;
}

size_t QKVToContextPluginDynamic::getWorkspaceSize(DynamicPluginTensorDesc const* /*inputs*/, int32_t /*nbInputs*/,
    DynamicPluginTensorDesc const* /*outputs*/, int32_t /*nbOutputs*/) const noexcept
{
    // only unfused kernel need workspace, and we need larger workspace for larger sequence length
    // we have already setup unfusedDispatcher with max sequence in configurePlugin
    // if unfusedDispatcher is not initialized in configurePlugin
    PLUGIN_ASSERT(unfusedDispatcher.get());
    return unfusedDispatcher->getWorkspaceSize();
}

// IPluginV2Ext Methods
int32_t QKVToContextPluginDynamic::getOutputDataTypes(
    DataType* outputTypes, int32_t nbOutputs, DataType const* inputTypes, int32_t nbInputs) const noexcept
{
    try
    {
        PLUGIN_ASSERT(
            inputTypes[0] == DataType::kFLOAT || inputTypes[0] == DataType::kHALF || inputTypes[0] == DataType::kINT8);
        outputTypes[0] = inputTypes[0];
        return pluginStatus_t::STATUS_SUCCESS;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return pluginStatus_t::STATUS_FAILURE;
}

void QKVToContextPluginDynamic::setCublasResources(std::shared_ptr<CublasWrapper> cublasWrapper)
{
    mCublasWrapper = cublasWrapper;
    // The shared cublasWrapper resource owns the handle.
    // but `this` instance has a non-owning pointer to the handle.
    // Note that the cublasWrapper inits the handle and checks for nullptr
    // so we don't have to do that here.
    mCublasHandle = mCublasWrapper->getCublasHandle();
}

IPluginV3* QKVToContextPluginDynamic::attachToContext(IPluginResourceContext* context) noexcept
{
    try
    {
        auto p = static_cast<QKVToContextPluginDynamic*>(clone());
        // the clone has shared ownership of underling cublasWrapper instance
        // that is mapped to current context
        p->setCublasResources(createPluginCublasWrapper(context));
        return p;
    }
    catch (const std::exception& e)
    {
        caughtError(e);
    }
    return nullptr;
}

char const* QKVToContextPluginDynamic::getPluginVersion() const noexcept
{
    return kQKV_TO_CONTEXT_PLUGIN_VERSION;
}

int32_t QKVToContextPluginDynamic::getNbOutputs() const noexcept
{
    return 1;
}

char const* QKVToContextPluginDynamic::getPluginName() const noexcept
{
    return kQKV_TO_CONTEXT_PLUGIN_NAME;
}


void QKVToContextPluginDynamic::setPluginNamespace(char const* libNamespace) noexcept
{
    mNamespace = libNamespace;
}

char const* QKVToContextPluginDynamic::getPluginNamespace() const noexcept
{
    return mNamespace.c_str();
}

int32_t QKVToContextPluginDynamic::enqueue(PluginTensorDesc const* inputDesc, PluginTensorDesc const* outputDesc,
    void const* const* inputs, void* const* outputs, void* workspace, cudaStream_t stream) noexcept
{
    PLUGIN_VALIDATE(inputDesc != nullptr && outputDesc != nullptr && inputs != nullptr && outputs != nullptr);
    PLUGIN_ASSERT(mS == inputDesc->dims.d[SDIM]);
    PLUGIN_ASSERT(mB == inputDesc->dims.d[BDIM]);

    try
    {
        void const* const maskPtr = mHasImask ? inputs[1] : nullptr;
        if (mHasImask && fusedDispatcher.get() && fusedDispatcher->isValid(mHeadSize, inputDesc->dims.d[SDIM]))
        {
            fusedDispatcher->run(
                inputDesc[0], outputDesc[0], inputs[0], maskPtr, outputs[0], workspace, stream, mCublasHandle);
        }
        else
        {
            PLUGIN_VALIDATE(unfusedDispatcher.get(), "The Unfused MHARunner is uninitialized, no MHARunner available!");
            PLUGIN_VALIDATE(mType != DataType::kINT8, "The Unfused MHARunner does not support INT8!");
            unfusedDispatcher->run(
                inputDesc[0], outputDesc[0], inputs[0], maskPtr, outputs[0], workspace, stream, mCublasHandle);
        }
    }
    catch (std::exception const& e)
    {
        caughtError(e);
        return -1;
    }
    return 0;
}

PluginFieldCollection const* QKVToContextPluginDynamic::getFieldsToSerialize() noexcept
{
    mDataToSerialize.clear();

    mDataToSerialize.emplace_back("type_id", &mType, PluginFieldType::kINT32, 1);
    mDataToSerialize.emplace_back("hidden_size", &mHiddenSize, PluginFieldType::kINT32, 1);
    mDataToSerialize.emplace_back("num_heads", &mNumHeads, PluginFieldType::kINT32, 1);
    mDataToSerialize.emplace_back("has_mask", &mHasImask, PluginFieldType::kINT32, 1);
    mDataToSerialize.emplace_back("S", &mS, PluginFieldType::kINT32, 1);
    mDataToSerialize.emplace_back("B", &mB, PluginFieldType::kINT32, 1);
    mDataToSerialize.emplace_back("SM", &mSM, PluginFieldType::kINT32, 1);

    if (unfusedDispatcher.get() && unfusedDispatcher->getWorkspaceSize())
    {
        mHasUnfusedDispatcher = 1;
        mRunnerStateBuffer.resize(unfusedDispatcher->getSerializationSize());
        unfusedDispatcher->serialize(mRunnerStateBuffer.data());
        mDataToSerialize.emplace_back("runnerStateBuffer", (void const*) mRunnerStateBuffer.data(),
            PluginFieldType::kUNKNOWN, mRunnerStateBuffer.size());
    }
    else
    {
        mHasUnfusedDispatcher = 0;
    }

    mDataToSerialize.emplace_back("hasUnfusedDispatcher", &mHasUnfusedDispatcher, PluginFieldType::kINT32, 1);

    if (mDqProbs >= 0)
    {
        mDataToSerialize.emplace_back("dq_probs", &mDqProbs, PluginFieldType::kFLOAT32, 1);
    }

    mFCToSerialize.nbFields = mDataToSerialize.size();
    mFCToSerialize.fields = mDataToSerialize.data();

    return &mFCToSerialize;
}

QKVToContextPluginDynamicCreator::QKVToContextPluginDynamicCreator()
{
    mPluginAttributes.emplace_back(PluginField("type_id", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("hidden_size", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("num_heads", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("has_mask", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("dq_probs", nullptr, PluginFieldType::kFLOAT32, 1));

    mFC.nbFields = mPluginAttributes.size();
    mFC.fields = mPluginAttributes.data();
}

char const* QKVToContextPluginDynamicCreator::getPluginName() const noexcept
{
    return kQKV_TO_CONTEXT_PLUGIN_NAME;
}

char const* QKVToContextPluginDynamicCreator::getPluginVersion() const noexcept
{
    return kQKV_TO_CONTEXT_PLUGIN_VERSION;
}

PluginFieldCollection const* QKVToContextPluginDynamicCreator::getFieldNames() noexcept
{
    return &mFC;
}

IPluginV3* QKVToContextPluginDynamicCreator::createPlugin(
    char const* name, PluginFieldCollection const* fc, TensorRTPhase phase) noexcept
{
    try
    {
        BERT_DEBUG_MSG("Creating QKV2ContextPlugin...");
        PLUGIN_VALIDATE(fc != nullptr);
        int32_t hiddenSize = 0;
        // Since numHeads must always exist or validateRequiredAttributes will fail,
        // we can set numHeads to -1 so that static analysis tools don't warn about
        // a division by zero in QKVToContextPluginDynamic constructor.
        int32_t numHeads{-1};
        bool hasMask = false;
        int32_t typeId = -1;
        int32_t s = -1;
        int32_t b = -1;
        int32_t sm = -1;
        bool hasUnfusedDispatcher = false;
        void const* runnerStateBuffer = nullptr;
        float dqProbs = -1.0F;

        PLUGIN_VALIDATE(fc->fields != nullptr);
        if (phase == TensorRTPhase::kBUILD)
        {
            plugin::validateRequiredAttributesExist({"type_id", "hidden_size", "num_heads", "has_mask"}, fc);
        }
        else
        {
            PLUGIN_ASSERT(phase == TensorRTPhase::kRUNTIME);
            plugin::validateRequiredAttributesExist(
                {"type_id", "S", "B", "hidden_size", "num_heads", "has_mask", "SM", "hasUnfusedDispatcher"}, fc);
        }

        for (int32_t i = 0; i < fc->nbFields; i++)
        {
            PLUGIN_VALIDATE(fc->fields[i].name != nullptr);
            PLUGIN_VALIDATE(fc->fields[i].data != nullptr);
            std::string_view const field_name = fc->fields[i].name;

            if (field_name == "type_id"sv)
            {
                typeId = *static_cast<int32_t const*>(fc->fields[i].data);
                PLUGIN_VALIDATE(typeId >= 0 && typeId <= 2, ("QKV: Invalid TypeId " + std::to_string(typeId)).c_str());
                BERT_DEBUG_VALUE("Building typeId: ", typeId);
            }
            else if (field_name == "hidden_size"sv)
            {
                hiddenSize = *static_cast<int32_t const*>(fc->fields[i].data);
                PLUGIN_VALIDATE(hiddenSize > 0, ("QKV: Invalid hiddenSize " + std::to_string(hiddenSize)).c_str());
                BERT_DEBUG_VALUE("Building hiddenSize: ", hiddenSize);
            }
            else if (field_name == "num_heads"sv)
            {
                numHeads = *static_cast<int32_t const*>(fc->fields[i].data);
                PLUGIN_VALIDATE(numHeads > 0, ("QKV: Invalid numHeads " + std::to_string(numHeads)).c_str());
                BERT_DEBUG_VALUE("Building numHeads: ", numHeads);
            }
            else if (field_name == "has_mask"sv)
            {
                auto hasMaskValue = *static_cast<int32_t const*>(fc->fields[i].data);
                PLUGIN_VALIDATE(hasMaskValue == 0 || hasMaskValue == 1,
                    ("QKV: Invalid hasMask " + std::to_string(hasMaskValue)).c_str());
                hasMask = static_cast<bool>(hasMaskValue);
                BERT_DEBUG_VALUE("Building hasMask: ", hasMask);
            }
            else if (field_name == "dq_probs"sv)
            {
                dqProbs = *static_cast<float const*>(fc->fields[i].data);
                PLUGIN_VALIDATE(dqProbs > 0.0F, ("QKV: Invalid dqProbs " + std::to_string(dqProbs)).c_str());
                BERT_DEBUG_VALUE("Building dqProbs: ", dqProbs);
            }
            else if (field_name == "S"sv)
            {
                PLUGIN_ASSERT(phase == TensorRTPhase::kRUNTIME);
                s = *static_cast<int32_t const*>(fc->fields[i].data);
                BERT_DEBUG_VALUE("Building S: ", s);
            }
            else if (field_name == "B"sv)
            {
                PLUGIN_ASSERT(phase == TensorRTPhase::kRUNTIME);
                b = *static_cast<int32_t const*>(fc->fields[i].data);
                BERT_DEBUG_VALUE("Building B: ", b);
            }
            else if (field_name == "SM"sv)
            {
                PLUGIN_ASSERT(phase == TensorRTPhase::kRUNTIME);
                sm = *static_cast<int32_t const*>(fc->fields[i].data);
                BERT_DEBUG_VALUE("Building SM: ", sm);
            }
            else if (field_name == "hasUnfusedDispatcher"sv)
            {
                PLUGIN_ASSERT(phase == TensorRTPhase::kRUNTIME);
                auto hasUnfusedDispatcherValue = *static_cast<int32_t const*>(fc->fields[i].data);
                PLUGIN_VALIDATE(hasUnfusedDispatcherValue == 0 || hasUnfusedDispatcherValue == 1,
                    ("QKV: Invalid hasUnfusedDispatcher " + std::to_string(hasUnfusedDispatcherValue)).c_str());
                hasUnfusedDispatcher = static_cast<bool>(hasUnfusedDispatcherValue);
                BERT_DEBUG_VALUE("Building hasUnfusedDispatcher: ", hasUnfusedDispatcher);
            }
            else if (field_name == "runnerStateBuffer"sv)
            {
                PLUGIN_ASSERT(phase == TensorRTPhase::kRUNTIME);
                runnerStateBuffer = static_cast<void const*>(fc->fields[i].data);
            }
        }

        BERT_DEBUG_MSG("Building the Plugin...");
        auto type = static_cast<DataType>(typeId);
        if (type == DataType::kINT8 && dqProbs < 0)
        {
            BERT_DEBUG_MSG("Using default scale factor");
            dqProbs = 1.F / 127.F;
        }

        if (phase == TensorRTPhase::kBUILD)
        {
            return new QKVToContextPluginDynamic(name, type, hiddenSize, numHeads, dqProbs, hasMask);
        }

        PLUGIN_VALIDATE(s != -1, "invalid S during runtime plugin creation");
        PLUGIN_VALIDATE(b != -1, "invalid B during runtime plugin creation");
        PLUGIN_VALIDATE(sm != -1, "invalid SM during runtime plugin creation");
        if (hasUnfusedDispatcher == 1)
        {
            PLUGIN_VALIDATE(runnerStateBuffer != nullptr, "invalid runnerStateBuffer during runtime plugin creation");
        }
        else
        {
            PLUGIN_VALIDATE(runnerStateBuffer == nullptr, "invalid runnerStateBuffer during runtime plugin creation");
        }

        return new QKVToContextPluginDynamic(
            name, type, s, b, sm, hiddenSize, numHeads, dqProbs, hasMask, hasUnfusedDispatcher, runnerStateBuffer);
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return nullptr;
}


void QKVToContextPluginDynamicCreator::setPluginNamespace(char const* libNamespace) noexcept
{
    mNamespace = libNamespace;
}

char const* QKVToContextPluginDynamicCreator::getPluginNamespace() const noexcept
{
    return mNamespace.c_str();
}


///// QKVToContextVarSeqlenPlugin (CustomQKVToContextPluginDynamic v5) ////

QKVToContextVarSeqlenPlugin::~QKVToContextVarSeqlenPlugin() {}

QKVToContextVarSeqlenPlugin::QKVToContextVarSeqlenPlugin(std::string const name, DataType const type,
    int32_t const hiddenSize, int32_t const numHeads, float const dqProbs, bool hasImask, bool varSeqlen,
    bool useInt8ScaleMax)
    : mLayerName(name)
    , mS(0)
    , mB(0)
    , mHeadSize(hiddenSize / numHeads)
    , mHiddenSize(hiddenSize)
    , mNumHeads(numHeads)
    , mType(type)
    , mDqProbs(dqProbs)
    , mHdim(HDIM)
{
    mSM = getSmVersion();
    mUseVarSeqlen = static_cast<int32_t>(varSeqlen);
    mUseInt8ScaleMax = static_cast<int32_t>(useInt8ScaleMax);
    mHasImask = static_cast<int32_t>(hasImask);

    if (varSeqlen)
    {
        // variable sequence length is only supported with the fused MHA kernels
        // we should not override mS!
        bool isSMSupported = elem(mSM, {kSM_75, kSM_80, kSM_86, kSM_87, kSM_89, kSM_90, kSM_100, kSM_120});
        PLUGIN_ASSERT(isSMSupported && (type == DataType::kINT8 || type == DataType::kHALF)
            && "requesting maxSeqlen not compatible with GPU arch");
        // the layout changes: SxB will be a combined \sum_i s_i and hdim will be the 2nd dimension instead of the third
        mHdim = 1;
    }
}

QKVToContextVarSeqlenPlugin::QKVToContextVarSeqlenPlugin(std::string const name, int32_t const S, int32_t const B,
    DataType const type, int32_t const hiddenSize, int32_t const numHeads, float const dqProbs, bool hasImask,
    bool varSeqlen, bool useInt8ScaleMax, void const* runnerStateBuffer)
    : mLayerName(name)
    , mS(S)
    , mB(B)
    , mHeadSize(hiddenSize / numHeads)
    , mHiddenSize(hiddenSize)
    , mNumHeads(numHeads)
    , mType(type)
    , mDqProbs(dqProbs)
    , mHdim(HDIM)
{
    mSM = getSmVersion();
    mUseVarSeqlen = static_cast<int32_t>(varSeqlen);
    mUseInt8ScaleMax = static_cast<int32_t>(useInt8ScaleMax);
    mHasImask = static_cast<int32_t>(hasImask);

    if (varSeqlen)
    {
        // variable sequence length is only supported with the fused MHA kernels
        // we should not override mS!
        bool isSMSupported = elem(mSM, {kSM_75, kSM_80, kSM_86, kSM_87, kSM_89, kSM_90, kSM_100, kSM_120});
        PLUGIN_ASSERT(isSMSupported && (type == DataType::kINT8 || type == DataType::kHALF)
            && "requesting maxSeqlen not compatible with GPU arch");
        // the layout changes: SxB will be a combined \sum_i s_i and hdim will be the 2nd dimension instead of the third
        mHdim = 1;
    }

    createMHARunner();

    PLUGIN_ASSERT(runnerStateBuffer != nullptr);
    auto length = mDispatcher->getSerializationSize();
    mDispatcher->deserialize(runnerStateBuffer, length);
}

IPluginCapability* QKVToContextVarSeqlenPlugin::getCapabilityInterface(PluginCapabilityType type) noexcept
{
    try
    {
        if (type == PluginCapabilityType::kBUILD)
        {
            return static_cast<IPluginV3OneBuild*>(this);
        }
        if (type == PluginCapabilityType::kRUNTIME)
        {
            return static_cast<IPluginV3OneRuntime*>(this);
        }
        PLUGIN_ASSERT(type == PluginCapabilityType::kCORE);
        return static_cast<IPluginV3OneCore*>(this);
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return nullptr;
}

void QKVToContextVarSeqlenPlugin::createMHARunner()
{
    if (mDispatcher.get())
    {
        return;
    }

    if (mUseVarSeqlen)
    {
        PLUGIN_ASSERT(mHeadSize <= 64);
        {
            if (mHeadSize != 64)
            {
                mPatcher.reset(new QkvPaddingRunner(mType));
            }
            if (mType == DataType::kHALF)
            {
                mDispatcher.reset(new FusedMHARunnerFP16v2(mNumHeads, mSM));
            }
            else if (mType == DataType::kINT8)
            {
                mDispatcher.reset(new FusedMHARunnerInt8v2(mNumHeads, mSM, mDqProbs, mUseInt8ScaleMax));
            }
        }
    }
    else
    {
        PLUGIN_ASSERT(mType != DataType::kINT8);
        mDispatcher.reset(new UnfusedMHARunner(mType, mNumHeads, mSM));
    }
}


IPluginV3* QKVToContextVarSeqlenPlugin::clone() noexcept
{
    BERT_DEBUG_MSG("QKV Clone");

    std::unique_ptr<QKVToContextVarSeqlenPlugin> ret;

    // the workspacesize is 0 if we have not call setup the dispatcher yet.
    if (mDispatcher.get())
    {
        mRunnerStateBuffer.resize(mDispatcher->getSerializationSize());
        void* const bufferData = mRunnerStateBuffer.data();
        mDispatcher->serialize(bufferData);

        ret = std::make_unique<QKVToContextVarSeqlenPlugin>(mLayerName, mS, mB, mType, mHiddenSize, mNumHeads, mDqProbs,
            mHasImask, mUseVarSeqlen, mUseInt8ScaleMax, bufferData);
    }
    else
    {
        // dispatcher not setup yet, use type 1 constructor
        ret = std::make_unique<QKVToContextVarSeqlenPlugin>(
            mLayerName, mType, mHiddenSize, mNumHeads, mDqProbs, mHasImask, mUseVarSeqlen, mUseInt8ScaleMax);
    }

    ret->setPluginNamespace(mNamespace.c_str());
    BERT_DEBUG_MSG("QKV Clone done");
    return ret.release();
}

int32_t QKVToContextVarSeqlenPlugin::getOutputShapes(DimsExprs const* inputs, int32_t nbInputs,
    DimsExprs const* shapeInputs, int32_t nbShapeInputs, DimsExprs* outputs, int32_t nbOutputs,
    IExprBuilder& exprBuilder) noexcept
{
    try
    {
        PLUGIN_ASSERT(inputs != nullptr);
        PLUGIN_ASSERT(nbInputs == 1 + mHasImask + 2 * mUseVarSeqlen);
        PLUGIN_ASSERT(nbShapeInputs == 0);
        PLUGIN_ASSERT(outputs != nullptr);
        PLUGIN_ASSERT(nbOutputs == 1);
        // Input is BxSx3*N*H, output should be BxSxN*H
        // Copy over everything
        outputs[kIIDX] = inputs[kIIDX];
        // Divide last dim by three
        auto const* three = exprBuilder.constant(3);
        // mHdim is 2 for fixed seqlen and is 1 for varseqlen
        outputs[kIIDX].d[mHdim]
            = exprBuilder.operation(DimensionOperation::kFLOOR_DIV, *inputs[kIIDX].d[mHdim], *three);
        return pluginStatus_t::STATUS_SUCCESS;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return pluginStatus_t::STATUS_FAILURE;
}

bool QKVToContextVarSeqlenPlugin::supportsFormatCombination(
    int32_t pos, DynamicPluginTensorDesc const* inOut, int32_t nbInputs, int32_t nbOutputs) noexcept
{
    // we only support variable sequence and int8 IO in fused mha runner, and we only support fused mha runner on
    // Turing, Ampere, Hopper and Blackwell
    bool const hasV2Kernels = elem(mSM, {kSM_75, kSM_80, kSM_86, kSM_87, kSM_89, kSM_90, kSM_100, kSM_120});
    PLUGIN_ASSERT((mType != DataType::kINT8 || hasV2Kernels)
        && "INT8 IO is only supported on Xavier, Turing, Ampere, Hopper and Blackwell!");
    PLUGIN_ASSERT((!mUseVarSeqlen || hasV2Kernels)
        && "Variable sequence is only supported on Xavier, Turing, Ampere, Hopper and Blackwell!");

    PLUGIN_ASSERT(pos >= 0);
    PLUGIN_ASSERT(pos < 2 + mHasImask + 2 * mUseVarSeqlen);
    PLUGIN_ASSERT(nbInputs == 1 + mHasImask + 2 * mUseVarSeqlen);
    PLUGIN_ASSERT(nbOutputs == 1);
    auto const* in = inOut;
    auto const* out = inOut + nbInputs;
    if (mUseVarSeqlen)
    {
        PLUGIN_ASSERT((mType == DataType::kHALF || mType == DataType::kINT8)
            && "Conditions for variable seqlen support not fulfilled");
        // qkv, mask, cu_seqlens, dummy
        PLUGIN_ASSERT(nbInputs == 4 && "for varseqlen, expected 4 inputs");
    }

    auto const inDims = in->desc.dims;
    auto const outDims = out->desc.dims;

    auto supportedFormat = TensorFormat::kLINEAR;
    if (mType == DataType::kINT8)
    {
        supportedFormat = (inDims.d[mHdim] % 32U == 0) ? TensorFormat::kCHW32 : TensorFormat::kCHW4;
    }

    int32_t supportedNbDims = 5;
    if (mUseVarSeqlen)
    {
        supportedNbDims = 4;
    }

    bool supportedHdim = (pos == 0) ? (inDims.d[mHdim] % 3U == 0) : (inDims.d[mHdim] / 3 == outDims.d[mHdim]);

    if (pos == 0 || pos == nbInputs)
    { // check input and output
        auto const& desc = inOut[pos].desc;
        return (desc.type == mType) &&               // check type
            (desc.format == supportedFormat) &&      // check format
            (desc.dims.nbDims == supportedNbDims) && // check dims:
            (supportedHdim) &&                       // - hidden dims multiple of 3 for qkv
            (desc.dims.d[mHdim + 1] == 1) &&         // - dummy 1 or h
            (desc.dims.d[mHdim + 2] == 1)            // - dummy 1 or w
            ;
    }

    PLUGIN_ASSERT(mHasImask);
    if (pos == 1)
    { // must be input mask
        auto const* mask = &inOut[pos].desc;
        if (mUseVarSeqlen)
        {
            // dummy input
            return true;
        }

        return mask->format == TensorFormat::kLINEAR && (mask->type == DataType::kINT32) && // precision
            (mask->dims.nbDims == 1);                                                       // num dims
    }
    PLUGIN_ASSERT(mUseVarSeqlen);
    if (pos == 2)
    { // must be cuSeqlens
        // cuSeqlens is a int32_t array of size B+1
        auto const* seqlens = &inOut[pos].desc;
        return (seqlens->type == DataType::kINT32) && (seqlens->format == TensorFormat::kLINEAR);
    }
    if (pos == 3)
    {
        // this is the dummy input
        return inOut[pos].desc.dims.nbDims == 1;
    }
    return false;
}

int32_t QKVToContextVarSeqlenPlugin::onShapeChange(
    PluginTensorDesc const* in, int32_t nbInputs, PluginTensorDesc const* out, int32_t nbOutputs) noexcept
{
    try
    {
        PLUGIN_ASSERT(in != nullptr);
        PLUGIN_ASSERT(nbInputs == 1 + mHasImask + 2 * mUseVarSeqlen);
        PLUGIN_ASSERT(nbOutputs == 1);
        PluginTensorDesc const& inDesc = in[kIIDX];
        TRT_UNUSED inDesc;
        PluginTensorDesc const& outDesc = out[0];
        TRT_UNUSED outDesc;
        PLUGIN_ASSERT(mType == inDesc.type);
        PLUGIN_ASSERT(mType == outDesc.type);
        if (!mUseVarSeqlen)
        {
            PLUGIN_ASSERT(inDesc.dims.d[BDIM] == outDesc.dims.d[BDIM]);
            PLUGIN_ASSERT(inDesc.dims.d[SDIM] == outDesc.dims.d[SDIM]);
            PLUGIN_ASSERT(inDesc.dims.d[mHdim] == 3 * outDesc.dims.d[mHdim]);
            if (mHasImask)
            {
                PluginTensorDesc const& maskDesc = in[kMIDX];
                TRT_UNUSED maskDesc;
                PLUGIN_ASSERT(maskDesc.dims.d[0] == inDesc.dims.d[BDIM]);
            }

            // during build, configurePlugin() should have set mS, mB appropriately
            // during inference, the engine should have mS, mB information
            PLUGIN_ASSERT(mS);
            PLUGIN_ASSERT(mB);

            BERT_DEBUG_MSG("setting up MHA runner for single sequence length");
            createMHARunner();
            this->mDispatcher->setup(mS, mB, mHeadSize);
        }
        else
        {
            BERT_DEBUG_MSG("setting up MHA runner for variable sequence length");
            createMHARunner();
            // need to initialize S and B with somewhat useful values, they will be reset at enqueue for the actual
            // batchsize
            this->mDispatcher->setup(256, 1, mHeadSize);
        }

        return pluginStatus_t::STATUS_SUCCESS;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return pluginStatus_t::STATUS_FAILURE;
}

int32_t QKVToContextVarSeqlenPlugin::configurePlugin(
    DynamicPluginTensorDesc const* in, int32_t nbInputs, DynamicPluginTensorDesc const* out, int32_t nbOutputs) noexcept
{
    try
    {
        PLUGIN_ASSERT(in != nullptr);
        PLUGIN_ASSERT(nbInputs == 1 + mHasImask + 2 * mUseVarSeqlen);
        PLUGIN_ASSERT(nbOutputs == 1);
        PluginTensorDesc const& inDesc = in[kIIDX].desc;
        TRT_UNUSED inDesc;
        PluginTensorDesc const& outDesc = out->desc;
        TRT_UNUSED outDesc;
        PLUGIN_ASSERT(mType == inDesc.type);
        PLUGIN_ASSERT(mType == outDesc.type);
        if (!mUseVarSeqlen)
        {
            PLUGIN_ASSERT(inDesc.dims.d[BDIM] == outDesc.dims.d[BDIM]);
            PLUGIN_ASSERT(inDesc.dims.d[SDIM] == outDesc.dims.d[SDIM]);
            PLUGIN_ASSERT(inDesc.dims.d[mHdim] == 3 * outDesc.dims.d[mHdim]);
            if (mHasImask)
            {
                PluginTensorDesc const& maskDesc = in[kMIDX].desc;
                TRT_UNUSED maskDesc;
                PLUGIN_ASSERT(maskDesc.dims.d[0] == inDesc.dims.d[BDIM]);
            }

            const int32_t S = inDesc.dims.d[SDIM] <= 0 ? in->max.d[SDIM] : inDesc.dims.d[SDIM];
            const int32_t B = inDesc.dims.d[BDIM] <= 0 ? in->max.d[BDIM] : inDesc.dims.d[BDIM];

            if (S != mS || B != mB)
            {
                BERT_DEBUG_MSG("setting up MHA runner for single sequence length");
                createMHARunner();
                this->mDispatcher->setup(S, B, mHeadSize);
                mS = S;
                mB = B;
            }
        }
        else
        {
            BERT_DEBUG_MSG("setting up MHA runner for variable sequence length");
            createMHARunner();
            // need to initialize S and B with somewhat useful values, they will be reset at enqueue for the actual
            // batchsize
            this->mDispatcher->setup(256, 1, mHeadSize);
        }

        return pluginStatus_t::STATUS_SUCCESS;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return pluginStatus_t::STATUS_FAILURE;
}

size_t QKVToContextVarSeqlenPlugin::getWorkspaceSize(DynamicPluginTensorDesc const* inputs, int32_t /* nbInputs */,
    DynamicPluginTensorDesc const* /* outputs */, int32_t /* nbOutputs */) const noexcept
{
    size_t paddingWorkpaceSize = mPatcher ? mPatcher->getWorkspaceSize(inputs[0].desc.dims.d[0], mNumHeads) : 0;
    return mDispatcher->getWorkspaceSize() + paddingWorkpaceSize;
}

int32_t QKVToContextVarSeqlenPlugin::getOutputDataTypes(
    DataType* outputTypes, int32_t nbOutputs, DataType const* inputTypes, int32_t nbInputs) const noexcept
{
    try
    {
        PLUGIN_ASSERT(
            inputTypes[0] == DataType::kFLOAT || inputTypes[0] == DataType::kHALF || inputTypes[0] == DataType::kINT8);
        outputTypes[0] = inputTypes[0];
        return pluginStatus_t::STATUS_SUCCESS;
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return pluginStatus_t::STATUS_FAILURE;
}

void QKVToContextVarSeqlenPlugin::setCublasResources(std::shared_ptr<CublasWrapper> cublasWrapper)
{
    mCublasWrapper = cublasWrapper;
    // The shared cublasWrapper resource owns the handle.
    // but `this` instance has a non-owning pointer to the handle.
    // Note that the cublasWrapper inits the handle and checks for nullptr
    // so we don't have to do that here.
    mCublasHandle = mCublasWrapper->getCublasHandle();
}

IPluginV3* QKVToContextVarSeqlenPlugin::attachToContext(IPluginResourceContext* context) noexcept
{
    try
    {
        auto p = static_cast<QKVToContextVarSeqlenPlugin*>(clone());
        // the clone has shared ownership of underling cublasWrapper instance
        // that is mapped to current context
        p->setCublasResources(createPluginCublasWrapper(context));
        return p;
    }
    catch (const std::exception& e)
    {
        caughtError(e);
    }
    return nullptr;
}

char const* QKVToContextVarSeqlenPlugin::getPluginVersion() const noexcept
{
    return kQKV_TO_CONTEXT_VAR_SEQLEN_PLUGIN_VERSION;
}

int32_t QKVToContextVarSeqlenPlugin::getNbOutputs() const noexcept
{
    return 1;
}

char const* QKVToContextVarSeqlenPlugin::getPluginName() const noexcept
{
    return kQKV_TO_CONTEXT_PLUGIN_NAME;
}


void QKVToContextVarSeqlenPlugin::setPluginNamespace(char const* libNamespace) noexcept
{
    mNamespace = libNamespace;
}

char const* QKVToContextVarSeqlenPlugin::getPluginNamespace() const noexcept
{
    return mNamespace.c_str();
}

// NOLINTNEXTLINE(readability-function-cognitive-complexity)
int32_t QKVToContextVarSeqlenPlugin::enqueue(nvinfer1::PluginTensorDesc const* inputDesc,
    nvinfer1::PluginTensorDesc const* outputDesc, void const* const* inputs, void* const* outputs, void* workspace,
    cudaStream_t stream) noexcept
{
    PLUGIN_VALIDATE(inputDesc != nullptr && outputDesc != nullptr && inputs != nullptr && outputs != nullptr);

    if (mUseVarSeqlen)
    {
        const int32_t B = inputDesc[2].dims.d[0] - 1;
        const int32_t maxS = inputDesc[3].dims.d[0];
        PLUGIN_ASSERT((maxS <= 512)
            && "No implementation for variable sequence length multi-head attention plugin with sequence > 512.");

        int32_t S = 512;
        if (DataType::kHALF == mType && maxS <= 64)
        {
            S = 64;
        }
        else if (DataType::kHALF == mType && maxS <= 96)
        {
            S = 96;
        }
        else if (maxS <= 128)
        {
            S = 128;
        }
        else if (maxS <= 192)
        {
            S = 192;
            if (mType == DataType::kHALF)
            {
                S = 256;
            }
        }
        else if (maxS <= 256)
        {
            S = 256;
        }
        else if (maxS <= 384)
        {
            S = 384;
        }

        auto runV2Kernel = [this, &S, &B, &workspace, &inputDesc, &outputDesc, &stream, &inputs, &outputs](
                               MHARunner* dispatcher, QkvPaddingRunner* patcher, int32_t padSize) {
            PLUGIN_ASSERT(dispatcher);
            // Validate that we can padding to the dispatch required head size also there is kernel exist for this
            // sequence length.
            if (mHeadSize > padSize || !dispatcher->isValid(padSize, S))
            {
                return false;
            }
            dispatcher->setup(S, B, padSize);

            // Need pad and unpad to run the V2 kernel.
            if (mHeadSize < padSize)
            {
                PLUGIN_ASSERT(patcher);
                PLUGIN_ASSERT(padSize <= patcher->getMaxPaddingHeadSize());
                auto sumSeqLen = inputDesc[0].dims.d[0];
                auto paddingWorkspace = patcher->get16BytesAlignedPointer(workspace, dispatcher->getWorkspaceSize());
                auto ret = mPatcher->pad(inputs[0], paddingWorkspace, sumSeqLen, mNumHeads, mHeadSize, padSize, stream);
                if (ret != cudaSuccess)
                {
                    return false;
                }

                MhaRunParameter paddingArgs = patcher->patchMhaArgs(
                    inputDesc, outputDesc, inputs, outputs, paddingWorkspace, sumSeqLen, mNumHeads, padSize);
                try
                {
                    dispatcher->run(paddingArgs.inputDesc, paddingArgs.outputDesc, paddingArgs.inputs,
                        paddingArgs.outputs, workspace, stream, mCublasHandle);
                }
                catch (std::exception const& e)
                {
                    caughtError(e);
                    return false;
                }

                ret = patcher->unpad(
                    paddingArgs.outputs[0], outputs[0], sumSeqLen, mNumHeads, mHeadSize, padSize, stream);
                return ret == cudaSuccess;
            }
            else
            {
                // No pad/unpad is needed.
                try
                {
                    dispatcher->run(inputDesc, outputDesc, inputs, outputs, workspace, stream, mCublasHandle);
                }
                catch (std::exception const& e)
                {
                    caughtError(e);
                    return false;
                }
                return true;
            }
        };
        // Try pad head size to 32 first, if it failed, then try to pad head size to 64.
        if (!runV2Kernel(mDispatcher.get(), mPatcher.get(), 32) && !runV2Kernel(mDispatcher.get(), mPatcher.get(), 64))
        {
            return false;
        }

        return cudaGetLastError();
    }

    PLUGIN_ASSERT(mS == inputDesc->dims.d[SDIM]);
    PLUGIN_ASSERT(mB == inputDesc->dims.d[BDIM]);

    void const* maskPtr = mHasImask ? inputs[1] : nullptr;
    mDispatcher->run(inputDesc[0], outputDesc[0], inputs[0], maskPtr, outputs[0], workspace, stream, mCublasHandle);
    return cudaGetLastError();
}

PluginFieldCollection const* QKVToContextVarSeqlenPlugin::getFieldsToSerialize() noexcept
{
    mDataToSerialize.clear();

    mDataToSerialize.emplace_back("type_id", &mType, PluginFieldType::kINT32, 1);
    mDataToSerialize.emplace_back("hidden_size", &mHiddenSize, PluginFieldType::kINT32, 1);
    mDataToSerialize.emplace_back("num_heads", &mNumHeads, PluginFieldType::kINT32, 1);
    mDataToSerialize.emplace_back("has_mask", &mHasImask, PluginFieldType::kINT32, 1);
    mDataToSerialize.emplace_back("var_seqlen", &mUseVarSeqlen, PluginFieldType::kINT32, 1);
    mDataToSerialize.emplace_back("use_int8_scale_max", &mUseInt8ScaleMax, PluginFieldType::kINT32, 1);
    mDataToSerialize.emplace_back("S", &mS, PluginFieldType::kINT32, 1);
    mDataToSerialize.emplace_back("B", &mB, PluginFieldType::kINT32, 1);

    mRunnerStateBuffer.resize(mDispatcher->getSerializationSize());
    mDispatcher->serialize(mRunnerStateBuffer.data());
    mDataToSerialize.emplace_back("runnerStateBuffer", (void const*) mRunnerStateBuffer.data(),
        PluginFieldType::kUNKNOWN, mRunnerStateBuffer.size());

    if (mDqProbs >= 0)
    {
        mDataToSerialize.emplace_back("dq_probs", &mDqProbs, PluginFieldType::kFLOAT32, 1);
    }

    mFCToSerialize.nbFields = mDataToSerialize.size();
    mFCToSerialize.fields = mDataToSerialize.data();

    return &mFCToSerialize;
}

QKVToContextVarSeqlenPluginCreator::QKVToContextVarSeqlenPluginCreator()
{
    mPluginAttributes.clear();
    mPluginAttributes.emplace_back(PluginField("type_id", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("hidden_size", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("num_heads", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("has_mask", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("dq_probs", nullptr, PluginFieldType::kFLOAT32, 1));
    mPluginAttributes.emplace_back(PluginField("var_seqlen", nullptr, PluginFieldType::kINT32, 1));
    mPluginAttributes.emplace_back(PluginField("use_int8_scale_max", nullptr, PluginFieldType::kINT32, 1));

    mFC.nbFields = mPluginAttributes.size();
    mFC.fields = mPluginAttributes.data();
}

char const* QKVToContextVarSeqlenPluginCreator::getPluginName() const noexcept
{
    return kQKV_TO_CONTEXT_PLUGIN_NAME;
}

char const* QKVToContextVarSeqlenPluginCreator::getPluginVersion() const noexcept
{
    return kQKV_TO_CONTEXT_VAR_SEQLEN_PLUGIN_VERSION;
}

PluginFieldCollection const* QKVToContextVarSeqlenPluginCreator::getFieldNames() noexcept
{
    return &mFC;
}

IPluginV3* QKVToContextVarSeqlenPluginCreator::createPlugin(
    char const* name, PluginFieldCollection const* fc, TensorRTPhase phase) noexcept
{
    try
    {
        BERT_DEBUG_MSG("Creating QKV2ContextPlugin...");
        PLUGIN_VALIDATE(fc != nullptr);
        int32_t hiddenSize = 0;
        // Since numHeads must always exist or validateRequiredAttributes will fail,
        // we can set numHeads to -1 so that static analysis tools don't warn about
        // a division by zero in QKVToContextVarSeqelnPlugin constructor.
        int32_t numHeads = -1;
        bool hasMask = false;
        int32_t typeId = -1;
        int32_t s = -1;
        int32_t b = -1;
        void const* runnerStateBuffer = nullptr;
        int32_t varSeqlen = 0;
        float dqProbs = -1.0F;
        int32_t useInt8ScaleMax = -1;

        PLUGIN_VALIDATE(fc->fields != nullptr);

        if (phase == TensorRTPhase::kBUILD)
        {
            plugin::validateRequiredAttributesExist({"type_id", "hidden_size", "num_heads", "has_mask"}, fc);
        }
        else
        {
            PLUGIN_ASSERT(phase == TensorRTPhase::kRUNTIME);
            // since fc is from a deserialized engine,
            // we expect all attributes (except dq_probs) to be present during runtime
            plugin::validateRequiredAttributesExist({"type_id", "S", "B", "hidden_size", "num_heads", "has_mask",
                                                        "var_seqlen", "use_int8_scale_max", "runnerStateBuffer"},
                fc);
        }
        for (int32_t i = 0; i < fc->nbFields; i++)
        {
            std::string_view const field_name = fc->fields[i].name;

            if (field_name == "type_id"sv)
            {
                typeId = *static_cast<int32_t const*>(fc->fields[i].data);
                PLUGIN_VALIDATE(typeId >= 0 && typeId <= 2, ("QKV: Invalid TypeId " + std::to_string(typeId)).c_str());
                BERT_DEBUG_VALUE("Building typeId: ", typeId);
            }
            else if (field_name == "hidden_size"sv)
            {
                hiddenSize = *static_cast<int32_t const*>(fc->fields[i].data);
                PLUGIN_VALIDATE(hiddenSize > 0, ("QKV: Invalid hiddenSize " + std::to_string(hiddenSize)).c_str());
                BERT_DEBUG_VALUE("Building hiddenSize: ", hiddenSize);
            }
            else if (field_name == "num_heads"sv)
            {
                numHeads = *static_cast<int32_t const*>(fc->fields[i].data);
                PLUGIN_VALIDATE(numHeads > 0, ("QKV: Invalid numHeads " + std::to_string(numHeads)).c_str());
                BERT_DEBUG_VALUE("Building numHeads: ", numHeads);
            }
            else if (field_name == "has_mask"sv)
            {
                hasMask = *static_cast<bool const*>(fc->fields[i].data);
                PLUGIN_VALIDATE(
                    hasMask == 0 || hasMask == 1, ("QKV: Invalid hasMask " + std::to_string(hasMask)).c_str());
                BERT_DEBUG_VALUE("Building hasMask: ", hasMask);
            }

            else if (field_name == "dq_probs"sv)
            {
                dqProbs = *static_cast<float const*>(fc->fields[i].data);
                PLUGIN_VALIDATE(dqProbs > 0.0F, ("QKV: Invalid dqProbs " + std::to_string(dqProbs)).c_str());
                BERT_DEBUG_VALUE("Building dqProbs: ", dqProbs);
            }
            else if (field_name == "var_seqlen"sv)
            {
                varSeqlen = *static_cast<int32_t const*>(fc->fields[i].data);
                BERT_DEBUG_VALUE("Building var_seqlen: ", varSeqlen);
            }
            else if (field_name == "use_int8_scale_max"sv)
            {
                useInt8ScaleMax = *static_cast<int32_t const*>(fc->fields[i].data);
                PLUGIN_VALIDATE(useInt8ScaleMax == 0 || useInt8ScaleMax == 1,
                    ("QKV: Invalid useInt8ScaleMax " + std::to_string(useInt8ScaleMax)).c_str());
                BERT_DEBUG_VALUE("Building useInt8ScaleMax: ", useInt8ScaleMax);
            }
            else if (field_name == "S"sv)
            {
                PLUGIN_ASSERT(phase == TensorRTPhase::kRUNTIME);
                s = *static_cast<int32_t const*>(fc->fields[i].data);
                BERT_DEBUG_VALUE("Building S: ", s);
            }
            else if (field_name == "B"sv)
            {
                PLUGIN_ASSERT(phase == TensorRTPhase::kRUNTIME);
                b = *static_cast<int32_t const*>(fc->fields[i].data);
                BERT_DEBUG_VALUE("Building B: ", b);
            }
            else if (field_name == "runnerStateBuffer"sv)
            {
                PLUGIN_ASSERT(phase == TensorRTPhase::kRUNTIME);
                runnerStateBuffer = static_cast<void const*>(fc->fields[i].data);
            }
        }

        if (useInt8ScaleMax < 0)
        {
            gLogInfo << "Using default for use_int8_scale_max: true" << std::endl;
            useInt8ScaleMax = 1;
        }

        BERT_DEBUG_MSG("Building the Plugin...");
        DataType type = static_cast<DataType>(typeId);
        if (type == DataType::kINT8 && dqProbs < 0)
        {
            gLogInfo << "Using default scale factor\n";
            dqProbs = 1.F / 127.F;
        }

        auto const useInt8ScaleMaxFlag = static_cast<bool>(useInt8ScaleMax);

        if (phase == TensorRTPhase::kBUILD)
        {
            return new QKVToContextVarSeqlenPlugin(
                name, type, hiddenSize, numHeads, dqProbs, hasMask, varSeqlen, useInt8ScaleMaxFlag);
        }

        PLUGIN_VALIDATE(s != -1, "invalid S during runtime plugin creation");
        PLUGIN_VALIDATE(b != -1, "invalid B during runtime plugin creation");
        PLUGIN_VALIDATE(runnerStateBuffer != nullptr, "invalid runnerStateBuffer during runtime plugin creation");

        return new QKVToContextVarSeqlenPlugin(name, s, b, type, hiddenSize, numHeads, dqProbs, hasMask, varSeqlen,
            useInt8ScaleMaxFlag, runnerStateBuffer);
    }
    catch (std::exception const& e)
    {
        caughtError(e);
    }
    return nullptr;
}

void QKVToContextVarSeqlenPluginCreator::setPluginNamespace(char const* libNamespace) noexcept
{
    mNamespace = libNamespace;
}

char const* QKVToContextVarSeqlenPluginCreator::getPluginNamespace() const noexcept
{
    return mNamespace.c_str();
}

#endif // CUDA_VERSION >= 10010
