This commit is contained in:
Chenhu Wang 2024-06-12 03:26:44 +02:00 committed by GitHub
commit de964d0e02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 334 additions and 20 deletions

View File

@ -0,0 +1,20 @@
# Snippets LIR passes serialization
LIR(Linear Intermediate Representation) is used as graph reprsentation in control flow pipeline, where dozens of passes are applied to LIR. This is to transfer the graph gradually to the stage that can generate kernel directly via expression instruction emission. When each pass is applied to LIR, there are some expected changes. Developers maybe want to check if the result is as expected. This capability is introduced to serialize LIRs before and after passes, then developer can check these LIR changes and status.
To turn on snippets LIR passses serialization feature, the following environment variable should be used:
```sh
OV_SNIPPETS_DUMP_LIR=<space_separated_options> binary ...
```
Examples:
```sh
OV_SNIPPETS_DUMP_LIR="passes=ExtractLoopInvariants dir=path/dumpdir formats=all" binary ...
OV_SNIPPETS_DUMP_LIR="passes=all dir=path/dumpdir formats=control_flow" binary ...
OV_SNIPPETS_DUMP_LIR="passes=Insert formats=data_flow" binary ...
```
Option names are case insensitive, the following options are supported:
- `passes` : Dump LIR around the pass if the value is pass name or is included in pass name. Key word 'all' means to dump LIR around every pass. This option is a must have, should not be omitted.
- `dir` : Path to dumped LIR files. If omitted, it defaults to intel_snippets_LIR_dump.
- `formats` : Support values are control_flow, data_flow and all. If omitted, it defaults to control_flow.

View File

@ -8,4 +8,5 @@ Use the following cmake option to enable snippets debug capabilities:
`-DENABLE_DEBUG_CAPS=ON`
* [Performance counters](perf_count.md)
* [Snippets segfault detector](snippets_segfault_detector.md)
* [Snippets segfault detector](snippets_segfault_detector.md)
* [LIR passes serialization](LIR_passes_serialization.md)

View File

@ -5,4 +5,4 @@ Subgraph in snippets could be very large. Sometimes developers are interested th
There are two perf count modes.
- `Chrono` : Perf count via chrono call. This is a universal method, and support multi-threads scenario to print perf count data for each thread.
- `BackendSpecific` : Perf count provided by backend. This is for device specific requirement. For example, for sake of more light overhead and more accurate result, x86 or x86-64 CPU specific mode via reading RDTSC register is implemented. At current this x86 or x86-64 CPU BackendSpecific mode only support single thread.
One can select prefered mode by setting `perf_count_mode` default value in [snippets Config](../../include/snippets/lowered/linear_ir.hpp)
One can select prefered mode by setting `perf_count_mode` default value in [snippets Config](../../include/snippets/debug_caps.hpp)

View File

