Skip to content

Commit

Permalink
encapulate LIR dumper for Vladislav comments
Browse files Browse the repository at this point in the history
  • Loading branch information
chenhu-wang committed May 29, 2024
1 parent 19a4833 commit 16d4d24
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 171 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 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 the result is as expected. This capability is introduced to serialize LIRs before and after passes, then developer can check these LIR changes and stages.
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
Expand All @@ -9,12 +9,12 @@ To turn on snippets LIR passses serialization feature, the following environment

Examples:
```sh
OV_SNIPPETS_DUMP_LIR="passes=all dir=path/dumpdir formats=all" binary ...
OV_SNIPPETS_DUMP_LIR="passes=Insert dir=path/dumpdir formats=control_flow" binary ...
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 set value is included in pass name. Key word 'all' means to dump LIR around every pass. If not set, will not dump.
- `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 value of control_flow, data_flow or all. If omitted, it defaults to control_flow.
- `formats` : Support values are control_flow, data_flow and all. If omitted, it defaults to control_flow.
2 changes: 1 addition & 1 deletion src/common/snippets/docs/debug_capabilities/perf_count.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
64 changes: 64 additions & 0 deletions src/common/snippets/include/snippets/LIR_pass_dumper.hpp
Original file line number Diff line number Diff line change
@@ -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
178 changes: 82 additions & 96 deletions src/common/snippets/include/snippets/debug_caps.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,135 +18,66 @@ namespace ov {
namespace snippets {

class DebugCapsConfig {
private:
struct PropertySetter;
using PropertySetterPtr = std::shared_ptr<PropertySetter>;

public:
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) {
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());
}
};

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;

private:
struct PropertySetter {
virtual bool parseAndSet(const std::string& str) = 0;
virtual std::string getPropertyValueDescription() const = 0;

PropertySetter(std::string name) : propertyName(std::move(name)) {}

virtual ~PropertySetter() = default;

const std::string& getPropertyName() const { return propertyName; }
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) {}

: 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; }
std::string getPropertyValueDescription() const override {
return propertyValueDescription;
}

private:
std::string& property;
const std::string propertyValueDescription;
};

template<std::size_t NumOfBits>
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(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), ',');
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; });
const auto& foundToken =
std::find_if(propertyTokens.begin(), propertyTokens.end(), [tokenName](const Token& token) {
return token.name == tokenName;
});
if (foundToken == propertyTokens.end())
return false;

Expand All @@ -163,7 +94,8 @@ class DebugCapsConfig {
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'");
supportedTokens.append(
"; -'token' is used for exclusion, case does not matter, no tokens is treated as 'all'");
return supportedTokens;
}

Expand All @@ -173,9 +105,63 @@ class DebugCapsConfig {
};

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
} // namespace snippets
} // namespace ov

#endif // SNIPPETS_DEBUG_CAPS
21 changes: 3 additions & 18 deletions src/common/snippets/include/snippets/lowered/linear_ir.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,29 +18,11 @@ 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;
DebugCapsConfig debug_config;
#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.
Expand All @@ -56,6 +38,9 @@ class Config {
// 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;
Expand Down
Loading

0 comments on commit 16d4d24

Please sign in to comment.