Skip to content

Commit 9fa0b3b

Browse files
jeffdailyfacebook-github-bot
authored andcommitted
derive TBE cache associativity from device warp size (#6037)
Summary: X-link: facebookresearch/FBGEMM#2940 The TBE LRU/LFU/LXU cache hardcoded associativity (ways per set) to 64 on ROCm (`DEFAULT_ASSOC`) and guarded the populate/lookup kernels with `TORCH_CHECK(warp_size() == 64)`, refusing to run on wave32 ROCm devices. Cache associativity must equal the device warp size because one warp cooperatively scans the ways of a single set. The cache kernels are not templated on warp size: device code uses the per-arch `kWarpSize` constant for row indexing, so a multi-arch fat binary already contains correct per-arch device code. Only the host-allocated cache geometry was wrong. Derive per-instance associativity from the running device (`torch.cuda.get_device_properties(dev).warp_size`) in the training and nbit inference modules, so cache-state tensors match the device kernel's per-arch indexing, and remove the wave64-only `TORCH_CHECK` guards. `DEFAULT_ASSOC` stays as the CPU/no-device fallback. Host-side cache kernel launch configs move to `kWarpSizeHost()`. The cache tests query the device warp size instead of assuming `DEFAULT_ASSOC`, and `lxu_cache_test` now also covers the direct-mapped (associativity 1) path. Because `DEFAULT_ASSOC` no longer equals the device warp size on ROCm, `cache_test.py`'s `test_lru_cache_insert_large_grid` (which drives the low-level `lru_cache_populate` op directly and must allocate cache geometry matching the kernel's `kWarpSize` row stride) is updated to query the device warp size. Left unchanged it allocates a 32-wide cache on a wave64 device and the kernel, striding by 64, leaves half the slots unpopulated. One deviation from the combined change (#5804): in `ssd_cache_actions_insert_kernel`, the flat cache-slot index (`cache_set * kWarpSize + insert_slot`) and the conflict-miss stride are `__device__` code and must use the per-arch device constant `kWarpSize`. #5804 changed these to `kWarpSizeHost()`, which calls `at::cuda::warp_size()` -- a host-only function not callable from device code. It is masked on single-arch builds only because `kWarpSizeHost() == kWarpSize` there. These sites are kept as `kWarpSize`; only the host-side launch configs in that file use `kWarpSizeHost()`. Third in the chain splitting #5804 into reviewable pieces; stacked on #6031. Authored with assistance from Claude (Anthropic). Reviewed By: henrylhtsang Differential Revision: D112940757 Pulled By: q10
1 parent 7c20d00 commit 9fa0b3b

15 files changed

Lines changed: 131 additions & 104 deletions

fbgemm_gpu/fbgemm_gpu/split_table_batched_embeddings_ops_inference.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -394,10 +394,6 @@ def __init__( # noqa C901
394394
f"Feature Gates: {[(feature.name, feature.is_enabled()) for feature in FeatureGateName]}"
395395
)
396396

397-
# 64 for AMD
398-
if cache_assoc == 32 and torch.version.hip is not None:
399-
cache_assoc = 64
400-
401397
if device is None:
402398
self.current_device: torch.device = torch.device(
403399
torch.cuda.current_device()
@@ -408,6 +404,20 @@ def __init__( # noqa C901
408404
self.current_device = torch.device(device)
409405
self.use_cpu: bool = self.current_device.type == "cpu"
410406

407+
# Cache associativity must equal the device warp size (see the training
408+
# module's _apply_cache_state for the rationale). Only query a live CUDA
409+
# device: on CPU, a meta device (tracing/sharding/publish), or a
410+
# CUDA-typed device on a host with no driver, get_device_properties
411+
# raises, so fall back to the default associativity in those cases.
412+
if (
413+
cache_assoc == 32
414+
and self.current_device.type == "cuda"
415+
and torch.cuda.is_available()
416+
):
417+
cache_assoc = torch.cuda.get_device_properties(
418+
self.current_device
419+
).warp_size
420+
411421
self.scale_bias_size_in_bytes = scale_bias_size_in_bytes
412422
self.pooling_mode = pooling_mode
413423
self.bounds_check_mode_int: int = bounds_check_mode.value

fbgemm_gpu/fbgemm_gpu/split_table_batched_embeddings_ops_training.py

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@
9292
pass
9393

9494

95-
DEFAULT_ASSOC = 32 if torch.version.hip is None else 64
95+
# Default cache associativity. Overridden to 64 in each module's
96+
# __init__ on AMD devices that have 64-lane waves.
97+
DEFAULT_ASSOC = 32
9698
INT8_EMB_ROW_DIM_OFFSET = 8
9799

98100

@@ -3730,6 +3732,20 @@ def _apply_cache_state(
37303732
self.timestep = 1
37313733
self.timesteps_prefetched = []
37323734

3735+
# Cache associativity (ways per set) must equal the device warp size:
3736+
# the cache kernels use one warp to cooperatively scan the ways of a
3737+
# single set. CDNA (gfx9xx) is warp 64, RDNA (gfx11xx) is warp 32, and
3738+
# NVIDIA is warp 32. Query the actual device when a live CUDA device is
3739+
# present, so a single multi-arch ROCm build is correct on both wave
3740+
# sizes. On CPU, a meta device (tracing/sharding/publish), or a
3741+
# CUDA-typed device on a host with no driver, get_device_properties
3742+
# raises, so fall back to the module-level DEFAULT_ASSOC in those cases.
3743+
self.cache_assoc: int = DEFAULT_ASSOC
3744+
if self.current_device.type == "cuda" and torch.cuda.is_available():
3745+
self.cache_assoc = torch.cuda.get_device_properties(
3746+
self.current_device
3747+
).warp_size
3748+
37333749
self.max_prefetch_depth = MAX_PREFETCH_DEPTH
37343750
self.lxu_cache_locations_list = []
37353751
self.lxu_cache_locations_empty = torch.empty(
@@ -3812,24 +3828,24 @@ def _apply_cache_state(
38123828
assert free_memory > 0
38133829
cache_sets = (
38143830
int(cache_state.total_cache_hash_size * cache_load_factor)
3815-
+ DEFAULT_ASSOC
3831+
+ self.cache_assoc
38163832
- 1
3817-
) // DEFAULT_ASSOC
3833+
) // self.cache_assoc
38183834
cache_sets = 1 if cache_sets == 0 else cache_sets
3819-
cache_size = cache_sets * DEFAULT_ASSOC * element_size * self.max_D_cache
3835+
cache_size = cache_sets * self.cache_assoc * element_size * self.max_D_cache
38203836
if cache_size > free_memory:
38213837
cache_sets = (
38223838
int(1.0 * free_memory / self.max_D_cache / element_size)
3823-
+ DEFAULT_ASSOC
3839+
+ self.cache_assoc
38243840
- 1
3825-
) // DEFAULT_ASSOC
3841+
) // self.cache_assoc
38263842
cache_load_factor = (
3827-
1.0 * cache_sets * DEFAULT_ASSOC / int(cache_state.total_cache_hash_size)
3843+
1.0 * cache_sets * self.cache_assoc / int(cache_state.total_cache_hash_size)
38283844
)
38293845
assert cache_sets > 0
38303846
if cache_algorithm == CacheAlgorithm.LFU:
38313847
assert cache_sets < 2**24 - 1
3832-
cache_size = cache_sets * DEFAULT_ASSOC * element_size * self.max_D_cache
3848+
cache_size = cache_sets * self.cache_assoc * element_size * self.max_D_cache
38333849
self.log(
38343850
f"Using on-device cache with admission algorithm "
38353851
f"{cache_algorithm}, {cache_sets} sets, "
@@ -3862,14 +3878,17 @@ def _apply_cache_state(
38623878
self.register_buffer(
38633879
"lxu_cache_state",
38643880
torch.zeros(
3865-
cache_sets, DEFAULT_ASSOC, device=self.current_device, dtype=torch.int64
3881+
cache_sets,
3882+
self.cache_assoc,
3883+
device=self.current_device,
3884+
dtype=torch.int64,
38663885
).fill_(-1),
38673886
)
38683887
# Cache itself, not auxiliary size
38693888
self.register_buffer(
38703889
"lxu_cache_weights",
38713890
torch.zeros(
3872-
cache_sets * DEFAULT_ASSOC,
3891+
cache_sets * self.cache_assoc,
38733892
self.max_D_cache,
38743893
device=self.current_device,
38753894
dtype=dtype,
@@ -3883,7 +3902,7 @@ def _apply_cache_state(
38833902
size=(
38843903
(self.total_cache_hash_size + 1,)
38853904
if cache_algorithm == CacheAlgorithm.LFU
3886-
else (cache_sets, DEFAULT_ASSOC)
3905+
else (cache_sets, self.cache_assoc)
38873906
),
38883907
device=self.current_device,
38893908
dtype=torch.int64,
@@ -4028,7 +4047,7 @@ def _init_uvm_cache_counter(self, cache_sets: int, persistent: bool) -> None:
40284047
"lxu_cache_locking_counter",
40294048
torch.zeros(
40304049
cache_sets,
4031-
DEFAULT_ASSOC,
4050+
self.cache_assoc,
40324051
device=self.current_device,
40334052
dtype=torch.int32,
40344053
),

fbgemm_gpu/fbgemm_gpu/tbe/cache/cache_config.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,12 @@
3030
import enum
3131
from typing import NamedTuple
3232

33-
import torch
34-
3533
# Import from config module
3634
from fbgemm_gpu.tbe.config import EmbeddingLocation
3735

38-
# Default associativity for the UVM cache (32 for CUDA, 64 for ROCm)
39-
DEFAULT_ASSOC: int = 32 if torch.version.hip is None else 64
36+
# Default cache associativity. Overridden to 64 in each module's
37+
# __init__ on AMD devices that have 64-lane waves.
38+
DEFAULT_ASSOC: int = 32
4039

4140

4241
class CacheAlgorithm(enum.Enum):

fbgemm_gpu/src/split_embeddings_cache/lfu_cache_find.cu

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,9 @@ std::pair<Tensor, Tensor> lfu_cache_find_uncached_cuda(
124124
FBGEMM_LAUNCH_KERNEL(
125125
(lfu_cache_find_uncached_kernel<index_t>),
126126
std::min(
127-
div_round_up(N, kMaxThreads / kWarpSize),
127+
div_round_up(N, kMaxThreads / kWarpSizeHost()),
128128
get_max_thread_blocks_for_cache_kernels_()),
129-
dim3(kWarpSize, kMaxThreads / kWarpSize),
129+
dim3(kWarpSizeHost(), kMaxThreads / kWarpSizeHost()),
130130
0,
131131
at::cuda::getCurrentCUDAStream(),
132132
PTA_B(unique_indices, index_t, 1, 32),

fbgemm_gpu/src/split_embeddings_cache/lfu_cache_populate.cu

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,9 @@ void lfu_cache_insert_cuda(
200200
FBGEMM_LAUNCH_KERNEL(
201201
(lfu_cache_insert_kernel<emb_t, cache_t>),
202202
std::min(
203-
div_round_up(N, kCacheMaxThreads / kWarpSize),
203+
div_round_up(N, kCacheMaxThreads / kWarpSizeHost()),
204204
get_max_thread_blocks_for_cache_kernels_()),
205-
dim3(kWarpSize, kCacheMaxThreads / kWarpSize),
205+
dim3(kWarpSizeHost(), kCacheMaxThreads / kWarpSizeHost()),
206206
0,
207207
at::cuda::getCurrentCUDAStream(),
208208
PTA_B(weights, emb_t, 1, 64),
@@ -248,15 +248,6 @@ DLL_PUBLIC void lfu_cache_populate_cuda(
248248

249249
CUDA_DEVICE_GUARD(weights);
250250

251-
#ifdef USE_ROCM
252-
TORCH_CHECK(
253-
at::cuda::warp_size() == 64,
254-
__func__,
255-
": TBE cache requires warpSize 64 on ROCm (got ",
256-
at::cuda::warp_size(),
257-
"); warpSize 32 devices are not yet supported");
258-
#endif
259-
260251
TORCH_CHECK(
261252
linear_cache_indices.numel() < std::numeric_limits<int32_t>::max());
262253
if (linear_cache_indices.numel() == 0) {

fbgemm_gpu/src/split_embeddings_cache/lfu_cache_populate_byte.cu

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,9 @@ void lfu_cache_insert_byte_cuda(
176176
FBGEMM_LAUNCH_KERNEL(
177177
(lfu_cache_insert_byte_kernel<index_t>),
178178
std::min(
179-
div_round_up(N, kCacheMaxThreads / kWarpSize),
179+
div_round_up(N, kCacheMaxThreads / kWarpSizeHost()),
180180
get_max_thread_blocks_for_cache_kernels_()),
181-
dim3(kWarpSize, kCacheMaxThreads / kWarpSize),
181+
dim3(kWarpSizeHost(), kCacheMaxThreads / kWarpSizeHost()),
182182
0,
183183
at::cuda::getCurrentCUDAStream(),
184184
PTA_B(weights, uint8_t, 1, 64),

fbgemm_gpu/src/split_embeddings_cache/lru_cache_find.cu

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,15 +214,15 @@ lru_cache_find_uncached_cuda(
214214
constexpr int PREFETCH_KERNEL_MAX_BLOCKS = 8;
215215

216216
auto grid_size = std::min(
217-
div_round_up(N, kMaxThreads / kWarpSize),
217+
div_round_up(N, kMaxThreads / kWarpSizeHost()),
218218
lock_cache_line ? PREFETCH_KERNEL_MAX_BLOCKS
219219
: get_max_thread_blocks_for_cache_kernels_());
220220

221221
// Find uncached indices
222222
FBGEMM_LAUNCH_KERNEL(
223223
(lru_cache_find_uncached_kernel<index_t>),
224224
grid_size,
225-
dim3(kWarpSize, kMaxThreads / kWarpSize),
225+
dim3(kWarpSizeHost(), kMaxThreads / kWarpSizeHost()),
226226
0,
227227
at::cuda::getCurrentCUDAStream(),
228228
PTA_B(unique_indices, index_t, 1, 32),

fbgemm_gpu/src/split_embeddings_cache/lru_cache_populate.cu

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ void lru_cache_insert_cuda(
226226

227227
const auto grid_size_uncapped = lock_cache_line
228228
? div_round_up(get_device_sm_cnt_(), ALL_TO_PREFETCH_SM_RATIO)
229-
: div_round_up(N, kMaxThreads / kWarpSize);
229+
: div_round_up(N, kMaxThreads / kWarpSizeHost());
230230
// HIP enforces a hard limit of 2^32 total threads per launch.
231231
// lru_cache_insert_kernel grid-strides over n, so capping is
232232
// correctness-preserving. The lock_cache_line=true branch is already
@@ -240,7 +240,7 @@ void lru_cache_insert_cuda(
240240
FBGEMM_LAUNCH_KERNEL(
241241
(lru_cache_insert_kernel<emb_t, cache_t>),
242242
grid_size,
243-
dim3(kWarpSize, kMaxThreads / kWarpSize),
243+
dim3(kWarpSizeHost(), kMaxThreads / kWarpSizeHost()),
244244
0,
245245
at::cuda::getCurrentCUDAStream(),
246246
PTA_B(weights, emb_t, 1, 64),
@@ -311,15 +311,6 @@ DLL_PUBLIC void lru_cache_populate_cuda(
311311

312312
CUDA_DEVICE_GUARD(weights);
313313

314-
#ifdef USE_ROCM
315-
TORCH_CHECK(
316-
at::cuda::warp_size() == 64,
317-
__func__,
318-
": TBE cache requires warpSize 64 on ROCm (got ",
319-
at::cuda::warp_size(),
320-
"); warpSize 32 devices are not yet supported");
321-
#endif
322-
323314
TORCH_CHECK(
324315
linear_cache_indices.numel() < std::numeric_limits<int32_t>::max());
325316
if (linear_cache_indices.numel() == 0) {

fbgemm_gpu/src/split_embeddings_cache/lru_cache_populate_byte.cu

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -397,9 +397,9 @@ void lru_cache_insert_byte_cuda(
397397
FBGEMM_LAUNCH_KERNEL(
398398
(lru_cache_insert_byte_kernel<index_t>),
399399
std::min(
400-
div_round_up(N, kMaxThreads / kWarpSize),
400+
div_round_up(N, kMaxThreads / kWarpSizeHost()),
401401
get_max_thread_blocks_for_cache_kernels_()),
402-
dim3(kWarpSize, kMaxThreads / kWarpSize),
402+
dim3(kWarpSizeHost(), kMaxThreads / kWarpSizeHost()),
403403
0,
404404
at::cuda::getCurrentCUDAStream(),
405405
PTA_B(weights, uint8_t, 1, 64),
@@ -462,9 +462,9 @@ void direct_mapped_lru_cache_insert_byte_cuda(
462462
FBGEMM_LAUNCH_KERNEL(
463463
(direct_mapped_lru_cache_insert_byte_kernel<index_t>),
464464
std::min(
465-
div_round_up(N, kMaxThreads / kWarpSize),
465+
div_round_up(N, kMaxThreads / kWarpSizeHost()),
466466
get_max_thread_blocks_for_cache_kernels_()),
467-
dim3(kWarpSize, kMaxThreads / kWarpSize),
467+
dim3(kWarpSizeHost(), kMaxThreads / kWarpSizeHost()),
468468
0,
469469
at::cuda::getCurrentCUDAStream(),
470470
PTA_B(weights, uint8_t, 1, 64),

fbgemm_gpu/src/split_embeddings_cache/lxu_cache.cu

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,8 @@ void lxu_cache_locking_counter_decrement_cuda(
224224

225225
auto count = at::zeros_like(lxu_cache_locking_counter);
226226
const int32_t C = lxu_cache_locking_counter.size(0);
227-
TORCH_CHECK(lxu_cache_locking_counter.size(1) == kWarpSize);
228-
auto fd = FixedDivisor(kWarpSize);
227+
TORCH_CHECK(lxu_cache_locking_counter.size(1) == kWarpSizeHost());
228+
auto fd = FixedDivisor(kWarpSizeHost());
229229

230230
const dim3 blocks(
231231
std::min(
@@ -245,9 +245,9 @@ void lxu_cache_locking_counter_decrement_cuda(
245245
FBGEMM_LAUNCH_KERNEL(
246246
lxu_cache_locking_counter_decrement_kernel,
247247
std::min(
248-
div_round_up(C, kMaxThreads / kWarpSize),
248+
div_round_up(C, kMaxThreads / kWarpSizeHost()),
249249
get_max_thread_blocks_for_cache_kernels_()),
250-
dim3(kWarpSize, kMaxThreads / kWarpSize),
250+
dim3(kWarpSizeHost(), kMaxThreads / kWarpSizeHost()),
251251
0,
252252
at::cuda::getCurrentCUDAStream(),
253253
PTA_B(lxu_cache_locking_counter, int32_t, 2, 32),
@@ -435,19 +435,6 @@ DLL_PUBLIC Tensor lxu_cache_lookup_cuda(
435435

436436
CUDA_DEVICE_GUARD(linear_cache_indices);
437437

438-
#ifdef USE_ROCM
439-
// Cache kernels use kWarpSize as the stride for indexing cache rows.
440-
// On warpSize 32 ROCm devices (e.g. gfx1100) this produces wrong
441-
// addresses because DEFAULT_ASSOC (kWarpSize on host) is 64.
442-
// D102579845 introduces kCacheAssoc to fix this; until then, guard.
443-
TORCH_CHECK(
444-
at::cuda::warp_size() == 64,
445-
__func__,
446-
": TBE cache requires warpSize 64 on ROCm (got ",
447-
at::cuda::warp_size(),
448-
"); warpSize 32 devices are not yet supported");
449-
#endif
450-
451438
const auto lxu_cache_locations =
452439
lxu_cache_locations_output.value_or(empty_like(
453440
linear_cache_indices,
@@ -459,7 +446,7 @@ DLL_PUBLIC Tensor lxu_cache_lookup_cuda(
459446
return lxu_cache_locations;
460447
}
461448

462-
const dim3 threads(kWarpSize, kMaxThreads / kWarpSize);
449+
const dim3 threads(kWarpSizeHost(), kMaxThreads / kWarpSizeHost());
463450
const dim3 blocks(div_round_up(N, kMaxThreads));
464451

465452
AT_DISPATCH_INDEX_TYPES(

0 commit comments

Comments
 (0)