@ -0,0 +1,64 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#pragma once
#ifdef SNIPPETS_DEBUG_CAPS
#include "snippets/debug_caps.hpp"
#include "openvino/util/file_util.hpp"
#include "snippets/lowered/linear_ir.hpp"
#include "snippets/lowered/pass/serialize_control_flow.hpp"
#include "snippets/lowered/pass/serialize_data_flow.hpp"
namespace ov {
namespace snippets {
class LIRPassDump {
public:
explicit LIRPassDump(lowered::LinearIR& linear_ir, std::string pass_name)
: linear_ir(linear_ir), pass_name(pass_name), debug_config(linear_ir.get_config().debug_config) {
dump("_in");
}
~LIRPassDump() {
dump("_out");
}
private:
void dump(const std::string&& postfix) const {
static int num = 0; // just to keep dumped IRs ordered in filesystem
const auto pathAndName = debug_config.dumpLIR.dir + "/lir_";
ov::util::create_directory_recursive(debug_config.dumpLIR.dir);
if (debug_config.dumpLIR.format.filter[DebugCapsConfig::LIRFormatFilter::controlFlow]) {
std::string xml_path = pathAndName + std::to_string(num) + '_' + pass_name + "_control_flow" + postfix +".xml";
lowered::pass::SerializeControlFlow SerializeLIR(xml_path);
SerializeLIR.run(linear_ir);
num++;
}
if (debug_config.dumpLIR.format.filter[DebugCapsConfig::LIRFormatFilter::dataFlow]) {
std::string xml_path = pathAndName + std::to_string(num) + '_' + pass_name + "_data_flow" + postfix +".xml";
lowered::pass::SerializeDataFlow SerializeLIR(xml_path);
SerializeLIR.run(linear_ir);
num++;
}
}
lowered::LinearIR& linear_ir;
const std::string pass_name;
const DebugCapsConfig& debug_config;
};
} // namespace snippets
} // namespace ov
#define SNIPPETS_DEBUG_LIR_PASS_DUMP(_linear_ir, _pass) \
auto dumpLIR = _linear_ir.get_config().debug_config.dumpLIR; \
auto pass_name = std::string(_pass->get_type_name()); \
auto dumperPtr = (dumpLIR.passes == "all" || (pass_name.find(dumpLIR.passes) != std::string::npos)) ? \
std::unique_ptr<LIRPassDump>(new LIRPassDump(_linear_ir, pass_name)) : \
nullptr
#else
#define SNIPPETS_DEBUG_LIR_PASS_DUMP(_linear_ir, _pass)
#endif // SNIPPETS_DEBUG_CAPS

View File

