Skip to content

Commit b059883

Browse files
sryapfacebook-github-bot
authored andcommitted
Make evicted_rows a UVA buffer (#3079)
Summary: Pull Request resolved: #3079 X-link: facebookresearch/FBGEMM#173 Prior to this diff, SSD-TBE used a combination of a pinned CPU buffer and the GPU buffer for `evicted_rows` (the buffer for staging rows that are evicted from L1 cache). It explicitly performed asynchronous memory copy (via `cudaMemcpyAsync`) to transfer `evicted_rows` from device to host. Since the number of evicted rows is known only on the device, SSD-TBE overallocated the `evicted_rows` CPU and GPU buffers. Therefore, it transferred extra data during the device-host memory copy. Such the extra data could be large and could make the memory copy a bottleneck of an execution. This diff mitigates the problem mentioned above by using a unified address buffer for `evicted_rows` and using a kernel (namely `masked_index_select` to load/store data instead of using a CUDA memory copy operation. This mechanism can avoid the extra memory copy. However, the memory copy can be less efficient (might not be able to fully saturate the available memory bandwidth) since it does not use the copy engine. Moreover, since it uses SMs for memory copy, when overlapping the operator with other computes, it can potentially compete for the SM resources with others. Reviewed By: q10 Differential Revision: D62114877
1 parent 153fa62 commit b059883

1 file changed

Lines changed: 58 additions & 15 deletions

File tree

fbgemm_gpu/fbgemm_gpu/tbe/ssd/training.py

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,22 @@ def __init__(
309309
* self.lxu_cache_weights.element_size()
310310
), "The precomputed cache_size does not match the actual cache size"
311311

312+
# For storing weights to evict
313+
# The max number of rows to be evicted is limited by the number of
314+
# slots in the cache. Thus, we allocate `lxu_cache_evicted_weights` to
315+
# be the same shape as the L1 cache (lxu_cache_weights)
316+
self.register_buffer(
317+
"lxu_cache_evicted_weights",
318+
torch.ops.fbgemm.new_unified_tensor(
319+
torch.zeros(
320+
1,
321+
device=self.current_device,
322+
dtype=cache_dtype,
323+
),
324+
self.lxu_cache_weights.shape,
325+
is_host_mapped=self.uvm_host_mapped,
326+
),
327+
)
312328
self.timestep = 0
313329

314330
# Dummy profile configuration for measuring the SSD get/set time
@@ -418,8 +434,10 @@ def __init__(
418434

419435
# SSD get completion event
420436
self.ssd_event_get = torch.cuda.Event()
421-
# SSD eviction completion event
422-
self.ssd_event_evict = torch.cuda.Event()
437+
# SSD scratch pad eviction completion event
438+
self.ssd_event_sp_evict = torch.cuda.Event()
439+
# SSD cache eviction completion event
440+
self.ssd_event_cache_evict = torch.cuda.Event()
423441
# SSD backward completion event
424442
self.ssd_event_backward = torch.cuda.Event()
425443
# SSD get's input copy completion event
@@ -875,7 +893,7 @@ def _evict_from_scratch_pad(self, grad: Tensor) -> None:
875893
actions_count_cpu=actions_count_cpu,
876894
stream=self.ssd_eviction_stream,
877895
pre_event=self.ssd_event_backward,
878-
post_event=self.ssd_event_evict,
896+
post_event=self.ssd_event_sp_evict,
879897
is_rows_uvm=True,
880898
name="scratch_pad",
881899
)
@@ -1065,13 +1083,35 @@ def prefetch( # noqa C901
10651083
self.local_ssd_cache_stats,
10661084
)
10671085

1086+
# Allocate output tensors for compact_indices
1087+
compact_evicted_indices = torch.empty_like(evicted_indices)
1088+
compact_assigned_cache_slots = torch.empty_like(assigned_cache_slots)
1089+
compact_actions_count_gpu = torch.empty_like(actions_count_gpu)
1090+
1091+
# Defrag indices based on evicted_indices (removing -1 and making
1092+
# the non -1 elements contiguous). We need to do this because the
1093+
# number of rows in `lxu_cache_evicted_weights` might be smaller
1094+
# than the number of elements in `evicted_indices`. Without this
1095+
# step, we can run into the index out of bound issue
1096+
current_stream.wait_event(self.ssd_event_cache_evict)
1097+
torch.ops.fbgemm.compact_indices(
1098+
compact_indices=[compact_evicted_indices, compact_assigned_cache_slots],
1099+
compact_count=compact_actions_count_gpu,
1100+
indices=[evicted_indices, assigned_cache_slots],
1101+
masks=torch.where(evicted_indices != -1, 1, 0),
1102+
count=actions_count_gpu,
1103+
)
1104+
1105+
evicted_indices = compact_evicted_indices
1106+
10681107
with record_function("## ssd_d2h_inserted_indices ##"):
10691108
# Transfer actions_count and insert_indices right away to
10701109
# incrase an overlap opportunity
1071-
actions_count_cpu, inserted_indices_cpu = (
1110+
actions_count_cpu, compact_actions_count_cpu, inserted_indices_cpu = (
10721111
self.to_pinned_cpu_on_stream_wait_on_another_stream(
10731112
tensors=[
10741113
actions_count_gpu,
1114+
compact_actions_count_gpu,
10751115
inserted_indices,
10761116
],
10771117
stream=self.ssd_memcpy_stream,
@@ -1095,26 +1135,29 @@ def prefetch( # noqa C901
10951135
# Copy rows to be evicted into a separate buffer (will be evicted
10961136
# later in the prefetch step)
10971137
with record_function("## ssd_compute_evicted_rows ##"):
1098-
assigned_cache_slots = assigned_cache_slots.long()
1099-
evicted_rows = self.lxu_cache_weights[
1100-
assigned_cache_slots.clamp(min=0).long(), :
1101-
]
1138+
torch.ops.fbgemm.masked_index_select(
1139+
self.lxu_cache_evicted_weights,
1140+
compact_assigned_cache_slots,
1141+
self.lxu_cache_weights,
1142+
compact_actions_count_gpu,
1143+
)
11021144

11031145
# Allocation a scratch pad for the current iteration. The scratch
11041146
# pad is a UVA tensor
1147+
inserted_rows_shape = (assigned_cache_slots.numel(), self.max_D)
11051148
if linear_cache_indices.numel() > 0:
11061149
inserted_rows = torch.ops.fbgemm.new_unified_tensor(
11071150
torch.zeros(
11081151
1,
11091152
device=self.current_device,
11101153
dtype=self.lxu_cache_weights.dtype,
11111154
),
1112-
evicted_rows.shape,
1155+
inserted_rows_shape,
11131156
is_host_mapped=self.uvm_host_mapped,
11141157
)
11151158
else:
11161159
inserted_rows = torch.empty(
1117-
evicted_rows.shape,
1160+
inserted_rows_shape,
11181161
dtype=self.lxu_cache_weights.dtype,
11191162
device=self.current_device,
11201163
)
@@ -1212,7 +1255,7 @@ def prefetch( # noqa C901
12121255
)
12131256

12141257
# Ensure the previous iterations eviction is complete
1215-
current_stream.wait_event(self.ssd_event_evict)
1258+
current_stream.wait_event(self.ssd_event_sp_evict)
12161259
# Ensure that D2H is done
12171260
current_stream.wait_event(self.ssd_event_get_inputs_cpy)
12181261

@@ -1249,15 +1292,15 @@ def prefetch( # noqa C901
12491292
if linear_cache_indices.numel() > 0:
12501293
# Evict rows from cache to SSD
12511294
self.evict(
1252-
rows=evicted_rows,
1295+
rows=self.lxu_cache_evicted_weights,
12531296
indices_cpu=evicted_indices_cpu,
1254-
actions_count_cpu=actions_count_cpu,
1297+
actions_count_cpu=compact_actions_count_cpu,
12551298
stream=self.ssd_eviction_stream,
12561299
pre_event=self.ssd_event_get,
12571300
# Record completion event after scratch pad eviction
12581301
# instead since that happens after L1 eviction
1259-
post_event=None,
1260-
is_rows_uvm=False,
1302+
post_event=self.ssd_event_cache_evict,
1303+
is_rows_uvm=True,
12611304
name="cache",
12621305
is_bwd=False,
12631306
)

0 commit comments

Comments
 (0)