[NPU]TP Communications compression For Qwen3 models for NPU (#20520)

Co-authored-by: ronnie_zheng <zl19940307@163.com>
This commit is contained in:
egvenediktov
2026-05-02 14:29:11 +03:00
committed by GitHub
co-authored by ronnie_zheng
parent ebbaab5597
commit 83bf5d6869
13 changed files with 191 additions and 10 deletions
+1 -1
View File
@@ -4,7 +4,7 @@ import time
import numpy as np
from sglang.api import set_default_backend
from sglang.lang.api import set_default_backend
from sglang.test.test_utils import (
add_common_sglang_args_and_parse,
select_sglang_backend,
@@ -119,6 +119,7 @@ Please consult the documentation below and [server_args.py](https://github.com/s
| `--modelopt-export-path` | Path to export the quantized model in HuggingFace format after ModelOpt quantization. The exported model can then be used directly with SGLang for inference. If not provided, the model will not be exported. | `None` | Type: str |
| `--quantize-and-serve` | Quantize the model with ModelOpt and immediately serve it without exporting. This is useful for development and prototyping. For production, it's recommended to use separate quantization and deployment steps. | `False` | bool flag (set to enable) |
| `--rl-quant-profile` | Path to the FlashRL quantization profile. Required when using --load-format flash_rl. | `None` | Type: str |
| `--enable-quant-communications` | Enable INT8 quantization of TP communications (Supported only for NPU for Qwen3 series). | `False` | bool flag (set to enable) |
## Memory and scheduling
| Argument | Description | Defaults | Options |
@@ -18,6 +18,11 @@ def tensor_model_parallel_all_reduce(input_: torch.Tensor) -> torch.Tensor:
return get_tp_group().all_reduce(input_)
def tensor_model_parallel_quant_all_reduce(input_: torch.Tensor) -> torch.Tensor:
"""All-reduce the input tensor across model parallel group."""
return get_tp_group().quant_all_reduce(input_)
def tensor_model_parallel_fused_allreduce_rmsnorm(
input_: torch.Tensor,
residual_inp_: torch.Tensor,
@@ -60,6 +65,13 @@ def attention_tensor_model_parallel_all_reduce(input_: torch.Tensor) -> torch.Te
return get_attn_tp_group().all_reduce(input_)
def attention_tensor_model_parallel_quant_all_reduce(
input_: torch.Tensor,
) -> torch.Tensor:
"""All-reduce the input tensor across attention parallel group."""
return get_attn_tp_group().quant_all_reduce(input_)
def moe_tensor_model_parallel_all_reduce(input_: torch.Tensor) -> torch.Tensor:
"""All-reduce the input tensor across moe parallel group."""
return get_moe_tp_group().all_reduce(input_)
@@ -4,11 +4,16 @@ from torch.distributed import ProcessGroup
from sglang.srt.utils import is_npu
_is_npu = is_npu()
if _is_npu:
from torch_npu import npu_dynamic_quant
class NpuCommunicator:
def __init__(self, group: ProcessGroup):
if not is_npu():
if not _is_npu:
self.disabled = True
return
self.disabled = False
@@ -19,6 +24,33 @@ class NpuCommunicator:
dist.all_reduce(x, group=self.group)
return x
def quant_all_reduce(self, x: torch.Tensor) -> torch.Tensor:
"""
Note:
All reduce is split into All gather + reduce.
All gather is performed in low precision, but reduce in full precision.
"""
world_size = self.world_size
input_size = x.size()
output_size = (input_size[0] * world_size,) + input_size[1:]
x_q, scale = npu_dynamic_quant(x, dst_type=torch.int8)
# Allocate output tensor.
output_tensor = torch.empty(output_size, dtype=x_q.dtype, device=x.device)
output_scale = torch.empty(
output_size[:1], dtype=scale.dtype, device=scale.device
)
# All-gather.
dist.all_gather_into_tensor(output_tensor, x_q, group=self.group)
dist.all_gather_into_tensor(output_scale, scale, group=self.group)
output_tensor = output_tensor.to(x.dtype) * output_scale.unsqueeze(-1).to(
x.dtype
)
# Reshape
output_tensor = output_tensor.reshape((world_size,) + input_size)
return output_tensor.sum(dim=0)
def all_gather(self, x: torch.Tensor, dim: int = -1) -> torch.Tensor:
world_size = self.world_size
if dim < 0:
@@ -633,6 +633,20 @@ class GroupCoordinator:
inplace_all_reduce(input_, group_name=self.unique_name)
return input_
def quant_all_reduce(self, input_: torch.Tensor) -> torch.Tensor:
"""
User-facing quant-all-reduce function similar to all-reduce. (NPU support only)
"""
# Bypass the function if we are using only 1 GPU.
if self.world_size == 1:
return input_
if self.npu_communicator is not None and not self.npu_communicator.disabled:
return self.npu_communicator.quant_all_reduce(input_)
else:
inplace_all_reduce(input_, group_name=self.unique_name)
return input_
def fused_allreduce_rmsnorm(
self,
input_: torch.Tensor,
+12 -2
View File
@@ -22,6 +22,7 @@ import torch
from sglang.srt.distributed import (
attention_tensor_model_parallel_all_reduce,
attention_tensor_model_parallel_quant_all_reduce,
get_tensor_model_parallel_rank,
get_tensor_model_parallel_world_size,
get_tp_group,
@@ -1000,9 +1001,18 @@ class CommunicateWithAllReduceAndLayerNormFn:
handled = True
if not handled:
hidden_states = attention_tensor_model_parallel_all_reduce(
hidden_states
quantize_communications = (
not forward_batch.forward_mode.is_decode_or_idle()
and get_global_server_args().enable_quant_communications
)
if quantize_communications:
hidden_states = attention_tensor_model_parallel_quant_all_reduce(
hidden_states
)
else:
hidden_states = attention_tensor_model_parallel_all_reduce(
hidden_states
)
if _is_npu and context.cache is not None:
_ = prepare_weight_cache(hidden_states, context.cache)
hidden_states, residual = layernorm(hidden_states, residual)
+15 -2
View File
@@ -19,6 +19,7 @@ from sglang.srt.distributed import (
split_tensor_along_last_dim,
tensor_model_parallel_all_gather,
tensor_model_parallel_all_reduce,
tensor_model_parallel_quant_all_reduce,
)
from sglang.srt.distributed.device_communicators.pynccl_allocator import (
use_symmetric_memory,
@@ -37,6 +38,7 @@ from sglang.srt.layers.parameter import (
_ColumnvLLMParameter,
)
from sglang.srt.layers.utils import pad_or_narrow_weight
from sglang.srt.server_args import get_global_server_args
from sglang.srt.utils import get_bool_env_var, is_cpu, is_hip, is_npu, set_weight_attrs
if TYPE_CHECKING:
@@ -1509,7 +1511,7 @@ class RowParallelLinear(LinearBase):
# Fallback for parameters that don't accept additional args
param.load_row_parallel_weight(loaded_weight)
def forward(self, input_, skip_all_reduce=False):
def forward(self, input_, skip_all_reduce=False, forward_batch=None):
if self.input_is_parallel:
input_parallel = input_
else:
@@ -1536,7 +1538,18 @@ class RowParallelLinear(LinearBase):
if self.use_dp_attention_reduce:
output = get_attention_tp_group().all_reduce(output_parallel)
else:
output = tensor_model_parallel_all_reduce(output_parallel)
quantize_communications = (
(
not forward_batch.forward_mode.is_decode_or_idle()
and get_global_server_args().enable_quant_communications
)
if forward_batch is not None
else False
)
if quantize_communications:
output = tensor_model_parallel_quant_all_reduce(output_parallel)
else:
output = tensor_model_parallel_all_reduce(output_parallel)
else:
output = output_parallel
+1 -1
View File
@@ -676,7 +676,7 @@ class RowParallelLinearWithLoRA(BaseLayerWithLoRA):
)
return lora_output
def forward(self, input_: torch.Tensor, skip_all_reduce=False):
def forward(self, input_: torch.Tensor, skip_all_reduce=False, forward_batch=None):
if self.base_layer.input_is_parallel:
input_parallel = input_
else:
+6 -2
View File
@@ -91,13 +91,17 @@ class Qwen2MLP(nn.Module):
)
self.act_fn = SiluAndMul()
def forward(self, x):
def forward(
self,
x: torch.Tensor,
forward_batch: ForwardBatch = None,
) -> torch.Tensor:
if get_global_server_args().rl_on_policy_target is not None:
x = x.bfloat16()
gate_up, _ = self.gate_up_proj(x)
x = self.act_fn(gate_up)
x, _ = self.down_proj(x)
x, _ = self.down_proj(x, forward_batch=forward_batch)
return x
+1 -1
View File
@@ -419,7 +419,7 @@ class Qwen3DecoderLayer(nn.Module):
else None
),
)
hidden_states = self.mlp(hidden_states)
hidden_states = self.mlp(hidden_states, forward_batch=forward_batch)
if _is_npu and get_cmo_stream():
wait_cmo_stream()
hidden_states, residual = self.layer_communicator.postprocess_layer(
+21
View File
@@ -773,6 +773,9 @@ class ServerArgs:
# For forward hooks
forward_hooks: Optional[List[dict[str, Any]]] = None
# For communications compression
enable_quant_communications: Optional[bool] = False
# For msProbe
msprobe_dump_config: Optional[str] = None
@@ -6654,6 +6657,13 @@ class ServerArgs:
help="JSON-formatted forward hook specifications to attach to the model.",
)
parser.add_argument(
"--enable-quant-communications",
action="store_true",
default=False,
help="Enable INT8 quantization of TP communications (limited support).",
)
# For msProbe
parser.add_argument(
"--msprobe-dump-config",
@@ -6929,6 +6939,17 @@ class ServerArgs:
"When enabling two batch overlap, moe_a2a_backend cannot be 'none'."
)
# Check communications compression
if self.enable_quant_communications and self.tp_size == 1:
raise ValueError(
"Communications quantization is only used with tp_size != 1"
)
if self.enable_quant_communications and self.device != "npu":
raise ValueError(
"Communications quantization is only supported for NPU device"
)
if (
self.enable_grpc
and self.grpc_port is not None
@@ -0,0 +1,37 @@
import unittest
from sglang.test.ascend.gsm8k_ascend_mixin import GSM8KAscendMixin
from sglang.test.ascend.test_ascend_utils import LLAMA_2_7B_WEIGHTS_PATH
from sglang.test.ci.ci_register import register_npu_ci
from sglang.test.test_utils import CustomTestCase
register_npu_ci(est_time=400, suite="nightly-2-npu-a3")
class TestLlama(GSM8KAscendMixin, CustomTestCase):
"""Testcase: Verify that the inference accuracy of the LLM-Research/Llama-2-7B model on the GSM8K dataset with tp communications quantization is no less than 0.18.
[Test Category] Model
[Test Target] LLM-Research/Llama-2-7B
"""
model = LLAMA_2_7B_WEIGHTS_PATH
accuracy = 0.18
other_args = [
"--trust-remote-code",
"--mem-fraction-static",
0.8,
"--max-running-requests",
32,
"--attention-backend",
"ascend",
"--cuda-graph-max-bs",
32,
"--tp-size",
2,
"--enable-quant-communications",
]
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,37 @@
import unittest
from sglang.test.ascend.gsm8k_ascend_mixin import GSM8KAscendMixin
from sglang.test.ascend.test_ascend_utils import QWEN3_8B_WEIGHTS_PATH
from sglang.test.ci.ci_register import register_npu_ci
from sglang.test.test_utils import CustomTestCase
register_npu_ci(est_time=400, suite="nightly-2-npu-a3")
class TestQwen38BCommQuantization(GSM8KAscendMixin, CustomTestCase):
"""Testcase: Verify that the inference accuracy of the Qwen/Qwen3-8B model with TP communications quantization on the GSM8K dataset is no less than 0.85.
[Test Category] Model
[Test Target] Qwen/Qwen3-8B
"""
model = QWEN3_8B_WEIGHTS_PATH
accuracy = 0.85
other_args = [
"--trust-remote-code",
"--mem-fraction-static",
0.8,
"--max-running-requests",
32,
"--attention-backend",
"ascend",
"--cuda-graph-max-bs",
32,
"--tp-size",
2,
"--enable-quant-communications",
]
if __name__ == "__main__":
unittest.main()