|
24 | 24 | #include <cxxabi.h> |
25 | 25 |
|
26 | 26 | #include <algorithm> |
| 27 | +#include <array> |
27 | 28 | #include <atomic> |
28 | 29 | #include <cstdint> |
29 | 30 | #include <cstdio> |
@@ -80,18 +81,25 @@ struct dispatch { |
80 | 81 | uint64_t duration_ns = 0; |
81 | 82 | }; |
82 | 83 |
|
83 | | -// Per-invocation identity kept for graph reconstruction. The geometry record is shared by every |
84 | | -// op of the same shape, so its ids cannot distinguish repeated layers; these are the ids of this |
85 | | -// specific launch. Ids are storage ids (view_src root), so a consumer that reads a view/reshape of |
86 | | -// a producer's output -- reshape/view/permute launch no kernel and have no row -- still resolves to |
87 | | -// the producing op. out_storage_id is what the launch writes (the last node of a fused span, else |
88 | | -// the node itself); in_storage_ids are the storages it reads from outside the span. The consumer |
89 | | -// links each in_storage_id to the most recent prior launch whose out_storage_id matches (last |
90 | | -// writer wins, which also resolves in-place ops whose output aliases an input). |
| 84 | +// Per-launch identity for graph reconstruction: the geometry record is shared across ops of the |
| 85 | +// same shape, so only these ids distinguish repeated layers. Ids are storage ids at the view_src |
| 86 | +// root, so a read of a view/reshape (which launches no kernel and has no row) still resolves to the |
| 87 | +// producing op. The consumer links each in_storage_id to the last prior launch whose out_storage_id |
| 88 | +// matches (last writer wins, which also resolves in-place ops). |
| 89 | +// One entry per external source (aligned 1:1 with in_storage_ids). Holds each operand's own |
| 90 | +// name/type/shape so the consumer can label and classify it (weight vs dynamic input, by name) -- |
| 91 | +// which a fused row's head-only geometry record can't supply. |
| 92 | +struct src_operand { |
| 93 | + std::string name; |
| 94 | + std::string type; // ggml_type_name |
| 95 | + int64_t ne[4] = {0, 0, 0, 0}; |
| 96 | +}; |
| 97 | + |
91 | 98 | struct node_topology { |
92 | | - uint64_t out_storage_id = 0; |
93 | | - std::vector<uint64_t> in_storage_ids; |
94 | | - std::string name; |
| 99 | + uint64_t out_storage_id = 0; |
| 100 | + std::vector<uint64_t> in_storage_ids; |
| 101 | + std::vector<src_operand> in_operands; // aligned with in_storage_ids |
| 102 | + std::string name; |
95 | 103 | }; |
96 | 104 |
|
97 | 105 | std::mutex g_mutex; |
@@ -195,6 +203,18 @@ const ggml_tensor * roofline_storage(const ggml_tensor * t) { |
195 | 203 | return t; |
196 | 204 | } |
197 | 205 |
|
| 206 | +// Capture one source operand's identity (name/type/shape) for the topology, so the consumer can |
| 207 | +// label and classify it directly. The name is taken from the storage root (a view of a model |
| 208 | +// weight keeps the weight's name), which is what makes weight-vs-input unambiguous. |
| 209 | +src_operand make_src_operand(const ggml_tensor * t) { |
| 210 | + src_operand op; |
| 211 | + const ggml_tensor * root = roofline_storage(t); |
| 212 | + op.name = root && root->name[0] ? root->name : (t->name[0] ? t->name : ""); |
| 213 | + op.type = ggml_type_name(t->type); |
| 214 | + for (int d = 0; d < 4; d++) op.ne[d] = t->ne[d]; |
| 215 | + return op; |
| 216 | +} |
| 217 | + |
198 | 218 | // Fill a record's geometry and single-node HBM byte fields from one ggml node. |
199 | 219 | void fill_head_record(op_record & rec, const ggml_tensor * node) { |
200 | 220 | const ggml_tensor * src0 = node->src[0]; |
@@ -412,6 +432,27 @@ void write_report() { |
412 | 432 | out << topo_it->second.in_storage_ids[j]; |
413 | 433 | } |
414 | 434 | out << "], "; |
| 435 | + // Per-operand identity aligned 1:1 with in_storage_ids: the tensor name (unambiguous |
| 436 | + // weight-vs-input), its dtype and its shape -- correct even for a fused span's non-head |
| 437 | + // operands, which the shared geometry record's head-only src arrays do not describe. |
| 438 | + const auto & ops = topo_it->second.in_operands; |
| 439 | + out << "\"in_names\": ["; |
| 440 | + for (size_t j = 0; j < ops.size(); j++) { |
| 441 | + if (j) out << ", "; |
| 442 | + out << "\""; json_escape(out, ops[j].name); out << "\""; |
| 443 | + } |
| 444 | + out << "], \"in_types\": ["; |
| 445 | + for (size_t j = 0; j < ops.size(); j++) { |
| 446 | + if (j) out << ", "; |
| 447 | + out << "\"" << ops[j].type << "\""; |
| 448 | + } |
| 449 | + out << "], \"in_ne\": ["; |
| 450 | + for (size_t j = 0; j < ops.size(); j++) { |
| 451 | + if (j) out << ", "; |
| 452 | + out << "[" << ops[j].ne[0] << ", " << ops[j].ne[1] << ", " |
| 453 | + << ops[j].ne[2] << ", " << ops[j].ne[3] << "]"; |
| 454 | + } |
| 455 | + out << "], "; |
415 | 456 | } |
416 | 457 | if (!rec.fused_nodes.empty()) { |
417 | 458 | out << "\"fused_ops\": ["; |
@@ -614,7 +655,10 @@ void ggml_cuda_roofline_begin_op(const struct ggml_tensor * node, void * stream) |
614 | 655 | topo.out_storage_id = (uint64_t) (uintptr_t) roofline_storage(node); |
615 | 656 | topo.name = node->name; |
616 | 657 | for (int j = 0; j < GGML_MAX_SRC; j++) { |
617 | | - if (node->src[j]) topo.in_storage_ids.push_back((uint64_t) (uintptr_t) roofline_storage(node->src[j])); |
| 658 | + if (node->src[j]) { |
| 659 | + topo.in_storage_ids.push_back((uint64_t) (uintptr_t) roofline_storage(node->src[j])); |
| 660 | + topo.in_operands.push_back(make_src_operand(node->src[j])); |
| 661 | + } |
618 | 662 | } |
619 | 663 |
|
620 | 664 | { |
@@ -689,6 +733,7 @@ void ggml_cuda_roofline_fuse_ops(const struct ggml_cgraph * cgraph, int node_idx |
689 | 733 | const uint64_t sid = (uint64_t) (uintptr_t) roofline_storage(n->src[s]); |
690 | 734 | if (!internal.count(sid) && seen.insert(sid).second) { |
691 | 735 | topo.in_storage_ids.push_back(sid); |
| 736 | + topo.in_operands.push_back(make_src_operand(n->src[s])); |
692 | 737 | } |
693 | 738 | } |
694 | 739 | } |
|
0 commit comments