Files
wehub-resource-sync c8a779b1bb
Docker Image CI / build-ubuntu2004 (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:55 +08:00

619 lines
21 KiB
C++

/*
* 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.
*/
// cublasLT was introduced in CUDA 10.1
#include <cuda.h>
#if CUDA_VERSION >= 10010
#ifndef TRT_FC_PLUGIN_H
#define TRT_FC_PLUGIN_H
#include "NvInferPlugin.h"
#include "common/bertCommon.h"
#include "common/cublasLtWrapper.h"
#include <string>
#include <vector>
namespace nvinfer1
{
namespace pluginInternal
{
class SharedStream : public IPluginResource
{
public:
SharedStream(bool init = false)
{
if (init)
{
PLUGIN_CUASSERT(cudaStreamCreate(&mStream));
}
}
void free()
{
if (mStream != nullptr)
{
PLUGIN_CUASSERT(cudaStreamDestroy(mStream));
mStream = nullptr;
}
}
int32_t release() noexcept override
{
try
{
free();
}
catch (std::exception const& e)
{
return -1;
}
return 0;
}
IPluginResource* clone() noexcept override
{
std::unique_ptr<SharedStream> cloned{};
try
{
cloned = std::make_unique<SharedStream>(/* init */ true);
}
catch (std::exception const& e)
{
return nullptr;
}
return cloned.release();
}
~SharedStream() override
{
if (mStream)
{
free();
}
}
cudaStream_t mStream{nullptr};
};
} // namespace pluginInternal
namespace plugin
{
namespace bert
{
template <typename T>
struct GemmTypes
{
};
char const* const kFCPLUGIN_SHARED_STREAM_KEY{"fcPlugin_timing_key"};
template <>
struct GemmTypes<half>
{
static cudaDataType_t const cudaTypeI = CUDA_R_16F;
using dataTypeI = half;
static cudaDataType_t const cudaTypeO = CUDA_R_16F;
using dataTypeO = half;
static cudaDataType_t const cudaTypeS = CUDA_R_16F;
using dataTypeS = half;
static nvinfer1::pluginInternal::cublasComputeType_t const cudaTypeCom
= nvinfer1::pluginInternal::CUBLAS_COMPUTE_16F;
};
template <>
struct GemmTypes<float>
{
static cudaDataType_t const cudaTypeI = CUDA_R_32F;
using dataTypeI = float;
static cudaDataType_t const cudaTypeO = CUDA_R_32F;
using dataTypeO = float;
static cudaDataType_t const cudaTypeS = CUDA_R_32F;
using dataTypeS = float;
static nvinfer1::pluginInternal::cublasComputeType_t const cudaTypeCom
= nvinfer1::pluginInternal::CUBLAS_COMPUTE_32F;
};
template <typename T>
struct Gemm
{
using Types = GemmTypes<T>;
typename Types::dataTypeI* A{nullptr};
typename Types::dataTypeI* B{nullptr};
typename Types::dataTypeO* C{nullptr};
int32_t m, n, k, ldA, ldB, ldC, rA, rB, rC, cA, cB, cC;
size_t bytesA;
size_t bytesB;
size_t bytesC;
size_t elemA;
size_t elemB;
size_t elemC;
bool transA;
bool transB;
nvinfer1::pluginInternal::cublasOperation_t opA;
nvinfer1::pluginInternal::cublasOperation_t opB;
int32_t const word_size{sizeof(T)};
typename Types::dataTypeS alpha;
typename Types::dataTypeS beta;
Gemm() {}
Gemm(int32_t m_, int32_t n_, int32_t k_, bool tA, bool tB)
{
init(m_, n_, k_, tA, tB);
}
void init(int32_t m_, int32_t n_, int32_t k_, bool tA, bool tB) noexcept
{
m = m_;
n = n_;
k = k_;
transA = tA;
transB = tB;
ldA = transA ? k : m;
ldB = transB ? n : k;
ldC = m;
rA = ldA;
rB = ldB;
rC = ldC;
cA = transA ? m : k;
cB = transB ? k : n;
cC = n;
opA = transA ? nvinfer1::pluginInternal::CUBLAS_OP_T : nvinfer1::pluginInternal::CUBLAS_OP_N;
opB = transB ? nvinfer1::pluginInternal::CUBLAS_OP_T : nvinfer1::pluginInternal::CUBLAS_OP_N;
elemA = m * k;
elemB = n * k;
elemC = n * m;
bytesA = word_size * elemA;
bytesB = word_size * elemB;
bytesC = word_size * elemC;
alpha = T(1.f);
beta = T(0.f);
}
};
auto constexpr kNB_ALGO_COMBINATIONS = 6000;
auto constexpr kNB_ALGO_IDS = 40;
auto constexpr kPRINT_ALGOS = 1;
auto constexpr kNB_KERNEL_REPEATS = 10;
auto constexpr kTHREADS_PER_BLOCK = 1024;
// Structure to store information about different run trials
typedef struct customMatMultPerfType_t
{
static constexpr float kMAX_TIME = 1000000.F;
nvinfer1::pluginInternal::cublasLtMatmulAlgo_t algo;
nvinfer1::pluginInternal::cublasStatus_t status;
float time{kMAX_TIME};
size_t workspaceSize; // actual memory workspace needed
nvinfer1::pluginInternal::cublasMath_t mathMode;
nvinfer1::pluginInternal::cublasLtReductionScheme_t reductionScheme;
int32_t customOption;
float wavesCount;
} customMatmulPerf_t;
// clang-format off
void LtGemmSearch(nvinfer1::pluginInternal::cublasLtHandle_t ltHandle,
nvinfer1::pluginInternal::cublasOperation_t transa,
nvinfer1::pluginInternal::cublasOperation_t transb,
int32_t const &m,
int32_t const &n,
int32_t const &k,
void const *alpha,
void const *A,
int32_t const &lda,
void const *B,
int32_t const &ldb,
void const *beta,
void *C,
int32_t const &ldc,
void *workSpace,
size_t workSpaceSize,
nvinfer1::pluginInternal::cublasComputeType_t computeType,
cudaDataType_t scaleType,
cudaDataType_t Atype,
cudaDataType_t Btype,
cudaDataType_t Ctype,
std::vector<customMatmulPerf_t> &perfResults,
cudaStream_t stream);
// clang-format on
template <typename T>
void LtGemmSearch(nvinfer1::pluginInternal::cublasLtHandle_t ltHandle, Gemm<T> const& g, void* workSpace,
size_t workSpaceSize, std::vector<customMatmulPerf_t>& perfResults, cudaStream_t stream)
{
// clang-format off
LtGemmSearch(
ltHandle,
g.opA,
g.opB,
g.m,
g.n,
g.k,
&g.alpha,
g.A,
g.ldA,
g.B,
g.ldB,
&g.beta,
g.C,
g.ldC,
workSpace,
workSpaceSize,
Gemm<T>::Types::cudaTypeCom,
Gemm<T>::Types::cudaTypeS,
Gemm<T>::Types::cudaTypeI,
Gemm<T>::Types::cudaTypeI,
Gemm<T>::Types::cudaTypeO,
perfResults,
stream
);
// clang-format on
}
struct LtContext
{
nvinfer1::pluginInternal::cublasLtHandle_t cublas{nullptr};
nvinfer1::pluginInternal::CublasLtWrapper& cublasLtWrapper = nvinfer1::pluginInternal::getCublasLtWrapper();
cudaDataType_t typeA;
cudaDataType_t typeB;
cudaDataType_t typeC;
nvinfer1::pluginInternal::cublasComputeType_t typeComp;
cudaDataType_t typeS;
nvinfer1::pluginInternal::cublasLtMatmulDesc_t operationDesc{nullptr};
nvinfer1::pluginInternal::cublasLtMatrixLayout_t Adesc{nullptr};
nvinfer1::pluginInternal::cublasLtMatrixLayout_t Bdesc{nullptr};
nvinfer1::pluginInternal::cublasLtMatrixLayout_t Cdesc{nullptr};
nvinfer1::pluginInternal::cublasLtMatmulHeuristicResult_t heuristicResult = {};
void attach()
{
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtCreate(&cublas));
}
void detach()
{
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtDestroy(cublas));
}
void destroy()
{
if (operationDesc)
{
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatmulDescDestroy(operationDesc));
operationDesc = nullptr;
}
if (Adesc)
{
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatrixLayoutDestroy(Adesc));
Adesc = nullptr;
}
if (Bdesc)
{
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatrixLayoutDestroy(Bdesc));
Bdesc = nullptr;
}
if (Cdesc)
{
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatrixLayoutDestroy(Cdesc));
Cdesc = nullptr;
}
}
template <typename T>
void create(Gemm<T>& g, size_t workspaceSize)
{
typeA = Gemm<T>::Types::cudaTypeI;
typeB = Gemm<T>::Types::cudaTypeI;
typeC = Gemm<T>::Types::cudaTypeO;
typeS = Gemm<T>::Types::cudaTypeS;
typeComp = Gemm<T>::Types::cudaTypeCom; // compute
// OPERATION
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatmulDescCreate(&operationDesc, typeComp, typeS));
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatmulDescSetAttribute(
operationDesc, nvinfer1::pluginInternal::CUBLASLT_MATMUL_DESC_TRANSA, &g.opA, sizeof(g.opA)));
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatmulDescSetAttribute(
operationDesc, nvinfer1::pluginInternal::CUBLASLT_MATMUL_DESC_TRANSB, &g.opB, sizeof(g.opB)));
// MAT DESC
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatrixLayoutCreate(&Adesc, typeA, g.rA, g.cA, g.ldA));
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatrixLayoutCreate(&Bdesc, typeB, g.rB, g.cB, g.ldB));
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatrixLayoutCreate(&Cdesc, typeC, g.rC, g.cC, g.ldC));
}
void setN(uint64_t n)
{
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatrixLayoutSetAttribute(
Bdesc, nvinfer1::pluginInternal::CUBLASLT_MATRIX_LAYOUT_COLS, &n, sizeof(n)));
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatrixLayoutSetAttribute(
Cdesc, nvinfer1::pluginInternal::CUBLASLT_MATRIX_LAYOUT_COLS, &n, sizeof(n)));
}
};
template <typename T>
nvinfer1::pluginInternal::cublasStatus_t cublasLtMatmul(LtContext& ctx, Gemm<T>& g,
nvinfer1::pluginInternal::cublasLtMatmulAlgo_t algo, void* workspace, size_t workspaceSize, cudaStream_t stream)
{
nvinfer1::pluginInternal::CublasLtWrapper& cublasLtWrapper = nvinfer1::pluginInternal::getCublasLtWrapper();
// clang-format off
return cublasLtWrapper.cublasLtMatmul(
ctx.cublas,
ctx.operationDesc,
&g.alpha,
g.A,
ctx.Adesc,
g.B,
ctx.Bdesc,
&g.beta,
g.C,
ctx.Cdesc,
g.C,
ctx.Cdesc,
&algo,
workspace,
workspaceSize,
stream
);
// clang-format on
}
// CAUTION : must match cublasLtMatmulTile_t
char const* const matmulTileName[] = {
"UNDEF",
"8x8",
"8x16",
"16x8",
"8x32",
"16x16",
"32x8",
"8x64",
"16x32",
"32x16",
"64x8",
"32x32",
"32x64",
"64x32",
"32x128",
"64x64",
"128x32",
"64x128",
"128x64",
"64x256",
"128x128",
"256x64",
"64x512",
"128x256",
"256x128",
"512x64",
};
struct AlgoProps
{
int32_t algoId;
int32_t tile;
int32_t swizzle;
int32_t customOption;
int32_t numSplitsK;
int32_t reductionScheme;
uint64_t numericImpl;
void populate(nvinfer1::pluginInternal::cublasLtMatmulAlgo_t const& algo)
{
nvinfer1::pluginInternal::cublasLtMatmulAlgo_t const* matmulAlgo = &algo;
nvinfer1::pluginInternal::CublasLtWrapper& cublasLtWrapper = nvinfer1::pluginInternal::getCublasLtWrapper();
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatmulAlgoConfigGetAttribute(
matmulAlgo, nvinfer1::pluginInternal::CUBLASLT_ALGO_CONFIG_ID, &algoId, sizeof(algoId), nullptr));
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatmulAlgoConfigGetAttribute(
matmulAlgo, nvinfer1::pluginInternal::CUBLASLT_ALGO_CONFIG_TILE_ID, &tile, sizeof(tile), nullptr));
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatmulAlgoConfigGetAttribute(matmulAlgo,
nvinfer1::pluginInternal::CUBLASLT_ALGO_CONFIG_SPLITK_NUM, &numSplitsK, sizeof(numSplitsK), nullptr));
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatmulAlgoConfigGetAttribute(matmulAlgo,
nvinfer1::pluginInternal::CUBLASLT_ALGO_CONFIG_REDUCTION_SCHEME, &reductionScheme, sizeof(reductionScheme),
nullptr));
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatmulAlgoConfigGetAttribute(matmulAlgo,
nvinfer1::pluginInternal::CUBLASLT_ALGO_CONFIG_CTA_SWIZZLING, &swizzle, sizeof(swizzle), nullptr));
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatmulAlgoConfigGetAttribute(matmulAlgo,
nvinfer1::pluginInternal::CUBLASLT_ALGO_CONFIG_CUSTOM_OPTION, &customOption, sizeof(customOption),
nullptr));
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtMatmulAlgoCapGetAttribute(matmulAlgo,
nvinfer1::pluginInternal::CUBLASLT_ALGO_CAP_NUMERICAL_IMPL_FLAGS, &numericImpl, sizeof(numericImpl),
nullptr));
}
};
template <typename T>
nvinfer1::pluginInternal::cublasLtMatmulAlgo_t gemmSearch(int32_t const m, int32_t const n, int32_t const k,
size_t const workspaceSize, size_t& actualWorkspace, cudaStream_t& stream)
{
Gemm<T> g(m, n, k, false, false);
std::vector<customMatmulPerf_t> perfResults(kNB_ALGO_COMBINATIONS);
bool const useAsync = supportsMemPools();
PLUGIN_CUASSERT(useAsync ? cudaMallocAsync(reinterpret_cast<void**>(&g.A), g.bytesA, stream)
: cudaMalloc(reinterpret_cast<void**>(&g.A), g.bytesA));
PLUGIN_CUASSERT(useAsync ? cudaMallocAsync(reinterpret_cast<void**>(&g.B), g.bytesB, stream)
: cudaMalloc(reinterpret_cast<void**>(&g.B), g.bytesB));
PLUGIN_CUASSERT(useAsync ? cudaMallocAsync(reinterpret_cast<void**>(&g.C), g.bytesC, stream)
: cudaMalloc(reinterpret_cast<void**>(&g.C), g.bytesC));
void* workspace;
PLUGIN_CUASSERT(
useAsync ? cudaMallocAsync(&workspace, workspaceSize, stream) : cudaMalloc(&workspace, workspaceSize));
nvinfer1::pluginInternal::cublasLtHandle_t lt;
nvinfer1::pluginInternal::CublasLtWrapper& cublasLtWrapper = nvinfer1::pluginInternal::getCublasLtWrapper();
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtCreate(&lt));
LtGemmSearch(lt, g, workspace, workspaceSize, perfResults, stream);
PLUGIN_CUASSERT(cudaStreamSynchronize(stream));
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtDestroy(lt));
PLUGIN_CUASSERT(useAsync ? cudaFreeAsync(workspace, stream) : cudaFree(workspace));
PLUGIN_CUASSERT(useAsync ? cudaFreeAsync(g.A, stream) : cudaFree(g.A));
PLUGIN_CUASSERT(useAsync ? cudaFreeAsync(g.B, stream) : cudaFree(g.B));
PLUGIN_CUASSERT(useAsync ? cudaFreeAsync(g.C, stream) : cudaFree(g.C));
actualWorkspace = perfResults[0].workspaceSize;
return perfResults[0].algo;
}
template <typename T>
nvinfer1::pluginInternal::cublasLtMatmulAlgo_t gemmSearch(
Gemm<T>& g, size_t const workspaceSize, size_t& actualWorkspace, cudaStream_t& stream)
{
std::vector<customMatmulPerf_t> perfResults(kNB_ALGO_COMBINATIONS);
bool const useAsync = supportsMemPools();
PLUGIN_CUASSERT(useAsync ? cudaMallocAsync(reinterpret_cast<void**>(&g.A), g.bytesA, stream)
: cudaMalloc(reinterpret_cast<void**>(&g.A), g.bytesA));
PLUGIN_CUASSERT(useAsync ? cudaMallocAsync(reinterpret_cast<void**>(&g.B), g.bytesB, stream)
: cudaMalloc(reinterpret_cast<void**>(&g.B), g.bytesB));
PLUGIN_CUASSERT(useAsync ? cudaMallocAsync(reinterpret_cast<void**>(&g.C), g.bytesC, stream)
: cudaMalloc(reinterpret_cast<void**>(&g.C), g.bytesC));
void* workspace;
PLUGIN_CUASSERT(
useAsync ? cudaMallocAsync(&workspace, workspaceSize, stream) : cudaMalloc(&workspace, workspaceSize));
nvinfer1::pluginInternal::cublasLtHandle_t lt;
nvinfer1::pluginInternal::CublasLtWrapper& cublasLtWrapper = nvinfer1::pluginInternal::getCublasLtWrapper();
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtCreate(&lt));
LtGemmSearch(lt, g, workspace, workspaceSize, perfResults, stream);
PLUGIN_CUASSERT(cudaStreamSynchronize(stream));
PLUGIN_CUBLASASSERT(cublasLtWrapper.cublasLtDestroy(lt));
PLUGIN_CUASSERT(useAsync ? cudaFreeAsync(workspace, stream) : cudaFree(workspace));
PLUGIN_CUASSERT(useAsync ? cudaFreeAsync(g.A, stream) : cudaFree(g.A));
PLUGIN_CUASSERT(useAsync ? cudaFreeAsync(g.B, stream) : cudaFree(g.B));
PLUGIN_CUASSERT(useAsync ? cudaFreeAsync(g.C, stream) : cudaFree(g.C));
actualWorkspace = perfResults[0].workspaceSize;
return perfResults[0].algo;
}
// One of the preferred ways of making TensorRT to be able to see
// our custom layer requires extending IPluginV2 and IPluginCreator classes.
// For requirements for overriden functions, check TensorRT API docs.
class TRT_DEPRECATED_BECAUSE("Superseded by IMatrixMultiplyLayer.") FCPluginDynamic
: public nvinfer1::IPluginV2DynamicExt
{
public:
FCPluginDynamic(
std::string const name, nvinfer1::DataType const type, int32_t const outDim, nvinfer1::Weights const& W);
FCPluginDynamic(std::string const name, void const* data, size_t length);
// It doesn't make sense to make FCPluginDynamic without arguments, so we
// delete default constructor.
FCPluginDynamic() = delete;
// IPluginV2DynamicExt Methods
[[nodiscard]] nvinfer1::IPluginV2DynamicExt* clone() const noexcept override;
nvinfer1::DimsExprs getOutputDimensions(int32_t outputIndex, nvinfer1::DimsExprs const* inputs, int32_t nbInputs,
nvinfer1::IExprBuilder& exprBuilder) noexcept override;
bool supportsFormatCombination(
int32_t pos, nvinfer1::PluginTensorDesc const* inOut, int32_t nbInputs, int32_t nbOutputs) noexcept override;
void configurePlugin(nvinfer1::DynamicPluginTensorDesc const* in, int32_t nbInputs,
nvinfer1::DynamicPluginTensorDesc const* out, int32_t nbOutputs) noexcept override;
size_t getWorkspaceSize(nvinfer1::PluginTensorDesc const* inputs, int32_t nbInputs,
nvinfer1::PluginTensorDesc const* outputs, int32_t nbOutputs) const noexcept override;
int32_t enqueue(nvinfer1::PluginTensorDesc const* inputDesc, nvinfer1::PluginTensorDesc const* outputDesc,
void const* const* inputs, void* const* outputs, void* workspace, cudaStream_t stream) noexcept override;
// IPluginV2Ext Methods
nvinfer1::DataType getOutputDataType(
int32_t index, nvinfer1::DataType const* inputTypes, int32_t nbInputs) const noexcept override;
// IPluginV2 Methods
char const* getPluginType() const noexcept override;
char const* getPluginVersion() const noexcept override;
int32_t getNbOutputs() const noexcept override;
int32_t 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(char const* pluginNamespace) noexcept override;
void attachToContext(cudnnContext* cudnnContext, cublasContext* cublasContext,
nvinfer1::IGpuAllocator* gpuAllocator) noexcept override;
void detachFromContext() noexcept override;
char const* getPluginNamespace() const noexcept override;
private:
std::string const mLayerName;
std::string mNamespace;
nvinfer1::DataType mType;
size_t mOutDim; // leading dim
size_t mNumParams;
int32_t mNmax;
int32_t mK;
nvinfer1::pluginInternal::cublasLtMatmulAlgo_t mAlgo;
bert::WeightsWithOwnership mW;
bert::cuda_unique_ptr<void> mWdev;
LtContext mLtContext;
cudaStream_t mSharedStream{nullptr};
};
class TRT_DEPRECATED_BECAUSE("Superseded by IMatrixMultiplyLayer.") FCPluginDynamicCreator
: public nvinfer1::IPluginCreator
{
public:
FCPluginDynamicCreator();
char const* getPluginName() const noexcept override;
char const* getPluginVersion() const noexcept override;
nvinfer1::PluginFieldCollection const* getFieldNames() noexcept override;
nvinfer1::IPluginV2* createPlugin(char const* name, nvinfer1::PluginFieldCollection const* fc) noexcept override;
nvinfer1::IPluginV2* deserializePlugin(
char const* name, void const* serialData, size_t serialLength) noexcept override;
void setPluginNamespace(char const* pluginNamespace) noexcept override;
char const* getPluginNamespace() const noexcept override;
private:
nvinfer1::PluginFieldCollection mFC;
std::vector<nvinfer1::PluginField> mPluginAttributes;
std::string mNamespace;
};
} // namespace bert
} // namespace plugin
} // namespace nvinfer1
#endif // TRT_FC_PLUGIN_H
#endif // #if CUDA_VERSION >= 10010