-
Notifications
You must be signed in to change notification settings - Fork 116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Extend the capabilities of the graph tooling to include extra information of the ttnn operations #18380
Conversation
@@ -33,6 +66,55 @@ std::string demangle(const char* name) { | |||
return ret_val; | |||
} | |||
|
|||
struct AnyToString { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lets move to a different file
and lets move specific serializations to a different file too please
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
solved!
@@ -67,6 +67,7 @@ class GraphProcessor : public tt::tt_metal::IGraphProcessor { | |||
int counter = 0; | |||
std::string node_type; | |||
std::unordered_map<std::string, std::string> params; | |||
std::vector<std::string> arguments; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think its good for this PR
But I am now think if this should store the copy of the any value itself and stringify only when we try to dump the trace 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without context, it is not clear what the difference is between "args" and "params". Consider documenting, since this is a public API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will definitely document it because it took me a good while to get the difference
namespace ttnn::graph { | ||
std::string graph_demangle(const char* name); | ||
|
||
struct GraphArgumentSerializer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can make this a class, make constructor private, move init inside constructor.
GraphArgumentSerializer::instance() or GraphArgumentSerializer::registry() will create an instance and the instance will get itself initialized.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1. If we use a static registry, right now it isn't clear what exactly is static here.
Also use tt::stl::Indestructible
for static data.
register_small_vector<T, 2>(); | ||
register_small_vector<T, 4>(); | ||
register_small_vector<T, 8>(); | ||
register_small_vector<T, 16>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only saw a single usage for Shape related things I think.
I'd reduce to a single default value for now.
CC @sminakov-tt
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please also add a sample C++ test?
We have some C++ consumers interested in this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great!
I think it is a good first step as is.
There are a couple things which I commented on, which are good to discuss separately, but they should not be done in this PR. Many things going to evolve over a couple iterations.
ttnn/cpp/ttnn/tensor/types.cpp
Outdated
case DataType::UINT16: return os << "UINT16"; | ||
case DataType::INT32: return os << "INT32"; | ||
case DataType::INVALID: return os << "Invalid"; | ||
default: return os << "Unknown data type"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the default
- not having it triggers the compiler error when new enums are added - this is useful for future changes.
#include <unordered_map> | ||
|
||
namespace ttnn::graph { | ||
std::string graph_demangle(const char* name); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can name
be std::string_view
?
static std::unordered_map<std::type_index, ConvertionFunction>& registry(); | ||
|
||
template <typename T, std::size_t N> | ||
static void register_small_vector(); | ||
|
||
// In case you don't care about all the variations of the type | ||
// such as const T, const T&, T, T&, etc | ||
template <typename T> | ||
static void register_special_type(); | ||
|
||
template <typename T> | ||
static void register_type(); | ||
|
||
template <typename OptionalT> | ||
static void register_optional_type(); | ||
|
||
static std::vector<std::string> to_list(const std::span<std::any>& span); | ||
|
||
static void initialize(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be public?
case Layout::ROW_MAJOR: return os << "Row Major"; | ||
case Layout::TILE: return os << "Tile"; | ||
case Layout::INVALID: return os << "Invalid"; | ||
default: return os << "Unknown layout"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ditto for default
@@ -154,6 +154,22 @@ std::vector<std::string> extract_calltrace(const nlohmann::json& trace) { | |||
return op_calls; | |||
} | |||
|
|||
std::vector<std::vector<std::string>> extract_arguments(const nlohmann::json& trace) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you want it in this way, it does not belong to common utils and need to be moved to tests.
If you want a useful common util, wdyt if the return type is slightly different? Something like
using OperationName = std::string; // tttn.op name
using Arguments = std::vector<std::string>; // vector of serialized args
using Returns = std::vector<std::string>; // vector of serialized TensorSpec for returned tensors
vector<OperationName, Arguments, Return>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds like a great idea! Right on it!
using ConvertionFunction = std::function<std::string(const std::any&)>; | ||
std::vector<std::string> to_list(const std::span<std::any>& span); | ||
|
||
static GraphArgumentSerializer* instance(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
static GraphArgumentSerializer* instance(); | |
static GraphArgumentSerializer& instance(); |
@@ -100,7 +88,11 @@ GraphProcessor::GraphProcessor(RunMode mode) : run_mode(mode) { | |||
end_function_any_map[typeid(std::reference_wrapper<Tensor>)] = [ptr = this](const std::any& val) mutable { | |||
ptr->end_function_process_tensor(val); | |||
}; | |||
|
|||
// Add all the elements in the map to handle different datatypes | |||
TT_ASSERT(GraphArgumentSerializer::instance()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is not needed really
GraphArgumentSerializer::GraphArgumentSerializer() { initialize(); } | ||
|
||
GraphArgumentSerializer* GraphArgumentSerializer::instance() { | ||
static GraphArgumentSerializer* new_instance = new GraphArgumentSerializer(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can as well be
static GraphArgumentSerializer* new_instance = new GraphArgumentSerializer(); | |
static GraphArgumentSerializer new_instance; |
|
||
GraphArgumentSerializer::GraphArgumentSerializer() { initialize(); } | ||
|
||
GraphArgumentSerializer* GraphArgumentSerializer::instance() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GraphArgumentSerializer* GraphArgumentSerializer::instance() { | |
GraphArgumentSerializer& GraphArgumentSerializer::instance() { |
auto tt_input1 = CreateTensor(); | ||
auto tt_input2 = CreateTensor(); | ||
ttnn::graph::GraphProcessor::begin_graph_capture(Mode); | ||
ttnn::operations::moreh::moreh_dot::MorehDot::invoke( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ttnn::moreh_dot(...)
it is a registered operation, do not call invoke directly please because it ends up being an undecorated call. Meaning you do not capture the actual call of ttnn.op but only underlying device operations or parts of the composite call
class TestGraphCaptureArgumentsMorehDot : public TTNNFixtureWithTensor { | ||
protected: | ||
tt::tt_metal::IGraphProcessor::RunMode Mode = tt::tt_metal::IGraphProcessor::RunMode::NORMAL; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mode_
for data members
But if it is just the constant, I think we can inline in the one place it is used?
TestGraphCaptureArgumentsTranspose_Transpose, | ||
TestGraphCaptureArgumentsTranspose, | ||
::testing::Values(CreateTensorParameters{ | ||
.input_shape = ttnn::Shape(tt::tt_metal::Array4D{1, 1, 2048, 512}), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can instantiate a shape directly with {1, 1, 2048, 512}
private: | ||
GraphArgumentSerializer(); | ||
std::unordered_map<std::type_index, ConvertionFunction>& registry(); | ||
|
||
template <typename T, std::size_t N> | ||
void register_small_vector(); | ||
|
||
// In case you don't care about all the variations of the type | ||
// such as const T, const T&, T, T&, etc | ||
template <typename T> | ||
void register_special_type(); | ||
|
||
template <typename T> | ||
void register_type(); | ||
|
||
template <typename OptionalT> | ||
void register_optional_type(); | ||
|
||
void initialize(); | ||
|
||
private: | ||
std::unordered_map<std::type_index, GraphArgumentSerializer::ConvertionFunction> map; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, but this can be further moved to .cpp, so that we don't change the header and the users don't need to re-compile it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@omilyutin-tt how could I use this in the graph_processor class if moved to a cpp? or do you mean an specific part? For instance, keeping the map the constructor and registry, while the others are functions in the cpp?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I was thinking have the map
as a function local static variable in .cpp, along with all of "register" functions:
// Anonymous namespace to enforce internal linkage
namespace {
std::unordered_map<std::type_index, GraphArgumentSerializer::ConvertionFunction>& registry() {
static tt::stl::Indestructible<...> registry;
return registry.get();
template <typename T, std::size_t N>
void register_small_vector() {...}
// In case you don't care about all the variations of the type
// such as const T, const T&, T, T&, etc
template <typename T>
void register_special_type() {...}
template <typename T>
void register_type() {...}
template <typename OptionalT>
void register_optional_type() {...}
} // namespace
GraphArgumentSerializer::initialize() {
// As before...
}
Unless I am missing something:)
return os; | ||
} | ||
|
||
std::string graph_demangle(const std::string_view& name) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pass string_view
by value
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
great catch!
2bdf296
to
95f740c
Compare
Ticket
#18120
Problem description
The org will be creating new operations and they need some tooling to identify what is actually being sent via TTNN, so we extended the graph solution to include extra information, in this case, the arguments of the ttnn calls
What's changed
Extended the capabilities of the graph tool to add extra information for those vertex that include arguments, so whoever wants to use them, will have more information available
Checklist