629 lines
21 KiB
C++
629 lines
21 KiB
C++
/*
|
|
* SPDX-FileCopyrightText: Copyright (c) 1993-2025 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.
|
|
*/
|
|
|
|
//! \file sampleEditableTimingCache.cpp
|
|
//!
|
|
//! \brief This file contains the implementation of the editable
|
|
//! timing cache sample.
|
|
//!
|
|
//! It builds two engines from a simple network. The second build
|
|
//! reuses a timing cache generated during the first build but made
|
|
//! some modifications, specifically assigning a different tactic to a
|
|
//! layer.
|
|
//!
|
|
//! The goal of this sample is to show how to build an engine with
|
|
//! desired tactics by modifying the timing cache.
|
|
//!
|
|
//! It can be run with the following command line:
|
|
//! Command: ./sample_editable_timing_cache
|
|
|
|
#include <cinttypes>
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
#include <cstdlib> // for strtoull
|
|
|
|
#define DEFINE_TRT_ENTRYPOINTS 1
|
|
#include "NvInfer.h"
|
|
#include "common.h"
|
|
#include "logger.h"
|
|
|
|
using namespace nvinfer1;
|
|
|
|
namespace
|
|
{
|
|
|
|
std::string const kSAMPLE_NAME = "TensorRT.sample_editable_timing_cache";
|
|
|
|
using Name = std::string;
|
|
|
|
//! \brief A hash string which starts with `0x` followed by some
|
|
//! hexadecimal digits.
|
|
using Hash = std::string;
|
|
|
|
//! \brief A pair that denotes a tactic of some op.
|
|
struct Tactic
|
|
{
|
|
Hash hash; //!< Hash string which uniquely identifies the tactic.
|
|
Name kernel; //!< Name of the kernel used by the tactic.
|
|
};
|
|
|
|
//! \brief A structure recording the profiling result of an op.
|
|
struct ProfilingRecord
|
|
{
|
|
Name op; //!< Name of the op.
|
|
|
|
Hash key; //!< Hash string which uniquely identifies the op. Its' used
|
|
//!< as a key in Timing Cache.
|
|
|
|
std::vector<Tactic> tactics; //!< Available tactics.
|
|
|
|
Hash selected; //!< Hash string which uniquely identifies the
|
|
//!< tactic finally used by the op.
|
|
};
|
|
|
|
//! \brief A mapping from the name of an op to its profiling result.
|
|
using ProfilingTable = std::unordered_map<Name, ProfilingRecord>;
|
|
|
|
void printProfilingTable(ProfilingTable const& table)
|
|
{
|
|
sample::gLogInfo << "Profiling table:\n";
|
|
|
|
for (auto const& [op, record] : table)
|
|
{
|
|
sample::gLogInfo << "\top: " << op << "\n";
|
|
sample::gLogInfo << "\t\tkey: " << record.key << "\n";
|
|
sample::gLogInfo << "\t\tselected: " << record.selected << "\n";
|
|
sample::gLogInfo << "\t\tavailable tactics:\n";
|
|
|
|
for (auto const& [hash, kernel] : record.tactics)
|
|
{
|
|
sample::gLogInfo << "\t\t\t" << hash << " " << kernel << "\n";
|
|
}
|
|
|
|
sample::gLogInfo << "\n\n";
|
|
}
|
|
}
|
|
|
|
// The implementation of std::regex is not entirely reliable on some
|
|
// platforms, so we use basic string interfaces for pattern matching.
|
|
namespace patterns
|
|
{
|
|
|
|
struct OpKey
|
|
{
|
|
Name op;
|
|
Hash key;
|
|
};
|
|
|
|
//! Searches \p text for a sub string like `Autotuning op matMul1(key: 0x1814870c44ff0f8574df6e3dda04cbd7)`
|
|
//! where in this example the field `op` of the returned `OpKey` would be assigned `matMul1`
|
|
//! and the field `key` would be assigned `0x181487...`.
|
|
[[nodiscard]] std::optional<OpKey> matchOpKey(char const* const text)
|
|
{
|
|
char const* const kPREFIX = "Autotuning op ";
|
|
|
|
char const* const substr = std::strstr(text, kPREFIX);
|
|
if (!substr)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
char op[128 + 1]{}; //< Plus one for the null terminator.
|
|
char key[128 + 1]{}; //< Plus one for the null terminator.
|
|
|
|
int numReceived = std::sscanf(substr + std::strlen(kPREFIX), "%128[^(](key: %128[^)])", op, key);
|
|
if (numReceived != 2)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
return OpKey{Name(op), Hash(key)};
|
|
}
|
|
|
|
[[nodiscard]] bool matchTacticHeader(std::string_view text)
|
|
{
|
|
return text.find("tactic_id, cost(in ms), cost/fastest_cost") != text.npos;
|
|
}
|
|
|
|
struct TacticKernel
|
|
{
|
|
Hash tactic;
|
|
Name kernel;
|
|
};
|
|
|
|
//! Searches \p text for a sub string like `4, 0.00520, 1.00, 0.883, sm86_xmma_gemm, 0x533a71cee0d0e,`
|
|
//! where in this example the field `tactic` of the returned `TacticKernel` would be assigned `0x533a71cee0d0e`
|
|
//! and the field `kernel` would be assigned `sm86_xmma_gemm`.
|
|
[[nodiscard]] std::optional<TacticKernel> matchTacticKernel(char const* const text)
|
|
{
|
|
char const* const kDIGITS = "0123456789";
|
|
|
|
char const* const substr = std::strpbrk(text, kDIGITS);
|
|
if (!substr)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
char kernel[128 + 1]{}; //< Plus one for the null terminator.
|
|
char tactic[128 + 1]{}; //< Plus one for the null terminator.
|
|
|
|
int numReceived = std::sscanf(substr, "%*d, %*f, %*f, %*f, %128[^,], %128[^,]", kernel, tactic);
|
|
if (numReceived != 2)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
return TacticKernel{Hash(tactic), Name(kernel)};
|
|
}
|
|
|
|
//! Searches \p text for a sub string like `The selected tactic is (tactic hash, cost(in ms)):0x533a71cee0d0e,
|
|
//! 0.0050048` where in this example the returned `Hash` would be `0x533a71cee0d0e`.
|
|
[[nodiscard]] std::optional<Hash> matchSelection(char const* const text)
|
|
{
|
|
char const* const kPREFIX = "(tactic hash, cost(in ms)):";
|
|
|
|
char const* const substr = std::strstr(text, kPREFIX);
|
|
if (!substr)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
char tactic[128 + 1]{}; //< Plus one for the null terminator.
|
|
|
|
int numReceived = sscanf(substr + std::strlen(kPREFIX), "%128[^,]", tactic);
|
|
if (numReceived != 1)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
return Hash(tactic);
|
|
}
|
|
|
|
struct LayerKernel
|
|
{
|
|
Name layer;
|
|
Name kernel;
|
|
};
|
|
|
|
//! Searches \p text for a sub string like `Name: matMul2_myl0_3,
|
|
//! LayerType: ...., TacticName: sm80_xmma_gemm, StreamId: 0` where in
|
|
//! this example the field `layer` of the returned `LayerKernel` would be `matMul2_myl0_3`
|
|
//! and the field `kernel` would be `sm80_xmma_gemm`.
|
|
[[nodiscard]] std::optional<LayerKernel> matchLayerKernel(char const* const text)
|
|
{
|
|
char const* const kLAYER_PREFIX = "Name: ";
|
|
|
|
char const* const layerSubstr = std::strstr(text, kLAYER_PREFIX);
|
|
if (!layerSubstr)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
char layer[128 + 1]{}; //< Plus one for the null terminator.
|
|
|
|
int numReceived = std::sscanf(layerSubstr + std::strlen(kLAYER_PREFIX), "%128[^,]", layer);
|
|
if (numReceived != 1)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
char const* const kKERNEL_PREFIX = "TacticName: ";
|
|
|
|
char const* const kernelSubstr = std::strstr(text, kKERNEL_PREFIX);
|
|
if (!kernelSubstr)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
char kernel[128 + 1]{}; //< Plus one for the null terminator.
|
|
|
|
numReceived = std::sscanf(kernelSubstr + std::strlen(kKERNEL_PREFIX), "%128[^,]", kernel);
|
|
if (numReceived != 1)
|
|
{
|
|
return std::nullopt;
|
|
}
|
|
|
|
return LayerKernel{Name(layer), Name(kernel)};
|
|
}
|
|
|
|
} // namespace patterns
|
|
|
|
//! \brief `ProfilingLogger` is a decorator of `ILogger`. It
|
|
//! dispatches the message to the decorated logger and extracts
|
|
//! profiling information from the message.
|
|
//!
|
|
//! \details This class overrides the method `log` of class `ILogger`
|
|
//! to analyze each line of the logs. Since the profiling information
|
|
//! are spread across different lines, it builds a simple state
|
|
//! machine to recognize and capture this information.
|
|
class ProfilingLogger : public nvinfer1::ILogger
|
|
{
|
|
private:
|
|
enum class State
|
|
{
|
|
kEXPECT_KEY,
|
|
kEXPECT_TACTIC_HEADER,
|
|
kEXPECT_TACTIC,
|
|
kEXPECT_SELECTION,
|
|
};
|
|
|
|
public:
|
|
ProfilingLogger(ILogger& logger)
|
|
: mLogger(logger)
|
|
, mState(State::kEXPECT_KEY)
|
|
{
|
|
}
|
|
|
|
void log(Severity severity, AsciiChar const* msg) noexcept override
|
|
{
|
|
mLogger.log(severity, msg);
|
|
|
|
bool resolved = false;
|
|
|
|
while (!resolved)
|
|
{
|
|
resolved = true;
|
|
|
|
switch (mState)
|
|
{
|
|
case State::kEXPECT_KEY:
|
|
{
|
|
if (auto optOpKey = patterns::matchOpKey(msg))
|
|
{
|
|
mRecord.op = std::move(optOpKey->op);
|
|
mRecord.key = std::move(optOpKey->key);
|
|
mState = State::kEXPECT_TACTIC_HEADER;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case State::kEXPECT_TACTIC_HEADER:
|
|
{
|
|
if (patterns::matchTacticHeader(msg))
|
|
{
|
|
mState = State::kEXPECT_TACTIC;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case State::kEXPECT_TACTIC:
|
|
{
|
|
if (auto optTacticKernel = patterns::matchTacticKernel(msg))
|
|
{
|
|
mRecord.tactics.push_back(
|
|
Tactic{std::move(optTacticKernel->tactic), std::move(optTacticKernel->kernel)});
|
|
}
|
|
else
|
|
{
|
|
mState = State::kEXPECT_SELECTION;
|
|
resolved = false;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case State::kEXPECT_SELECTION:
|
|
{
|
|
if (auto optTactic = patterns::matchSelection(msg))
|
|
{
|
|
mRecord.selected = std::move(*optTactic);
|
|
mTable[mRecord.op] = mRecord;
|
|
mRecord = ProfilingRecord{};
|
|
mState = State::kEXPECT_KEY;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//! \brief Get the profiling result and reset the state machine.
|
|
ProfilingTable fetchTable()
|
|
{
|
|
mState = State::kEXPECT_KEY;
|
|
mRecord = ProfilingRecord{};
|
|
return std::exchange(mTable, ProfilingTable{});
|
|
}
|
|
|
|
private:
|
|
ILogger& mLogger;
|
|
State mState;
|
|
ProfilingTable mTable;
|
|
ProfilingRecord mRecord;
|
|
};
|
|
|
|
//! \brief Build a simple graph with three nodes: MatMul -> SoftMax ->
|
|
//! MatMul.
|
|
//!
|
|
//! \details The two MatMuls are identical in all attributes
|
|
//! except for their names.
|
|
//!
|
|
//! \return a pointer to the first MatMul.
|
|
ILayer const* buildGraph(INetworkDefinition* network)
|
|
{
|
|
auto input = network->addInput("input", DataType::kFLOAT, Dims2{128, 128});
|
|
auto weight1 = network->addInput("weight1", DataType::kFLOAT, Dims2{128, 128});
|
|
auto weight2 = network->addInput("weight2", DataType::kFLOAT, Dims2{128, 128});
|
|
auto matMul1 = network->addMatrixMultiply(*input, MatrixOperation::kNONE, *weight1, MatrixOperation::kNONE);
|
|
auto softmax = network->addSoftMax(*matMul1->getOutput(0));
|
|
auto matMul2
|
|
= network->addMatrixMultiply(*softmax->getOutput(0), MatrixOperation::kNONE, *weight2, MatrixOperation::kNONE);
|
|
|
|
network->markOutput(*matMul2->getOutput(0));
|
|
|
|
matMul1->setName("matMul1");
|
|
softmax->setName("softmax");
|
|
matMul2->setName("matMul2");
|
|
|
|
return matMul1;
|
|
}
|
|
|
|
//! \brief Find a tactic different from the selected one in the
|
|
//! candidate set.
|
|
std::optional<Tactic> findDifferentTactic(ProfilingRecord const& record)
|
|
{
|
|
auto it = std::find_if(record.tactics.cbegin(), record.tactics.cend(),
|
|
[&](auto const& entry) { return entry.hash != record.selected; });
|
|
|
|
return it == record.tactics.end() ? std::nullopt : std::make_optional(*it);
|
|
}
|
|
|
|
constexpr int64_t kNUM_PREFIX_CHARS = std::char_traits<char>::length("0x");
|
|
constexpr int64_t kCHARS_PER_BYTE = 2;
|
|
|
|
constexpr int64_t kBYTES_PER_KEY = 16;
|
|
constexpr int64_t kTOTAL_CHARS_PER_KEY = kNUM_PREFIX_CHARS + kBYTES_PER_KEY * kCHARS_PER_BYTE;
|
|
|
|
//! \brief Parse a TimingCacheKey from its text form.
|
|
//! \return false if an error occurs.
|
|
bool parseKey(std::string_view text, TimingCacheKey* key)
|
|
{
|
|
CHECK_RETURN_W_MSG(static_cast<int64_t>(text.size()) == kTOTAL_CHARS_PER_KEY, false, "Unexpected length of key");
|
|
|
|
for (int64_t i = 0, offset = kNUM_PREFIX_CHARS; i < kBYTES_PER_KEY; ++i, offset += kCHARS_PER_BYTE)
|
|
{
|
|
CHECK_RETURN(1 == sscanf(text.data() + offset, "%2" SCNx8, &key->data[i]), false);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
constexpr int64_t kBYTES_PER_TACTIC = 8;
|
|
constexpr int64_t kTOTAL_CAHRS_PER_TACTIC = kNUM_PREFIX_CHARS + kBYTES_PER_TACTIC * kCHARS_PER_BYTE;
|
|
|
|
//! \brief Parse a tactic hash from its text form.
|
|
//! \return false if an error occurs.
|
|
bool parseTactic(std::string_view text, size_t* hash)
|
|
{
|
|
CHECK_RETURN_W_MSG(
|
|
static_cast<int64_t>(text.size()) <= kTOTAL_CAHRS_PER_TACTIC, false, "Unexpected length of tactic");
|
|
|
|
char const* start = text.data() + kNUM_PREFIX_CHARS;
|
|
char* end = nullptr;
|
|
*hash = std::strtoull(start, &end, 16);
|
|
CHECK_RETURN_W_MSG(end == text.data() + text.size(), false, "Found junk in the text.");
|
|
|
|
return true;
|
|
}
|
|
|
|
//! \brief Set a new tactic for some key in the timing cache.
|
|
//! \return false if an error occurs.
|
|
bool setTactic(ITimingCache* cache, std::string_view keyText, std::string_view tacticText)
|
|
{
|
|
TimingCacheKey key;
|
|
CHECK_RETURN_W_MSG(parseKey(keyText, &key), false, "Failed to parse the key.");
|
|
|
|
TimingCacheValue value;
|
|
CHECK_RETURN_W_MSG(parseTactic(tacticText, &value.tacticHash), false, "Failed to parse the tactic hash");
|
|
|
|
value.timingMSec = 1.0F;
|
|
CHECK_RETURN_W_MSG(cache->update(key, value), false, "Failed to update the timing cache.");
|
|
return true;
|
|
}
|
|
|
|
//! \brief A pair which denotes a layer in the engine.
|
|
struct LayerKernel
|
|
{
|
|
Name layer; //!< Name of the layer.
|
|
Name kernel; //!< Name of the kernel used by the layer.
|
|
};
|
|
|
|
//! \brief Extract the name of each layer in the engine, along with
|
|
//! the kernel used by it.
|
|
void extractLayerKernels(ICudaEngine const* engine, std::vector<LayerKernel>& table)
|
|
{
|
|
std::unique_ptr<IEngineInspector> inspector{engine->createEngineInspector()};
|
|
|
|
int32_t numLayers = engine->getNbLayers();
|
|
|
|
for (int32_t i = 0; i < numLayers; ++i)
|
|
{
|
|
char const* line = inspector->getLayerInformation(i, LayerInformationFormat::kONELINE);
|
|
|
|
if (auto optLayerKernel = patterns::matchLayerKernel(line))
|
|
{
|
|
table.push_back({std::move(optLayerKernel->layer), std::move(optLayerKernel->kernel)});
|
|
}
|
|
}
|
|
}
|
|
|
|
void printLayerKernels(std::vector<LayerKernel> const& table)
|
|
{
|
|
for (size_t i = 0; i < table.size(); ++i)
|
|
{
|
|
auto const& [layer, kernel] = table[i];
|
|
sample::gLogInfo << "#" << i << ": " << std::setw(30) << std::setfill(' ') << std::left << layer << " =uses=> "
|
|
<< kernel << "\n";
|
|
}
|
|
}
|
|
|
|
bool isPrefixOf(std::string_view shorter, std::string_view longer)
|
|
{
|
|
return shorter.size() <= longer.size() && std::equal(shorter.begin(), shorter.end(), longer.begin());
|
|
}
|
|
|
|
//! \brief Find the layer derived from the op.
|
|
//!
|
|
//! \details In this sample, the name of a layer derived from a MatMul
|
|
//! op is prefixed with the op's name.
|
|
std::optional<LayerKernel> findLayer(std::vector<LayerKernel> const& table, std::string_view op)
|
|
{
|
|
auto it = std::find_if(
|
|
table.begin(), table.end(), [op](LayerKernel const& entry) { return isPrefixOf(op, entry.layer); });
|
|
|
|
return it == table.end() ? std::nullopt : std::make_optional(*it);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
#define FAIL_IF_NOT(status, errMsg) \
|
|
do \
|
|
{ \
|
|
if (!(status)) \
|
|
{ \
|
|
sample::gLogError << (errMsg) << " Error in " << __FILE__ << ", function " << FN_NAME << "(), line " \
|
|
<< __LINE__ << std::endl; \
|
|
return sample::gLogger.reportFail(sampleTest); \
|
|
} \
|
|
} while (0)
|
|
|
|
int32_t main(int32_t argc, char* argv[])
|
|
{
|
|
auto sampleTest = sample::gLogger.defineTest(kSAMPLE_NAME, argc, argv);
|
|
|
|
sample::gLogger.reportTestStart(sampleTest);
|
|
|
|
try
|
|
{
|
|
// Set the logging level to kVERBOSE to see the profiling
|
|
// information.
|
|
sample::gLogger.setReportableSeverity(ILogger::Severity::kVERBOSE);
|
|
|
|
ProfilingLogger profilingLogger(sample::gLogger.getTRTLogger());
|
|
|
|
std::unique_ptr<IBuilder> builder{createInferBuilder(profilingLogger)};
|
|
FAIL_IF_NOT(builder, "Failed to create inference builder.");
|
|
|
|
NetworkDefinitionCreationFlags flags = 1U
|
|
<< static_cast<uint32_t>(NetworkDefinitionCreationFlag::kSTRONGLY_TYPED);
|
|
std::unique_ptr<INetworkDefinition> network{builder->createNetworkV2(flags)};
|
|
FAIL_IF_NOT(network, "Failed to create network.");
|
|
|
|
ILayer const* matMul1 = buildGraph(network.get());
|
|
std::string const opName = matMul1->getName();
|
|
|
|
std::unique_ptr<IBuilderConfig> config{builder->createBuilderConfig()};
|
|
FAIL_IF_NOT(config, "Failed to create builder config.");
|
|
|
|
// Tell the builder to save the name of tactic used by each layer
|
|
// in the engine.
|
|
config->setProfilingVerbosity(ProfilingVerbosity::kDETAILED);
|
|
|
|
// Enable the editable timing cache. In editable mode, the logs
|
|
// will contain profiling results of all layers. Besides, each
|
|
// layer will have its own tactics, which means that changes in
|
|
// one layer will not affect others.
|
|
config->setFlag(BuilderFlag::kEDITABLE_TIMING_CACHE);
|
|
|
|
// Provide the builder with an empty timing cache.
|
|
std::unique_ptr<ITimingCache> timingCache{config->createTimingCache(nullptr, 0)};
|
|
FAIL_IF_NOT(timingCache, "Failed to set timing cache.");
|
|
|
|
FAIL_IF_NOT(config->setTimingCache(*timingCache, true), "Failed to set timing cache.");
|
|
|
|
// Build the first engine.
|
|
std::unique_ptr<IHostMemory> plan{builder->buildSerializedNetwork(*network, *config)};
|
|
FAIL_IF_NOT(plan, "Failed to build serialized engine.");
|
|
|
|
std::unique_ptr<IRuntime> runtime{createInferRuntime(profilingLogger)};
|
|
FAIL_IF_NOT(runtime, "Failed to create the runtime.");
|
|
|
|
std::unique_ptr<ICudaEngine> engine{runtime->deserializeCudaEngine(plan->data(), plan->size())};
|
|
FAIL_IF_NOT(engine, "Failed to deserialize the engine.");
|
|
|
|
// Extract layers' information of the first engine.
|
|
std::vector<LayerKernel> layerKernels;
|
|
extractLayerKernels(engine.get(), layerKernels);
|
|
|
|
std::optional<LayerKernel> matMulLayer = findLayer(layerKernels, opName);
|
|
FAIL_IF_NOT(matMulLayer.has_value(), "Cannot find the layer derived from the first MatMul node.");
|
|
|
|
// Extract profiling results from the logs.
|
|
ProfilingTable table = profilingLogger.fetchTable();
|
|
|
|
// Find a different tactic for the first MatMul.
|
|
ProfilingRecord const& opRecord = table.at(opName);
|
|
|
|
std::optional<Tactic> newTactic = findDifferentTactic(opRecord);
|
|
FAIL_IF_NOT(newTactic.has_value(), "No other tactics.");
|
|
|
|
// Put the new tactic in the cache.
|
|
CHECK_RETURN(setTactic(timingCache.get(), opRecord.key, newTactic->hash), EXIT_FAILURE);
|
|
|
|
// Build the second engine, with the modified timing cache.
|
|
std::unique_ptr<IHostMemory> newPlan{builder->buildSerializedNetwork(*network, *config)};
|
|
FAIL_IF_NOT(newPlan, "Failed to build the engine again.");
|
|
|
|
std::unique_ptr<ICudaEngine> newEngine{runtime->deserializeCudaEngine(newPlan->data(), newPlan->size())};
|
|
FAIL_IF_NOT(newEngine, "Failed to deserialize the engine again.");
|
|
|
|
// Extract layers' information of the second engine.
|
|
std::vector<LayerKernel> newLayerKernels;
|
|
extractLayerKernels(newEngine.get(), newLayerKernels);
|
|
|
|
std::optional<LayerKernel> newMatMulLayer = findLayer(newLayerKernels, opName);
|
|
|
|
FAIL_IF_NOT(newMatMulLayer.has_value(), "Cannot find the layer derived from the first MatMul node.");
|
|
|
|
FAIL_IF_NOT(newMatMulLayer->kernel == newTactic->kernel, "The layer didn't use the assigned new kernel.");
|
|
|
|
sample::gLogInfo << "\n";
|
|
|
|
sample::gLogInfo << "Layers of the first engine:\n";
|
|
printLayerKernels(layerKernels);
|
|
|
|
sample::gLogInfo << "\n";
|
|
|
|
printProfilingTable(table);
|
|
|
|
sample::gLogInfo << "Originally, layer `" << matMulLayer->layer << "` used kernel `" << matMulLayer->kernel
|
|
<< "`.\n";
|
|
sample::gLogInfo << "Now, it should use the new kernel `" << newTactic->kernel << ".`\n";
|
|
sample::gLogInfo << "\n";
|
|
|
|
sample::gLogInfo << "Layers of the second engine:\n";
|
|
printLayerKernels(newLayerKernels);
|
|
|
|
sample::gLogInfo << "\n";
|
|
|
|
return sample::gLogger.reportPass(sampleTest);
|
|
}
|
|
catch (std::exception const& err)
|
|
{
|
|
sample::gLogError << "Exception: " << err.what() << "\n";
|
|
return sample::gLogger.reportFail(sampleTest);
|
|
}
|
|
}
|