Add KV replicate feature when kv_head<tp_size - #47811
Conversation
|
@Cyrilvallez @3outeille This PR tries to support 1 scenario: when the model need to be loaded with tp mode, but the tp_size is larger than kv_head, we need to do KV replicate like the feature in VLLM. I have verified the correctness of this PR using |
|
Hey super cool ! Just a note that we are undergoing a refactor of our Tensor Parallel cf #47579. If that's not too much, is it possible to rebase your work on top of the new branch ? |
|
Here is the example code: |
|
Hey @kaixuanliu, PR has been merged ! Feel free to rebase, i'll review it |
Signed-off-by: kaixuanliu <kaixuan.liu@intel.com>
ba2e639 to
4a78c79
Compare
Signed-off-by: kaixuanliu <kaixuan.liu@intel.com>
…mers into kv-replicate
|
@3outeille ,rebase work is done, pls help review, thx! |
|
The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update. |
| local_rank = mesh.get_local_rank() | ||
| group = None | ||
| for start in range(0, len(global_ranks), n_rep): | ||
| candidate = dist.new_group(ranks=global_ranks[start : start + n_rep]) |
There was a problem hiding this comment.
does that still holds for 2D mesh ?
There was a problem hiding this comment.
pls help review again
| ``dist.new_group`` is collective, so every rank walks through all the groups in the same order and keeps the | ||
| one it belongs to. The result is cached because every attention layer asks for the same group. | ||
| """ | ||
| key = (id(mesh), n_rep) |
There was a problem hiding this comment.
double check because iirc id(mesh) as a key doesnt work. Had issue back in the day
import torch.distributed as dist
from torch.distributed.device_mesh import init_device_mesh
dist.init_process_group("gloo")
mesh = init_device_mesh("cpu", (2,), mesh_dim_names=["tp"])
a, b = mesh["tp"], mesh["tp"]
print(id(a) is id(b)) # False
| for param in mod.parameters(recurse=False): | ||
| if param.grad is not None: | ||
| grad = param.grad | ||
| dist.all_reduce(grad.to_local() if isinstance(grad, DTensor) else grad, group=group) |
There was a problem hiding this comment.
to double check it works, pick a model with tp_size > num_kv_heads (i.e: "Qwen/Qwen2.5-VL-3B-Instruct") and add it to TP_DISTRIBUTED_TEST_MODEL_TYPES in test_tensor_parallel_mixin.py and test its forward + backward to see if everything pass
I think we can leave the model in the list so that we can catch KV_replication regression later
There was a problem hiding this comment.
Added test_tp_kv_head_replication to TensorParallelTesterMixin: it forces num_key_value_heads=1 on the tiny config so num_kv_heads < tp_size, asserts the layers actually went through ReplicateKVHeadsParallel, and then runs the full forward + backward (incl. per-parameter grad comparison). This runs for every model already in TP_DISTRIBUTED_TEST_MODEL_TYPES (qwen2/qwen3/qwen3_moe/...), so we get regression coverage without having to wire the TP mixin into a VLM test class (Qwen2_5_VLModelTest doesn't use CausalLMModelTester, so it would be skipped anyway).
| rank, world_size = sub_mesh.get_local_rank(), sub_mesh.size() | ||
| dim_idx = self._normalize_param_dim(placement.dim) | ||
| if self.kv_replication > 1 and placement.is_shard(): | ||
| rank, world_size = rank // self.kv_replication, world_size // self.kv_replication |
There was a problem hiding this comment.
correct for 1D, but not for 2D mesh
There was a problem hiding this comment.
Since apply_tensor_parallelism is always handed device_mesh["tp"] and TP/FSDP/PP are mutually exclusive, this is unreachable today: L194-L206, I have added a guard in __init__ func
|
|
||
| _validate_tp_plan_styles(model.tp_plan) | ||
| if model.tp_plan is not None: | ||
| model.tp_plan = _maybe_enable_kv_head_replication(model, model.tp_plan, tp_mesh.size()) |
There was a problem hiding this comment.
if enable_expert_parallel=True, it will swap out model.tp_plan to ep_plan. Double check if replication is still applied
There was a problem hiding this comment.
Thx for advice!! Have updated.
Signed-off-by: kaixuanliu <kaixuan.liu@intel.com>
Signed-off-by: kaixuanliu <kaixuan.liu@intel.com>
Signed-off-by: Liu, Kaixuan <kaixuan.liu@intel.com>
ArthurZucker
left a comment
There was a problem hiding this comment.
Really not aligned as this IMO should be much much simpler to do
| """ | ||
|
|
||
| def __init__(self, param: DTensor): | ||
| def __init__(self, param: DTensor, kv_replication: int = 1): |
There was a problem hiding this comment.
I really don't think it makes sense to put this attention, kv specific argument into a very general shading scheme.
There was a problem hiding this comment.
Well, I agree. It is not a good design indeed..., have fixed it.
There was a problem hiding this comment.
this is highly bloated, and that makes me think it just does not follow the design.
We are probably missing ShardingOps, that you would want for that, otherwise we can just properly resolve, based on the dim, what to do with the weights, regardless of attention or not. The same happens for WP sharding: see #48237
There was a problem hiding this comment.
Well the diff is a little big. But I don't think a purely dim-based rule can cover this case. Also take bigcode/starcoder2-3b as example, num_key_value_heads=2, head_dim=128, so k_proj.out_features=256. On tp=4, 256 % 4 == 0 — a dim-based rule shards it happily, and attention then dies on k_proj(x).view(*input_shape, -1, self.head_dim) with 64 features per rank. Whenever kv_heads < tp_size you get less than one head per rank, so this always happens. It can't be fixed in attention either: rotate_half pairs dim i with i + head_dim/2, and qk/softmax/FA kernels all need a whole head. head_dim is a hard granularity floor, so replication is the only option.
There was a problem hiding this comment.
@ArthurZucker I have refactored the code to align with your comments: the sharding machinery is based on head_dim.
Co-authored-by: Arthur <48595927+ArthurZucker@users.noreply.github.com>
Signed-off-by: kaixuanliu <kaixuan.liu@intel.com>
Signed-off-by: kaixuanliu <kaixuan.liu@intel.com>
CI recapDashboard: View test results in Grafana |
Uh oh!
There was an error while loading. Please reload this page.