已合并
perf(index): 优化 KthValue、Median 与 NanMedian 选择及归并路径 #10411
黄晓彬创建于 9月14日
perf(index): 优化 KthValue、Median 与 NanMedian 选择及归并路径 #10411
已合并
黄晓彬创建于 9月14日
共 27 个文件变更+2133-412
@@ -353,9 +353,9 @@ static ge::graphStatus SetRadixMoreCoreTiling(gert::TilingContext* context, Sort
353}353}
354 354 
355static ge::graphStatus SetKthValueMergeSortTiling(gert::TilingContext* context, SortKthTileInfo& info,355static ge::graphStatus SetKthValueMergeSortTiling(gert::TilingContext* context, SortKthTileInfo& info,
356- uint32_t& blockDim, uint64_t& schId)356+ uint32_t& blockDim, uint64_t& schId, bool useUbCapacity)
357{357{
358- OP_CHECK_IF(!ComputeMergeSortTiling(context, info, static_cast<uint32_t>(sizeof(uint32_t))),358+ OP_CHECK_IF(!ComputeMergeSortTiling(context, info, static_cast<uint32_t>(sizeof(uint32_t)), useUbCapacity),
359 OP_LOGE_FOR_INVALID_VALUE_WITH_REASON(context->GetNodeName(), "ComputeMergeSortTiling", "false",359 OP_LOGE_FOR_INVALID_VALUE_WITH_REASON(context->GetNodeName(), "ComputeMergeSortTiling", "false",
360 "The value of ComputeMergeSortTiling must be true."),360 "The value of ComputeMergeSortTiling must be true."),
361 return ge::GRAPH_FAILED);361 return ge::GRAPH_FAILED);
@@ -377,7 +377,8 @@ static ge::graphStatus SetMergeMoreCoreTiling(gert::TilingContext* context, Sort
377 "The value of ComputeMergeMoreCoreTiling must be true."),377 "The value of ComputeMergeMoreCoreTiling must be true."),
378 return ge::GRAPH_FAILED);378 return ge::GRAPH_FAILED);
379 blockDim = info.coreNumNeed;379 blockDim = info.coreNumNeed;
380- OP_LOGI("KthValueMergeMoreCoreTiling", "maxDealingNum: %u", info.keyParams0);380+ OP_LOGI("KthValueMergeMoreCoreTiling", "maxDealingNum: %u, syncMergeBlockSize: %u", info.keyParams0,
381+ info.keyParams1);
381 return ge::GRAPH_SUCCESS;382 return ge::GRAPH_SUCCESS;
382}383}
383 384 
@@ -589,7 +590,76 @@ static bool TryRadixOneCore(gert::TilingContext* context, const SortKthTileInfo&
589 return true;590 return true;
590}591}
591 592 
592-static bool IsRadixSelectProfitable(const SortKthTileInfo& info, int64_t kthIndex)593+// Empirical route policy, including the power-of-two crossovers below. These are not
594+// hardware capacity guarantees; each tiling builder independently checks its UB footprint.
595+namespace KthRoutePolicy {
596+constexpr int64_t RESIDENT_MIN_AXIS = 4096;
597+constexpr int64_t RESIDENT_MAX_AXIS = 16384;
598+constexpr uint64_t RADIX_SELECT_MAX_TILE_ELEMS = 32768UL;
599+constexpr int64_t INT32_RESIDENT_MIN_AXIS = 1024;
600+constexpr int64_t FLOAT_SELECT_MIN_AXIS = 1024;
601+constexpr int64_t RESIDENT_HISTOGRAM_MAX_AXIS = 2048;
602+constexpr int64_t RESIDENT_HISTOGRAM_SINGLE_WAVE_MAX_AXIS = 8192;
603+constexpr int64_t RESIDENT_HISTOGRAM_MIN_ROWS_PER_CORE = 8;
604+// Keep the validated resident-half range and tile/core split stable. Growing this cap
605+// changes both route eligibility and cores per row, even when the extra elements fit UB.
606+constexpr uint64_t HALF_SELECT_MAX_TILE_ELEMS = 49152UL;
607+// Measured merge/radix crossover, not an alignment constraint. Cast + merge is not
608+// selected for the shorter rows handled by the earlier narrow/byte policies.
609+constexpr int64_t INT16_MERGE_MIN_AXIS = 192;
610+constexpr int64_t INT16_MERGE_MAX_AXIS = 512;
611+// KthValue batches wider byte rows than Sort. Keep its policy explicit rather
612+// than copying and patching the shared Sort rule at runtime.
613+// Signed and unsigned byte keys use the same one-byte radix pipeline.
614+constexpr SmallAxisRule BYTE_SMALL_AXIS_RULES[] = {
615+ {ge::DT_INT8, 8, 384, {{4, 2}, {8, 7}, {0, 0}}, {{3, 8}, {64, 7}, {384, 16}, {0, 0}}},
616+ {ge::DT_UINT8, 8, 384, {{4, 2}, {8, 7}, {0, 0}}, {{3, 8}, {64, 7}, {384, 16}, {0, 0}}},
617+};
618+static_assert(HALF_SELECT_MAX_TILE_ELEMS <= std::numeric_limits<uint16_t>::max(),
619+ "A per-tile cumulative histogram must not overflow uint16_t");
620+static_assert(RADIX_SELECT_MAX_TILE_ELEMS <= std::numeric_limits<uint16_t>::max(),
621+ "A per-tile cumulative histogram must not overflow uint16_t");
622+} // namespace KthRoutePolicy
623+ 
624+static bool IsResidentIntegerSelect(const SortKthTileInfo& info)
625+{
626+ using namespace KthRoutePolicy;
627+ if (info.dataType == ge::DT_INT32 && info.lastAxis >= INT32_RESIDENT_MIN_AXIS &&
628+ info.lastAxis < RESIDENT_MIN_AXIS) {
629+ return true;
630+ }
631+ // Int16 rows can use the full existing tile capacity; the tiling builder
632+ // still limits allocation by available UB before selecting the kernel.
633+ int64_t maxResidentAxis = info.dataType == ge::DT_INT16 ? static_cast<int64_t>(RADIX_SELECT_MAX_TILE_ELEMS) :
634+ RESIDENT_MAX_AXIS;
635+ return (info.dataType == ge::DT_INT16 || info.dataType == ge::DT_INT32 || info.dataType == ge::DT_UINT32 ||
636+ info.dataType == ge::DT_INT64 || info.dataType == ge::DT_UINT64) &&
637+ info.lastAxis >= RESIDENT_MIN_AXIS && info.lastAxis <= maxResidentAxis;
638+}
639+ 
640+static bool IsResidentFloatSelect(const SortKthTileInfo& info, uint32_t medianMode)
641+{
642+ // Resident rows avoid full-sort traffic. NaN propagation keeps its existing multi-core schedule.
643+ using namespace KthRoutePolicy;
644+ return info.dataType == ge::DT_FLOAT && (info.unsortedDim == 1 || medianMode != MEDIAN_MODE_PROPAGATE_NAN) &&
645+ info.lastAxis > RESIDENT_MIN_AXIS && info.lastAxis <= RESIDENT_MAX_AXIS;
646+}
647+ 
648+static bool IsKthFloatSelect(const SortKthTileInfo& info, uint32_t medianMode)
649+{
650+ using namespace KthRoutePolicy;
651+ if (medianMode != MEDIAN_MODE_STATIC || info.lastAxis < FLOAT_SELECT_MIN_AXIS) {
652+ return false;
653+ }
654+ // Extend small FP32 rows and resident 16-bit float rows; retain median policies.
655+ if (info.dataType == ge::DT_FLOAT) {
黄
黄黄晓彬9月17日

[P1] 扩大浮点 radix-select 路由前,需要为普通 KthValue 统一 NaN 排序键

新增 IsKthFloatSelect 将末轴长度 1024–4096 的 FP32 KthValue 优先送入 radix-select,但普通 KthValue 实例化 EnableMedian=false。kth_value_radix_select.h 的 MakeSortKeys 调用 TwiddleInFp32<T, UT, 0>,该 helper 不归一化 NaN;后续 MapNanKeysToMax 又仅在 EnableMedian=true 时执行。负 NaN 因此会被当作最小键。

具体反例:FP32 [1,1024],k=1、dim=-1,输入 [-NaN,1,2,...,1023],其中负 NaN 位模式为 0xffc00000。PR 的位变换将其映射为 0x003fffff,小于所有正数键,选择结果为 NaN/index 0;仓内 PyTorch golden 期望为 1/index 1。已实际运行 CPU torch.kthvalue 与复刻该位变换的最小模型,得到上述差异。新增 FP16/BF16 radix-select 范围也有同类问题。

建议浮点 KthValue 也将所有 NaN 映射到最大键,保留原始输入用于输出取值,并补充负 NaN、混合符号/不同 payload NaN、k=1/k=N 的回归用例。

验证范围:当前提交 0a31a902ccdbf2a9dce48681ea2c2bd311d00012 的静态分析及 CPU 键语义模型,未做 NPU 实跑。

likedislike
黄晓彬
9月17日 评论:
656+ return info.lastAxis <= RESIDENT_MIN_AXIS;
657+ }
658+ return (info.dataType == ge::DT_FLOAT16 || info.dataType == ge::DT_BF16) &&
659+ info.lastAxis <= static_cast<int64_t>(HALF_SELECT_MAX_TILE_ELEMS);
660+}
661+ 
662+static bool IsRadixSelectProfitable(const SortKthTileInfo& info, int64_t kthIndex, uint32_t medianMode)
593{663{
594 if (kthIndex < 0 || info.lastAxis <= 0 || info.unsortedDim <= 0) {664 if (kthIndex < 0 || info.lastAxis <= 0 || info.unsortedDim <= 0) {
595 return false;665 return false;
@@ -600,6 +670,12 @@ static bool IsRadixSelectProfitable(const SortKthTileInfo& info, int64_t kthInde
600 return false;670 return false;
601 }671 }
602 672 
673+ // Retained rows can select in UB without scattering a complete sorted row.
674+ if (IsResidentIntegerSelect(info) || IsResidentFloatSelect(info, medianMode) ||
675+ IsKthFloatSelect(info, medianMode)) {
676+ return true;
677+ }
678+ 
603 constexpr uint64_t radixSelectMinAxis = 65536UL;679 constexpr uint64_t radixSelectMinAxis = 65536UL;
604 constexpr uint64_t radixSelectHugeAxis = 1000000UL;680 constexpr uint64_t radixSelectHugeAxis = 1000000UL;
605 constexpr uint64_t radixSelectInt64SmallAxis = 4096UL;681 constexpr uint64_t radixSelectInt64SmallAxis = 4096UL;
@@ -665,26 +741,33 @@ static uint64_t GetRadixSelectFixedBytes()
665 ((reservedRawBytes + radixSelectReserveAlign - 1UL) / radixSelectReserveAlign) * radixSelectReserveAlign;741 ((reservedRawBytes + radixSelectReserveAlign - 1UL) / radixSelectReserveAlign) * radixSelectReserveAlign;
666}742}
667 743 
668-static bool ComputeRadixSelectTileElems(const SortKthTileInfo& info, uint64_t& tileElems)744+static bool ComputeRadixSelectTileElems(const SortKthTileInfo& info, uint32_t medianMode, uint64_t& tileElems)
669{745{
670- constexpr uint64_t radixSelectMaxTileElems = 32768UL;
671 constexpr uint64_t radixSelectMinAlignElems = 256UL;746 constexpr uint64_t radixSelectMinAlignElems = 256UL;
672 uint64_t usableUb = ComputeUbAfterSimtReserve(info.ubSize);747 uint64_t usableUb = ComputeUbAfterSimtReserve(info.ubSize);
673- uint64_t bytesPerElem = static_cast<uint64_t>(info.dtypeSize) * 2UL;748+ // Retain the established slice/core plan outside the resident integer range.
749+ // A larger tile there can reduce cores per row and lose parallelism.
750+ uint64_t bytesPerElem = static_cast<uint64_t>(info.dtypeSize) * (IsResidentIntegerSelect(info) ? 1UL : 2UL);
674 uint64_t fixedBytes = GetRadixSelectFixedBytes();751 uint64_t fixedBytes = GetRadixSelectFixedBytes();
675 if (bytesPerElem == 0U || usableUb <= fixedBytes) {752 if (bytesPerElem == 0U || usableUb <= fixedBytes) {
676 return false;753 return false;
677 }754 }
678- tileElems = std::min<uint64_t>((usableUb - fixedBytes) / bytesPerElem, radixSelectMaxTileElems);755+ // Only the newly enabled resident half/bfloat range needs the larger tile.
756+ // Both caps (32768/49152) are below 65536: each tile clears its uint16
757+ // cumulative histogram, then widens it before accumulating across tiles.
758+ uint64_t maxTileElems = IsKthFloatSelect(info, medianMode) && info.dataType != ge::DT_FLOAT ?
759+ KthRoutePolicy::HALF_SELECT_MAX_TILE_ELEMS :
760+ KthRoutePolicy::RADIX_SELECT_MAX_TILE_ELEMS;
761+ tileElems = std::min<uint64_t>((usableUb - fixedBytes) / bytesPerElem, maxTileElems);
679 uint64_t alignElems = std::max<uint64_t>(radixSelectMinAlignElems, info.blockUbSize / info.dtypeSize);762 uint64_t alignElems = std::max<uint64_t>(radixSelectMinAlignElems, info.blockUbSize / info.dtypeSize);
680 tileElems = tileElems / alignElems * alignElems;763 tileElems = tileElems / alignElems * alignElems;
681 tileElems = std::min<uint64_t>(tileElems, static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()));764 tileElems = std::min<uint64_t>(tileElems, static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()));
682 return tileElems != 0U;765 return tileElems != 0U;
683}766}
684 767 
685-static bool ComputeRadixSelectPlan(const SortKthTileInfo& info, RadixSelectPlan& plan)768+static bool ComputeRadixSelectPlan(const SortKthTileInfo& info, uint32_t medianMode, RadixSelectPlan& plan)
686{769{
687- if (!ComputeRadixSelectTileElems(info, plan.tileElems)) {770+ if (!ComputeRadixSelectTileElems(info, medianMode, plan.tileElems)) {
688 return false;771 return false;
689 }772 }
690 plan.rowsParallel = static_cast<uint32_t>(773 plan.rowsParallel = static_cast<uint32_t>(
@@ -723,12 +806,20 @@ static bool ComputeRadixSelectWorkspace(const SortKthTileInfo& info, RadixSelect
723static bool TryRadixSelect(gert::TilingContext* context, SortKthTileInfo& info, KthValueTilingData* tilingData,806static bool TryRadixSelect(gert::TilingContext* context, SortKthTileInfo& info, KthValueTilingData* tilingData,
724 uint32_t& blockDim, uint64_t& schId)807 uint32_t& blockDim, uint64_t& schId)
725{808{
726- if (!IsRadixSelectProfitable(info, tilingData->kthIndex) || info.isNonLastAxis ||809+ if (info.isNonLastAxis || info.lastAxis <= static_cast<int64_t>(SMALL_AXIS_THRESHOLD) || info.maxCoreNum == 0U) {
727- info.lastAxis <= static_cast<int64_t>(SMALL_AXIS_THRESHOLD) || info.maxCoreNum == 0U) {
728 return false;810 return false;
729 }811 }
730 RadixSelectPlan plan;812 RadixSelectPlan plan;
731- if (!ComputeRadixSelectPlan(info, plan) || !ComputeRadixSelectWorkspace(info, plan)) {813+ if (!ComputeRadixSelectPlan(info, tilingData->medianMode, plan) || !ComputeRadixSelectWorkspace(info, plan)) {
814+ return false;
815+ }
816+ // A single B64 row can skip common prefix bytes across its cooperating cores when
817+ // every slice remains in UB. Streaming rows retain the established routing policy.
818+ // ParseKthValueShapeInfo has already checked 1 <= k <= lastAxis before
819+ // kthIndex = k - 1 is initialized; this exception only bypasses profitability.
820+ bool retainedB64Row = (info.dataType == ge::DT_INT64 || info.dataType == ge::DT_UINT64) && info.unsortedDim == 1 &&
821+ plan.coresPerRow > 1U && plan.tileCount <= plan.coresPerRow;
822+ if (!IsRadixSelectProfitable(info, tilingData->kthIndex, tilingData->medianMode) && !retainedB64Row) {
732 return false;823 return false;
733 }824 }
734 size_t* userWorkspaceSize = context->GetWorkspaceSizes(1);825 size_t* userWorkspaceSize = context->GetWorkspaceSizes(1);
@@ -750,7 +841,18 @@ static bool TryRadixSelect(gert::TilingContext* context, SortKthTileInfo& info,
750 info = candidate;841 info = candidate;
751 blockDim = plan.blockDim;842 blockDim = plan.blockDim;
752 PlanToTilingData(info, tilingData);843 PlanToTilingData(info, tilingData);
753- schId = KTH_VALUE_SCHID_RADIX_SELECT;844+ // Use local histograms for batched medium rows or longer rows that fit in one core wave.
845+ bool batchedResidentHistogram = info.lastAxis >= KthRoutePolicy::FLOAT_SELECT_MIN_AXIS &&
846+ info.lastAxis <= KthRoutePolicy::RESIDENT_HISTOGRAM_MAX_AXIS &&
847+ info.unsortedDim >= static_cast<int64_t>(info.maxCoreNum) *
848+ KthRoutePolicy::RESIDENT_HISTOGRAM_MIN_ROWS_PER_CORE;
849+ bool singleWaveResidentHistogram = info.lastAxis > KthRoutePolicy::RESIDENT_MIN_AXIS &&
850+ info.lastAxis <= KthRoutePolicy::RESIDENT_HISTOGRAM_SINGLE_WAVE_MAX_AXIS &&
851+ info.unsortedDim <= static_cast<int64_t>(info.maxCoreNum);
852+ bool useResidentHistogram = tilingData->medianMode == MEDIAN_MODE_STATIC && info.dataType == ge::DT_FLOAT16 &&
853+ (batchedResidentHistogram || singleWaveResidentHistogram) && plan.coresPerRow == 1U &&
854+ plan.tileCount == 1U;
855+ schId = useResidentHistogram ? KTH_VALUE_SCHID_RESIDENT_HISTOGRAM : KTH_VALUE_SCHID_RADIX_SELECT;
754 userWorkspaceSize[0] = static_cast<size_t>(WORK_SPACE_SIZE + plan.workspace);856 userWorkspaceSize[0] = static_cast<size_t>(WORK_SPACE_SIZE + plan.workspace);
755 context->SetScheduleMode(1);857 context->SetScheduleMode(1);
756 return true;858 return true;
@@ -788,8 +890,61 @@ static bool TrySmallAxisShortRankSelect(const SortKthTileInfo& info, KthValueTil
788 return true;890 return true;
789}891}
790 892 
893+static bool SelectKthSmallAxisRoute(const SortKthTileInfo& info, SmallAxisRoutePlan& plan)
894+{
895+ for (const auto& rule : KthRoutePolicy::BYTE_SMALL_AXIS_RULES) {
896+ if (info.dataType == rule.dtype) {
897+ return SelectSmallAxisRoute(info, plan, &rule);
898+ }
899+ }
900+ return SelectSmallAxisRoute(info, plan);
901+}
902+ 
903+static bool SetNarrowSelectBatch(const SortKthTileInfo& info, uint32_t medianMode, SmallAxisRoutePlan& plan)
904+{
905+ constexpr int64_t maxByteAxis = 384;
906+ constexpr int64_t maxSignedByteAxis = 512;
907+ constexpr int64_t minFloatAxis = 32;
908+ constexpr int64_t maxFloatAxis = 256;
909+ constexpr uint64_t rowsPerWave = 32UL;
910+ constexpr uint64_t maxInputBytes = 131072UL;
911+ bool isByte = info.dataType == ge::DT_INT8 || info.dataType == ge::DT_UINT8;
912+ bool isFloat = info.dataType == ge::DT_FLOAT16 || info.dataType == ge::DT_BF16;
913+ if (info.isNonLastAxis || medianMode != MEDIAN_MODE_STATIC || info.lastAxis <= 0 || info.maxCoreNum == 0U ||
914+ (!isByte && !isFloat) || info.unsortedDim < static_cast<int64_t>(info.maxCoreNum * rowsPerWave)) {
915+ return false;
916+ }
917+ int64_t maxAxis = isFloat ? maxFloatAxis : (info.dataType == ge::DT_INT8 ? maxSignedByteAxis : maxByteAxis);
918+ if (info.lastAxis > maxAxis || (isFloat && info.lastAxis < minFloatAxis) ||
919+ (isByte && info.lastAxis <= maxByteAxis && plan.kind != SmallAxisRouteKind::TWO_STAGE)) {
920+ return false;
921+ }
922+ uint64_t elementBytes = isByte ? sizeof(uint8_t) : sizeof(uint16_t);
923+ if (info.dtypeSize != elementBytes) {
924+ return false;
925+ }
926+ uint64_t inputBytes = std::min<uint64_t>(ComputeUbAfterSimtReserve(info.ubSize), maxInputBytes);
927+ inputBytes = inputBytes / rowsPerWave * rowsPerWave;
928+ uint64_t capacity = inputBytes / (static_cast<uint64_t>(info.lastAxis) * elementBytes);
929+ if (capacity == 0U) {
930+ return false;
931+ }
932+ uint64_t rows = Ops::Base::CeilDiv(static_cast<uint64_t>(info.unsortedDim), static_cast<uint64_t>(info.maxCoreNum));
933+ rows = std::min<uint64_t>(Ops::Base::CeilAlign(rows, rowsPerWave), capacity);
934+ uint64_t batches = Ops::Base::CeilDiv(static_cast<uint64_t>(info.unsortedDim), rows);
935+ if (batches > std::numeric_limits<uint32_t>::max()) {
936+ return false;
937+ }
938+ plan.batchSize = static_cast<uint32_t>(rows);
939+ plan.batchNum = static_cast<uint32_t>(batches);
940+ plan.blockDim = std::min<uint32_t>(plan.batchNum, info.maxCoreNum);
941+ plan.tmpUbSize = 0;
942+ plan.kind = SmallAxisRouteKind::TWO_STAGE;
943+ return true;
944+}
945+ 
791static bool TrySmallAxis(gert::TilingContext* context, SortKthTileInfo& info, KthValueTilingData* tilingData,946static bool TrySmallAxis(gert::TilingContext* context, SortKthTileInfo& info, KthValueTilingData* tilingData,
792- uint32_t& blockDim, uint64_t& schId)947+ uint32_t& blockDim, uint64_t& schId, bool isMedianOp)
793{948{
794 if (info.lastAxis > static_cast<int64_t>(SMALL_AXIS_THRESHOLD)) {949 if (info.lastAxis > static_cast<int64_t>(SMALL_AXIS_THRESHOLD)) {
795 return false;950 return false;
@@ -804,8 +959,9 @@ static bool TrySmallAxis(gert::TilingContext* context, SortKthTileInfo& info, Kt
804 return true;959 return true;
805 }960 }
806 SmallAxisRoutePlan plan;961 SmallAxisRoutePlan plan;
807- bool selected = info.isNonLastAxis ? SelectNonLastSmallAxisRoute(info, plan) : SelectSmallAxisRoute(info, plan);962+ bool selected = info.isNonLastAxis ? SelectNonLastSmallAxisRoute(info, plan) : SelectKthSmallAxisRoute(info, plan);
808- if (!selected) {963+ bool narrowSelect = !isMedianOp && SetNarrowSelectBatch(info, tilingData->medianMode, plan);
964+ if (!selected && !narrowSelect) {
809 return false;965 return false;
810 }966 }
811 if (info.isNonLastAxis) {967 if (info.isNonLastAxis) {
@@ -814,24 +970,78 @@ static bool TrySmallAxis(gert::TilingContext* context, SortKthTileInfo& info, Kt
814 }970 }
815 } else {971 } else {
816 FillSmallAxisTiling(tilingData, plan, static_cast<uint32_t>(info.lastAxis), blockDim);972 FillSmallAxisTiling(tilingData, plan, static_cast<uint32_t>(info.lastAxis), blockDim);
973+ if (narrowSelect) {
974+ // Small-axis keyParams3 selects the narrow-key value-domain kernel and its input-only UB layout.
975+ constexpr uint32_t narrowSelectMode = 1U;
976+ tilingData->keyParams3 = narrowSelectMode;
977+ }
817 }978 }
818 schId = plan.kind == SmallAxisRouteKind::TWO_STAGE ? KTH_VALUE_SCHID_SMALL_AXIS_TWO_STAGE :979 schId = plan.kind == SmallAxisRouteKind::TWO_STAGE ? KTH_VALUE_SCHID_SMALL_AXIS_TWO_STAGE :
819 KTH_VALUE_SCHID_SMALL_AXIS_INSERTION;980 KTH_VALUE_SCHID_SMALL_AXIS_INSERTION;
820 return true;981 return true;
821}982}
822 983 
984+static bool FitsInt16MergeUb(const SortKthTileInfo& info)
985+{
986+ uint64_t rows = info.keyParams0;
987+ uint64_t alignedAxis = info.keyParams3;
988+ uint64_t buffers = info.keyParams4 == 0U ? 1U : info.keyParams4;
989+ uint64_t compactValues = Ops::Base::CeilAlign(rows * sizeof(int16_t), static_cast<uint64_t>(info.blockUbSize));
990+ uint64_t compactIndices = Ops::Base::CeilAlign(rows * sizeof(int64_t), static_cast<uint64_t>(info.blockUbSize));
991+ // Input/value/index queues and compact outputs, two proposal buffers, source
992+ // indices, and the exact int16-to-float cast batch mirror Kernel::Init.
993+ uint64_t queues = buffers * (2UL * info.keyParams1 + info.keyParams2 + compactValues + compactIndices);
994+ uint64_t proposalsAndIndices = alignedAxis * (2UL * MERGE_SORT_DATA_BYTES + sizeof(uint32_t));
995+ uint64_t castBatch = alignedAxis * rows * sizeof(float);
996+ uint64_t fullOutputUb = queues + proposalsAndIndices + castBatch;
997+ uint64_t mergeBatchRows = alignedAxis >= KTH_INT16_MERGE_BATCH_MIN_AXIS &&
998+ alignedAxis <= KTH_INT16_MERGE_BATCH_MAX_AXIS &&
999+ rows >= KTH_INT16_MERGE_BATCH_ROWS ?
1000+ KTH_INT16_MERGE_BATCH_ROWS :
1001+ 1U;
1002+ // Non-median int16 keeps only one Sort32 block per output row. The freed
1003+ // space holds per-row proposals and repeated source indices for batched merging.
1004+ uint64_t compactRowBytes = SORT32_SMALL_AXIS_THRESHOLD * (sizeof(int16_t) + sizeof(uint32_t));
1005+ uint64_t compactQueues = buffers * (info.keyParams1 + rows * compactRowBytes + compactValues + compactIndices);
1006+ uint64_t compactOutputUb = compactQueues + proposalsAndIndices * mergeBatchRows + castBatch;
1007+ // Median retains full outputs, so validate both kernel layouts before accepting the route.
1008+ return std::max(fullOutputUb, compactOutputUb) <= info.ubSize;
1009+}
1010+ 
1011+static bool PreferKthMergeMoreCore(const SortKthTileInfo& info)
1012+{
1013+ // Splitting every row needs at least two cores per row. Beyond that budget,
1014+ // prefer the existing row-parallel plan when its UB and occupancy checks pass.
1015+ constexpr uint32_t minCoresPerSplitRow = 2U;
1016+ bool canSplitEveryRow = info.unsortedDim <= static_cast<int64_t>(info.maxCoreNum / minCoresPerSplitRow);
1017+ return IsMergeMoreCoreProfitable(info.dataType, info.lastAxis, info.unsortedDim, info.maxCoreNum) &&
1018+ (canSplitEveryRow ||
1019+ !IsMergeIntraCoreSupported(info.dataType, info.lastAxis, info.unsortedDim, info.maxCoreNum, info.ubSize));
1020+}
1021+ 
823static bool TryMerge(gert::TilingContext* context, SortKthTileInfo& info, KthValueTilingData* tilingData,1022static bool TryMerge(gert::TilingContext* context, SortKthTileInfo& info, KthValueTilingData* tilingData,
824 uint32_t& blockDim, uint64_t& schId)1023 uint32_t& blockDim, uint64_t& schId)
825{1024{
826- if (IsMergeSortSupported(info.dataType, info.lastAxis)) {1025+ // Dispatch, compact extraction and GatherKthInt16 implement signed int16 only.
1026+ // uint16 needs its own dispatch/cast validation; byte types already have a one-byte/narrow route.
1027+ // int16 is exactly representable in float. Batch medium short rows through
1028+ // the merge hardware instead of paying two radix passes and scalar waits per row.
1029+ bool useInt16Merge = info.dataType == ge::DT_INT16 && info.lastAxis > KthRoutePolicy::INT16_MERGE_MIN_AXIS &&
1030+ info.lastAxis <= KthRoutePolicy::INT16_MERGE_MAX_AXIS &&
1031+ info.unsortedDim >= static_cast<int64_t>(info.maxCoreNum);
1032+ if (useInt16Merge || IsMergeSortSupported(info.dataType, info.lastAxis)) {
827 SortKthTileInfo candidate = info;1033 SortKthTileInfo candidate = info;
828- if (SetKthValueMergeSortTiling(context, candidate, blockDim, schId) == ge::GRAPH_SUCCESS) {1034+ if (SetKthValueMergeSortTiling(context, candidate, blockDim, schId,
1035+ tilingData->medianMode == MEDIAN_MODE_STATIC) == ge::GRAPH_SUCCESS) {
1036+ if (useInt16Merge && !FitsInt16MergeUb(candidate)) {
1037+ return false;
1038+ }
829 info = candidate;1039 info = candidate;
830 PlanToTilingData(info, tilingData);1040 PlanToTilingData(info, tilingData);
831 return true;1041 return true;
832 }1042 }
833 }1043 }
834- if (IsMergeMoreCoreSupported(info.dataType, info.lastAxis, info.unsortedDim, info.maxCoreNum)) {1044+ if (PreferKthMergeMoreCore(info)) {
835 SortKthTileInfo candidate = info;1045 SortKthTileInfo candidate = info;
836 if (SetMergeMoreCoreTiling(context, candidate, blockDim) == ge::GRAPH_SUCCESS) {1046 if (SetMergeMoreCoreTiling(context, candidate, blockDim) == ge::GRAPH_SUCCESS) {
837 info = candidate;1047 info = candidate;
@@ -873,12 +1083,13 @@ static bool TryNonLastSmallAxis(gert::TilingContext* context, const SortKthTileI
873// Route selection and finalization1083// Route selection and finalization
874// =============================================================================1084// =============================================================================
875static ge::graphStatus SelectKthValueRoute(gert::TilingContext* context, SortKthTileInfo& info,1085static ge::graphStatus SelectKthValueRoute(gert::TilingContext* context, SortKthTileInfo& info,
876- KthValueTilingData* tilingData, uint32_t& blockDim, uint64_t& schId)1086+ KthValueTilingData* tilingData, uint32_t& blockDim, uint64_t& schId,
1087+ bool isMedianOp)
877{1088{
878 if (TrySmallAxisShortRankSelect(info, tilingData, blockDim, schId)) {1089 if (TrySmallAxisShortRankSelect(info, tilingData, blockDim, schId)) {
879 return ge::GRAPH_SUCCESS;1090 return ge::GRAPH_SUCCESS;
880 }1091 }
881- if (TrySmallAxis(context, info, tilingData, blockDim, schId)) {1092+ if (TrySmallAxis(context, info, tilingData, blockDim, schId, isMedianOp)) {
882 return ge::GRAPH_SUCCESS;1093 return ge::GRAPH_SUCCESS;
883 }1094 }
884 if (TryNonLastSmallAxis(context, info, tilingData, blockDim, schId)) {1095 if (TryNonLastSmallAxis(context, info, tilingData, blockDim, schId)) {
@@ -890,6 +1101,13 @@ static ge::graphStatus SelectKthValueRoute(gert::TilingContext* context, SortKth
890 "The value of sortAxis must be the last axis or meet no-transpose schedule constraints.");1101 "The value of sortAxis must be the last axis or meet no-transpose schedule constraints.");
891 return ge::GRAPH_FAILED;1102 return ge::GRAPH_FAILED;
892 }1103 }
1104+ // Priority: small/non-last axes, resident select, merge, radix-one,
1105+ // general select, intra-core merge, then radix-more as the fallback.
1106+ if ((IsResidentIntegerSelect(info) || IsResidentFloatSelect(info, tilingData->medianMode) ||
1107+ IsKthFloatSelect(info, tilingData->medianMode)) &&
1108+ TryRadixSelect(context, info, tilingData, blockDim, schId)) {
1109+ return ge::GRAPH_SUCCESS;
1110+ }
893 if (TryMerge(context, info, tilingData, blockDim, schId) || TryRadixOneCore(context, info, tilingData, schId) ||1111 if (TryMerge(context, info, tilingData, blockDim, schId) || TryRadixOneCore(context, info, tilingData, schId) ||
894 TryRadixSelect(context, info, tilingData, blockDim, schId) ||1112 TryRadixSelect(context, info, tilingData, blockDim, schId) ||
895 TryMergeIntraCore(context, info, tilingData, blockDim, schId)) {1113 TryMergeIntraCore(context, info, tilingData, blockDim, schId)) {
@@ -932,7 +1150,8 @@ static ge::graphStatus FinalizeKthValueRoute(gert::TilingContext* context,
932 OP_CHECK_NULL_WITH_CONTEXT(context, userWorkspaceSize);1150 OP_CHECK_NULL_WITH_CONTEXT(context, userWorkspaceSize);
933 if (schId != KTH_VALUE_SCHID_MERGE_MORE_CORE && schId != KTH_VALUE_SCHID_MERGE_INTRA_CORE &&1151 if (schId != KTH_VALUE_SCHID_MERGE_MORE_CORE && schId != KTH_VALUE_SCHID_MERGE_INTRA_CORE &&
934 schId != KTH_VALUE_SCHID_NON_LAST_SMALL_AXIS && schId != KTH_VALUE_SCHID_NON_LAST_SMALL_AXIS_RADIX &&1152 schId != KTH_VALUE_SCHID_NON_LAST_SMALL_AXIS && schId != KTH_VALUE_SCHID_NON_LAST_SMALL_AXIS_RADIX &&
935- schId != KTH_VALUE_SCHID_RADIX_MORE_CORE && schId != KTH_VALUE_SCHID_RADIX_SELECT) {1153+ schId != KTH_VALUE_SCHID_RADIX_MORE_CORE && schId != KTH_VALUE_SCHID_RADIX_SELECT &&
1154+ schId != KTH_VALUE_SCHID_RESIDENT_HISTOGRAM) {
936 userWorkspaceSize[0] = WORK_SPACE_SIZE;1155 userWorkspaceSize[0] = WORK_SPACE_SIZE;
937 }1156 }
938 return ge::GRAPH_SUCCESS;1157 return ge::GRAPH_SUCCESS;
@@ -946,8 +1165,8 @@ static void SetKthValueTilingContext(gert::TilingContext* context, uint64_t schI
946 context->SetBlockDim(blockDim);1165 context->SetBlockDim(blockDim);
947 if (schId == KTH_VALUE_SCHID_SMALL_AXIS_INSERTION || schId == KTH_VALUE_SCHID_SMALL_AXIS_TWO_STAGE ||1166 if (schId == KTH_VALUE_SCHID_SMALL_AXIS_INSERTION || schId == KTH_VALUE_SCHID_SMALL_AXIS_TWO_STAGE ||
948 schId == KTH_VALUE_SCHID_SMALL_AXIS_SHORT_RANK_SELECT || schId == KTH_VALUE_SCHID_RADIX_MORE_CORE ||1167 schId == KTH_VALUE_SCHID_SMALL_AXIS_SHORT_RANK_SELECT || schId == KTH_VALUE_SCHID_RADIX_MORE_CORE ||
949- schId == KTH_VALUE_SCHID_RADIX_SELECT || schId == KTH_VALUE_SCHID_NON_LAST_SMALL_AXIS ||1168+ schId == KTH_VALUE_SCHID_RADIX_SELECT || schId == KTH_VALUE_SCHID_RESIDENT_HISTOGRAM ||
950- schId == KTH_VALUE_SCHID_NON_LAST_SMALL_AXIS_RADIX) {1169+ schId == KTH_VALUE_SCHID_NON_LAST_SMALL_AXIS || schId == KTH_VALUE_SCHID_NON_LAST_SMALL_AXIS_RADIX) {
951 context->SetLocalMemorySize(info.ubSize - SIMT_UB);1170 context->SetLocalMemorySize(info.ubSize - SIMT_UB);
952 } else {1171 } else {
953 context->SetLocalMemorySize(info.ubSize);1172 context->SetLocalMemorySize(info.ubSize);
@@ -1017,6 +1236,44 @@ static ge::graphStatus AddMedianWorkspace(gert::TilingContext* context, const So
1017 return ge::GRAPH_SUCCESS;1236 return ge::GRAPH_SUCCESS;
1018}1237}
1019 1238 
1239+// Serialize the selected layout only after routing and workspace planning have completed.
1240+static void StoreKthValueTiling(gert::TilingContext* context, uint64_t schId, const KthValueTilingData& data)
1241+{
1242+ if (schId == KTH_VALUE_SCHID_MERGE_SORT || schId == KTH_VALUE_SCHID_SORT32_SMALL_AXIS) {
1243+ KthValueMergeOneCoreTilingData compact{};
1244+ compact.kthIndex = data.kthIndex;
1245+ compact.unsortedDimNum = data.unsortedDimNum;
1246+ compact.numTileDataSize = data.numTileDataSize;
1247+ compact.unsortedDimParallel = data.unsortedDimParallel;
1248+ compact.sortLoopTimes = data.sortLoopTimes;
1249+ compact.keyParams0 = data.keyParams0;
1250+ compact.keyParams1 = data.keyParams1;
1251+ compact.keyParams2 = data.keyParams2;
1252+ compact.keyParams3 = data.keyParams3;
1253+ compact.keyParams4 = data.keyParams4;
1254+ compact.medianMode = data.medianMode;
1255+ // Capacity was already checked for the larger common layout.
1256+ *context->GetTilingData<KthValueMergeOneCoreTilingData>() = compact;
1257+ } else if (schId == KTH_VALUE_SCHID_RADIX_ONE_CORE) {
1258+ KthValueRadixOneCoreTilingData compact{};
1259+ compact.kthIndex = data.kthIndex;
1260+ compact.lastAxisNum = data.lastAxisNum;
1261+ compact.unsortedDimNum = data.unsortedDimNum;
1262+ compact.numTileDataSize = data.numTileDataSize;
1263+ compact.unsortedDimParallel = data.unsortedDimParallel;
1264+ compact.keyParams0 = data.keyParams0;
1265+ compact.keyParams1 = data.keyParams1;
1266+ compact.keyParams3 = data.keyParams3;
1267+ compact.keyParams4 = data.keyParams4;
1268+ compact.tmpUbSize = data.tmpUbSize;
1269+ compact.medianMode = data.medianMode;
1270+ // Capacity was already checked for the larger common layout.
1271+ *context->GetTilingData<KthValueRadixOneCoreTilingData>() = compact;
1272+ } else {
1273+ *context->GetTilingData<KthValueTilingData>() = data;
1274+ }
1275+}
1276+ 
1020static ge::graphStatus SelectAndFinalizeKthValueRoute(gert::TilingContext* context,1277static ge::graphStatus SelectAndFinalizeKthValueRoute(gert::TilingContext* context,
1021 const platform_ascendc::PlatformAscendC& ascendcPlatform,1278 const platform_ascendc::PlatformAscendC& ascendcPlatform,
1022 SortKthTileInfo& info, KthValueTilingData* tilingData,1279 SortKthTileInfo& info, KthValueTilingData* tilingData,
@@ -1025,10 +1282,11 @@ static ge::graphStatus SelectAndFinalizeKthValueRoute(gert::TilingContext* conte
1025 KthValueTilingData candidateTilingData = *tilingData;1282 KthValueTilingData candidateTilingData = *tilingData;
1026 uint64_t schId = KTH_VALUE_SCHID_RADIX_MORE_CORE;1283 uint64_t schId = KTH_VALUE_SCHID_RADIX_MORE_CORE;
1027 uint32_t blockDim = 1;1284 uint32_t blockDim = 1;
1028- OP_CHECK_IF((SelectKthValueRoute(context, info, &candidateTilingData, blockDim, schId) != ge::GRAPH_SUCCESS),1285+ OP_CHECK_IF(
1029- OP_LOGE_FOR_INVALID_VALUE_WITH_REASON(context->GetNodeName(), "SelectKthValueRoute", "GRAPH_FAILED",1286+ (SelectKthValueRoute(context, info, &candidateTilingData, blockDim, schId, isMedianOp) != ge::GRAPH_SUCCESS),
1030- "The value of SelectKthValueRoute must be GRAPH_SUCCESS."),1287+ OP_LOGE_FOR_INVALID_VALUE_WITH_REASON(context->GetNodeName(), "SelectKthValueRoute", "GRAPH_FAILED",
1031- return ge::GRAPH_FAILED);1288+ "The value of SelectKthValueRoute must be GRAPH_SUCCESS."),
1289+ return ge::GRAPH_FAILED);
1032 OP_CHECK_IF((FinalizeKthValueRoute(context, ascendcPlatform, info, &candidateTilingData, schId, blockDim) !=1290 OP_CHECK_IF((FinalizeKthValueRoute(context, ascendcPlatform, info, &candidateTilingData, schId, blockDim) !=
1033 ge::GRAPH_SUCCESS),1291 ge::GRAPH_SUCCESS),
1034 OP_LOGE_FOR_INVALID_VALUE_WITH_REASON(context->GetNodeName(), "FinalizeKthValueRoute", "GRAPH_FAILED",1292 OP_LOGE_FOR_INVALID_VALUE_WITH_REASON(context->GetNodeName(), "FinalizeKthValueRoute", "GRAPH_FAILED",
@@ -1039,7 +1297,7 @@ static ge::graphStatus SelectAndFinalizeKthValueRoute(gert::TilingContext* conte
1039 OP_LOGE_FOR_INVALID_VALUE_WITH_REASON(context->GetNodeName(), "AddMedianWorkspace", "GRAPH_FAILED",1297 OP_LOGE_FOR_INVALID_VALUE_WITH_REASON(context->GetNodeName(), "AddMedianWorkspace", "GRAPH_FAILED",
1040 "The value of AddMedianWorkspace must be GRAPH_SUCCESS."),1298 "The value of AddMedianWorkspace must be GRAPH_SUCCESS."),
1041 return ge::GRAPH_FAILED);1299 return ge::GRAPH_FAILED);
1042- *tilingData = candidateTilingData;1300+ StoreKthValueTiling(context, schId, candidateTilingData);
1043 OP_LOGI(context->GetNodeName(),1301 OP_LOGI(context->GetNodeName(),
1044 "KthValueTiling: schId=%lu, blockDim=%u, lastAxis=%ld, unsortedDim=%ld, "1302 "KthValueTiling: schId=%lu, blockDim=%u, lastAxis=%ld, unsortedDim=%ld, "
1045 "isNonLastAxis=%d, dtypeSize=%u",1303 "isNonLastAxis=%d, dtypeSize=%u",
@@ -10,6 +10,7 @@
10 10 
11#include "kth_value_tiling_common.h"11#include "kth_value_tiling_common.h"
12 12 
13+#include <algorithm>
13#include <cmath>14#include <cmath>
14#include <functional>15#include <functional>
15#include <string>16#include <string>
@@ -267,8 +268,14 @@ bool GetNonLastSortTmpSize(ge::DataType dataType, uint32_t sortCount, bool useMe
267 uint32_t minValue = 0;268 uint32_t minValue = 0;
268 AscendC::GetSortMaxMinTmpSize(srcShape, GetNonLastSortDtype(dataType, useMergeSort), ge::DT_UINT32, true, config,269 AscendC::GetSortMaxMinTmpSize(srcShape, GetNonLastSortDtype(dataType, useMergeSort), ge::DT_UINT32, true, config,
269 maxValue, minValue);270 maxValue, minValue);
270- tmpUbSize = maxValue;271+ // The unified merge path reuses tmp as the pong proposal buffer. Each proposal is
271- return maxValue > 0;272+ // value/index packed into 8 bytes, independent of the value storage dtype.
273+ uint64_t proposalBytes = static_cast<uint64_t>(sortCount) * MERGE_SORT_DATA_BYTES;
274+ if (proposalBytes > static_cast<uint64_t>(UINT32_MAX)) {
275+ return false;
276+ }
277+ tmpUbSize = useMergeSort ? std::max(maxValue, static_cast<uint32_t>(proposalBytes)) : maxValue;
278+ return tmpUbSize > 0;
272}279}
273 280 
274bool SearchNonLastSmallAxisPlan(281bool SearchNonLastSmallAxisPlan(
@@ -410,7 +417,8 @@ static bool AdjustBSharedSingleHTile(int64_t axisLen, int64_t unsortedDim, uint3
410 if (hCore == 0U) {417 if (hCore == 0U) {
411 return false;418 return false;
412 }419 }
413- uint64_t hTileData64 = static_cast<uint64_t>(axisLen) / hCore;420+ // Cover the full axis with hCore tiles. Floor division may introduce an extra tail tile after alignment.
421+ uint64_t hTileData64 = Ops::Base::CeilDiv(static_cast<uint64_t>(axisLen), static_cast<uint64_t>(hCore));
414 if (hTileData64 > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {422 if (hTileData64 > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
415 return false;423 return false;
416 }424 }
@@ -427,7 +435,8 @@ static bool AdjustMultiTileHWithBSharing(int64_t axisLen, int64_t unsortedDim, u
427 uint32_t& tileData, uint32_t lastDimTileNum, uint32_t& tmpUbSize,435 uint32_t& tileData, uint32_t lastDimTileNum, uint32_t& tmpUbSize,
428 bool& adjusted)436 bool& adjusted)
429{437{
430- uint64_t newTileData64 = static_cast<uint64_t>(axisLen) / static_cast<uint64_t>(lastDimTileNum);438+ // Keep the adjusted split within lastDimTileNum; floor division can recreate a one-element tail tile.
439+ uint64_t newTileData64 = Ops::Base::CeilDiv(static_cast<uint64_t>(axisLen), static_cast<uint64_t>(lastDimTileNum));
431 if (!CeilAlignUint32(newTileData64, BIN_NUM, tileData)) {440 if (!CeilAlignUint32(newTileData64, BIN_NUM, tileData)) {
432 return false;441 return false;
433 }442 }
@@ -706,14 +715,57 @@ bool ComputeMergeSortPlan(int64_t axisLen, int64_t unsortedDim, uint32_t blockUb
706 return true;715 return true;
707}716}
708 717 
709-bool FillMergeSortInfo(SortKthTileInfo& info, uint32_t indexDtypeSize, uint32_t concatTmpSize)718+static bool ComputeMergeSortBatchCapacity(const SortKthTileInfo& info, uint32_t indexDtypeSize, uint32_t& tileDataNum)
710{719{
711- constexpr uint32_t TILE_DATA_NUM = 4096;720+ if (info.lastAxis <= 0 || info.blockUbSize == 0U || info.dtypeSize == 0U || indexDtypeSize == 0U) {
712- MergeSortPlan plan;
713- if (!ComputeMergeSortPlan(info.lastAxis, info.unsortedDim, info.blockUbSize, TILE_DATA_NUM, info.maxCoreNum,
714- plan)) {
715 return false;721 return false;
716 }722 }
723+ uint64_t alignedAxis = Ops::Base::CeilAlign(static_cast<uint64_t>(info.lastAxis),
724+ static_cast<uint64_t>(info.blockUbSize));
725+ uint32_t bufferNum = info.lastAxis > ONE_CORE_DATA_SIZE ? 1U : DOUBLE_BUFFER_NUM;
726+ // Both kernels keep one index vector and two 8-byte proposal buffers per row axis.
727+ uint64_t fixedBytes = alignedAxis * (sizeof(uint32_t) + 2U * MERGE_SORT_DATA_BYTES);
728+ uint32_t castBytes = (info.dataType == ge::DT_BF16 || info.dataType == ge::DT_INT16) ? sizeof(float) : 0U;
729+ uint64_t rowBytes = alignedAxis * ((2U * info.dtypeSize + indexDtypeSize) * bufferNum + castBytes);
730+ rowBytes += (info.dtypeSize + sizeof(int64_t)) * bufferNum;
731+ // KthValue additionally aligns two compact output queues independently per batch.
732+ fixedBytes += 2U * bufferNum * (info.blockUbSize - 1U);
733+ if (fixedBytes >= info.ubSize || rowBytes == 0U) {
734+ return false;
735+ }
736+ uint64_t rows = (info.ubSize - fixedBytes) / rowBytes;
737+ // ComputeMergeSortPlan consumes half of tileDataNum as the batch element budget.
738+ uint64_t tileElements = rows * alignedAxis * 2U;
739+ if (rows == 0U || tileElements > std::numeric_limits<uint32_t>::max()) {
740+ return false;
741+ }
742+ tileDataNum = static_cast<uint32_t>(tileElements);
743+ return true;
744+}
745+ 
746+bool FillMergeSortInfo(SortKthTileInfo& info, uint32_t indexDtypeSize, bool useUbCapacity)
747+{
748+ // The fixed budget keeps small Sort batches pipelined; the UB-derived budget also
749+ // rebalances rows across rounds. Switching policies is not only a capacity check.
750+ constexpr uint32_t SORT_TILE_DATA_NUM = 4096;
751+ uint32_t tileDataNum = SORT_TILE_DATA_NUM;
752+ if (useUbCapacity && !ComputeMergeSortBatchCapacity(info, indexDtypeSize, tileDataNum)) {
753+ return false;
754+ }
755+ MergeSortPlan plan;
756+ if (!ComputeMergeSortPlan(info.lastAxis, info.unsortedDim, info.blockUbSize, tileDataNum, info.maxCoreNum, plan)) {
757+ return false;
758+ }
759+ if (useUbCapacity) {
760+ // Balance the per-core row budget instead of leaving an oversized final batch on a few cores.
761+ uint64_t rowsPerCore = Ops::Base::CeilDiv(static_cast<uint64_t>(info.unsortedDim),
762+ static_cast<uint64_t>(plan.coreNumNeed));
763+ uint64_t rounds = Ops::Base::CeilDiv(rowsPerCore, static_cast<uint64_t>(plan.oneCoreRowNum));
764+ plan.oneCoreRowNum = static_cast<uint32_t>(Ops::Base::CeilDiv(rowsPerCore, rounds));
765+ uint64_t rowsPerRound = static_cast<uint64_t>(plan.coreNumNeed) * plan.oneCoreRowNum;
766+ plan.sortLoopTimes = static_cast<uint32_t>(
767+ Ops::Base::CeilDiv(static_cast<uint64_t>(info.unsortedDim), rowsPerRound));
768+ }
717 info.sortLoopTimes = plan.sortLoopTimes;769 info.sortLoopTimes = plan.sortLoopTimes;
718 info.lastDimTileNum = 1;770 info.lastDimTileNum = 1;
719 info.unsortedDimParallel = plan.coreNumNeed;771 info.unsortedDimParallel = plan.coreNumNeed;
@@ -725,24 +777,15 @@ bool FillMergeSortInfo(SortKthTileInfo& info, uint32_t indexDtypeSize, uint32_t
725 info.keyParams2 = plan.alignNum * plan.oneCoreRowNum * indexDtypeSize;777 info.keyParams2 = plan.alignNum * plan.oneCoreRowNum * indexDtypeSize;
726 info.keyParams3 = plan.alignNum;778 info.keyParams3 = plan.alignNum;
727 info.keyParams4 = info.lastAxis > ONE_CORE_DATA_SIZE ? 1 : DOUBLE_BUFFER_NUM;779 info.keyParams4 = info.lastAxis > ONE_CORE_DATA_SIZE ? 1 : DOUBLE_BUFFER_NUM;
728- info.tmpUbSize = std::max(concatTmpSize, info.blockUbSize);780+ info.tmpUbSize = 0;
729 info.workspaceSize = WORK_SPACE_SIZE;781 info.workspaceSize = WORK_SPACE_SIZE;
730 return true;782 return true;
731}783}
732 784 
733-bool ComputeMergeSortTiling(gert::TilingContext* context, SortKthTileInfo& info, uint32_t indexDtypeSize)785+bool ComputeMergeSortTiling(gert::TilingContext* context, SortKthTileInfo& info, uint32_t indexDtypeSize,
786+ bool useUbCapacity)
734{787{
735- constexpr uint32_t TILE_DATA_NUM = 4096;788+ if (!FillMergeSortInfo(info, indexDtypeSize, useUbCapacity)) {
736- MergeSortPlan plan;
737- if (!ComputeMergeSortPlan(info.lastAxis, info.unsortedDim, info.blockUbSize, TILE_DATA_NUM, info.maxCoreNum,
738- plan)) {
739- return false;
740- }
741- auto platformInfo = context->GetPlatformInfo();
742- auto plat = platform_ascendc::PlatformAscendC(platformInfo);
743- uint32_t maxTypeSize = (info.dataType == ge::DT_BF16) ? static_cast<uint32_t>(sizeof(float)) : info.dtypeSize;
744- uint32_t concatTmpSize = AscendC::GetConcatTmpSize(plat, plan.alignNum, maxTypeSize);
745- if (!FillMergeSortInfo(info, indexDtypeSize, concatTmpSize)) {
746 return false;789 return false;
747 }790 }
748 size_t* userWorkSpaceSize = context->GetWorkspaceSizes(1);791 size_t* userWorkSpaceSize = context->GetWorkspaceSizes(1);
@@ -753,51 +796,167 @@ bool ComputeMergeSortTiling(gert::TilingContext* context, SortKthTileInfo& info,
753// =============================================================================796// =============================================================================
754// Merge sort — multi-core (more-core)797// Merge sort — multi-core (more-core)
755// =============================================================================798// =============================================================================
756-bool IsMergeMoreCoreSupported(ge::DataType dataType, int64_t axisLen, int64_t unsortedDim, uint32_t maxCoreNum)799+// Select per-core data chunk size (2048 or 4096) for merge-more-core. Prefers the smaller base size when direct
800+// parallel fits within maxCoreNum; otherwise falls back to the larger size to reduce horizontal core count.
801+bool SelectMergeMoreCoreDataSize(int64_t axisLen, int64_t unsortedDim, uint32_t maxCoreNum, uint32_t& dataSize)
757{802{
758- if (dataType != ge::DT_FLOAT || axisLen <= static_cast<int64_t>(MERGE_SORT_MAX_AXIS_FP32) ||803+ if (axisLen <= 0 || unsortedDim <= 0 || maxCoreNum == 0U) {
759- axisLen > static_cast<int64_t>(MULTI_CORE_MERGE_SORT_MAX_AXIS)) {
760 return false;804 return false;
761 }805 }
762- uint64_t hCoreNum = (static_cast<uint64_t>(axisLen) + ONE_CORE_DATA_SIZE - 1U) / ONE_CORE_DATA_SIZE;806+ uint32_t baseHCoreNum;
763- return hCoreNum > 0 && static_cast<uint64_t>(unsortedDim) * hCoreNum <= maxCoreNum;807+ if (!CeilDivUint32(static_cast<uint64_t>(axisLen), MERGE_MORE_CORE_DATA_SIZE_BASE, baseHCoreNum) ||
808+ baseHCoreNum == 0U || baseHCoreNum > maxCoreNum) {
809+ return false;
810+ }
811+ if (static_cast<uint64_t>(unsortedDim) * baseHCoreNum <= maxCoreNum) {
812+ dataSize = MERGE_MORE_CORE_DATA_SIZE_BASE;
813+ return true;
814+ }
815+ uint32_t largeHCoreNum;
816+ if (!CeilDivUint32(static_cast<uint64_t>(axisLen), MERGE_MORE_CORE_DATA_SIZE_LARGE, largeHCoreNum) ||
817+ largeHCoreNum == 0U || largeHCoreNum > maxCoreNum) {
818+ return false;
819+ }
820+ dataSize = MERGE_MORE_CORE_DATA_SIZE_LARGE;
821+ return true;
764}822}
765 823 
766-bool ComputeMergeMoreCorePlan(int64_t axisLen, int64_t unsortedDim, uint32_t ubSize, uint32_t mergeBytesPerElem,824+// Select a sync-merge block size (aligned to 2048) that groups multiple 4096-element chunks into one block,
767- MergeMoreCorePlan& plan)825+// reducing horizontal core count so that unsortedDim * hCoreNum fits within maxCoreNum.
826+bool SelectMergeSyncMergeBlockSize(int64_t axisLen, int64_t unsortedDim, uint32_t maxCoreNum, uint32_t& blockSize)
827+{
828+ if (axisLen <= static_cast<int64_t>(MERGE_MORE_CORE_DATA_SIZE_LARGE) || unsortedDim <= 0 || maxCoreNum == 0U) {
829+ return false;
830+ }
831+ uint32_t maxHCoreByCore = static_cast<uint32_t>(static_cast<uint64_t>(maxCoreNum) /
832+ static_cast<uint64_t>(unsortedDim));
833+ uint32_t rawChunkNum = 0;
834+ if (!CeilDivUint32(static_cast<uint64_t>(axisLen), MERGE_MORE_CORE_DATA_SIZE_LARGE, rawChunkNum) ||
835+ rawChunkNum <= 1U || maxHCoreByCore <= 1U) {
836+ return false;
837+ }
838+ // A sync-merge block is larger than one 4096-element Sort block. Use the available per-row core budget,
839+ // capped below the direct 4096 split, then align to the existing 2048-element more-core granularity.
840+ uint32_t targetHCore = std::min(rawChunkNum - 1U, maxHCoreByCore);
841+ if (targetHCore <= 1U) {
842+ return false;
843+ }
844+ uint32_t candidate = 0;
845+ if (!CeilAlignUint32(Ops::Base::CeilDiv(static_cast<uint64_t>(axisLen), static_cast<uint64_t>(targetHCore)),
846+ MERGE_MORE_CORE_DATA_SIZE_BASE, candidate) ||
847+ candidate <= MERGE_MORE_CORE_DATA_SIZE_LARGE) {
848+ return false;
849+ }
850+ uint32_t hCoreNum = 0;
851+ if (!CeilDivUint32(static_cast<uint64_t>(axisLen), candidate, hCoreNum) || hCoreNum <= 1U ||
852+ hCoreNum > maxCoreNum ||
853+ static_cast<uint64_t>(unsortedDim) * static_cast<uint64_t>(hCoreNum) > static_cast<uint64_t>(maxCoreNum)) {
854+ return false;
855+ }
856+ blockSize = candidate;
857+ return true;
858+}
859+ 
860+bool IsMergeMoreCoreProfitable(ge::DataType dataType, int64_t axisLen, int64_t unsortedDim, uint32_t maxCoreNum)
861+{
862+ if (dataType != ge::DT_FLOAT || axisLen <= static_cast<int64_t>(MERGE_SORT_MAX_AXIS_FP32) ||
863+ axisLen > static_cast<int64_t>(MULTI_CORE_MERGE_SORT_MAX_AXIS) || unsortedDim <= 0 || maxCoreNum == 0U) {
864+ return false;
865+ }
866+ uint32_t dataSize;
867+ if (!SelectMergeMoreCoreDataSize(axisLen, unsortedDim, maxCoreNum, dataSize)) {
868+ return false;
869+ }
870+ uint64_t hCoreNum = (static_cast<uint64_t>(axisLen) + dataSize - 1U) / dataSize;
871+ if (static_cast<uint64_t>(unsortedDim) * hCoreNum <= maxCoreNum) {
872+ return true;
873+ }
874+ uint32_t syncBlockSize = 0;
875+ if (SelectMergeSyncMergeBlockSize(axisLen, unsortedDim, maxCoreNum, syncBlockSize)) {
876+ return true;
877+ }
878+ return hCoreNum >= MERGE_MORE_CORE_MIN_CORES_PER_ROW_FOR_MULTI_ROUND;
879+}
880+ 
881+namespace {
882+ 
883+bool BuildMergeMoreCoreRoundPlan(int64_t axisLen, int64_t unsortedDim, uint32_t ubSize, uint32_t maxCoreNum,
黄
黄黄晓彬9月17日

【可维护性】新增 BuildMergeMoreCoreRoundPlan 有 9 个参数,其中 ubSize、maxCoreNum、mergeBytesPerElem、hCoreNum、numTileDataSize、syncBlockSize 是连续的 uint32_t,调用处容易发生顺序错置且编译器无法区分。

建议把有关联的输入聚合成计划参数结构体,保留 plan 作为输出,减少调用方的位置参数负担。这是接口可读性建议,不是已确认的参数错传,也未假定仓库 CodeCheck 的参数数量阈值。

likedislike
黄晓彬
9月17日 评论:
884+ uint32_t mergeBytesPerElem, uint32_t hCoreNum, uint32_t numTileDataSize,
885+ uint32_t syncBlockSize, MergeMoreCorePlan& plan)
886+{
887+ uint64_t rowsPerRound64 = std::min(static_cast<uint64_t>(maxCoreNum) / hCoreNum,
888+ static_cast<uint64_t>(unsortedDim));
889+ if (rowsPerRound64 == 0U || rowsPerRound64 > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
890+ return false;
891+ }
892+ uint64_t coreNumNeed64 = rowsPerRound64 * hCoreNum;
893+ uint64_t sortLoopTimes64 = Ops::Base::CeilDiv(static_cast<uint64_t>(unsortedDim), rowsPerRound64);
894+ if (static_cast<uint64_t>(axisLen) > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) ||
895+ static_cast<uint64_t>(unsortedDim) > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) ||
896+ coreNumNeed64 > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) ||
897+ sortLoopTimes64 > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
898+ return false;
899+ }
900+ plan.lastDimTileNum = static_cast<uint32_t>(axisLen);
901+ plan.lastDimNeedCore = hCoreNum;
902+ plan.numTileDataSize = numTileDataSize;
903+ plan.unsortedDimParallel = static_cast<uint32_t>(rowsPerRound64);
904+ plan.sortLoopTimes = static_cast<uint32_t>(sortLoopTimes64);
905+ plan.coreNumNeed = static_cast<uint32_t>(coreNumNeed64);
906+ plan.keyParams0 = ubSize / mergeBytesPerElem;
907+ plan.keyParams1 = syncBlockSize;
908+ return true;
909+}
910+ 
911+bool BuildMergeSyncBlockPlan(int64_t axisLen, int64_t unsortedDim, uint32_t ubSize, uint32_t maxCoreNum,
912+ uint32_t mergeBytesPerElem, uint32_t syncBlockSize, MergeMoreCorePlan& plan)
913+{
914+ uint32_t syncHCoreNum = 0;
915+ if (!CeilDivUint32(static_cast<uint64_t>(axisLen), syncBlockSize, syncHCoreNum) || syncHCoreNum <= 1U ||
916+ syncHCoreNum > maxCoreNum) {
917+ return false;
918+ }
919+ return BuildMergeMoreCoreRoundPlan(axisLen, unsortedDim, ubSize, maxCoreNum, mergeBytesPerElem, syncHCoreNum,
920+ MERGE_MORE_CORE_DATA_SIZE_LARGE, syncBlockSize, plan);
921+}
922+ 
923+} // namespace
924+ 
925+// Try direct, sync-block, then multi-round scheduling in that order.
926+bool ComputeMergeMoreCorePlan(int64_t axisLen, int64_t unsortedDim, uint32_t ubSize, uint32_t maxCoreNum,
927+ uint32_t mergeBytesPerElem, MergeMoreCorePlan& plan)
768{928{
769 if (axisLen <= 0 || unsortedDim <= 0 || mergeBytesPerElem == 0U) {929 if (axisLen <= 0 || unsortedDim <= 0 || mergeBytesPerElem == 0U) {
770 return false;930 return false;
771 }931 }
772- uint32_t hCoreNum;932+ uint32_t dataSize = 0;
773- if (!CeilDivUint32(static_cast<uint64_t>(axisLen), ONE_CORE_DATA_SIZE, hCoreNum)) {933+ uint32_t hCoreNum = 0;
934+ if (!SelectMergeMoreCoreDataSize(axisLen, unsortedDim, maxCoreNum, dataSize) ||
935+ !CeilDivUint32(static_cast<uint64_t>(axisLen), dataSize, hCoreNum) || hCoreNum == 0U || hCoreNum > maxCoreNum) {
774 return false;936 return false;
775 }937 }
776- uint64_t coreNumNeed64 = static_cast<uint64_t>(unsortedDim) * hCoreNum;938+ bool directFit = static_cast<uint64_t>(unsortedDim) * hCoreNum <= maxCoreNum;
777- if (static_cast<uint64_t>(axisLen) > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) ||939+ uint32_t syncBlockSize = 0;
778- static_cast<uint64_t>(unsortedDim) > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) ||940+ if (!directFit && SelectMergeSyncMergeBlockSize(axisLen, unsortedDim, maxCoreNum, syncBlockSize)) {
779- coreNumNeed64 > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {941+ return BuildMergeSyncBlockPlan(axisLen, unsortedDim, ubSize, maxCoreNum, mergeBytesPerElem, syncBlockSize,
942+ plan);
943+ }
944+ if (!directFit && hCoreNum < MERGE_MORE_CORE_MIN_CORES_PER_ROW_FOR_MULTI_ROUND) {
780 return false;945 return false;
781 }946 }
782 uint64_t numTileDataSize64 = static_cast<uint64_t>(axisLen) / hCoreNum;947 uint64_t numTileDataSize64 = static_cast<uint64_t>(axisLen) / hCoreNum;
783 if (numTileDataSize64 > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {948 if (numTileDataSize64 > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
784 return false;949 return false;
785 }950 }
786- plan.lastDimTileNum = static_cast<uint32_t>(static_cast<uint64_t>(axisLen));951+ return BuildMergeMoreCoreRoundPlan(axisLen, unsortedDim, ubSize, maxCoreNum, mergeBytesPerElem, hCoreNum,
787- plan.lastDimNeedCore = hCoreNum;952+ static_cast<uint32_t>(numTileDataSize64), 0U, plan);
788- plan.numTileDataSize = static_cast<uint32_t>(numTileDataSize64);
789- plan.unsortedDimParallel = static_cast<uint32_t>(static_cast<uint64_t>(unsortedDim));
790- plan.sortLoopTimes = 1;
791- plan.coreNumNeed = static_cast<uint32_t>(coreNumNeed64);
792- // keyParams0 is max elements handled per UB round by the merge more-core pipeline.
793- plan.keyParams0 = ubSize / mergeBytesPerElem;
794- return true;
795}953}
796 954 
797bool FillMergeMoreCoreInfo(SortKthTileInfo& info, uint32_t mergeBytesPerElem)955bool FillMergeMoreCoreInfo(SortKthTileInfo& info, uint32_t mergeBytesPerElem)
798{956{
799 MergeMoreCorePlan plan;957 MergeMoreCorePlan plan;
800- if (!ComputeMergeMoreCorePlan(info.lastAxis, info.unsortedDim, info.ubSize, mergeBytesPerElem, plan)) {958+ if (!ComputeMergeMoreCorePlan(info.lastAxis, info.unsortedDim, info.ubSize, info.maxCoreNum, mergeBytesPerElem,
959+ plan)) {
801 return false;960 return false;
802 }961 }
803 info.lastDimTileNum = plan.lastDimTileNum;962 info.lastDimTileNum = plan.lastDimTileNum;
@@ -807,6 +966,7 @@ bool FillMergeMoreCoreInfo(SortKthTileInfo& info, uint32_t mergeBytesPerElem)
807 info.sortLoopTimes = plan.sortLoopTimes;966 info.sortLoopTimes = plan.sortLoopTimes;
808 info.coreNumNeed = plan.coreNumNeed;967 info.coreNumNeed = plan.coreNumNeed;
809 info.keyParams0 = plan.keyParams0;968 info.keyParams0 = plan.keyParams0;
969+ info.keyParams1 = plan.keyParams1;
810 uint64_t wsBytes = static_cast<uint64_t>(MERGE_SORT_WORKSPACE_PARAM) * info.lastAxis * info.unsortedDim *970 uint64_t wsBytes = static_cast<uint64_t>(MERGE_SORT_WORKSPACE_PARAM) * info.lastAxis * info.unsortedDim *
811 sizeof(int32_t);971 sizeof(int32_t);
812 info.workspaceSize = static_cast<size_t>(wsBytes + WORK_SPACE_SIZE);972 info.workspaceSize = static_cast<size_t>(wsBytes + WORK_SPACE_SIZE);
@@ -834,6 +994,19 @@ uint32_t ComputeMergeIntraCoreBlockSortSize(uint32_t ubSize)
834 return (blockSortSize / MERGE_INTRA_CORE_SORT_ALIGN) * MERGE_INTRA_CORE_SORT_ALIGN;994 return (blockSortSize / MERGE_INTRA_CORE_SORT_ALIGN) * MERGE_INTRA_CORE_SORT_ALIGN;
835}995}
836 996 
997+// Max elements a single core can sort in one pass (single-block mode).
998+// Constrained by the tighter of phase1 (initial sort) and phase3 (extract/merge) UB capacity.
999+uint32_t ComputeMergeIntraCoreSingleBlockSortSize(uint32_t ubSize)
1000+{
1001+ constexpr uint32_t MERGE_INTRA_PHASE_BUFFER_NUM = 2;
1002+ constexpr uint32_t PHASE1_BYTES_PER_ELEM = MERGE_INTRA_PHASE_BUFFER_NUM * sizeof(float) + SORT_STRUCT_BYTES +
1003+ MERGE_INTRA_PHASE_BUFFER_NUM * SORT_STRUCT_BYTES + sizeof(uint32_t);
1004+ uint32_t phase1Size = ubSize / PHASE1_BYTES_PER_ELEM;
1005+ uint32_t phase3Size = ComputeMergeIntraCoreExtractChunkSize(ubSize);
1006+ uint32_t singleBlockSize = std::min(phase1Size, phase3Size);
1007+ return (singleBlockSize / MERGE_INTRA_CORE_SORT_ALIGN) * MERGE_INTRA_CORE_SORT_ALIGN;
1008+}
1009+ 
837uint32_t ComputeMergeIntraCoreExtractChunkSize(uint32_t ubSize)1010uint32_t ComputeMergeIntraCoreExtractChunkSize(uint32_t ubSize)
838{1011{
839 constexpr uint32_t PHASE3_BYTES_PER_ELEM = (SORT_STRUCT_BYTES + sizeof(float) + sizeof(int32_t) + sizeof(int64_t)) *1012 constexpr uint32_t PHASE3_BYTES_PER_ELEM = (SORT_STRUCT_BYTES + sizeof(float) + sizeof(int32_t) + sizeof(int64_t)) *
@@ -854,11 +1027,13 @@ bool IsMergeIntraCoreSupported(ge::DataType dataType, int64_t axisLen, int64_t u
854 return false;1027 return false;
855 }1028 }
856 uint32_t blockSortSize = ComputeMergeIntraCoreBlockSortSize(ubSize);1029 uint32_t blockSortSize = ComputeMergeIntraCoreBlockSortSize(ubSize);
1030+ uint32_t singleBlockSortSize = ComputeMergeIntraCoreSingleBlockSortSize(ubSize);
857 uint32_t extractChunkSize = ComputeMergeIntraCoreExtractChunkSize(ubSize);1031 uint32_t extractChunkSize = ComputeMergeIntraCoreExtractChunkSize(ubSize);
858- if (blockSortSize == 0U || extractChunkSize == 0U) {1032+ if (blockSortSize == 0U || singleBlockSortSize == 0U || extractChunkSize == 0U) {
859 return false;1033 return false;
860 }1034 }
861- return axisLen <= static_cast<int64_t>(blockSortSize) * MERGE_INTRA_CORE_MAX_BLOCKS;1035+ return axisLen <= static_cast<int64_t>(singleBlockSortSize) ||
1036+ axisLen <= static_cast<int64_t>(blockSortSize) * MERGE_INTRA_CORE_MAX_BLOCKS;
862}1037}
863 1038 
864bool ComputeMergeIntraCorePlan(int64_t axisLen, int64_t unsortedDim, uint32_t ubSize, uint32_t maxCoreNum,1039bool ComputeMergeIntraCorePlan(int64_t axisLen, int64_t unsortedDim, uint32_t ubSize, uint32_t maxCoreNum,
@@ -877,14 +1052,26 @@ bool ComputeMergeIntraCorePlan(int64_t axisLen, int64_t unsortedDim, uint32_t ub
877 return false;1052 return false;
878 }1053 }
879 plan.actualCoreNum = static_cast<uint32_t>(actualCoreNum64);1054 plan.actualCoreNum = static_cast<uint32_t>(actualCoreNum64);
880- plan.blockSortSize = ComputeMergeIntraCoreBlockSortSize(ubSize);
881- if (plan.blockSortSize == 0U) {
882- return false;
883- }
884 plan.extractChunkSize = ComputeMergeIntraCoreExtractChunkSize(ubSize);1055 plan.extractChunkSize = ComputeMergeIntraCoreExtractChunkSize(ubSize);
885 if (plan.extractChunkSize == 0U) {1056 if (plan.extractChunkSize == 0U) {
886 return false;1057 return false;
887 }1058 }
1059+ uint32_t singleBlockSortSize = ComputeMergeIntraCoreSingleBlockSortSize(ubSize);
1060+ if (axisLen <= static_cast<int64_t>(singleBlockSortSize)) {
1061+ uint64_t blockSortSize64 = Ops::Base::CeilAlign(static_cast<uint64_t>(axisLen),
1062+ static_cast<uint64_t>(MERGE_INTRA_CORE_SORT_ALIGN));
1063+ if (blockSortSize64 == 0U || blockSortSize64 > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
1064+ return false;
1065+ }
1066+ plan.blockSortSize = static_cast<uint32_t>(blockSortSize64);
1067+ plan.blocksPerRow = 1;
1068+ plan.alignNum = plan.blockSortSize;
1069+ return true;
1070+ }
1071+ plan.blockSortSize = ComputeMergeIntraCoreBlockSortSize(ubSize);
1072+ if (plan.blockSortSize == 0U) {
1073+ return false;
1074+ }
888 uint64_t blocksPerRow64 = (static_cast<uint64_t>(axisLen) + plan.blockSortSize - 1U) / plan.blockSortSize;1075 uint64_t blocksPerRow64 = (static_cast<uint64_t>(axisLen) + plan.blockSortSize - 1U) / plan.blockSortSize;
889 if (blocksPerRow64 > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {1076 if (blocksPerRow64 > static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
890 return false;1077 return false;
@@ -1258,13 +1445,14 @@ static bool EstimateSmallAxisTwoStageBatching(const SortKthTileInfo& info, uint3
1258// =============================================================================1445// =============================================================================
1259static bool SelectSmallAxisRouteImpl(const SortKthTileInfo& info, uint32_t batchSizeCap,1446static bool SelectSmallAxisRouteImpl(const SortKthTileInfo& info, uint32_t batchSizeCap,
1260 std::function<bool(uint32_t, uint32_t&)> computeBatchNum,1447 std::function<bool(uint32_t, uint32_t&)> computeBatchNum,
1261- bool preferFullCoreBatching, SmallAxisRoutePlan& plan)1448+ bool preferFullCoreBatching, SmallAxisRoutePlan& plan,
1449+ const SmallAxisRule* ruleOverride = nullptr)
1262{1450{
1263 uint32_t axisLen = static_cast<uint32_t>(info.lastAxis);1451 uint32_t axisLen = static_cast<uint32_t>(info.lastAxis);
1264 if (axisLen <= 1U) {1452 if (axisLen <= 1U) {
1265 return false;1453 return false;
1266 }1454 }
1267- const SmallAxisRule* rule = FindSmallAxisRule(info.dataType);1455+ const SmallAxisRule* rule = ruleOverride == nullptr ? FindSmallAxisRule(info.dataType) : ruleOverride;
1268 if (rule == nullptr) {1456 if (rule == nullptr) {
1269 return false;1457 return false;
1270 }1458 }
@@ -1292,7 +1480,7 @@ static bool SelectSmallAxisRouteImpl(const SortKthTileInfo& info, uint32_t batch
1292 return true;1480 return true;
1293}1481}
1294 1482 
1295-bool SelectSmallAxisRoute(const SortKthTileInfo& info, SmallAxisRoutePlan& plan)1483+bool SelectSmallAxisRoute(const SortKthTileInfo& info, SmallAxisRoutePlan& plan, const SmallAxisRule* ruleOverride)
1296{1484{
1297 uint32_t fullCoreSegs = 0;1485 uint32_t fullCoreSegs = 0;
1298 if (!CeilDivUint32(static_cast<uint64_t>(info.unsortedDim), static_cast<uint64_t>(info.maxCoreNum), fullCoreSegs)) {1486 if (!CeilDivUint32(static_cast<uint64_t>(info.unsortedDim), static_cast<uint64_t>(info.maxCoreNum), fullCoreSegs)) {
@@ -1301,7 +1489,7 @@ bool SelectSmallAxisRoute(const SortKthTileInfo& info, SmallAxisRoutePlan& plan)
1301 auto computeBatchNum = [&info](uint32_t batchSize, uint32_t& batchNum) -> bool {1489 auto computeBatchNum = [&info](uint32_t batchSize, uint32_t& batchNum) -> bool {
1302 return CeilDivUint32(static_cast<uint64_t>(info.unsortedDim), batchSize, batchNum);1490 return CeilDivUint32(static_cast<uint64_t>(info.unsortedDim), batchSize, batchNum);
1303 };1491 };
1304- return SelectSmallAxisRouteImpl(info, fullCoreSegs, computeBatchNum, false, plan);1492+ return SelectSmallAxisRouteImpl(info, fullCoreSegs, computeBatchNum, false, plan, ruleOverride);
1305}1493}
1306 1494 
1307static bool SelectNonLastSmallAxisRouteImpl(const SortKthTileInfo& info, SmallAxisRoutePlan& plan,1495static bool SelectNonLastSmallAxisRouteImpl(const SortKthTileInfo& info, SmallAxisRoutePlan& plan,
@@ -41,6 +41,8 @@ constexpr int64_t NON_LAST_SMALL_AXIS_MIN_AXIS_LEN = 2;
41constexpr int64_t NON_LAST_SMALL_AXIS_THRESHOLD = 2048;41constexpr int64_t NON_LAST_SMALL_AXIS_THRESHOLD = 2048;
42constexpr int64_t ONE_CORE_DATA_SIZE = 2048;42constexpr int64_t ONE_CORE_DATA_SIZE = 2048;
43constexpr uint32_t DOUBLE_BUFFER_NUM = 2;43constexpr uint32_t DOUBLE_BUFFER_NUM = 2;
44+constexpr uint32_t MERGE_MORE_CORE_DATA_SIZE_BASE = 2048;
45+constexpr uint32_t MERGE_MORE_CORE_DATA_SIZE_LARGE = 4096;
44constexpr uint32_t SORT32_SMALL_AXIS_THRESHOLD = 32;46constexpr uint32_t SORT32_SMALL_AXIS_THRESHOLD = 32;
45constexpr uint32_t SMALL_AXIS_MAX_DATACOPY_BLOCK_COUNT = 4095; // DataCopy hardware limit for blockCount47constexpr uint32_t SMALL_AXIS_MAX_DATACOPY_BLOCK_COUNT = 4095; // DataCopy hardware limit for blockCount
46constexpr uint32_t SORT_STRUCT_BYTES = 8; // fp32 sort struct size (index + value)48constexpr uint32_t SORT_STRUCT_BYTES = 8; // fp32 sort struct size (index + value)
@@ -216,7 +218,8 @@ bool SearchNonLastSmallAxisPlan(
216 std::function<bool(SortKthTileInfo&, uint32_t, uint64_t&, NonLastSmallAxisCandidate&)> estimateUb,218 std::function<bool(SortKthTileInfo&, uint32_t, uint64_t&, NonLastSmallAxisCandidate&)> estimateUb,
217 NonLastSmallAxisCandidate& best, SortKthTileInfo* selectedInfo = nullptr);219 NonLastSmallAxisCandidate& best, SortKthTileInfo* selectedInfo = nullptr);
218 220 
219-bool SelectSmallAxisRoute(const SortKthTileInfo& info, SmallAxisRoutePlan& plan);221+bool SelectSmallAxisRoute(const SortKthTileInfo& info, SmallAxisRoutePlan& plan,
222+ const SmallAxisRule* ruleOverride = nullptr);
220bool SelectNonLastSmallAxisRoute(const SortKthTileInfo& info, SmallAxisRoutePlan& plan);223bool SelectNonLastSmallAxisRoute(const SortKthTileInfo& info, SmallAxisRoutePlan& plan);
221bool SelectSortNonLastSmallAxisRoute(const SortKthTileInfo& info, SmallAxisRoutePlan& plan);224bool SelectSortNonLastSmallAxisRoute(const SortKthTileInfo& info, SmallAxisRoutePlan& plan);
222 225 
@@ -265,6 +268,9 @@ constexpr uint32_t MERGE_SORT_LIST_NUM = 4;
265constexpr uint32_t MERGE_SORT_DATA_BYTES = 8;268constexpr uint32_t MERGE_SORT_DATA_BYTES = 8;
266constexpr int64_t MERGE_SORT_WORKSPACE_PARAM = 5;269constexpr int64_t MERGE_SORT_WORKSPACE_PARAM = 5;
267constexpr uint32_t MULTI_CORE_MERGE_SORT_MAX_AXIS = 32768;270constexpr uint32_t MULTI_CORE_MERGE_SORT_MAX_AXIS = 32768;
271+// Multi-round merge-more-core executes global barriers for every merge level. Use this route only when the sort axis
272+// can occupy at least eight cores per row; smaller horizontal splits fall back to a route with less barrier overhead.
273+constexpr uint32_t MERGE_MORE_CORE_MIN_CORES_PER_ROW_FOR_MULTI_ROUND = 8;
268constexpr uint32_t MERGE_INTRA_CORE_SORT_ALIGN = 32; // Sort/Extract API alignment requirement (elements)274constexpr uint32_t MERGE_INTRA_CORE_SORT_ALIGN = 32; // Sort/Extract API alignment requirement (elements)
269// Beyond 4 merge rounds (>256 blocks), radix sort has little performance disadvantage.275// Beyond 4 merge rounds (>256 blocks), radix sort has little performance disadvantage.
270constexpr uint32_t MERGE_INTRA_CORE_MAX_BLOCKS = 256;276constexpr uint32_t MERGE_INTRA_CORE_MAX_BLOCKS = 256;
@@ -280,7 +286,7 @@ struct MergeSortPlan {
280 286 
281bool ComputeMergeSortPlan(int64_t axisLen, int64_t unsortedDim, uint32_t blockUbSize, uint32_t tileDataNum,287bool ComputeMergeSortPlan(int64_t axisLen, int64_t unsortedDim, uint32_t blockUbSize, uint32_t tileDataNum,
282 uint32_t maxCoreNum, MergeSortPlan& plan);288 uint32_t maxCoreNum, MergeSortPlan& plan);
283-bool FillMergeSortInfo(SortKthTileInfo& info, uint32_t indexDtypeSize, uint32_t concatTmpSize);289+bool FillMergeSortInfo(SortKthTileInfo& info, uint32_t indexDtypeSize, bool useUbCapacity = false);
284 290 
285// =============================================================================291// =============================================================================
286// Merge sort — multi-core (more-core)292// Merge sort — multi-core (more-core)
@@ -293,17 +299,23 @@ struct MergeMoreCorePlan {
293 uint32_t sortLoopTimes = 1;299 uint32_t sortLoopTimes = 1;
294 uint32_t coreNumNeed = 0;300 uint32_t coreNumNeed = 0;
295 uint32_t keyParams0 = 0;301 uint32_t keyParams0 = 0;
302+ uint32_t keyParams1 = 0;
296};303};
297 304 
298-bool IsMergeMoreCoreSupported(ge::DataType dataType, int64_t axisLen, int64_t unsortedDim, uint32_t maxCoreNum);305+// Shared performance eligibility; the tiling builder still validates resource feasibility.
299-bool ComputeMergeMoreCorePlan(int64_t axisLen, int64_t unsortedDim, uint32_t ubSize, uint32_t mergeBytesPerElem,306+// Callers may impose operator-specific priorities before choosing this route.
300- MergeMoreCorePlan& plan);307+bool IsMergeMoreCoreProfitable(ge::DataType dataType, int64_t axisLen, int64_t unsortedDim, uint32_t maxCoreNum);
308+bool SelectMergeMoreCoreDataSize(int64_t axisLen, int64_t unsortedDim, uint32_t maxCoreNum, uint32_t& dataSize);
309+bool SelectMergeSyncMergeBlockSize(int64_t axisLen, int64_t unsortedDim, uint32_t maxCoreNum, uint32_t& blockSize);
310+bool ComputeMergeMoreCorePlan(int64_t axisLen, int64_t unsortedDim, uint32_t ubSize, uint32_t maxCoreNum,
311+ uint32_t mergeBytesPerElem, MergeMoreCorePlan& plan);
301bool FillMergeMoreCoreInfo(SortKthTileInfo& info, uint32_t mergeBytesPerElem);312bool FillMergeMoreCoreInfo(SortKthTileInfo& info, uint32_t mergeBytesPerElem);
302 313 
303// =============================================================================314// =============================================================================
304// Merge sort — intra-core315// Merge sort — intra-core
305// =============================================================================316// =============================================================================
306uint32_t ComputeMergeIntraCoreBlockSortSize(uint32_t ubSize);317uint32_t ComputeMergeIntraCoreBlockSortSize(uint32_t ubSize);
318+uint32_t ComputeMergeIntraCoreSingleBlockSortSize(uint32_t ubSize);
307uint32_t ComputeMergeIntraCoreExtractChunkSize(uint32_t ubSize);319uint32_t ComputeMergeIntraCoreExtractChunkSize(uint32_t ubSize);
308 320 
309struct MergeIntraCorePlan {321struct MergeIntraCorePlan {
@@ -370,7 +382,8 @@ void PlanToTilingData(const SortKthTileInfo& info, TilingData* tiling)
370 tiling->indexAxisBytes = info.indexAxisBytes;382 tiling->indexAxisBytes = info.indexAxisBytes;
371}383}
372 384 
373-bool ComputeMergeSortTiling(gert::TilingContext* context, SortKthTileInfo& info, uint32_t indexDtypeSize);385+bool ComputeMergeSortTiling(gert::TilingContext* context, SortKthTileInfo& info, uint32_t indexDtypeSize,
386+ bool useUbCapacity = false);
374bool ComputeMergeMoreCoreTiling(gert::TilingContext* context, SortKthTileInfo& info, uint32_t mergeBytesPerElem);387bool ComputeMergeMoreCoreTiling(gert::TilingContext* context, SortKthTileInfo& info, uint32_t mergeBytesPerElem);
375bool ComputeMergeIntraCoreTiling(gert::TilingContext* context, SortKthTileInfo& info);388bool ComputeMergeIntraCoreTiling(gert::TilingContext* context, SortKthTileInfo& info);
376 389 
@@ -25,12 +25,12 @@
25#include "op_kernel/math_util.h"25#include "op_kernel/math_util.h"
26#include "op_kernel/platform_util.h"26#include "op_kernel/platform_util.h"
27#include "merge_sort_constants.h"27#include "merge_sort_constants.h"
28+#include "ping_pong_merge_sort.h"
28 29 
29namespace MergeIntraCoreCommon {30namespace MergeIntraCoreCommon {
30 31 
31using namespace AscendC;32using namespace AscendC;
32 33 
33-using MergeSortConstants::DEALING_CONCAT_NUM_ONCE;
34using MergeSortConstants::DEALING_EXTRACT_NUM_ONCE;34using MergeSortConstants::DEALING_EXTRACT_NUM_ONCE;
35using MergeSortConstants::DEALING_SORT_NUM_ONCE;35using MergeSortConstants::DEALING_SORT_NUM_ONCE;
36using MergeSortConstants::MERGE_INTRA_BUFFER_NUM;36using MergeSortConstants::MERGE_INTRA_BUFFER_NUM;
@@ -83,13 +83,12 @@ public:
83 uint32_t lastBlockSize_ = 0; // Actual element count of the last block83 uint32_t lastBlockSize_ = 0; // Actual element count of the last block
84 uint32_t sortBufferSize_ = 0; // blockSortLen_ * sizeof(ValueType)84 uint32_t sortBufferSize_ = 0; // blockSortLen_ * sizeof(ValueType)
85 uint32_t sortRepeatTimes_ = 0; // blockSortSize_ / DEALING_SORT_NUM_ONCE85 uint32_t sortRepeatTimes_ = 0; // blockSortSize_ / DEALING_SORT_NUM_ONCE
86- uint32_t concatRepeatTimes_ = 0; // blockSortSize_ / DEALING_CONCAT_NUM_ONCE
87 uint32_t maxMergeIterations_ = 0; // INT32_MAX / blockSortSize_86 uint32_t maxMergeIterations_ = 0; // INT32_MAX / blockSortSize_
88 87 
89 // Queues and buffers for UB sorting (Phase 1)88 // Queues and buffers for UB sorting (Phase 1)
90 TQue<QuePosition::VECIN, MERGE_INTRA_BUFFER_NUM> inQueueX_;89 TQue<QuePosition::VECIN, MERGE_INTRA_BUFFER_NUM> inQueueX_;
91 TQue<QuePosition::VECOUT, MERGE_INTRA_BUFFER_NUM> outValueQueue_, outIdxQueue_, outIdxInt64Queue_;90 TQue<QuePosition::VECOUT, MERGE_INTRA_BUFFER_NUM> outValueQueue_, outIdxQueue_, outIdxInt64Queue_;
92- TBuf<TPosition::VECCALC> concatTmpBuf_, sortTmpBuf_, indexTmpBuf_;91+ TBuf<TPosition::VECCALC> sortTmpBuf_, indexTmpBuf_;
93 TQue<QuePosition::VECOUT, MERGE_INTRA_BUFFER_NUM> sortedOutQueue_;92 TQue<QuePosition::VECOUT, MERGE_INTRA_BUFFER_NUM> sortedOutQueue_;
94 93 
95 // Merge queues (Phase 2)94 // Merge queues (Phase 2)
@@ -142,16 +141,21 @@ __aicore__ inline void MergeIntraCoreBase<Derived, ValueType, IndexType, IsDesce
142 return;141 return;
143 }142 }
144 143 
145- for (int64_t batchIdx = startBatch; batchIdx < endBatch; batchIdx++) {144+ int64_t batchOffset = startBatch * this->sortAxisNum_;
145+ for (int64_t batchIdx = startBatch; batchIdx < endBatch; batchIdx++, batchOffset += this->sortAxisNum_) {
146 // Phase 1: derived owns the phase-specific UB allocation; base owns phase ordering.146 // Phase 1: derived owns the phase-specific UB allocation; base owns phase ordering.
147 static_cast<Derived*>(this)->InitPhase1Buffers();147 static_cast<Derived*>(this)->InitPhase1Buffers();
148- this->SortSingleBatchInUb(this->inputXGm_, batchIdx * this->sortAxisNum_);148+ this->SortSingleBatchInUb(this->inputXGm_, batchOffset);
149 PhaseBarrierAndReset();149 PhaseBarrierAndReset();
150 150 
151 // Phase 2: merge sorted blocks in the per-core ping-pong cache.151 // Phase 2: merge sorted blocks in the per-core ping-pong cache.
152- static_cast<Derived*>(this)->InitPhase2Buffers();152+ // A single block is already a complete sorted row in cache region 0, so skip the merge phase and its UB.
153- uint32_t resultRegion = this->MergeSingleBatch();153+ uint32_t resultRegion = 0;
154- PhaseBarrierAndReset();154+ if (this->blocksPerRow_ > 1) {
155+ static_cast<Derived*>(this)->InitPhase2Buffers();
156+ resultRegion = this->MergeSingleBatch();
157+ PhaseBarrierAndReset();
158+ }
155 159 
156 // Phase 3: derived writes either the full sorted row or the kth pair.160 // Phase 3: derived writes either the full sorted row or the kth pair.
157 static_cast<Derived*>(this)->InitPhase3Buffers();161 static_cast<Derived*>(this)->InitPhase3Buffers();
@@ -175,8 +179,8 @@ template <typename Derived, typename ValueType, typename IndexType, bool IsDesce
175__aicore__ inline void MergeIntraCoreBase<Derived, ValueType, IndexType, IsDescend>::SortSingleBatchInUb(179__aicore__ inline void MergeIntraCoreBase<Derived, ValueType, IndexType, IsDescend>::SortSingleBatchInUb(
176 GlobalTensor<ValueType> inputX, int64_t batchOffset)180 GlobalTensor<ValueType> inputX, int64_t batchOffset)
177{181{
178- for (uint32_t blockId = 0; blockId < this->blocksPerRow_; blockId++) {182+ for (uint32_t blockId = 0, elemOffset = 0; blockId < this->blocksPerRow_;
179- uint32_t elemOffset = blockId * this->blockSortSize_;183+ blockId++, elemOffset += this->blockSortSize_) {
180 uint32_t actualElem = (blockId < this->blocksPerRow_ - 1) ? this->blockSortSize_ : this->lastBlockSize_;184 uint32_t actualElem = (blockId < this->blocksPerRow_ - 1) ? this->blockSortSize_ : this->lastBlockSize_;
181 185 
182 LocalTensor<ValueType> xLocal = this->inQueueX_.template AllocTensor<ValueType>();186 LocalTensor<ValueType> xLocal = this->inQueueX_.template AllocTensor<ValueType>();
@@ -201,7 +205,7 @@ __aicore__ inline void MergeIntraCoreBase<Derived, ValueType, IndexType, IsDesce
201{205{
202 ValueType defaultValue = IsDescend ? static_cast<ValueType>(-INFINITY) : static_cast<ValueType>(NAN);206 ValueType defaultValue = IsDescend ? static_cast<ValueType>(-INFINITY) : static_cast<ValueType>(NAN);
203 207 
204- // For the last block (actualElem < blockSortSize_), Sort/Concat APIs process up to208+ // For the last block (actualElem < blockSortSize_), Sort API processes up to
205 // CeilAlign(actualElem, UB_BLOCK_BYTES) elements. DataCopyPad only pads to209 // CeilAlign(actualElem, UB_BLOCK_BYTES) elements. DataCopyPad only pads to
206 // CeilAlign(actualElem, UB_BLOCK_BYTES/sizeof(ValueType)) which may be smaller.210 // CeilAlign(actualElem, UB_BLOCK_BYTES/sizeof(ValueType)) which may be smaller.
207 // Pre-fill with Duplicate to cover the full aligned region, then overwrite valid data.211 // Pre-fill with Duplicate to cover the full aligned region, then overwrite valid data.
@@ -230,11 +234,6 @@ __aicore__ inline void MergeIntraCoreBase<Derived, ValueType, IndexType, IsDesce
230 uint32_t sortRepeatTimes = (actualElem == this->blockSortSize_) ?234 uint32_t sortRepeatTimes = (actualElem == this->blockSortSize_) ?
231 this->sortRepeatTimes_ :235 this->sortRepeatTimes_ :
232 Ops::Base::CeilDiv(alignSize, DEALING_SORT_NUM_ONCE);236 Ops::Base::CeilDiv(alignSize, DEALING_SORT_NUM_ONCE);
233- uint32_t concatRepeatTimes = (actualElem == this->blockSortSize_) ?
234- this->concatRepeatTimes_ :
235- Ops::Base::CeilDiv(alignSize, DEALING_CONCAT_NUM_ONCE);
236- 
237- LocalTensor<ValueType> concatTmpLocal = this->concatTmpBuf_.template Get<ValueType>();
238 LocalTensor<ValueType> sortTmpLocal = this->sortTmpBuf_.template Get<ValueType>();237 LocalTensor<ValueType> sortTmpLocal = this->sortTmpBuf_.template Get<ValueType>();
239 LocalTensor<uint32_t> indexTmpLocal = this->indexTmpBuf_.template Get<uint32_t>();238 LocalTensor<uint32_t> indexTmpLocal = this->indexTmpBuf_.template Get<uint32_t>();
240 ArithProgression<int32_t>(indexTmpLocal.template ReinterpretCast<int32_t>(), baseOffset, 1, actualElem);239 ArithProgression<int32_t>(indexTmpLocal.template ReinterpretCast<int32_t>(), baseOffset, 1, actualElem);
@@ -247,9 +246,11 @@ __aicore__ inline void MergeIntraCoreBase<Derived, ValueType, IndexType, IsDesce
247 Adds(castTensor, castTensor, 0x80000000, alignSize); // Flip sign bit for ascending float order246 Adds(castTensor, castTensor, 0x80000000, alignSize); // Flip sign bit for ascending float order
248 }247 }
249 248 
250- LocalTensor<ValueType> concatLocal;249+ bool resultInSorted = PingPongMergeSortCommon::SortToProposal(sortedLocal, sortTmpLocal, xLocal, indexTmpLocal,
251- Concat(concatLocal, xLocal, concatTmpLocal, concatRepeatTimes);250+ sortRepeatTimes);
252- AscendC::Sort<ValueType, true>(sortedLocal, concatLocal, indexTmpLocal, sortTmpLocal, sortRepeatTimes);251+ if (!resultInSorted) {
252+ DataCopy(sortedLocal, sortTmpLocal, AscendC::GetSortLen<ValueType>(alignSize));
253+ }
253}254}
254 255 
255template <typename Derived, typename ValueType, typename IndexType, bool IsDescend>256template <typename Derived, typename ValueType, typename IndexType, bool IsDescend>
@@ -20,13 +20,13 @@
20#include "kernel_operator.h"20#include "kernel_operator.h"
21#include "op_kernel/platform_util.h"21#include "op_kernel/platform_util.h"
22#include "merge_sort_constants.h"22#include "merge_sort_constants.h"
23+#include "ping_pong_merge_sort.h"
23#include "util_type_simd.h"24#include "util_type_simd.h"
24 25 
25namespace MergeMoreCoreCommon {26namespace MergeMoreCoreCommon {
26 27 
27using namespace AscendC;28using namespace AscendC;
28 29 
29-using MergeSortConstants::DEALING_CONCAT_NUM_ONCE;
30using MergeSortConstants::DEALING_EXTRACT_NUM_ONCE;30using MergeSortConstants::DEALING_EXTRACT_NUM_ONCE;
31using MergeSortConstants::DEALING_SORT_NUM_ONCE;31using MergeSortConstants::DEALING_SORT_NUM_ONCE;
32using MergeSortConstants::FP32_DTYPE_BYTES;32using MergeSortConstants::FP32_DTYPE_BYTES;
@@ -77,27 +77,28 @@ public:
77 // ===== Local Buffers =====77 // ===== Local Buffers =====
78 TBuf<QuePosition::VECCALC> sortedValueUb_;78 TBuf<QuePosition::VECCALC> sortedValueUb_;
79 TBuf<QuePosition::VECCALC> sortedValueIndexUb_;79 TBuf<QuePosition::VECCALC> sortedValueIndexUb_;
80- TBuf<QuePosition::VECCALC> concatTempBuf_;
81 TBuf<QuePosition::VECCALC> sortTempBuf_;80 TBuf<QuePosition::VECCALC> sortTempBuf_;
82- TBuf<QuePosition::VECCALC> sortedValueLocalCastTbuf_;
83 81 
84 // ===== Scalar Members =====82 // ===== Scalar Members =====
85 TPipe* pipe_ = nullptr;83 TPipe* pipe_ = nullptr;
86 uint32_t blockIdx_ = 0;84 uint32_t blockIdx_ = 0;
87 uint32_t numTileData_ = 0;85 uint32_t numTileData_ = 0;
88- uint32_t sortLoopRound_ = 0;
89- uint32_t platformCoreNum_ = 0;
90 uint32_t outputLastDimValue_ = 0;86 uint32_t outputLastDimValue_ = 0;
91 uint32_t frontCoreNum_ = 0;87 uint32_t frontCoreNum_ = 0;
88+ uint32_t rowGroupIdx_ = 0;
92 uint32_t rowIdx_ = 0;89 uint32_t rowIdx_ = 0;
93 uint32_t rowCoreIdx_ = 0;90 uint32_t rowCoreIdx_ = 0;
91+ uint32_t rowsPerRound_ = 0;
92+ uint32_t sortLoopTimes_ = 1;
93+ uint32_t logicalBlockSize_ = 0;
94+ bool syncMergeSortMode_ = false;
95+ int64_t batchNum_ = 0;
94 uint32_t vfLenFp32_ = Ops::Base::GetVRegSize() / FP32_DTYPE_BYTES;96 uint32_t vfLenFp32_ = Ops::Base::GetVRegSize() / FP32_DTYPE_BYTES;
95 int64_t rowDataOffset_ = 0;97 int64_t rowDataOffset_ = 0;
96 int64_t rowWorkspaceOffset_ = 0;98 int64_t rowWorkspaceOffset_ = 0;
97 99 
98 // ===== Merge-sort state =====100 // ===== Merge-sort state =====
99 int64_t listNum_{0};101 int64_t listNum_{0};
100- int64_t flag_ = 0;
101 int64_t remainListNum_{0};102 int64_t remainListNum_{0};
102 int64_t outOffset_{0};103 int64_t outOffset_{0};
103 int64_t offsets_[4] = {0};104 int64_t offsets_[4] = {0};
@@ -112,49 +113,173 @@ public:
112 uint16_t validBitTail_;113 uint16_t validBitTail_;
113 uint32_t listSortedNums_[4] = {0};114 uint32_t listSortedNums_[4] = {0};
114 uint32_t workSpaceFlag_ = 0;115 uint32_t workSpaceFlag_ = 0;
115- LocalTensor<CONVERT_TYPE> ubInputs_[4];
116- LocalTensor<CONVERT_TYPE> ubMainInput_;
117 116 
118 // ======================== Shared Member Functions ========================117 // ======================== Shared Member Functions ========================
119 118 
120 __aicore__ inline void Process()119 __aicore__ inline void Process()
120+ {
121+ if (this->frontCoreNum_ == 0U || this->rowsPerRound_ == 0U || this->sortLoopTimes_ == 0U) {
122+ return;
123+ }
124+ for (uint32_t loopIdx = 0; loopIdx < this->sortLoopTimes_; loopIdx++) {
125+ this->rowIdx_ = loopIdx * this->rowsPerRound_ + this->rowGroupIdx_;
126+ // In multi-round mode, the last round may have fewer rows than rowsPerRound_.
127+ // Cores assigned to out-of-range rows skip real work but still join SyncAll to avoid deadlock.
128+ bool rowActive = static_cast<int64_t>(this->rowIdx_) < this->batchNum_;
129+ if (rowActive) {
130+ static_cast<Derived*>(this)->PrepareRowWorkspace();
131+ static_cast<Derived*>(this)->InitSortBuffers();
132+ }
133+ 
134+ int64_t offsetPerCore = 0;
135+ uint32_t tileNum = 0;
136+ if (rowActive) {
137+ uint32_t phase1BlockSize = this->syncMergeSortMode_ ? this->logicalBlockSize_ : this->numTileData_;
138+ offsetPerCore = static_cast<int64_t>(phase1BlockSize) * static_cast<int64_t>(this->rowCoreIdx_);
139+ if (this->rowCoreIdx_ < this->frontCoreNum_ - 1) {
140+ tileNum = phase1BlockSize;
141+ } else if (this->rowCoreIdx_ == this->frontCoreNum_ - 1) {
142+ tileNum = this->outputLastDimValue_ - phase1BlockSize * (this->frontCoreNum_ - 1);
143+ }
144+ }
145+ if (rowActive && tileNum > 0U) {
146+ if (this->syncMergeSortMode_) {
147+ this->SortLogicalBlock(tileNum, offsetPerCore);
148+ } else {
149+ this->SortInSingleCore(tileNum, offsetPerCore);
150+ }
151+ }
152+ SyncAll();
153+ this->pipe_->Reset();
154+ 
155+ if (rowActive) {
156+ static_cast<Derived*>(this)->InitMergeBuffers();
157+ if (this->syncMergeSortMode_ && tileNum > 0U) {
158+ this->workspaceInput_ = this->workspaceGm_[0];
159+ this->workspaceOutput_ = this->workspaceGm_[1];
160+ this->MergeLogicalBlock(tileNum, offsetPerCore);
161+ this->ClearCache();
162+ }
163+ }
164+ if (this->syncMergeSortMode_) {
165+ SyncAll();
166+ }
167+ 
168+ InitMergeState(this->syncMergeSortMode_ ? this->logicalBlockSize_ : this->numTileData_,
169+ this->syncMergeSortMode_ ? 1U : 0U);
170+ MergeAcrossCores(rowActive);
171+ this->pipe_->Reset();
172+ }
173+ }
174+ 
175+ // The one-round, non-sync schedule has one active row per core group. Keeping it in a dedicated
176+ // kernel instance removes the generic row-activity and sync-mode branches from this hot orchestration path.
177+ __aicore__ inline void ProcessDirect()
121 {178 {
122 if (this->frontCoreNum_ == 0U) {179 if (this->frontCoreNum_ == 0U) {
123 return;180 return;
124 }181 }
125- int64_t offsetPerCore = 0;182+ int64_t offsetPerCore = static_cast<int64_t>(this->numTileData_) * this->rowCoreIdx_;
126- if (this->rowCoreIdx_ < this->frontCoreNum_ - 1) {183+ uint32_t tileNum = this->rowCoreIdx_ < this->frontCoreNum_ - 1 ?
127- offsetPerCore = this->numTileData_ * this->rowCoreIdx_;184+ this->numTileData_ :
128- this->SortInSingleCore(this->numTileData_, offsetPerCore);185+ this->outputLastDimValue_ - this->numTileData_ * (this->frontCoreNum_ - 1);
129- } else if (this->rowCoreIdx_ == this->frontCoreNum_ - 1) {186+ SortInSingleCore(tileNum, offsetPerCore);
130- uint32_t tailNum = this->outputLastDimValue_ - this->numTileData_ * (this->frontCoreNum_ - 1);
131- offsetPerCore = this->numTileData_ * this->rowCoreIdx_;
132- this->SortInSingleCore(tailNum, offsetPerCore);
133- }
134 SyncAll();187 SyncAll();
135 this->pipe_->Reset();188 this->pipe_->Reset();
136 189 
137 static_cast<Derived*>(this)->InitMergeBuffers();190 static_cast<Derived*>(this)->InitMergeBuffers();
191+ InitMergeState(this->numTileData_, 0U);
192+ MergeAcrossCores(true);
193+ }
138 194 
195+ __aicore__ inline void InitMergeState(int64_t blockElements, uint32_t workspaceFlag)
196+ {
197+ this->workSpaceFlag_ = workspaceFlag;
139 this->listNum_ = this->frontCoreNum_;198 this->listNum_ = this->frontCoreNum_;
140- this->currentElements_ = this->numTileData_;199+ this->currentElements_ = blockElements;
141- this->currentTailElements_ = this->outputLastDimValue_ - this->numTileData_ * (this->frontCoreNum_ - 1);200+ this->currentTailElements_ = this->outputLastDimValue_ - blockElements * (this->frontCoreNum_ - 1);
142- uint32_t currentCoreNum;201+ }
143- uint32_t remainListNum;202+ 
203+ // Inactive row groups advance the same merge levels and participate in the
204+ // barriers inside SortInMultiCore/FinalSortAndCopy, without accessing row workspace.
205+ __aicore__ inline void MergeAcrossCores(bool rowActive)
206+ {
144 while (this->listNum_ > MERGE_LIST_MAX_NUM) {207 while (this->listNum_ > MERGE_LIST_MAX_NUM) {
145- this->workspaceInput_ = this->workspaceGm_[this->workSpaceFlag_];208+ if (rowActive) {
146- this->workspaceOutput_ = this->workspaceGm_[1 - this->workSpaceFlag_];209+ this->workspaceInput_ = this->workspaceGm_[this->workSpaceFlag_];
147- this->SortInMultiCore();210+ this->workspaceOutput_ = this->workspaceGm_[1U - this->workSpaceFlag_];
148- currentCoreNum = Ops::Base::CeilDiv(this->listNum_, static_cast<int64_t>(MERGE_LIST_MAX_NUM));211+ }
149- remainListNum = this->listNum_ - (currentCoreNum - 1) * MERGE_LIST_MAX_NUM;212+ SortInMultiCore(rowActive);
150- this->currentTailElements_ = this->currentElements_ * (remainListNum - 1) + this->currentTailElements_;213+ uint32_t currentCoreNum = Ops::Base::CeilDiv(this->listNum_, static_cast<int64_t>(MERGE_LIST_MAX_NUM));
214+ uint32_t remainListNum = this->listNum_ - (currentCoreNum - 1U) * MERGE_LIST_MAX_NUM;
215+ this->currentTailElements_ = this->currentElements_ * (remainListNum - 1U) + this->currentTailElements_;
151 this->listNum_ = currentCoreNum;216 this->listNum_ = currentCoreNum;
152- this->currentElements_ = this->currentElements_ * MERGE_LIST_MAX_NUM;217+ this->currentElements_ *= MERGE_LIST_MAX_NUM;
153- this->workSpaceFlag_ = (this->workSpaceFlag_ + 1) % MERGE_WORKSPACE_BUFFER_NUM;218+ this->workSpaceFlag_ = 1U - this->workSpaceFlag_;
219+ }
220+ if (rowActive) {
221+ this->workspaceInput_ = this->workspaceGm_[this->workSpaceFlag_];
222+ this->workspaceOutput_ = this->workspaceGm_[1U - this->workSpaceFlag_];
223+ }
224+ FinalSortAndCopy(rowActive);
225+ }
226+ 
227+ // Sync-merge mode: sort a logical block (larger than numTileData_) in multiple passes,
228+ // each pass handling one numTileData_-sized chunk and flushing to workspaceGm_[0].
229+ __aicore__ inline void SortLogicalBlock(uint32_t tileNum, int64_t offsetPerCore)
230+ {
231+ uint32_t sortedNum = 0;
232+ while (sortedNum < tileNum) {
233+ uint32_t curTileNum = this->numTileData_;
234+ if (curTileNum > tileNum - sortedNum) {
235+ curTileNum = tileNum - sortedNum;
236+ }
237+ this->SortInSingleCore(curTileNum, offsetPerCore + static_cast<int64_t>(sortedNum));
238+ event_t eventIdMTE3ToS = static_cast<event_t>(this->pipe_->FetchEventID(HardEvent::MTE3_S));
239+ SetFlag<HardEvent::MTE3_S>(eventIdMTE3ToS);
240+ WaitFlag<HardEvent::MTE3_S>(eventIdMTE3ToS);
241+ sortedNum += curTileNum;
242+ }
243+ }
244+ 
245+ // Sync-merge mode: merge the locally sorted chunks (produced by SortLogicalBlock) into one
246+ // contiguous sorted logical block, using the streaming merge loop (CopyIn → MrgSort → CopyOut).
247+ __aicore__ inline void MergeLogicalBlock(uint32_t tileNum, int64_t offsetPerCore)
248+ {
249+ this->listNum_ = Ops::Base::CeilDiv(tileNum, this->numTileData_);
250+ this->currentElements_ = this->numTileData_;
251+ this->currentTailElements_ = tileNum - this->numTileData_ * (this->listNum_ - 1);
252+ this->InitLogicalBlockMerge(tileNum, offsetPerCore);
253+ for (; this->allRemainElements_ > 0;) {
254+ CopyInMultiCore();
255+ UpdateMrgParam();
256+ DealingMergeSort();
257+ UpdateSortInfo();
258+ CopyOutMultiCore();
259+ }
260+ }
261+ 
262+ // Set up per-run offsets and remaining element counts for the logical block merge.
263+ // Each run corresponds to one numTileData_-sized chunk sorted in SortLogicalBlock.
264+ __aicore__ inline void InitLogicalBlockMerge(uint32_t tileNum, int64_t offsetPerCore)
265+ {
266+ this->outOffset_ = GetSortLen<CONVERT_TYPE>(offsetPerCore);
267+ for (int64_t i = 0; i < MERGE_LIST_MAX_NUM; i++) {
268+ if (i < this->listNum_ - 1) {
269+ this->listRemainElements_[i] = this->numTileData_;
270+ this->offsets_[i] = GetSortOffset<CONVERT_TYPE>(offsetPerCore + i * this->numTileData_);
271+ this->allRemainElements_ += this->listRemainElements_[i];
272+ } else if (i == this->listNum_ - 1) {
273+ this->listRemainElements_[i] = this->currentTailElements_;
274+ this->offsets_[i] = GetSortOffset<CONVERT_TYPE>(offsetPerCore + i * this->numTileData_);
275+ this->allRemainElements_ += this->currentTailElements_;
276+ } else {
277+ this->listRemainElements_[i] = 0;
278+ }
279+ }
280+ if (this->listNum_ == 1) {
281+ this->currentTailElements_ = tileNum;
154 }282 }
155- this->workspaceInput_ = this->workspaceGm_[this->workSpaceFlag_];
156- this->workspaceOutput_ = this->workspaceGm_[1 - this->workSpaceFlag_];
157- this->FinalSortAndCopy();
158 }283 }
159 284 
160 __aicore__ inline void SortInSingleCore(uint32_t tileNum, int64_t offsetPerCore)285 __aicore__ inline void SortInSingleCore(uint32_t tileNum, int64_t offsetPerCore)
@@ -172,6 +297,8 @@ public:
172 this->inputQueue_.FreeTensor(inputLocal);297 this->inputQueue_.FreeTensor(inputLocal);
173 }298 }
174 299 
300+ // Copy tileNum elements from GM to UB, padding the tail to alignment with defaultValue
301+ // (−INF for descending, NAN for ascending) so Sort API processes a full aligned block.
175 __aicore__ inline void CopyInData(uint32_t tileNum, int64_t offsetPerCore)302 __aicore__ inline void CopyInData(uint32_t tileNum, int64_t offsetPerCore)
176 {303 {
177 LocalTensor<T> inputLocal = this->inputQueue_.template AllocTensor<T>();304 LocalTensor<T> inputLocal = this->inputQueue_.template AllocTensor<T>();
@@ -213,22 +340,18 @@ public:
213 LocalTensor<uint32_t> sortedValueIndexLocal)340 LocalTensor<uint32_t> sortedValueIndexLocal)
214 {341 {
215 AscendC::LocalTensor<CONVERT_TYPE> sortTempLocal = this->sortTempBuf_.template Get<CONVERT_TYPE>();342 AscendC::LocalTensor<CONVERT_TYPE> sortTempLocal = this->sortTempBuf_.template Get<CONVERT_TYPE>();
216- AscendC::LocalTensor<CONVERT_TYPE> concatTempLocal = this->concatTempBuf_.template Get<CONVERT_TYPE>();
217- AscendC::LocalTensor<CONVERT_TYPE> sortedValueLocalCast = this->sortedValueLocalCastTbuf_
218- .template Get<CONVERT_TYPE>();
219- 
220 uint32_t aglinTileNum = ROUND_UP_AGLIN(tileNum);343 uint32_t aglinTileNum = ROUND_UP_AGLIN(tileNum);
221 uint32_t sortRepeatTimes = Ops::Base::CeilDiv(aglinTileNum, DEALING_SORT_NUM_ONCE);344 uint32_t sortRepeatTimes = Ops::Base::CeilDiv(aglinTileNum, DEALING_SORT_NUM_ONCE);
222- uint32_t concatRepeatTimes = Ops::Base::CeilDiv(aglinTileNum, DEALING_CONCAT_NUM_ONCE);
223 345 
224 static_cast<Derived*>(this)->PrepareInputForSort(inputLocal, aglinTileNum);346 static_cast<Derived*>(this)->PrepareInputForSort(inputLocal, aglinTileNum);
225 if constexpr (!IS_DESCEND) {347 if constexpr (!IS_DESCEND) {
226 FlipSignBit(inputLocal, aglinTileNum);348 FlipSignBit(inputLocal, aglinTileNum);
227 }349 }
228- AscendC::LocalTensor<CONVERT_TYPE> concatLocal;350+ bool resultInSorted = PingPongMergeSortCommon::SortToProposal(sortedValueLocal, sortTempLocal, inputLocal,
229- AscendC::Concat(concatLocal, inputLocal, concatTempLocal, concatRepeatTimes);351+ sortedValueIndexLocal, sortRepeatTimes);
230- AscendC::Sort<CONVERT_TYPE, true>(sortedValueLocal, concatLocal, sortedValueIndexLocal, sortTempLocal,352+ if (!resultInSorted) {
231- sortRepeatTimes);353+ DataCopy(sortedValueLocal, sortTempLocal, GetSortOffset<CONVERT_TYPE>(aglinTileNum));
354+ }
232 }355 }
233 356 
234 __aicore__ inline void FlipSignBit(LocalTensor<CONVERT_TYPE> xLocal, uint32_t aglinTileNum)357 __aicore__ inline void FlipSignBit(LocalTensor<CONVERT_TYPE> xLocal, uint32_t aglinTileNum)
@@ -272,12 +395,12 @@ public:
272 this->sortedQueue_.template FreeTensor<CONVERT_TYPE>(sortTempBuffer);395 this->sortedQueue_.template FreeTensor<CONVERT_TYPE>(sortTempBuffer);
273 }396 }
274 397 
275- __aicore__ inline void SortInMultiCore()398+ __aicore__ inline void SortInMultiCore(bool rowActive)
276 {399 {
277 // Phase 2: every active row core merges up to 4 sorted runs. The merged output is written to the alternate400 // Phase 2: every active row core merges up to 4 sorted runs. The merged output is written to the alternate
278 // workspace buffer, and Process/derived code swaps the input/output workspace for the next merge level.401 // workspace buffer, and Process/derived code swaps the input/output workspace for the next merge level.
279 uint32_t needCoreNum = Ops::Base::CeilDiv(this->listNum_, static_cast<int64_t>(MERGE_LIST_MAX_NUM));402 uint32_t needCoreNum = Ops::Base::CeilDiv(this->listNum_, static_cast<int64_t>(MERGE_LIST_MAX_NUM));
280- if (this->rowCoreIdx_ < needCoreNum) {403+ if (rowActive && this->rowCoreIdx_ < needCoreNum) {
281 MultiCoreInit();404 MultiCoreInit();
282 for (; this->allRemainElements_ > 0;) {405 for (; this->allRemainElements_ > 0;) {
283 CopyInMultiCore();406 CopyInMultiCore();
@@ -291,11 +414,11 @@ public:
291 SyncAll();414 SyncAll();
292 }415 }
293 416 
294- __aicore__ inline void FinalSortAndCopy()417+ __aicore__ inline void FinalSortAndCopy(bool rowActive)
295 {418 {
296 // Phase 3: once at most 4 runs remain for the row, rowCoreIdx_ 0 performs the final merge and lets the419 // Phase 3: once at most 4 runs remain for the row, rowCoreIdx_ 0 performs the final merge and lets the
297 // derived kernel extract values/indices directly to the operator outputs.420 // derived kernel extract values/indices directly to the operator outputs.
298- if (this->rowCoreIdx_ == 0) {421+ if (rowActive && this->rowCoreIdx_ == 0) {
299 FinalSortInit();422 FinalSortInit();
300 for (; this->allRemainElements_ > 0;) {423 for (; this->allRemainElements_ > 0;) {
301 CopyInMultiCore();424 CopyInMultiCore();
@@ -395,6 +518,9 @@ public:
395 }518 }
396 }519 }
397 520 
521+ // Perform MrgSort on 2/3/4 source runs. Unused source-list slots are filled with a duplicate
522+ // of tmpUbInputs[0] because MrgSort requires valid tensor addresses for all four parameters;
523+ // validBitTail_ controls which lists actually participate.
398 __aicore__ inline void DealingMergeSort()524 __aicore__ inline void DealingMergeSort()
399 {525 {
400 LocalTensor<CONVERT_TYPE> sortTempBuffer = this->sortedQueue_.template AllocTensor<CONVERT_TYPE>();526 LocalTensor<CONVERT_TYPE> sortTempBuffer = this->sortedQueue_.template AllocTensor<CONVERT_TYPE>();
@@ -24,6 +24,8 @@
24#include "simt_api/asc_simt.h"24#include "simt_api/asc_simt.h"
25#include "signed_zero_sort_utils.h"25#include "signed_zero_sort_utils.h"
26#include "util_type_simd.h"26#include "util_type_simd.h"
27+#include "merge_sort_constants.h"
28+#include "ping_pong_merge_sort.h"
27 29 
28namespace SmallAxisCommon {30namespace SmallAxisCommon {
29using namespace AscendC;31using namespace AscendC;
@@ -61,6 +63,9 @@ public:
61 const uint64_t startTile = static_cast<uint64_t>(this->blockIdx_) * tilesPerCore;63 const uint64_t startTile = static_cast<uint64_t>(this->blockIdx_) * tilesPerCore;
62 uint64_t endTile = startTile + tilesPerCore;64 uint64_t endTile = startTile + tilesPerCore;
63 endTile = endTile > tileCount ? tileCount : endTile;65 endTile = endTile > tileCount ? tileCount : endTile;
66+ // The derived class may place this tensor in shared UB instead of sortedValueBuf_.
67+ // Keep its allocated address across tiles: merge Extract redirects sortedValue_ to sortInput_.
68+ LocalTensor<SortT> proposalPing = this->sortedValue_;
64 for (uint64_t tileId = startTile; tileId < endTile; ++tileId) {69 for (uint64_t tileId = startTile; tileId < endTile; ++tileId) {
65 const uint64_t outerId = tileId / this->innerLoopNum_;70 const uint64_t outerId = tileId / this->innerLoopNum_;
66 const uint32_t innerTileId = static_cast<uint32_t>(tileId -71 const uint32_t innerTileId = static_cast<uint32_t>(tileId -
@@ -76,7 +81,7 @@ public:
76 int64_t outputOffset = static_cast<int64_t>(outerId) * this->innerSize_ + innerStart;81 int64_t outputOffset = static_cast<int64_t>(outerId) * this->innerSize_ + innerStart;
77 this->LoadTile(inputOffset, curInnerChunk);82 this->LoadTile(inputOffset, curInnerChunk);
78 this->TransposeToSortMajor(curInnerChunk);83 this->TransposeToSortMajor(curInnerChunk);
79- this->SortRows(curInnerChunk);84+ this->SortRows(curInnerChunk, proposalPing);
80 static_cast<Derived*>(this)->StoreTile(inputOffset, outputOffset, curInnerChunk);85 static_cast<Derived*>(this)->StoreTile(inputOffset, outputOffset, curInnerChunk);
81 }86 }
82 }87 }
@@ -247,13 +252,29 @@ protected:
247 }252 }
248 }253 }
249 254 
250- __aicore__ inline void SortRows(uint32_t curInnerChunk)255+ __aicore__ inline void SortRows(uint32_t curInnerChunk, LocalTensor<SortT> proposalPing)
251 {256 {
257+ LocalTensor<uint32_t> baseIndex;
258+ if constexpr (UseMergeSort) {
259+ // The last row's indices stay unused until its Extract, after every Sort32 has read this sequence.
260+ baseIndex = this->sortedIndex_[(curInnerChunk - 1U) * this->indexAxisElems_];
261+ ArithProgression<int32_t>(baseIndex.template ReinterpretCast<int32_t>(), 0, 1, this->sortCount_);
262+ }
263+ // Keep one batched flip, but exclude trailing UB storage that Sort32 never reads.
264+ // sortCount_ includes the padding needed by the final Sort32 group.
265+ if constexpr (UseMergeSort && !IsDescend) {
266+ FlipMergeSignBit(this->sortInput_, (curInnerChunk - 1U) * this->valueAxisElems_ + this->sortCount_);
267+ }
252 for (uint32_t inner = 0; inner < curInnerChunk; ++inner) {268 for (uint32_t inner = 0; inner < curInnerChunk; ++inner) {
253 LocalTensor<SortT> src = this->sortInput_[inner * this->valueAxisElems_];269 LocalTensor<SortT> src = this->sortInput_[inner * this->valueAxisElems_];
254- LocalTensor<SortT> dst = this->sortedValue_[inner * this->valueAxisElems_];270+ LocalTensor<SortT> dst = proposalPing[inner * this->valueAxisElems_];
255 LocalTensor<uint32_t> dstIndex = this->sortedIndex_[inner * this->indexAxisElems_];271 LocalTensor<uint32_t> dstIndex = this->sortedIndex_[inner * this->indexAxisElems_];
256- if constexpr (NormalizeSignedZero) {272+ if constexpr (UseMergeSort) {
273+ LocalTensor<SortT> pong = this->tmp_.template ReinterpretCast<SortT>();
274+ uint32_t repeatTimes = this->sortCount_ / MergeSortConstants::DEALING_SORT_NUM_ONCE;
275+ bool resultInPing = PingPongMergeSortCommon::SortToProposal(dst, pong, src, baseIndex, repeatTimes);
276+ Extract(src, dstIndex, resultInPing ? dst : pong, repeatTimes);
277+ } else if constexpr (NormalizeSignedZero) {
257 SignedZeroSortCommon::PrepareSignedZeroKeysVec(src, this->sourceIndex_, this->sortCount_);278 SignedZeroSortCommon::PrepareSignedZeroKeysVec(src, this->sourceIndex_, this->sortCount_);
258 AscendC::Sort<SortT, true, sortConfig_>(dst, dstIndex, src, this->tmp_, this->sortCount_);279 AscendC::Sort<SortT, true, sortConfig_>(dst, dstIndex, src, this->tmp_, this->sortCount_);
259 SignedZeroSortCommon::RestoreSignedZeroValuesByIndexVec(dst, dstIndex, this->sourceIndex_,280 SignedZeroSortCommon::RestoreSignedZeroValuesByIndexVec(dst, dstIndex, this->sourceIndex_,
@@ -262,6 +283,25 @@ protected:
262 AscendC::Sort<SortT, true, sortConfig_>(dst, dstIndex, src, this->tmp_, this->sortCount_);283 AscendC::Sort<SortT, true, sortConfig_>(dst, dstIndex, src, this->tmp_, this->sortCount_);
263 }284 }
264 }285 }
286+ if constexpr (UseMergeSort) {
287+ if constexpr (!IsDescend) {
288+ FlipMergeSignBit(this->sortInput_, (curInnerChunk - 1U) * this->valueAxisElems_ + this->sortCount_);
289+ }
290+ // Extract writes the sorted values back to sortInput_ after its original input is dead,
291+ // so the input buffer is intentionally reused as the value output without extra UB.
292+ this->sortedValue_ = this->sortInput_;
293+ }
294+ }
295+ 
296+ __aicore__ inline void FlipMergeSignBit(LocalTensor<SortT> tensor, uint32_t count)
297+ {
298+ if constexpr (std::is_same_v<SortT, float>) {
299+ LocalTensor<int32_t> bits = tensor.template ReinterpretCast<int32_t>();
300+ Adds(bits, bits, MergeSortConstants::XOR_OP_VALUE_FP, count);
301+ } else if constexpr (std::is_same_v<SortT, half>) {
302+ LocalTensor<int16_t> bits = tensor.template ReinterpretCast<int16_t>();
303+ Adds(bits, bits, MergeSortConstants::XOR_OP_VALUE_HALF, count);
304+ }
265 }305 }
266};306};
267 307 
@@ -0,0 +1,115 @@
1+/**
2+ * Copyright (c) 2026 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+#ifndef PING_PONG_MERGE_SORT_H
12+#define PING_PONG_MERGE_SORT_H
13+ 
14+#include "kernel_operator.h"
15+#include "merge_sort_constants.h"
16+ 
17+namespace PingPongMergeSortCommon {
18+using namespace AscendC;
19+ 
20+using MergeSortConstants::DEALING_SORT_NUM_ONCE;
21+using MergeSortConstants::MERGE_LIST_MAX_NUM;
22+ 
23+// One round of 4-way merge: src has sorted runs of length queueLength, merge every 4 adjacent runs into dst.
24+// Full groups (4 complete runs) are batched in one MrgSort call; an incomplete final group is handled separately.
25+// Current arch35 callers keep both proposal buffers in UB (>=8 bytes/element
26+// each). Even allowing all 256 KiB, totalElements <=16384. Queue lengths are
27+// 32*4^s; while queueCount > 1 the largest is 8192, so uint16 counts do not
28+// truncate. Recheck this bound if larger UB or a different proposal layout is used.
29+template <typename T>
30+__aicore__ inline void MergeStage(LocalTensor<T> dst, LocalTensor<T> src, uint32_t totalElements, uint32_t queueLength,
31+ uint32_t queueCount)
32+{
33+ constexpr uint16_t allListsValid = (1U << MERGE_LIST_MAX_NUM) - 1U;
34+ // Full groups: each group merges 4 runs of queueLength elements.
35+ uint32_t groupSpan = queueLength * MERGE_LIST_MAX_NUM;
36+ uint32_t fullGroupCount = queueCount / MERGE_LIST_MAX_NUM;
37+ uint32_t tailQueueCount = queueCount % MERGE_LIST_MAX_NUM;
38+ // A divisible queue count can still end in a partial run. Leave that final
39+ // group to the tail path so its last element count is described exactly.
40+ if (tailQueueCount == 0U && totalElements != queueCount * queueLength) {
41+ --fullGroupCount;
42+ tailQueueCount = MERGE_LIST_MAX_NUM;
43+ }
44+ if (fullGroupCount > 0U) {
45+ uint16_t elementCounts[MERGE_LIST_MAX_NUM] = {
46+ static_cast<uint16_t>(queueLength), static_cast<uint16_t>(queueLength), static_cast<uint16_t>(queueLength),
47+ static_cast<uint16_t>(queueLength)};
48+ uint32_t sourceStride = GetSortOffset<T>(queueLength);
49+ MrgSortSrcList<T> sourceList(src, src[sourceStride], src[sourceStride * 2U], src[sourceStride * 3U]);
50+ uint32_t sortedCounts[MERGE_LIST_MAX_NUM];
51+ // false disables exhausted-list suspension: consume every valid list.
52+ MrgSort<T, false>(dst, sourceList, elementCounts, sortedCounts, allListsValid,
53+ static_cast<int32_t>(fullGroupCount));
54+ }
55+ 
56+ // Tail: 1-4 remaining runs; a fourth run can be shorter than queueLength.
57+ uint32_t tailOffset = fullGroupCount * groupSpan;
58+ uint32_t tailElements = totalElements - tailOffset;
59+ if (tailElements == 0U) {
60+ return;
61+ }
62+ uint32_t proposalTailOffset = GetSortOffset<T>(tailOffset);
63+ if (tailQueueCount == 1U) {
64+ DataCopy(dst[proposalTailOffset], src[proposalTailOffset], GetSortOffset<T>(tailElements));
65+ return;
66+ }
67+ 
68+ uint16_t elementCounts[MERGE_LIST_MAX_NUM] = {0, 0, 0, 0};
69+ uint32_t sourceOffsets[MERGE_LIST_MAX_NUM] = {tailOffset, tailOffset, tailOffset, tailOffset};
70+ for (uint32_t list = 0; list < tailQueueCount; ++list) {
71+ uint32_t sourceOffset = tailOffset + list * queueLength;
72+ uint32_t sourceRemaining = totalElements - sourceOffset;
73+ sourceOffsets[list] = sourceOffset;
74+ elementCounts[list] = static_cast<uint16_t>(sourceRemaining < queueLength ? sourceRemaining : queueLength);
75+ }
76+ MrgSortSrcList<T> sourceList(src[GetSortOffset<T>(sourceOffsets[0])], src[GetSortOffset<T>(sourceOffsets[1])],
77+ src[GetSortOffset<T>(sourceOffsets[2])], src[GetSortOffset<T>(sourceOffsets[3])]);
78+ uint32_t sortedCounts[MERGE_LIST_MAX_NUM];
79+ uint16_t validBit = static_cast<uint16_t>((1U << tailQueueCount) - 1U);
80+ MrgSort<T, false>(dst[proposalTailOffset], sourceList, elementCounts, sortedCounts, validBit, 1);
81+}
82+ 
83+// Ping-pong merge sort in UB:
84+// 1. Sort32 produces repeatTimes sorted runs of DEALING_SORT_NUM_ONCE elements into ping.
85+// 2. Iteratively 4-way merge adjacent runs, alternating src/dst between ping and pong each round.
86+// After each round, run length grows by 4x until only one sorted run remains.
87+// Returns true when the final sorted result is in ping, false when in pong.
88+template <typename T>
89+__aicore__ inline bool SortToProposal(LocalTensor<T> ping, LocalTensor<T> pong, LocalTensor<T> srcValue,
90+ LocalTensor<uint32_t> baseIndex, uint32_t repeatTimes)
91+{
92+ Sort32<T>(ping, srcValue, baseIndex, repeatTimes);
93+ if (repeatTimes <= 1U) {
94+ return true;
95+ }
96+ uint32_t totalElements = repeatTimes * DEALING_SORT_NUM_ONCE;
97+ uint32_t queueLength = DEALING_SORT_NUM_ONCE;
98+ uint32_t queueCount = repeatTimes;
99+ bool sourceIsPing = true;
100+ while (queueCount > 1U) {
101+ if (sourceIsPing) {
102+ MergeStage(pong, ping, totalElements, queueLength, queueCount);
103+ } else {
104+ MergeStage(ping, pong, totalElements, queueLength, queueCount);
105+ }
106+ sourceIsPing = !sourceIsPing;
107+ queueLength *= MERGE_LIST_MAX_NUM;
108+ queueCount = (queueCount + MERGE_LIST_MAX_NUM - 1U) / MERGE_LIST_MAX_NUM;
109+ }
110+ return sourceIsPing;
111+}
112+ 
113+} // namespace PingPongMergeSortCommon
114+ 
115+#endif // PING_PONG_MERGE_SORT_H
@@ -130,7 +130,7 @@ __aicore__ inline void TwiddleInFp16Impl(LocalTensor<T1> inputX, LocalTensor<UT>
130 Reg::Duplicate(xorMaskReg, LOWEST_KEY_VALUE_B16, maskB16);130 Reg::Duplicate(xorMaskReg, LOWEST_KEY_VALUE_B16, maskB16);
131 Reg::Duplicate(vandMask, XOR_OP_VALUE_B16, maskB16);131 Reg::Duplicate(vandMask, XOR_OP_VALUE_B16, maskB16);
132 Reg::Duplicate(twiddledZeroReg, TWIDDLED_ZERO_BITS_FP16, maskB16);132 Reg::Duplicate(twiddledZeroReg, TWIDDLED_ZERO_BITS_FP16, maskB16);
133- // Median mode only: precompute the canonical NaN key and the abs-bits mask used below.133+ // Precompute the canonical NaN key and abs-bits mask when normalization is enabled.
134 if constexpr (CanonicalizeNan) {134 if constexpr (CanonicalizeNan) {
135 Reg::Duplicate(canonicalNanReg, CANONICAL_NAN_KEY_B16, maskB16);135 Reg::Duplicate(canonicalNanReg, CANONICAL_NAN_KEY_B16, maskB16);
136 Reg::Duplicate(absMaskReg, FLOAT_ABS_MASK_B16, maskB16);136 Reg::Duplicate(absMaskReg, FLOAT_ABS_MASK_B16, maskB16);
@@ -161,7 +161,7 @@ __aicore__ inline void TwiddleInFp16Impl(LocalTensor<T1> inputX, LocalTensor<UT>
161 // change -0.0 to +0.0161 // change -0.0 to +0.0
162 Reg::RegTensor<uint16_t> resultReg;162 Reg::RegTensor<uint16_t> resultReg;
163 Reg::Select(resultReg, twiddledZeroReg, xorVectorOne, minusZeroMask);163 Reg::Select(resultReg, twiddledZeroReg, xorVectorOne, minusZeroMask);
164- // Median mode only: detect NaN from the raw input bits (|bits| > inf, sign-independent)164+ // Detect NaN from the raw input bits (|bits| > inf, sign-independent)
165 // and overwrite the twiddled key with the canonical NaN key, so every NaN lands in one165 // and overwrite the twiddled key with the canonical NaN key, so every NaN lands in one
166 // shared max bucket. Only the output key changes; the input data stays untouched.166 // shared max bucket. Only the output key changes; the input data stays untouched.
167 if constexpr (CanonicalizeNan) {167 if constexpr (CanonicalizeNan) {
@@ -20,6 +20,7 @@ using namespace AscendC;
20#define KTH_VALUE_RADIX_MORE_CORE_UINT32_TILING_KEY 25820#define KTH_VALUE_RADIX_MORE_CORE_UINT32_TILING_KEY 258
21#define KTH_VALUE_MERGE_MORE_CORE_TILING_KEY 25921#define KTH_VALUE_MERGE_MORE_CORE_TILING_KEY 259
22#define KTH_VALUE_RADIX_SELECT_TILING_KEY 26722#define KTH_VALUE_RADIX_SELECT_TILING_KEY 267
23+#define KTH_VALUE_RESIDENT_HISTOGRAM_TILING_KEY 269
23 24 
24template <uint64_t schId, uint64_t isInt32>25template <uint64_t schId, uint64_t isInt32>
25__global__ __aicore__ void kth_value(GM_ADDR x, GM_ADDR y1, GM_ADDR y2, GM_ADDR workspace, GM_ADDR tiling)26__global__ __aicore__ void kth_value(GM_ADDR x, GM_ADDR y1, GM_ADDR y2, GM_ADDR workspace, GM_ADDR tiling)
@@ -29,8 +30,22 @@ __global__ __aicore__ void kth_value(GM_ADDR x, GM_ADDR y1, GM_ADDR y2, GM_ADDR
29 KERNEL_TASK_TYPE(KTH_VALUE_RADIX_MORE_CORE_UINT32_TILING_KEY, KERNEL_TYPE_MIX_AIV_1_0);30 KERNEL_TASK_TYPE(KTH_VALUE_RADIX_MORE_CORE_UINT32_TILING_KEY, KERNEL_TYPE_MIX_AIV_1_0);
30 KERNEL_TASK_TYPE(KTH_VALUE_MERGE_MORE_CORE_TILING_KEY, KERNEL_TYPE_MIX_AIV_1_0);31 KERNEL_TASK_TYPE(KTH_VALUE_MERGE_MORE_CORE_TILING_KEY, KERNEL_TYPE_MIX_AIV_1_0);
31 KERNEL_TASK_TYPE(KTH_VALUE_RADIX_SELECT_TILING_KEY, KERNEL_TYPE_MIX_AIV_1_0);32 KERNEL_TASK_TYPE(KTH_VALUE_RADIX_SELECT_TILING_KEY, KERNEL_TYPE_MIX_AIV_1_0);
33+ KERNEL_TASK_TYPE(KTH_VALUE_RESIDENT_HISTOGRAM_TILING_KEY, KERNEL_TYPE_MIX_AIV_1_0);
32 REGISTER_TILING_DEFAULT(KthValueTilingData);34 REGISTER_TILING_DEFAULT(KthValueTilingData);
33- GET_TILING_DATA_WITH_STRUCT(KthValueTilingData, tilingData, tiling);
34 TPipe pipe;35 TPipe pipe;
35- KthValue::Dispatch<false, schId, isInt32>(x, y1, y2, workspace, &tilingData, &pipe);36+ // Host StoreKthValueTiling serializes a different layout for these single-core keys.
37+ // Register and decode the matching compact struct before Dispatch; a full-struct read
38+ // would use wrong field offsets. if constexpr removes the other layouts from each binary.
39+ if constexpr (schId == KTH_VALUE_SCHID_MERGE_SORT || schId == KTH_VALUE_SCHID_SORT32_SMALL_AXIS) {
40+ REGISTER_TILING_FOR_TILINGKEY(KTH_VALUE_MERGE_ONE_CORE_TILING_CONDITION, KthValueMergeOneCoreTilingData);
41+ GET_TILING_DATA_WITH_STRUCT(KthValueMergeOneCoreTilingData, tilingData, tiling);
42+ KthValue::Dispatch<false, schId, isInt32>(x, y1, y2, workspace, &tilingData, &pipe);
43+ } else if constexpr (schId == KTH_VALUE_SCHID_RADIX_ONE_CORE) {
44+ REGISTER_TILING_FOR_TILINGKEY(KTH_VALUE_RADIX_ONE_CORE_TILING_CONDITION, KthValueRadixOneCoreTilingData);
45+ GET_TILING_DATA_WITH_STRUCT(KthValueRadixOneCoreTilingData, tilingData, tiling);
46+ KthValue::Dispatch<false, schId, isInt32>(x, y1, y2, workspace, &tilingData, &pipe);
47+ } else {
48+ GET_TILING_DATA_WITH_STRUCT(KthValueTilingData, tilingData, tiling);
49+ KthValue::Dispatch<false, schId, isInt32>(x, y1, y2, workspace, &tilingData, &pipe);
50+ }
36}51}
@@ -30,10 +30,11 @@ namespace KthValue {
30using namespace AscendC;30using namespace AscendC;
31 31 
32template <bool EnableMedian, uint64_t schId>32template <bool EnableMedian, uint64_t schId>
33-__aicore__ inline void RunMergeSortRoute(GM_ADDR x, GM_ADDR y1, GM_ADDR y2, KthValueTilingData* tilingData, TPipe* pipe)33+__aicore__ inline void RunMergeSortRoute(GM_ADDR x, GM_ADDR y1, GM_ADDR y2, KthValueMergeOneCoreTilingData* tilingData,
34+ TPipe* pipe)
34{35{
35 constexpr uint64_t isSort32SmallAxis = (schId == KTH_VALUE_SCHID_SORT32_SMALL_AXIS);36 constexpr uint64_t isSort32SmallAxis = (schId == KTH_VALUE_SCHID_SORT32_SMALL_AXIS);
36- if constexpr (IsSameType<bfloat16_t, DTYPE_X>::value) {37+ if constexpr (IsSameType<bfloat16_t, DTYPE_X>::value || IsSameType<int16_t, DTYPE_X>::value) {
37 KthValueMergeSortOneCore<DTYPE_X, float, isSort32SmallAxis, EnableMedian> op;38 KthValueMergeSortOneCore<DTYPE_X, float, isSort32SmallAxis, EnableMedian> op;
38 op.Init(x, y1, y2, tilingData, pipe);39 op.Init(x, y1, y2, tilingData, pipe);
39 op.Process();40 op.Process();
@@ -158,8 +159,8 @@ __aicore__ inline void RunAxisOneCopyRoute(GM_ADDR x, GM_ADDR y1, GM_ADDR y2, GM
158}159}
159 160 
160template <bool EnableMedian>161template <bool EnableMedian>
161-__aicore__ inline void RunRadixOneCoreRoute(GM_ADDR x, GM_ADDR y1, GM_ADDR y2, KthValueTilingData* tilingData,162+__aicore__ inline void RunRadixOneCoreRoute(GM_ADDR x, GM_ADDR y1, GM_ADDR y2,
162- TPipe* pipe)163+ KthValueRadixOneCoreTilingData* tilingData, TPipe* pipe)
163{164{
164 constexpr bool enableNanMode = EnableMedian && IS_MEDIAN_FLOAT_TYPE<DTYPE_X>;165 constexpr bool enableNanMode = EnableMedian && IS_MEDIAN_FLOAT_TYPE<DTYPE_X>;
165 KthValueRadixOneCore<DTYPE_X, enableNanMode> op;166 KthValueRadixOneCore<DTYPE_X, enableNanMode> op;
@@ -167,7 +168,7 @@ __aicore__ inline void RunRadixOneCoreRoute(GM_ADDR x, GM_ADDR y1, GM_ADDR y2, K
167 op.Process();168 op.Process();
168}169}
169 170 
170-template <bool EnableMedian>171+template <bool EnableMedian, bool UseResidentHistogram = false>
171__aicore__ inline void RunRadixSelectRoute(GM_ADDR x, GM_ADDR y1, GM_ADDR y2, GM_ADDR workspace,172__aicore__ inline void RunRadixSelectRoute(GM_ADDR x, GM_ADDR y1, GM_ADDR y2, GM_ADDR workspace,
172 KthValueTilingData* tilingData, TPipe* pipe)173 KthValueTilingData* tilingData, TPipe* pipe)
173{174{
@@ -175,19 +176,19 @@ __aicore__ inline void RunRadixSelectRoute(GM_ADDR x, GM_ADDR y1, GM_ADDR y2, GM
175 if constexpr (sizeof(DTYPE_X) == 1) {176 if constexpr (sizeof(DTYPE_X) == 1) {
176 KthValueRadixSelect<DTYPE_X, uint8_t, enableNanMode> op;177 KthValueRadixSelect<DTYPE_X, uint8_t, enableNanMode> op;
177 op.Init(x, y1, y2, workspace, tilingData, pipe);178 op.Init(x, y1, y2, workspace, tilingData, pipe);
178- op.Process();179+ op.template Process<UseResidentHistogram>();
179 } else if constexpr (sizeof(DTYPE_X) == 2) {180 } else if constexpr (sizeof(DTYPE_X) == 2) {
180 KthValueRadixSelect<DTYPE_X, uint16_t, enableNanMode> op;181 KthValueRadixSelect<DTYPE_X, uint16_t, enableNanMode> op;
181 op.Init(x, y1, y2, workspace, tilingData, pipe);182 op.Init(x, y1, y2, workspace, tilingData, pipe);
182- op.Process();183+ op.template Process<UseResidentHistogram>();
183 } else if constexpr (sizeof(DTYPE_X) == 4) {184 } else if constexpr (sizeof(DTYPE_X) == 4) {
184 KthValueRadixSelect<DTYPE_X, uint32_t, enableNanMode> op;185 KthValueRadixSelect<DTYPE_X, uint32_t, enableNanMode> op;
185 op.Init(x, y1, y2, workspace, tilingData, pipe);186 op.Init(x, y1, y2, workspace, tilingData, pipe);
186- op.Process();187+ op.template Process<UseResidentHistogram>();
187 } else if constexpr (sizeof(DTYPE_X) == 8) {188 } else if constexpr (sizeof(DTYPE_X) == 8) {
188 KthValueRadixSelect<DTYPE_X, uint64_t, enableNanMode> op;189 KthValueRadixSelect<DTYPE_X, uint64_t, enableNanMode> op;
189 op.Init(x, y1, y2, workspace, tilingData, pipe);190 op.Init(x, y1, y2, workspace, tilingData, pipe);
190- op.Process();191+ op.template Process<UseResidentHistogram>();
191 }192 }
192}193}
193 194 
@@ -232,8 +233,8 @@ __aicore__ inline bool TryRunSmallAxisRoute(GM_ADDR x, GM_ADDR y1, GM_ADDR y2, K
232 233 
233// EnableMedian selects the shared Median/NanMedian behavior. tilingData->medianMode234// EnableMedian selects the shared Median/NanMedian behavior. tilingData->medianMode
234// distinguishes propagate-NaN Median from ignore-NaN NanMedian at runtime.235// distinguishes propagate-NaN Median from ignore-NaN NanMedian at runtime.
235-template <bool EnableMedian, uint64_t schId, uint64_t isInt32>236+template <bool EnableMedian, uint64_t schId, uint64_t isInt32, typename TilingData>
236-__aicore__ inline void Dispatch(GM_ADDR x, GM_ADDR y1, GM_ADDR y2, GM_ADDR workspace, KthValueTilingData* tilingData,237+__aicore__ inline void Dispatch(GM_ADDR x, GM_ADDR y1, GM_ADDR y2, GM_ADDR workspace, TilingData* tilingData,
237 TPipe* pipe)238 TPipe* pipe)
238{239{
239 GM_ADDR userWorkspace = AscendC::GetUserWorkspace(workspace);240 GM_ADDR userWorkspace = AscendC::GetUserWorkspace(workspace);
@@ -245,6 +246,8 @@ __aicore__ inline void Dispatch(GM_ADDR x, GM_ADDR y1, GM_ADDR y2, GM_ADDR works
245 RunRadixOneCoreRoute<EnableMedian>(x, y1, y2, tilingData, pipe);246 RunRadixOneCoreRoute<EnableMedian>(x, y1, y2, tilingData, pipe);
246 } else if constexpr (schId == KTH_VALUE_SCHID_RADIX_MORE_CORE) {247 } else if constexpr (schId == KTH_VALUE_SCHID_RADIX_MORE_CORE) {
247 RunRadixMoreCoreRoute<EnableMedian, isInt32>(x, y1, y2, userWorkspace, tilingData, pipe);248 RunRadixMoreCoreRoute<EnableMedian, isInt32>(x, y1, y2, userWorkspace, tilingData, pipe);
249+ } else if constexpr (schId == KTH_VALUE_SCHID_RESIDENT_HISTOGRAM) {
250+ RunRadixSelectRoute<EnableMedian, true>(x, y1, y2, userWorkspace, tilingData, pipe);
248 } else if constexpr (schId == KTH_VALUE_SCHID_RADIX_SELECT) {251 } else if constexpr (schId == KTH_VALUE_SCHID_RADIX_SELECT) {
249 RunRadixSelectRoute<EnableMedian>(x, y1, y2, userWorkspace, tilingData, pipe);252 RunRadixSelectRoute<EnableMedian>(x, y1, y2, userWorkspace, tilingData, pipe);
250 } else if constexpr (schId == KTH_VALUE_SCHID_SMALL_AXIS_INSERTION ||253 } else if constexpr (schId == KTH_VALUE_SCHID_SMALL_AXIS_INSERTION ||
@@ -32,7 +32,6 @@ namespace KthValue {
32using namespace AscendC;32using namespace AscendC;
33 33 
34// Import shared constants from MergeSortConstants namespace34// Import shared constants from MergeSortConstants namespace
35-using MergeSortConstants::DEALING_CONCAT_NUM_ONCE;
36using MergeSortConstants::DEALING_EXTRACT_NUM_ONCE;35using MergeSortConstants::DEALING_EXTRACT_NUM_ONCE;
37using MergeSortConstants::DEALING_SORT_NUM_ONCE;36using MergeSortConstants::DEALING_SORT_NUM_ONCE;
38using MergeSortConstants::MERGE_INTRA_BUFFER_NUM;37using MergeSortConstants::MERGE_INTRA_BUFFER_NUM;
@@ -117,7 +116,6 @@ __aicore__ inline void KthValueMergeIntraCore<ValueType, IndexType, IsDescend, E
117 this->batchSortLen_ = AscendC::GetSortLen<ValueType>(this->alignNum_);116 this->batchSortLen_ = AscendC::GetSortLen<ValueType>(this->alignNum_);
118 this->sortBufferSize_ = this->blockSortLen_ * sizeof(ValueType);117 this->sortBufferSize_ = this->blockSortLen_ * sizeof(ValueType);
119 this->sortRepeatTimes_ = this->blockSortSize_ / DEALING_SORT_NUM_ONCE;118 this->sortRepeatTimes_ = this->blockSortSize_ / DEALING_SORT_NUM_ONCE;
120- this->concatRepeatTimes_ = this->blockSortSize_ / DEALING_CONCAT_NUM_ONCE;
121 this->lastBlockSize_ = static_cast<uint32_t>(this->sortAxisNum_ -119 this->lastBlockSize_ = static_cast<uint32_t>(this->sortAxisNum_ -
122 static_cast<int64_t>(this->blocksPerRow_ - 1) * this->blockSortSize_);120 static_cast<int64_t>(this->blocksPerRow_ - 1) * this->blockSortSize_);
123 121 
@@ -134,7 +132,6 @@ template <typename ValueType, typename IndexType, bool IsDescend, bool EnableMed
134__aicore__ inline void KthValueMergeIntraCore<ValueType, IndexType, IsDescend, EnableMedian>::InitPhase1Buffers()132__aicore__ inline void KthValueMergeIntraCore<ValueType, IndexType, IsDescend, EnableMedian>::InitPhase1Buffers()
135{133{
136 this->pipe_->InitBuffer(this->inQueueX_, MERGE_INTRA_BUFFER_NUM, this->blockSortSize_ * sizeof(ValueType));134 this->pipe_->InitBuffer(this->inQueueX_, MERGE_INTRA_BUFFER_NUM, this->blockSortSize_ * sizeof(ValueType));
137- this->pipe_->InitBuffer(this->concatTmpBuf_, this->sortBufferSize_);
138 this->pipe_->InitBuffer(this->sortTmpBuf_, this->sortBufferSize_);135 this->pipe_->InitBuffer(this->sortTmpBuf_, this->sortBufferSize_);
139 this->pipe_->InitBuffer(this->sortedOutQueue_, MERGE_INTRA_BUFFER_NUM, this->sortBufferSize_);136 this->pipe_->InitBuffer(this->sortedOutQueue_, MERGE_INTRA_BUFFER_NUM, this->sortBufferSize_);
140 this->pipe_->InitBuffer(this->indexTmpBuf_, this->blockSortSize_ * sizeof(uint32_t));137 this->pipe_->InitBuffer(this->indexTmpBuf_, this->blockSortSize_ * sizeof(uint32_t));
@@ -178,7 +175,13 @@ __aicore__ inline void KthValueMergeIntraCore<ValueType, IndexType, IsDescend, E
178{175{
179 int64_t outputOffset = batchIdx;176 int64_t outputOffset = batchIdx;
180 if constexpr (EnableMedian && IS_MEDIAN_FLOAT_TYPE<ValueType>) {177 if constexpr (EnableMedian && IS_MEDIAN_FLOAT_TYPE<ValueType>) {
181- kthIndex_ = ResolveBatchK(batchIdx);178+ if constexpr (KTH_VALUE_ENABLE_STATIC_MEDIAN_FAST_PATH) {
179+ if (medianMode_ != MEDIAN_MODE_STATIC) {
180+ kthIndex_ = ResolveBatchK(batchIdx);
181+ }
182+ } else {
183+ kthIndex_ = ResolveBatchK(batchIdx);
184+ }
182 }185 }
183 186 
184 // resultRegion: 0 = Ping (offset 0), 1 = Pong (offset batchSortLen_)187 // resultRegion: 0 = Ping (offset 0), 1 = Pong (offset batchSortLen_)
@@ -26,7 +26,6 @@ namespace KthValue {
26using namespace AscendC;26using namespace AscendC;
27 27 
28// Import shared constants from MergeSortConstants namespace28// Import shared constants from MergeSortConstants namespace
29-using MergeSortConstants::DEALING_CONCAT_NUM_ONCE;
30using MergeSortConstants::DEALING_EXTRACT_NUM_ONCE;29using MergeSortConstants::DEALING_EXTRACT_NUM_ONCE;
31using MergeSortConstants::DEALING_SORT_NUM_ONCE;30using MergeSortConstants::DEALING_SORT_NUM_ONCE;
32using MergeSortConstants::MERGE_LIST_MAX_NUM;31using MergeSortConstants::MERGE_LIST_MAX_NUM;
@@ -48,6 +47,8 @@ struct KthValueMergeSortMoreCore : public MergeMoreCoreCommon::MergeMoreCoreBase
48 __aicore__ inline KthValueMergeSortMoreCore() {}47 __aicore__ inline KthValueMergeSortMoreCore() {}
49 __aicore__ inline void Init(GM_ADDR inputValue, GM_ADDR value, GM_ADDR indices, GM_ADDR workSpace,48 __aicore__ inline void Init(GM_ADDR inputValue, GM_ADDR value, GM_ADDR indices, GM_ADDR workSpace,
50 const KthValueTilingData* tilingData, TPipe* pipe);49 const KthValueTilingData* tilingData, TPipe* pipe);
50+ __aicore__ inline void PrepareRowWorkspace();
51+ __aicore__ inline void InitSortBuffers();
51 __aicore__ inline void InitMergeBuffers();52 __aicore__ inline void InitMergeBuffers();
52 __aicore__ inline void ExtractAndCopyOut();53 __aicore__ inline void ExtractAndCopyOut();
53 __aicore__ inline void OnInputLoaded(LocalTensor<T> inputLocal, uint32_t tileNum);54 __aicore__ inline void OnInputLoaded(LocalTensor<T> inputLocal, uint32_t tileNum);
@@ -56,8 +57,13 @@ struct KthValueMergeSortMoreCore : public MergeMoreCoreCommon::MergeMoreCoreBase
56 57 
57 // KthValue-specific member58 // KthValue-specific member
58 uint32_t kthIndex_ = 0;59 uint32_t kthIndex_ = 0;
60+ uint32_t staticKthIndex_ = 0;
59 uint32_t medianMode_ = 0;61 uint32_t medianMode_ = 0;
62+ uint32_t nonNanCount_ = 0;
63+ TBuf<QuePosition::VECCALC> medianScratchBuf_;
60 GlobalTensor<uint32_t> medianCountGm_;64 GlobalTensor<uint32_t> medianCountGm_;
65+ __gm__ CONVERT_TYPE* workspaceBase_ = nullptr;
66+ uint64_t rowWorkspaceElements_ = 0;
61};67};
62 68 
63template <typename T, typename CONVERT_TYPE, bool IS_DESCEND, typename INDEX_TYPE, bool EnableMedian>69template <typename T, typename CONVERT_TYPE, bool IS_DESCEND, typename INDEX_TYPE, bool EnableMedian>
@@ -73,45 +79,72 @@ __aicore__ inline void KthValueMergeSortMoreCore<T, CONVERT_TYPE, IS_DESCEND, IN
73 this->outputLastDimValue_ = tilingData->lastDimTileNum;79 this->outputLastDimValue_ = tilingData->lastDimTileNum;
74 this->numTileData_ = tilingData->numTileDataSize;80 this->numTileData_ = tilingData->numTileDataSize;
75 this->frontCoreNum_ = tilingData->lastDimNeedCore;81 this->frontCoreNum_ = tilingData->lastDimNeedCore;
82+ this->rowsPerRound_ = tilingData->unsortedDimParallel;
83+ this->sortLoopTimes_ = tilingData->sortLoopTimes;
84+ this->batchNum_ = tilingData->unsortedDimNum;
85+ this->logicalBlockSize_ = tilingData->keyParams1;
86+ this->syncMergeSortMode_ = this->logicalBlockSize_ > this->numTileData_ && this->numTileData_ > 0U;
76 kthIndex_ = tilingData->kthIndex;87 kthIndex_ = tilingData->kthIndex;
88+ staticKthIndex_ = kthIndex_;
77 medianMode_ = tilingData->medianMode;89 medianMode_ = tilingData->medianMode;
78- if (this->frontCoreNum_ == 0U) {90+ if (this->frontCoreNum_ == 0U || this->rowsPerRound_ == 0U || this->sortLoopTimes_ == 0U) {
79 return;91 return;
80 }92 }
81 uint32_t sortBufferSize = 8;93 uint32_t sortBufferSize = 8;
82- this->rowIdx_ = this->blockIdx_ / this->frontCoreNum_;94+ this->rowGroupIdx_ = this->blockIdx_ / this->frontCoreNum_;
83 this->rowCoreIdx_ = this->blockIdx_ % this->frontCoreNum_;95 this->rowCoreIdx_ = this->blockIdx_ % this->frontCoreNum_;
84- this->rowDataOffset_ = static_cast<int64_t>(this->rowIdx_) * static_cast<int64_t>(this->outputLastDimValue_);
85 // Per-row workspace stores Sort API sort-struct data. This capacity uses sortBufferSize bytes per96 // Per-row workspace stores Sort API sort-struct data. This capacity uses sortBufferSize bytes per
86 // original element and UB-block byte alignment; it must cover later GetSortLen-based accesses.97 // original element and UB-block byte alignment; it must cover later GetSortLen-based accesses.
87 uint64_t rowWorkspaceBytes = ROUND_UP_AGLIN_UINT64(static_cast<uint64_t>(this->outputLastDimValue_) *98 uint64_t rowWorkspaceBytes = ROUND_UP_AGLIN_UINT64(static_cast<uint64_t>(this->outputLastDimValue_) *
88 sortBufferSize);99 sortBufferSize);
89- uint64_t rowWorkspaceElements = rowWorkspaceBytes / sizeof(CONVERT_TYPE);100+ this->rowWorkspaceElements_ = rowWorkspaceBytes / sizeof(CONVERT_TYPE);
90- this->rowWorkspaceOffset_ = static_cast<int64_t>(this->rowIdx_) * static_cast<int64_t>(rowWorkspaceElements) * 2;
91 this->onceMaxElements_ = tilingData->keyParams0 / DEALING_SORT_NUM_ONCE * DEALING_SORT_NUM_ONCE;101 this->onceMaxElements_ = tilingData->keyParams0 / DEALING_SORT_NUM_ONCE * DEALING_SORT_NUM_ONCE;
92 102 
93 this->inputValueGm_.SetGlobalBuffer((__gm__ T*)(inputValue));103 this->inputValueGm_.SetGlobalBuffer((__gm__ T*)(inputValue));
94 this->outValueGm_.SetGlobalBuffer((__gm__ T*)(value));104 this->outValueGm_.SetGlobalBuffer((__gm__ T*)(value));
95 this->outIndexGm_.SetGlobalBuffer((__gm__ INDEX_TYPE*)(indices));105 this->outIndexGm_.SetGlobalBuffer((__gm__ INDEX_TYPE*)(indices));
96- this->workspaceGm_[0].SetGlobalBuffer((__gm__ CONVERT_TYPE*)(workSpace) + this->rowWorkspaceOffset_,106+ this->workspaceBase_ = (__gm__ CONVERT_TYPE*)workSpace;
97- rowWorkspaceElements);
98- this->workspaceGm_[1].SetGlobalBuffer(
99- (__gm__ CONVERT_TYPE*)(workSpace) + this->rowWorkspaceOffset_ + rowWorkspaceElements, rowWorkspaceElements);
100 uint64_t medianCountOffset = ROUND_UP_AGLIN_UINT64(static_cast<uint64_t>(tilingData->lastAxisNum) *107 uint64_t medianCountOffset = ROUND_UP_AGLIN_UINT64(static_cast<uint64_t>(tilingData->lastAxisNum) *
101 static_cast<uint64_t>(tilingData->unsortedDimNum) *108 static_cast<uint64_t>(tilingData->unsortedDimNum) *
102 MERGE_SORT_WORKSPACE_PARAM * sizeof(uint32_t));109 MERGE_SORT_WORKSPACE_PARAM * sizeof(uint32_t));
103 medianCountGm_.SetGlobalBuffer(reinterpret_cast<__gm__ uint32_t*>(workSpace + medianCountOffset),110 medianCountGm_.SetGlobalBuffer(reinterpret_cast<__gm__ uint32_t*>(workSpace + medianCountOffset),
104 tilingData->lastDimNeedCore * tilingData->unsortedDimNum);111 tilingData->lastDimNeedCore * tilingData->unsortedDimNum);
112+}
105 113 
106- uint32_t tailNum = this->outputLastDimValue_ - (this->frontCoreNum_ - 1) * this->numTileData_;114+// Called by the base Process() loop before each row's sort phase so the ping-pong
107- uint32_t alignTile = ROUND_UP_AGLIN(tailNum);115+// workspace is rebound to the current row's GM region.
116+template <typename T, typename CONVERT_TYPE, bool IS_DESCEND, typename INDEX_TYPE, bool EnableMedian>
117+__aicore__ inline void
118+KthValueMergeSortMoreCore<T, CONVERT_TYPE, IS_DESCEND, INDEX_TYPE, EnableMedian>::PrepareRowWorkspace()
119+{
120+ this->rowDataOffset_ = static_cast<int64_t>(this->rowIdx_) * static_cast<int64_t>(this->outputLastDimValue_);
121+ this->rowWorkspaceOffset_ = static_cast<int64_t>(this->rowIdx_) *
122+ static_cast<int64_t>(this->rowWorkspaceElements_) * 2;
123+ this->workspaceGm_[0].SetGlobalBuffer(this->workspaceBase_ + this->rowWorkspaceOffset_,
124+ this->rowWorkspaceElements_);
125+ this->workspaceGm_[1].SetGlobalBuffer(
126+ this->workspaceBase_ + this->rowWorkspaceOffset_ + this->rowWorkspaceElements_, this->rowWorkspaceElements_);
127+ nonNanCount_ = 0U;
128+}
129+ 
130+template <typename T, typename CONVERT_TYPE, bool IS_DESCEND, typename INDEX_TYPE, bool EnableMedian>
131+__aicore__ inline void
132+KthValueMergeSortMoreCore<T, CONVERT_TYPE, IS_DESCEND, INDEX_TYPE, EnableMedian>::InitSortBuffers()
133+{
134+ uint32_t sortBufferSize = 8;
135+ uint32_t sortTileNum = this->syncMergeSortMode_ ?
136+ this->numTileData_ :
137+ this->outputLastDimValue_ - (this->frontCoreNum_ - 1) * this->numTileData_;
138+ uint32_t alignTile = ROUND_UP_AGLIN(sortTileNum);
108 this->pipe_->InitBuffer(this->inputQueue_, MERGE_MORE_BUFFER_NUM, alignTile * sizeof(T));139 this->pipe_->InitBuffer(this->inputQueue_, MERGE_MORE_BUFFER_NUM, alignTile * sizeof(T));
109 140 
110 this->pipe_->InitBuffer(this->sortedValueUb_, alignTile * sortBufferSize);141 this->pipe_->InitBuffer(this->sortedValueUb_, alignTile * sortBufferSize);
111 this->pipe_->InitBuffer(this->sortedValueIndexUb_, alignTile * sizeof(uint32_t));142 this->pipe_->InitBuffer(this->sortedValueIndexUb_, alignTile * sizeof(uint32_t));
112- this->pipe_->InitBuffer(this->concatTempBuf_, alignTile * sortBufferSize);
113 this->pipe_->InitBuffer(this->sortTempBuf_, alignTile * sortBufferSize);143 this->pipe_->InitBuffer(this->sortTempBuf_, alignTile * sortBufferSize);
114- this->pipe_->InitBuffer(this->sortedValueLocalCastTbuf_, alignTile * sortBufferSize);144+ if constexpr (EnableMedian && IS_MEDIAN_FLOAT_TYPE<T>) {
145+ uint32_t medianScratchBytes = ROUND_UP_AGLIN(this->frontCoreNum_ * sizeof(uint32_t));
146+ this->pipe_->InitBuffer(medianScratchBuf_, medianScratchBytes);
147+ }
115}148}
116 149 
117template <typename T, typename CONVERT_TYPE, bool IS_DESCEND, typename INDEX_TYPE, bool EnableMedian>150template <typename T, typename CONVERT_TYPE, bool IS_DESCEND, typename INDEX_TYPE, bool EnableMedian>
@@ -119,10 +152,15 @@ __aicore__ inline void KthValueMergeSortMoreCore<T, CONVERT_TYPE, IS_DESCEND, IN
119 LocalTensor<T> inputLocal, uint32_t tileNum)152 LocalTensor<T> inputLocal, uint32_t tileNum)
120{153{
121 if constexpr (EnableMedian && IS_MEDIAN_FLOAT_TYPE<T>) {154 if constexpr (EnableMedian && IS_MEDIAN_FLOAT_TYPE<T>) {
122- LocalTensor<float> scratch = this->concatTempBuf_.template Get<float>();155+ if constexpr (KTH_VALUE_ENABLE_STATIC_MEDIAN_FAST_PATH) {
123- uint32_t nonNanCount = CountNonNan(inputLocal, tileNum, scratch, this->pipe_);156+ if (medianMode_ == MEDIAN_MODE_STATIC) {
157+ return;
158+ }
159+ }
160+ LocalTensor<float> scratch = medianScratchBuf_.template Get<float>();
161+ nonNanCount_ += CountNonNan(inputLocal, tileNum, scratch, this->pipe_);
124 LocalTensor<uint32_t> countLocal = scratch.template ReinterpretCast<uint32_t>();162 LocalTensor<uint32_t> countLocal = scratch.template ReinterpretCast<uint32_t>();
125- countLocal.SetValue(0, nonNanCount);163+ countLocal.SetValue(0, nonNanCount_);
126 event_t eventId = static_cast<event_t>(this->pipe_->FetchEventID(HardEvent::S_MTE3));164 event_t eventId = static_cast<event_t>(this->pipe_->FetchEventID(HardEvent::S_MTE3));
127 SetFlag<HardEvent::S_MTE3>(eventId);165 SetFlag<HardEvent::S_MTE3>(eventId);
128 WaitFlag<HardEvent::S_MTE3>(eventId);166 WaitFlag<HardEvent::S_MTE3>(eventId);
@@ -150,8 +188,9 @@ template <typename T, typename CONVERT_TYPE, bool IS_DESCEND, typename INDEX_TYP
150__aicore__ inline uint32_t188__aicore__ inline uint32_t
151KthValueMergeSortMoreCore<T, CONVERT_TYPE, IS_DESCEND, INDEX_TYPE, EnableMedian>::ResolveRowK()189KthValueMergeSortMoreCore<T, CONVERT_TYPE, IS_DESCEND, INDEX_TYPE, EnableMedian>::ResolveRowK()
152{190{
153- LocalTensor<uint32_t> countLocal = this->concatTempBuf_.template Get<uint32_t>();191+ LocalTensor<uint32_t> countLocal = medianScratchBuf_.template Get<uint32_t>();
154- uint32_t countOffset = this->rowIdx_ * this->frontCoreNum_;192+ // Each round finishes before the next one starts, so the physical row-group count slots can be reused.
193+ uint32_t countOffset = this->rowGroupIdx_ * this->frontCoreNum_;
155 DataCopyExtParams copyParams{1, this->frontCoreNum_ * static_cast<uint32_t>(sizeof(uint32_t)), 0, 0, 0};194 DataCopyExtParams copyParams{1, this->frontCoreNum_ * static_cast<uint32_t>(sizeof(uint32_t)), 0, 0, 0};
156 DataCopyPad(countLocal, medianCountGm_[countOffset], copyParams, {false, 0, 0, 0});195 DataCopyPad(countLocal, medianCountGm_[countOffset], copyParams, {false, 0, 0, 0});
157 event_t eventId = static_cast<event_t>(this->pipe_->FetchEventID(HardEvent::MTE2_S));196 event_t eventId = static_cast<event_t>(this->pipe_->FetchEventID(HardEvent::MTE2_S));
@@ -161,7 +200,7 @@ KthValueMergeSortMoreCore<T, CONVERT_TYPE, IS_DESCEND, INDEX_TYPE, EnableMedian>
161 for (uint32_t core = 0U; core < this->frontCoreNum_; ++core) {200 for (uint32_t core = 0U; core < this->frontCoreNum_; ++core) {
162 nonNanCount += countLocal.GetValue(core);201 nonNanCount += countLocal.GetValue(core);
163 }202 }
164- return ResolveMedianK(kthIndex_, this->outputLastDimValue_, nonNanCount, medianMode_);203+ return ResolveMedianK(staticKthIndex_, this->outputLastDimValue_, nonNanCount, medianMode_);
165}204}
166 205 
167template <typename T, typename CONVERT_TYPE, bool IS_DESCEND, typename INDEX_TYPE, bool EnableMedian>206template <typename T, typename CONVERT_TYPE, bool IS_DESCEND, typename INDEX_TYPE, bool EnableMedian>
@@ -186,8 +225,14 @@ __aicore__ inline void
186KthValueMergeSortMoreCore<T, CONVERT_TYPE, IS_DESCEND, INDEX_TYPE, EnableMedian>::ExtractAndCopyOut()225KthValueMergeSortMoreCore<T, CONVERT_TYPE, IS_DESCEND, INDEX_TYPE, EnableMedian>::ExtractAndCopyOut()
187{226{
188 if constexpr (EnableMedian && IS_MEDIAN_FLOAT_TYPE<T>) {227 if constexpr (EnableMedian && IS_MEDIAN_FLOAT_TYPE<T>) {
189- if (this->outOffset_ == 0) {228+ if constexpr (KTH_VALUE_ENABLE_STATIC_MEDIAN_FAST_PATH) {
190- kthIndex_ = ResolveRowK();229+ if (medianMode_ != MEDIAN_MODE_STATIC && this->outOffset_ == 0) {
230+ kthIndex_ = ResolveRowK();
231+ }
232+ } else {
233+ if (this->outOffset_ == 0) {
234+ kthIndex_ = ResolveRowK();
235+ }
191 }236 }
192 }237 }
193 LocalTensor<CONVERT_TYPE> sortTempBuffer = this->sortedQueue_.template DeQue<CONVERT_TYPE>();238 LocalTensor<CONVERT_TYPE> sortTempBuffer = this->sortedQueue_.template DeQue<CONVERT_TYPE>();
@@ -12,28 +12,80 @@
12#define KTH_VALUE_MERGE_SORT_ONE_CORE_H12#define KTH_VALUE_MERGE_SORT_ONE_CORE_H
13 13 
14#include <cmath>14#include <cmath>
15+#include <limits>
15#include "kernel_operator.h"16#include "kernel_operator.h"
16#include "op_kernel/platform_util.h"17#include "op_kernel/platform_util.h"
17#include "kth_value_median_utils.h"18#include "kth_value_median_utils.h"
18#include "kth_value_tiling_data.h"19#include "kth_value_tiling_data.h"
19#include "common/merge_sort_constants.h"20#include "common/merge_sort_constants.h"
21+#include "common/ping_pong_merge_sort.h"
20#include "common/util_type_simd.h"22#include "common/util_type_simd.h"
21 23 
22namespace KthValue {24namespace KthValue {
23using namespace AscendC;25using namespace AscendC;
24 26 
25-using MergeSortConstants::DEALING_CONCAT_NUM_ONCE;
26using MergeSortConstants::DEALING_SORT_NUM_ONCE;27using MergeSortConstants::DEALING_SORT_NUM_ONCE;
27using MergeSortConstants::XOR_OP_VALUE_FP;28using MergeSortConstants::XOR_OP_VALUE_FP;
28using MergeSortConstants::XOR_OP_VALUE_HALF;29using MergeSortConstants::XOR_OP_VALUE_HALF;
29 30 
30constexpr uint32_t UB_BLOCK_SIZE = Ops::Base::GetUbBlockSize();31constexpr uint32_t UB_BLOCK_SIZE = Ops::Base::GetUbBlockSize();
31 32 
33+// Gather one fixed-rank element per row and widen its nonnegative index.
34+// Rebase each value block so its relative gather offsets fit in uint16_t.
35+__aicore__ inline void GatherKthInt16(LocalTensor<int16_t>& values, LocalTensor<uint32_t>& indices,
36+ LocalTensor<int16_t>& outValues, LocalTensor<int64_t>& outIndices, uint32_t rows,
37+ uint32_t stride, uint32_t kth)
38+{
39+ constexpr uint32_t lanes = Ops::Base::GetVRegSize() / sizeof(uint32_t);
40+ constexpr uint32_t wideLanes = Ops::Base::GetVRegSize() / sizeof(int64_t);
41+ __ubuf__ int16_t* valuePtr = reinterpret_cast<__ubuf__ int16_t*>(values.GetPhyAddr());
42+ __ubuf__ uint32_t* indexPtr = reinterpret_cast<__ubuf__ uint32_t*>(indices.GetPhyAddr());
43+ __ubuf__ int16_t* valueOut = reinterpret_cast<__ubuf__ int16_t*>(outValues.GetPhyAddr());
44+ __ubuf__ int64_t* indexOut = reinterpret_cast<__ubuf__ int64_t*>(outIndices.GetPhyAddr());
45+ uint16_t repeats = static_cast<uint16_t>(CeilDivision(rows, lanes));
46+ uint32_t remain = rows;
47+ __VEC_SCOPE__
48+ {
49+ Reg::RegTensor<int32_t> arange;
50+ Reg::RegTensor<uint32_t> offsets, selected, zero;
51+ Reg::RegTensor<int64_t> low, high;
52+ Reg::RegTensor<int16_t> result;
53+ Reg::RegTensor<uint16_t> narrowOffsets, highWords;
54+ Reg::MaskReg full = Reg::CreateMask<uint32_t>();
55+ Reg::Arange(arange, 0);
56+ Reg::Muls(arange, arange, static_cast<int32_t>(stride), full);
57+ Reg::Adds(arange, arange, static_cast<int32_t>(kth), full);
58+ offsets = (Reg::RegTensor<uint32_t>&)arange;
59+ Reg::Duplicate(zero, 0U);
60+ Reg::DeInterleave(narrowOffsets, highWords, (Reg::RegTensor<uint16_t>&)offsets,
61+ (Reg::RegTensor<uint16_t>&)offsets);
62+ for (uint16_t rep = 0; rep < repeats; ++rep) {
63+ uint32_t count = remain < lanes ? remain : lanes;
64+ uint32_t valueCount = count;
65+ uint32_t lowCount = count < wideLanes ? count : wideLanes;
66+ uint32_t highCount = count > wideLanes ? count - wideLanes : 0;
67+ Reg::MaskReg valid = Reg::UpdateMask<uint32_t>(remain);
68+ Reg::MaskReg valueMask = Reg::UpdateMask<int16_t>(valueCount);
69+ Reg::MaskReg lowMask = Reg::UpdateMask<int64_t>(lowCount);
70+ Reg::MaskReg highMask = Reg::UpdateMask<int64_t>(highCount);
71+ Reg::Gather(result, valuePtr + rep * lanes * stride, narrowOffsets, valueMask);
72+ Reg::Gather(selected, indexPtr, offsets, valid);
73+ Reg::Interleave((Reg::RegTensor<uint32_t>&)low, (Reg::RegTensor<uint32_t>&)high, selected, zero);
74+ Reg::StoreAlign(valueOut + rep * lanes, result, valueMask);
75+ Reg::StoreAlign(indexOut + rep * lanes, low, lowMask);
76+ if (count > wideLanes) {
77+ Reg::StoreAlign(indexOut + rep * lanes + wideLanes, high, highMask);
78+ }
79+ Reg::Adds(offsets, offsets, stride * lanes, full);
80+ }
81+ }
82+}
83+ 
32template <typename T, typename CONVERT_TYPE, uint64_t isSort32SmallAxis = 0, bool EnableMedian = false>84template <typename T, typename CONVERT_TYPE, uint64_t isSort32SmallAxis = 0, bool EnableMedian = false>
33class KthValueMergeSortOneCore {85class KthValueMergeSortOneCore {
34public:86public:
35- __aicore__ inline void Init(GM_ADDR x, GM_ADDR values, GM_ADDR indices, const KthValueTilingData* tiling,87+ __aicore__ inline void Init(GM_ADDR x, GM_ADDR values, GM_ADDR indices,
36- TPipe* pipe);88+ const KthValueMergeOneCoreTilingData* tiling, TPipe* pipe);
37 __aicore__ inline void Process();89 __aicore__ inline void Process();
38 90 
39private:91private:
@@ -43,27 +95,34 @@ private:
43 __aicore__ inline void FlipSignBit(LocalTensor<CONVERT_TYPE> xLocal, uint32_t offset, uint32_t count);95 __aicore__ inline void FlipSignBit(LocalTensor<CONVERT_TYPE> xLocal, uint32_t offset, uint32_t count);
44 __aicore__ inline void SortRows(LocalTensor<T> xLocal, LocalTensor<T> sortedValueLocal,96 __aicore__ inline void SortRows(LocalTensor<T> xLocal, LocalTensor<T> sortedValueLocal,
45 LocalTensor<uint32_t> sortedIndexLocal, uint32_t rowNum);97 LocalTensor<uint32_t> sortedIndexLocal, uint32_t rowNum);
98+ __aicore__ inline void PadConvertedInt16(LocalTensor<CONVERT_TYPE> xSortLocal, uint32_t rowNum);
99+ __aicore__ inline void SortCompactBatches(LocalTensor<CONVERT_TYPE> xSortLocal,
100+ LocalTensor<CONVERT_TYPE> sortedValueCast,
101+ LocalTensor<uint32_t> sortedIndexLocal, uint32_t rowNum,
102+ uint32_t outputStride);
103+ __aicore__ inline void SelectScalarRows(LocalTensor<T> sortedValueLocal, LocalTensor<uint32_t> sortedIndexLocal,
104+ LocalTensor<T> compactValue, LocalTensor<int64_t> compactIndex,
105+ uint32_t rowNum, uint32_t outputStride, uint32_t outputK);
46 __aicore__ inline void CopyKthToGm(uint64_t outputOffset, uint32_t rowNum);106 __aicore__ inline void CopyKthToGm(uint64_t outputOffset, uint32_t rowNum);
47 __aicore__ inline void ProcessSingleRound(uint32_t round);107 __aicore__ inline void ProcessSingleRound(uint32_t round);
48 108 
49 static constexpr uint32_t SORT_STRUCT_BYTES = 8;109 static constexpr uint32_t SORT_STRUCT_BYTES = 8;
50 110 
111+ uint32_t mergeBatchRows_ = 1U;
51 GlobalTensor<T> xGm_;112 GlobalTensor<T> xGm_;
52 GlobalTensor<T> valuesGm_;113 GlobalTensor<T> valuesGm_;
53 GlobalTensor<int64_t> indicesGm_;114 GlobalTensor<int64_t> indicesGm_;
54 115 
55 TPipe* pipe_{nullptr};116 TPipe* pipe_{nullptr};
56- const KthValueTilingData* tiling_{nullptr};117+ const KthValueMergeOneCoreTilingData* tiling_{nullptr};
57 TQue<QuePosition::VECIN, 1> inQueueX_;118 TQue<QuePosition::VECIN, 1> inQueueX_;
58 TQue<QuePosition::VECOUT, 1> outValueQueue_;119 TQue<QuePosition::VECOUT, 1> outValueQueue_;
59 TQue<QuePosition::VECOUT, 1> outIndexQueue_;120 TQue<QuePosition::VECOUT, 1> outIndexQueue_;
60 TQue<QuePosition::VECOUT, 1> compactValueQueue_;121 TQue<QuePosition::VECOUT, 1> compactValueQueue_;
61 TQue<QuePosition::VECOUT, 1> compactIndexQueue_;122 TQue<QuePosition::VECOUT, 1> compactIndexQueue_;
62- TBuf<TPosition::VECCALC> concatTmpBuf_;
63 TBuf<TPosition::VECCALC> sortTmpBuf_;123 TBuf<TPosition::VECCALC> sortTmpBuf_;
64 TBuf<TPosition::VECCALC> sortedLocalBuf_;124 TBuf<TPosition::VECCALC> sortedLocalBuf_;
65 TBuf<TPosition::VECCALC> xCastBuf_;125 TBuf<TPosition::VECCALC> xCastBuf_;
66- TBuf<TPosition::VECCALC> sortedValueCastBuf_;
67 TBuf<TPosition::VECCALC> indexLocalBuf_;126 TBuf<TPosition::VECCALC> indexLocalBuf_;
68 LocalTensor<uint32_t> indexLocal_;127 LocalTensor<uint32_t> indexLocal_;
69 128 
@@ -80,7 +139,7 @@ private:
80 139 
81template <typename T, typename CONVERT_TYPE, uint64_t isSort32SmallAxis, bool EnableMedian>140template <typename T, typename CONVERT_TYPE, uint64_t isSort32SmallAxis, bool EnableMedian>
82__aicore__ inline void KthValueMergeSortOneCore<T, CONVERT_TYPE, isSort32SmallAxis, EnableMedian>::Init(141__aicore__ inline void KthValueMergeSortOneCore<T, CONVERT_TYPE, isSort32SmallAxis, EnableMedian>::Init(
83- GM_ADDR x, GM_ADDR values, GM_ADDR indices, const KthValueTilingData* tiling, TPipe* pipe)142+ GM_ADDR x, GM_ADDR values, GM_ADDR indices, const KthValueMergeOneCoreTilingData* tiling, TPipe* pipe)
84{143{
85 if (tiling == nullptr || pipe == nullptr) {144 if (tiling == nullptr || pipe == nullptr) {
86 return;145 return;
@@ -96,22 +155,34 @@ __aicore__ inline void KthValueMergeSortOneCore<T, CONVERT_TYPE, isSort32SmallAx
96 155 
97 uint32_t bufferNum = tiling_->keyParams4 == 0U ? 1U : tiling_->keyParams4;156 uint32_t bufferNum = tiling_->keyParams4 == 0U ? 1U : tiling_->keyParams4;
98 pipe_->InitBuffer(inQueueX_, bufferNum, tiling_->keyParams1);157 pipe_->InitBuffer(inQueueX_, bufferNum, tiling_->keyParams1);
99- pipe_->InitBuffer(outValueQueue_, bufferNum, tiling_->keyParams1);158+ constexpr bool compact = !EnableMedian && IsSameType<T, int16_t>::value;
100- pipe_->InitBuffer(outIndexQueue_, bufferNum, tiling_->keyParams2);159+ if constexpr (compact) {
160+ if (alignSize_ >= KTH_INT16_MERGE_BATCH_MIN_AXIS && alignSize_ <= KTH_INT16_MERGE_BATCH_MAX_AXIS &&
161+ oneCoreRowNum_ >= KTH_INT16_MERGE_BATCH_ROWS) {
162+ mergeBatchRows_ = KTH_INT16_MERGE_BATCH_ROWS;
163+ }
164+ }
165+ uint32_t outputElements = DEALING_SORT_NUM_ONCE * oneCoreRowNum_;
166+ pipe_->InitBuffer(outValueQueue_, bufferNum, compact ? outputElements * sizeof(T) : tiling_->keyParams1);
167+ pipe_->InitBuffer(outIndexQueue_, bufferNum, compact ? outputElements * sizeof(uint32_t) : tiling_->keyParams2);
101 pipe_->InitBuffer(compactValueQueue_, bufferNum, ROUND_UP_AGLIN(oneCoreRowNum_ * sizeof(T)));168 pipe_->InitBuffer(compactValueQueue_, bufferNum, ROUND_UP_AGLIN(oneCoreRowNum_ * sizeof(T)));
102 pipe_->InitBuffer(compactIndexQueue_, bufferNum, ROUND_UP_AGLIN(oneCoreRowNum_ * sizeof(int64_t)));169 pipe_->InitBuffer(compactIndexQueue_, bufferNum, ROUND_UP_AGLIN(oneCoreRowNum_ * sizeof(int64_t)));
103 170 
104- uint32_t sortStructElems = alignSize_ * SORT_STRUCT_BYTES;171+ uint32_t sortStructBytes = alignSize_ * SORT_STRUCT_BYTES * mergeBatchRows_;
105- pipe_->InitBuffer(indexLocalBuf_, alignSize_ * sizeof(uint32_t));172+ pipe_->InitBuffer(indexLocalBuf_, alignSize_ * sizeof(uint32_t) * mergeBatchRows_);
106- pipe_->InitBuffer(concatTmpBuf_, tiling_->tmpUbSize);173+ pipe_->InitBuffer(sortTmpBuf_, sortStructBytes);
107- pipe_->InitBuffer(sortTmpBuf_, sortStructElems * sizeof(CONVERT_TYPE));174+ pipe_->InitBuffer(sortedLocalBuf_, sortStructBytes);
108- pipe_->InitBuffer(sortedLocalBuf_, sortStructElems * sizeof(CONVERT_TYPE));
109 if constexpr (!IsSameType<T, CONVERT_TYPE>::value) {175 if constexpr (!IsSameType<T, CONVERT_TYPE>::value) {
110 pipe_->InitBuffer(xCastBuf_, alignSize_ * oneCoreRowNum_ * sizeof(CONVERT_TYPE));176 pipe_->InitBuffer(xCastBuf_, alignSize_ * oneCoreRowNum_ * sizeof(CONVERT_TYPE));
111- pipe_->InitBuffer(sortedValueCastBuf_, alignSize_ * oneCoreRowNum_ * sizeof(CONVERT_TYPE));
112 }177 }
113 indexLocal_ = indexLocalBuf_.AllocTensor<uint32_t>();178 indexLocal_ = indexLocalBuf_.AllocTensor<uint32_t>();
114 InitIndexLocal();179 InitIndexLocal();
180+ // Each row keeps its own zero-based indices even when Sort32 processes multiple rows.
181+ if (mergeBatchRows_ > 1U) {
182+ for (uint32_t row = 1; row < mergeBatchRows_; ++row) {
183+ DataCopy(indexLocal_[row * alignSize_], indexLocal_, alignSize_);
184+ }
185+ }
115}186}
116 187 
117template <typename T, typename CONVERT_TYPE, uint64_t isSort32SmallAxis, bool EnableMedian>188template <typename T, typename CONVERT_TYPE, uint64_t isSort32SmallAxis, bool EnableMedian>
@@ -152,7 +223,11 @@ __aicore__ inline void KthValueMergeSortOneCore<T, CONVERT_TYPE, isSort32SmallAx
152 uint64_t tileOffset, uint32_t currTileSize, uint32_t rowNum)223 uint64_t tileOffset, uint32_t currTileSize, uint32_t rowNum)
153{224{
154 LocalTensor<T> xLocal = inQueueX_.AllocTensor<T>();225 LocalTensor<T> xLocal = inQueueX_.AllocTensor<T>();
155- Duplicate(xLocal, static_cast<T>(NAN), alignSize_ * rowNum);226+ T padding = 0;
227+ if constexpr (!IsSameType<T, int16_t>::value) {
228+ padding = static_cast<T>(NAN);
229+ }
230+ Duplicate(xLocal, padding, alignSize_ * rowNum);
156 event_t eventId = static_cast<event_t>(pipe_->FetchEventID(HardEvent::V_MTE2));231 event_t eventId = static_cast<event_t>(pipe_->FetchEventID(HardEvent::V_MTE2));
157 SetFlag<HardEvent::V_MTE2>(eventId);232 SetFlag<HardEvent::V_MTE2>(eventId);
158 WaitFlag<HardEvent::V_MTE2>(eventId);233 WaitFlag<HardEvent::V_MTE2>(eventId);
@@ -160,8 +235,7 @@ __aicore__ inline void KthValueMergeSortOneCore<T, CONVERT_TYPE, isSort32SmallAx
160 uint32_t currTileSizeAlign = (currTileSize * sizeof(T) + UB_BLOCK_SIZE - 1U) / UB_BLOCK_SIZE * UB_BLOCK_SIZE /235 uint32_t currTileSizeAlign = (currTileSize * sizeof(T) + UB_BLOCK_SIZE - 1U) / UB_BLOCK_SIZE * UB_BLOCK_SIZE /
161 sizeof(T);236 sizeof(T);
162 uint32_t dstStride = ((alignSize_ - currTileSizeAlign) * sizeof(T)) / UB_BLOCK_SIZE;237 uint32_t dstStride = ((alignSize_ - currTileSizeAlign) * sizeof(T)) / UB_BLOCK_SIZE;
163- DataCopyPadExtParams<T> padParams{true, 0, static_cast<uint8_t>(currTileSizeAlign - currTileSize),238+ DataCopyPadExtParams<T> padParams{true, 0, static_cast<uint8_t>(currTileSizeAlign - currTileSize), padding};
164- static_cast<T>(NAN)};
165 DataCopyExtParams copyParam{static_cast<uint16_t>(rowNum), static_cast<uint32_t>(currTileSize * sizeof(T)), 0,239 DataCopyExtParams copyParam{static_cast<uint16_t>(rowNum), static_cast<uint32_t>(currTileSize * sizeof(T)), 0,
166 dstStride, 0};240 dstStride, 0};
167 DataCopyPad(xLocal, xGm_[tileOffset], copyParam, padParams);241 DataCopyPad(xLocal, xGm_[tileOffset], copyParam, padParams);
@@ -181,47 +255,168 @@ __aicore__ inline void KthValueMergeSortOneCore<T, CONVERT_TYPE, isSort32SmallAx
181 }255 }
182}256}
183 257 
258+template <typename T, typename CONVERT_TYPE, uint64_t isSort32SmallAxis, bool EnableMedian>
259+__aicore__ inline void KthValueMergeSortOneCore<T, CONVERT_TYPE, isSort32SmallAxis, EnableMedian>::PadConvertedInt16(
260+ LocalTensor<CONVERT_TYPE> xSortLocal, uint32_t rowNum)
261+{
262+ // Integer padding cannot represent infinity. Mark it after the exact
263+ // conversion so it sorts after every real value, including INT16_MAX.
264+ constexpr uint32_t vectorElems = Ops::Base::GetVRegSize() / sizeof(float);
265+ uint32_t tailStart = numTileData_ / vectorElems * vectorElems;
266+ uint32_t firstPadLane = numTileData_ - tailStart;
267+ uint32_t tailElems = alignSize_ - tailStart;
268+ uint32_t rowStride = alignSize_;
269+ __ubuf__ float* castPtr = reinterpret_cast<__ubuf__ float*>(xSortLocal.GetPhyAddr());
270+ __VEC_SCOPE__
271+ {
272+ Reg::RegTensor<float> infinity;
273+ Reg::RegTensor<int32_t> lanes;
274+ Reg::MaskReg valid = Reg::UpdateMask<float>(tailElems);
275+ Reg::MaskReg paddingMask;
276+ Reg::Arange(lanes, 0);
277+ Reg::Compares<int32_t, CMPMODE::GE>(paddingMask, lanes, static_cast<int32_t>(firstPadLane), valid);
278+ Reg::Duplicate(infinity, static_cast<float>(INFINITY));
279+ for (uint16_t row = 0; row < static_cast<uint16_t>(rowNum); ++row) {
280+ Reg::StoreAlign<float, Reg::StoreDist::DIST_NORM_B32>(castPtr + row * rowStride + tailStart, infinity,
281+ paddingMask);
282+ }
283+ }
284+}
285+ 
286+template <typename T, typename CONVERT_TYPE, uint64_t isSort32SmallAxis, bool EnableMedian>
287+__aicore__ inline void KthValueMergeSortOneCore<T, CONVERT_TYPE, isSort32SmallAxis, EnableMedian>::SortCompactBatches(
288+ LocalTensor<CONVERT_TYPE> xSortLocal, LocalTensor<CONVERT_TYPE> sortedValueCast,
289+ LocalTensor<uint32_t> sortedIndexLocal, uint32_t rowNum, uint32_t outputStride)
290+{
291+ uint32_t sortRepeatTimes = alignSize_ / DEALING_SORT_NUM_ONCE;
292+ LocalTensor<CONVERT_TYPE> sortedLocal = sortedLocalBuf_.Get<CONVERT_TYPE>();
293+ LocalTensor<CONVERT_TYPE> sortTmp = sortTmpBuf_.Get<CONVERT_TYPE>();
294+ for (uint32_t first = 0; first < rowNum; first += mergeBatchRows_) {
295+ uint32_t rows = rowNum - first < mergeBatchRows_ ? rowNum - first : mergeBatchRows_;
296+ Sort32(sortedLocal, xSortLocal[first * alignSize_], indexLocal_, sortRepeatTimes * rows);
297+ bool inPing = true;
298+ uint32_t runCount = sortRepeatTimes;
299+ for (uint32_t run = DEALING_SORT_NUM_ONCE; runCount > 1U; run *= MergeSortConstants::MERGE_LIST_MAX_NUM) {
300+ for (uint32_t row = 0; row < rows; ++row) {
301+ uint32_t offset = GetSortOffset<CONVERT_TYPE>(row * alignSize_);
302+ if (inPing) {
303+ PingPongMergeSortCommon::MergeStage(sortTmp[offset], sortedLocal[offset], alignSize_, run,
304+ runCount);
305+ } else {
306+ PingPongMergeSortCommon::MergeStage(sortedLocal[offset], sortTmp[offset], alignSize_, run,
307+ runCount);
308+ }
309+ }
310+ inPing = !inPing;
311+ runCount = (runCount + MergeSortConstants::MERGE_LIST_MAX_NUM - 1U) /
312+ MergeSortConstants::MERGE_LIST_MAX_NUM;
313+ }
314+ LocalTensor<CONVERT_TYPE> proposal = inPing ? sortedLocal : sortTmp;
315+ __ubuf__ uint32_t* packed = (__ubuf__ uint32_t*)proposal.GetPhyAddr();
316+ __ubuf__ uint32_t* valueOut = (__ubuf__ uint32_t*)sortedValueCast.GetPhyAddr() + first * outputStride;
317+ __ubuf__ uint32_t* indexOut = (__ubuf__ uint32_t*)sortedIndexLocal.GetPhyAddr() + first * outputStride;
318+ // Each FP32 proposal stores the value followed by its original B32 index.
319+ // Gather only k, keeping one result per row instead of extracting a Sort32 block.
320+ constexpr uint32_t proposalWords = SORT_STRUCT_BYTES / sizeof(uint32_t);
321+ constexpr uint32_t proposalIndexWord = 1U;
322+ static_assert(KTH_INT16_MERGE_BATCH_ROWS <= Ops::Base::GetVRegSize() / sizeof(uint32_t));
323+ uint32_t proposalStride = alignSize_ * proposalWords;
324+ uint32_t selected = kthIndex_ * proposalWords;
325+ __VEC_SCOPE__
326+ {
327+ Reg::RegTensor<uint32_t> lanes, srcIdx, values, indices;
328+ Reg::MaskReg mask = Reg::UpdateMask<uint32_t>(rows);
329+ Reg::Arange((Reg::RegTensor<int32_t>&)lanes, 0);
330+ Reg::Muls(srcIdx, lanes, proposalStride, mask);
331+ Reg::Adds(srcIdx, srcIdx, selected, mask);
332+ Reg::Gather(values, packed, srcIdx, mask);
333+ Reg::Adds(srcIdx, srcIdx, proposalIndexWord, mask);
334+ Reg::Gather(indices, packed, srcIdx, mask);
335+ Reg::StoreAlign(valueOut, values, mask);
336+ Reg::StoreAlign(indexOut, indices, mask);
337+ }
338+ }
339+}
340+ 
184template <typename T, typename CONVERT_TYPE, uint64_t isSort32SmallAxis, bool EnableMedian>341template <typename T, typename CONVERT_TYPE, uint64_t isSort32SmallAxis, bool EnableMedian>
185__aicore__ inline void KthValueMergeSortOneCore<T, CONVERT_TYPE, isSort32SmallAxis, EnableMedian>::SortRows(342__aicore__ inline void KthValueMergeSortOneCore<T, CONVERT_TYPE, isSort32SmallAxis, EnableMedian>::SortRows(
186 LocalTensor<T> xLocal, LocalTensor<T> sortedValueLocal, LocalTensor<uint32_t> sortedIndexLocal, uint32_t rowNum)343 LocalTensor<T> xLocal, LocalTensor<T> sortedValueLocal, LocalTensor<uint32_t> sortedIndexLocal, uint32_t rowNum)
187{344{
188 uint32_t sortRepeatTimes = alignSize_ / DEALING_SORT_NUM_ONCE;345 uint32_t sortRepeatTimes = alignSize_ / DEALING_SORT_NUM_ONCE;
189- uint32_t concatRepeatTimes = alignSize_ / DEALING_CONCAT_NUM_ONCE;
190- LocalTensor<CONVERT_TYPE> concatTmp = concatTmpBuf_.Get<CONVERT_TYPE>();
191 LocalTensor<CONVERT_TYPE> sortedLocal = sortedLocalBuf_.Get<CONVERT_TYPE>();346 LocalTensor<CONVERT_TYPE> sortedLocal = sortedLocalBuf_.Get<CONVERT_TYPE>();
192 LocalTensor<CONVERT_TYPE> sortTmp = sortTmpBuf_.Get<CONVERT_TYPE>();347 LocalTensor<CONVERT_TYPE> sortTmp = sortTmpBuf_.Get<CONVERT_TYPE>();
193 LocalTensor<CONVERT_TYPE> xSortLocal;348 LocalTensor<CONVERT_TYPE> xSortLocal;
194 LocalTensor<CONVERT_TYPE> sortedValueCast;349 LocalTensor<CONVERT_TYPE> sortedValueCast;
195 if constexpr (!IsSameType<T, CONVERT_TYPE>::value) {350 if constexpr (!IsSameType<T, CONVERT_TYPE>::value) {
196 xSortLocal = xCastBuf_.Get<CONVERT_TYPE>();351 xSortLocal = xCastBuf_.Get<CONVERT_TYPE>();
197- sortedValueCast = sortedValueCastBuf_.Get<CONVERT_TYPE>();352+ // SortToProposal consumes each source row before Extract, so the cast input can hold the result.
黄
黄黄晓彬9月17日

【函数规模】SortRows 由修改前 41 行增至 112 行 NBNC,超过本轮检视采用的 50 行阈值。

建议将类型转换、批量排序和结果提取等独立阶段拆成职责明确的 helper,保持现有 buffer 生命周期及流水线同步关系。

统计范围为当前文件第 253–373 行;剔除空行/注释,保留签名、大括号及函数内预处理指令,不计函数前独立 template/修饰宏行。此为静态 NBNC 复核,非正式 CodeCheck 输出。

likedislike
黄晓彬
9月17日 评论:
353+ sortedValueCast = xSortLocal;
198 Cast(xSortLocal, xLocal, RoundMode::CAST_NONE, alignSize_ * rowNum);354 Cast(xSortLocal, xLocal, RoundMode::CAST_NONE, alignSize_ * rowNum);
355+ if constexpr (IsSameType<T, int16_t>::value) {
356+ PadConvertedInt16(xSortLocal, rowNum);
357+ }
199 } else {358 } else {
200 xSortLocal = xLocal;359 xSortLocal = xLocal;
201 sortedValueCast = sortedValueLocal;360 sortedValueCast = sortedValueLocal;
202 }361 }
203- for (uint32_t row = 0; row < rowNum; ++row) {362+ if constexpr (EnableMedian && IS_MEDIAN_FLOAT_TYPE<CONVERT_TYPE>) {
204- uint32_t offset = row * alignSize_;363+ if (medianMode_ != MEDIAN_MODE_STATIC) {
205- if constexpr (EnableMedian && IS_MEDIAN_FLOAT_TYPE<CONVERT_TYPE>) {364+ for (uint32_t row = 0, offset = 0; row < rowNum; ++row, offset += alignSize_) {
206- if (medianMode_ != MEDIAN_MODE_STATIC) {
207 CanonicalizeNanValues(xSortLocal[offset], alignSize_);365 CanonicalizeNanValues(xSortLocal[offset], alignSize_);
208 }366 }
209 }367 }
210- // MergeSort sorts sign-flipped ascending keys. Flip before Sort/Sort32 and flip back after Extract.
211- FlipSignBit(xSortLocal, offset, alignSize_);
212- if constexpr (isSort32SmallAxis == 1) {
213- Sort32<CONVERT_TYPE>(sortedLocal, xSortLocal[offset], indexLocal_, 1);
214- } else {
215- LocalTensor<CONVERT_TYPE> concatLocal;
216- Concat(concatLocal, xSortLocal[offset], concatTmp, concatRepeatTimes);
217- AscendC::Sort<CONVERT_TYPE, true>(sortedLocal, concatLocal, indexLocal_, sortTmp, sortRepeatTimes);
218- }
219- Extract(sortedValueCast[offset], sortedIndexLocal[offset], sortedLocal,
220- isSort32SmallAxis == 1 ? 1 : sortRepeatTimes);
221- FlipSignBit(sortedValueCast, offset, alignSize_);
222 }368 }
369+ // MergeSort sorts sign-flipped ascending keys. Flip before Sort/Sort32 and flip back after Extract.
370+ FlipSignBit(xSortLocal, 0, alignSize_ * rowNum);
371+ // Batched int16 selects k directly; the single-row fallback extracts its Sort32 block.
372+ constexpr bool compactExtract = !EnableMedian && IsSameType<T, int16_t>::value;
373+ uint32_t outputStride = compactExtract ? (mergeBatchRows_ > 1U ? 1U : DEALING_SORT_NUM_ONCE) : alignSize_;
374+ // Batch Sort32 and process rows at each merge level; each MergeStage remains confined to one row.
375+ if (compactExtract && mergeBatchRows_ > 1U) {
376+ SortCompactBatches(xSortLocal, sortedValueCast, sortedIndexLocal, rowNum, outputStride);
377+ } else {
378+ for (uint32_t row = 0, offset = 0; row < rowNum; ++row, offset += alignSize_) {
379+ bool resultInPing = PingPongMergeSortCommon::SortToProposal(sortedLocal, sortTmp, xSortLocal[offset],
380+ indexLocal_, sortRepeatTimes);
381+ // Packed results overwrite only rows whose input has already been consumed.
382+ LocalTensor<CONVERT_TYPE> proposal = resultInPing ? sortedLocal : sortTmp;
383+ uint32_t proposalOffset = compactExtract ? GetSortOffset<CONVERT_TYPE>(kthIndex_ / DEALING_SORT_NUM_ONCE *
384+ DEALING_SORT_NUM_ONCE) :
385+ 0U;
386+ Extract(sortedValueCast[row * outputStride], sortedIndexLocal[row * outputStride], proposal[proposalOffset],
387+ compactExtract || isSort32SmallAxis == 1 ? 1 : sortRepeatTimes);
388+ }
389+ }
390+ FlipSignBit(sortedValueCast, 0, outputStride * rowNum);
223 if constexpr (!IsSameType<T, CONVERT_TYPE>::value) {391 if constexpr (!IsSameType<T, CONVERT_TYPE>::value) {
224- Cast(sortedValueLocal, sortedValueCast, RoundMode::CAST_RINT, alignSize_ * rowNum);392+ Cast(sortedValueLocal, sortedValueCast, RoundMode::CAST_RINT, outputStride * rowNum);
393+ }
394+}
395+ 
396+template <typename T, typename CONVERT_TYPE, uint64_t isSort32SmallAxis, bool EnableMedian>
397+__aicore__ inline void KthValueMergeSortOneCore<T, CONVERT_TYPE, isSort32SmallAxis, EnableMedian>::SelectScalarRows(
398+ LocalTensor<T> sortedValueLocal, LocalTensor<uint32_t> sortedIndexLocal, LocalTensor<T> compactValue,
399+ LocalTensor<int64_t> compactIndex, uint32_t rowNum, uint32_t outputStride, uint32_t outputK)
400+{
401+ for (uint32_t row = 0; row < rowNum; ++row) {
402+ uint32_t rowOffset = row * outputStride;
403+ uint32_t selectedK = outputK;
404+ if constexpr (EnableMedian && IS_MEDIAN_FLOAT_TYPE<T>) {
405+ if constexpr (KTH_VALUE_ENABLE_STATIC_MEDIAN_FAST_PATH) {
406+ if (medianMode_ != MEDIAN_MODE_STATIC) {
407+ uint32_t nonNanCount = CountNonNan(sortedValueLocal[rowOffset], numTileData_,
408+ sortTmpBuf_.Get<float>(), pipe_);
409+ selectedK = ResolveMedianK(kthIndex_, numTileData_, nonNanCount, medianMode_);
410+ }
411+ } else {
412+ uint32_t nonNanCount = CountNonNan(sortedValueLocal[rowOffset], numTileData_, sortTmpBuf_.Get<float>(),
413+ pipe_);
414+ selectedK = ResolveMedianK(kthIndex_, numTileData_, nonNanCount, medianMode_);
415+ }
416+ }
417+ uint32_t srcOffset = rowOffset + selectedK;
418+ compactValue.SetValue(row, sortedValueLocal.GetValue(srcOffset));
419+ compactIndex.SetValue(row, static_cast<int64_t>(sortedIndexLocal.GetValue(srcOffset)));
225 }420 }
226}421}
227 422 
@@ -232,31 +427,43 @@ __aicore__ inline void KthValueMergeSortOneCore<T, CONVERT_TYPE, isSort32SmallAx
232 LocalTensor<T> sortedValueLocal = outValueQueue_.DeQue<T>();427 LocalTensor<T> sortedValueLocal = outValueQueue_.DeQue<T>();
233 LocalTensor<uint32_t> sortedIndexLocal = outIndexQueue_.DeQue<uint32_t>();428 LocalTensor<uint32_t> sortedIndexLocal = outIndexQueue_.DeQue<uint32_t>();
234 429 
235- event_t eventIdVToS = static_cast<event_t>(pipe_->FetchEventID(HardEvent::V_S));430+ constexpr bool compactExtract = !EnableMedian && IsSameType<T, int16_t>::value;
黄
黄黄晓彬9月17日

【函数规模】CopyKthToGm 由修改前 38 行增至 70 行 NBNC,超过本轮检视采用的 50 行阈值。

建议拆分索引/值的提取与输出分支,降低单个函数规模;不要仅通过合并语句或压缩换行规避指标。

统计范围为当前文件第 376–446 行;剔除空行/注释,保留签名、大括号及函数内预处理指令,不计函数前独立 template/修饰宏行。此为静态 NBNC 复核,非正式 CodeCheck 输出。

likedislike
黄晓彬
9月17日 评论:
236- SetFlag<HardEvent::V_S>(eventIdVToS);431+ uint32_t outputStride = compactExtract ? (mergeBatchRows_ > 1U ? 1U : DEALING_SORT_NUM_ONCE) : alignSize_;
237- WaitFlag<HardEvent::V_S>(eventIdVToS);432+ uint32_t outputK = compactExtract ? (mergeBatchRows_ > 1U ? 0U : kthIndex_ % DEALING_SORT_NUM_ONCE) : kthIndex_;
238- 433+ constexpr uint32_t gatherLanes = Ops::Base::GetVRegSize() / sizeof(uint32_t);
434+ constexpr uint32_t minGatherRows = 8U;
435+ // With k < stride, the largest B16 gather offset is 63*stride+k
436+ // <= 64*stride-1 <= 65471 for stride <= floor(65535/64) = 1023.
437+ constexpr uint32_t maxGatherStride = std::numeric_limits<uint16_t>::max() / gatherLanes;
438+ const bool useGather = !EnableMedian && IsSameType<T, int16_t>::value && rowNum >= minGatherRows &&
439+ alignSize_ <= maxGatherStride;
440+ if (!useGather) {
441+ event_t eventIdVToS = static_cast<event_t>(pipe_->FetchEventID(HardEvent::V_S));
442+ SetFlag<HardEvent::V_S>(eventIdVToS);
443+ WaitFlag<HardEvent::V_S>(eventIdVToS);
444+ }
239 LocalTensor<T> compactValue = compactValueQueue_.AllocTensor<T>();445 LocalTensor<T> compactValue = compactValueQueue_.AllocTensor<T>();
240 LocalTensor<int64_t> compactIndex = compactIndexQueue_.AllocTensor<int64_t>();446 LocalTensor<int64_t> compactIndex = compactIndexQueue_.AllocTensor<int64_t>();
241- for (uint32_t row = 0; row < rowNum; ++row) {447+ if (useGather) {
242- uint32_t rowOffset = row * alignSize_;448+ LocalTensor<int16_t> sourceValues = sortedValueLocal.template ReinterpretCast<int16_t>();
243- uint32_t selectedK = kthIndex_;449+ LocalTensor<int16_t> targetValues = compactValue.template ReinterpretCast<int16_t>();
244- if constexpr (EnableMedian && IS_MEDIAN_FLOAT_TYPE<T>) {450+ GatherKthInt16(sourceValues, sortedIndexLocal, targetValues, compactIndex, rowNum, outputStride, outputK);
245- uint32_t nonNanCount = CountNonNan(sortedValueLocal[rowOffset], numTileData_, concatTmpBuf_.Get<float>(),451+ } else {
246- pipe_);452+ SelectScalarRows(sortedValueLocal, sortedIndexLocal, compactValue, compactIndex, rowNum, outputStride, outputK);
247- selectedK = ResolveMedianK(kthIndex_, numTileData_, nonNanCount, medianMode_);
248- }
249- uint32_t srcOffset = rowOffset + selectedK;
250- compactValue.SetValue(row, sortedValueLocal.GetValue(srcOffset));
251- compactIndex.SetValue(row, static_cast<int64_t>(sortedIndexLocal.GetValue(srcOffset)));
252 }453 }
253 compactValueQueue_.EnQue<T>(compactValue);454 compactValueQueue_.EnQue<T>(compactValue);
254 compactIndexQueue_.EnQue<int64_t>(compactIndex);455 compactIndexQueue_.EnQue<int64_t>(compactIndex);
255 compactValue = compactValueQueue_.DeQue<T>();456 compactValue = compactValueQueue_.DeQue<T>();
256 compactIndex = compactIndexQueue_.DeQue<int64_t>();457 compactIndex = compactIndexQueue_.DeQue<int64_t>();
257- event_t eventIdSToMte3 = static_cast<event_t>(pipe_->FetchEventID(HardEvent::S_MTE3));458+ if (useGather) {
258- SetFlag<HardEvent::S_MTE3>(eventIdSToMte3);459+ event_t ready = static_cast<event_t>(pipe_->FetchEventID(HardEvent::V_MTE3));
259- WaitFlag<HardEvent::S_MTE3>(eventIdSToMte3);460+ SetFlag<HardEvent::V_MTE3>(ready);
461+ WaitFlag<HardEvent::V_MTE3>(ready);
462+ } else {
463+ event_t eventIdSToMte3 = static_cast<event_t>(pipe_->FetchEventID(HardEvent::S_MTE3));
464+ SetFlag<HardEvent::S_MTE3>(eventIdSToMte3);
465+ WaitFlag<HardEvent::S_MTE3>(eventIdSToMte3);
466+ }
260 DataCopyExtParams valueCopyParam{1, static_cast<uint32_t>(rowNum * sizeof(T)), 0, 0, 0};467 DataCopyExtParams valueCopyParam{1, static_cast<uint32_t>(rowNum * sizeof(T)), 0, 0, 0};
261 DataCopyPad(valuesGm_[outputOffset], compactValue, valueCopyParam);468 DataCopyPad(valuesGm_[outputOffset], compactValue, valueCopyParam);
262 DataCopyExtParams indexCopyParam{1, static_cast<uint32_t>(rowNum * sizeof(int64_t)), 0, 0, 0};469 DataCopyExtParams indexCopyParam{1, static_cast<uint32_t>(rowNum * sizeof(int64_t)), 0, 0, 0};
@@ -0,0 +1,173 @@
1+/**
2+ * Copyright (c) 2026 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+#ifndef KTH_VALUE_NARROW_SELECT_H
12+#define KTH_VALUE_NARROW_SELECT_H
13+ 
14+#include "kernel_operator.h"
15+#include "simt_api/asc_simt.h"
16+#include "common/small_axis_two_stage_base.h"
17+ 
18+namespace KthValue {
19+constexpr uint32_t NARROW_SELECT_MODE = 1U;
20+constexpr uint32_t NARROW_SELECT_WARP_SIZE = 32U;
21+constexpr uint32_t NARROW_SELECT_CAPACITY_SMALL = 64U;
22+constexpr uint32_t NARROW_SELECT_CAPACITY_MEDIUM = 128U;
23+constexpr uint32_t NARROW_SELECT_CAPACITY_LARGE = 256U;
24+constexpr uint32_t NARROW_SELECT_CAPACITY_MAX = 512U;
25+constexpr uint32_t NARROW_SELECT_CAPACITY_BYTE = 384U;
26+constexpr uint16_t NARROW_SELECT_ABS_MASK = 0x7FFFU;
27+constexpr uint16_t NARROW_SELECT_SIGN_MASK = 0x8000U;
28+constexpr uint16_t NARROW_SELECT_KEY_MASK = 0xFFFFU;
29+constexpr uint16_t NARROW_SELECT_HALF_INF = 0x7C00U;
30+constexpr uint16_t NARROW_SELECT_BFLOAT_INF = 0x7F80U;
31+constexpr int32_t NARROW_SELECT_UPPER_SENTINEL = 65536;
32+constexpr int32_t NARROW_SELECT_BYTE_SENTINEL = 256;
33+constexpr int32_t NARROW_SELECT_LOWER_SENTINEL = -129;
34+constexpr int32_t NARROW_SELECT_BINARY_RADIX = 2;
35+ 
36+// Ordering keys collapse both zeros and all NaN encodings. The final value is read
37+// from the selected raw input, so the output preserves its zero sign or NaN payload.
38+template <typename T>
39+__simt_callee__ __aicore__ inline int32_t MakeNarrowFloatKey(uint16_t raw)
40+{
41+ uint16_t absBits = raw & NARROW_SELECT_ABS_MASK;
42+ constexpr uint16_t infBits = IsSameType<T, half>::value ? NARROW_SELECT_HALF_INF : NARROW_SELECT_BFLOAT_INF;
43+ if (absBits == 0U) {
44+ raw = 0U;
45+ }
46+ if (absBits > infBits) {
47+ return NARROW_SELECT_KEY_MASK;
48+ }
49+ if ((raw & NARROW_SELECT_SIGN_MASK) != 0U) {
50+ return (~raw) & NARROW_SELECT_KEY_MASK;
51+ }
52+ return raw ^ NARROW_SELECT_SIGN_MASK;
53+}
54+ 
55+template <typename T>
56+__simt_callee__ __aicore__ inline int32_t LoadNarrowKey(__ubuf__ T* input)
57+{
58+ if constexpr (IsSameType<T, half>::value || IsSameType<T, bfloat16_t>::value) {
59+ return MakeNarrowFloatKey<T>(*reinterpret_cast<__ubuf__ uint16_t*>(input));
60+ } else {
61+ return static_cast<int32_t>(*input);
62+ }
63+}
64+ 
65+template <uint32_t Chunks>
66+__simt_callee__ __aicore__ inline int32_t SelectNarrowKey(const int32_t (&keys)[Chunks], int32_t low, int32_t high,
67+ uint32_t kth)
68+{
69+ while (low < high) {
70+ int32_t mid = low + (high - low) / NARROW_SELECT_BINARY_RADIX;
71+ uint32_t count = 0;
72+#pragma unroll
73+ for (uint32_t c = 0; c < Chunks; ++c) {
74+ count += keys[c] <= mid ? 1U : 0U;
75+ }
76+ count = asc_reduce_add(count);
77+ if (count > kth) {
78+ high = mid;
79+ } else {
80+ low = mid + 1;
81+ }
82+ }
83+ return low;
84+}
85+ 
86+// Resolve ties in original column order: skip complete matching chunks, then select
87+// the rank-th matching lane with an inclusive prefix. Every lane participates in collectives.
88+template <uint32_t Chunks>
89+__simt_callee__ __aicore__ inline uint32_t FindNarrowIndex(const int32_t (&keys)[Chunks], int32_t key, uint32_t kth,
90+ uint32_t lane, uint32_t cols)
91+{
92+ uint32_t less = 0;
93+#pragma unroll
94+ for (uint32_t c = 0; c < Chunks; ++c) {
95+ less += keys[c] < key ? 1U : 0U;
96+ }
97+ less = asc_reduce_add(less);
98+ uint32_t rank = kth - less;
99+ uint32_t selected = cols;
100+#pragma unroll
101+ for (uint32_t c = 0; c < Chunks; ++c) {
102+ uint32_t match = keys[c] == key ? 1U : 0U;
103+ uint32_t count = asc_reduce_add(match);
104+ if (rank < count) {
105+ uint32_t prefix = match;
106+#pragma unroll
107+ for (uint32_t shift = 1; shift < NARROW_SELECT_WARP_SIZE; shift *= NARROW_SELECT_BINARY_RADIX) {
108+ uint32_t before = asc_shfl_up(prefix, shift);
109+ if (lane >= shift) {
110+ prefix += before;
111+ }
112+ }
113+ selected = match != 0 && prefix == rank + 1 ? c * NARROW_SELECT_WARP_SIZE + lane : cols;
114+ selected = asc_reduce_min(selected);
115+ break;
116+ }
117+ rank -= count;
118+ }
119+ return selected;
120+}
121+ 
122+// One warp selects a narrow-key row without sorting; cached keys avoid repeated UB loads.
123+template <typename T, uint32_t MaxAxis>
124+__simt_vf__ LAUNCH_BOUND(SmallAxisCommon::TWO_STAGE_THREAD_NUM) __aicore__
125+ void SimtSelectNarrowRows(uint32_t rows, uint32_t cols, uint32_t kth, uint64_t outputStart, __ubuf__ T* input,
黄
黄黄晓彬9月17日

【函数规模】SimtSelectNarrowRows 在本次重构后为 103 行 NBNC,超过本轮检视采用的 50 行阈值。

建议拆分 key 构造、二分选择、重复值稳定索引选择阶段,并保持 warp collective 的一致参与。第 65–68 行的 NaN/符号位转换还是嵌套三目表达式,可一并抽成命名明确的 key 转换 helper,用显式分支说明特殊值语义。

统计范围为当前文件第 39–141 行;剔除空行/注释,保留签名、大括号及函数内预处理指令,不计函数前独立 template/修饰宏行。此为静态 NBNC 复核,非正式 CodeCheck 输出。

likedislike
黄晓彬
9月17日 评论:
黄晓彬
9月17日 评论:
126+ __gm__ volatile T* values, __gm__ volatile int64_t* indices)
127+{
128+ constexpr bool isFloat = IsSameType<T, half>::value || IsSameType<T, bfloat16_t>::value;
129+ constexpr int32_t upperSentinel = isFloat ? NARROW_SELECT_UPPER_SENTINEL : NARROW_SELECT_BYTE_SENTINEL;
130+ constexpr uint32_t lanes = NARROW_SELECT_WARP_SIZE;
131+ constexpr uint32_t chunks = MaxAxis / lanes;
132+ // One warp owns one row; invalid tail lanes hold a key above every valid value.
133+ // Key selection finds the kth value first, then FindNarrowIndex recovers its stable index.
134+ uint32_t lane = static_cast<uint32_t>(threadIdx.x) % lanes;
135+ uint32_t warp = static_cast<uint32_t>(threadIdx.x) / lanes;
136+ for (uint32_t row = warp; row < rows; row += SmallAxisCommon::TWO_STAGE_THREAD_NUM / lanes) {
137+ int32_t keys[chunks];
138+ int32_t low = upperSentinel;
139+ int32_t high = NARROW_SELECT_LOWER_SENTINEL;
140+#pragma unroll
141+ for (uint32_t c = 0; c < chunks; ++c) {
142+ uint32_t col = c * lanes + lane;
143+ int32_t value = upperSentinel;
144+ if (col < cols) {
145+ value = LoadNarrowKey(input + row * cols + col);
146+ }
147+ keys[c] = value;
148+ if (col < cols) {
149+ low = value < low ? value : low;
150+ high = value > high ? value : high;
151+ }
152+ }
153+ low = asc_reduce_min(low);
154+ high = asc_reduce_max(high);
155+ uint32_t selected = kth;
156+ if (low != high) {
157+ low = SelectNarrowKey(keys, low, high, kth);
158+ selected = FindNarrowIndex(keys, low, kth, lane, cols);
159+ }
160+ if (lane == 0) {
161+ if constexpr (isFloat) {
162+ values[outputStart + row] = input[row * cols + selected];
163+ } else {
164+ values[outputStart + row] = static_cast<T>(low);
165+ }
166+ indices[outputStart + row] = selected;
167+ }
168+ }
169+}
170+ 
171+} // namespace KthValue
172+ 
173+#endif
@@ -194,6 +194,8 @@ __aicore__ inline void KthValueNonLastSmallAxis<T, IsDescend, UseMergeSort, Enab
194 if (endTile > tileCount) {194 if (endTile > tileCount) {
195 endTile = tileCount;195 endTile = tileCount;
196 }196 }
197+ // Merge Extract redirects sortedValue_ to sortInput_; preserve the proposal storage across tiles.
198+ LocalTensor<SortT> proposalPing = this->sortedValue_;
197 for (uint64_t tileId = startTile; tileId < endTile; ++tileId) {199 for (uint64_t tileId = startTile; tileId < endTile; ++tileId) {
198 uint64_t outerId = tileId / this->innerLoopNum_;200 uint64_t outerId = tileId / this->innerLoopNum_;
199 uint32_t innerTileId = static_cast<uint32_t>(tileId - outerId * static_cast<uint64_t>(this->innerLoopNum_));201 uint32_t innerTileId = static_cast<uint32_t>(tileId - outerId * static_cast<uint64_t>(this->innerLoopNum_));
@@ -208,7 +210,7 @@ __aicore__ inline void KthValueNonLastSmallAxis<T, IsDescend, UseMergeSort, Enab
208 this->LoadTile(inputOffset, curInnerChunk);210 this->LoadTile(inputOffset, curInnerChunk);
209 this->TransposeToSortMajor(curInnerChunk);211 this->TransposeToSortMajor(curInnerChunk);
210 PrepareSortRows(curInnerChunk);212 PrepareSortRows(curInnerChunk);
211- this->SortRows(curInnerChunk);213+ this->SortRows(curInnerChunk, proposalPing);
212 StoreTile(inputOffset, outputOffset, curInnerChunk);214 StoreTile(inputOffset, outputOffset, curInnerChunk);
213 }215 }
214}216}
@@ -25,12 +25,13 @@ using namespace AscendC;
25template <typename T, bool EnableMedian = false>25template <typename T, bool EnableMedian = false>
26class KthValueRadixOneCore {26class KthValueRadixOneCore {
27public:27public:
28- __aicore__ inline void Init(GM_ADDR x, GM_ADDR values, GM_ADDR indices, const KthValueTilingData* tiling,28+ __aicore__ inline void Init(GM_ADDR x, GM_ADDR values, GM_ADDR indices,
29- TPipe* pipe);29+ const KthValueRadixOneCoreTilingData* tiling, TPipe* pipe);
30 __aicore__ inline void Process();30 __aicore__ inline void Process();
31 31 
32private:32private:
33 __aicore__ inline void ParseTilingData();33 __aicore__ inline void ParseTilingData();
34+ __aicore__ inline uint32_t PrepareMedianRank(LocalTensor<T> xLocal);
34 __aicore__ inline void CopyInputToUb(LocalTensor<T>& xLocal, int64_t row);35 __aicore__ inline void CopyInputToUb(LocalTensor<T>& xLocal, int64_t row);
35 __aicore__ inline void ProcessOneRow(int64_t row, uint32_t localOffset, LocalTensor<T>& compactValue,36 __aicore__ inline void ProcessOneRow(int64_t row, uint32_t localOffset, LocalTensor<T>& compactValue,
36 LocalTensor<int64_t>& compactIndex);37 LocalTensor<int64_t>& compactIndex);
@@ -46,7 +47,7 @@ private:
46 GlobalTensor<int64_t> indicesGm_;47 GlobalTensor<int64_t> indicesGm_;
47 48 
48 TPipe* pipe_{nullptr};49 TPipe* pipe_{nullptr};
49- const KthValueTilingData* tiling_{nullptr};50+ const KthValueRadixOneCoreTilingData* tiling_{nullptr};
50 TQue<QuePosition::VECIN, 1> inQueueX_;51 TQue<QuePosition::VECIN, 1> inQueueX_;
51 TQue<QuePosition::VECOUT, 1> outValueQueue_;52 TQue<QuePosition::VECOUT, 1> outValueQueue_;
52 TQue<QuePosition::VECOUT, 1> outIndexQueue_;53 TQue<QuePosition::VECOUT, 1> outIndexQueue_;
@@ -73,7 +74,8 @@ private:
73 74 
74template <typename T, bool EnableMedian>75template <typename T, bool EnableMedian>
75__aicore__ inline void KthValueRadixOneCore<T, EnableMedian>::Init(GM_ADDR x, GM_ADDR values, GM_ADDR indices,76__aicore__ inline void KthValueRadixOneCore<T, EnableMedian>::Init(GM_ADDR x, GM_ADDR values, GM_ADDR indices,
76- const KthValueTilingData* tiling, TPipe* pipe)77+ const KthValueRadixOneCoreTilingData* tiling,
78+ TPipe* pipe)
77{79{
78 if (tiling == nullptr || pipe == nullptr) {80 if (tiling == nullptr || pipe == nullptr) {
79 return;81 return;
@@ -127,6 +129,29 @@ __aicore__ inline void KthValueRadixOneCore<T, EnableMedian>::CopyInputToUb(Loca
127 DataCopyPad(xLocal, xGm_[inputOffset], inputCopyParam, inputPadParam);129 DataCopyPad(xLocal, xGm_[inputOffset], inputCopyParam, inputPadParam);
128}130}
129 131 
132+template <typename T, bool EnableMedian>
133+__aicore__ inline uint32_t KthValueRadixOneCore<T, EnableMedian>::PrepareMedianRank(LocalTensor<T> xLocal)
134+{
135+ uint32_t selectedK = kthIndex_;
136+ if constexpr (EnableMedian && IS_MEDIAN_FLOAT_TYPE<T>) {
137+ if constexpr (KTH_VALUE_ENABLE_STATIC_MEDIAN_FAST_PATH) {
138+ if (medianMode_ != MEDIAN_MODE_STATIC) {
139+ CanonicalizeNanValues(xLocal, numTileData_);
140+ uint32_t nonNanCount = CountNonNan(xLocal, numTileData_, tmpUb_.Get<float>(), pipe_);
141+ selectedK = ResolveMedianK(kthIndex_, numTileData_, nonNanCount, medianMode_);
142+ }
143+ } else {
144+ if (medianMode_ != MEDIAN_MODE_STATIC) {
145+ CanonicalizeNanValues(xLocal, numTileData_);
146+ }
147+ uint32_t nonNanCount = CountNonNan(xLocal, numTileData_, tmpUb_.Get<float>(), pipe_);
148+ selectedK = ResolveMedianK(kthIndex_, numTileData_, nonNanCount, medianMode_);
149+ }
150+ }
151+ 
152+ return selectedK;
153+}
154+ 
130template <typename T, bool EnableMedian>155template <typename T, bool EnableMedian>
131__aicore__ inline void KthValueRadixOneCore<T, EnableMedian>::ProcessOneRow(int64_t row, uint32_t localOffset,156__aicore__ inline void KthValueRadixOneCore<T, EnableMedian>::ProcessOneRow(int64_t row, uint32_t localOffset,
132 LocalTensor<T>& compactValue,157 LocalTensor<T>& compactValue,
@@ -139,14 +164,7 @@ __aicore__ inline void KthValueRadixOneCore<T, EnableMedian>::ProcessOneRow(int6
139 LocalTensor<T> sortedValue = outValueQueue_.AllocTensor<T>();164 LocalTensor<T> sortedValue = outValueQueue_.AllocTensor<T>();
140 LocalTensor<uint32_t> sortedIndex = outIndexQueue_.AllocTensor<uint32_t>();165 LocalTensor<uint32_t> sortedIndex = outIndexQueue_.AllocTensor<uint32_t>();
141 LocalTensor<uint8_t> tmpUb = tmpUb_.Get<uint8_t>();166 LocalTensor<uint8_t> tmpUb = tmpUb_.Get<uint8_t>();
142- uint32_t selectedK = kthIndex_;167+ uint32_t selectedK = PrepareMedianRank(xLocal);
143- if constexpr (EnableMedian && IS_MEDIAN_FLOAT_TYPE<T>) {
144- if (medianMode_ != MEDIAN_MODE_STATIC) {
145- CanonicalizeNanValues(xLocal, numTileData_);
146- }
147- uint32_t nonNanCount = CountNonNan(xLocal, numTileData_, tmpUb_.Get<float>(), pipe_);
148- selectedK = ResolveMedianK(kthIndex_, numTileData_, nonNanCount, medianMode_);
149- }
150 168 
151 bool hasNegativeZero = false;169 bool hasNegativeZero = false;
152 if constexpr (SignedZeroSortCommon::IS_FLOATING_POINT_V<T>) {170 if constexpr (SignedZeroSortCommon::IS_FLOATING_POINT_V<T>) {
@@ -18,6 +18,7 @@
18#include "simt_api/asc_simt.h"18#include "simt_api/asc_simt.h"
19#include "kth_value_median_utils.h"19#include "kth_value_median_utils.h"
20#include "kth_value_tiling_data.h"20#include "kth_value_tiling_data.h"
21+#include "kth_value_narrow_select.h"
21#include "common/small_axis_two_stage_base.h"22#include "common/small_axis_two_stage_base.h"
22 23 
23namespace KthValue {24namespace KthValue {
@@ -69,6 +70,9 @@ __simt_vf__ LAUNCH_BOUND(SmallAxisCommon::TWO_STAGE_THREAD_NUM) __aicore__
69 }70 }
70}71}
71 72 
73+// The shared base sorts each short segment in two stages; this adapter emits only its kth pair.
74+// Partial batches use validSegs throughout, and indices stay local to the selected axis.
75+// Median hooks canonicalize ordering keys before sorting and resolve NaN policy before the final gather.
72template <typename T, bool EnableMedian = false>76template <typename T, bool EnableMedian = false>
73class KthValueSmallAxisTwoStage77class KthValueSmallAxisTwoStage
74 : public SmallAxisCommon::SmallAxisTwoStageBase<KthValueSmallAxisTwoStage<T, EnableMedian>, T, uint32_t, false> {78 : public SmallAxisCommon::SmallAxisTwoStageBase<KthValueSmallAxisTwoStage<T, EnableMedian>, T, uint32_t, false> {
@@ -101,6 +105,7 @@ private:
101 __aicore__ inline void LoadBatch(uint32_t batchId, uint32_t validSegs, uint32_t totalElems);105 __aicore__ inline void LoadBatch(uint32_t batchId, uint32_t validSegs, uint32_t totalElems);
102 __aicore__ inline void StoreKth(uint32_t batchId, int64_t segStart, uint32_t validSegs);106 __aicore__ inline void StoreKth(uint32_t batchId, int64_t segStart, uint32_t validSegs);
103 107 
108+ TBuf<TPosition::VECCALC> narrowInputBuf_;
104 GlobalTensor<T> inputGm_;109 GlobalTensor<T> inputGm_;
105 GlobalTensor<T> valueGm_;110 GlobalTensor<T> valueGm_;
106 GlobalTensor<int64_t> indexGm_;111 GlobalTensor<int64_t> indexGm_;
@@ -143,6 +148,14 @@ __aicore__ inline void KthValueSmallAxisTwoStage<T, EnableMedian>::Init(GM_ADDR
143 return;148 return;
144 }149 }
145 150 
151+ if constexpr (!EnableMedian && (IsSameType<T, int8_t>::value || IsSameType<T, uint8_t>::value ||
152+ IsSameType<T, half>::value || IsSameType<T, bfloat16_t>::value)) {
153+ if (!IsNonLastMode() && tiling_->keyParams3 == NARROW_SELECT_MODE) {
154+ pipe_->InitBuffer(narrowInputBuf_, ROUND_UP_AGLIN(maxFlatElems_ * sizeof(T)));
155+ inputValues_ = narrowInputBuf_.template Get<T>();
156+ return;
157+ }
158+ }
146 Base::InitSortBuffers(pipe, maxFlatElems_, tiling_->tmpUbSize, tiling_->keyParams2 != 0U, sizeof(uint32_t));159 Base::InitSortBuffers(pipe, maxFlatElems_, tiling_->tmpUbSize, tiling_->keyParams2 != 0U, sizeof(uint32_t));
147}160}
148 161 
@@ -181,6 +194,44 @@ __aicore__ inline void KthValueSmallAxisTwoStage<T, EnableMedian>::ProcessBatch(
181 uint32_t totalElems = validSegs * segmentLen_;194 uint32_t totalElems = validSegs * segmentLen_;
182 int64_t segStart = GetOutputStart(batchId);195 int64_t segStart = GetOutputStart(batchId);
183 LoadBatch(batchId, validSegs, totalElems);196 LoadBatch(batchId, validSegs, totalElems);
197+ if constexpr (!EnableMedian && (IsSameType<T, int8_t>::value || IsSameType<T, uint8_t>::value ||
198+ IsSameType<T, half>::value || IsSameType<T, bfloat16_t>::value)) {
199+ if (!IsNonLastMode() && tiling_->keyParams3 == NARROW_SELECT_MODE) {
200+ bool useByteCapacity = (IsSameType<T, int8_t>::value || IsSameType<T, uint8_t>::value) &&
201+ segmentLen_ > NARROW_SELECT_CAPACITY_MEDIUM &&
202+ segmentLen_ <= NARROW_SELECT_CAPACITY_BYTE;
203+ if (useByteCapacity) {
204+ asc_vf_call<SimtSelectNarrowRows<T, NARROW_SELECT_CAPACITY_BYTE>>(
205+ dim3(SmallAxisCommon::TWO_STAGE_THREAD_NUM), validSegs, segmentLen_, kthIndex_,
206+ static_cast<uint64_t>(segStart), (__ubuf__ T*)inputValues_.GetPhyAddr(),
207+ (__gm__ volatile T*)valueGm_.GetPhyAddr(), (__gm__ volatile int64_t*)indexGm_.GetPhyAddr());
208+ } else if (segmentLen_ <= NARROW_SELECT_CAPACITY_SMALL) {
209+ asc_vf_call<SimtSelectNarrowRows<T, NARROW_SELECT_CAPACITY_SMALL>>(
210+ dim3(SmallAxisCommon::TWO_STAGE_THREAD_NUM), validSegs, segmentLen_, kthIndex_,
211+ static_cast<uint64_t>(segStart), (__ubuf__ T*)inputValues_.GetPhyAddr(),
212+ (__gm__ volatile T*)valueGm_.GetPhyAddr(), (__gm__ volatile int64_t*)indexGm_.GetPhyAddr());
213+ } else if (segmentLen_ <= NARROW_SELECT_CAPACITY_MEDIUM) {
214+ asc_vf_call<SimtSelectNarrowRows<T, NARROW_SELECT_CAPACITY_MEDIUM>>(
215+ dim3(SmallAxisCommon::TWO_STAGE_THREAD_NUM), validSegs, segmentLen_, kthIndex_,
216+ static_cast<uint64_t>(segStart), (__ubuf__ T*)inputValues_.GetPhyAddr(),
217+ (__gm__ volatile T*)valueGm_.GetPhyAddr(), (__gm__ volatile int64_t*)indexGm_.GetPhyAddr());
218+ } else if (segmentLen_ <= NARROW_SELECT_CAPACITY_LARGE) {
219+ asc_vf_call<SimtSelectNarrowRows<T, NARROW_SELECT_CAPACITY_LARGE>>(
220+ dim3(SmallAxisCommon::TWO_STAGE_THREAD_NUM), validSegs, segmentLen_, kthIndex_,
221+ static_cast<uint64_t>(segStart), (__ubuf__ T*)inputValues_.GetPhyAddr(),
222+ (__gm__ volatile T*)valueGm_.GetPhyAddr(), (__gm__ volatile int64_t*)indexGm_.GetPhyAddr());
223+ } else if (segmentLen_ <= NARROW_SELECT_CAPACITY_MAX) {
224+ asc_vf_call<SimtSelectNarrowRows<T, NARROW_SELECT_CAPACITY_MAX>>(
225+ dim3(SmallAxisCommon::TWO_STAGE_THREAD_NUM), validSegs, segmentLen_, kthIndex_,
226+ static_cast<uint64_t>(segStart), (__ubuf__ T*)inputValues_.GetPhyAddr(),
227+ (__gm__ volatile T*)valueGm_.GetPhyAddr(), (__gm__ volatile int64_t*)indexGm_.GetPhyAddr());
228+ }
229+ event_t done = static_cast<event_t>(pipe_->FetchEventID(HardEvent::V_S));
230+ SetFlag<HardEvent::V_S>(done);
231+ WaitFlag<HardEvent::V_S>(done);
232+ return;
233+ }
234+ }
184 if constexpr (EnableMedian && IS_MEDIAN_FLOAT_TYPE<T>) {235 if constexpr (EnableMedian && IS_MEDIAN_FLOAT_TYPE<T>) {
185 asc_vf_call<SimtCanonicalizeMedianSortValues<T>>(dim3(SmallAxisCommon::TWO_STAGE_THREAD_NUM), totalElems,236 asc_vf_call<SimtCanonicalizeMedianSortValues<T>>(dim3(SmallAxisCommon::TWO_STAGE_THREAD_NUM), totalElems,
186 reinterpret_cast<__ubuf__ T*>(inputValues_.GetPhyAddr()));237 reinterpret_cast<__ubuf__ T*>(inputValues_.GetPhyAddr()));
@@ -17,6 +17,18 @@ constexpr uint32_t MEDIAN_MODE_STATIC = 0U;
17constexpr uint32_t MEDIAN_MODE_PROPAGATE_NAN = 1U;17constexpr uint32_t MEDIAN_MODE_PROPAGATE_NAN = 1U;
18constexpr uint32_t MEDIAN_MODE_IGNORE_NAN = 2U;18constexpr uint32_t MEDIAN_MODE_IGNORE_NAN = 2U;
19 19 
20+// Median opts into the legacy fixed-rank shortcut before including the shared kernels.
21+// This translation-unit selector is independent of runtime medianMode; normal Median uses
22+// PROPAGATE_NAN, so it still counts NaNs. Implementation branches below use if constexpr.
23+#ifndef KTH_VALUE_STATIC_MEDIAN_FAST_PATH
24+#define KTH_VALUE_STATIC_MEDIAN_FAST_PATH 0
25+#endif
26+constexpr bool KTH_VALUE_ENABLE_STATIC_MEDIAN_FAST_PATH = KTH_VALUE_STATIC_MEDIAN_FAST_PATH != 0;
27+ 
28+constexpr uint32_t KTH_INT16_MERGE_BATCH_ROWS = 8U;
29+constexpr uint32_t KTH_INT16_MERGE_BATCH_MIN_AXIS = 128U;
30+constexpr uint32_t KTH_INT16_MERGE_BATCH_MAX_AXIS = 512U;
31+ 
20struct KthValueTilingData {32struct KthValueTilingData {
21 uint32_t numTileDataSize; // h轴ub一次处理个数33 uint32_t numTileDataSize; // h轴ub一次处理个数
22 uint32_t unsortedDimParallel; // b轴使用的核数34 uint32_t unsortedDimParallel; // b轴使用的核数
@@ -39,6 +51,7 @@ struct KthValueTilingData {
39 // small_axis_insertion/two_stage: batchNum_51 // small_axis_insertion/two_stage: batchNum_
40 // non_last_small_axis: inputValueAxisBytes_ (bf16 merge)52 // non_last_small_axis: inputValueAxisBytes_ (bf16 merge)
41 // axis_one_copy: loopTimes_53 // axis_one_copy: loopTimes_
54+ // merge_more_core: sort_sync_merge_sort 逻辑块大小,0 表示普通 more-core
42 uint32_t keyParams1;55 uint32_t keyParams1;
43 // radix: 清零的一次 ub 数据量56 // radix: 清零的一次 ub 数据量
44 // merge_sort_one_core: y2OutQue 的 ub 大小57 // merge_sort_one_core: y2OutQue 的 ub 大小
@@ -72,4 +85,33 @@ struct KthValueTilingData {
72 uint32_t medianMode;85 uint32_t medianMode;
73};86};
74 87 
88+// Single-core routes transfer only the fields consumed by their kernels.
89+struct KthValueMergeOneCoreTilingData {
90+ int64_t kthIndex;
91+ int64_t unsortedDimNum;
92+ uint32_t numTileDataSize;
93+ uint32_t unsortedDimParallel;
94+ uint32_t sortLoopTimes;
95+ uint32_t keyParams0; // rows per UB batch (> 0)
96+ uint32_t keyParams1; // input queue capacity in bytes
97+ uint32_t keyParams2; // index queue capacity in bytes
98+ uint32_t keyParams3; // Sort32-aligned axis length in elements
99+ uint32_t keyParams4; // queue buffer count: 0/1 = single, 2 = double
100+ uint32_t medianMode;
101+};
102+ 
103+struct KthValueRadixOneCoreTilingData {
104+ int64_t kthIndex;
105+ int64_t lastAxisNum;
106+ int64_t unsortedDimNum;
107+ uint32_t numTileDataSize;
108+ uint32_t unsortedDimParallel;
109+ uint32_t keyParams0; // input UB capacity in bytes
110+ uint32_t keyParams1; // index UB capacity in bytes
111+ uint32_t keyParams3; // queue buffer count: 2 = double, otherwise single
112+ uint32_t keyParams4; // output rows per loop: 0 defaults to 1
113+ uint32_t tmpUbSize;
114+ uint32_t medianMode;
115+};
116+ 
75#endif117#endif
@@ -26,15 +26,21 @@
26#define KTH_VALUE_SCHID_NON_LAST_SMALL_AXIS_RADIX 1026#define KTH_VALUE_SCHID_NON_LAST_SMALL_AXIS_RADIX 10
27#define KTH_VALUE_SCHID_RADIX_SELECT 1127#define KTH_VALUE_SCHID_RADIX_SELECT 11
28#define KTH_VALUE_SCHID_SMALL_AXIS_SHORT_RANK_SELECT 1228#define KTH_VALUE_SCHID_SMALL_AXIS_SHORT_RANK_SELECT 12
29+#define KTH_VALUE_SCHID_RESIDENT_HISTOGRAM 13
29 30 
30-#define KTH_VALUE_TPL_KEY_DECL() \31+// Compact-layout registration uses string conditions consumed by the compiler.
31- ASCENDC_TPL_UINT_DECL(schId, ASCENDC_TPL_8_BW, ASCENDC_TPL_UI_LIST, KTH_VALUE_SCHID_MERGE_SORT, \32+// With isInt32 == 1 in the upper byte, schId 0/8 encode 256/264 and schId 1 encodes 257.
32- KTH_VALUE_SCHID_RADIX_ONE_CORE, KTH_VALUE_SCHID_RADIX_MORE_CORE, \33+#define KTH_VALUE_MERGE_ONE_CORE_TILING_CONDITION "TILING_KEY_VAR == 256 || TILING_KEY_VAR == 264"
33- KTH_VALUE_SCHID_MERGE_MORE_CORE, KTH_VALUE_SCHID_MERGE_INTRA_CORE, \34+#define KTH_VALUE_RADIX_ONE_CORE_TILING_CONDITION "TILING_KEY_VAR == 257"
34- KTH_VALUE_SCHID_SMALL_AXIS_INSERTION, KTH_VALUE_SCHID_SMALL_AXIS_TWO_STAGE, \35+ 
35- KTH_VALUE_SCHID_AXIS_ONE_COPY, KTH_VALUE_SCHID_SORT32_SMALL_AXIS, \36+#define KTH_VALUE_TPL_KEY_DECL() \
36- KTH_VALUE_SCHID_NON_LAST_SMALL_AXIS, KTH_VALUE_SCHID_NON_LAST_SMALL_AXIS_RADIX, \37+ ASCENDC_TPL_UINT_DECL( \
37- KTH_VALUE_SCHID_RADIX_SELECT, KTH_VALUE_SCHID_SMALL_AXIS_SHORT_RANK_SELECT), \38+ schId, ASCENDC_TPL_8_BW, ASCENDC_TPL_UI_LIST, KTH_VALUE_SCHID_MERGE_SORT, KTH_VALUE_SCHID_RADIX_ONE_CORE, \
39+ KTH_VALUE_SCHID_RADIX_MORE_CORE, KTH_VALUE_SCHID_MERGE_MORE_CORE, KTH_VALUE_SCHID_MERGE_INTRA_CORE, \
40+ KTH_VALUE_SCHID_SMALL_AXIS_INSERTION, KTH_VALUE_SCHID_SMALL_AXIS_TWO_STAGE, KTH_VALUE_SCHID_AXIS_ONE_COPY, \
41+ KTH_VALUE_SCHID_SORT32_SMALL_AXIS, KTH_VALUE_SCHID_NON_LAST_SMALL_AXIS, \
42+ KTH_VALUE_SCHID_NON_LAST_SMALL_AXIS_RADIX, KTH_VALUE_SCHID_RADIX_SELECT, \
43+ KTH_VALUE_SCHID_SMALL_AXIS_SHORT_RANK_SELECT, KTH_VALUE_SCHID_RESIDENT_HISTOGRAM), \
38 ASCENDC_TPL_UINT_DECL(isInt32, ASCENDC_TPL_8_BW, ASCENDC_TPL_UI_LIST, 0, 1)44 ASCENDC_TPL_UINT_DECL(isInt32, ASCENDC_TPL_8_BW, ASCENDC_TPL_UI_LIST, 0, 1)
39 45 
40#define KTH_VALUE_TPL_MERGE_SORT_KEY_SEL() \46#define KTH_VALUE_TPL_MERGE_SORT_KEY_SEL() \
@@ -99,8 +105,12 @@
99 ASCENDC_TPL_ARGS_SEL(KTH_VALUE_TPL_RADIX_SELECT_KEY_SEL()), \105 ASCENDC_TPL_ARGS_SEL(KTH_VALUE_TPL_RADIX_SELECT_KEY_SEL()), \
100 ASCENDC_TPL_ARGS_SEL(KTH_VALUE_TPL_SMALL_AXIS_SHORT_RANK_SELECT_KEY_SEL())106 ASCENDC_TPL_ARGS_SEL(KTH_VALUE_TPL_SMALL_AXIS_SHORT_RANK_SELECT_KEY_SEL())
101 107 
108+#define KTH_VALUE_TPL_RESIDENT_HISTOGRAM_KEY_SEL() \
109+ ASCENDC_TPL_UINT_SEL(schId, ASCENDC_TPL_UI_LIST, KTH_VALUE_SCHID_RESIDENT_HISTOGRAM), \
110+ ASCENDC_TPL_UINT_SEL(isInt32, ASCENDC_TPL_UI_LIST, 1)
111+ 
102ASCENDC_TPL_ARGS_DECL(KthValue, KTH_VALUE_TPL_KEY_DECL());112ASCENDC_TPL_ARGS_DECL(KthValue, KTH_VALUE_TPL_KEY_DECL());
103 113 
104-ASCENDC_TPL_SEL(KTH_VALUE_TPL_ALL_KEY_SEL());114+ASCENDC_TPL_SEL(KTH_VALUE_TPL_ALL_KEY_SEL(), ASCENDC_TPL_ARGS_SEL(KTH_VALUE_TPL_RESIDENT_HISTOGRAM_KEY_SEL()));
105 115 
106#endif116#endif
@@ -93,6 +93,22 @@ def _fill_pattern_along_axis(tensor, values, axis):
93 93 
94def kth_value_input(x, *, dim=-1, testcase_name="", **kwargs):94def kth_value_input(x, *, dim=-1, testcase_name="", **kwargs):
95 name = str(testcase_name)95 name = str(testcase_name)
96+ if name.endswith("_mixed_nan"):
97+ # Preserve NaN signs and payloads rather than relying on float conversions.
98+ if str(x.dtype) == "float32":
99+ bits = x.view("uint32").reshape(-1)
100+ encodings = (0xFFC00000, 0x7FC00000, 0xFFC00001, 0x7FC00002)
101+ else:
102+ bits = x.view("uint16").reshape(-1)
103+ nan_bits = 0xFFC0 if "bfloat16" in str(x.dtype) else 0xFE00
104+ encodings = (
105+ nan_bits,
106+ nan_bits & 0x7FFF,
107+ nan_bits | 1,
108+ (nan_bits | 2) & 0x7FFF,
109+ )
110+ bits[: len(encodings)] = encodings
111+ return (x,)
96 patterns = {112 patterns = {
97 "duplicate": [2.0, 1.0, 1.0, 3.0, 2.0, 1.0, 3.0, 1.0],113 "duplicate": [2.0, 1.0, 1.0, 3.0, 2.0, 1.0, 3.0, 1.0],
98 "signed_zero": [-0.0, 0.0, 0.0, -0.0, 1.0, -1.0, 0.0, -0.0],114 "signed_zero": [-0.0, 0.0, 0.0, -0.0, 1.0, -1.0, 0.0, -0.0],
@@ -24,3 +24,8 @@ kth_value_duplicate,,kth_value,"((2, 8),)","('float32',)","('ND',)","((2, 1), (2
24kth_value_signed_zero,,kth_value,"((2, 8),)","('float32',)","('ND',)","((2, 1), (2, 1))","('float32', 'int64')","('ND', 'ND')",,,,,"{'k': 4, 'dim': -1}","((-4, 4),)",,,(),(),True,stable signed-zero index,,0,,(),()24kth_value_signed_zero,,kth_value,"((2, 8),)","('float32',)","('ND',)","((2, 1), (2, 1))","('float32', 'int64')","('ND', 'ND')",,,,,"{'k': 4, 'dim': -1}","((-4, 4),)",,,(),(),True,stable signed-zero index,,0,,(),()
25kth_value_nan,,kth_value,"((2, 8),)","('float32',)","('ND',)","((2, 1), (2, 1))","('float32', 'int64')","('ND', 'ND')",,,,,"{'k': 7, 'dim': -1}","((-4, 4),)",,,(),(),True,stable first NaN rank,,0,,(),()25kth_value_nan,,kth_value,"((2, 8),)","('float32',)","('ND',)","((2, 1), (2, 1))","('float32', 'int64')","('ND', 'ND')",,,,,"{'k': 7, 'dim': -1}","((-4, 4),)",,,(),(),True,stable first NaN rank,,0,,(),()
26kth_value_all_nan,,kth_value,"((2, 8),)","('float32',)","('ND',)","((2, 1), (2, 1))","('float32', 'int64')","('ND', 'ND')",,,,,"{'k': 4, 'dim': -1}","((-4, 4),)",,,(),(),True,stable all-NaN index,,0,,(),()26kth_value_all_nan,,kth_value,"((2, 8),)","('float32',)","('ND',)","((2, 1), (2, 1))","('float32', 'int64')","('ND', 'ND')",,,,,"{'k': 4, 'dim': -1}","((-4, 4),)",,,(),(),True,stable all-NaN index,,0,,(),()
27+kth_value_int16_compact_301_k301,,kth_value,"((128, 301),)","('int16',)","('ND',)","((128, 1), (128, 1))","('int16', 'int64')","('ND', 'ND')",,,,,"{'k': 301, 'dim': -1}","((-32768,32767),)","((0,0),(0,0))","(0,0)",(),(),True,Compact merge extraction: original indices at rank-block and axis-tail boundaries,,0,,(),()
28+kth_value_int16_compact_320_k33,,kth_value,"((128, 320),)","('int16',)","('ND',)","((128, 1), (128, 1))","('int16', 'int64')","('ND', 'ND')",,,,,"{'k': 33, 'dim': -1}","((-32768,32767),)","((0,0),(0,0))","(0,0)",(),(),True,Compact merge extraction: original indices at rank-block and axis-tail boundaries,,0,,(),()
29+kth_value_int16_merge_batch_tail,,kth_value,"((1031, 301),)","('int16',)","('ND',)","((1031, 1), (1031, 1))","('int16', 'int64')","('ND','ND')","((1031, 301),)",,"((1031, 1), (1031, 1))",,"{'k': 301, 'dim': -1}","((-32768, 32767),)","((0,0), (0,0))",0,(),(),True,Batched merge with partial final groups and row-relative stable indices,,,,(),()
30+kth_value_radix_fp32_k1_mixed_nan,,kth_value,"((1, 1024),)","('float32',)","('ND',)","((1, 1), (1, 1))","('float32', 'int64')","('ND', 'ND')",,,,,"{'k': 1, 'dim': -1, 'keepdim': True}","((-10, 10),)",,,(),(),True,Radix select: signed NaNs and distinct payloads,,0,,(),()
31+kth_value_radix_fp32_k1024_mixed_nan,,kth_value,"((1, 1024),)","('float32',)","('ND',)","((1, 1), (1, 1))","('float32', 'int64')","('ND', 'ND')",,,,,"{'k': 1024, 'dim': -1, 'keepdim': True}","((-10, 10),)",,,(),(),True,Radix select: signed NaNs and distinct payloads,,0,,(),()
@@ -55,16 +55,16 @@ gert::TilingContextPara MakeKthValueTilingContext(const gert::StorageShape& xSha
55}55}
56} // namespace56} // namespace
57 57 
58-TEST_F(KthValueTilingTest, test_kthvalue_merge_sort_fp32_2x1024)58+TEST_F(KthValueTilingTest, test_kthvalue_radix_select_fp32_2x1024)
59{59{
60 auto tilingContextPara = MakeKthValueTilingContext({{2, 1024}, {2, 1024}}, {{2, 1}, {2, 1}}, {{2, 1}, {2, 1}},60 auto tilingContextPara = MakeKthValueTilingContext({{2, 1024}, {2, 1024}}, {{2, 1}, {2, 1}}, {{2, 1}, {2, 1}},
61 ge::DT_FLOAT, 5);61 ge::DT_FLOAT, 5);
62 62 
63 TilingInfo tilingInfo;63 TilingInfo tilingInfo;
64 ASSERT_TRUE(ExecuteTiling(tilingContextPara, tilingInfo));64 ASSERT_TRUE(ExecuteTiling(tilingContextPara, tilingInfo));
65- EXPECT_EQ(tilingInfo.tilingKey, 256);65+ EXPECT_EQ(tilingInfo.tilingKey, 267);
66 ASSERT_EQ(tilingInfo.workspaceSizes.size(), 1);66 ASSERT_EQ(tilingInfo.workspaceSizes.size(), 1);
67- EXPECT_EQ(tilingInfo.workspaceSizes[0], WORK_SPACE_SIZE);67+ EXPECT_EQ(tilingInfo.workspaceSizes[0], WORK_SPACE_SIZE + 4224UL);
68}68}
69 69 
70TEST_F(KthValueTilingTest, test_kthvalue_axis_one_copy_fp32_100x1)70TEST_F(KthValueTilingTest, test_kthvalue_axis_one_copy_fp32_100x1)
@@ -91,16 +91,16 @@ TEST_F(KthValueTilingTest, test_kthvalue_merge_sort_bf16_8x512)
91 EXPECT_EQ(tilingInfo.workspaceSizes[0], WORK_SPACE_SIZE);91 EXPECT_EQ(tilingInfo.workspaceSizes[0], WORK_SPACE_SIZE);
92}92}
93 93 
94-TEST_F(KthValueTilingTest, test_kthvalue_merge_sort_fp32_3d_8x16x1024)94+TEST_F(KthValueTilingTest, test_kthvalue_radix_select_fp32_3d_8x16x1024)
95{95{
96 auto tilingContextPara = MakeKthValueTilingContext({{8, 16, 1024}, {8, 16, 1024}}, {{8, 16, 1}, {8, 16, 1}},96 auto tilingContextPara = MakeKthValueTilingContext({{8, 16, 1024}, {8, 16, 1024}}, {{8, 16, 1}, {8, 16, 1}},
97 {{8, 16, 1}, {8, 16, 1}}, ge::DT_FLOAT, 500, 2);97 {{8, 16, 1}, {8, 16, 1}}, ge::DT_FLOAT, 500, 2);
98 98 
99 TilingInfo tilingInfo;99 TilingInfo tilingInfo;
100 ASSERT_TRUE(ExecuteTiling(tilingContextPara, tilingInfo));100 ASSERT_TRUE(ExecuteTiling(tilingContextPara, tilingInfo));
101- EXPECT_EQ(tilingInfo.tilingKey, 256);101+ EXPECT_EQ(tilingInfo.tilingKey, 267);
102 ASSERT_EQ(tilingInfo.workspaceSizes.size(), 1);102 ASSERT_EQ(tilingInfo.workspaceSizes.size(), 1);
103- EXPECT_EQ(tilingInfo.workspaceSizes[0], WORK_SPACE_SIZE);103+ EXPECT_EQ(tilingInfo.workspaceSizes[0], WORK_SPACE_SIZE + 135168UL);
104}104}
105 105 
106TEST_F(KthValueTilingTest, test_kthvalue_axis_one_copy_int32_64x1)106TEST_F(KthValueTilingTest, test_kthvalue_axis_one_copy_int32_64x1)
@@ -132,7 +132,7 @@ TEST_F(KthValueTilingTest, test_kthvalue_axis_one_copy_allows_unsorted_dim_over_
132 EXPECT_EQ(tilingInfo.workspaceSizes[0], WORK_SPACE_SIZE);132 EXPECT_EQ(tilingInfo.workspaceSizes[0], WORK_SPACE_SIZE);
133}133}
134 134 
135-TEST_F(KthValueTilingTest, test_kthvalue_merge_intra_core_allows_unsorted_dim_over_uint32)135+TEST_F(KthValueTilingTest, test_kthvalue_radix_select_allows_unsorted_dim_over_uint32)
136{136{
137 int64_t largeBatch = static_cast<int64_t>(std::numeric_limits<uint32_t>::max()) + 1;137 int64_t largeBatch = static_cast<int64_t>(std::numeric_limits<uint32_t>::max()) + 1;
138 auto tilingContextPara = MakeKthValueTilingContext({{largeBatch, 4097}, {largeBatch, 4097}},138 auto tilingContextPara = MakeKthValueTilingContext({{largeBatch, 4097}, {largeBatch, 4097}},
@@ -141,7 +141,7 @@ TEST_F(KthValueTilingTest, test_kthvalue_merge_intra_core_allows_unsorted_dim_ov
141 141 
142 TilingInfo tilingInfo;142 TilingInfo tilingInfo;
143 ASSERT_TRUE(ExecuteTiling(tilingContextPara, tilingInfo));143 ASSERT_TRUE(ExecuteTiling(tilingContextPara, tilingInfo));
144- EXPECT_EQ(tilingInfo.tilingKey, 260);144+ EXPECT_EQ(tilingInfo.tilingKey, 267);
145 ASSERT_GE(tilingInfo.tilingDataSize, sizeof(KthValueTilingData));145 ASSERT_GE(tilingInfo.tilingDataSize, sizeof(KthValueTilingData));
146 const auto* tilingData = reinterpret_cast<const KthValueTilingData*>(tilingInfo.tilingData.get());146 const auto* tilingData = reinterpret_cast<const KthValueTilingData*>(tilingInfo.tilingData.get());
147 EXPECT_EQ(tilingData->unsortedDimNum, largeBatch);147 EXPECT_EQ(tilingData->unsortedDimNum, largeBatch);
@@ -11,6 +11,7 @@
11#include "kernel_operator.h"11#include "kernel_operator.h"
12#include "kernel_tiling/kernel_tiling.h"12#include "kernel_tiling/kernel_tiling.h"
13 13 
14+#define KTH_VALUE_STATIC_MEDIAN_FAST_PATH 1
14#include "../../kth_value/arch35/kth_value_kernel_dispatch.h"15#include "../../kth_value/arch35/kth_value_kernel_dispatch.h"
15 16 
16using namespace AscendC;17using namespace AscendC;
@@ -31,7 +32,17 @@ __global__ __aicore__ void median(GM_ADDR x, GM_ADDR y, GM_ADDR indices, GM_ADDR
31 KERNEL_TASK_TYPE(MEDIAN_MERGE_MORE_CORE_TILING_KEY, KERNEL_TYPE_MIX_AIV_1_0);32 KERNEL_TASK_TYPE(MEDIAN_MERGE_MORE_CORE_TILING_KEY, KERNEL_TYPE_MIX_AIV_1_0);
32 KERNEL_TASK_TYPE(MEDIAN_RADIX_SELECT_TILING_KEY, KERNEL_TYPE_MIX_AIV_1_0);33 KERNEL_TASK_TYPE(MEDIAN_RADIX_SELECT_TILING_KEY, KERNEL_TYPE_MIX_AIV_1_0);
33 REGISTER_TILING_DEFAULT(KthValueTilingData);34 REGISTER_TILING_DEFAULT(KthValueTilingData);
34- GET_TILING_DATA_WITH_STRUCT(KthValueTilingData, tilingData, tiling);
35 TPipe pipe;35 TPipe pipe;
36- KthValue::Dispatch<true, schId, isInt32>(x, y, indices, workspace, &tilingData, &pipe);36+ if constexpr (schId == KTH_VALUE_SCHID_MERGE_SORT || schId == KTH_VALUE_SCHID_SORT32_SMALL_AXIS) {
37+ REGISTER_TILING_FOR_TILINGKEY(KTH_VALUE_MERGE_ONE_CORE_TILING_CONDITION, KthValueMergeOneCoreTilingData);
38+ GET_TILING_DATA_WITH_STRUCT(KthValueMergeOneCoreTilingData, tilingData, tiling);
39+ KthValue::Dispatch<true, schId, isInt32>(x, y, indices, workspace, &tilingData, &pipe);
40+ } else if constexpr (schId == KTH_VALUE_SCHID_RADIX_ONE_CORE) {
41+ REGISTER_TILING_FOR_TILINGKEY(KTH_VALUE_RADIX_ONE_CORE_TILING_CONDITION, KthValueRadixOneCoreTilingData);
42+ GET_TILING_DATA_WITH_STRUCT(KthValueRadixOneCoreTilingData, tilingData, tiling);
43+ KthValue::Dispatch<true, schId, isInt32>(x, y, indices, workspace, &tilingData, &pipe);
44+ } else {
45+ GET_TILING_DATA_WITH_STRUCT(KthValueTilingData, tilingData, tiling);
46+ KthValue::Dispatch<true, schId, isInt32>(x, y, indices, workspace, &tilingData, &pipe);
47+ }
37}48}
@@ -51,7 +51,8 @@ TEST(MedianTilingTest, FloatMedianUsesNanPropagatingMode)
51 TilingInfo tilingInfo;51 TilingInfo tilingInfo;
52 ASSERT_TRUE(ExecuteTiling(context, tilingInfo));52 ASSERT_TRUE(ExecuteTiling(context, tilingInfo));
53 EXPECT_EQ(tilingInfo.tilingKey, 264);53 EXPECT_EQ(tilingInfo.tilingKey, 264);
54- const auto* tilingData = GetTilingData(tilingInfo);54+ ASSERT_EQ(tilingInfo.tilingDataSize, sizeof(KthValueMergeOneCoreTilingData));
55+ const auto* tilingData = reinterpret_cast<const KthValueMergeOneCoreTilingData*>(tilingInfo.tilingData.get());
55 EXPECT_EQ(tilingData->medianMode, 1U);56 EXPECT_EQ(tilingData->medianMode, 1U);
56 EXPECT_EQ(tilingData->kthIndex, 2);57 EXPECT_EQ(tilingData->kthIndex, 2);
57 ASSERT_EQ(tilingInfo.workspaceSizes.size(), 1);58 ASSERT_EQ(tilingInfo.workspaceSizes.size(), 1);
@@ -31,7 +31,17 @@ __global__ __aicore__ void nan_median(GM_ADDR x, GM_ADDR y, GM_ADDR indices, GM_
31 KERNEL_TASK_TYPE(NAN_MEDIAN_MERGE_MORE_CORE_TILING_KEY, KERNEL_TYPE_MIX_AIV_1_0);31 KERNEL_TASK_TYPE(NAN_MEDIAN_MERGE_MORE_CORE_TILING_KEY, KERNEL_TYPE_MIX_AIV_1_0);
32 KERNEL_TASK_TYPE(NAN_MEDIAN_RADIX_SELECT_TILING_KEY, KERNEL_TYPE_MIX_AIV_1_0);32 KERNEL_TASK_TYPE(NAN_MEDIAN_RADIX_SELECT_TILING_KEY, KERNEL_TYPE_MIX_AIV_1_0);
33 REGISTER_TILING_DEFAULT(KthValueTilingData);33 REGISTER_TILING_DEFAULT(KthValueTilingData);
34- GET_TILING_DATA_WITH_STRUCT(KthValueTilingData, tilingData, tiling);
35 TPipe pipe;34 TPipe pipe;
36- KthValue::Dispatch<true, schId, isInt32>(x, y, indices, workspace, &tilingData, &pipe);35+ if constexpr (schId == KTH_VALUE_SCHID_MERGE_SORT || schId == KTH_VALUE_SCHID_SORT32_SMALL_AXIS) {
36+ REGISTER_TILING_FOR_TILINGKEY(KTH_VALUE_MERGE_ONE_CORE_TILING_CONDITION, KthValueMergeOneCoreTilingData);
37+ GET_TILING_DATA_WITH_STRUCT(KthValueMergeOneCoreTilingData, tilingData, tiling);
38+ KthValue::Dispatch<true, schId, isInt32>(x, y, indices, workspace, &tilingData, &pipe);
39+ } else if constexpr (schId == KTH_VALUE_SCHID_RADIX_ONE_CORE) {
40+ REGISTER_TILING_FOR_TILINGKEY(KTH_VALUE_RADIX_ONE_CORE_TILING_CONDITION, KthValueRadixOneCoreTilingData);
41+ GET_TILING_DATA_WITH_STRUCT(KthValueRadixOneCoreTilingData, tilingData, tiling);
42+ KthValue::Dispatch<true, schId, isInt32>(x, y, indices, workspace, &tilingData, &pipe);
43+ } else {
44+ GET_TILING_DATA_WITH_STRUCT(KthValueTilingData, tilingData, tiling);
45+ KthValue::Dispatch<true, schId, isInt32>(x, y, indices, workspace, &tilingData, &pipe);
46+ }
37}47}
@@ -48,7 +48,8 @@ TEST(NanMedianTilingTest, UsesIgnoreNanMode)
48 TilingInfo tilingInfo;48 TilingInfo tilingInfo;
49 ASSERT_TRUE(ExecuteTiling(context, tilingInfo));49 ASSERT_TRUE(ExecuteTiling(context, tilingInfo));
50 EXPECT_EQ(tilingInfo.tilingKey, 264);50 EXPECT_EQ(tilingInfo.tilingKey, 264);
51- const auto* tilingData = GetTilingData(tilingInfo);51+ ASSERT_EQ(tilingInfo.tilingDataSize, sizeof(KthValueMergeOneCoreTilingData));
52+ const auto* tilingData = reinterpret_cast<const KthValueMergeOneCoreTilingData*>(tilingInfo.tilingData.get());
52 EXPECT_EQ(tilingData->medianMode, 2U);53 EXPECT_EQ(tilingData->medianMode, 2U);
53 EXPECT_EQ(tilingData->kthIndex, 3);54 EXPECT_EQ(tilingData->kthIndex, 3);
54}55}