Line data Source code
1 : /**
2 : * Copyright (c) 2025 Huawei Technologies Co., Ltd.
3 : * This program is free software, you can redistribute it and/or modify it under the terms and conditions of
4 : * CANN Open Software License Agreement Version 2.0 (the "License").
5 : * Please refer to the License for details. You may not use this file except in compliance with the License.
6 : * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED,
7 : * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE.
8 : * See LICENSE in the root of the software repository for the full text of the License.
9 : */
10 :
11 : #include "all_gather_ring.h"
12 : #include "alg_template_register.h"
13 :
14 : namespace hccl {
15 32 : AllGatherRing::AllGatherRing(const HcclDispatcher dispatcher) : AlgTemplateBase(dispatcher)
16 : {
17 32 : }
18 :
19 55 : AllGatherRing::~AllGatherRing()
20 : {
21 55 : }
22 :
23 0 : HcclResult AllGatherRing::TxVector(const LINK &link, const std::vector<Slice> &txSlices)
24 : {
25 0 : std::vector<TxMemoryInfo> txMems;
26 0 : for (const Slice &txSlice : txSlices) {
27 0 : DeviceMem srcMem = outputMem_.range(txSlice.offset, txSlice.size);
28 0 : HCCL_DEBUG("tx srcMem[%p] range[%llu] size[%llu] ", srcMem.ptr(), txSlice.offset, txSlice.size);
29 0 : txMems.emplace_back(TxMemoryInfo{UserMemType::OUTPUT_MEM, txSlice.offset + baseOffset_,
30 0 : srcMem.ptr(), txSlice.size});
31 0 : }
32 0 : CHK_RET(link->TxAsync(txMems, stream_));
33 0 : HCCL_DEBUG("[AllGatherRing]TxVector for txMems success");
34 0 : return HCCL_SUCCESS;
35 0 : }
36 :
37 0 : HcclResult AllGatherRing::RxVector(const LINK &link, const std::vector<Slice> &rxSlices)
38 : {
39 0 : std::vector<RxMemoryInfo> rxMems;
40 0 : for (const Slice &rxSlice : rxSlices) {
41 0 : DeviceMem dstMem = outputMem_.range(rxSlice.offset, rxSlice.size);
42 0 : HCCL_DEBUG("rx dstMem[%p] range[%llu], size[%llu] ", dstMem.ptr(),
43 : rxSlice.offset, rxSlice.size);
44 0 : rxMems.emplace_back(RxMemoryInfo{UserMemType::OUTPUT_MEM, rxSlice.offset + baseOffset_,
45 0 : dstMem.ptr(), rxSlice.size});
46 0 : }
47 0 : CHK_RET(link->RxAsync(rxMems, stream_));
48 0 : return HCCL_SUCCESS;
49 0 : }
50 :
51 0 : HcclResult AllGatherRing::Tx(const LINK &link, const Slice &txSlice)
52 : {
53 0 : DeviceMem srcMem = outputMem_.range(txSlice.offset, txSlice.size);
54 0 : HCCL_DEBUG("tx srcMem[%p] range[%llu] size[%llu] ", srcMem.ptr(), txSlice.offset, txSlice.size);
55 0 : CHK_RET(link->TxAsync(UserMemType::OUTPUT_MEM, txSlice.offset + baseOffset_, srcMem.ptr(), txSlice.size, stream_));
56 0 : return HCCL_SUCCESS;
57 0 : }
58 :
59 0 : HcclResult AllGatherRing::Rx(const LINK &link, const Slice &rxSlice)
60 : {
61 0 : DeviceMem dstMem = outputMem_.range(rxSlice.offset, rxSlice.size);
62 0 : HCCL_DEBUG("rx dstMem[%p] range[%llu], size[%llu] ", dstMem.ptr(),
63 : rxSlice.offset, rxSlice.size);
64 0 : CHK_RET(link->RxAsync(UserMemType::OUTPUT_MEM, rxSlice.offset + baseOffset_, dstMem.ptr(), rxSlice.size, stream_));
65 0 : return HCCL_SUCCESS;
66 0 : }
67 :
68 : // 服务器间allgather的入口函数
69 0 : HcclResult AllGatherRing::RunAsync(const u32 rank, const u32 rankSize, const std::vector<LINK> &links)
70 : {
71 0 : CHK_SMART_PTR_NULL(dispatcher_);
72 0 : CHK_PTR_NULL(stream_.ptr());
73 0 : HCCL_INFO("AllGatherRing run_async rank[%u] ranksize[%u] inputMem[%p] outputMem[%p] count[%llu]", \
74 : rank, rankSize, inputMem_.ptr(), outputMem_.ptr(), count_);
75 :
76 0 : if (rankSize == 1) {
77 0 : if (inputMem_ != outputMem_) {
78 0 : CHK_RET(HcclD2DMemcpyAsync(dispatcher_, outputMem_, inputMem_, stream_));
79 : }
80 0 : return HCCL_SUCCESS;
81 : }
82 0 : HCCL_DEBUG("[AllGatherRing][RunAsync] AllGather Ring begins");
83 : // 获取ring algorithm所需的通信连接
84 0 : u32 ringPrevRank = (rank + rankSize - 1) % rankSize;
85 0 : u32 ringNextRank = (rank + 1) % rankSize;
86 :
87 0 : if (links.size() < rankSize) {
88 0 : HCCL_ERROR("[AllGatherRing][RunAsync]rank[%u] linkSize is less than rankSize", rank);
89 0 : return HCCL_E_INTERNAL;
90 : }
91 :
92 0 : linkLeft_ = links[ringPrevRank];
93 0 : CHK_SMART_PTR_NULL(linkLeft_);
94 :
95 0 : linkRight_ = links[ringNextRank];
96 0 : CHK_SMART_PTR_NULL(linkRight_);
97 :
98 0 : u32 unitSize = DataUnitSize(dataType_);
99 0 : if (unitSize == 0) {
100 0 : HCCL_ERROR("[AllGatherRing][RunAsync]unitSize is zero");
101 0 : return HCCL_E_INTERNAL;
102 : }
103 :
104 0 : std::vector<Slice> inputSlices(slices_);
105 0 : if (slices_.size() == 0) {
106 0 : slices_.resize(rankSize);
107 0 : inputSlices.resize(rankSize);
108 :
109 0 : u64 sliceSize = count_ * unitSize;
110 0 : for (u32 i = 0; i < rankSize; i++) {
111 0 : slices_[i].size = sliceSize;
112 0 : slices_[i].offset = sliceSize * i;
113 0 : inputSlices[i].size = sliceSize;
114 0 : inputSlices[i].offset = (inputMem_.size() < outputMem_.size()) ? 0 : (sliceSize * i);
115 0 : HCCL_DEBUG("rank[%u], slices[%u].offset=%llu, slices[%u].size=%llu", \
116 : rank, i, slices_[i].offset, i, slices_[i].size);
117 : }
118 : }
119 :
120 : // 双buffer下, 先将input拷贝到output的合适位置
121 0 : if (inputMem_ != outputMem_) {
122 0 : DeviceMem dst = outputMem_.range(slices_[rank].offset, slices_[rank].size);
123 0 : DeviceMem src = inputMem_.range(inputSlices[rank].offset, inputSlices[rank].size);
124 0 : CHK_RET(HcclD2DMemcpyAsync(dispatcher_, dst, src, stream_));
125 0 : }
126 :
127 : // 运行all-gather, ring算法
128 : // 单环场景下 nicRankList_ 长度默认为 8。
129 : // 多环场景下 nicRankList_ 长度为网口数量。此时若 rankSize != nicRankList_ 则为网口裁剪场景
130 0 : if (rankSize != HCCL_NIC_MAX_NUM || nicRankList_.size() == HCCL_NIC_MAX_NUM) {
131 : // 非网口裁剪场景:
132 0 : CHK_RET(RunAllGather(rank, rankSize, slices_));
133 : } else {
134 : // 网口裁剪场景:当前仅在 910A 8P_RING (4环),且网口不满配情况下使用
135 0 : CHK_RET(AllGatherSlicesPrep(rankSize, nicRankList_.size()));
136 0 : CHK_RET(RunAllGatherChunk(rank, rankSize, slices_));
137 : }
138 :
139 0 : if (barrierSwitchOn_) {
140 : // 执行barrier,保证数据收发完成
141 0 : CHK_RET(ExecuteBarrier(linkLeft_, linkRight_));
142 : }
143 0 : HCCL_INFO("AllGatherRing finished: rank[%u] end", rank);
144 0 : return HCCL_SUCCESS;
145 0 : }
146 :
147 0 : HcclResult AllGatherRing::RunAllGather(u32 rank, u32 rankSize, const std::vector<Slice> &outputSlices)
148 : {
149 0 : if (outputSlices.size() < rankSize) {
150 0 : HCCL_ERROR("[Run][AllGather]rank[%u] OutputSlice Size is less than rank size", rank);
151 0 : return HCCL_E_INTERNAL;
152 : }
153 0 : HcclResult ret = HCCL_SUCCESS;
154 :
155 : // 首次传输,将本rank的数据发送到下游
156 0 : u32 sliceSize = outputSlices.size() / rankSize;
157 0 : u32 rxSliceIndex = ForwordRank(rank, rankSize, 1);
158 0 : u32 txSliceIndex = rank;
159 0 : HCCL_DEBUG("[AllGatherRing][RunAllGather]sliceSize is %u, rxSliceIndex is %u", sliceSize, rxSliceIndex);
160 0 : for (u32 i = 0; i < rankSize - 1; i++) {
161 0 : HCCL_DEBUG("rank[%u] round[%u] will tx_ack outputslice[%u].offset is[%llu] size[%llu]",
162 : rank, i, rxSliceIndex, outputSlices[rxSliceIndex].offset, outputSlices[rxSliceIndex].size);
163 0 : CHK_RET(linkLeft_->TxAck(stream_));
164 :
165 : // reduce目的操作
166 0 : HCCL_DEBUG("rank[%u] round[%u] will rx ack because outputSlices[%u] size[%llu] ", rank, \
167 : i, txSliceIndex, outputSlices[txSliceIndex].size);
168 0 : CHK_RET(linkRight_->RxAck(stream_));
169 :
170 0 : std::vector<Slice> txSegsSlice;
171 0 : std::vector<Slice> rxSegsSlice;
172 0 : for (u32 j = 0; j < sliceSize; j++) {
173 0 : txSegsSlice.push_back(outputSlices[txSliceIndex * sliceSize + j]);
174 0 : rxSegsSlice.push_back(outputSlices[rxSliceIndex * sliceSize + j]);
175 : }
176 0 : ret = TxVector(linkRight_, txSegsSlice);
177 0 : CHK_PRT_RET(ret != HCCL_SUCCESS,
178 : HCCL_ERROR("[Run][AllGather]rank[%u] round[%u] Right Link tx outputSlices[%u] "\
179 : "Failed", rank, i, txSliceIndex), ret);
180 :
181 : // reduce源操作
182 0 : HCCL_DEBUG("rank[%u] round[%u] rx data outputSlices[%u] offset[%llu] size[%llu]", \
183 : rank, i, rxSliceIndex, outputSlices[rxSliceIndex].offset, outputSlices[rxSliceIndex].size);
184 0 : ret = RxVector(linkLeft_, rxSegsSlice);
185 0 : CHK_PRT_RET(ret != HCCL_SUCCESS,
186 : HCCL_ERROR("[Run][AllGather]rank[%u] round[%u] Left Link rx outputSlices[%u] "\
187 : "Failed", rank, i, rxSliceIndex), ret);
188 :
189 : // 末尾传输, 只接收一次, 不用再次发送
190 0 : txSliceIndex = ForwordRank(txSliceIndex, rankSize, 1);
191 0 : rxSliceIndex = ForwordRank(rxSliceIndex, rankSize, 1);
192 :
193 0 : ret = linkLeft_->RxWaitDone(stream_);
194 0 : CHK_PRT_RET(ret != HCCL_SUCCESS, HCCL_ERROR("[Run][ReduceScatter]RxWaitDone failed"), ret);
195 0 : ret = linkRight_->TxWaitDone(stream_);
196 0 : CHK_PRT_RET(ret != HCCL_SUCCESS, HCCL_ERROR("[Run][ReduceScatter]TxWaitDone failed"), ret);
197 0 : }
198 0 : return HCCL_SUCCESS;
199 : }
200 :
201 0 : HcclResult AllGatherRing::RunAllGatherChunk(const u32 rank, const u32 rankSize,
202 : const std::vector<Slice> &outputSlices)
203 : {
204 0 : if (outputSlices.size() < rankSize) {
205 0 : HCCL_ERROR("[Run][AllGatherChunk]rank[%u] OutputSlice Size is less than rank size", rank);
206 0 : return HCCL_E_INTERNAL;
207 : }
208 : HcclResult ret;
209 0 : u32 sendSliceLen = rankSliceLists_[rank].size();
210 0 : u32 chunkSize = HCCL_NIC_MAX_NUM / nicRankList_.size();
211 0 : if (sendSliceLen >= chunkSize) {
212 0 : CHK_RET(HeadAllGatherChunk(rank, rankSize, outputSlices));
213 0 : for (u32 midRankIdx = 1; midRankIdx < sendSliceLen - 1; midRankIdx++) {
214 0 : ret = MidAllGatherChunk(rank, rankSize, midRankIdx, outputSlices);
215 0 : CHK_PRT_RET(ret != HCCL_SUCCESS,
216 : HCCL_ERROR("[Run][AllGatherChunk]rank[%u] run mid[%u] ReduceScatter chunk failed", rank, midRankIdx),
217 : HCCL_E_INTERNAL);
218 : }
219 0 : CHK_RET(TailAllGatherChunk(rank, rankSize, sendSliceLen - 1, outputSlices));
220 : } else {
221 0 : for (u32 rxSliceIndex = 0; rxSliceIndex < HCCL_NIC_MAX_NUM; rxSliceIndex++) {
222 0 : CHK_RET(linkLeft_->TxAck(stream_));
223 :
224 0 : ret = Rx(linkLeft_, outputSlices[rxSliceIndex]);
225 0 : CHK_PRT_RET(ret != HCCL_SUCCESS, HCCL_ERROR("[Run][AllGatherChunk]rank[%u] Left Link rx outputSlices[%u] "\
226 : "Failed", rank, rxSliceIndex), ret);
227 : }
228 : }
229 0 : return HCCL_SUCCESS;
230 : }
231 :
232 0 : HcclResult AllGatherRing::HeadAllGatherChunk(u32 rank, u32 rankSize, const std::vector<Slice> &outputSlices)
233 : {
234 0 : if (outputSlices.size() < rankSize) {
235 0 : HCCL_ERROR("[AllGatherRing][HeadAllGatherChunk]rank[%u] OutputSlice Size is less than rank size", rank);
236 0 : return HCCL_E_INTERNAL;
237 : }
238 : HcclResult ret;
239 0 : u32 rxSliceIndex = rankSliceLists_[rank][0];
240 0 : u32 txSliceIndex = rxSliceIndex;
241 0 : std::vector<u32> preRankSlices(rankSliceLists_[(rank - 1 + rankSize) % rankSize]);
242 0 : std::vector<u32>::iterator iterSlice = std::find(preRankSlices.begin(), preRankSlices.end(), rxSliceIndex);
243 0 : if (iterSlice != preRankSlices.end()) {
244 0 : CHK_RET(linkLeft_->TxAck(stream_));
245 :
246 0 : ret = Rx(linkLeft_, outputSlices[rxSliceIndex]);
247 0 : CHK_PRT_RET(ret != HCCL_SUCCESS, HCCL_ERROR("[AllGatherRing][HeadAllGatherChunk]rank[%u] Left Link rx "\
248 : "outputSlices[%u] Failed", rank, rxSliceIndex), ret);
249 : }
250 :
251 0 : iterSlice = std::find(preRankSlices.begin(), preRankSlices.end(), rankSliceLists_[rank][1]);
252 0 : if (iterSlice != preRankSlices.end()) {
253 0 : CHK_RET(MidAllGatherChunk(rank, rankSize, 0, outputSlices));
254 : } else {
255 0 : CHK_RET(linkRight_->RxAck(stream_));
256 :
257 0 : ret = Tx(linkRight_, outputSlices[txSliceIndex]);
258 0 : CHK_PRT_RET(ret != HCCL_SUCCESS, HCCL_ERROR("[AllGatherRing][HeadAllGatherChunk]rank[%u] Right Link tx "\
259 : "outputSlices[%u] Failed", rank, txSliceIndex), ret);
260 : }
261 0 : return HCCL_SUCCESS;
262 0 : }
263 :
264 0 : HcclResult AllGatherRing::MidAllGatherChunk(u32 rank, u32 rankSize, u32 sliceIdx,
265 : const std::vector<Slice> &outputSlices)
266 : {
267 0 : if (outputSlices.size() < rankSize) {
268 0 : HCCL_ERROR("[AllGatherRing][MidAllGatherChunk]rank[%u] OutputSlice Size is less than rank size", rank);
269 0 : return HCCL_E_INTERNAL;
270 : }
271 : HcclResult ret;
272 0 : u32 rxSliceIndex = rankSliceLists_[rank][sliceIdx + 1];
273 0 : u32 txSliceIndex = rankSliceLists_[rank][sliceIdx];
274 0 : std::vector<u32> preRankSlices(rankSliceLists_[(rank - 1 + rankSize) % rankSize]);
275 0 : std::vector<u32>::iterator iterSlice = std::find(preRankSlices.begin(), preRankSlices.end(), rxSliceIndex);
276 0 : if (iterSlice != preRankSlices.end()) {
277 0 : CHK_RET(linkLeft_->TxAck(stream_));
278 :
279 0 : CHK_RET(linkRight_->RxAck(stream_));
280 :
281 0 : ret = Tx(linkRight_, outputSlices[txSliceIndex]);
282 0 : CHK_PRT_RET(ret != HCCL_SUCCESS, HCCL_ERROR("[AllGatherRing][MidAllGatherChunk]rank[%u] Right Link tx "\
283 : "outputSlices[%u] Failed", rank, txSliceIndex), ret);
284 0 : ret = Rx(linkLeft_, outputSlices[rxSliceIndex]);
285 0 : CHK_PRT_RET(ret != HCCL_SUCCESS, HCCL_ERROR("[AllGatherRing][MidAllGatherChunk]rank[%u] Left Link rx "\
286 : "outputSlices[%u] Failed", rank, rxSliceIndex), ret);
287 : } else {
288 0 : CHK_RET(linkRight_->RxAck(stream_));
289 :
290 0 : ret = Tx(linkRight_, outputSlices[txSliceIndex]);
291 0 : CHK_PRT_RET(ret != HCCL_SUCCESS, HCCL_ERROR("[AllGatherRing][MidAllGatherChunk]rank[%u] Right Link tx "\
292 : "outputSlices[%u] Failed", rank, txSliceIndex), ret);
293 : }
294 0 : return HCCL_SUCCESS;
295 0 : }
296 :
297 0 : HcclResult AllGatherRing::TailAllGatherChunk(u32 rank, u32 rankSize, u32 sliceIdx,
298 : const std::vector<Slice> &outputSlices)
299 : {
300 0 : if (outputSlices.size() < rankSize) {
301 0 : HCCL_ERROR("[AllGatherRing][TailAllGatherChunk]rank[%u] OutputSlice Size is less than rank size", rank);
302 0 : return HCCL_E_INTERNAL;
303 : }
304 : HcclResult ret;
305 0 : u32 chunkSize = HCCL_NIC_MAX_NUM / nicRankList_.size();
306 0 : u32 txSliceIndex = rankSliceLists_[rank][sliceIdx];
307 0 : u32 nextRank = (rank + 1 + rankSize) % rankSize;
308 0 : std::vector<u32>::iterator iterNic = std::find(nicRankList_.begin(), nicRankList_.end(), nextRank);
309 0 : if (iterNic != nicRankList_.end()) {
310 0 : u32 nicIdx = distance(nicRankList_.begin(), iterNic);
311 0 : u32 chunkStart = nicIdx * chunkSize;
312 0 : u32 rxSliceIndex = chunkStart;
313 0 : CHK_RET(linkLeft_->TxAck(stream_));
314 :
315 0 : CHK_RET(linkRight_->RxAck(stream_));
316 :
317 0 : ret = Tx(linkRight_, outputSlices[txSliceIndex]);
318 0 : CHK_PRT_RET(ret != HCCL_SUCCESS, HCCL_ERROR("[AllGatherRing][TailAllGatherChunk]rank[%u] Right Link tx "\
319 : "outputSlices[%u] Failed", rank, txSliceIndex), ret);
320 0 : ret = Rx(linkLeft_, outputSlices[rxSliceIndex]);
321 0 : CHK_PRT_RET(ret != HCCL_SUCCESS, HCCL_ERROR("[AllGatherRing][TailAllGatherChunk]rank[%u] Left Link rx "\
322 : "outputSlices[%u] Failed", rank, rxSliceIndex), ret);
323 :
324 0 : for (u32 sliceIdx = 1; sliceIdx < chunkSize; sliceIdx++) {
325 0 : rxSliceIndex = chunkStart + sliceIdx;
326 0 : CHK_RET(linkLeft_->TxAck(stream_));
327 :
328 0 : ret = Rx(linkLeft_, outputSlices[rxSliceIndex]);
329 0 : CHK_PRT_RET(ret != HCCL_SUCCESS, HCCL_ERROR("[AllGatherRing][TailAllGatherChunk]rank[%u] Left Link rx "\
330 : "outputSlices[%u] Failed", rank, rxSliceIndex), ret);
331 : }
332 : } else {
333 0 : CHK_RET(linkRight_->RxAck(stream_));
334 :
335 0 : ret = Tx(linkRight_, outputSlices[txSliceIndex]);
336 0 : CHK_PRT_RET(ret != HCCL_SUCCESS, HCCL_ERROR("[AllGatherRing][TailAllGatherChunk]rank[%u] Right Link tx "\
337 : "outputSlices[%u] Failed", rank, txSliceIndex), ret);
338 : }
339 0 : return HCCL_SUCCESS;
340 : }
341 :
342 : // 多网口allgather各rank发送slice准备
343 0 : HcclResult AllGatherRing::AllGatherSlicesPrep(u32 rankSize, u32 nicSize)
344 : {
345 0 : u32 chunkSize = HCCL_NIC_MAX_NUM / nicSize;
346 0 : for (u32 rankIdx = 0; rankIdx < rankSize; rankIdx++) {
347 0 : std::vector<u32> sliceList; // 单个rank上的发送slice编号
348 0 : for (u32 nicDis = 0; nicDis <= rankSize - 2; nicDis++) { // 递减从当前rank遍历至(rank+2+ranksize)%ranksize的位置
349 0 : u32 nicIdx = (rankIdx + rankSize - nicDis) % rankSize;
350 0 : std::vector<u32>::iterator iterNic = std::find(nicRankList_.begin(), nicRankList_.end(), nicIdx);
351 0 : if (iterNic != nicRankList_.end()) { // 当前rank为网口所在位置,将网口对应的chunksize份silce放入sliceList
352 0 : u32 nicListIdx = distance(nicRankList_.begin(), iterNic);
353 0 : for (u32 chunkIdx = 0; chunkIdx < chunkSize; chunkIdx++) {
354 0 : sliceList.push_back(chunkSize * nicListIdx + chunkIdx);
355 : }
356 : }
357 : }
358 0 : HCCL_DEBUG("[AllGatherRing][AllGatherSlicesPrep]rankIdx now is [%u]", rankIdx);
359 0 : rankSliceLists_.push_back(sliceList);
360 0 : }
361 0 : return HCCL_SUCCESS;
362 : }
363 :
364 0 : HcclResult AllGatherRing::GetNslbAdjInfo(const u32 rank, const u32 rankSize,
365 : const std::vector<LINK> &links, AdjInfo& nslbAdjInfo)
366 : {
367 0 : NslbDpAdjInfo adjInfoStep = {0};
368 0 : u32 ringNextRank = (rank + 1) % rankSize;
369 0 : LINK nslbNext = links[ringNextRank];
370 :
371 0 : nslbAdjInfo.dstRankNum = 1;
372 0 : adjInfoStep.dstLocalRankId = nslbNext->GetRemoteRank();
373 0 : adjInfoStep.phaseId = 1;
374 0 : adjInfoStep.rev = 0;
375 0 : nslbAdjInfo.nsAdjInfo.push_back(adjInfoStep);
376 :
377 0 : return HCCL_SUCCESS;
378 0 : }
379 : REGISTER_TEMPLATE(TemplateType::TEMPLATE_ALL_GATHER_RING, AllGatherRing);
380 : } // namespace hccl
|