[Ascend]Support of piecewise graph compilation for prefill on NPU (#12287)

Co-authored-by: ronnie_zheng <zl19940307@163.com>
This commit is contained in:
Vladimir221
2025-12-11 16:10:07 +03:00
committed by GitHub
parent 5d804a3767
commit 27032cecd9
10 changed files with 289 additions and 40 deletions

View File

@@ -19,8 +19,9 @@ from sglang.srt.compilation.compilation_config import CompilationConfig
from sglang.srt.compilation.compilation_counter import compilation_counter
from sglang.srt.compilation.compiler_interface import EagerAdapter, InductorAdaptor
from sglang.srt.compilation.cuda_piecewise_backend import CUDAPiecewiseBackend
from sglang.srt.compilation.npu_piecewise_backend import NPUPiecewiseBackend
from sglang.srt.compilation.pass_manager import PostGradPassManager
from sglang.srt.utils.common import rank0_log
from sglang.srt.utils.common import is_npu, rank0_log
logger = logging.getLogger(__name__)
@@ -44,6 +45,32 @@ def make_compiler(config: CompilationConfig):
raise ValueError(f"Unknown compiler: {config.compiler}")
def make_backend(
graph: fx.GraphModule,
compile_config: CompilationConfig,
inductor_config: dict[str, Any],
graph_pool: Any,
piecewise_compile_index: int,
total_piecewise_compiles: int,
sym_shape_indices: list[int],
compiled_graph_for_general_shape: Callable,
sglang_backend,
):
backend_cls = CUDAPiecewiseBackend if not is_npu() else NPUPiecewiseBackend
return backend_cls(
graph,
compile_config,
inductor_config,
graph_pool,
piecewise_compile_index,
total_piecewise_compiles,
sym_shape_indices,
compiled_graph_for_general_shape,
sglang_backend,
)
class CompilerManager:
def __init__(
self,
@@ -302,7 +329,7 @@ class PiecewiseCompileInterpreter(torch.fx.Interpreter):
)
)
self.module.__dict__[target] = CUDAPiecewiseBackend(
self.module.__dict__[target] = make_backend(
submod,
self.compile_config,
self.inductor_config,

View File

@@ -3,35 +3,19 @@
import dataclasses
import logging
from contextlib import ExitStack
from typing import Any, Callable, Optional, Union
from typing import Any, Callable, Optional
from unittest.mock import patch
import torch
import torch.fx as fx
from sgl_kernel import weak_ref_tensor
from sglang.srt.compilation.compilation_config import CompilationConfig
from sglang.srt.compilation.compilation_counter import compilation_counter
from sglang.srt.compilation.weak_ref_tensor import weak_ref_tensors
logger = logging.getLogger(__name__)
def weak_ref_tensors(
tensors: Union[torch.Tensor, list[torch.Tensor], tuple[torch.Tensor]]
) -> Union[torch.Tensor, list[Any], tuple[Any], Any]:
"""
Convenience function to create weak references to tensors,
for single tensor, list of tensors or tuple of tensors.
"""
if isinstance(tensors, torch.Tensor):
return weak_ref_tensor(tensors)
if isinstance(tensors, list):
return [weak_ref_tensor(t) for t in tensors]
if isinstance(tensors, tuple):
return tuple(weak_ref_tensor(t) for t in tensors)
raise ValueError("Invalid type for tensors")
@dataclasses.dataclass
class ConcreteSizeEntry:
runtime_shape: int

View File

@@ -0,0 +1,109 @@
from contextlib import ExitStack
from typing import Any, Callable
from unittest.mock import patch
import torch
import torch.fx as fx
from sglang.srt.compilation.compilation_config import CompilationConfig
from sglang.srt.compilation.compilation_counter import compilation_counter
from sglang.srt.compilation.cuda_piecewise_backend import (
CUDAPiecewiseBackend,
weak_ref_tensors,
)
class NPUPiecewiseBackend(CUDAPiecewiseBackend):
def __init__(
self,
graph: fx.GraphModule,
compile_config: CompilationConfig,
inductor_config: dict[str, Any],
graph_pool: Any,
piecewise_compile_index: int,
total_piecewise_compiles: int,
sym_shape_indices: list[int],
compiled_graph_for_general_shape: Callable,
sglang_backend,
):
super().__init__(
graph,
compile_config,
inductor_config,
graph_pool,
piecewise_compile_index,
total_piecewise_compiles,
sym_shape_indices,
compiled_graph_for_general_shape,
sglang_backend,
)
def __call__(self, *args):
runtime_shape = args[self.sym_shape_indices[0]]
if runtime_shape not in self.concrete_size_entries:
# we don't need to do anything for this shape
return self.compiled_graph_for_general_shape(*args)
entry = self.concrete_size_entries[runtime_shape]
if entry.runnable is None:
entry.runnable = self.compiled_graph_for_general_shape
if entry.cudagraph is None:
if entry.num_finished_warmup < 1: # noqa
entry.num_finished_warmup += 1
return entry.runnable(*args)
if self.compile_config.get_enable_debug_mode():
input_addresses = [
x.data_ptr() for x in args if isinstance(x, torch.Tensor)
]
entry.input_addresses = input_addresses
npugraph = torch.npu.NPUGraph()
with ExitStack() as stack:
if not self.is_first_graph:
# during every model forward, we will capture
# many pieces of cudagraphs (roughly one per layer).
# running gc again and again across layers will
# make the cudagraph capture very slow.
# therefore, we only run gc for the first graph,
# and disable gc for the rest of the graphs.
stack.enter_context(patch("gc.collect", lambda: None))
stack.enter_context(patch("torch.npu.empty_cache", lambda: None))
# mind-exploding: carefully manage the reference and memory.
with torch.npu.graph(npugraph, pool=self.graph_pool):
# `output` is managed by pytorch's cudagraph pool
output = entry.runnable(*args)
if self.is_last_graph:
# by converting it to weak ref,
# the original `output` will immediately be released
# to save memory. It is only safe to do this for
# the last graph, because the output of the last graph
# will not be used by any other cuda graph.
output = weak_ref_tensors(output)
# here we always use weak ref for the output
# to save memory
entry.output = weak_ref_tensors(output)
entry.cudagraph = npugraph
compilation_counter.num_cudagraph_captured += 1
# important: we need to return the output, rather than
# the weak ref of the output, so that pytorch can correctly
# manage the memory during cuda graph capture
return output
if self.compile_config.get_enable_debug_mode():
# check if the input addresses are the same
new_input_addresses = [
x.data_ptr() for x in args if isinstance(x, torch.Tensor)
]
assert new_input_addresses == entry.input_addresses, (
"Input addresses for cudagraphs are different during replay."
f" Expected {entry.input_addresses}, got {new_input_addresses}"
)
entry.cudagraph.replay()
return entry.output

View File

@@ -0,0 +1,28 @@
from typing import Any, Union
import torch
from sglang.srt.utils.common import is_cuda, is_npu
if is_cuda():
from sgl_kernel import weak_ref_tensor
elif is_npu():
from torch_npu._C import _weak_ref_tensor as weak_ref_tensor
else:
raise NotImplementedError("weak_ref_tensor is implemented only for CUDA and NPU.")
def weak_ref_tensors(
tensors: Union[torch.Tensor, list[torch.Tensor], tuple[torch.Tensor]]
) -> Union[torch.Tensor, list[Any], tuple[Any], Any]:
"""
Convenience function to create weak references to tensors,
for single tensor, list of tensors or tuple of tensors.
"""
if isinstance(tensors, torch.Tensor):
return weak_ref_tensor(tensors)
if isinstance(tensors, list):
return [weak_ref_tensor(t) for t in tensors]
if isinstance(tensors, tuple):
return tuple(weak_ref_tensor(t) for t in tensors)
raise ValueError("Invalid type for tensors")