Skip to content

Commit 8c48f53

Browse files
author
w112009931-bit
committed
add
1 parent eaae33b commit 8c48f53

5 files changed

Lines changed: 382 additions & 1 deletion

File tree

benchmark_graph/consts.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,9 @@ def compile_options():
187187
((8, 8, 32, 32), 1, "AVG"),
188188
((8, 4, 16, 16), 1, "MUL"),
189189
)
190+
191+
192+
RMSNORM_RHT_AMAX_SHAPES = (
193+
(64, 2048, 2),
194+
(128, 4096, 4),
195+
)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import pytest
2+
import torch
3+
4+
import flag_dnn
5+
from benchmark_graph import consts
6+
from benchmark_graph.base import CudnnCompareBenchmark, get_cudnn
7+
8+
EPS = 1e-5
9+
10+
11+
class RmsNormRhtAmaxBenchmark(CudnnCompareBenchmark):
12+
op_name = "rmsnorm_rht_amax_wrapper_sm100"
13+
dtypes = (torch.bfloat16,)
14+
shapes = consts.RMSNORM_RHT_AMAX_SHAPES
15+
shape_ids_env = "FLAGDNN_CUDNN_RMSNORM_RHT_AMAX_PERF_SHAPE_IDS"
16+
17+
def make_inputs(self, case, dtype):
18+
m, n, rows_per_cta = case
19+
self.rows_per_cta = rows_per_cta
20+
x = torch.randn(
21+
(m, n), device=flag_dnn.device, dtype=dtype
22+
).contiguous()
23+
w = torch.randn((n,), device=flag_dnn.device, dtype=dtype).contiguous()
24+
return x, w
25+
26+
def build_cudnn_runner(self, inputs):
27+
cudnn = get_cudnn()
28+
try:
29+
wrapper = getattr(cudnn, "rmsnorm_rht_amax_wrapper_sm100")
30+
except (AttributeError, ImportError, RuntimeError) as exc:
31+
pytest.skip(
32+
f"cuDNN RMSNorm RHT amax wrapper is unavailable: {exc}"
33+
)
34+
x, w = inputs
35+
36+
def run():
37+
return wrapper(x, w, eps=EPS, rows_per_cta=self.rows_per_cta)
38+
39+
try:
40+
run()
41+
torch.cuda.synchronize()
42+
except (AssertionError, ImportError, RuntimeError, ValueError) as exc:
43+
pytest.skip(
44+
"cuDNN RMSNorm RHT amax wrapper is unsupported here: " f"{exc}"
45+
)
46+
return run
47+
48+
def build_flag_dnn_runner(self, inputs):
49+
x, w = inputs
50+
51+
@flag_dnn.graph
52+
def flag_dnn_rmsnorm_rht_amax_graph(x, w):
53+
return flag_dnn.rmsnorm_rht_amax_wrapper_sm100(
54+
x, w, eps=EPS, rows_per_cta=self.rows_per_cta
55+
)
56+
57+
compiled = flag_dnn.compile(
58+
flag_dnn_rmsnorm_rht_amax_graph,
59+
inputs=[
60+
flag_dnn.TensorSpec.from_tensor(x, "x"),
61+
flag_dnn.TensorSpec.from_tensor(w, "w"),
62+
],
63+
options=consts.compile_options(),
64+
)
65+
assert [node.op_type for node in compiled.graph.nodes] == [
66+
"rmsnorm_rht_amax_wrapper_sm100"
67+
]
68+
69+
def run():
70+
return compiled.run(x, w)
71+
72+
return run
73+
74+
def transfer_bytes(self, inputs):
75+
x, w = inputs
76+
num_ctas = x.shape[0] // self.rows_per_cta
77+
return (
78+
x.numel() * x.element_size()
79+
+ w.numel() * w.element_size()
80+
+ x.numel() * x.element_size()
81+
+ num_ctas * torch.empty((), dtype=torch.float32).element_size()
82+
)
83+
84+
def shape_detail(self, inputs):
85+
x, w = inputs
86+
return [x.size(), w.size(), self.rows_per_cta]
87+
88+
89+
@pytest.mark.cudnn_frontend
90+
@pytest.mark.rmsnorm_rht_amax
91+
@pytest.mark.graph
92+
@pytest.mark.perf
93+
@pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA is required")
94+
@pytest.mark.parametrize("dtype", RmsNormRhtAmaxBenchmark.dtypes)
95+
def test_perf_graph_rmsnorm_rht_amax_vs_cudnn_frontend(cudnn_handle, dtype):
96+
torch.manual_seed(0)
97+
RmsNormRhtAmaxBenchmark(cudnn_handle).run(dtype)

src/flag_dnn/graph/registry.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,27 @@ def _layernorm_shape(
931931
]
932932

933933

