-
Notifications
You must be signed in to change notification settings - Fork 63
Hetero subgraph with dispatching #43
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
base: main
Are you sure you want to change the base?
Changes from 6 commits
2a50f4c
cba41dd
7494c2d
f87e8f3
8a1cb14
08faf36
c59be3f
dd2e8b6
d1c98cc
0a4bc01
1f20313
f8f9059
c4c446a
487eaf6
48c115d
663a675
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,11 @@ | ||
| #include "subgraph.h" | ||
| #include <pyg_lib/csrc/utils/hetero_dispatch.h> | ||
|
|
||
| #include <ATen/core/dispatch/Dispatcher.h> | ||
| #include <torch/library.h> | ||
|
|
||
| #include <functional> | ||
|
|
||
| namespace pyg { | ||
| namespace sampler { | ||
|
|
||
|
|
@@ -11,7 +14,7 @@ std::tuple<at::Tensor, at::Tensor, c10::optional<at::Tensor>> subgraph( | |
| const at::Tensor& col, | ||
| const at::Tensor& nodes, | ||
| const bool return_edge_id) { | ||
| at::TensorArg rowptr_t{rowptr, "rowtpr", 1}; | ||
| at::TensorArg rowptr_t{rowptr, "rowptr", 1}; | ||
| at::TensorArg col_t{col, "col", 1}; | ||
| at::TensorArg nodes_t{nodes, "nodes", 1}; | ||
|
|
||
|
|
@@ -25,10 +28,42 @@ std::tuple<at::Tensor, at::Tensor, c10::optional<at::Tensor>> subgraph( | |
| return op.call(rowptr, col, nodes, return_edge_id); | ||
| } | ||
|
|
||
| c10::Dict<utils::edge_t, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I actually would have expected we return a tuple of dictionaries, similar to how the input looks like. |
||
| std::tuple<at::Tensor, at::Tensor, c10::optional<at::Tensor>>> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO, the output should be a tuple of dictionaries (similar to the input). |
||
| hetero_subgraph(const utils::edge_tensor_dict_t& rowptr, | ||
| const utils::edge_tensor_dict_t& col, | ||
| const utils::node_tensor_dict_t& nodes, | ||
| const c10::Dict<utils::edge_t, bool>& return_edge_id) { | ||
| // Define the homogeneous implementation as a std function to pass the type | ||
| // check | ||
| std::function<std::tuple<at::Tensor, at::Tensor, c10::optional<at::Tensor>>( | ||
| const at::Tensor&, const at::Tensor&, const at::Tensor&, bool)> | ||
| func = subgraph; | ||
|
|
||
| // Construct an operator | ||
| utils::HeteroDispatchOp<decltype(func)> op(rowptr, col, func); | ||
|
|
||
| // Construct dispatchable arguments | ||
| // TODO: We filter source node by assuming hetero graph is a dict of homo | ||
| // graph here; both source and destination nodes should be considered when | ||
| // filtering a bipartite graph | ||
| utils::HeteroDispatchArg<utils::node_tensor_dict_t, at::Tensor, | ||
| utils::NodeSrcMode> | ||
| nodes_arg(nodes); | ||
| utils::HeteroDispatchArg<c10::Dict<utils::edge_t, bool>, bool, | ||
| utils::EdgeMode> | ||
| edge_id_arg(return_edge_id); | ||
| return op(nodes_arg, edge_id_arg); | ||
| } | ||
|
|
||
| TORCH_LIBRARY_FRAGMENT(pyg, m) { | ||
| m.def(TORCH_SELECTIVE_SCHEMA( | ||
| "pyg::subgraph(Tensor rowptr, Tensor col, Tensor " | ||
| "nodes, bool return_edge_id) -> (Tensor, Tensor, Tensor?)")); | ||
| m.def(TORCH_SELECTIVE_SCHEMA( | ||
| "pyg::hetero_subgraph(Dict(str, Tensor) rowptr, Dict(str, " | ||
| "Tensor) col, Dict(str, Tensor) nodes, Dict(str, bool) " | ||
| "return_edge_id) -> Dict(str, (Tensor, Tensor, Tensor?))")); | ||
| } | ||
|
|
||
| } // namespace sampler | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| #pragma once | ||
|
|
||
| #include "types.h" | ||
|
|
||
| #include <type_traits> | ||
|
|
||
| namespace pyg { | ||
|
|
||
| namespace utils { | ||
|
|
||
| // Base class for easier type check | ||
| struct HeteroDispatchMode {}; | ||
|
|
||
| // List hetero dispatch mode as different types to avoid non-type template | ||
| // specialization. | ||
| struct SkipMode : public HeteroDispatchMode {}; | ||
|
|
||
| struct NodeSrcMode : public HeteroDispatchMode {}; | ||
|
|
||
| struct NodeDstMode : public HeteroDispatchMode {}; | ||
|
|
||
| struct EdgeMode : public HeteroDispatchMode {}; | ||
|
|
||
| // Check if the argument is a c10::dict so that is could be filtered by an edge | ||
| // type. | ||
| template <typename... T> | ||
| struct is_c10_dict : std::false_type {}; | ||
|
|
||
| template <typename T, typename N> | ||
| struct is_c10_dict<c10::Dict<T, N>> : std::true_type {}; | ||
|
|
||
| // TODO: Should specialize as if-constexpr when in C++17 | ||
| template <typename T, typename V, typename MODE> | ||
| class HeteroDispatchArg {}; | ||
|
|
||
| // In SkipMode we do not filter this arg | ||
| template <typename T, typename V> | ||
| class HeteroDispatchArg<T, V, SkipMode> { | ||
| public: | ||
| HeteroDispatchArg(const T& val) : val_(val) {} | ||
|
|
||
| // If we pass the filter, we will obtain the value of the argument. | ||
| template <typename K> | ||
| V value_by_edge(const K& key) { | ||
| return val_; | ||
| } | ||
|
|
||
| bool filter_by_edge(const edge_t& edge) { return true; } | ||
|
|
||
| private: | ||
| T val_; | ||
| }; | ||
|
|
||
| // In NodeSrcMode we check if source node is in the dict | ||
| template <typename T, typename V> | ||
| class HeteroDispatchArg<T, V, NodeSrcMode> { | ||
| public: | ||
| HeteroDispatchArg(const T& val) : val_(val) { | ||
| static_assert(is_c10_dict<T>::value, "Should be a c10::dict"); | ||
| } | ||
|
|
||
| // Dict value lookup | ||
| template <typename K> | ||
| V value_by_edge(const K& key) { | ||
| return val_.at(get_src(key)); | ||
| } | ||
|
|
||
| // Dict if key exists | ||
| bool filter_by_edge(const edge_t& edge) { | ||
| return val_.contains(get_src(edge)); | ||
| } | ||
|
|
||
| private: | ||
| T val_; | ||
| }; | ||
|
|
||
| // In NodeDstMode we check if destination node is in the dict | ||
| template <typename T, typename V> | ||
| class HeteroDispatchArg<T, V, NodeDstMode> { | ||
| public: | ||
| HeteroDispatchArg(const T& val) : val_(val) { | ||
| static_assert(is_c10_dict<T>::value, "Should be a c10::dict"); | ||
| } | ||
|
|
||
| template <typename K> | ||
| V value_by_edge(const K& key) { | ||
| return val_.at(get_dst(key)); | ||
| } | ||
|
|
||
| bool filter_by_edge(const edge_t& edge) { | ||
| return val_.contains(get_dst(edge)); | ||
| } | ||
|
|
||
| private: | ||
| T val_; | ||
| }; | ||
|
|
||
| // In EdgeMode we check if edge is in the dict | ||
| template <typename T, typename V> | ||
| class HeteroDispatchArg<T, V, EdgeMode> { | ||
| public: | ||
| HeteroDispatchArg(const T& val) : val_(val) { | ||
| static_assert(is_c10_dict<T>::value, "Should be a c10::dict"); | ||
| } | ||
|
|
||
| template <typename K> | ||
| V value_by_edge(const K& key) { | ||
| return val_.at(key); | ||
| } | ||
|
|
||
| bool filter_by_edge(const edge_t& edge) { return val_.contains(edge); } | ||
|
|
||
| private: | ||
| T val_; | ||
| }; | ||
|
|
||
| // The following will help static type checks: | ||
| template <typename... T> | ||
| struct is_hetero_arg : std::false_type {}; | ||
|
|
||
| // Just check inheritance, a workaround without introducing concepts | ||
| template <typename T, typename V, typename Mode> | ||
| struct is_hetero_arg<HeteroDispatchArg<T, V, Mode>> : std::true_type { | ||
| static_assert(std::is_base_of<HeteroDispatchMode, Mode>::value, | ||
| "Must pass a mode for dispatching"); | ||
| }; | ||
|
|
||
| // Specialize | ||
| template <typename... Args> | ||
| bool filter_args_by_edge(const edge_t& edge, Args&&... args) {} | ||
|
|
||
| // Stop condition of argument filtering | ||
| template <> | ||
| bool filter_args_by_edge(const edge_t& edge) { | ||
| return true; | ||
| } | ||
|
|
||
| // We filter each argument individually by the given edge using a variadic | ||
| // template | ||
| template <typename T, typename... Args> | ||
| bool filter_args_by_edge(const edge_t& edge, T&& t, Args&&... args) { | ||
| static_assert( | ||
| is_hetero_arg<std::remove_const_t<std::remove_reference_t<T>>>::value, | ||
| "args should be HeteroDispatchArg"); | ||
| return t.filter_by_edge(edge) && filter_args_by_edge(edge, args...); | ||
| } | ||
|
|
||
| // Check if a callable is wrapped by std::function | ||
| template <typename... T> | ||
| struct is_std_function : std::false_type {}; | ||
|
|
||
| template <typename T, typename... Args> | ||
| struct is_std_function<std::function<T(Args...)>> : std::true_type {}; | ||
|
|
||
| template <typename T> | ||
| class HeteroDispatchOp { | ||
| public: | ||
| using result_type = typename T::result_type; | ||
| HeteroDispatchOp(const edge_tensor_dict_t& rowptr, | ||
| const edge_tensor_dict_t& col, | ||
| T op) | ||
| : rowptr_(rowptr), col_(col), op_(op) { | ||
| // Check early | ||
| static_assert(is_std_function<T>::value, "Must pass a function"); | ||
| } | ||
|
|
||
| template <typename... Args> | ||
| c10::Dict<edge_t, result_type> operator()(Args&&... args) { | ||
| c10::Dict<edge_t, result_type> dict; | ||
| for (const auto& kv : rowptr_) { | ||
| auto edge = kv.key(); | ||
|
ZenoTan marked this conversation as resolved.
Outdated
|
||
| auto rowptr = kv.value(); | ||
| auto col = col_.at(edge); | ||
| bool pass = filter_args_by_edge(edge, args...); | ||
| if (pass) { | ||
| result_type res = op_(rowptr, col, args.value_by_edge(edge)...); | ||
| dict.insert(edge, res); | ||
| } | ||
| } | ||
| return dict; | ||
| } | ||
|
|
||
| private: | ||
| edge_tensor_dict_t rowptr_; | ||
| edge_tensor_dict_t col_; | ||
| T op_; | ||
| }; | ||
|
|
||
| } // namespace utils | ||
|
|
||
| } // namespace pyg | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| #pragma once | ||
|
|
||
| #include <string> | ||
|
|
||
| #include <ATen/ATen.h> | ||
|
|
||
| namespace pyg { | ||
| namespace utils { | ||
|
|
||
| const std::string SPLIT_TOKEN = "__"; | ||
|
|
||
| using edge_t = std::string; | ||
|
ZenoTan marked this conversation as resolved.
Outdated
|
||
| using node_t = std::string; | ||
| using rel_t = std::string; | ||
|
|
||
| using edge_tensor_dict_t = c10::Dict<edge_t, at::Tensor>; | ||
| using node_tensor_dict_t = c10::Dict<node_t, at::Tensor>; | ||
|
|
||
| node_t get_src(const edge_t& e) { | ||
| return e.substr(0, e.find_first_of(SPLIT_TOKEN)); | ||
| } | ||
|
|
||
| rel_t get_rel(const edge_t& e) { | ||
| auto beg = e.find_first_of(SPLIT_TOKEN) + SPLIT_TOKEN.size(); | ||
| return e.substr(beg, | ||
| e.find_last_of(SPLIT_TOKEN) - SPLIT_TOKEN.size() + 1 - beg); | ||
| } | ||
|
|
||
| node_t get_dst(const edge_t& e) { | ||
| return e.substr(e.find_last_of(SPLIT_TOKEN) + 1); | ||
| } | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could also add a function that maps tuples to strings and vice versa.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good idea. |
||
| } // namespace utils | ||
|
|
||
| } // namespace pyg | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| #include <gtest/gtest.h> | ||
|
|
||
| #include "pyg_lib/csrc/sampler/subgraph.h" | ||
|
ZenoTan marked this conversation as resolved.
Outdated
|
||
|
|
||
| TEST(UtilsTypeTest, BasicAssertions) { | ||
| pyg::utils::edge_t edge = "node1__to__node2"; | ||
|
|
||
| auto src = pyg::utils::get_src(edge); | ||
| auto dst = pyg::utils::get_dst(edge); | ||
| auto rel = pyg::utils::get_rel(edge); | ||
|
|
||
| EXPECT_EQ(src, std::string("node1")); | ||
| EXPECT_EQ(dst, std::string("node2")); | ||
| EXPECT_EQ(rel, std::string("to")); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.