Files
2026-07-13 12:40:42 +08:00

168 lines
6.5 KiB
C++

// Copyright (c) 2023 PaddlePaddle Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "paddle/phi/backends/gpu/gpu_context.h"
#include "paddle/phi/common/amp_type_traits.h"
namespace phi {
namespace funcs {
template <typename T, typename IdT, int WarpSize, int BlockDimY, bool UseLimit>
__global__ void EmbeddingGradDeterministicKernel(T* table,
const T* output,
const IdT* ids,
const int64_t K,
const int64_t D,
const int64_t start_idx,
const int64_t end_idx) {
using MT = typename MPTypeTrait<T>::Type;
constexpr int64_t kInvalidId = -1;
extern __shared__ char buf[];
MT* smem = reinterpret_cast<MT*>(buf);
MT* my_s = smem + WarpSize * threadIdx.y;
IdT* indices_batch =
reinterpret_cast<IdT*>(buf + sizeof(MT) * WarpSize * BlockDimY);
const int stride = static_cast<int>(D);
const int feature = threadIdx.x + blockIdx.x * WarpSize;
// To ensure determinism. If any other warps pulled grad data targeting
// dst_row, we elect the first warp in each matching group as the leader.
// Each leader warp serializes the accumulates targeting dst_row in shared
// memory, then adding the accumulated buffer to dst_row in table.
for (int batch_start = 0; batch_start < K;
batch_start += WarpSize * BlockDimY) {
int tid = threadIdx.x + threadIdx.y * WarpSize;
if (batch_start + tid < K) {
int64_t cur_id = static_cast<int64_t>(ids[batch_start + tid]);
if (UseLimit) {
if (cur_id >= start_idx && cur_id < end_idx) {
cur_id -= start_idx;
} else {
cur_id = kInvalidId;
}
}
indices_batch[tid] = cur_id;
}
int batch_end =
min(static_cast<int64_t>(batch_start + WarpSize * BlockDimY), K);
// Loop over the batch of <= 1024 loaded indices in chunks of BLOCKDIMY
for (int chunk_start = batch_start; chunk_start < batch_end;
chunk_start += BlockDimY) {
// This sync makes sure that indices_batch is ready and match-group
// leaders are done with their accumulates before other warps start
// loading again.
__syncthreads();
int n_this_chunk = min(batch_end - chunk_start, BlockDimY);
int64_t src_row = static_cast<int64_t>(chunk_start + threadIdx.y);
int64_t dst_row = indices_batch[src_row - batch_start];
if (src_row < K && feature < stride) {
if (UseLimit && dst_row == kInvalidId) {
my_s[threadIdx.x] = static_cast<MT>(0);
} else {
my_s[threadIdx.x] = static_cast<MT>(output[src_row * D + feature]);
}
}
__syncthreads();
if (src_row < K) {
int match_found_this_thread = 0;
if (threadIdx.x < n_this_chunk &&
(!UseLimit || dst_row != kInvalidId)) {
match_found_this_thread =
(dst_row ==
indices_batch[chunk_start - batch_start + threadIdx.x]);
}
#ifdef PADDLE_WITH_HIP
unsigned long long int matchmask = // NOLINT
__ballot(match_found_this_thread); // NOLINT
int first_remaining_peer = __ffsll(matchmask) - 1;
#else
// If and only if match_found_this_thread of the Nth thread is non-zero,
// set the Nth bit of matchmask to 1.
unsigned int matchmask =
__ballot_sync(0xffffffff, match_found_this_thread);
// Find the position of the first bit set to 1 in matchmask.
int first_remaining_peer = __ffs(matchmask) - 1;
#endif
// select lowest-indexed warp as the leader
if (threadIdx.y == first_remaining_peer) {
// Set the first bit 1 in matchmask to 0.
matchmask ^= (1 << first_remaining_peer);
while (matchmask) {
#ifdef PADDLE_WITH_HIP
first_remaining_peer = __ffsll(matchmask) - 1;
#else
first_remaining_peer = __ffs(matchmask) - 1;
#endif
my_s[threadIdx.x] +=
smem[threadIdx.x + WarpSize * first_remaining_peer];
matchmask ^= (1 << first_remaining_peer);
}
if (feature < stride && (!UseLimit || dst_row != kInvalidId)) {
auto table_idx = dst_row * D + feature;
table[table_idx] = static_cast<T>(
static_cast<MT>(table[table_idx]) + my_s[threadIdx.x]);
}
}
}
}
}
}
template <typename T, typename IdT>
void LaunchEmbeddingGradDeterministicKernel(const GPUContext& dev_ctx,
const IdT* ids,
const T* d_out,
T* d_table,
int64_t N,
int64_t D,
int64_t K,
int64_t start_idx = -1) {
#ifdef PADDLE_WITH_HIP
constexpr int kWarpSize = 64;
constexpr int kBlockDimY = 16;
#else
constexpr int kWarpSize = 32;
constexpr int kBlockDimY = 32;
#endif
dim3 threads(kWarpSize, kBlockDimY);
dim3 grids(static_cast<int>((D + kWarpSize - 1) / kWarpSize));
using MT = typename MPTypeTrait<T>::Type;
constexpr auto kSharedMemSize = sizeof(MT) * kWarpSize * kBlockDimY +
sizeof(IdT) * kWarpSize * kBlockDimY;
if (start_idx < 0) {
EmbeddingGradDeterministicKernel<T, IdT, kWarpSize, kBlockDimY, false>
<<<grids, threads, kSharedMemSize, dev_ctx.stream()>>>(
d_table, d_out, ids, K, D, -1, -1);
} else {
int64_t end_idx = start_idx + N;
EmbeddingGradDeterministicKernel<T, IdT, kWarpSize, kBlockDimY, true>
<<<grids, threads, kSharedMemSize, dev_ctx.stream()>>>(
d_table, d_out, ids, K, D, start_idx, end_idx);
}
}
} // namespace funcs
} // namespace phi