Skip to content
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

Merged
merged 10 commits into from
Mar 6, 2025

Conversation

dgomezTT
Copy link
Contributor

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

@@ -33,6 +66,55 @@ std::string demangle(const char* name) {
return ret_val;
}

struct AnyToString {
Copy link
Member

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

Copy link
Contributor Author

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;
Copy link
Member

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 🤔

Copy link
Contributor

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.

Copy link
Contributor Author

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 {
Copy link
Member

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.

Copy link
Contributor

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.

Comment on lines +120 to +129
register_small_vector<T, 2>();
register_small_vector<T, 4>();
register_small_vector<T, 8>();
register_small_vector<T, 16>();
Copy link
Member

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

Copy link
Member

@ayerofieiev-tt ayerofieiev-tt left a 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.

Copy link
Member

@ayerofieiev-tt ayerofieiev-tt left a 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.

case DataType::UINT16: return os << "UINT16";
case DataType::INT32: return os << "INT32";
case DataType::INVALID: return os << "Invalid";
default: return os << "Unknown data type";
Copy link
Contributor

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);
Copy link
Contributor

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?

Comment on lines 19 to 37
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();
Copy link
Contributor

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";
Copy link
Contributor

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) {
Copy link
Member

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>

Copy link
Contributor Author

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();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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());
Copy link
Member

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();
Copy link
Member

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

Suggested change
static GraphArgumentSerializer* new_instance = new GraphArgumentSerializer();
static GraphArgumentSerializer new_instance;


GraphArgumentSerializer::GraphArgumentSerializer() { initialize(); }

GraphArgumentSerializer* GraphArgumentSerializer::instance() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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(
Copy link
Member

@ayerofieiev-tt ayerofieiev-tt Mar 4, 2025

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

Comment on lines 17 to 20
class TestGraphCaptureArgumentsMorehDot : public TTNNFixtureWithTensor {
protected:
tt::tt_metal::IGraphProcessor::RunMode Mode = tt::tt_metal::IGraphProcessor::RunMode::NORMAL;
};
Copy link
Contributor

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}),
Copy link
Contributor

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}

Comment on lines +23 to +44
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;
Copy link
Contributor

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?

Copy link
Contributor Author

@dgomezTT dgomezTT Mar 5, 2025

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?

Copy link
Contributor

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) {
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great catch!

@dgomezTT dgomezTT force-pushed the dgomeztt/18097 branch 2 times, most recently from 2bdf296 to 95f740c Compare March 5, 2025 22:13
@dgomezTT dgomezTT merged commit a33d64a into main Mar 6, 2025
220 of 222 checks passed
@dgomezTT dgomezTT deleted the dgomeztt/18097 branch March 6, 2025 13:02
tt-rkim pushed a commit that referenced this pull request Mar 6, 2025
… information of the ttnn operations (#18380)"

This reverts commit a33d64a, which
broke ttnn unit tests 1.
dgomezTT added a commit that referenced this pull request Mar 6, 2025
…tion of the ttnn operations (#18380)""

bring back commit 87ab998.
dgomezTT added a commit that referenced this pull request Mar 6, 2025
…tion of the ttnn operations (#18380)""

bring back commit 87ab998.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants