@@ -4779,11 +4779,6 @@ static void ggml_backend_cuda_graph_optimize(ggml_backend_t backend, ggml_cgraph
47794779 // store {fork_idx, join_idx}
47804780 std::vector<std::pair<int , int >> concurrent_node_ranges;
47814781
4782- // per-event lists of concurrent branch nodes to place in the dedicated scratch buffer (below),
4783- // so the branches are mutually disjoint and disjoint from tensors read across the region -
4784- // this replaces the fragile node interleaving that ggml-alloc/execution order can desync
4785- std::vector<std::vector<const ggml_tensor *>> concurrent_groups;
4786-
47874782 for (const auto & [root_node, count] : fan_out) {
47884783 if (count >= min_fan_out && count <= max_fan_out) {
47894784 const int root_node_idx = node_indices[root_node];
@@ -4875,6 +4870,10 @@ static void ggml_backend_cuda_graph_optimize(ggml_backend_t backend, ggml_cgraph
48754870 int fork_node_idx = node_indices[root_node];
48764871 int join_node_idx = node_indices[join_node];
48774872
4873+ int current_branch_idx = 0 ;
4874+ int current_node_idx = fork_node_idx + 1 ;
4875+ const int n_branches = nodes_per_branch.size ();
4876+
48784877 int total_branch_nodes = 0 ;
48794878 for (std::vector<const ggml_tensor *> branch_nodes : nodes_per_branch) {
48804879 total_branch_nodes += branch_nodes.size ();
@@ -4903,20 +4902,48 @@ static void ggml_backend_cuda_graph_optimize(ggml_backend_t backend, ggml_cgraph
49034902 GGML_LOG_DEBUG (" Adding stream at node %s %p\n " , root_node->name , root_node);
49044903 concurrent_node_ranges.emplace_back (fork_node_idx, join_node_idx);
49054904
4906- // place all branch nodes in the dedicated scratch buffer (below) instead of
4907- // interleaving them: ggml-alloc then keeps the branches mutually disjoint and
4908- // disjoint from the fork output that every branch reads concurrently
4909- std::vector<const ggml_tensor *> group;
4910- for (const auto & branch_nodes : nodes_per_branch) {
4911- for (const ggml_tensor * n : branch_nodes) {
4912- group.push_back (n);
4905+ // interleave tensors to extend lifetimes so that ggml graph doesn't recycle them
4906+ // example transformation:
4907+ // [attn-norm, QMul, QNorm, QRope, KMul, KNorm, KRope, VMul, attn] ->
4908+ // [attn-norm, QMul, KMul, VMul, QNorm, VNorm, QRope, KRope, attn]
4909+ while (current_node_idx < join_node_idx) {
4910+ std::vector<const ggml_tensor *> & branch_nodes = nodes_per_branch[current_branch_idx];
4911+
4912+ bool has_node = false ;
4913+ for (std::vector<const ggml_tensor *> branch_node : nodes_per_branch) {
4914+ has_node |= branch_node.size () > 0 ;
4915+ }
4916+
4917+ GGML_ASSERT (has_node);
4918+
4919+ if (branch_nodes.empty ()) {
4920+ current_branch_idx = (current_branch_idx + 1 ) % n_branches;
4921+ continue ;
4922+ }
4923+
4924+ cgraph->nodes [current_node_idx] = const_cast <ggml_tensor *>(branch_nodes.front ());
4925+ current_node_idx++;
4926+ branch_nodes.erase (branch_nodes.begin ());
4927+
4928+ // append all empty nodes
4929+ while (!branch_nodes.empty () && is_noop (branch_nodes.front ())) {
4930+ cgraph->nodes [current_node_idx] = const_cast <ggml_tensor *>(branch_nodes.front ());
4931+ current_node_idx++;
4932+ branch_nodes.erase (branch_nodes.begin ());
49134933 }
4934+
4935+ current_branch_idx = (current_branch_idx + 1 ) % n_branches;
49144936 }
4915- concurrent_groups.push_back (std::move (group));
49164937 }
49174938 }
49184939 }
49194940
4941+ // per-event lists of concurrent branch nodes to place in the dedicated scratch buffer (below),
4942+ // so the branches are mutually disjoint and disjoint from tensors read across the region.
4943+ // The QKV path below uses the legacy node interleaving instead; only the MoE shared-expert path
4944+ // populates this, so the two disjointness mechanisms coexist.
4945+ std::vector<std::vector<const ggml_tensor *>> concurrent_groups;
4946+
49204947 ggml_cuda_detect_shared_expert_concurrency (cgraph, cuda_ctx, node_indices, concurrent_node_ranges, concurrent_groups);
49214948
49224949 // Place every concurrent branch (attention QKV and MoE shared-expert) in a dedicated buffer so
0 commit comments