@ -0,0 +1,167 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#ifdef SNIPPETS_DEBUG_CAPS
#pragma once
#include "openvino/util/common_util.hpp"
#include "openvino/core/except.hpp"
#include <bitset>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>
namespace ov {
namespace snippets {
class DebugCapsConfig {
struct PropertySetter {
PropertySetter(std::string name) : propertyName(std::move(name)) {}
virtual ~PropertySetter() = default;
virtual bool parseAndSet(const std::string& str) = 0;
virtual std::string getPropertyValueDescription() const = 0;
const std::string& getPropertyName() const {
return propertyName;
}
private:
const std::string propertyName;
};
struct StringPropertySetter : PropertySetter {
StringPropertySetter(const std::string& name, std::string& ref, const std::string&& valueDescription)
: PropertySetter(name),
property(ref),
propertyValueDescription(valueDescription) {}
~StringPropertySetter() override = default;
bool parseAndSet(const std::string& str) override {
property = str;
return true;
}
std::string getPropertyValueDescription() const override {
return propertyValueDescription;
}
private:
std::string& property;
const std::string propertyValueDescription;
};
template <std::size_t NumOfBits>
struct BitsetFilterPropertySetter : PropertySetter {
struct Token {
std::string name;
std::vector<size_t> bits;
};
BitsetFilterPropertySetter(const std::string& name,
std::bitset<NumOfBits>& ref,
const std::vector<Token>&& tokens)
: PropertySetter(name),
property(ref),
propertyTokens(tokens) {}
~BitsetFilterPropertySetter() override = default;
bool parseAndSet(const std::string& str) override {
const auto& tokens =
str.empty() ? std::vector<std::string>{"all"} : ov::util::split(ov::util::to_lower(str), ',');
property.reset();
for (const auto& token : tokens) {
const bool tokenVal = (token.front() != '-');
const auto& tokenName = tokenVal ? token : token.substr(1);
const auto& foundToken =
std::find_if(propertyTokens.begin(), propertyTokens.end(), [tokenName](const Token& token) {
return token.name == tokenName;
});
if (foundToken == propertyTokens.end())
return false;
for (const auto& bit : foundToken->bits) {
property.set(bit, tokenVal);
}
}
return true;
}
std::string getPropertyValueDescription() const override {
std::string supportedTokens = "comma separated filter tokens: ";
for (size_t i = 0; i < propertyTokens.size(); i++) {
if (i)
supportedTokens.push_back(',');
supportedTokens.append(propertyTokens[i].name);
}
supportedTokens.append(
"; -'token' is used for exclusion, case does not matter, no tokens is treated as 'all'");
return supportedTokens;
}
private:
std::bitset<NumOfBits>& property;
const std::vector<Token> propertyTokens;
};
void readProperties();
public:
using PropertySetterPtr = std::shared_ptr<PropertySetter>;
DebugCapsConfig() {
readProperties();
}
struct LIRFormatFilter {
enum Type : uint8_t { controlFlow = 0, dataFlow, NumOfTypes };
std::bitset<NumOfTypes> filter;
PropertySetterPtr getPropertySetter() {
return PropertySetterPtr(new BitsetFilterPropertySetter<NumOfTypes>("formats",
filter,
{
{"all", {controlFlow, dataFlow}},
{"control_flow", {controlFlow}},
{"data_flow", {dataFlow}},
}));
}
};
struct PropertyGroup {
virtual std::vector<PropertySetterPtr> getPropertySetters() = 0;
void parseAndSet(const std::string& str);
};
struct : PropertyGroup {
std::string dir = "intel_snippets_LIR_dump";
LIRFormatFilter format = {1 << LIRFormatFilter::controlFlow};
std::string passes = "";
std::vector<PropertySetterPtr> getPropertySetters() override {
return {PropertySetterPtr(new StringPropertySetter("dir", dir, "path to dumped LIRs")),
format.getPropertySetter(),
PropertySetterPtr(
new StringPropertySetter("passes", passes, "indicate dumped LIRs around these passes"))};
}
} dumpLIR;
// Snippets performance count mode
// Disabled - default, w/o perf count for snippets
// Chrono - perf count with chrono call. This is a universal method, and support multi-thread case to output perf
// count data for each thread. BackendSpecific - perf count provided by backend. This is for device specific
// requirment. For example, in sake of more light overhead and more accurate result, x86 CPU specific mode via read
// RDTSC register is implemented, which take ~50ns, while Chrono mode take 260ns for a pair of perf count start and
// perf count end execution, on ICX. This mode only support single thread.
enum PerfCountMode {
Disabled,
Chrono,
BackendSpecific,
};
PerfCountMode perf_count_mode = PerfCountMode::Disabled;
};
} // namespace snippets
} // namespace ov
#endif // SNIPPETS_DEBUG_CAPS

View File