934+
_RMSNORM_RHT_AMAX_RPC_CANDIDATES = (2, 4, 8)
935+
_RMSNORM_RHT_AMAX_TARGET_MIN_CTAS = 148
936+
937+
938+
def _rmsnorm_rht_amax_pick_rows_per_cta(m: int) -> int:
939+
for rows_per_cta in reversed(_RMSNORM_RHT_AMAX_RPC_CANDIDATES):
940+
if m % rows_per_cta != 0:
941+
continue
942+
if m // rows_per_cta >= _RMSNORM_RHT_AMAX_TARGET_MIN_CTAS:
943+
return rows_per_cta
944+
return _RMSNORM_RHT_AMAX_RPC_CANDIDATES[0]
945+
946+
947+
def _squeeze_trailing_unit_spec_shape(
948+
shape: tuple[Any, ...], expected_rank: int
949+
) -> tuple[Any, ...]:
950+
if len(shape) == expected_rank + 1 and shape[-1] == 1:
951+
return shape[:-1]
952+
return shape
953+
954+
934955
def _rmsnorm_shape(
935956
input_specs: list[TensorSpec], attrs: dict[str, Any]
936957
) -> list[TensorSpec]:
@@ -950,6 +971,68 @@ def _rmsnorm_shape(
950971
]
951972

952973

974+
def _rmsnorm_rht_amax_shape(
975+
input_specs: list[TensorSpec], attrs: dict[str, Any]
976+
) -> list[TensorSpec]:
977+
inp = input_specs[0]
978+
weight = input_specs[1]
979+
x_shape = _squeeze_trailing_unit_spec_shape(tuple(inp.shape), 2)
980+
w_shape = _squeeze_trailing_unit_spec_shape(tuple(weight.shape), 1)
981+
if len(x_shape) != 2:
982+
raise RuntimeError(
983+
"rmsnorm_rht_amax_wrapper_sm100 x_tensor must be 2D"
984+
)
985+
if len(w_shape) != 1:
986+
raise RuntimeError(
987+
"rmsnorm_rht_amax_wrapper_sm100 w_tensor must be 1D"
988+
)
989+
m, n = x_shape
990+
if isinstance(n, int) and isinstance(w_shape[0], int) and w_shape[0] != n:
991+
raise RuntimeError(
992+
"rmsnorm_rht_amax_wrapper_sm100 w_tensor length must match "
993+
"x hidden dimension"
994+
)
995+
if isinstance(n, int) and n % 16 != 0:
996+
raise RuntimeError(
997+
"rmsnorm_rht_amax_wrapper_sm100 N must be divisible by 16"
998+
)
999+
1000+
rows_per_cta = attrs.get("rows_per_cta")
1001+
if rows_per_cta is None:
1002+
if not isinstance(m, int):
1003+
raise RuntimeError(
1004+
"rmsnorm_rht_amax_wrapper_sm100 requires concrete M when "
1005+
"rows_per_cta is omitted"
1006+
)
1007+
rows_per_cta = _rmsnorm_rht_amax_pick_rows_per_cta(m)
1008+
rows_per_cta = int(rows_per_cta)
1009+
if rows_per_cta <= 0:
1010+
raise RuntimeError(
1011+
"rmsnorm_rht_amax_wrapper_sm100 rows_per_cta must be positive"
1012+
)
1013+
if isinstance(m, int):
1014+
if m % rows_per_cta != 0:
1015+
raise RuntimeError(
1016+
"rmsnorm_rht_amax_wrapper_sm100 M must be divisible by "
1017+
"rows_per_cta"
1018+
)
1019+
amax_shape = (m // rows_per_cta,)
1020+
else:
1021+
amax_shape = (m,)
1022+
1023+
return [
1024+
TensorSpec(
1025+
name="o_tensor",
1026+
shape=x_shape,
1027+
dtype=inp.dtype,
1028+
layout="contiguous",
1029+
device=inp.device,
1030+
contiguous=True,
1031+
),
1032+
_float32_spec(amax_shape, inp.device).with_name("amax_tensor"),
1033+
]
1034+
1035+
9531036
def _batchnorm_shape(
9541037
input_specs: list[TensorSpec], attrs: dict[str, Any]
9551038
) -> list[TensorSpec]:
@@ -1068,6 +1151,51 @@ def _normalize_rmsnorm(
10681151
return input_ids, attrs
10691152

10701153

1154+
def _normalize_rmsnorm_rht_amax(
1155+
ctx: Any, args: tuple[Any, ...], kwargs: dict[str, Any]
1156+
) -> tuple[list[int], dict]:
1157+
names = ("x_tensor", "w_tensor")
1158+
if len(args) > len(names):
1159+
raise TypeError(
1160+
"rmsnorm_rht_amax_wrapper_sm100 got too many positional args"
1161+
)
1162+
params = dict(kwargs)
1163+
values = {}
1164+
for name, value in zip(names, args):
1165+
values[name] = value
1166+
for name in names[len(args) :]:
1167+
if name in params:
1168+
values[name] = params.pop(name)
1169+
missing = [name for name in names if name not in values]
1170+
if missing:
1171+
raise TypeError(f"rmsnorm_rht_amax_wrapper_sm100 missing {missing[0]}")
1172+
current_stream = params.pop("current_stream", None)
1173+
if current_stream is not None:
1174+
raise TypeError(
1175+
"rmsnorm_rht_amax_wrapper_sm100 current_stream is not supported "
1176+
"in graph capture"
1177+
)
1178+
attrs = {
1179+
"eps": float(params.pop("eps", 1e-5)),
1180+
"num_threads": params.pop("num_threads", None),
1181+
"rows_per_cta": params.pop("rows_per_cta", None),
1182+
"name": params.pop("name", ""),
1183+
}
1184+
if attrs["num_threads"] is not None:
1185+
attrs["num_threads"] = int(attrs["num_threads"])
1186+
if attrs["rows_per_cta"] is not None:
1187+
attrs["rows_per_cta"] = int(attrs["rows_per_cta"])
1188+
if params:
1189+
raise TypeError(
1190+
"rmsnorm_rht_amax_wrapper_sm100 got unsupported graph attrs: "
1191+
f"{sorted(params)}"
1192+
)
1193+
return [
1194+
ctx.as_value(values["x_tensor"], "x_tensor"),
1195+
ctx.as_value(values["w_tensor"], "w_tensor"),
1196+
], attrs
1197+
1198+
10711199
def _normalize_batchnorm(
10721200
ctx: Any, args: tuple[Any, ...], kwargs: dict[str, Any]
10731201
) -> tuple[list[int], dict]:
@@ -2416,6 +2544,21 @@ def run(inputs: list[Any], attrs: dict[str, Any]) -> Any:
24162544
return run
24172545

24182546

2547+
def _run_rmsnorm_rht_amax(flag_ops: Any) -> RunFn:
2548+
def run(inputs: list[Any], attrs: dict[str, Any]) -> Any:
2549+
_require_runtime_backend(inputs[:2], "rmsnorm_rht_amax_wrapper_sm100")
2550+
result = flag_ops.rmsnorm_rht_amax_wrapper_sm100(
2551+
inputs[0],
2552+
inputs[1],
2553+
eps=attrs.get("eps", 1e-5),
2554+
num_threads=attrs.get("num_threads"),
2555+
rows_per_cta=attrs.get("rows_per_cta"),
2556+
)
2557+
return result["o_tensor"], result["amax_tensor"]
2558+
2559+
return run
2560+
2561+
24192562
def _run_causal_conv1d(flag_ops: Any) -> RunFn:
24202563
def run(inputs: list[Any], attrs: dict[str, Any]) -> torch.Tensor:
24212564
_require_runtime_backend(inputs, "causal_conv1d")
@@ -2804,6 +2947,17 @@ def register_default_ops() -> None:
28042947
)
28052948
)
28062949

