Files
nvidia--tensorrt/samples/trtexec/trtexec.cpp
T
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

1393 lines
54 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.
*/
//! \file trtexec.cpp
//!
//! \brief Reusable trtexec implementation, separated from main() so that custom
//! command-line tools can be built on top of trtexec's engine-building and
//! inference workflow. See trtexec.h for details.
#include "trtexec.h"
#include <algorithm>
#include <cctype>
#include <cerrno>
#include <chrono>
#include <cstring>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <functional>
#include <iostream>
#include <limits>
#include <memory>
#include <optional>
#include <sys/stat.h>
#if !defined(_WIN32)
#include <sys/wait.h>
#include <unistd.h>
#endif
#include <system_error>
#include <vector>
#include "NvInfer.h"
#include "NvInferPlugin.h"
#include "buffers.h"
#include "common.h"
#include "logger.h"
#include "sampleDevice.h"
#include "sampleEngines.h"
#include "sampleInference.h"
#include "sampleOptions.h"
#include "sampleReporting.h"
#include "sampleTuning.h"
#include "sampleUtils.h"
#include <nlohmann/json.hpp>
using namespace nvinfer1;
using namespace sample;
using namespace samplesCommon;
#if ENABLE_UNIFIED_BUILDER
using namespace nvinfer2::safe;
__attribute__((weak)) std::shared_ptr<sample::SampleSafeRecorder> gSafeRecorder
= std::make_shared<sample::SampleSafeRecorder>(nvinfer2::safe::Severity::kINFO);
#endif
namespace
{
using LibraryPtr = std::unique_ptr<DynamicLibrary>;
std::function<void*(void*, int32_t)> pCreateInferRuntimeInternal{};
std::function<void*(void*, void*, int32_t)> pCreateInferRefitterInternal{};
std::function<void*(void*, int32_t)> pCreateInferBuilderInternal{};
std::function<void*(void*, void*, int)> pCreateNvOnnxParserInternal{};
std::function<void*(void*, void*, int)> pCreateNvOnnxRefitterInternal{};
//! Track runtime used for the execution of trtexec.
//! Must be tracked as a global variable due to how library init functions APIs are organized.
RuntimeMode gUseRuntime = RuntimeMode::kFULL;
bool initNvinfer()
{
#if !TRT_STATIC
static LibraryPtr libnvinferPtr{};
auto fetchPtrs = [](DynamicLibrary* l) {
pCreateInferRuntimeInternal = l->symbolAddress<void*(void*, int32_t)>("createInferRuntime_INTERNAL");
try
{
pCreateInferRefitterInternal
= l->symbolAddress<void*(void*, void*, int32_t)>("createInferRefitter_INTERNAL");
}
catch (const std::exception& e)
{
sample::gLogWarning << "Could not load function createInferRefitter_INTERNAL : " << e.what() << std::endl;
}
if (gUseRuntime == RuntimeMode::kFULL)
{
pCreateInferBuilderInternal = l->symbolAddress<void*(void*, int32_t)>("createInferBuilder_INTERNAL");
}
};
return initLibrary(libnvinferPtr, getRuntimeLibraryName(gUseRuntime), fetchPtrs);
#else
pCreateInferRuntimeInternal = createInferRuntime_INTERNAL;
pCreateInferRefitterInternal = createInferRefitter_INTERNAL;
pCreateInferBuilderInternal = createInferBuilder_INTERNAL;
return true;
#endif // !TRT_STATIC
}
bool initNvonnxparser()
{
#if !TRT_STATIC
static LibraryPtr libnvonnxparserPtr{};
auto fetchPtrs = [](DynamicLibrary* l) {
pCreateNvOnnxParserInternal = l->symbolAddress<void*(void*, void*, int)>("createNvOnnxParser_INTERNAL");
pCreateNvOnnxRefitterInternal
= l->symbolAddress<void*(void*, void*, int)>("createNvOnnxParserRefitter_INTERNAL");
};
return initLibrary(libnvonnxparserPtr, kNVONNXPARSER_LIBNAME, fetchPtrs);
#else
pCreateNvOnnxParserInternal = createNvOnnxParser_INTERNAL;
pCreateNvOnnxRefitterInternal = createNvOnnxParserRefitter_INTERNAL;
return true;
#endif // !TRT_STATIC
}
} // namespace
IRuntime* createRuntime()
{
if (!initNvinfer())
{
return {};
}
ASSERT(pCreateInferRuntimeInternal != nullptr);
return static_cast<IRuntime*>(pCreateInferRuntimeInternal(&gLogger.getTRTLogger(), NV_TENSORRT_VERSION));
}
IBuilder* createBuilder()
{
if (!initNvinfer())
{
return {};
}
ASSERT(pCreateInferBuilderInternal != nullptr);
return static_cast<IBuilder*>(pCreateInferBuilderInternal(&gLogger.getTRTLogger(), NV_TENSORRT_VERSION));
}
IRefitter* createRefitter(ICudaEngine& engine)
{
if (!initNvinfer())
{
return {};
}
ASSERT(pCreateInferRefitterInternal != nullptr);
return static_cast<IRefitter*>(pCreateInferRefitterInternal(&engine, &gLogger.getTRTLogger(), NV_TENSORRT_VERSION));
}
nvonnxparser::IParser* createONNXParser(INetworkDefinition& network)
{
if (!initNvonnxparser())
{
return {};
}
ASSERT(pCreateNvOnnxParserInternal != nullptr);
return static_cast<nvonnxparser::IParser*>(
pCreateNvOnnxParserInternal(&network, &gLogger.getTRTLogger(), NV_ONNX_PARSER_VERSION));
}
nvonnxparser::IParserRefitter* createONNXRefitter(nvinfer1::IRefitter& refitter)
{
if (!initNvonnxparser())
{
return {};
}
ASSERT(pCreateNvOnnxRefitterInternal != nullptr);
return static_cast<nvonnxparser::IParserRefitter*>(
pCreateNvOnnxRefitterInternal(&refitter, &gLogger.getTRTLogger(), NV_ONNX_PARSER_VERSION));
}
#if ENABLE_UNIFIED_BUILDER
bool processSafetyPluginLibrary(nvinfer2::safe::ISafePluginRegistry* safetyPluginRegistry, DynamicLibrary* libPtr,
samplesSafeCommon::SafetyPluginLibraryArgument const& pluginArgs)
{
if (libPtr == nullptr)
{
sample::gLogError << "Cannot open safety plugin library " << pluginArgs.libraryName << std::endl;
return false;
}
std::string const pluginGetterSymbolName{"getSafetyPluginCreator"};
auto pGetSafetyPluginCreator
= libPtr->symbolAddress<void*(char const*, char const*)>(pluginGetterSymbolName.c_str());
if (pGetSafetyPluginCreator == nullptr)
{
sample::gLogError << "Cannot find plugin creator getter symbol from plugin library: " << pluginArgs.libraryName
<< std::endl;
sample::gLogError << "Please ensure interface function is correctly implemented and exported." << std::endl;
return false;
}
for (auto const& pluginAttr : pluginArgs.pluginAttrs)
{
auto pluginCreator = static_cast<IPluginCreatorInterface*>(
pGetSafetyPluginCreator(pluginAttr.pluginNamespace.c_str(), pluginAttr.pluginName.c_str()));
if (pluginCreator == nullptr)
{
sample::gLogWarning << "Plugin interface getSafetyPluginCreator return nullptr for "
<< pluginAttr.pluginNamespace << "::" << pluginAttr.pluginName
<< " in the safety plugin library: " << pluginArgs.libraryName << std::endl;
sample::gLogWarning
<< "Please ensure interface function is implemented correctly and plugin name/namespace is matched."
<< std::endl;
continue;
}
sample::gLogInfo << "Registering " << pluginAttr.pluginNamespace << "::" << pluginAttr.pluginName
<< " for TensorRT safety." << std::endl;
ErrorCode errorCode
= safetyPluginRegistry->registerCreator(*pluginCreator, pluginAttr.pluginNamespace.c_str(), *gSafeRecorder);
if (errorCode != ErrorCode::kSUCCESS)
{
sample::gLogWarning << "Failed to register safety plugin " << pluginAttr.pluginNamespace
<< "::" << pluginAttr.pluginName << std::endl;
if (errorCode == ErrorCode::kINVALID_ARGUMENT)
{
sample::gLogWarning << "Is getPluginName/getPluginNamespace/getPluginVersion interface implemented and "
"return non-nullptr?"
<< std::endl;
}
}
}
return true;
}
#endif
using time_point = std::chrono::time_point<std::chrono::high_resolution_clock>;
using duration = std::chrono::duration<float>;
// Sentinel returned by parseArgs() to signal that main() should continue into build/infer.
constexpr int32_t kCONTINUE_MAIN{-1};
//! Prepare \p iEnv for the profile run. Caller must have already populated \p iEnv.profiler.
//! On TRT-RTX, CUDA graphs capture obscure per-layer profile events, so the existing context is torn down and rebuilt
//! with CUDA graphs disabled. On TRT-Enterprise, the existing context is reused and the profiler is attached in place.
bool prepareProfileRun(InferenceEnvironmentBase& iEnv, InferenceOptions& infOpts, SystemOptions const& sysOpts)
{
IExecutionContext& ctx = *static_cast<InferenceEnvironmentStd&>(iEnv).contexts.front();
ctx.setProfiler(iEnv.profiler.get());
ctx.setEnqueueEmitsProfile(false);
return true;
}
//! Print the knob database JSON (from IBuilderConfig::getAllBuildRoutes()) and return EXIT_SUCCESS,
//! or EXIT_FAILURE on error. With a non-empty knobName, parse the JSON and emit only the
//! tuner_options entry whose `option` field matches (matching is leading-dash-insensitive so
//! `--helpBuildRoute=conv_use_long_w` and `--helpBuildRoute=-conv_use_long_w` behave identically).
//! No match → EXIT_FAILURE with a clear "no such knob" diagnostic.
//! Guarded by ENABLE_FEATURE_GLOBAL_PERF_TUNER: without the feature, getAllBuildRoutes() is not
//! linked in, so this returns EXIT_FAILURE with a diagnostic.
int32_t printBuildRouteHelp(std::string const& knobName)
{
std::unique_ptr<IBuilder> builder{createBuilder()};
if (!builder)
{
sample::gLogError << "Failed to create builder for --helpBuildRoute" << std::endl;
return EXIT_FAILURE;
}
std::unique_ptr<IBuilderConfig> config{builder->createBuilderConfig()};
if (!config)
{
sample::gLogError << "Failed to create builder config for --helpBuildRoute" << std::endl;
return EXIT_FAILURE;
}
char const* allBuildRoutes = config->getAllBuildRoutes();
if (allBuildRoutes == nullptr)
{
sample::gLogError << "getAllBuildRoutes() returned null" << std::endl;
return EXIT_FAILURE;
}
// No filter → emit the database verbatim and exit.
if (knobName.empty())
{
std::cout << allBuildRoutes << std::endl;
return EXIT_SUCCESS;
}
// Filter mode: parse the JSON, pick the matching tuner_options entry, re-emit.
nlohmann::ordered_json root;
try
{
root = nlohmann::ordered_json::parse(allBuildRoutes);
}
catch (nlohmann::json::parse_error const& e)
{
sample::gLogError << "Failed to parse knob database JSON: " << e.what() << std::endl;
return EXIT_FAILURE;
}
if (!root.contains("tuner_options") || !root["tuner_options"].is_array())
{
sample::gLogError << "Knob database JSON has no 'tuner_options' array." << std::endl;
return EXIT_FAILURE;
}
// Database stores names with a leading '-'; accept the user's input both with and without
// the dash by comparing the unprefixed substrings.
auto stripDash = [](std::string const& s) -> std::string {
return (!s.empty() && s.front() == '-') ? s.substr(1) : s;
};
std::string const wantedKnob = stripDash(knobName);
nlohmann::ordered_json filteredOptions = nlohmann::ordered_json::array();
for (auto const& opt : root["tuner_options"])
{
if (opt.contains("option") && opt["option"].is_string()
&& stripDash(opt["option"].get<std::string>()) == wantedKnob)
{
filteredOptions.push_back(opt);
}
}
if (filteredOptions.empty())
{
sample::gLogError << "--helpBuildRoute=" << knobName << ": no such knob in the database. "
<< "Run --helpBuildRoute (no value) to see the full list." << std::endl;
return EXIT_FAILURE;
}
nlohmann::ordered_json filtered;
if (root.contains("tuner_version"))
{
filtered["tuner_version"] = root["tuner_version"];
}
filtered["tuner_options"] = std::move(filteredOptions);
std::cout << filtered.dump(/*indent=*/2) << std::endl;
return EXIT_SUCCESS;
}
// \param quiet If true, suppress option banner printing (used by child workers to avoid
// repeating the same option dump that the parent already printed).
int32_t parseArgs(Logger::TestAtom& sampleTest, Arguments& args, AllOptions& options, bool quiet = false)
{
// For DLA pre-procssing
bool const kENABLE_STATIC_PLUGINS = true;
options.system.enableStaticPlugins = kENABLE_STATIC_PLUGINS;
// Start parsing
static_assert(kCONTINUE_MAIN != EXIT_SUCCESS, "kCONTINUE_MAIN must not be EXIT_SUCCESS");
static_assert(kCONTINUE_MAIN != EXIT_FAILURE, "kCONTINUE_MAIN must not be EXIT_FAILURE");
if (parseHelp(args))
{
AllOptions::help(std::cout, kENABLE_STATIC_PLUGINS);
return EXIT_SUCCESS;
}
if (!args.empty())
{
bool failed{false};
try
{
options.parse(args);
if (!args.empty())
{
AllOptions::help(std::cout, kENABLE_STATIC_PLUGINS);
for (auto const& arg : args)
{
sample::gLogError << "Unknown option: " << arg.first << " " << arg.second.first << std::endl;
}
failed = true;
}
}
catch (std::invalid_argument const& arg)
{
AllOptions::help(std::cout, kENABLE_STATIC_PLUGINS);
sample::gLogError << arg.what() << std::endl;
failed = true;
}
if (failed)
{
return EXIT_FAILURE;
}
}
else
{
options.helps = true;
}
if (options.helps)
{
AllOptions::help(std::cout, kENABLE_STATIC_PLUGINS);
return EXIT_SUCCESS;
}
#if defined(_WIN32)
if (options.tuning.helpBuildRoute || !options.build.buildRoute.empty())
{
sample::gLogError << "--helpBuildRoute and --setBuildRoute are not supported on Windows." << std::endl;
return EXIT_FAILURE;
}
#endif
// --helpBuildRoute prints the knob database JSON and exits. Suppresses all other flags
// (they are parsed for validation but ignored). Lower precedence than --help (above),
// matching the tests' expected ordering.
if (options.tuning.helpBuildRoute)
{
return printBuildRouteHelp(options.tuning.helpBuildRouteKnob);
}
// Print the parsed options banner. Suppressed in child workers (quiet=true)
// to avoid repeating the same dump that the parent already printed.
if (!quiet)
{
sample::gLogInfo << options;
}
return kCONTINUE_MAIN;
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
int32_t runOnceBuildAndInfer(
Logger::TestAtom& sampleTest, AllOptions& options, sample::PostConfigCallback const& postConfigHook = nullptr)
{
if (options.reporting.verbose)
{
sample::setReportableSeverity(ILogger::Severity::kVERBOSE);
}
std::string const jitInVersion;
if (!options.build.cpuOnly)
{
setCudaDevice(options.system.device, sample::gLogInfo);
}
sample::gLogInfo << std::endl;
sample::gLogInfo << "TensorRT version: " << NV_TENSORRT_MAJOR << "." << NV_TENSORRT_MINOR << "."
<< NV_TENSORRT_PATCH << jitInVersion << std::endl;
// Record specified runtime
gUseRuntime = options.build.useRuntime;
#if !TRT_STATIC
LibraryPtr nvinferPluginLib{};
#endif /* TRT_STATIC */
std::vector<LibraryPtr> pluginLibs;
if (gUseRuntime == RuntimeMode::kFULL && !options.build.safe)
{
sample::gLogInfo << "Loading standard plugins" << std::endl;
#if !TRT_STATIC
nvinferPluginLib = loadLibrary(kNVINFER_PLUGIN_LIBNAME);
auto pInitLibNvinferPlugins
= nvinferPluginLib->symbolAddress<bool(void*, char const*)>("initLibNvInferPlugins");
#else /* TRT_STATIC */
auto pInitLibNvinferPlugins = initLibNvInferPlugins;
#endif /* TRT_STATIC */
ASSERT(pInitLibNvinferPlugins != nullptr);
pInitLibNvinferPlugins(&sample::gLogger.getTRTLogger(), "");
for (auto const& pluginPath : options.system.plugins)
{
sample::gLogInfo << "Loading supplied plugin library: " << pluginPath << std::endl;
pluginLibs.emplace_back(loadLibrary(pluginPath));
}
}
else if (gUseRuntime == RuntimeMode::kFULL && options.build.safe)
{
sample::gLogInfo << "Skipping standard plugin loading due to --safe flag" << std::endl;
}
else if (!options.system.plugins.empty())
{
throw std::runtime_error("TRT-18412: Plugins require --useRuntime=full.");
}
#if ENABLE_UNIFIED_BUILDER
auto safetyPluginRegistry = sample::safe::getSafePluginRegistry(*gSafeRecorder);
ASSERT(safetyPluginRegistry != nullptr);
// getSafePluginRegistry() mutates the singleton's stored ISafeRecorder on every call. The ONNX parser calls it
// during plugin lookup with its own library-local static recorder; at process exit libnvonnxparser.so is
// unloaded before libnvinfer_safe.so, leaving the singleton with a pointer into unmapped memory that its
// destructor then dereferences via decRefCount(). Restore the recorder to this executable's process-lifetime
// gSafeRecorder on every return path. The restore runs during stack unwind while both DSOs are still mapped,
// relying on libnvonnxparser.so staying loaded for the duration of trtexecMain.
struct RestoreSafeRecorderGuard
{
nvinfer2::safe::ISafePluginRegistry* mRegistry;
~RestoreSafeRecorderGuard() noexcept
{
if (mRegistry != nullptr && gSafeRecorder)
{
mRegistry->setSafeRecorder(*gSafeRecorder);
}
}
} restoreSafeRecorderGuard{safetyPluginRegistry};
if (!options.system.safetyPlugins.empty())
{
for (auto const& safetyPluginArg : options.system.safetyPlugins)
{
sample::gLogInfo << "Loading supplied safety plugin library with manual registration: "
<< safetyPluginArg.libraryName << std::endl;
auto pluginLib = loadLibrary(safetyPluginArg.libraryName);
processSafetyPluginLibrary(safetyPluginRegistry, pluginLib.get(), safetyPluginArg);
pluginLibs.emplace_back(std::move(pluginLib));
}
}
#endif // ENABLE_UNIFIED_BUILDER
if (options.build.safe && !sample::hasSafeRuntime())
{
sample::gLogError << "Safety is not supported because safety runtime library is unavailable." << std::endl;
return EXIT_FAILURE;
}
if (!options.build.safe && options.build.consistency)
{
sample::gLogInfo << "Skipping consistency checker on non-safety mode." << std::endl;
options.build.consistency = false;
}
// Windows does not have setenv call
#if !defined(_WIN32)
// Set CPU-only environment variable if the option is enabled
if (options.build.cpuOnly)
{
// The use of `TRT_INTERNAL_OPTIONS` is special to TensorRT 10.15 and will disappear in later releases.
sample::gLogInfo << "Setting CPU-only mode" << std::endl;
char* internalOptions = std::getenv("TRT_INTERNAL_OPTIONS");
std::string internalOptionsStr;
if (internalOptions)
{
internalOptionsStr = std::string(internalOptions) + " --cpu_only=1";
}
else
{
internalOptionsStr = "--cpu_only=1";
}
setenv("TRT_INTERNAL_OPTIONS", internalOptionsStr.c_str(), 1);
}
#endif // !defined(_WIN32)
// Start engine building phase.
std::unique_ptr<BuildEnvironment> bEnv(
new BuildEnvironment(options.build.safe, options.build.versionCompatible, options.system.DLACore,
options.build.tempdir, options.build.tempfileControls, options.build.leanDLLPath, sampleTest.getCmdline()));
bool buildPass
= getEngineBuildEnv(options.model, options.build, options.system, *bEnv, sample::gLogError, postConfigHook);
if (!buildPass)
{
sample::gLogError << "Engine set up failed" << std::endl;
return EXIT_FAILURE;
}
// Exit as version is already printed during getEngineBuildEnv
if (options.build.getPlanVersionOnly)
{
return EXIT_SUCCESS;
}
// dynamicPlugins may have been updated by getEngineBuildEnv above
bEnv->engine.setDynamicPlugins(options.system.dynamicPlugins);
// When some options are enabled, engine deserialization is not supported on the platform that the engine was
// built.
bool const supportDeserialization = !options.build.safe && !options.build.buildDLAStandalone
&& options.build.runtimePlatform == nvinfer1::RuntimePlatform::kSAME_AS_BUILD;
if (supportDeserialization && options.build.refittable)
{
auto* engine = bEnv->engine.get();
if (options.reporting.refit)
{
dumpRefittable(*engine);
}
// Refit from ONNX model
if (!options.inference.refitOnnxModel.empty())
{
bool const success = refitFromOnnx(*engine, options.inference.refitOnnxModel, options.inference.threads);
if (!success)
{
sample::gLogError << "Engine refit from ONNX model failed." << std::endl;
return EXIT_FAILURE;
}
}
if (options.inference.timeRefit)
{
if (bEnv->network.operator bool())
{
bool const success = timeRefit(*bEnv->network, *engine, options.inference.threads);
if (!success)
{
sample::gLogError << "Engine refit failed." << std::endl;
return EXIT_FAILURE;
}
}
else
{
sample::gLogWarning << "Network not available, skipped timing refit." << std::endl;
}
}
}
if (options.build.skipInference)
{
if (supportDeserialization)
{
printLayerInfo(options.reporting, bEnv->engine.get(), nullptr);
printOptimizationProfileInfo(options.reporting, bEnv->engine.get());
}
sample::gLogInfo << "Skipped inference phase since --skipInference is added." << std::endl;
return EXIT_SUCCESS;
}
std::unique_ptr<InferenceEnvironmentBase> iEnv;
if (!options.build.safe)
{
iEnv = std::make_unique<InferenceEnvironmentStd>(*bEnv);
}
else
{
#if ENABLE_UNIFIED_BUILDER
iEnv = std::make_unique<InferenceEnvironmentSafe>(*bEnv);
#else
sample::gLogInfo << "--safe flag is enabled but application is not compatible with safety." << std::endl;
return EXIT_FAILURE;
#endif
}
// We avoid re-loading some dynamic plugins while deserializing
// if they were already serialized with `setPluginsToSerialize`.
std::vector<std::string> dynamicPluginsNotSerialized;
for (auto& pluginName : options.system.dynamicPlugins)
{
if (std::find(
options.system.setPluginsToSerialize.begin(), options.system.setPluginsToSerialize.end(), pluginName)
== options.system.setPluginsToSerialize.end())
{
dynamicPluginsNotSerialized.emplace_back(pluginName);
}
}
iEnv->engine.setDynamicPlugins(dynamicPluginsNotSerialized);
// Delete build environment.
bEnv.reset();
if (options.inference.timeDeserialize)
{
if (timeDeserialize(*iEnv, options.system))
{
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
if (options.build.safe && options.system.DLACore >= 0)
{
sample::gLogInfo << "Safe DLA capability is detected. Please save DLA loadable with --saveEngine option, "
"then use dla_safety_runtime to run inference with saved DLA loadable, "
"or alternatively run with your own application"
<< std::endl;
return EXIT_FAILURE;
}
bool const profilerEnabled = options.reporting.profile || !options.reporting.exportProfile.empty();
bool const layerInfoEnabled = options.reporting.layerInfo || !options.reporting.exportLayerInfo.empty();
if (iEnv->safe && (profilerEnabled || layerInfoEnabled))
{
sample::gLogError << "Safe runtime does not support --dumpProfile or --exportProfile=<file> or "
"--dumpLayerInfo or --exportLayerInfo=<file>, please use "
"--verbose to print profiling info."
<< std::endl;
return EXIT_FAILURE;
}
if (!setUpInference(*iEnv, options.inference, options.system))
{
sample::gLogError << "Inference set up failed" << std::endl;
return EXIT_FAILURE;
}
if (!options.build.safe)
{
printLayerInfo(options.reporting, iEnv->engine.get(),
static_cast<InferenceEnvironmentStd*>(iEnv.get())->contexts.front().get());
printOptimizationProfileInfo(options.reporting, iEnv->engine.get());
}
std::vector<InferenceTrace> trace;
sample::gLogInfo << "Starting inference" << std::endl;
#if !defined(_WIN32)
// Load ALL reference outputs for accuracy validation (if provided)
// This loads all refPairs upfront since they will be used in the inner loop
if (!options.build.safe)
{
auto* iEnvStd = static_cast<InferenceEnvironmentStd*>(iEnv.get());
for (size_t pairIdx = 0; pairIdx < options.inference.refPairs.size(); ++pairIdx)
{
loadRefOutputs(*iEnv, options.inference, *iEnvStd->contexts.front(), pairIdx);
}
}
#if ENABLE_UNIFIED_BUILDER
else
{
auto* iEnvSafe = static_cast<InferenceEnvironmentSafe*>(iEnv.get());
for (size_t pairIdx = 0; pairIdx < options.inference.refPairs.size(); ++pairIdx)
{
loadRefOutputs(*iEnv, options.inference, *iEnvSafe->mClonedGraphs.front(), pairIdx);
}
}
#endif
#endif // !defined(_WIN32) && !TRT_WINML
if (!runInference(options.inference, *iEnv, options.system.device, trace, options.reporting))
{
sample::gLogError << "Error occurred during inference" << std::endl;
return EXIT_FAILURE;
}
printPerformanceReport(
trace, options.reporting, options.inference, sample::gLogInfo, sample::gLogWarning, sample::gLogVerbose);
printOutput(options.reporting, *iEnv, options.inference.batch);
if (profilerEnabled)
{
iEnv->profiler = std::make_unique<Profiler>();
if (!prepareProfileRun(*iEnv, options.inference, options.system))
{
return EXIT_FAILURE;
}
if (!runInference(options.inference, *iEnv, options.system.device, trace, options.reporting))
{
sample::gLogError << "Error occurred during inference" << std::endl;
return EXIT_FAILURE;
}
}
printPerformanceProfile(options.reporting, *iEnv);
// --tuningResultFile is the hidden parent->child IPC channel used by the
// tuning loop. Write a compact JSON with gpu_time_ms, accuracy_failed, and
// per-tensor accuracy_loss so the parent can update its cache + best
// tracking after waitpid(). The flag is omitted from --help; an end user
// who passes it manually still gets the same JSON, which is intentional —
// it makes a tuning iteration reproducible by `--setBuildRoute=<route>
// --tuningResultFile=...`.
if (!options.tuning.tuningResultFile.empty())
{
double meanGpuTimeMs{0.0};
if (!trace.empty())
{
double sum{0.0};
for (auto const& t : trace)
{
sum += static_cast<double>(t.computeEnd - t.computeStart);
}
meanGpuTimeMs = sum / static_cast<double>(trace.size());
}
nlohmann::json j;
j["gpu_time_ms"] = meanGpuTimeMs;
j["accuracy_failed"] = iEnv->accuracyFailed;
auto lossJson = nlohmann::json::object();
for (auto const& [name, loss] : iEnv->accuracyLossValues)
{
lossJson[name] = loss;
}
j["accuracy_loss"] = std::move(lossJson);
std::ofstream out(options.tuning.tuningResultFile);
if (!out)
{
sample::gLogError << "Cannot open --tuningResultFile for writing: "
<< options.tuning.tuningResultFile << std::endl;
}
else
{
out << j.dump(2) << std::endl;
}
}
// Check if accuracy validation failed
if (iEnv->accuracyFailed)
{
sample::gLogError << "Accuracy validation FAILED: one or more tensors exceeded the threshold." << std::endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}
int sample::trtexecMain(int argc, char** argv, PostConfigCallback const& postConfigHook)
{
std::string const sampleName = "TensorRT.trtexec";
auto sampleTest = sample::gLogger.defineTest(sampleName, argc, argv);
AllOptions options;
try
{
sample::gLogger.reportTestStart(sampleTest);
Arguments args = argsToArgumentsMap(argc, argv);
int32_t const parseResult = parseArgs(sampleTest, args, options);
if (parseResult != kCONTINUE_MAIN)
{
return parseResult;
}
int32_t const result = runOnceBuildAndInfer(sampleTest, options, postConfigHook);
if (result != EXIT_SUCCESS)
{
return sample::gLogger.reportFail(sampleTest);
}
}
catch (std::exception const& e)
{
sample::gLogError << "Uncaught exception detected: " << e.what() << std::endl;
return sample::gLogger.reportFail(sampleTest);
}
return sample::gLogger.reportPass(sampleTest);
}
// ============================================================================
// Tuning-loop driver: parent side of --tuneBuildRoutes / --continue.
//
// Architecture: the parent never builds an engine itself. It enumerates routes,
// fork+execs a child trtexec per iteration with `--setBuildRoute=<route>` and
// `--tuningResultFile=<json>` injected, then waitpid()s and reads the JSON the
// child wrote. trtexecMain is therefore reused unmodified — every iteration is
// reproducible by re-running the child's argv by hand.
//
// Windows path: tuning isn't supported (no fork). runTuningLoop returns an
// error there. Linux is the only target until a separate cross-platform
// implementation is needed.
// ============================================================================
#if defined(_WIN32)
int32_t sample::runTuningLoop(int32_t /*argc*/, char** /*argv*/)
{
sample::gLogError << "--tuneBuildRoutes is not supported on Windows (no fork())." << std::endl;
return EXIT_FAILURE;
}
#else // POSIX && ENABLE_FEATURE_GLOBAL_PERF_TUNER
namespace
{
//! Per-iteration result extracted from the child's --tuningResultFile JSON.
struct IterationResult
{
bool crashed{false}; //!< Child crashed or fork/waitpid failed.
bool accuracyFailed{false}; //!< Child reported accuracy-threshold failure.
int32_t exitCode{0}; //!< Child exit code (or -1 on fork/waitpid error).
double gpuTimeMs{0.0}; //!< Mean GPU compute time (ms) from the trace.
std::string errorMessage; //!< Brief diagnostic for the parent log.
std::unordered_map<std::string, double> accuracyLossValues;
};
//! Read the child's tuning result JSON. Returns a partial IterationResult
//! with crashed=true if the file is missing or unparseable.
IterationResult readChildResult(std::string const& jsonPath)
{
IterationResult r;
std::ifstream in(jsonPath);
if (!in)
{
r.crashed = true;
r.errorMessage = "missing tuning result file " + jsonPath
+ " (child likely crashed before writing)";
return r;
}
try
{
nlohmann::json j;
in >> j;
r.gpuTimeMs = j.value("gpu_time_ms", 0.0);
r.accuracyFailed = j.value("accuracy_failed", false);
if (j.contains("accuracy_loss") && j["accuracy_loss"].is_object())
{
for (auto const& [k, v] : j["accuracy_loss"].items())
{
r.accuracyLossValues[k] = v.get<double>();
}
}
}
catch (std::exception const& e)
{
r.crashed = true;
r.errorMessage = std::string{"failed to parse tuning result JSON: "} + e.what();
}
return r;
}
//! Fork+exec one child trtexec invocation for the given route. Reads the
//! resulting JSON and returns an IterationResult. Never throws — failures
//! are reported as `crashed=true` with a brief errorMessage.
IterationResult runChildForOneRoute(int32_t argc, char** argv, BigInt const& globalIndex, std::string const& route,
std::string const& enginePath, std::string const& resultJsonPath)
{
std::vector<std::string> storage;
auto const childArgv = buildTuningChildArgv(argc, argv, route, enginePath, resultJsonPath, storage);
sample::gLogInfo << "Tuning iteration [" << globalIndex.toString() << "]: " << route << std::endl;
// Ensure stale result files from a previous iteration aren't mistaken for this one's output.
std::remove(resultJsonPath.c_str());
pid_t const pid = fork();
if (pid < 0)
{
IterationResult r;
r.crashed = true;
r.exitCode = -1;
r.errorMessage = std::string{"fork() failed: "} + std::strerror(errno);
sample::gLogError << r.errorMessage << std::endl;
return r;
}
if (pid == 0)
{
// Child: replace ourselves with a fresh trtexec invocation. execvp searches PATH for argv[0].
execvp(childArgv[0], childArgv.data());
// execvp returns only on failure.
std::cerr << "execvp() failed: " << std::strerror(errno) << std::endl;
_exit(127);
}
// Parent: wait for the child.
int32_t status{};
pid_t const w = waitpid(pid, &status, 0);
if (w < 0)
{
IterationResult r;
r.crashed = true;
r.exitCode = -1;
r.errorMessage = std::string{"waitpid() failed: "} + std::strerror(errno);
return r;
}
IterationResult r = readChildResult(resultJsonPath);
if (WIFEXITED(status))
{
r.exitCode = WEXITSTATUS(status);
if (r.exitCode != EXIT_SUCCESS && !r.crashed)
{
// Child wrote a result file but exited non-zero — e.g. accuracy validation
// exceeded the threshold. Not a crash; the result is still valid for tuning.
r.errorMessage = "child exited with status " + std::to_string(r.exitCode);
}
}
else if (WIFSIGNALED(status))
{
r.crashed = true;
r.exitCode = -1;
int32_t const sig = WTERMSIG(status);
r.errorMessage = std::string{"child killed by signal "} + std::to_string(sig);
char const* name = strsignal(sig);
if (name != nullptr)
{
r.errorMessage += " (";
r.errorMessage += name;
r.errorMessage += ")";
}
}
return r;
}
//! Resolve the tuning context up-front: parse the user's expression, query the
//! knob database from --setBuildRoute's empty-route side-effect path (the
//! default-constructed BuilderConfig populates `allBuildRoutes` from Myelin),
//! and expand into a TuningContext.
//!
//! Returns false (and logs) if the expression is malformed or empty.
bool buildTuningContext(TuningOptions const& tuning, TuningContext& ctx)
{
// Get the knob database JSON by spinning up a default BuilderConfig.
auto builder = std::unique_ptr<nvinfer1::IBuilder>{createBuilder()};
if (!builder)
{
sample::gLogError << "buildTuningContext: createBuilder failed" << std::endl;
return false;
}
auto config = std::unique_ptr<nvinfer1::IBuilderConfig>(builder->createBuilderConfig());
if (!config)
{
sample::gLogError << "buildTuningContext: createBuilderConfig failed" << std::endl;
return false;
}
char const* allRoutesC = config->getAllBuildRoutes();
std::string const allRoutesStr = allRoutesC != nullptr ? allRoutesC : "";
if (allRoutesStr.empty())
{
sample::gLogError << "IBuilderConfig::getAllBuildRoutes() returned empty — "
<< "tuning requires the Myelin knob database, which is only available on "
<< "enterprise TRT (non-RTX, non-WoA)." << std::endl;
return false;
}
BuildRouteKnobDatabase db;
if (!db.loadFromJsonString(allRoutesStr))
{
sample::gLogError << "Failed to parse knob database from getAllBuildRoutes()." << std::endl;
return false;
}
BuildRouteExprParser parser(db);
auto parsed = parser.parse(tuning.tuningExpr);
if (!parsed.has_value())
{
sample::gLogError << "Failed to parse --tuneBuildRoutes expression: " << parser.getError() << std::endl;
return false;
}
ctx.parsedExprs = std::move(*parsed);
ctx.searchAlgorithm = tuning.tuningSearchAlgorithm;
ctx.tunerVersion = db.getTunerVersion();
ctx.defaultBuildRoute = db.buildDefaultPath();
// Fill defaultValues parallel to parsedExprs (used by fast / mixed mode).
ctx.defaultValues.clear();
ctx.defaultValues.reserve(ctx.parsedExprs.size());
for (auto const& p : ctx.parsedExprs)
{
ctx.defaultValues.emplace_back(db.getDefaultValue(p.mKnobName));
}
ctx.totalCount = ctx.count();
sample::gLogInfo << "Expanded to " << ctx.totalCount.toString() << " build route configurations" << std::endl;
return true;
}
//! Storage + view of an argv reconstructed from a tuning-cache header. The caller
//! reads `argc` / `argv()` and passes them to parseArgs(); `resumeFromIter` is the
//! iteration index to skip up to. `storage` owns the strings backing `argvPtrs`.
struct ContinueResumeState
{
std::vector<std::string> storage;
std::vector<char*> argvPtrs;
int32_t argc{0};
int64_t resumeFromIter{0};
[[nodiscard]] char** argv() noexcept
{
return argvPtrs.data();
}
};
//! \brief Validate the bare-`--continue` invocation and rebuild argv from the cache header.
//!
//! The user must pass exactly `--continue` + `--tuningCacheFile=<path>` (in any order) and
//! no other options — the cache header is the source of truth for everything else. The
//! function logs and returns false on validation or I/O errors so the caller can fail fast.
//!
//! On success, `state` carries the reconstructed argv (with absolute paths) and the resume
//! iteration index. Caller must pass the SAME argv[0] as the running binary, since the cache
//! stores the original argv[0] which may not match a relocated trtexec.
bool reconstructArgvForContinue(int32_t argc, char** argv, ContinueResumeState& state)
{
constexpr char const* kCACHE_FLAG = "--tuningCacheFile=";
uint64_t const flagLen = std::strlen(kCACHE_FLAG);
// Pre-check: the only allowed args are `--continue` and `--tuningCacheFile=<path>`.
std::string cachePath;
for (int32_t i = 1; i < argc; ++i)
{
if (argv[i] == nullptr)
{
continue;
}
std::string const arg(argv[i]);
bool const isContinue = (arg == "--continue");
bool const isCacheFile = (std::strncmp(argv[i], kCACHE_FLAG, flagLen) == 0);
if (!isContinue && !isCacheFile)
{
sample::gLogError << "--continue requires exactly --tuningCacheFile=<path>; "
<< "no other options are allowed (got '" << arg << "'). "
<< "All options come from the cache header." << std::endl;
return false;
}
if (isCacheFile)
{
cachePath = argv[i] + flagLen;
}
}
if (cachePath.empty())
{
sample::gLogError << "--continue requires --tuningCacheFile=<path> on the command line." << std::endl;
return false;
}
auto header = readTuningCacheHeader(cachePath);
if (!header.has_value())
{
sample::gLogError << "--continue: failed to read cache header from " << cachePath << std::endl;
return false;
}
state.resumeFromIter = header->completedIterations;
state.storage = reconstructArgvFromCacheHeader(*header, argv[0], cachePath);
state.argvPtrs.reserve(state.storage.size() + 1);
for (auto& s : state.storage)
{
state.argvPtrs.emplace_back(s.data());
}
state.argvPtrs.emplace_back(nullptr);
state.argc = static_cast<int32_t>(state.storage.size());
sample::gLogInfo << "--continue: resuming from iteration " << state.resumeFromIter << " using " << state.argc
<< " args reconstructed from cache header." << std::endl;
return true;
}
//! \brief Cross-flag validation specific to the tuning loop. Returns false (and logs)
//! when --saveEngine is missing in a mode that requires it. Called after parseArgs
//! and after the cache-header re-assert.
bool validateTuningOptions(AllOptions const& options)
{
// --saveEngine is optional for pure benchmarking, but required when --loadRefOutputs is set:
// the accuracy-validation path picks a "best" engine and needs somewhere to persist it.
if (options.build.engine.empty() && !options.tuning.dryRun)
{
bool const hasRefOutputs = !options.inference.refPairs.empty() && !options.inference.refPairs[0].second.empty();
if (hasRefOutputs)
{
sample::gLogError << "--tuneBuildRoutes with --loadRefOutputs requires --saveEngine to "
<< "persist the best engine selected by accuracy validation." << std::endl;
return false;
}
sample::gLogWarning << "--tuneBuildRoutes without --saveEngine: best engine will not be saved." << std::endl;
}
return true;
}
//! \brief Print the dryRun enumeration of routes. No engine work is performed.
void emitDryRunListing(TuningContext const& ctx)
{
sample::gLogInfo << "--dryRun: " << ctx.totalCount.toString() << " build routes would be tried:" << std::endl;
for (BigInt i{0}; i < ctx.totalCount; ++i)
{
sample::gLogInfo << "[" << i.toString() << "]:" << ctx.getPathAtIndex(i) << std::endl;
}
}
//! Shared mutable state for a phase (or phase 1 + phase 2 of mixed-mode) of the tuning loop.
//! Aggregated into a single struct so runOnePhase can be a free function instead of a deeply
//! nested lambda.
struct PhaseState
{
AllOptions const& options; //!< Parsed options for this run.
Logger::TestAtom const& sampleTest; //!< For TASK_BEGIN/END/ABORT banners.
pid_t const ppid{}; //!< Parent PID, used in temp filenames.
std::chrono::steady_clock::time_point const startTime; //!< Loop start, for --tuningTimeOut.
int32_t const argc{}; //!< Parent argv (passed verbatim to children).
char** const argv{}; //!< Parent argv (passed verbatim to children).
// Mutable across iterations and across phases:
BigInt successCount{0};
double bestGpuTimeMs{std::numeric_limits<double>::infinity()};
std::string bestEnginePath;
std::string bestRoute;
};
//! Build the temp-engine path for one iteration. With --saveAllEngines, use a stable
//! per-iteration name under the user's --saveEngine (matches the format the turtle
//! tests assert against: `<engine>.iter<N>`). Without it, use a pid-scoped temp
//! that gets unlinked at promotion time. The phase label disambiguates the temp
//! path so mixed-mode phase 2 cannot overwrite a phase-1 file mid-flight.
std::string makeIterationEnginePath(PhaseState const& state, char const* phaseLabel, BigInt const& i)
{
if (state.options.build.saveAllEngines)
{
return state.options.build.engine + ".iter" + i.toString();
}
return "/tmp/trtexec_tuning_" + std::to_string(state.ppid) + "_iter" + phaseLabel + "_" + i.toString() + ".plan";
}
//! \brief Run one phase of the tuning loop (phase 1, or phase 2 of mixed mode).
//!
//! Iterates phaseCtx.totalCount times, fork+execs a child per iteration, and updates
//! `state` with the best route seen. When `positiveKnobs` is non-null and we're past
//! the baseline iteration (i==0), records each iteration that beats the baseline so
//! mixed-mode can build its phase-2 sub-context.
//!
//! Returns false if --tuningTimeOut tripped (so the caller stops chaining phases),
//! true on normal completion.
//!
//! NOLINT: orchestrator function that touches every per-iteration concern (timeout, route
//! enumeration, child invocation, best-tracker update, mixed-mode positive-knob collection,
//! cache writing, temp-file cleanup, TASK_BEGIN/END/ABORT logging). Further extraction would
//! either fragment one logical iteration step across multiple functions or require passing
//! the same PhaseState everywhere — both make the per-iteration story harder to read.
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
bool runOnePhase(PhaseState& state, TuningContext const& phaseCtx, char const* phaseLabel,
std::vector<MixedSearchKnobResult>* positiveKnobs, double* baselineGpuTimeMsOut, int64_t skipUntil)
{
sample::gLogInfo << "Tuning " << phaseLabel << ": " << phaseCtx.totalCount.toString() << " iterations." << std::endl;
double baselineGpuTimeMs = std::numeric_limits<double>::infinity();
for (BigInt i{0}; i < phaseCtx.totalCount; ++i)
{
// --continue: skip iterations already in the cache.
if (skipUntil > 0 && i < BigInt{static_cast<uint64_t>(skipUntil)})
{
continue;
}
if (state.options.tuning.timeout > 0)
{
auto const elapsedS = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::steady_clock::now() - state.startTime).count();
if (elapsedS >= state.options.tuning.timeout)
{
sample::gLogInfo << "Tuning timeout reached (" << state.options.tuning.timeout
<< "s); stopping early." << std::endl;
return false;
}
}
std::string const route = phaseCtx.getPathAtIndex(i);
std::string const enginePath = makeIterationEnginePath(state, phaseLabel, i);
std::string const jsonPath = "/tmp/trtexec_tuning_" + std::to_string(state.ppid)
+ "_iter" + i.toString() + ".json";
sample::gLogger.reportTaskBegin(state.sampleTest, i.toString(), route);
IterationResult const result = runChildForOneRoute(state.argc, state.argv, i, route, enginePath, jsonPath);
if (!result.crashed && result.exitCode == EXIT_SUCCESS)
{
++state.successCount;
if (result.gpuTimeMs < state.bestGpuTimeMs)
{
state.bestGpuTimeMs = result.gpuTimeMs;
state.bestEnginePath = enginePath;
state.bestRoute = route;
}
// Track baseline (index 0) so mixed-mode can decide "positive knob".
if (i.isZero())
{
baselineGpuTimeMs = result.gpuTimeMs;
}
sample::gLogger.reportTaskEnd(state.sampleTest, i.toString(), route);
}
else
{
sample::gLogWarning << "Iteration [" << i.toString() << "] failed: "
<< (result.errorMessage.empty() ? "(no message)" : result.errorMessage) << std::endl;
sample::gLogger.reportTaskAbort(state.sampleTest, i.toString(), route);
}
// For mixed-mode phase 1, collect knobs that beat the baseline.
if (positiveKnobs != nullptr && !i.isZero() && baselineGpuTimeMs != std::numeric_limits<double>::infinity())
{
collectPositiveKnobFromResult(
result.crashed, result.gpuTimeMs, baselineGpuTimeMs, i, phaseCtx, *positiveKnobs);
}
// Append this iteration's result to the tuning cache file (--tuningCacheFile).
if (!state.options.tuning.tuningCacheFile.empty())
{
writeTuningCacheIteration(state.options.tuning.tuningCacheFile, i.toUint64(), route, result.crashed,
result.errorMessage, result.accuracyLossValues, result.gpuTimeMs);
}
std::remove(jsonPath.c_str());
}
if (baselineGpuTimeMsOut != nullptr)
{
*baselineGpuTimeMsOut = baselineGpuTimeMs;
}
return true;
}
//! \brief Copy the best-iteration engine to the user's --saveEngine path and emit the
//! final summary. Cleans up the per-iteration temp engine if --saveAllEngines was off.
//! Returns the trtexec exit code (pass if any iteration succeeded, fail otherwise).
int32_t finalizeBestEngine(PhaseState const& state, BigInt const& totalCount)
{
if (state.bestEnginePath.empty())
{
sample::gLogError << "No tuning iteration succeeded; no engine written." << std::endl;
return sample::gLogger.reportFail(state.sampleTest);
}
if (state.bestEnginePath != state.options.build.engine)
{
std::ifstream src(state.bestEnginePath, std::ios::binary);
std::ofstream dst(state.options.build.engine, std::ios::binary);
dst << src.rdbuf();
if (!state.options.build.saveAllEngines)
{
// Per-iteration engine was a temp; clean it up now that it's been promoted.
std::remove(state.bestEnginePath.c_str());
}
}
sample::gLogInfo << "Best iteration: " << state.bestRoute << " (gpu_time_ms=" << state.bestGpuTimeMs << ")"
<< std::endl;
sample::gLogInfo << "Tuning summary: " << state.successCount.toString() << " / " << totalCount.toString()
<< " iterations succeeded." << std::endl;
return sample::gLogger.reportPass(state.sampleTest);
}
} // namespace
int32_t sample::runTuningLoop(int32_t argc, char** argv)
{
std::string const sampleName =
"TensorRT.trtexec";
auto sampleTest = sample::gLogger.defineTest(sampleName, argc, argv);
sample::gLogger.reportTestStart(sampleTest);
// 1. --continue: rebuild argv from the cache header. The header stores the original argv
// with absolute paths and the expanded tuning expression; everything else is rejected.
ContinueResumeState resume;
int32_t effectiveArgc = argc;
char** effectiveArgv = argv;
if (peekArg(argc, argv, "--continue"))
{
if (!reconstructArgvForContinue(argc, argv, resume))
{
return sample::gLogger.reportFail(sampleTest);
}
effectiveArgc = resume.argc;
effectiveArgv = resume.argv();
}
// 2. Parse options against the effective argv (cache-reconstructed for --continue, raw otherwise).
AllOptions options;
try
{
Arguments args = argsToArgumentsMap(effectiveArgc, effectiveArgv);
int32_t const parseResult = parseArgs(sampleTest, args, options);
if (parseResult != kCONTINUE_MAIN)
{
return parseResult;
}
}
catch (std::exception const& e)
{
sample::gLogError << "Argument parse error in tuning loop: " << e.what() << std::endl;
return sample::gLogger.reportFail(sampleTest);
}
// 3. Resume mode: the reconstructed argv has no --continue, so parseArgs left continueFromCache
// false. Re-assert it now so the cache-header writer below preserves existing rows.
if (effectiveArgv != argv)
{
options.tuning.continueFromCache = true;
}
if (!validateTuningOptions(options))
{
return sample::gLogger.reportFail(sampleTest);
}
// 4. Expand the tuning expression into a TuningContext.
TuningContext ctx;
if (!buildTuningContext(options.tuning, ctx))
{
return sample::gLogger.reportFail(sampleTest);
}
if (options.tuning.dryRun)
{
emitDryRunListing(ctx);
return sample::gLogger.reportPass(sampleTest);
}
// 5. Fresh tuning runs write a cache header; resumed runs skip it (the file already has one).
if (!options.tuning.tuningCacheFile.empty() && !options.tuning.continueFromCache)
{
writeTuningCacheHeader(
options.tuning.tuningCacheFile, options, argc, argv, ctx.tunerVersion, ctx.defaultBuildRoute);
}
// 6. Phase 1 — always runs. For mixed mode, may be followed by a phase 2 over positive knobs.
// Children must see the reconstructed argv when resuming — the user's bare
// `--continue --tuningCacheFile=path` argv has none of the original build/inference flags.
PhaseState state{options, sampleTest, getpid(), std::chrono::steady_clock::now(), effectiveArgc, effectiveArgv};
std::vector<MixedSearchKnobResult> positiveKnobs;
double phase1BaselineMs{std::numeric_limits<double>::infinity()};
bool const isMixed = options.tuning.tuningSearchAlgorithm == TuningSearchAlgorithm::kMIXED;
bool const phase1Completed
= runOnePhase(state, ctx, "phase1", isMixed ? &positiveKnobs : nullptr, &phase1BaselineMs, resume.resumeFromIter);
if (phase1Completed && isMixed && positiveKnobs.size() > 1)
{
sample::gLogInfo << "Mixed search: " << positiveKnobs.size()
<< " positive knobs identified; entering phase 2." << std::endl;
TuningContext const phase2Ctx = buildMixedPhase2Context(ctx, positiveKnobs);
// Phase 2 always starts fresh (no resume mid-phase-2).
(void) runOnePhase(state, phase2Ctx, "phase2", nullptr, nullptr, 0);
}
else if (isMixed)
{
sample::gLogInfo << "Mixed search: " << positiveKnobs.size()
<< " positive knob(s); skipping phase 2 (need >1)." << std::endl;
}
// 7. Promote the best iteration's engine to the user's --saveEngine path.
return finalizeBestEngine(state, ctx.totalCount);
}
#endif // POSIX && ENABLE_FEATURE_GLOBAL_PERF_TUNER