NVIDIA / NVIDIA/Megatron-LM

[QUESTION] In-depth explanation and verification of roll_tensor logic for Context Parallelism in MTP

Open
#1,795 0 comments 0 reactions 0 assignees View on GitHub
community-request module: moe question
Dominant language
Python
Stars
17.9k
Forks
4.5k
Avg merge
4d 6h
Merged PRs (30d)
271

Description

I'm currently studying the Multi-Token Prediction (MTP) implementation, specifically the `roll_tensor` function located in `megatron/core/transformer/multi_token_prediction.py`. I have a question regarding its logic when operating with Context Parallelism (`CP > 1`).

To understand its behavior, I manually traced the function with a concrete example, and the result was counter-intuitive. I would be very grateful if you could verify my trace and clarify the underlying logic.

**My Trace of `roll_tensor(shifts=-1)` with CP=2:**

Let's assume a global sequence `[1, 2, 3, 4, 5, 6, 7, 8]` is split across two CP ranks as follows:
* **CP Rank 0 holds:** `[1, 2, 3, 4]`
* **CP Rank 1 holds:** `[5, 6, 7, 8]`

My goal is to understand the output on each rank after the `roll_tensor` call.

**On CP Rank 0 (local_rank=0):**
1. **Initial Tensor:** `[1, 2, 3, 4]`
2. **Chunking:** `[1, 2]` and `[3, 4]`
3. **Local Roll:** `[2, 1]` and `[4, 3]`
4. **Send/Recv Prep:** `tensor_send_list` is `[1, 3]`. Rank 0 will send `3` (from the 2nd chunk) to Rank 1 and expects to receive a value from Rank 1 to fill the end of its 1st chunk.
5. **Communication:** Let's assume Rank 0 receives `5` from Rank 1.
6. **Splicing:**
* The 1st chunk `[2, 1]` becomes `[2, 5]`.
* The 2nd chunk `[4, 3]` becomes `[4, 0]` (since for `rank=0`, `tensor_recv_list[1]` is hardcoded to 0).
7. **Final Concatenation on Rank 0:** `torch.cat([2, 5], [4, 0])` results in **`[2, 5, 4, 0]`**.

**On CP Rank 1 (local_rank=1, the last rank):**
1. **Initial Tensor:** `[5, 6, 7, 8]`
2. **Chunking:** `[5, 6]` and `[7, 8]`
3. **Local Roll:** `[6, 5]` and `[8, 7]`
4. **Send/Recv Prep:** `tensor_send_list` is `[5, 7]`. Rank 1 will send `5` (from the 1st chunk) to Rank 0 and expects to receive a value from Rank 0 to fill the end of its 2nd chunk.
5. **Communication:** Let's assume Rank 1 receives `3` from Rank 0. The special rule for the last rank also applies: `tensor_recv_list[0] = tensor_send_list[1]`, so `tensor_recv_list[0]` becomes `7`.
6. **Splicing:**
* The 1st chunk `[6, 5]` becomes `[6, 7]`.
* The 2nd chunk `[8, 7]` becomes `[8, 3]`.
7. **Final Concatenation on Rank 1:** `torch.cat([6, 7], [8, 3])` results in **`[6, 7, 8, 3]`**.

**My Question:**

The traced outputs (`[2, 5, 4, 0]` on Rank 0 and `[6, 7, 8, 3]` on Rank 1) are not what I would intuitively expect from a simple distributed roll. My intuitive expectation for the final state would be `[2, 3, 4, 5]` on Rank 0 and `[6, 7, 8, 0]` on Rank 1.

This discrepancy strongly suggests that the data layout from `get_batch_on_this_cp_rank()` is more complex than a simple sequential split.

Could you please confirm if my trace is correct? If so, could you explain the data layout that `roll_tensor` is designed to work with, which leads to this specific behavior?

Finally, what is the purpose of splitting the tensor into two chunks (`tensor.chunk(2, dim=dims)`) at the beginning of the function? Is it related to handling micro-batches?

Thank you for your time and for helping clarify this complex but important detail!

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.