已合并
【PR】: elmwise 双输入时为 compact reduce 兜底检查插入 pad #1963
【PR】: elmwise 双输入时为 compact reduce 兜底检查插入 pad #1963
已合并
JacsonPile创建于 18 天前
3 个文件变更+236-8
@@ -303,14 +303,102 @@ af::Status BaseAlignmentStrategy::AddRemovePadForOneNode(ascir::ImplGraph &impl_
303 return af::SUCCESS;303 return af::SUCCESS;
304}304}
305 305 
306+bool BaseAlignmentStrategy::FindCompactReduceInput(const af::AscNodePtr &node, af::InDataAnchorPtr &reduce_input,
307+ af::OutDataAnchorPtr &reduce_output) const {
308+ if (node->attr.api.compute_type != af::ComputeType::kComputeElewise) {
309+ return false;
310+ }
311+ 
312+ bool has_aligned_broadcast = false;
313+ size_t linked_input_count = 0UL;
314+ for (const auto &in_anchor : node->GetAllInDataAnchorsPtr()) {
315+ const auto peer_out = in_anchor->GetPeerOutAnchor();
316+ if (peer_out == nullptr) {
317+ continue;
318+ }
319+ ++linked_input_count;
320+ const auto producer = std::dynamic_pointer_cast<af::AscNode>(peer_out->GetOwnerNode());
321+ if (producer == nullptr) {
322+ continue;
323+ }
324+ const auto state_iter = tensor_to_align_type_.find(&af::AscTensorAttr::GetTensorAttr(*peer_out));
325+ if (state_iter == tensor_to_align_type_.end()) {
326+ continue;
327+ }
328+ if (af::ops::IsOps<af::ascir_op::Broadcast>(producer) && state_iter->second.align_type == AlignmentType::kAligned) {
329+ has_aligned_broadcast = true;
330+ } else if (af::ops::IsOps<af::ascir_op::Sum>(producer)) {
331+ const auto &reduce_attr = af::AscTensorAttr::GetTensorAttr(*peer_out);
332+ if (!reduce_attr.vectorized_axis.empty()) {
333+ const auto axis = reduce_attr.vectorized_axis.back();
334+ const auto axis_iter = std::find(reduce_attr.axis.begin(), reduce_attr.axis.end(), axis);
335+ const auto axis_index = static_cast<size_t>(std::distance(reduce_attr.axis.begin(), axis_iter));
336+ const bool compact_output = axis_iter != reduce_attr.axis.end() && axis_index < reduce_attr.strides.size() &&
337+ af::SymbolicUtils::StaticCheckEq(reduce_attr.strides[axis_index],
338+ af::sym::kSymbolZero) == af::TriBool::kTrue;
339+ // Compact Reduce 的尾轴 stride 为零,即使反向传播已把状态改成 kAligned,物理布局仍是 not-aligned。
340+ if (compact_output) {
341+ reduce_input = node->GetInDataAnchor(in_anchor->GetIdx());
342+ reduce_output = peer_out;
343+ }
344+ }
345+ }
346+ }
347+ return linked_input_count == 2UL && has_aligned_broadcast && reduce_input != nullptr && reduce_output != nullptr;
348+}
349+ 
350+af::Status BaseAlignmentStrategy::InsertPadForCompactReduce(ascir::ImplGraph &impl_graph,
351+ const af::InDataAnchorPtr &reduce_input,
352+ const af::OutDataAnchorPtr &reduce_output, bool &inserted) {
353+ const auto reduce_node = std::dynamic_pointer_cast<af::AscNode>(reduce_output->GetOwnerNode());
354+ GE_ASSERT_NOTNULL(reduce_node);
355+ const auto output_index = reduce_output->GetIdx();
356+ auto &reduce_attr = reduce_node->outputs[output_index].attr;
357+ bool is_no_need_pad = false;
358+ GE_ASSERT_SUCCESS(CheckIsNoNeedPad(reduce_node, reduce_attr, is_no_need_pad));
359+ if (is_no_need_pad) {
360+ return af::SUCCESS;
361+ }
362+ std::vector<af::DataType> exp_dtypes{reduce_attr.dtype};
363+ if (ScheduleUtils::CallAscirInferDataType<af::ascir_op::Pad>({reduce_attr.dtype}, exp_dtypes) != af::SUCCESS) {
364+ GELOGW("Pad is unsupported for compact Reduce output in graph [%s].", impl_graph.GetName().c_str());
365+ return af::UNSUPPORTED;
366+ }
367+ 
368+ const std::string node_name = reduce_node->GetName() + "_" + std::to_string(output_index) + "_pad";
369+ af::ascir_op::Pad pad_op(node_name.c_str());
370+ const auto pad_node = impl_graph.AddNode(pad_op);
371+ GE_ASSERT_NOTNULL(pad_node);
372+ pad_node->attr = reduce_node->attr;
373+ pad_node->outputs[0].attr = reduce_attr;
374+ pad_node->attr.api.compute_type = af::ComputeType::kComputeElewise;
375+ pad_node->attr.api.type = af::ApiType::kAPITypeCompute;
376+ pad_node->attr.api.unit = af::ComputeUnit::kUnitVector;
377+ tensor_to_align_type_[&pad_node->outputs[0].attr].align_type = AlignmentType::kAligned;
378+ GE_ASSERT_SUCCESS(af::AscGraphUtils::InsertNodeAfter(reduce_output, {reduce_input}, pad_node));
379+ inserted = true;
380+ return af::SUCCESS;
381+}
382+ 
383+af::Status BaseAlignmentStrategy::AddPadForCompactReduce(ascir::ImplGraph &impl_graph, const af::AscNodePtr &node,
384+ bool &inserted) {
385+ af::InDataAnchorPtr reduce_input;
386+ af::OutDataAnchorPtr reduce_output;
387+ if (!FindCompactReduceInput(node, reduce_input, reduce_output)) {
388+ return af::SUCCESS;
389+ }
390+ return InsertPadForCompactReduce(impl_graph, reduce_input, reduce_output, inserted);
391+}
392+ 
306af::Status BaseAlignmentStrategy::CheckIsNoNeedPad(const af::AscNodePtr &node, af::AscTensorAttr &out_attr,393af::Status BaseAlignmentStrategy::CheckIsNoNeedPad(const af::AscNodePtr &node, af::AscTensorAttr &out_attr,
307 bool &is_no_need_pad) const {394 bool &is_no_need_pad) const {
308- size_t valid_axis_num = 0UL;395+ bool has_valid_outer_axis = false;
309 bool tail_axis_aligned = false;396 bool tail_axis_aligned = false;
310 for (auto axis_it = out_attr.vectorized_axis.rbegin(); axis_it != out_attr.vectorized_axis.rend(); ++axis_it) {397 for (auto axis_it = out_attr.vectorized_axis.rbegin(); axis_it != out_attr.vectorized_axis.rend(); ++axis_it) {
311 auto it = std::find(out_attr.axis.begin(), out_attr.axis.end(), *axis_it);398 auto it = std::find(out_attr.axis.begin(), out_attr.axis.end(), *axis_it);
312 GE_ASSERT_TRUE(it != out_attr.axis.end());399 GE_ASSERT_TRUE(it != out_attr.axis.end());
313 const size_t distance = std::distance(out_attr.axis.begin(), it);400 const size_t distance = std::distance(out_attr.axis.begin(), it);
401+ GE_ASSERT_TRUE(distance < out_attr.repeats.size() && distance < out_attr.strides.size());
314 if (axis_it == out_attr.vectorized_axis.rbegin()) {402 if (axis_it == out_attr.vectorized_axis.rbegin()) {
315 const auto dtype_size = af::GetSizeByDataType(out_attr.dtype);403 const auto dtype_size = af::GetSizeByDataType(out_attr.dtype);
316 GE_ASSERT_TRUE(dtype_size > 0, "Node [%s]'s data type size:[%d] is invalid.", node->GetNamePtr(), dtype_size);404 GE_ASSERT_TRUE(dtype_size > 0, "Node [%s]'s data type size:[%d] is invalid.", node->GetNamePtr(), dtype_size);
@@ -322,12 +410,17 @@ af::Status BaseAlignmentStrategy::CheckIsNoNeedPad(const af::AscNodePtr &node, a
322 af::SymbolicUtils::ToString(repeat).c_str(), node->GetNamePtr());410 af::SymbolicUtils::ToString(repeat).c_str(), node->GetNamePtr());
323 break;411 break;
324 }412 }
325- } else if (af::SymbolicUtils::StaticCheckNe(out_attr.strides[distance], af::sym::kSymbolZero) ==413+ continue;
326- af::TriBool::kTrue) {414+ }
327- valid_axis_num++;415+ const bool is_inactive_axis =
416+ af::SymbolicUtils::StaticCheckEq(out_attr.strides[distance], af::sym::kSymbolZero) == af::TriBool::kTrue ||
417+ af::SymbolicUtils::StaticCheckEq(out_attr.repeats[distance], af::sym::kSymbolOne) == af::TriBool::kTrue;
418+ if (!is_inactive_axis) {
419+ has_valid_outer_axis = true;
420+ break;
328 }421 }
329 }422 }
330- is_no_need_pad = tail_axis_aligned || valid_axis_num == 0UL;423+ is_no_need_pad = tail_axis_aligned || !has_valid_outer_axis;
331 return af::SUCCESS;424 return af::SUCCESS;
332}425}
333 426 
@@ -385,6 +478,9 @@ af::Status BaseAlignmentStrategy::AlignVectorizedStrides(ascir::ImplGraph &impl_
385 }478 }
386 GE_CHK_STATUS_RET_NOLOG(ForEachNode(impl_graph, &BaseAlignmentStrategy::AddRemovePadForOneNode));479 GE_CHK_STATUS_RET_NOLOG(ForEachNode(impl_graph, &BaseAlignmentStrategy::AddRemovePadForOneNode));
387 GE_CHK_STATUS_RET_NOLOG(ForEachNode(impl_graph, &BaseAlignmentStrategy::InferAlignmentForOneNode));480 GE_CHK_STATUS_RET_NOLOG(ForEachNode(impl_graph, &BaseAlignmentStrategy::InferAlignmentForOneNode));
481+ // Compact Reduce 依赖已推导的对齐状态,必须在对齐推导之后、通用冲突补 Pad 与向量化 stride 生成之前执行,
482+ // 确保只改写原始 Reduce 边一次,并让新插入的 Pad 参与最终 stride 生成。
483+ GE_CHK_STATUS_RET_NOLOG(ForEachNode(impl_graph, &BaseAlignmentStrategy::AddPadForCompactReduce));
388 GE_CHK_STATUS_RET_NOLOG(ForEachNode(impl_graph, &BaseAlignmentStrategy::AddPadForAlignmentConflictOneNode));484 GE_CHK_STATUS_RET_NOLOG(ForEachNode(impl_graph, &BaseAlignmentStrategy::AddPadForAlignmentConflictOneNode));
389 GE_CHK_STATUS_RET_NOLOG(ForEachNode(impl_graph, &BaseAlignmentStrategy::SetVectorizedStridesForOneNode));485 GE_CHK_STATUS_RET_NOLOG(ForEachNode(impl_graph, &BaseAlignmentStrategy::SetVectorizedStridesForOneNode));
390 return af::SUCCESS;486 return af::SUCCESS;
@@ -428,9 +524,7 @@ void BaseAlignmentStrategy::SetAlignInfoForNodeInputs(AlignmentType aligned_type
428 continue;524 continue;
429 }525 }
430 526 
431- const bool is_compact_reduce =527+ if (align_info.align_type == AlignmentType::kFixedNotAligned) {
432- ScheduleUtils::IsReduce(asc_node) && align_info.align_type == AlignmentType::kNotAligned;
433- if (align_info.align_type == AlignmentType::kFixedNotAligned || is_compact_reduce) {
434 align_info.conflict_with_output = true;528 align_info.conflict_with_output = true;
435 GELOGD("SetAlignInfoForNodeInputs: input[%s] is FixedNotAligned, set conflict_with_output=true.",529 GELOGD("SetAlignInfoForNodeInputs: input[%s] is FixedNotAligned, set conflict_with_output=true.",
436 asc_node->GetNamePtr());530 asc_node->GetNamePtr());
@@ -88,6 +88,11 @@ class BaseAlignmentStrategy {
88 std::queue<af::Node *> &node_queue);88 std::queue<af::Node *> &node_queue);
89 89 
90 af::Status AddRemovePadForOneNode(ascir::ImplGraph &impl_graph, const af::AscNodePtr &node, bool &inserted);90 af::Status AddRemovePadForOneNode(ascir::ImplGraph &impl_graph, const af::AscNodePtr &node, bool &inserted);
91+ af::Status AddPadForCompactReduce(ascir::ImplGraph &impl_graph, const af::AscNodePtr &node, bool &inserted);
92+ bool FindCompactReduceInput(const af::AscNodePtr &node, af::InDataAnchorPtr &reduce_input,
93+ af::OutDataAnchorPtr &reduce_output) const;
94+ af::Status InsertPadForCompactReduce(ascir::ImplGraph &impl_graph, const af::InDataAnchorPtr &reduce_input,
95+ const af::OutDataAnchorPtr &reduce_output, bool &inserted);
91 af::Status CheckIsNoNeedPad(const af::AscNodePtr &node, af::AscTensorAttr &out_attr, bool &is_no_need_pad) const;96 af::Status CheckIsNoNeedPad(const af::AscNodePtr &node, af::AscTensorAttr &out_attr, bool &is_no_need_pad) const;
92 af::Status AddPadForAlignmentConflictOneNode(ascir::ImplGraph &impl_graph, const af::AscNodePtr &node,97 af::Status AddPadForAlignmentConflictOneNode(ascir::ImplGraph &impl_graph, const af::AscNodePtr &node,
93 bool &inserted);98 bool &inserted);
@@ -44,6 +44,28 @@ void SetTwoDimNodeAttr(Op &op, Dtype dtype, af::ComputeType compute_type, const
44 *op.y.vectorized_axis = axes;44 *op.y.vectorized_axis = axes;
45}45}
46 46 
47+void BuildCompactReduceAddGraph(af::AscGraph &graph, const af::Expression &reduce_tail_stride) {
48+ auto rows = af::Symbol(8);
49+ auto cols = af::Symbol(11);
50+ auto row_axis = graph.CreateAxis("row", rows);
51+ auto col_axis = graph.CreateAxis("col", cols);
52+ std::vector<af::AxisId> axes = {row_axis.id, col_axis.id};
53+ 
54+ af::ascir_op::Data data("data", graph);
55+ data.y.dtype = ge::DT_FLOAT;
56+ data.attr.api.type = af::ApiType::kAPITypeBuffer;
57+ af::ascir_op::Sum sum("sum");
58+ sum.x = data.y;
59+ SetTwoDimNodeAttr(sum, ge::DT_FLOAT, af::ComputeType::kComputeReduce, axes, {rows, One}, {One, reduce_tail_stride});
60+ af::ascir_op::Broadcast broadcast("broadcast");
61+ broadcast.x = data.y;
62+ SetTwoDimNodeAttr(broadcast, ge::DT_FLOAT, af::ComputeType::kComputeBroadcast, axes, {rows, cols}, {cols, One});
63+ af::ascir_op::Add add("add");
64+ add.x1 = sum.y;
65+ add.x2 = broadcast.y;
66+ SetTwoDimNodeAttr(add, ge::DT_FLOAT, af::ComputeType::kComputeElewise, axes, {rows, cols}, {cols, One});
67+}
68+ 
47void ExpectCompactReduceBranch(const af::AscGraph &graph, const char *aligned_input) {69void ExpectCompactReduceBranch(const af::AscGraph &graph, const char *aligned_input) {
48 std::vector<af::Expression> aligned_input_strides = {af::Symbol(24), One};70 std::vector<af::Expression> aligned_input_strides = {af::Symbol(24), One};
49 std::vector<af::Expression> compact_output_strides = {One, Zero};71 std::vector<af::Expression> compact_output_strides = {One, Zero};
@@ -77,6 +99,13 @@ class VectorizedAlignmentUT : public testing::Test {
77 af::Status AccessInferAlignment(ImplGraph &impl_graph) {99 af::Status AccessInferAlignment(ImplGraph &impl_graph) {
78 return ForEachNode(impl_graph, &AlignmentStrategyShadow::InferAlignmentForOneNode);100 return ForEachNode(impl_graph, &AlignmentStrategyShadow::InferAlignmentForOneNode);
79 }101 }
102+ bool AccessFindCompactReduceInput(const af::AscNodePtr &node, af::InDataAnchorPtr &reduce_input,
103+ af::OutDataAnchorPtr &reduce_output) const {
104+ return FindCompactReduceInput(node, reduce_input, reduce_output);
105+ }
106+ void SetAlignmentType(const af::AscNodePtr &node, AlignmentType align_type) {
107+ tensor_to_align_type_[&node->outputs[0].attr].align_type = align_type;
108+ }
80 // 当前tensor的对齐行为只会出现在尾轴,如果没有新的对齐行为或者类型,该函数不应该修改109 // 当前tensor的对齐行为只会出现在尾轴,如果没有新的对齐行为或者类型,该函数不应该修改
81 af::Status AccessSetVectorizedStrides(ImplGraph &impl_graph) {110 af::Status AccessSetVectorizedStrides(ImplGraph &impl_graph) {
82 return ForEachNode(impl_graph, &AlignmentStrategyShadow::SetVectorizedStridesForOneNode);111 return ForEachNode(impl_graph, &AlignmentStrategyShadow::SetVectorizedStridesForOneNode);
@@ -870,4 +899,104 @@ TEST_F(VectorizedAlignmentUT, sibling_reduce_keeps_compact_output_alignment) {
870 ExpectCompactReduceBranch(graph, "relu");899 ExpectCompactReduceBranch(graph, "relu");
871}900}
872 901 
902+TEST_F(VectorizedAlignmentUT, compact_reduce_pad_for_aligned_broadcast_add) {
903+ af::AscGraph graph("compact_reduce_pad_for_aligned_broadcast_add");
904+ auto rows = af::Symbol(8);
905+ auto cols = af::Symbol(11);
906+ auto row_axis = graph.CreateAxis("row", rows);
907+ auto col_axis = graph.CreateAxis("col", cols);
908+ std::vector<af::AxisId> axes = {row_axis.id, col_axis.id};
909+ 
910+ af::ascir_op::Data data("data", graph);
911+ data.y.dtype = ge::DT_FLOAT;
912+ data.attr.api.type = af::ApiType::kAPITypeBuffer;
913+ af::ascir_op::Load reduce_load("reduce_load");
914+ reduce_load.x = data.y;
915+ SetTwoDimNodeAttr(reduce_load, ge::DT_FLOAT, af::ComputeType::kComputeLoad, axes, {rows, cols}, {cols, One});
916+ af::ascir_op::Sum reduce_sum("reduce_sum");
917+ reduce_sum.x = reduce_load.y;
918+ SetTwoDimNodeAttr(reduce_sum, ge::DT_FLOAT, af::ComputeType::kComputeReduce, axes, {rows, One}, {One, Zero});
919+ af::ascir_op::Load aligned_load("aligned_load");
920+ aligned_load.x = data.y;
921+ SetTwoDimNodeAttr(aligned_load, ge::DT_FLOAT, af::ComputeType::kComputeLoad, axes, {rows, cols},
922+ {cols * af::Symbol(2), One});
923+ af::ascir_op::Broadcast broadcast("broadcast");
924+ broadcast.x = aligned_load.y;
925+ SetTwoDimNodeAttr(broadcast, ge::DT_FLOAT, af::ComputeType::kComputeBroadcast, axes, {rows, cols},
926+ {cols * af::Symbol(2), One});
927+ af::ascir_op::Add add("add");
928+ add.x1 = reduce_sum.y;
929+ add.x2 = broadcast.y;
930+ SetTwoDimNodeAttr(add, ge::DT_FLOAT, af::ComputeType::kComputeElewise, axes, {rows, cols}, {cols, One});
931+ af::ascir_op::Store store("store");
932+ store.x = add.y;
933+ SetTwoDimNodeAttr(store, ge::DT_FLOAT, af::ComputeType::kComputeStore, axes, {rows, cols}, {cols, One});
934+ 
935+ ASSERT_EQ(AlignmentHandler::AlignVectorizedStrides(graph), af::SUCCESS);
936+ auto pad_node = graph.FindNode("reduce_sum_0_pad");
937+ ASSERT_NE(pad_node, nullptr);
938+ auto add_node = graph.FindNode("add");
939+ ASSERT_NE(add_node, nullptr);
940+ EXPECT_EQ(add_node->inputs[0].anchor.GetOwnerNodeBarePtr(), pad_node.get());
941+}
942+ 
943+TEST_F(VectorizedAlignmentUT, scalar_reduce_skips_pad_for_aligned_broadcast_add) {
944+ af::AscGraph graph("scalar_reduce_skips_pad_for_aligned_broadcast_add");
945+ auto rows = af::Symbol(8);
946+ auto cols = af::Symbol(11);
947+ auto row_axis = graph.CreateAxis("row", rows);
948+ auto col_axis = graph.CreateAxis("col", cols);
949+ std::vector<af::AxisId> axes = {row_axis.id, col_axis.id};
950+ 
951+ af::ascir_op::Data data("data", graph);
952+ data.y.dtype = ge::DT_FLOAT;
953+ data.attr.api.type = af::ApiType::kAPITypeBuffer;
954+ af::ascir_op::Load reduce_load("reduce_load");
955+ reduce_load.x = data.y;
956+ SetTwoDimNodeAttr(reduce_load, ge::DT_FLOAT, af::ComputeType::kComputeLoad, axes, {rows, cols}, {cols, One});
957+ af::ascir_op::Sum reduce_sum("reduce_sum");
958+ reduce_sum.x = reduce_load.y;
959+ SetTwoDimNodeAttr(reduce_sum, ge::DT_FLOAT, af::ComputeType::kComputeReduce, axes, {One, One}, {One, Zero});
960+ af::ascir_op::Load aligned_load("aligned_load");
961+ aligned_load.x = data.y;
962+ SetTwoDimNodeAttr(aligned_load, ge::DT_FLOAT, af::ComputeType::kComputeLoad, axes, {rows, cols},
963+ {cols * af::Symbol(2), One});
964+ af::ascir_op::Broadcast broadcast("broadcast");
965+ broadcast.x = aligned_load.y;
966+ SetTwoDimNodeAttr(broadcast, ge::DT_FLOAT, af::ComputeType::kComputeBroadcast, axes, {rows, cols},
967+ {cols * af::Symbol(2), One});
968+ af::ascir_op::Add add("add");
969+ add.x1 = reduce_sum.y;
970+ add.x2 = broadcast.y;
971+ SetTwoDimNodeAttr(add, ge::DT_FLOAT, af::ComputeType::kComputeElewise, axes, {rows, cols}, {cols, One});
972+ 
973+ ASSERT_EQ(AlignmentHandler::AlignVectorizedStrides(graph), af::SUCCESS);
974+ EXPECT_EQ(graph.FindNode("reduce_sum_0_pad"), nullptr);
975+ auto add_node = graph.FindNode("add");
976+ ASSERT_NE(add_node, nullptr);
977+ EXPECT_EQ(add_node->inputs[0].anchor.GetOwnerNodeBarePtr(), graph.FindNode("reduce_sum").get());
978+}
979+ 
980+TEST_F(VectorizedAlignmentUT, non_aligned_broadcast_skips_compact_reduce_pad) {
981+ af::AscGraph graph("non_aligned_broadcast_skips_compact_reduce_pad");
982+ BuildCompactReduceAddGraph(graph, Zero);
983+ AlignmentStrategyShadow strategy;
984+ strategy.SetAlignmentType(graph.FindNode("sum"), AlignmentType::kNotAligned);
985+ strategy.SetAlignmentType(graph.FindNode("broadcast"), AlignmentType::kNotAligned);
986+ af::InDataAnchorPtr reduce_input;
987+ af::OutDataAnchorPtr reduce_output;
988+ EXPECT_FALSE(strategy.AccessFindCompactReduceInput(graph.FindNode("add"), reduce_input, reduce_output));
989+}
990+ 
991+TEST_F(VectorizedAlignmentUT, non_compact_sum_skips_compact_reduce_pad) {
992+ af::AscGraph graph("non_compact_sum_skips_compact_reduce_pad");
993+ BuildCompactReduceAddGraph(graph, One);
994+ AlignmentStrategyShadow strategy;
995+ strategy.SetAlignmentType(graph.FindNode("sum"), AlignmentType::kNotAligned);
996+ strategy.SetAlignmentType(graph.FindNode("broadcast"), AlignmentType::kAligned);
997+ af::InDataAnchorPtr reduce_input;
998+ af::OutDataAnchorPtr reduce_output;
999+ EXPECT_FALSE(strategy.AccessFindCompactReduceInput(graph.FindNode("add"), reduce_input, reduce_output));
1000+}
1001+ 
873} // namespace optimize1002} // namespace optimize