2950+
register_op(
2951+
OpSchema(
2952+
name="rmsnorm_rht_amax_wrapper_sm100",
2953+
normalize_fn=_normalize_rmsnorm_rht_amax,
2954+
shape_fn=_rmsnorm_rht_amax_shape,
2955+
run_fn=_run_rmsnorm_rht_amax(flag_ops),
2956+
num_outputs=2,
2957+
fusible=True,
2958+
)
2959+
)
2960+
28072961
register_op(
28082962
OpSchema(
28092963
name="matmul",

src/flag_dnn/graph/wrappers.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"batchnorm_inference",
5656
"layernorm",
5757
"rmsnorm",
58+
"rmsnorm_rht_amax_wrapper_sm100",
5859
"reduction",
5960
"sqrt",
6061
"square",
@@ -73,6 +74,11 @@
7374
)
7475

7576

77+
_DICT_OUTPUT_OPS = {
78+
"rmsnorm_rht_amax_wrapper_sm100": ("o_tensor", "amax_tensor"),
79+
}
80+
81+
7682
def install_graph_wrappers(namespace: dict[str, Any]) -> None:
7783
for op_type in GRAPH_AWARE_OPS:
7884
eager_fn = namespace.get(op_type)
@@ -98,7 +104,18 @@ def wrapper(*args: Any, **kwargs: Any) -> Any:
98104
raise RuntimeError(
99105
f"FlagDNN graph op {op_type} used outside graph capture"
100106
)
101-
return ctx.add_op_call(op_type, args, kwargs)
107+
outputs = ctx.add_op_call(op_type, args, kwargs)
108+
output_keys = _DICT_OUTPUT_OPS.get(op_type)
109+
if output_keys is not None:
110+
if not isinstance(outputs, tuple):
111+
outputs = (outputs,)
112+
if len(outputs) != len(output_keys):
113+
raise RuntimeError(
114+
f"FlagDNN graph op {op_type} returned "
115+
f"{len(outputs)} outputs, expected {len(output_keys)}"
116+
)
117+
return dict(zip(output_keys, outputs))
118+
return outputs
102119
return eager_fn(*args, **kwargs)
103120

104121
setattr(wrapper, "__flagdnn_graph_wrapped__", True)

0 commit comments

Comments
 (0)