@@ -8602,6 +8602,164 @@ TEST_F(GraphTransformationTests, ReshapeFusionOpsetTest) {
86028602}
86038603#endif
86048604
8605+ // Test reshape fusion when Shape feeds from a tensor with the same symbolic dim_param names
8606+ // as the reshape root (e.g., position_ids reshaped using Shape from input_ids in Qwen).
8607+ TEST_F(GraphTransformationTests, ReshapeFusionDimParamMatch) {
8608+ auto build_test_case = [&](ModelTestBuilder& builder) {
8609+ // input_ids: [batch_size, sequence_length] — symbolic dims
8610+ auto* input_ids = builder.MakeSymbolicInput<float>({std::string("batch_size"), std::string("sequence_length")});
8611+ // position_ids: [batch_size, sequence_length] — same symbolic dims
8612+ auto* position_ids = builder.MakeSymbolicInput<float>({std::string("batch_size"), std::string("sequence_length")});
8613+
8614+ auto* shape_out = builder.MakeIntermediate();
8615+ auto* gather_out = builder.MakeIntermediate();
8616+ auto* unsqueeze_out = builder.MakeIntermediate();
8617+ auto* concat_out = builder.MakeIntermediate();
8618+ auto* out = builder.MakeOutput();
8619+
8620+ auto* scalar_int_0 = builder.MakeInitializer<int64_t>({}, {0});
8621+ auto* unsqueeze_axes = builder.MakeInitializer<int64_t>({1}, {0});
8622+ auto* const_neg1 = builder.MakeInitializer<int64_t>({1}, {-1});
8623+
8624+ // Shape(input_ids) -> Gather(0) -> Unsqueeze -> Concat([unsqueeze, -1]) -> Reshape(position_ids)
8625+ builder.AddNode("Shape", {input_ids}, {shape_out});
8626+ builder.AddNode("Gather", {shape_out, scalar_int_0}, {gather_out});
8627+ builder.AddNode("Unsqueeze", {gather_out, unsqueeze_axes}, {unsqueeze_out});
8628+ builder.AddNode("Concat", {unsqueeze_out, const_neg1}, {concat_out}).AddAttribute("axis", static_cast<int64_t>(0));
8629+ builder.AddNode("Reshape", {position_ids, concat_out}, {out});
8630+ };
8631+
8632+ auto pre_graph_checker = [&](Graph& graph) {
8633+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Shape"] == 1);
8634+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Gather"] == 1);
8635+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Unsqueeze"] == 1);
8636+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Concat"] == 1);
8637+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Reshape"] == 1);
8638+ return Status::OK();
8639+ };
8640+
8641+ auto post_graph_checker = [&](Graph& graph) {
8642+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Shape"] == 0);
8643+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Gather"] == 0);
8644+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Unsqueeze"] == 0);
8645+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Concat"] == 0);
8646+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Reshape"] == 1);
8647+ return Status::OK();
8648+ };
8649+
8650+ std::unique_ptr<GraphTransformer> transformer = std::make_unique<ReshapeFusion>();
8651+ ASSERT_STATUS_OK(TestGraphTransformer(build_test_case, 14, *logger_, std::move(transformer),
8652+ TransformerLevel::Level1, 1, pre_graph_checker, post_graph_checker));
8653+ }
8654+
8655+ // Test reshape fusion when Shape feeds from a different tensor than the reshape root,
8656+ // but the gathered dimension matches by symbolic dim_param (moondream2 vision encoder pattern:
8657+ // Shape from pre-projection tensor, Reshape on post-projection output).
8658+ TEST_F(GraphTransformationTests, ReshapeFusionCrossTensorDimMatch) {
8659+ auto build_test_case = [&](ModelTestBuilder& builder) {
8660+ // pre_proj: [batch_size, seq_len, 768] — pre-projection (layernorm output)
8661+ auto* pre_proj = builder.MakeSymbolicInput<float>(
8662+ {std::string("batch_size"), std::string("seq_len"), int64_t(768)});
8663+ // post_proj: [batch_size, seq_len, 2304] — post-projection (qkv linear output, different last dim)
8664+ auto* post_proj = builder.MakeSymbolicInput<float>(
8665+ {std::string("batch_size"), std::string("seq_len"), int64_t(2304)});
8666+
8667+ auto* shape_out = builder.MakeIntermediate();
8668+ auto* gather_out = builder.MakeIntermediate();
8669+ auto* unsqueeze_out = builder.MakeIntermediate();
8670+ auto* concat_out = builder.MakeIntermediate();
8671+ auto* out = builder.MakeOutput();
8672+
8673+ auto* scalar_int_0 = builder.MakeInitializer<int64_t>({}, {0});
8674+ auto* unsqueeze_axes = builder.MakeInitializer<int64_t>({1}, {0});
8675+ auto* const_neg1 = builder.MakeInitializer<int64_t>({1}, {-1});
8676+ auto* const_3 = builder.MakeInitializer<int64_t>({1}, {3});
8677+ auto* const_256 = builder.MakeInitializer<int64_t>({1}, {256});
8678+
8679+ // Shape(pre_proj) -> Gather(0) -> Unsqueeze -> Concat([unsqueeze, -1, 3, 256]) -> Reshape(post_proj)
8680+ builder.AddNode("Shape", {pre_proj}, {shape_out});
8681+ builder.AddNode("Gather", {shape_out, scalar_int_0}, {gather_out});
8682+ builder.AddNode("Unsqueeze", {gather_out, unsqueeze_axes}, {unsqueeze_out});
8683+ builder.AddNode("Concat", {unsqueeze_out, const_neg1, const_3, const_256}, {concat_out})
8684+ .AddAttribute("axis", static_cast<int64_t>(0));
8685+ builder.AddNode("Reshape", {post_proj, concat_out}, {out});
8686+ };
8687+
8688+ auto pre_graph_checker = [&](Graph& graph) {
8689+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Shape"] == 1);
8690+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Gather"] == 1);
8691+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Unsqueeze"] == 1);
8692+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Concat"] == 1);
8693+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Reshape"] == 1);
8694+ return Status::OK();
8695+ };
8696+
8697+ auto post_graph_checker = [&](Graph& graph) {
8698+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Shape"] == 0);
8699+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Gather"] == 0);
8700+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Unsqueeze"] == 0);
8701+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Concat"] == 0);
8702+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Reshape"] == 1);
8703+ return Status::OK();
8704+ };
8705+
8706+ std::unique_ptr<GraphTransformer> transformer = std::make_unique<ReshapeFusion>();
8707+ ASSERT_STATUS_OK(TestGraphTransformer(build_test_case, 14, *logger_, std::move(transformer),
8708+ TransformerLevel::Level1, 1, pre_graph_checker, post_graph_checker));
8709+ }
8710+
8711+ // Test reshape fusion with GlobalAveragePool passthrough pattern (MobileNetV2):
8712+ // Shape is taken from the pre-pool input, Reshape operates on the pool output,
8713+ // and only batch dim (gather index 0) is extracted.
8714+ TEST_F(GraphTransformationTests, ReshapeFusionGlobalAveragePoolPassthrough) {
8715+ auto build_test_case = [&](ModelTestBuilder& builder) {
8716+ auto* input = builder.MakeInput<float>({{1, 1280, 7, 7}});
8717+ auto* pool_out = builder.MakeIntermediate();
8718+ auto* shape_out = builder.MakeIntermediate();
8719+ auto* gather_out = builder.MakeIntermediate();
8720+ auto* unsqueeze_out = builder.MakeIntermediate();
8721+ auto* concat_out = builder.MakeIntermediate();
8722+ auto* out = builder.MakeOutput();
8723+
8724+ auto* scalar_int_0 = builder.MakeInitializer<int64_t>({}, {0});
8725+ auto* unsqueeze_axes = builder.MakeInitializer<int64_t>({1}, {0});
8726+ auto* const_neg1 = builder.MakeInitializer<int64_t>({1}, {-1});
8727+
8728+ // input -> GlobalAveragePool -> pool_out
8729+ builder.AddNode("GlobalAveragePool", {input}, {pool_out});
8730+ // Shape(input) -> Gather(0) -> Unsqueeze -> Concat([unsqueeze, -1]) -> Reshape(pool_out)
8731+ builder.AddNode("Shape", {input}, {shape_out});
8732+ builder.AddNode("Gather", {shape_out, scalar_int_0}, {gather_out});
8733+ builder.AddNode("Unsqueeze", {gather_out, unsqueeze_axes}, {unsqueeze_out});
8734+ builder.AddNode("Concat", {unsqueeze_out, const_neg1}, {concat_out}).AddAttribute("axis", static_cast<int64_t>(0));
8735+ builder.AddNode("Reshape", {pool_out, concat_out}, {out});
8736+ };
8737+
8738+ auto pre_graph_checker = [&](Graph& graph) {
8739+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["GlobalAveragePool"] == 1);
8740+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Shape"] == 1);
8741+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Gather"] == 1);
8742+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Unsqueeze"] == 1);
8743+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Concat"] == 1);
8744+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Reshape"] == 1);
8745+ return Status::OK();
8746+ };
8747+
8748+ auto post_graph_checker = [&](Graph& graph) {
8749+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["GlobalAveragePool"] == 1);
8750+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Shape"] == 0);
8751+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Gather"] == 0);
8752+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Unsqueeze"] == 0);
8753+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Concat"] == 0);
8754+ TEST_RETURN_IF_NOT(CountOpsInGraph(graph)["Reshape"] == 1);
8755+ return Status::OK();
8756+ };
8757+
8758+ std::unique_ptr<GraphTransformer> transformer = std::make_unique<ReshapeFusion>();
8759+ ASSERT_STATUS_OK(TestGraphTransformer(build_test_case, 14, *logger_, std::move(transformer),
8760+ TransformerLevel::Level1, 1, pre_graph_checker, post_graph_checker));
8761+ }
8762+
86058763TEST_F(GraphTransformationTests, DynamicQuantizeMatMulTest) {
86068764 constexpr const ORTCHAR_T* model_uri = MODEL_FOLDER "fusion/dynamic_quantize_matmul.onnx";
86078765 std::shared_ptr<Model> p_model;
0 commit comments