[GH-ISSUE #138] The first call to dispatch after a short period has high latency. #121

Closed
opened 2026-10-26 09:54:47 +00:00 by bsakaguchi · 11 comments
Owner

Originally created by @TheRainstorm on GitHub (Apr 27, 2025).
Original GitHub issue: https://github.com/deepseek-ai/DeepEP/issues/138

Dear developers, thank you for your open-source project. Our project uses DeepEP to implement dispatch and combine in FFN. Currently, there is no overlap between computation and communication latency, so dispatch latency is quite important. However, I encountered an issue with high dispatch latency when using DeepEP.

Test environment

  • Two nodes, each with 8 H20 GPUs, connected by CX6 network (200Gb, ~25GB/s) cards using RoCE mode.
  • Using the latest DeepEP code.

The result of tests/test_intranode.py

[tuning] Best dispatch (FP8): SMs 24, NVL chunk 8, 326.56 GB/s (NVL)
[tuning] Best dispatch (BF16): SMs 24, NVL chunk 12, 344.45 GB/s (NVL)
[tuning] Best combine: SMs 24, NVL chunk 3: 332.57 GB/s (NVL)

The result of tests/test_internode.py

[tuning] Best dispatch (FP8): SMs 24, NVL chunk 8, RDMA chunk 32: 17.77 GB/s (RDMA), 58.21 GB/s (NVL)
[tuning] Best dispatch (BF16): SMs 24, NVL chunk 24, RDMA chunk 32: 21.00 GB/s (RDMA), 68.76 GB/s (NVL)
[tuning] Best combine: SMs 24, NVL chunk 1, RDMA chunk 32: 22.66 GB/s (RDMA), 74.21 GB/s (NVL)

problem

We have slightly modified the code in tests/test_intranode.py to test the latency of consecutive dispatch calls. The code is as follows:

def run_dispatch():
    recv_x, recv_topk_idx, recv_topk_weights, recv_num_tokens_per_expert_list, handle, event = buffer.dispatch(**dispatch_args)
    event.current_stream_wait() if async_mode else ()
    recv_x = per_token_cast_back(*recv_x) if isinstance(recv_x, tuple) else recv_x
    return recv_x, recv_topk_idx, recv_topk_weights, recv_num_tokens_per_expert_list, handle, event

dispatch_times = []
for i in range(loop_num):
    start = time.perf_counter()
    recv_x, recv_topk_idx, recv_topk_weights, recv_num_tokens_per_expert_list, handle, event = run_dispatch()
    end = time.perf_counter()
    dispatch_times.append(end- start)
    if loop_outside%2 == 0:
        time.sleep(0.1)

dispatch_times records the latency of ten consecutive dispatch calls. loop_outside is in the outermost loop and controls whether to insert a 100ms sleep between each dispatch call. Through experiments, we found that if we sleep for 100ms between each dispatch call, the dispatch latency becomes very high. The results are as follows:

[config] num_tokens=8, hidden=7168, num_topk=8 [layout] Kernel performance: 0.023 ms 
loop_outside=0 
[testing] Running with BF16, with top-k (async=True, previous=False) ...===== Gathered Logs from All Ranks ===== 
rank: 0 avg: 0.383380ms dispatch_times: ['1.414ms', '0.397ms', '0.366ms', '0.432ms', '0.364ms', '0.448ms', '0.364ms', '0.357ms', '0.367ms', '0.356ms'] 
rank: 1 avg: 0.381347ms dispatch_times: ['0.683ms', '0.363ms', '0.348ms', '0.426ms', '0.374ms', '0.467ms', '0.333ms', '0.386ms', '0.368ms', '0.369ms'] 
rank: 2 avg: 0.382258ms dispatch_times: ['1.352ms', '0.378ms', '0.372ms', '0.430ms', '0.368ms', '0.434ms', '0.364ms', '0.369ms', '0.364ms', '0.362ms'] 
rank: 3 avg: 0.382750ms dispatch_times: ['2.582ms', '0.386ms', '0.367ms', '0.427ms', '0.373ms', '0.433ms', '0.366ms', '0.370ms', '0.362ms', '0.361ms'] 
rank: 4 avg: 0.380382ms dispatch_times: ['1.487ms', '0.372ms', '0.367ms', '0.445ms', '0.346ms', '0.441ms', '0.363ms', '0.369ms', '0.364ms', '0.356ms'] 
rank: 5 avg: 0.383581ms dispatch_times: ['1.424ms', '0.401ms', '0.362ms', '0.424ms', '0.371ms', '0.437ms', '0.364ms', '0.371ms', '0.362ms', '0.359ms'] 
rank: 6 avg: 0.375469ms dispatch_times: ['0.724ms', '0.357ms', '0.405ms', '0.401ms', '0.331ms', '0.457ms', '0.353ms', '0.374ms', '0.344ms', '0.357ms'] 
rank: 7 avg: 0.381126ms dispatch_times: ['1.501ms', '0.387ms', '0.384ms', '0.393ms', '0.391ms', '0.421ms', '0.359ms', '0.380ms', '0.354ms', '0.360ms'] 
======================================== 
loop_outside=1 
[testing] Running with BF16, with top-k (async=True, previous=False) ...===== Gathered Logs from All Ranks ===== 
rank: 0 avg: 0.085823ms dispatch_times: ['0.314ms', '0.077ms', '0.079ms', '0.066ms', '0.070ms', '0.068ms', '0.092ms', '0.100ms', '0.127ms', '0.093ms'] 
rank: 1 avg: 0.087467ms dispatch_times: ['0.427ms', '0.084ms', '0.071ms', '0.071ms', '0.070ms', '0.071ms', '0.086ms', '0.114ms', '0.103ms', '0.117ms'] 
rank: 2 avg: 0.086978ms dispatch_times: ['0.346ms', '0.082ms', '0.068ms', '0.071ms', '0.068ms', '0.072ms', '0.087ms', '0.116ms', '0.103ms', '0.117ms'] 
rank: 3 avg: 0.087819ms dispatch_times: ['0.360ms', '0.081ms', '0.074ms', '0.071ms', '0.066ms', '0.075ms', '0.085ms', '0.119ms', '0.104ms', '0.117ms'] 
rank: 4 avg: 0.089808ms dispatch_times: ['0.469ms', '0.085ms', '0.075ms', '0.071ms', '0.069ms', '0.087ms', '0.100ms', '0.105ms', '0.113ms', '0.104ms'] 
rank: 5 avg: 0.086365ms dispatch_times: ['0.418ms', '0.085ms', '0.074ms', '0.070ms', '0.070ms', '0.068ms', '0.092ms', '0.098ms', '0.130ms', '0.091ms'] 
rank: 6 avg: 0.086438ms dispatch_times: ['0.430ms', '0.085ms', '0.074ms', '0.073ms', '0.065ms', '0.070ms', '0.107ms', '0.084ms', '0.142ms', '0.077ms'] 
rank: 7 avg: 0.087954ms dispatch_times: ['0.432ms', '0.092ms', '0.067ms', '0.074ms', '0.063ms', '0.076ms', '0.084ms', '0.115ms', '0.102ms', '0.118ms'] 
======================================== 
loop_outside=2 
[testing] Running with BF16, with top-k (async=True, previous=False) ...===== Gathered Logs from All Ranks ===== 
rank: 0 avg: 0.380560ms dispatch_times: ['0.087ms', '0.477ms', '0.359ms', '0.357ms', '0.350ms', '0.363ms', '0.350ms', '0.337ms', '0.342ms', '0.489ms'] 
rank: 1 avg: 0.377908ms dispatch_times: ['0.219ms', '0.474ms', '0.361ms', '0.360ms', '0.346ms', '0.364ms', '0.355ms', '0.337ms', '0.348ms', '0.455ms'] 
rank: 2 avg: 0.378615ms dispatch_times: ['0.174ms', '0.476ms', '0.359ms', '0.355ms', '0.351ms', '0.362ms', '0.352ms', '0.337ms', '0.347ms', '0.468ms'] 
rank: 3 avg: 0.379110ms dispatch_times: ['0.221ms', '0.483ms', '0.354ms', '0.358ms', '0.350ms', '0.363ms', '0.357ms', '0.331ms', '0.342ms', '0.475ms'] 
rank: 4 avg: 0.379415ms dispatch_times: ['0.192ms', '0.474ms', '0.359ms', '0.359ms', '0.349ms', '0.362ms', '0.363ms', '0.346ms', '0.355ms', '0.447ms'] 
rank: 5 avg: 0.376185ms dispatch_times: ['0.219ms', '0.475ms', '0.363ms', '0.351ms', '0.356ms', '0.358ms', '0.349ms', '0.343ms', '0.357ms', '0.434ms'] 
rank: 6 avg: 0.380119ms dispatch_times: ['0.204ms', '0.488ms', '0.372ms', '0.335ms', '0.349ms', '0.365ms', '0.353ms', '0.343ms', '0.371ms', '0.445ms'] 
rank: 7 avg: 0.369653ms dispatch_times: ['0.202ms', '0.467ms', '0.356ms', '0.359ms', '0.350ms', '0.363ms', '0.355ms', '0.338ms', '0.341ms', '0.400ms'] 
======================================== 
loop_outside=3 
[testing] Running with BF16, with top-k (async=True, previous=False) ...===== Gathered Logs from All Ranks ===== 
rank: 0 avg: 0.069453ms dispatch_times: ['0.154ms', '0.089ms', '0.066ms', '0.069ms', '0.064ms', '0.065ms', '0.065ms', '0.066ms', '0.063ms', '0.077ms'] 
rank: 1 avg: 0.069724ms dispatch_times: ['0.248ms', '0.092ms', '0.067ms', '0.070ms', '0.064ms', '0.064ms', '0.065ms', '0.066ms', '0.064ms', '0.075ms'] 
rank: 2 avg: 0.070025ms dispatch_times: ['0.232ms', '0.094ms', '0.070ms', '0.066ms', '0.064ms', '0.066ms', '0.064ms', '0.066ms', '0.063ms', '0.076ms'] 
rank: 3 avg: 0.070308ms dispatch_times: ['0.224ms', '0.098ms', '0.063ms', '0.069ms', '0.064ms', '0.065ms', '0.069ms', '0.063ms', '0.070ms', '0.073ms'] 
rank: 4 avg: 0.069646ms dispatch_times: ['0.302ms', '0.091ms', '0.066ms', '0.069ms', '0.064ms', '0.064ms', '0.069ms', '0.063ms', '0.066ms', '0.073ms'] 
rank: 5 avg: 0.070054ms dispatch_times: ['0.299ms', '0.094ms', '0.067ms', '0.069ms', '0.064ms', '0.064ms', '0.070ms', '0.063ms', '0.067ms', '0.071ms'] 
rank: 6 avg: 0.069861ms dispatch_times: ['0.312ms', '0.094ms', '0.066ms', '0.070ms', '0.064ms', '0.065ms', '0.065ms', '0.066ms', '0.077ms', '0.062ms'] 
rank: 7 avg: 0.069753ms dispatch_times: ['0.298ms', '0.093ms', '0.072ms', '0.064ms', '0.064ms', '0.065ms', '0.065ms', '0.066ms', '0.073ms', '0.067ms'] 

It can be seen that except for the very first dispatch in the entire program which has a latency of 1-2ms, subsequent dispatch calls made every 100ms have a latency of approximately 400us, while completely consecutive dispatch calls have a latency of 70us. The maximum sleep interval that keeps the latency at 70us is 49ms (at 50ms it becomes 400us).

Similar phenomena are observed on test_internode. The latency of the first dispatch call is 2-3 times higher than that of consecutive calls. In our application scenario, there are other operations between two dispatches, so we are experiencing the higher latency result each time.

We would appreciate your guidance on the possible reasons for this phenomenon, such as whether network card configuration is involved, and any recommendations for debugging.

Originally created by @TheRainstorm on GitHub (Apr 27, 2025). Original GitHub issue: https://github.com/deepseek-ai/DeepEP/issues/138 Dear developers, thank you for your open-source project. Our project uses DeepEP to implement dispatch and combine in FFN. Currently, there is no overlap between computation and communication latency, so dispatch latency is quite important. However, I encountered an issue with high dispatch latency when using DeepEP. ### Test environment - Two nodes, each with 8 H20 GPUs, connected by CX6 network (200Gb, ~25GB/s) cards using **RoCE** mode. - Using the latest DeepEP code. The result of `tests/test_intranode.py` ```shell [tuning] Best dispatch (FP8): SMs 24, NVL chunk 8, 326.56 GB/s (NVL) [tuning] Best dispatch (BF16): SMs 24, NVL chunk 12, 344.45 GB/s (NVL) [tuning] Best combine: SMs 24, NVL chunk 3: 332.57 GB/s (NVL) ``` The result of `tests/test_internode.py` ```shell [tuning] Best dispatch (FP8): SMs 24, NVL chunk 8, RDMA chunk 32: 17.77 GB/s (RDMA), 58.21 GB/s (NVL) [tuning] Best dispatch (BF16): SMs 24, NVL chunk 24, RDMA chunk 32: 21.00 GB/s (RDMA), 68.76 GB/s (NVL) [tuning] Best combine: SMs 24, NVL chunk 1, RDMA chunk 32: 22.66 GB/s (RDMA), 74.21 GB/s (NVL) ``` ### problem We have slightly modified the code in tests/test_intranode.py to test the latency of consecutive dispatch calls. The code is as follows: ```python def run_dispatch(): recv_x, recv_topk_idx, recv_topk_weights, recv_num_tokens_per_expert_list, handle, event = buffer.dispatch(**dispatch_args) event.current_stream_wait() if async_mode else () recv_x = per_token_cast_back(*recv_x) if isinstance(recv_x, tuple) else recv_x return recv_x, recv_topk_idx, recv_topk_weights, recv_num_tokens_per_expert_list, handle, event dispatch_times = [] for i in range(loop_num): start = time.perf_counter() recv_x, recv_topk_idx, recv_topk_weights, recv_num_tokens_per_expert_list, handle, event = run_dispatch() end = time.perf_counter() dispatch_times.append(end- start) if loop_outside%2 == 0: time.sleep(0.1) ``` `dispatch_times` records the latency of ten consecutive dispatch calls. `loop_outside` is in the outermost loop and controls whether to insert a 100ms sleep between each dispatch call. Through experiments, we found that if we sleep for 100ms between each dispatch call, the dispatch latency becomes very high. The results are as follows: ``` [config] num_tokens=8, hidden=7168, num_topk=8 [layout] Kernel performance: 0.023 ms loop_outside=0 [testing] Running with BF16, with top-k (async=True, previous=False) ...===== Gathered Logs from All Ranks ===== rank: 0 avg: 0.383380ms dispatch_times: ['1.414ms', '0.397ms', '0.366ms', '0.432ms', '0.364ms', '0.448ms', '0.364ms', '0.357ms', '0.367ms', '0.356ms'] rank: 1 avg: 0.381347ms dispatch_times: ['0.683ms', '0.363ms', '0.348ms', '0.426ms', '0.374ms', '0.467ms', '0.333ms', '0.386ms', '0.368ms', '0.369ms'] rank: 2 avg: 0.382258ms dispatch_times: ['1.352ms', '0.378ms', '0.372ms', '0.430ms', '0.368ms', '0.434ms', '0.364ms', '0.369ms', '0.364ms', '0.362ms'] rank: 3 avg: 0.382750ms dispatch_times: ['2.582ms', '0.386ms', '0.367ms', '0.427ms', '0.373ms', '0.433ms', '0.366ms', '0.370ms', '0.362ms', '0.361ms'] rank: 4 avg: 0.380382ms dispatch_times: ['1.487ms', '0.372ms', '0.367ms', '0.445ms', '0.346ms', '0.441ms', '0.363ms', '0.369ms', '0.364ms', '0.356ms'] rank: 5 avg: 0.383581ms dispatch_times: ['1.424ms', '0.401ms', '0.362ms', '0.424ms', '0.371ms', '0.437ms', '0.364ms', '0.371ms', '0.362ms', '0.359ms'] rank: 6 avg: 0.375469ms dispatch_times: ['0.724ms', '0.357ms', '0.405ms', '0.401ms', '0.331ms', '0.457ms', '0.353ms', '0.374ms', '0.344ms', '0.357ms'] rank: 7 avg: 0.381126ms dispatch_times: ['1.501ms', '0.387ms', '0.384ms', '0.393ms', '0.391ms', '0.421ms', '0.359ms', '0.380ms', '0.354ms', '0.360ms'] ======================================== loop_outside=1 [testing] Running with BF16, with top-k (async=True, previous=False) ...===== Gathered Logs from All Ranks ===== rank: 0 avg: 0.085823ms dispatch_times: ['0.314ms', '0.077ms', '0.079ms', '0.066ms', '0.070ms', '0.068ms', '0.092ms', '0.100ms', '0.127ms', '0.093ms'] rank: 1 avg: 0.087467ms dispatch_times: ['0.427ms', '0.084ms', '0.071ms', '0.071ms', '0.070ms', '0.071ms', '0.086ms', '0.114ms', '0.103ms', '0.117ms'] rank: 2 avg: 0.086978ms dispatch_times: ['0.346ms', '0.082ms', '0.068ms', '0.071ms', '0.068ms', '0.072ms', '0.087ms', '0.116ms', '0.103ms', '0.117ms'] rank: 3 avg: 0.087819ms dispatch_times: ['0.360ms', '0.081ms', '0.074ms', '0.071ms', '0.066ms', '0.075ms', '0.085ms', '0.119ms', '0.104ms', '0.117ms'] rank: 4 avg: 0.089808ms dispatch_times: ['0.469ms', '0.085ms', '0.075ms', '0.071ms', '0.069ms', '0.087ms', '0.100ms', '0.105ms', '0.113ms', '0.104ms'] rank: 5 avg: 0.086365ms dispatch_times: ['0.418ms', '0.085ms', '0.074ms', '0.070ms', '0.070ms', '0.068ms', '0.092ms', '0.098ms', '0.130ms', '0.091ms'] rank: 6 avg: 0.086438ms dispatch_times: ['0.430ms', '0.085ms', '0.074ms', '0.073ms', '0.065ms', '0.070ms', '0.107ms', '0.084ms', '0.142ms', '0.077ms'] rank: 7 avg: 0.087954ms dispatch_times: ['0.432ms', '0.092ms', '0.067ms', '0.074ms', '0.063ms', '0.076ms', '0.084ms', '0.115ms', '0.102ms', '0.118ms'] ======================================== loop_outside=2 [testing] Running with BF16, with top-k (async=True, previous=False) ...===== Gathered Logs from All Ranks ===== rank: 0 avg: 0.380560ms dispatch_times: ['0.087ms', '0.477ms', '0.359ms', '0.357ms', '0.350ms', '0.363ms', '0.350ms', '0.337ms', '0.342ms', '0.489ms'] rank: 1 avg: 0.377908ms dispatch_times: ['0.219ms', '0.474ms', '0.361ms', '0.360ms', '0.346ms', '0.364ms', '0.355ms', '0.337ms', '0.348ms', '0.455ms'] rank: 2 avg: 0.378615ms dispatch_times: ['0.174ms', '0.476ms', '0.359ms', '0.355ms', '0.351ms', '0.362ms', '0.352ms', '0.337ms', '0.347ms', '0.468ms'] rank: 3 avg: 0.379110ms dispatch_times: ['0.221ms', '0.483ms', '0.354ms', '0.358ms', '0.350ms', '0.363ms', '0.357ms', '0.331ms', '0.342ms', '0.475ms'] rank: 4 avg: 0.379415ms dispatch_times: ['0.192ms', '0.474ms', '0.359ms', '0.359ms', '0.349ms', '0.362ms', '0.363ms', '0.346ms', '0.355ms', '0.447ms'] rank: 5 avg: 0.376185ms dispatch_times: ['0.219ms', '0.475ms', '0.363ms', '0.351ms', '0.356ms', '0.358ms', '0.349ms', '0.343ms', '0.357ms', '0.434ms'] rank: 6 avg: 0.380119ms dispatch_times: ['0.204ms', '0.488ms', '0.372ms', '0.335ms', '0.349ms', '0.365ms', '0.353ms', '0.343ms', '0.371ms', '0.445ms'] rank: 7 avg: 0.369653ms dispatch_times: ['0.202ms', '0.467ms', '0.356ms', '0.359ms', '0.350ms', '0.363ms', '0.355ms', '0.338ms', '0.341ms', '0.400ms'] ======================================== loop_outside=3 [testing] Running with BF16, with top-k (async=True, previous=False) ...===== Gathered Logs from All Ranks ===== rank: 0 avg: 0.069453ms dispatch_times: ['0.154ms', '0.089ms', '0.066ms', '0.069ms', '0.064ms', '0.065ms', '0.065ms', '0.066ms', '0.063ms', '0.077ms'] rank: 1 avg: 0.069724ms dispatch_times: ['0.248ms', '0.092ms', '0.067ms', '0.070ms', '0.064ms', '0.064ms', '0.065ms', '0.066ms', '0.064ms', '0.075ms'] rank: 2 avg: 0.070025ms dispatch_times: ['0.232ms', '0.094ms', '0.070ms', '0.066ms', '0.064ms', '0.066ms', '0.064ms', '0.066ms', '0.063ms', '0.076ms'] rank: 3 avg: 0.070308ms dispatch_times: ['0.224ms', '0.098ms', '0.063ms', '0.069ms', '0.064ms', '0.065ms', '0.069ms', '0.063ms', '0.070ms', '0.073ms'] rank: 4 avg: 0.069646ms dispatch_times: ['0.302ms', '0.091ms', '0.066ms', '0.069ms', '0.064ms', '0.064ms', '0.069ms', '0.063ms', '0.066ms', '0.073ms'] rank: 5 avg: 0.070054ms dispatch_times: ['0.299ms', '0.094ms', '0.067ms', '0.069ms', '0.064ms', '0.064ms', '0.070ms', '0.063ms', '0.067ms', '0.071ms'] rank: 6 avg: 0.069861ms dispatch_times: ['0.312ms', '0.094ms', '0.066ms', '0.070ms', '0.064ms', '0.065ms', '0.065ms', '0.066ms', '0.077ms', '0.062ms'] rank: 7 avg: 0.069753ms dispatch_times: ['0.298ms', '0.093ms', '0.072ms', '0.064ms', '0.064ms', '0.065ms', '0.065ms', '0.066ms', '0.073ms', '0.067ms'] ``` It can be seen that except for the very first dispatch in the entire program which has a latency of 1-2ms, **subsequent dispatch calls made every 100ms have a latency of approximately 400us, while completely consecutive dispatch calls have a latency of 70us**. The maximum sleep interval that keeps the latency at 70us is 49ms (at 50ms it becomes 400us). Similar phenomena are observed on test_internode. The latency of the first dispatch call is 2-3 times higher than that of consecutive calls. In our application scenario, there are other operations between two dispatches, so we are experiencing the higher latency result each time. We would appreciate your guidance on the possible reasons for this phenomenon, such as whether network card configuration is involved, and any recommendations for debugging.
Author
Owner

@LyricZhao commented on GitHub (Apr 27, 2025):

A tip for debugging such things would be aligning the timeline (wall time) of profiling results.

The kernel only spends more time on unbalanced GPU launch time, so you better check whether, e.g. time.sleep(0.1) is accurate for all ranks, or something else making the start time of kernels unaligned.

Or maybe add a dist.all_reduce before the first launch.

<!-- gh-comment-id:2833350369 --> @LyricZhao commented on GitHub (Apr 27, 2025): A tip for debugging such things would be **aligning the timeline (wall time) of profiling results**. The kernel _only_ spends more time on unbalanced GPU launch time, so you better check whether, e.g. `time.sleep(0.1)` is accurate for all ranks, or something else making the start time of kernels unaligned. Or maybe add a `dist.all_reduce` before the first launch.
Author
Owner

@TheRainstorm commented on GitHub (Apr 27, 2025):

Thank you for your quick reply. I tried synchronizing using all_reduce before each dispatch measurement, but the phenomenon described above still exists. My main code is as below (modified in tests/test_intranode.py):

    loop_num = 10
    previous_mode = False
    for loop_outside in range(5):
        if rank==0:
            print(f"{loop_outside=}")
        for async_mode in (True, ):
            for current_x in (x_pure_rand, ):
                for with_topk in (True, ):
                    if local_rank == 0:
                        print(f'[testing] Running with {"FP8" if isinstance(current_x, tuple) else "BF16"}, {"with" if with_topk else "without"} top-k (async={async_mode}, previous={previous_mode}) ...', flush=True, end='')
                    dispatch_args = {'x': current_x, 'num_tokens_per_rank': num_tokens_per_rank,  'is_token_in_rank': is_token_in_rank,
                                     'num_tokens_per_expert': num_tokens_per_expert, 'config': config, 'async_finish': async_mode}
                    if with_topk:
                        dispatch_args.update({'topk_idx': topk_idx, 'topk_weights': topk_weights_pure_rand if current_x is x_pure_rand else topk_weights})
                    if previous_mode:
                        dispatch_args.update({'previous_event': buffer.capture()})
                    
                    def run_dispatch():
                        recv_x, recv_topk_idx, recv_topk_weights, recv_num_tokens_per_expert_list, handle, event = buffer.dispatch(**dispatch_args)
                        event.current_stream_wait() if async_mode else ()
                        recv_x = per_token_cast_back(*recv_x) if isinstance(recv_x, tuple) else recv_x
                        return recv_x, recv_topk_idx, recv_topk_weights, recv_num_tokens_per_expert_list, handle, event
                    
                    tensor = torch.tensor([rank]).float()
                    # dist.all_reduce(tensor, op=dist.ReduceOp.SUM)
                    dispatch_times = []
                    for i in range(loop_num):
                        dist.all_reduce(tensor, op=dist.ReduceOp.SUM)
                        start = time.perf_counter()
                        recv_x, recv_topk_idx, recv_topk_weights, recv_num_tokens_per_expert_list, handle, event = run_dispatch()
                        end = time.perf_counter()
                        dispatch_times.append(end- start)
                        if loop_outside%2 == 0:
                            time.sleep(0.1)
<!-- gh-comment-id:2833392210 --> @TheRainstorm commented on GitHub (Apr 27, 2025): Thank you for your quick reply. I tried synchronizing using all_reduce before each dispatch measurement, but the phenomenon described above still exists. My main code is as below (modified in `tests/test_intranode.py`): ```python loop_num = 10 previous_mode = False for loop_outside in range(5): if rank==0: print(f"{loop_outside=}") for async_mode in (True, ): for current_x in (x_pure_rand, ): for with_topk in (True, ): if local_rank == 0: print(f'[testing] Running with {"FP8" if isinstance(current_x, tuple) else "BF16"}, {"with" if with_topk else "without"} top-k (async={async_mode}, previous={previous_mode}) ...', flush=True, end='') dispatch_args = {'x': current_x, 'num_tokens_per_rank': num_tokens_per_rank, 'is_token_in_rank': is_token_in_rank, 'num_tokens_per_expert': num_tokens_per_expert, 'config': config, 'async_finish': async_mode} if with_topk: dispatch_args.update({'topk_idx': topk_idx, 'topk_weights': topk_weights_pure_rand if current_x is x_pure_rand else topk_weights}) if previous_mode: dispatch_args.update({'previous_event': buffer.capture()}) def run_dispatch(): recv_x, recv_topk_idx, recv_topk_weights, recv_num_tokens_per_expert_list, handle, event = buffer.dispatch(**dispatch_args) event.current_stream_wait() if async_mode else () recv_x = per_token_cast_back(*recv_x) if isinstance(recv_x, tuple) else recv_x return recv_x, recv_topk_idx, recv_topk_weights, recv_num_tokens_per_expert_list, handle, event tensor = torch.tensor([rank]).float() # dist.all_reduce(tensor, op=dist.ReduceOp.SUM) dispatch_times = [] for i in range(loop_num): dist.all_reduce(tensor, op=dist.ReduceOp.SUM) start = time.perf_counter() recv_x, recv_topk_idx, recv_topk_weights, recv_num_tokens_per_expert_list, handle, event = run_dispatch() end = time.perf_counter() dispatch_times.append(end- start) if loop_outside%2 == 0: time.sleep(0.1) ```
Author
Owner

@LyricZhao commented on GitHub (Apr 28, 2025):

After dist.all_reduce(tensor, op=dist.ReduceOp.SUM), you should call CUDA synchronize, as you are couting the CPU time.

<!-- gh-comment-id:2833744317 --> @LyricZhao commented on GitHub (Apr 28, 2025): After `dist.all_reduce(tensor, op=dist.ReduceOp.SUM)`, you should call CUDA synchronize, as you are couting the CPU time.
Author
Owner

@LyricZhao commented on GitHub (Apr 28, 2025):

I highly recommend you to count the GPU time using CUDA event or CUPTI (PyTorch profiler), but not time.perf_counter. The current code snippet is only counting the dispatch barrier (notify_dispatch) time (including a CPU sync) + dispatch kernel launch time.

<!-- gh-comment-id:2833746594 --> @LyricZhao commented on GitHub (Apr 28, 2025): I highly recommend you to count the GPU time using CUDA event or CUPTI (PyTorch profiler), but not `time.perf_counter`. The current code snippet is only counting the dispatch barrier (`notify_dispatch`) time (including a CPU sync) + dispatch kernel launch time.
Author
Owner

@TheRainstorm commented on GitHub (Apr 28, 2025):

I highly recommend you to count the GPU time using CUDA event or CUPTI (PyTorch profiler), but not time.perf_counter. The current code snippet is only counting the dispatch barrier (notify_dispatch) time (including a CPU sync) + dispatch kernel launch time.

Thank you for pointing out my mistake. I went back to review the relevant knowledge and found a very good article introducing how to precisely time CUDA kernels.

This is my modified code:

tensor = torch.tensor([rank]).float()
dispatch_times = []
start_events = [torch.cuda.Event(enable_timing=True) for _ in range(loop_num)]
end_events = [torch.cuda.Event(enable_timing=True) for _ in range(loop_num)]

for i in range(loop_num):
    dist.all_reduce(tensor, op=dist.ReduceOp.SUM)       # align all gpus
    torch.cuda.synchronize() # now, all gpu and cpu sync

    start_events[i].record()
    buffer.dispatch(**dispatch_args)
    end_events[i].record()
    
    torch.cuda.synchronize()   # sync to launch next dispatch
    if loop_outside%2 == 0:
        time.sleep(0.05)
    # if loop_outside%2 == 0:
    #     torch.cuda.__nanosleep(50_000_000)   # dispatch interval 50ms
    
torch.cuda.synchronize()  # sync with last end event
dispatch_times = [s.elapsed_time(e)/1000 for s, e in zip(start_events, end_events)]  # seconds

dist_print(f"rank: {rank} avg: {mean(dispatch_times[1:])*1000:.6f}ms dispatch_times: {ms_format(dispatch_times)}", rank, num_ranks)

This is the result after my modification. Surprisingly, there is almost no difference compared to before. Is it possible that dispatch already implements synchronization between the CPU and GPU? The new results still have the issues mentioned above.

loop_outside=0
[testing] Running with BF16, with top-k (async=True, previous=False) ...===== Gathered Logs from All Ranks =====
rank: 0 avg: 0.435972ms dispatch_times: ['1.308ms', '0.405ms', '0.494ms', '0.532ms', '0.429ms', '0.461ms', '0.392ms', '0.413ms', '0.396ms', '0.401ms']
rank: 1 avg: 0.431868ms dispatch_times: ['1.235ms', '0.399ms', '0.492ms', '0.529ms', '0.420ms', '0.453ms', '0.389ms', '0.413ms', '0.394ms', '0.398ms']
rank: 2 avg: 0.432612ms dispatch_times: ['1.235ms', '0.403ms', '0.489ms', '0.530ms', '0.420ms', '0.457ms', '0.388ms', '0.413ms', '0.393ms', '0.399ms']
rank: 3 avg: 0.432686ms dispatch_times: ['1.224ms', '0.402ms', '0.490ms', '0.529ms', '0.420ms', '0.457ms', '0.389ms', '0.414ms', '0.394ms', '0.399ms']
rank: 4 avg: 0.433216ms dispatch_times: ['1.245ms', '0.399ms', '0.488ms', '0.558ms', '0.415ms', '0.453ms', '0.384ms', '0.412ms', '0.392ms', '0.399ms']
rank: 5 avg: 0.432366ms dispatch_times: ['1.267ms', '0.399ms', '0.487ms', '0.533ms', '0.425ms', '0.457ms', '0.389ms', '0.410ms', '0.393ms', '0.398ms']
rank: 6 avg: 0.431438ms dispatch_times: ['1.242ms', '0.400ms', '0.488ms', '0.529ms', '0.419ms', '0.456ms', '0.388ms', '0.413ms', '0.392ms', '0.397ms']
rank: 7 avg: 0.431058ms dispatch_times: ['1.230ms', '0.398ms', '0.487ms', '0.529ms', '0.421ms', '0.455ms', '0.387ms', '0.413ms', '0.392ms', '0.397ms']
========================================
loop_outside=1
[testing] Running with BF16, with top-k (async=True, previous=False) ...===== Gathered Logs from All Ranks =====
rank: 0 avg: 0.085796ms dispatch_times: ['0.228ms', '0.088ms', '0.086ms', '0.087ms', '0.086ms', '0.092ms', '0.079ms', '0.079ms', '0.084ms', '0.090ms']
rank: 1 avg: 0.087726ms dispatch_times: ['0.240ms', '0.098ms', '0.086ms', '0.090ms', '0.090ms', '0.092ms', '0.079ms', '0.089ms', '0.083ms', '0.083ms']
rank: 2 avg: 0.088537ms dispatch_times: ['0.240ms', '0.096ms', '0.088ms', '0.093ms', '0.087ms', '0.094ms', '0.080ms', '0.090ms', '0.084ms', '0.086ms']
rank: 3 avg: 0.087019ms dispatch_times: ['0.238ms', '0.095ms', '0.084ms', '0.088ms', '0.085ms', '0.092ms', '0.079ms', '0.089ms', '0.085ms', '0.085ms']
rank: 4 avg: 0.086930ms dispatch_times: ['0.240ms', '0.094ms', '0.087ms', '0.089ms', '0.083ms', '0.093ms', '0.080ms', '0.091ms', '0.082ms', '0.083ms']
rank: 5 avg: 0.085483ms dispatch_times: ['0.238ms', '0.092ms', '0.085ms', '0.088ms', '0.082ms', '0.091ms', '0.078ms', '0.087ms', '0.082ms', '0.084ms']
rank: 6 avg: 0.086677ms dispatch_times: ['0.237ms', '0.096ms', '0.086ms', '0.089ms', '0.087ms', '0.090ms', '0.078ms', '0.089ms', '0.080ms', '0.084ms']
rank: 7 avg: 0.086674ms dispatch_times: ['0.237ms', '0.093ms', '0.085ms', '0.089ms', '0.085ms', '0.092ms', '0.078ms', '0.088ms', '0.086ms', '0.084ms']
========================================
loop_outside=2
[testing] Running with BF16, with top-k (async=True, previous=False) ...===== Gathered Logs from All Ranks =====
rank: 0 avg: 0.425227ms dispatch_times: ['0.094ms', '0.485ms', '0.396ms', '0.382ms', '0.400ms', '0.401ms', '0.432ms', '0.408ms', '0.406ms', '0.516ms']
rank: 1 avg: 0.423399ms dispatch_times: ['0.098ms', '0.478ms', '0.393ms', '0.384ms', '0.398ms', '0.398ms', '0.434ms', '0.407ms', '0.404ms', '0.514ms']
rank: 2 avg: 0.423755ms dispatch_times: ['0.099ms', '0.482ms', '0.394ms', '0.383ms', '0.397ms', '0.398ms', '0.433ms', '0.409ms', '0.404ms', '0.514ms']
rank: 3 avg: 0.423157ms dispatch_times: ['0.101ms', '0.481ms', '0.393ms', '0.382ms', '0.397ms', '0.397ms', '0.434ms', '0.409ms', '0.404ms', '0.513ms']
rank: 4 avg: 0.421550ms dispatch_times: ['0.102ms', '0.479ms', '0.392ms', '0.382ms', '0.392ms', '0.396ms', '0.431ms', '0.407ms', '0.402ms', '0.513ms']
rank: 5 avg: 0.423275ms dispatch_times: ['0.101ms', '0.483ms', '0.394ms', '0.382ms', '0.398ms', '0.397ms', '0.434ms', '0.405ms', '0.402ms', '0.513ms']
rank: 6 avg: 0.422347ms dispatch_times: ['0.101ms', '0.480ms', '0.392ms', '0.381ms', '0.396ms', '0.397ms', '0.431ms', '0.407ms', '0.403ms', '0.513ms']
rank: 7 avg: 0.419908ms dispatch_times: ['0.103ms', '0.480ms', '0.386ms', '0.381ms', '0.396ms', '0.394ms', '0.430ms', '0.407ms', '0.402ms', '0.504ms']
========================================
loop_outside=3
[testing] Running with BF16, with top-k (async=True, previous=False) ...===== Gathered Logs from All Ranks =====
rank: 0 avg: 0.083630ms dispatch_times: ['0.269ms', '0.090ms', '0.087ms', '0.087ms', '0.089ms', '0.086ms', '0.078ms', '0.081ms', '0.078ms', '0.077ms']
rank: 1 avg: 0.084192ms dispatch_times: ['0.292ms', '0.094ms', '0.090ms', '0.085ms', '0.082ms', '0.089ms', '0.079ms', '0.078ms', '0.081ms', '0.079ms']
rank: 2 avg: 0.085440ms dispatch_times: ['0.291ms', '0.094ms', '0.089ms', '0.085ms', '0.085ms', '0.090ms', '0.081ms', '0.080ms', '0.083ms', '0.082ms']
rank: 3 avg: 0.085138ms dispatch_times: ['0.289ms', '0.092ms', '0.085ms', '0.085ms', '0.090ms', '0.090ms', '0.081ms', '0.080ms', '0.084ms', '0.081ms']
rank: 4 avg: 0.084818ms dispatch_times: ['0.290ms', '0.094ms', '0.091ms', '0.081ms', '0.082ms', '0.090ms', '0.081ms', '0.080ms', '0.081ms', '0.082ms']
rank: 5 avg: 0.085148ms dispatch_times: ['0.291ms', '0.094ms', '0.086ms', '0.083ms', '0.079ms', '0.089ms', '0.095ms', '0.077ms', '0.081ms', '0.081ms']
rank: 6 avg: 0.082809ms dispatch_times: ['0.288ms', '0.093ms', '0.084ms', '0.083ms', '0.081ms', '0.081ms', '0.079ms', '0.078ms', '0.084ms', '0.081ms']
rank: 7 avg: 0.083502ms dispatch_times: ['0.288ms', '0.094ms', '0.084ms', '0.082ms', '0.082ms', '0.089ms', '0.080ms', '0.079ms', '0.081ms', '0.081ms']
========================================
<!-- gh-comment-id:2833872811 --> @TheRainstorm commented on GitHub (Apr 28, 2025): > I highly recommend you to count the GPU time using CUDA event or CUPTI (PyTorch profiler), but not `time.perf_counter`. The current code snippet is only counting the dispatch barrier (`notify_dispatch`) time (including a CPU sync) + dispatch kernel launch time. Thank you for pointing out my mistake. I went back to review the relevant knowledge and found a very good [article](https://www.speechmatics.com/company/articles-and-news/timing-operations-in-pytorch) introducing how to precisely time CUDA kernels. This is my modified code: ```python tensor = torch.tensor([rank]).float() dispatch_times = [] start_events = [torch.cuda.Event(enable_timing=True) for _ in range(loop_num)] end_events = [torch.cuda.Event(enable_timing=True) for _ in range(loop_num)] for i in range(loop_num): dist.all_reduce(tensor, op=dist.ReduceOp.SUM) # align all gpus torch.cuda.synchronize() # now, all gpu and cpu sync start_events[i].record() buffer.dispatch(**dispatch_args) end_events[i].record() torch.cuda.synchronize() # sync to launch next dispatch if loop_outside%2 == 0: time.sleep(0.05) # if loop_outside%2 == 0: # torch.cuda.__nanosleep(50_000_000) # dispatch interval 50ms torch.cuda.synchronize() # sync with last end event dispatch_times = [s.elapsed_time(e)/1000 for s, e in zip(start_events, end_events)] # seconds dist_print(f"rank: {rank} avg: {mean(dispatch_times[1:])*1000:.6f}ms dispatch_times: {ms_format(dispatch_times)}", rank, num_ranks) ``` This is the result after my modification. Surprisingly, there is almost no difference compared to before. Is it possible that dispatch already implements synchronization between the CPU and GPU? The new results still have the issues mentioned above. ```shell loop_outside=0 [testing] Running with BF16, with top-k (async=True, previous=False) ...===== Gathered Logs from All Ranks ===== rank: 0 avg: 0.435972ms dispatch_times: ['1.308ms', '0.405ms', '0.494ms', '0.532ms', '0.429ms', '0.461ms', '0.392ms', '0.413ms', '0.396ms', '0.401ms'] rank: 1 avg: 0.431868ms dispatch_times: ['1.235ms', '0.399ms', '0.492ms', '0.529ms', '0.420ms', '0.453ms', '0.389ms', '0.413ms', '0.394ms', '0.398ms'] rank: 2 avg: 0.432612ms dispatch_times: ['1.235ms', '0.403ms', '0.489ms', '0.530ms', '0.420ms', '0.457ms', '0.388ms', '0.413ms', '0.393ms', '0.399ms'] rank: 3 avg: 0.432686ms dispatch_times: ['1.224ms', '0.402ms', '0.490ms', '0.529ms', '0.420ms', '0.457ms', '0.389ms', '0.414ms', '0.394ms', '0.399ms'] rank: 4 avg: 0.433216ms dispatch_times: ['1.245ms', '0.399ms', '0.488ms', '0.558ms', '0.415ms', '0.453ms', '0.384ms', '0.412ms', '0.392ms', '0.399ms'] rank: 5 avg: 0.432366ms dispatch_times: ['1.267ms', '0.399ms', '0.487ms', '0.533ms', '0.425ms', '0.457ms', '0.389ms', '0.410ms', '0.393ms', '0.398ms'] rank: 6 avg: 0.431438ms dispatch_times: ['1.242ms', '0.400ms', '0.488ms', '0.529ms', '0.419ms', '0.456ms', '0.388ms', '0.413ms', '0.392ms', '0.397ms'] rank: 7 avg: 0.431058ms dispatch_times: ['1.230ms', '0.398ms', '0.487ms', '0.529ms', '0.421ms', '0.455ms', '0.387ms', '0.413ms', '0.392ms', '0.397ms'] ======================================== loop_outside=1 [testing] Running with BF16, with top-k (async=True, previous=False) ...===== Gathered Logs from All Ranks ===== rank: 0 avg: 0.085796ms dispatch_times: ['0.228ms', '0.088ms', '0.086ms', '0.087ms', '0.086ms', '0.092ms', '0.079ms', '0.079ms', '0.084ms', '0.090ms'] rank: 1 avg: 0.087726ms dispatch_times: ['0.240ms', '0.098ms', '0.086ms', '0.090ms', '0.090ms', '0.092ms', '0.079ms', '0.089ms', '0.083ms', '0.083ms'] rank: 2 avg: 0.088537ms dispatch_times: ['0.240ms', '0.096ms', '0.088ms', '0.093ms', '0.087ms', '0.094ms', '0.080ms', '0.090ms', '0.084ms', '0.086ms'] rank: 3 avg: 0.087019ms dispatch_times: ['0.238ms', '0.095ms', '0.084ms', '0.088ms', '0.085ms', '0.092ms', '0.079ms', '0.089ms', '0.085ms', '0.085ms'] rank: 4 avg: 0.086930ms dispatch_times: ['0.240ms', '0.094ms', '0.087ms', '0.089ms', '0.083ms', '0.093ms', '0.080ms', '0.091ms', '0.082ms', '0.083ms'] rank: 5 avg: 0.085483ms dispatch_times: ['0.238ms', '0.092ms', '0.085ms', '0.088ms', '0.082ms', '0.091ms', '0.078ms', '0.087ms', '0.082ms', '0.084ms'] rank: 6 avg: 0.086677ms dispatch_times: ['0.237ms', '0.096ms', '0.086ms', '0.089ms', '0.087ms', '0.090ms', '0.078ms', '0.089ms', '0.080ms', '0.084ms'] rank: 7 avg: 0.086674ms dispatch_times: ['0.237ms', '0.093ms', '0.085ms', '0.089ms', '0.085ms', '0.092ms', '0.078ms', '0.088ms', '0.086ms', '0.084ms'] ======================================== loop_outside=2 [testing] Running with BF16, with top-k (async=True, previous=False) ...===== Gathered Logs from All Ranks ===== rank: 0 avg: 0.425227ms dispatch_times: ['0.094ms', '0.485ms', '0.396ms', '0.382ms', '0.400ms', '0.401ms', '0.432ms', '0.408ms', '0.406ms', '0.516ms'] rank: 1 avg: 0.423399ms dispatch_times: ['0.098ms', '0.478ms', '0.393ms', '0.384ms', '0.398ms', '0.398ms', '0.434ms', '0.407ms', '0.404ms', '0.514ms'] rank: 2 avg: 0.423755ms dispatch_times: ['0.099ms', '0.482ms', '0.394ms', '0.383ms', '0.397ms', '0.398ms', '0.433ms', '0.409ms', '0.404ms', '0.514ms'] rank: 3 avg: 0.423157ms dispatch_times: ['0.101ms', '0.481ms', '0.393ms', '0.382ms', '0.397ms', '0.397ms', '0.434ms', '0.409ms', '0.404ms', '0.513ms'] rank: 4 avg: 0.421550ms dispatch_times: ['0.102ms', '0.479ms', '0.392ms', '0.382ms', '0.392ms', '0.396ms', '0.431ms', '0.407ms', '0.402ms', '0.513ms'] rank: 5 avg: 0.423275ms dispatch_times: ['0.101ms', '0.483ms', '0.394ms', '0.382ms', '0.398ms', '0.397ms', '0.434ms', '0.405ms', '0.402ms', '0.513ms'] rank: 6 avg: 0.422347ms dispatch_times: ['0.101ms', '0.480ms', '0.392ms', '0.381ms', '0.396ms', '0.397ms', '0.431ms', '0.407ms', '0.403ms', '0.513ms'] rank: 7 avg: 0.419908ms dispatch_times: ['0.103ms', '0.480ms', '0.386ms', '0.381ms', '0.396ms', '0.394ms', '0.430ms', '0.407ms', '0.402ms', '0.504ms'] ======================================== loop_outside=3 [testing] Running with BF16, with top-k (async=True, previous=False) ...===== Gathered Logs from All Ranks ===== rank: 0 avg: 0.083630ms dispatch_times: ['0.269ms', '0.090ms', '0.087ms', '0.087ms', '0.089ms', '0.086ms', '0.078ms', '0.081ms', '0.078ms', '0.077ms'] rank: 1 avg: 0.084192ms dispatch_times: ['0.292ms', '0.094ms', '0.090ms', '0.085ms', '0.082ms', '0.089ms', '0.079ms', '0.078ms', '0.081ms', '0.079ms'] rank: 2 avg: 0.085440ms dispatch_times: ['0.291ms', '0.094ms', '0.089ms', '0.085ms', '0.085ms', '0.090ms', '0.081ms', '0.080ms', '0.083ms', '0.082ms'] rank: 3 avg: 0.085138ms dispatch_times: ['0.289ms', '0.092ms', '0.085ms', '0.085ms', '0.090ms', '0.090ms', '0.081ms', '0.080ms', '0.084ms', '0.081ms'] rank: 4 avg: 0.084818ms dispatch_times: ['0.290ms', '0.094ms', '0.091ms', '0.081ms', '0.082ms', '0.090ms', '0.081ms', '0.080ms', '0.081ms', '0.082ms'] rank: 5 avg: 0.085148ms dispatch_times: ['0.291ms', '0.094ms', '0.086ms', '0.083ms', '0.079ms', '0.089ms', '0.095ms', '0.077ms', '0.081ms', '0.081ms'] rank: 6 avg: 0.082809ms dispatch_times: ['0.288ms', '0.093ms', '0.084ms', '0.083ms', '0.081ms', '0.081ms', '0.079ms', '0.078ms', '0.084ms', '0.081ms'] rank: 7 avg: 0.083502ms dispatch_times: ['0.288ms', '0.094ms', '0.084ms', '0.082ms', '0.082ms', '0.089ms', '0.080ms', '0.079ms', '0.081ms', '0.081ms'] ======================================== ```
Author
Owner

@LyricZhao commented on GitHub (Apr 28, 2025):

Is it possible that dispatch already implements synchronization between the CPU and GPU?

Dispatch has two stages: notify_dispatch and dispatch itself, the first one have synchronization.

How about:

for i in range(loop_num):
    dist.barrier()  # align CPU launch time
    lhs = torch.randn((8192, 8192), dtype=torch.float, device='cuda')
    rhs = torch.randn((8192, 8192), dtype=torch.float, device='cuda')
    lhs @ rhs
    dist.all_reduce(tensor, op=dist.ReduceOp.SUM)       # align GPU time with a large GEMM ahead

    start_events[i].record()
    buffer.dispatch(**dispatch_args)
    end_events[i].record()
    
    torch.cuda.synchronize()   # sync to launch next dispatch
    if loop_outside%2 == 0:
        time.sleep(0.05)

After all, my debugging experience is: the kernel only spends more time on unbalanced GPU launch time. If the problem still exists, I guess you should dump the profiling timeline.

<!-- gh-comment-id:2833881300 --> @LyricZhao commented on GitHub (Apr 28, 2025): > Is it possible that dispatch already implements synchronization between the CPU and GPU? Dispatch has two stages: `notify_dispatch` and `dispatch` itself, the first one have synchronization. How about: ```python for i in range(loop_num): dist.barrier() # align CPU launch time lhs = torch.randn((8192, 8192), dtype=torch.float, device='cuda') rhs = torch.randn((8192, 8192), dtype=torch.float, device='cuda') lhs @ rhs dist.all_reduce(tensor, op=dist.ReduceOp.SUM) # align GPU time with a large GEMM ahead start_events[i].record() buffer.dispatch(**dispatch_args) end_events[i].record() torch.cuda.synchronize() # sync to launch next dispatch if loop_outside%2 == 0: time.sleep(0.05) ``` After all, my debugging experience is: the kernel only spends more time on unbalanced GPU launch time. If the problem still exists, I guess you should dump the profiling timeline.
Author
Owner

@TheRainstorm commented on GitHub (Apr 28, 2025):

If the problem still exists, I guess you should dump the profiling timeline.

The problem still exists, I will profling with Nsight. Thank you for your help.

<!-- gh-comment-id:2833904750 --> @TheRainstorm commented on GitHub (Apr 28, 2025): > If the problem still exists, I guess you should dump the profiling timeline. The problem still exists, I will profling with Nsight. Thank you for your help.
Author
Owner

@cataluna84 commented on GitHub (Apr 28, 2025):

@TheRainstorm Maybe, the first loop's latency is because of the warmup steps, which are kinda required for these distributed systems. You could do a trial run with a simple warmup step in a dummy tensor loop and see the results.

For the consecutive steps, maybe the gradient accumulation is kinda paused as the electricity consumption fluctuates when not under load, as it happens with a single GPU. I am just speculating, we need to look at the profiler, but does the 100ms delay achieves higher accuracy?

<!-- gh-comment-id:2835493098 --> @cataluna84 commented on GitHub (Apr 28, 2025): @TheRainstorm Maybe, the first loop's latency is because of the warmup steps, which are kinda required for these distributed systems. You could do a trial run with a simple warmup step in a dummy tensor loop and see the results. For the consecutive steps, maybe the gradient accumulation is kinda paused as the electricity consumption fluctuates when not under load, as it happens with a single GPU. I am just speculating, we need to look at the profiler, but does the 100ms delay achieves higher accuracy?
Author
Owner

@TheRainstorm commented on GitHub (Apr 29, 2025):

If the problem still exists, I guess you should dump the profiling timeline.

I profiled the program using nsys and found that the issue is in notify_dispatch. When run python dispatch with an interval of 50 ms between calls, notify_dispatch takes approximately 200us. However, when run python dispatch function continuously, the time for notify_dispatch is very small, less than 20us.

Therefore, When the number of tokens is small, such as only 8 tokens per rank, the dispatch time is only 90us(I test in intranode), which makes notify_dispatch a bottleneck. When the number of tokens is 4096 per rank, the dispatch time is 470us, and the 200us for notify_dispatch still leads to significantly worse results.

Image

Image

Do you know why the notify_dispatch time changes even though the input hasn't changed?

<!-- gh-comment-id:2838570329 --> @TheRainstorm commented on GitHub (Apr 29, 2025): > If the problem still exists, I guess you should dump the profiling timeline. I profiled the program using nsys and found that the issue is in `notify_dispatch`. When run python `dispatch` with an interval of 50 ms between calls, `notify_dispatch` takes approximately 200us. However, when run python dispatch function continuously, the time for `notify_dispatch` is very small, less than 20us. Therefore, When the number of tokens is small, such as only 8 tokens per rank, the dispatch time is only 90us(I test in intranode), which makes `notify_dispatch` a bottleneck. When the number of tokens is 4096 per rank, the `dispatch` time is 470us, and the 200us for `notify_dispatch` still leads to significantly worse results. ![Image](https://github.com/user-attachments/assets/26831b02-7f4e-4a30-8031-db9f5c79db55) ![Image](https://github.com/user-attachments/assets/728cf476-51b2-4e08-8aa2-b858aa7e7ce5) Do you know why the `notify_dispatch` time changes even though the input hasn't changed?
Author
Owner

@terminator123 commented on GitHub (Oct 16, 2025):

If the problem still exists, I guess you should dump the profiling timeline.

I profiled the program using nsys and found that the issue is in notify_dispatch. When run python dispatch with an interval of 50 ms between calls, notify_dispatch takes approximately 200us. However, when run python dispatch function continuously, the time for notify_dispatch is very small, less than 20us.

Therefore, When the number of tokens is small, such as only 8 tokens per rank, the dispatch time is only 90us(I test in intranode), which makes notify_dispatch a bottleneck. When the number of tokens is 4096 per rank, the dispatch time is 470us, and the 200us for notify_dispatch still leads to significantly worse results.

Image

Image

Do you know why the notify_dispatch time changes even though the input hasn't changed?

have you resolved the problem ?

<!-- gh-comment-id:3408885762 --> @terminator123 commented on GitHub (Oct 16, 2025): > > If the problem still exists, I guess you should dump the profiling timeline. > > I profiled the program using nsys and found that the issue is in `notify_dispatch`. When run python `dispatch` with an interval of 50 ms between calls, `notify_dispatch` takes approximately 200us. However, when run python dispatch function continuously, the time for `notify_dispatch` is very small, less than 20us. > > Therefore, When the number of tokens is small, such as only 8 tokens per rank, the dispatch time is only 90us(I test in intranode), which makes `notify_dispatch` a bottleneck. When the number of tokens is 4096 per rank, the `dispatch` time is 470us, and the 200us for `notify_dispatch` still leads to significantly worse results. > > ![Image](https://github.com/user-attachments/assets/26831b02-7f4e-4a30-8031-db9f5c79db55) > > ![Image](https://github.com/user-attachments/assets/728cf476-51b2-4e08-8aa2-b858aa7e7ce5) > > Do you know why the `notify_dispatch` time changes even though the input hasn't changed? have you resolved the problem ?
Author
Owner

@TheRainstorm commented on GitHub (Nov 6, 2025):

have you resolved the problem ?

This might not be an actual issue, as my previous testing methodology was flawed.

As LyricZhao pointed out, the dispatch interface internally consists of two phases: notify_dispatch and dispatch. The former phase acts as a synchronization point for all GPUs. Consequently, with a mix of faster and slower GPUs, the notify_dispatch duration can become significantly long, as it must wait for the slowest GPU to reach this synchronization point.

The "dispatch time" I was measuring was actually the combined duration of notify_dispatch + dispatch on a single GPU. By introducing a sleep call, I caused the GPUs to arrive at the sync point at different times, which in turn inflated the notify_dispatch time.

In summary, the root of the problem was my misunderstanding of what the dispatch time from the Python interface truly represents.

<!-- gh-comment-id:3496673073 --> @TheRainstorm commented on GitHub (Nov 6, 2025): > have you resolved the problem ? This might not be an actual issue, as my previous testing methodology was flawed. As LyricZhao pointed out, the dispatch interface internally consists of two phases: notify_dispatch and dispatch. The former phase acts as a synchronization point for all GPUs. Consequently, with a mix of faster and slower GPUs, the notify_dispatch duration can become significantly long, as it must wait for the slowest GPU to reach this synchronization point. The "dispatch time" I was measuring was actually the combined duration of notify_dispatch + dispatch on a single GPU. By introducing a sleep call, I caused the GPUs to arrive at the sync point at different times, which in turn inflated the notify_dispatch time. In summary, the root of the problem was my misunderstanding of what the dispatch time from the Python interface truly represents.
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
deepseek-ai/DeepEP#121
No description provided.