@ -10,32 +10,19 @@
#include "snippets/target_machine.hpp"
#include "snippets/shape_inference/shape_inference.hpp"
#ifdef SNIPPETS_DEBUG_CAPS
#include "snippets/debug_caps.hpp"
#endif
namespace ov {
namespace snippets {
namespace lowered {
#ifdef SNIPPETS_DEBUG_CAPS
// Snippets performance count mode
// Disabled - default, w/o perf count for snippets
// Chrono - perf count with chrono call. This is a universal method, and support multi-thread case to output perf count data for each thread.
// BackendSpecific - perf count provided by backend. This is for device specific requirment.
// For example, in sake of more light overhead and more accurate result, x86 CPU specific mode via read RDTSC register is implemented,
// which take ~50ns, while Chrono mode take 260ns for a pair of perf count start and perf count end execution, on ICX. This mode only support single thread.
enum PerfCountMode {
Disabled,
Chrono,
BackendSpecific,
};
#endif
class Config {
public:
// True if we should check runtime info for nodes to call specific needed transformations
bool m_need_fill_tail_register = false;
size_t m_loop_depth = 1;
#ifdef SNIPPETS_DEBUG_CAPS
PerfCountMode perf_count_mode = PerfCountMode::Disabled;
#endif
// Some Subgraphs doesn't support domain optimization due to operations' semantics
bool m_enable_domain_optimization = false;
// Minimal advised work amount for parallel execution.
@ -51,6 +38,9 @@ public:
// True if LIR can be fully manually built: all (including I/O) expressions can be added to LIR
// False if LIR can be built from ov::Model only. Prevents adding I/O expressions
bool m_manual_build_support = false;
#ifdef SNIPPETS_DEBUG_CAPS
DebugCapsConfig debug_config;
#endif
};
class LinearIRBuilder;

View File

@ -0,0 +1,69 @@
// Copyright (C) 2024 Intel Corporation
// SPDX-License-Identifier: Apache-2.0
//
#ifdef SNIPPETS_DEBUG_CAPS
#include "snippets/debug_caps.hpp"
namespace ov {
namespace snippets {
void DebugCapsConfig::readProperties() {
auto readEnv = [](const char* envVar) {
const char* env = std::getenv(envVar);
if (env && *env)
return env;
return (const char*)nullptr;
};
const char* envVarValue = nullptr;
if ((envVarValue = readEnv("OV_SNIPPETS_DUMP_LIR"))) {
dumpLIR.parseAndSet(envVarValue);
OPENVINO_ASSERT(!dumpLIR.passes.empty(), "Passes option in OV_SNIPPETS_DUMP_LIR must be provided.");
}
}
void DebugCapsConfig::PropertyGroup::parseAndSet(const std::string& str) {
const auto& options = ov::util::split(str, ' ');
const auto& propertySetters = getPropertySetters();
bool failed = false;
auto getHelp = [propertySetters]() {
std::string help;
for (const auto& property : propertySetters)
help.append('\t' + property->getPropertyName() + "=<" + property->getPropertyValueDescription() + ">\n");
return help;
};
for (const auto& option : options) {
const auto& parts = ov::util::split(option, '=');
if (parts.size() > 2) {
failed = true;
break;
}
const auto& propertyName = ov::util::to_lower(parts.front());
const auto& foundSetter = std::find_if(propertySetters.begin(),
propertySetters.end(),
[propertyName](const PropertySetterPtr& setter) {
return setter->getPropertyName() == propertyName;
});
if (foundSetter == propertySetters.end() ||
!(*foundSetter)->parseAndSet(parts.size() == 1 ? "" : parts.back())) {
failed = true;
break;
}
}
if (failed)
OPENVINO_THROW("Wrong syntax: ",
str,
"\n",
"The following space separated options are supported (option names are case insensitive):",
"\n",
getHelp());
}
} // namespace snippets
} // namespace ov
#endif // SNIPPETS_DEBUG_CAPS

View File

@ -3,6 +3,7 @@
//
#include "snippets/lowered/pass/pass.hpp"
#include "snippets/LIR_pass_dumper.hpp"
#include "snippets/utils.hpp"
@ -33,6 +34,8 @@ void PassPipeline::run(LinearIR& linear_ir) const {
void PassPipeline::run(LinearIR& linear_ir, LinearIR::constExprIt begin, LinearIR::constExprIt end) const {
for (const auto& pass : m_passes) {
OPENVINO_ASSERT(pass != nullptr, "PassPipeline has empty pass!");
SNIPPETS_DEBUG_LIR_PASS_DUMP(linear_ir, pass);
if (m_pass_config->is_disabled(pass->get_type_info())) {
continue;
}

View File

@ -470,7 +470,7 @@ void Subgraph::control_flow_transformations(size_t min_parallel_work_amount, siz
pipeline.run(*m_linear_ir);
#ifdef SNIPPETS_DEBUG_CAPS
if (m_linear_ir->get_config().perf_count_mode != lowered::PerfCountMode::Disabled) {
if (m_linear_ir->get_config().debug_config.perf_count_mode != DebugCapsConfig::PerfCountMode::Disabled) {
lowered::pass::InsertPerfCount perf_count_pass({});
perf_count_pass.run(*m_linear_ir, m_linear_ir->cbegin(), m_linear_ir->cend());
}