main
py 664 lines 22.2 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3
4 import multiprocessing
5 import random
6 import sys
7 import types
8 from types import SimpleNamespace
9
10 import pytest
11 import ray
12 import torch
13 import torch.distributed as dist
14
15 import vllm._aiter_ops as aiter_ops
16 import vllm.distributed as vllm_distributed
17 import vllm.envs as envs
18 from vllm import _custom_ops as ops
19 from vllm.distributed.communication_op import tensor_model_parallel_all_reduce # noqa
20 from vllm.distributed.device_communicators.quick_all_reduce import (
21 KB,
22 MB,
23 QuickAllReduce,
24 QuickReduceRegime,
25 )
26 from vllm.distributed.parallel_state import get_tp_group, graph_capture
27 from vllm.envs import disable_envs_cache
28 from vllm.platforms import current_platform
29
30 from ..utils import (
31 ensure_model_parallel_initialized,
32 init_test_distributed_environment,
33 multi_process_parallel,
34 set_random_seed,
35 )
36
37
38 def on_gfx942() -> bool:
39 if current_platform.is_rocm():
40 from vllm.platforms.rocm import on_gfx942 as rocm_on_gfx942
41
42 return rocm_on_gfx942()
43 return False
44
45
46 def _quickreduce_tolerance(quant_level: QuickReduceRegime) -> dict:
47 # With INT3 inputs of absmax 23, per-rank error peaks at 2.75,
48 # doubled by the TP2 sum = 5.5.
49 if quant_level == QuickReduceRegime.INT3:
50 return {"atol": 5.5, "rtol": 0.1}
51 return {"atol": 2.5, "rtol": 0.1}
52
53
54 set_random_seed(42)
55 _test_size_rng = random.Random(44)
56 # Size over 8MB is sufficient for custom quick allreduce.
57 test_sizes = [
58 _test_size_rng.randint(8 * 1024 * 1024, 10 * 1024 * 1024) for _ in range(8)
59 ]
60 for i, v in enumerate(test_sizes):
61 test_sizes[i] -= v % 8
62
63
64 def _assert_quickreduce(fa, inp):
65 assert fa is not None
66 assert not fa.disabled
67 assert fa.should_quick_allreduce(inp)
68
69
70 @pytest.fixture
71 def envs_cache_disabled():
72 disable_envs_cache()
73 yield
74 disable_envs_cache()
75
76
77 def _make_quick_allreduce_for_test(
78 min_size_mb: int | None = None,
79 quantization_min_size: int | None = None,
80 ) -> QuickAllReduce:
81 quick_reduce = QuickAllReduce.__new__(QuickAllReduce)
82 quick_reduce.disabled = False
83 quick_reduce.qr_max_size = 16 * MB
84 quick_reduce.qr_min_size = min_size_mb * MB if min_size_mb is not None else None
85 quick_reduce.qr_quant_level = QuickReduceRegime.INT4
86 quick_reduce.qr_quantization_min_size = quantization_min_size
87 quick_reduce.use_fp16_kernels = False
88 quick_reduce.world_size = 2
89 return quick_reduce
90
91
92 def test_should_quick_allreduce_uses_builtin_min_size_when_unset():
93 quick_reduce = _make_quick_allreduce_for_test(min_size_mb=None)
94
95 below_builtin_min = torch.empty(MB // 4, dtype=torch.float16)
96 at_builtin_min = torch.empty(MB // 2, dtype=torch.float16)
97
98 assert not quick_reduce.should_quick_allreduce(below_builtin_min)
99 assert quick_reduce.should_quick_allreduce(at_builtin_min)
100
101
102 def test_should_quick_allreduce_uses_min_size_override():
103 quick_reduce = _make_quick_allreduce_for_test(min_size_mb=0)
104
105 below_builtin_min = torch.empty(8, dtype=torch.float16)
106
107 assert quick_reduce.should_quick_allreduce(below_builtin_min)
108
109
110 def test_quick_allreduce_min_size_env_unset(
111 monkeypatch: pytest.MonkeyPatch,
112 envs_cache_disabled,
113 ):
114 monkeypatch.delenv("VLLM_ROCM_QUICK_REDUCE_MIN_SIZE_BYTES_MB", raising=False)
115
116 assert QuickAllReduce._get_qr_min_size(qr_max_size=16 * MB) is None
117
118
119 def test_quick_allreduce_min_size_env_converts_mb_to_bytes(
120 monkeypatch: pytest.MonkeyPatch,
121 envs_cache_disabled,
122 ):
123 monkeypatch.setenv("VLLM_ROCM_QUICK_REDUCE_MIN_SIZE_BYTES_MB", "4")
124
125 assert QuickAllReduce._get_qr_min_size(qr_max_size=16 * MB) == 4 * MB
126
127
128 def test_quick_allreduce_min_size_env_rejects_negative(
129 monkeypatch: pytest.MonkeyPatch,
130 envs_cache_disabled,
131 ):
132 monkeypatch.setenv("VLLM_ROCM_QUICK_REDUCE_MIN_SIZE_BYTES_MB", "-1")
133
134 with pytest.raises(ValueError, match="must be non-negative"):
135 QuickAllReduce._get_qr_min_size(qr_max_size=16 * MB)
136
137
138 def test_quick_allreduce_min_size_env_allows_equal_to_max(
139 monkeypatch: pytest.MonkeyPatch,
140 envs_cache_disabled,
141 ):
142 monkeypatch.setenv("VLLM_ROCM_QUICK_REDUCE_MIN_SIZE_BYTES_MB", "16")
143
144 assert QuickAllReduce._get_qr_min_size(qr_max_size=16 * MB) == 16 * MB
145
146
147 def test_quick_allreduce_min_size_env_rejects_larger_than_max(
148 monkeypatch: pytest.MonkeyPatch,
149 envs_cache_disabled,
150 ):
151 monkeypatch.setenv("VLLM_ROCM_QUICK_REDUCE_MIN_SIZE_BYTES_MB", "17")
152
153 with pytest.raises(ValueError, match="effective QuickReduce max size"):
154 QuickAllReduce._get_qr_min_size(qr_max_size=16 * MB)
155
156
157 def test_quick_allreduce_quantization_min_size_env_unset(
158 monkeypatch: pytest.MonkeyPatch,
159 envs_cache_disabled,
160 ):
161 monkeypatch.delenv("VLLM_ROCM_QUICK_REDUCE_QUANTIZATION_MIN_SIZE_KB", raising=False)
162
163 assert QuickAllReduce._get_qr_quantization_min_size() is None
164
165
166 def test_quick_allreduce_quantization_min_size_env_converts_kb_to_bytes(
167 monkeypatch: pytest.MonkeyPatch,
168 envs_cache_disabled,
169 ):
170 monkeypatch.setenv("VLLM_ROCM_QUICK_REDUCE_QUANTIZATION_MIN_SIZE_KB", "2048")
171
172 assert QuickAllReduce._get_qr_quantization_min_size() == 2048 * KB
173
174
175 def test_quick_allreduce_quantization_min_size_env_rejects_negative(
176 monkeypatch: pytest.MonkeyPatch,
177 envs_cache_disabled,
178 ):
179 monkeypatch.setenv("VLLM_ROCM_QUICK_REDUCE_QUANTIZATION_MIN_SIZE_KB", "-1")
180
181 with pytest.raises(ValueError, match="must be non-negative"):
182 QuickAllReduce._get_qr_quantization_min_size()
183
184
185 def test_quick_allreduce_quantization_min_size_unset_uses_configured_codec():
186 quick_reduce = _make_quick_allreduce_for_test(quantization_min_size=None)
187 inp = torch.empty(8, dtype=torch.float16)
188
189 assert quick_reduce._get_qr_quant_level(inp) == QuickReduceRegime.INT4.value
190
191
192 def test_quick_allreduce_quantization_min_size_uses_fp_below_threshold():
193 quick_reduce = _make_quick_allreduce_for_test(quantization_min_size=2048)
194 inp = torch.empty(1024 // 2, dtype=torch.float16)
195
196 assert quick_reduce._get_qr_quant_level(inp) == QuickReduceRegime.FP.value
197
198
199 def test_quick_allreduce_quantization_min_size_uses_configured_codec_at_threshold():
200 quick_reduce = _make_quick_allreduce_for_test(quantization_min_size=2048)
201 inp = torch.empty(2048 // 2, dtype=torch.float16)
202
203 assert quick_reduce._get_qr_quant_level(inp) == QuickReduceRegime.INT4.value
204
205
206 def test_quick_allreduce_quantization_min_size_does_not_change_eligibility():
207 quick_reduce = _make_quick_allreduce_for_test(quantization_min_size=2 * MB)
208
209 below_builtin_min = torch.empty(MB // 4, dtype=torch.float16)
210 at_builtin_min = torch.empty(MB // 2, dtype=torch.float16)
211
212 assert not quick_reduce.should_quick_allreduce(below_builtin_min)
213 assert quick_reduce.should_quick_allreduce(at_builtin_min)
214
215
216 def test_quick_allreduce_passes_dynamic_quant_level(
217 monkeypatch: pytest.MonkeyPatch,
218 ):
219 quick_reduce = _make_quick_allreduce_for_test(quantization_min_size=2 * KB)
220 quick_reduce._ptr = object()
221 inp = torch.empty(KB // 2, dtype=torch.float16)
222 called_quant_level = None
223
224 def fake_qr_all_reduce(
225 fa,
226 inp,
227 out,
228 quant_level,
229 cast_bf2half,
230 ):
231 nonlocal called_quant_level
232 called_quant_level = quant_level
233
234 monkeypatch.setattr(ops, "qr_all_reduce", fake_qr_all_reduce)
235
236 quick_reduce.quick_all_reduce(inp)
237
238 assert called_quant_level == QuickReduceRegime.FP.value
239
240
241 def test_rocm_aiter_fused_rmsnorm_uses_aiter_qr_rmsnorm_for_prefill(
242 monkeypatch: pytest.MonkeyPatch,
243 ):
244 class FakeAiterAllReduce:
245 aiter_ca = SimpleNamespace(world_size=2, fully_connected=True)
246
247 def use_1stage_fused_ar_rms(self, inp):
248 return False
249
250 def custom_fused_ar_rms(self, *args, **kwargs):
251 raise AssertionError("expected fused QR+RMSNorm dispatch")
252
253 class FakeQuickReduce:
254 disabled = False
255
256 def __init__(self):
257 self.checked_input = None
258
259 def should_quick_allreduce(self, inp):
260 self.checked_input = inp
261 return True
262
263 def _get_qr_quant_level(self, inp):
264 return QuickReduceRegime.INT4.value
265
266 qr_comm = FakeQuickReduce()
267 device_comm = SimpleNamespace(cpu_group=object(), qr_comm=qr_comm)
268 monkeypatch.setattr(
269 vllm_distributed,
270 "get_tp_group",
271 lambda: SimpleNamespace(device_communicator=device_comm),
272 )
273 monkeypatch.setattr(
274 aiter_ops.rocm_aiter_ops,
275 "get_aiter_allreduce",
276 lambda: FakeAiterAllReduce(),
277 )
278 monkeypatch.setattr(aiter_ops.dist, "get_world_size", lambda group: 2)
279 monkeypatch.setattr(aiter_ops.dist, "get_rank", lambda group: 0)
280 monkeypatch.setattr(
281 aiter_ops.dist,
282 "all_gather_object",
283 lambda handles, handle, group: handles.__setitem__(0, handle),
284 )
285
286 calls = []
287 fake_aiter = types.SimpleNamespace(
288 init_custom_qr=lambda rank, world_size, qr_max_size: 123,
289 qr_get_handle=lambda ptr: b"handle",
290 qr_open_handles=lambda ptr, handles: None,
291 )
292
293 def fake_qr_all_reduce_rmsnorm(
294 ptr,
295 inp,
296 residual,
297 residual_out,
298 out,
299 weight,
300 eps,
301 hidden_dim,
302 quant_level,
303 cast_bf2half,
304 ):
305 calls.append(
306 {
307 "ptr": ptr,
308 "inp": inp,
309 "residual": residual,
310 "residual_out": residual_out,
311 "out": out,
312 "weight": weight,
313 "eps": eps,
314 "hidden_dim": hidden_dim,
315 "quant_level": quant_level,
316 "cast_bf2half": cast_bf2half,
317 }
318 )
319 out.copy_(inp)
320 residual_out.copy_(residual)
321
322 fake_aiter.qr_all_reduce_rmsnorm = fake_qr_all_reduce_rmsnorm
323 monkeypatch.setitem(sys.modules, "aiter", fake_aiter)
324 monkeypatch.setattr(envs, "VLLM_ROCM_QUICK_REDUCE_MAX_SIZE_BYTES_MB", None)
325 monkeypatch.setattr(envs, "VLLM_ROCM_QUICK_REDUCE_CAST_BF16_TO_FP16", True)
326
327 # token_num > 80 forces vLLM's AITER fused AR+RMS wrapper onto its
328 # non-1stage prefill path. hidden_dim=4096 bf16 gives an 8 KiB row, so four
329 # independent RMSNorm rows fit in one 32 KiB QR tile.
330 inp = torch.randn(81, 4096, dtype=torch.bfloat16)
331 residual = torch.randn_like(inp)
332 weight = torch.randn(4096, dtype=torch.bfloat16)
333
334 out, residual_out = aiter_ops._rocm_aiter_fused_allreduce_rmsnorm_impl(
335 inp, residual, weight, 1e-6
336 )
337
338 assert qr_comm.checked_input is inp
339 assert len(calls) == 1
340 assert calls[0]["ptr"] == 123
341 assert calls[0]["inp"] is inp
342 assert calls[0]["residual"] is residual
343 assert calls[0]["out"] is out
344 assert calls[0]["residual_out"] is residual_out
345 assert calls[0]["weight"] is weight
346 assert calls[0]["hidden_dim"] == 4096
347 assert calls[0]["quant_level"] == QuickReduceRegime.INT4.value
348 assert calls[0]["cast_bf2half"] is True
349 torch.testing.assert_close(out, inp)
350 torch.testing.assert_close(residual_out, residual)
351
352
353 def test_rocm_aiter_fused_rmsnorm_keeps_1stage_decode_path(
354 monkeypatch: pytest.MonkeyPatch,
355 ):
356 calls = []
357
358 class FakeAiterCA:
359 world_size = 2
360 fully_connected = True
361
362 def custom_fused_ar_rms(
363 self, inp, residual, weight, epsilon, *, use_1stage, gemma_norm
364 ):
365 calls.append(
366 {
367 "inp": inp,
368 "residual": residual,
369 "weight": weight,
370 "eps": epsilon,
371 "use_1stage": use_1stage,
372 "gemma_norm": gemma_norm,
373 }
374 )
375 return inp + 1, residual + 1
376
377 class FakeAiterAllReduce:
378 aiter_ca = FakeAiterCA()
379
380 def use_1stage_fused_ar_rms(self, inp):
381 return True
382
383 monkeypatch.setattr(
384 aiter_ops.rocm_aiter_ops,
385 "get_aiter_allreduce",
386 lambda: FakeAiterAllReduce(),
387 )
388 monkeypatch.setattr(torch.cuda, "is_current_stream_capturing", lambda: False)
389
390 inp = torch.randn(4, 4096, dtype=torch.bfloat16)
391 residual = torch.randn_like(inp)
392 weight = torch.randn(4096, dtype=torch.bfloat16)
393
394 out, residual_out = aiter_ops._rocm_aiter_fused_allreduce_rmsnorm_impl(
395 inp, residual, weight, 1e-6
396 )
397
398 assert len(calls) == 1
399 assert calls[0]["inp"] is inp
400 assert calls[0]["residual"] is residual
401 assert calls[0]["weight"] is weight
402 assert calls[0]["use_1stage"] is True
403 assert calls[0]["gemma_norm"] is False
404 torch.testing.assert_close(out, inp + 1)
405 torch.testing.assert_close(residual_out, residual + 1)
406
407
408 @ray.remote(num_gpus=1, max_calls=1)
409 def graph_quickreduce(
410 monkeypatch: pytest.MonkeyPatch,
411 tp_size,
412 pp_size,
413 rank,
414 distributed_init_port,
415 ):
416 with monkeypatch.context() as m:
417 m.delenv("CUDA_VISIBLE_DEVICES", raising=False)
418 m.delenv("HIP_VISIBLE_DEVICES", raising=False)
419 m.delenv("ROCR_VISIBLE_DEVICES", raising=False)
420 device = torch.device(f"cuda:{rank}")
421 torch.accelerator.set_device_index(device)
422 init_test_distributed_environment(tp_size, pp_size, rank, distributed_init_port)
423 ensure_model_parallel_initialized(tp_size, pp_size)
424 group = get_tp_group().device_group
425 fa = get_tp_group().device_communicator.qr_comm
426
427 # A small all_reduce for warmup.
428 # this is needed because device communicators might be created lazily
429 # (e.g. NCCL). This will ensure that the communicator is initialized
430 # before any communication happens, so that this group can be used for
431 # graph capture immediately.
432 data = torch.zeros(1)
433 data = data.to(device=device)
434 torch.distributed.all_reduce(data, group=group)
435 torch.accelerator.synchronize()
436 del data
437
438 # we use the first group to communicate once
439 # and the second group to communicate twice
440 # and so on
441 # this is used to demonstrate that each group can
442 # communicate independently
443 num_communication = rank // tp_size + 1
444
445 for sz in test_sizes:
446 for dtype in [torch.float16, torch.bfloat16]:
447 with graph_capture(device=device) as graph_capture_context:
448 device_idx = torch.accelerator.current_device_index()
449 inp1 = torch.randint(1, 23, (sz,), dtype=dtype, device=device_idx)
450 inp2 = torch.randint(-23, 1, (sz,), dtype=dtype, device=device_idx)
451 _assert_quickreduce(fa, inp1)
452 _assert_quickreduce(fa, inp2)
453
454 torch.accelerator.synchronize()
455 graph = torch.cuda.CUDAGraph()
456 with torch.cuda.graph(graph, stream=graph_capture_context.stream):
457 for _ in range(num_communication):
458 out1 = tensor_model_parallel_all_reduce(inp1)
459 dist.all_reduce(inp1, group=group)
460 out2 = tensor_model_parallel_all_reduce(inp2)
461 dist.all_reduce(inp2, group=group)
462 graph.replay()
463 tol = _quickreduce_tolerance(fa.qr_quant_level)
464 torch.testing.assert_close(out1, inp1, **tol)
465 torch.testing.assert_close(out2, inp2, **tol)
466
467
468 @ray.remote(num_gpus=1, max_calls=1)
469 def eager_quickreduce(
470 monkeypatch: pytest.MonkeyPatch,
471 tp_size,
472 pp_size,
473 rank,
474 distributed_init_port,
475 ):
476 with monkeypatch.context() as m:
477 m.delenv("CUDA_VISIBLE_DEVICES", raising=False)
478 m.delenv("HIP_VISIBLE_DEVICES", raising=False)
479 m.delenv("ROCR_VISIBLE_DEVICES", raising=False)
480 device = torch.device(f"cuda:{rank}")
481 torch.accelerator.set_device_index(device)
482
483 init_test_distributed_environment(tp_size, pp_size, rank, distributed_init_port)
484
485 # Size over 8MB is sufficient for custom quick allreduce.
486 sz = 16 * 1024 * 1024
487 fa = get_tp_group().device_communicator.qr_comm
488 inp = torch.tensor(
489 [1.0 * ((i) % 23) for i in range(sz)], dtype=torch.float16, device=device
490 )
491 _assert_quickreduce(fa, inp)
492 out = fa.quick_all_reduce(inp)
493 tol = _quickreduce_tolerance(fa.qr_quant_level)
494 torch.testing.assert_close(out, inp * tp_size, **tol)
495
496 inp = torch.tensor(
497 [1.0 * ((i) % 23) for i in range(sz)], dtype=torch.bfloat16, device=device
498 )
499 _assert_quickreduce(fa, inp)
500 out = fa.quick_all_reduce(inp)
501 torch.testing.assert_close(out, inp * tp_size, **tol)
502
503
504 @ray.remote(num_gpus=1, max_calls=1)
505 def bf16_cast_quickreduce(
506 monkeypatch: pytest.MonkeyPatch,
507 tp_size,
508 pp_size,
509 rank,
510 distributed_init_port,
511 ):
512 with monkeypatch.context() as m:
513 m.delenv("CUDA_VISIBLE_DEVICES", raising=False)
514 m.delenv("HIP_VISIBLE_DEVICES", raising=False)
515 m.delenv("ROCR_VISIBLE_DEVICES", raising=False)
516 m.setenv("VLLM_ROCM_QUICK_REDUCE_CAST_BF16_TO_FP16", "1")
517 device = torch.device(f"cuda:{rank}")
518 torch.accelerator.set_device_index(device)
519 init_test_distributed_environment(tp_size, pp_size, rank, distributed_init_port)
520
521 sz = 16 * 1024 * 1024
522 fa = get_tp_group().device_communicator.qr_comm
523 inp = torch.tensor(
524 [1.0 * (i % 23) for i in range(sz)], dtype=torch.bfloat16, device=device
525 )
526 _assert_quickreduce(fa, inp)
527 assert fa.use_fp16_kernels
528 out = fa.quick_all_reduce(inp)
529 torch.testing.assert_close(out, inp * tp_size, atol=2.5, rtol=0.1)
530
531
532 @pytest.mark.skipif(
533 not current_platform.is_rocm(), reason="only test quick allreduce for rocm"
534 )
535 @pytest.mark.parametrize("quant_mode", ["FP", "INT8", "INT6", "INT4", "INT3"])
536 @pytest.mark.parametrize("tp_size", [2])
537 @pytest.mark.parametrize("pipeline_parallel_size", [1, 2])
538 @pytest.mark.parametrize("test_target", [graph_quickreduce, eager_quickreduce])
539 def test_custom_quick_allreduce(
540 monkeypatch: pytest.MonkeyPatch,
541 tp_size,
542 pipeline_parallel_size,
543 test_target,
544 quant_mode,
545 ):
546 world_size = tp_size * pipeline_parallel_size
547 if world_size > torch.accelerator.device_count():
548 pytest.skip("Not enough GPUs to run the test.")
549 if test_target is graph_quickreduce and on_gfx942():
550 pytest.xfail(
551 "CUDA graph capture with quick reduce hits "
552 "hipErrorStreamCaptureInvalidated on gfx942"
553 )
554
555 monkeypatch.setenv("VLLM_ROCM_QUICK_REDUCE_QUANTIZATION", quant_mode)
556
557 multi_process_parallel(monkeypatch, tp_size, pipeline_parallel_size, test_target)
558
559
560 @pytest.mark.skipif(
561 not current_platform.is_rocm(), reason="only test quick allreduce for rocm"
562 )
563 def test_custom_quick_allreduce_bf16_cast(monkeypatch: pytest.MonkeyPatch):
564 if torch.accelerator.device_count() < 2:
565 pytest.skip("Not enough GPUs to run the test.")
566 monkeypatch.setenv("VLLM_ROCM_QUICK_REDUCE_QUANTIZATION", "FP")
567 multi_process_parallel(monkeypatch, 2, 1, bf16_cast_quickreduce)
568
569
570 def qr_variable_input(rank, world_size):
571 """When the tensor parallelism is set to 4 or 8, frequent changes
572 in the input shape can cause QuickReduce to hang (this issue
573 has been observed with the gpt_oss model).
574 """
575 device = torch.device(f"cuda:{rank}")
576 torch.accelerator.set_device_index(device)
577 qr_max_size = None # MB
578 _ptr = ops.init_custom_qr(rank, world_size, qr_max_size)
579 ranks = []
580 for i in range(world_size):
581 ranks.append(i)
582 if envs.VLLM_DISTRIBUTED_USE_SPLIT_GROUP:
583 dist.init_process_group(
584 backend="cpu:gloo,cuda:nccl",
585 init_method="tcp://127.0.0.1:29500",
586 rank=rank,
587 world_size=world_size,
588 device_id=device,
589 )
590 else:
591 dist.init_process_group(
592 backend="nccl",
593 init_method="tcp://127.0.0.1:29500",
594 rank=rank,
595 world_size=world_size,
596 )
597 if envs.VLLM_DISTRIBUTED_USE_SPLIT_GROUP:
598 cpu_group = torch.distributed.split_group(
599 split_ranks=[ranks], backend="cpu:gloo,cuda:nccl"
600 )
601 else:
602 cpu_group = torch.distributed.new_group(ranks, backend="nccl")
603
604 handle = ops.qr_get_handle(_ptr)
605 world_size = dist.get_world_size(group=cpu_group)
606 handles = [None] * world_size
607 dist.all_gather_object(handles, handle, group=cpu_group)
608 ops.qr_open_handles(_ptr, handles)
609
610 num = 1
611 s1 = 1024
612 while num < 50000: # 50000 is sufficient to identify issues.
613 dtype = torch.float16
614 device_idx = torch.accelerator.current_device_index()
615 if num % 2 == 0:
616 s2 = 1024
617 inp1 = torch.zeros((s1, s2), dtype=dtype, device=device_idx)
618 else:
619 s2 = 2048
620 inp1 = torch.ones((s1, s2), dtype=dtype, device=device_idx)
621 result = torch.empty_like(inp1)
622 # FP = 0 INT8 = 1 INT6 = 2 INT4 = 3 INT3 = 4
623 ops.qr_all_reduce(_ptr, inp1, result, 3, cast_bf2half=True)
624 try:
625 if inp1[0, 0] == 0:
626 assert torch.all(result == 0)
627 else:
628 assert torch.all(result == world_size)
629 except AssertionError:
630 print("Assertion failed! Allreduce results are incorrect.")
631 raise
632 num += 1
633
634
635 @pytest.mark.skipif(
636 not current_platform.is_rocm(), reason="only test quick allreduce for rocm"
637 )
638 @pytest.mark.parametrize("tp_size", [4, 8])
639 @pytest.mark.parametrize("pipeline_parallel_size", [1])
640 def test_custom_quick_allreduce_variable_input(tp_size, pipeline_parallel_size):
641 world_size = tp_size * pipeline_parallel_size
642 if world_size > torch.accelerator.device_count():
643 pytest.skip("Not enough GPUs to run the test.")
644
645 multiprocessing.set_start_method("spawn", force=True)
646 # 60s is enough
647 timeout = 60
648 processes = []
649 for rank in range(tp_size):
650 p = multiprocessing.Process(target=qr_variable_input, args=(rank, tp_size))
651 p.start()
652 processes.append((rank, p))
653 for rank, p in processes:
654 p.join(timeout=timeout)
655 if p.is_alive():
656 for r, proc in processes:
657 if proc.is_alive():
658 proc.terminate()
659 proc.join()
660 raise RuntimeError(f"QuickReduce hang detected after {timeout} seconds!")
661
662
663 if __name__ == "__main__":
664 test_custom_quick_allreduce_variable_input(tp_size=4, pipeline_parallel_size=1)