import os import time import pytest import torch import torch.distributed as dist import torch.multiprocessing as mp from torch import nn from lmdeploy.pytorch.distributed import DefaultContext from lmdeploy.pytorch.nn import ParallelEmbedding def parallel_emb(rank: int, world_size: int, vocab_size: int, feat_size: int, padding_idx: int, dtype: torch.dtype, x: torch.Tensor, weight: torch.Tensor, result_queue: mp.Queue): dist.init_process_group('nccl', rank=rank, world_size=world_size) gpu_group = dist.new_group(ranks=list(range(world_size)), backend='nccl') DefaultContext.attn_tp_group.rank = rank DefaultContext.dist_config.attn_tp = world_size DefaultContext.attn_tp_group.gpu_group = gpu_group model = ParallelEmbedding(vocab_size=vocab_size, hidden_size=feat_size, padding_idx=padding_idx, dtype=dtype, is_tp=True, device=torch.device(type='cuda', index=rank)) weight = weight.to(torch.device(type='cuda', index=rank)) model.weight_loader(model.weight, weight) input = x.to(torch.device(type='cuda', index=rank)) with torch.inference_mode(): out = model(input) if rank == 0: result_queue.put(mp.reductions.reduce_tensor(out)) if dist.is_initialized(): dist.destroy_process_group() class TestEmbedding: @pytest.fixture def vocab_size(self, request): yield request.param @pytest.fixture def feat_size(self, request): yield request.param @pytest.fixture def padding_idx(self, request): yield request.param @pytest.fixture def dtype(self, request): yield request.param @pytest.fixture def tp(self, request): yield request.param @pytest.fixture def seqlen(self, request): yield request.param @pytest.fixture def weight(self, vocab_size, feat_size, dtype): yield torch.rand(vocab_size, feat_size, dtype=dtype) @pytest.fixture def x(self, seqlen, vocab_size): yield torch.randint(low=0, high=vocab_size, size=(seqlen, ), dtype=torch.int32) @pytest.fixture def gt(self, x, vocab_size, feat_size, padding_idx, dtype, weight): token_emb = nn.Embedding(vocab_size, feat_size, padding_idx=padding_idx, dtype=dtype, device=torch.device(type='cuda', index=0)) token_emb.weight.data.copy_(weight) token_emb._fill_padding_idx_with_zero() input = x.to(torch.device(type='cuda', index=0)) yield token_emb(input) @pytest.mark.parametrize('vocab_size', [65576, 65533, 3333], indirect=True) @pytest.mark.parametrize('feat_size', [4096, 768], indirect=True) @pytest.mark.parametrize('padding_idx', [None], indirect=True) @pytest.mark.parametrize('seqlen', [1024, 1011, 128], indirect=True) @pytest.mark.parametrize('tp', [2], indirect=True) @pytest.mark.parametrize('dtype', [torch.bfloat16], indirect=True) def test_embedding(self, vocab_size, feat_size, padding_idx, seqlen, tp, dtype, x, weight, gt): os.environ['MASTER_ADDR'] = 'localhost' os.environ['MASTER_PORT'] = '29500' os.environ['NCCL_SOCKET_IFNAME'] = 'lo' world_size = tp processes = [] mp.set_start_method('spawn', force=True) result_queue = mp.Queue() for rank in range(world_size): p = mp.Process(target=parallel_emb, args=(rank, world_size, vocab_size, feat_size, padding_idx, dtype, x, weight, result_queue)) processes.append(p) p.start() time.sleep(0.5) func, args = result_queue.get() out = func(*args) for p in processes: p.join(timeout=10) if p.is_alive(): p.terminate() p.join(timeout=5) if p.is_alive(): p.kill() torch.testing.assert_close(out, gt)