Skip to content

Commit fda56bd

Browse files
authored
Merge branch 'main' into keep-bwd
2 parents 433880d + f2a1a3e commit fda56bd

103 files changed

Lines changed: 10011 additions & 3847 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/trigger-ci.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ jobs:
5858
|| github.actor == 'vthumbe1503'
5959
|| github.actor == 'shengfangd'
6060
|| github.actor == 'kainzhong'
61+
|| github.actor == 'cspades'
62+
|| github.actor == 'jomitchellnv'
6163
)
6264
steps:
6365
- name: Check if comment is issued by authorized person

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,4 @@ compile_commands.json
4242
tensor_dumps/
4343
artifacts/
4444
.DS_Store
45+
.claude/

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ repos:
4040
files: ^transformer_engine.*\.(c|cc|cxx|cpp|cu|cuh|h|hpp)$
4141

4242
- repo: https://github.com/netromdk/vermin
43-
rev: c75aca72f4e85c6e47252139e8695f1c8b5f9ae3
43+
rev: b70ff9611a01a2bf2f702aa537d14e71e330edba
4444
hooks:
4545
- id: vermin
4646
args: ['-t=3.10-', '--violations']

3rdparty/cudnn-frontend

Submodule cudnn-frontend updated 217 files
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
# Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
#
3+
# See LICENSE for license information.
4+
5+
import argparse
6+
import torch
7+
import torch.utils.benchmark as benchmark
8+
import pandas as pd
9+
10+
from transformer_engine.pytorch.module import Linear as TELinear
11+
from transformer_engine.common.recipe import (
12+
Float8BlockScaling,
13+
MXFP8BlockScaling,
14+
NVFP4BlockScaling,
15+
)
16+
from transformer_engine.pytorch.quantization import autocast, FP8GlobalStateManager
17+
from contextlib import nullcontext
18+
19+
"""
20+
# Profile BF16 recipe with Nsight Systems
21+
nsys profile \
22+
--output=./benchmarks/linear/b200_linear_bf16 \
23+
--force-overwrite true \
24+
--trace=cuda,nvtx,cudnn,cublas \
25+
python benchmarks/linear/benchmark_linear.py --profile --recipe bf16
26+
27+
# Profile FP8 sub-channel recipe with Nsight Systems
28+
nsys profile \
29+
--output=./benchmarks/linear/b200_linear_fp8_sub_channel \
30+
--force-overwrite true \
31+
--trace=cuda,nvtx,cudnn,cublas \
32+
python benchmarks/linear/benchmark_linear.py --profile --recipe fp8_sub_channel
33+
34+
# Profile MXFP8 recipe with Nsight Systems
35+
nsys profile \
36+
--output=./benchmarks/linear/b200_linear_mxfp8 \
37+
--force-overwrite true \
38+
--trace=cuda,nvtx,cudnn,cublas \
39+
python benchmarks/linear/benchmark_linear.py --profile --recipe mxfp8
40+
41+
# Profile NVFP4 recipe with Nsight Systems
42+
nsys profile \
43+
--output=./benchmarks/linear/b200_linear_nvfp4_rht_cast_fusion \
44+
--force-overwrite true \
45+
--trace=cuda,nvtx,cudnn,cublas \
46+
python benchmarks/linear/benchmark_linear.py --profile --recipe nvfp4
47+
48+
# Example to look at a single kernel target with NCU, like the fused hadamard amax kernel for NVFP4 recipe
49+
ncu -f -o ./benchmarks/linear/ncu_b200_linear_nvfp4_rht_cast_fusion \
50+
--set=full \
51+
--kernel-name "row_col_rht_gemm_device" \
52+
-s 5 -c 5 \
53+
python benchmarks/linear/benchmark_linear.py --profile --recipe nvfp4
54+
55+
"""
56+
57+
RECIPES = {
58+
"bf16": None,
59+
"fp8_sub_channel": Float8BlockScaling(),
60+
"mxfp8": MXFP8BlockScaling(),
61+
"nvfp4": NVFP4BlockScaling(),
62+
}
63+
64+
mxfp8_available, reason_for_no_mxfp8 = FP8GlobalStateManager.is_mxfp8_available()
65+
fp8_block_scaling_available, reason_for_no_fp8_block_scaling = (
66+
FP8GlobalStateManager.is_fp8_block_scaling_available()
67+
)
68+
nvfp4_available, reason_for_no_nvfp4 = FP8GlobalStateManager.is_nvfp4_available()
69+
70+
71+
def run_linear_multiple_steps(layer, x, mode, gradient, run_num_steps=1, recipe=None):
72+
assert mode in ["fwd_only", "fwd_bwd"]
73+
quantization_context = (
74+
autocast(enabled=True, recipe=recipe) if recipe is not None else nullcontext()
75+
)
76+
77+
if mode == "fwd_only":
78+
with torch.no_grad(), quantization_context:
79+
for i in range(run_num_steps):
80+
y_q = layer.forward(
81+
x,
82+
is_first_microbatch=(i == 0),
83+
)
84+
return y_q
85+
else:
86+
# reset gradients
87+
layer.zero_grad()
88+
x.grad = None
89+
90+
with quantization_context:
91+
for i in range(run_num_steps):
92+
label = f"step_{i}"
93+
torch.cuda.nvtx.range_push(label)
94+
y_q = layer.forward(
95+
x,
96+
is_first_microbatch=(i == 0),
97+
)
98+
y_q.backward(gradient)
99+
torch.cuda.nvtx.range_pop()
100+
101+
grads_q = []
102+
grads_q.append(x.grad)
103+
# remaining derivatives are in respect to model parameters
104+
for p in layer.parameters():
105+
if p.requires_grad:
106+
grads_q.append(p.grad)
107+
108+
return y_q, grads_q
109+
110+
111+
def benchmark_linear(
112+
x,
113+
w,
114+
bias,
115+
recipe_name,
116+
mode,
117+
):
118+
params_dtype = torch.bfloat16
119+
recipe = RECIPES[recipe_name]
120+
121+
in_features = x.shape[1]
122+
out_features = w.shape[0]
123+
gradient = torch.ones((x.shape[0], out_features), dtype=torch.bfloat16, device=x.device)
124+
125+
layer = TELinear(
126+
in_features,
127+
out_features,
128+
bias=bias is not None,
129+
params_dtype=params_dtype,
130+
)
131+
132+
layer = layer.to("cuda")
133+
with torch.no_grad():
134+
layer.weight.copy_(w)
135+
if bias is not None:
136+
layer.bias.copy_(bias)
137+
138+
num_microbatches = 32
139+
140+
label = f"{recipe_name}_{'linear'}"
141+
torch.cuda.nvtx.range_push(label)
142+
timing = benchmark.Timer(
143+
stmt="run_linear_multiple_steps(layer, x, mode, gradient, num_microbatches, recipe)",
144+
globals={
145+
"run_linear_multiple_steps": run_linear_multiple_steps,
146+
"layer": layer,
147+
"x": x,
148+
"mode": mode,
149+
"gradient": gradient,
150+
"num_microbatches": num_microbatches,
151+
"recipe": recipe,
152+
},
153+
num_threads=1,
154+
).blocked_autorange(min_run_time=10)
155+
print(f"{recipe_name}: {timing} \n")
156+
timing_ms = timing.median * 1000 / num_microbatches
157+
158+
return timing_ms
159+
160+
161+
def run_benchmark_linear(mkns, recipe_name, use_bias, fwd_only=False):
162+
data = []
163+
assert not use_bias, "Bias is not supported in this benchmark script"
164+
165+
print(f"========== Benchmarking {recipe_name} ==========")
166+
for m, k, n in mkns:
167+
device = "cuda"
168+
x = torch.randn((m, k), dtype=torch.bfloat16, device=device, requires_grad=True)
169+
w = torch.randn((n, k), dtype=torch.bfloat16, device=device)
170+
bias = None
171+
172+
# Run the benchmark
173+
print(f"fwd_m={m}, fwd_k={k}, fwd_n={n}")
174+
print(f"fwd_only: {fwd_only}")
175+
176+
linear_fwd_bwd_timing_ms = benchmark_linear(
177+
x,
178+
w,
179+
bias,
180+
recipe_name,
181+
mode="fwd_only" if fwd_only else "fwd_bwd",
182+
)
183+
184+
# Append the results
185+
data.append(
186+
[
187+
m,
188+
k,
189+
n,
190+
recipe_name,
191+
linear_fwd_bwd_timing_ms,
192+
]
193+
)
194+
195+
timing_notation = "linear_fwd_time_ms" if fwd_only else "linear_fwd_bwd_time_ms"
196+
197+
df = pd.DataFrame(
198+
data=data,
199+
columns=[
200+
"m",
201+
"k",
202+
"n",
203+
"recipe",
204+
timing_notation,
205+
],
206+
)
207+
208+
print(df, "\n")
209+
return df
210+
211+
212+
if __name__ == "__main__":
213+
214+
parser = argparse.ArgumentParser()
215+
parser.add_argument("--profile", action="store_true", help="Enable profiling mode")
216+
parser.add_argument(
217+
"--output-dir",
218+
type=str,
219+
default="benchmark_output/",
220+
help="output path for report",
221+
)
222+
# arguments for recipe, options are fp8_sub_channel, mxfp8, bf16, all
223+
parser.add_argument(
224+
"--recipe",
225+
type=str,
226+
default="bf16",
227+
help="Recipe to use, options are fp8_sub_channel, mxfp8, bf16, or all",
228+
)
229+
parser.add_argument(
230+
"--token-dim",
231+
type=int,
232+
default=None,
233+
help="Token dimension to use, calculated by SEQ_LEN * MBS / TP_SIZE",
234+
)
235+
parser.add_argument(
236+
"--hidden-dim",
237+
type=int,
238+
default=None,
239+
help="Hidden dimension to use",
240+
)
241+
parser.add_argument(
242+
"--output-dim",
243+
type=int,
244+
default=None,
245+
help="Output dimension to use",
246+
)
247+
parser.add_argument(
248+
"--fwd-only",
249+
action="store_true",
250+
default=False,
251+
help="Run forward pass only, default is both forward and backward passes",
252+
)
253+
args = parser.parse_args()
254+
255+
use_bias = False
256+
257+
token_dim_list = [16384]
258+
hidden_dim_list = [4096]
259+
output_dim_list = [4096]
260+
261+
if args.token_dim is not None:
262+
token_dim_list = [args.token_dim]
263+
264+
if args.hidden_dim is not None:
265+
hidden_dim_list = [args.hidden_dim]
266+
267+
if args.output_dim is not None:
268+
output_dim_list = [args.output_dim]
269+
270+
# MKN for linear
271+
mkns = []
272+
for m in token_dim_list:
273+
for k in hidden_dim_list:
274+
for n in output_dim_list:
275+
mkns.append((m, k, n))
276+
277+
# default recipes to run if not specified
278+
recipe_list = ["bf16"]
279+
280+
if args.recipe == "all":
281+
recipe_list = ["bf16", "fp8_sub_channel", "mxfp8", "nvfp4"]
282+
else:
283+
recipe_list = [args.recipe]
284+
285+
profiler_ctx = None
286+
if args.profile:
287+
hidden_dim_to_profile = 4096 if args.hidden_dim is None else args.hidden_dim
288+
output_dim_to_profile = 4096 if args.output_dim is None else args.output_dim
289+
token_dim_to_profile = 16384 if args.token_dim is None else args.token_dim
290+
mkns = [(token_dim_to_profile, hidden_dim_to_profile, output_dim_to_profile)]
291+
# in profile mode, only run one recipe specified in args.recipe
292+
assert args.recipe != "all", (
293+
"In profile mode, only one recipe can be specified, please specify the recipe as"
294+
" fp8_sub_channel, mxfp8, nvfp4, or bf16"
295+
)
296+
recipe_list = [args.recipe]
297+
profiler_ctx = torch.autograd.profiler.emit_nvtx(record_shapes=True)
298+
profiler_ctx.__enter__()
299+
300+
# Initialize a dataframe to store the results
301+
df_linears = pd.DataFrame()
302+
303+
# Run the fp8 benchmarks
304+
for recipe_name in recipe_list:
305+
assert recipe_name in [
306+
"bf16",
307+
"fp8_sub_channel",
308+
"mxfp8",
309+
"nvfp4",
310+
], "Recipe must be one of bf16, fp8_sub_channel, mxfp8, or nvfp4"
311+
if recipe_name == "mxfp8" and not mxfp8_available:
312+
print(f"MXFP8 is not available, skipping {recipe_name}")
313+
continue
314+
if recipe_name == "fp8_sub_channel" and not fp8_block_scaling_available:
315+
print(f"FP8 block scaling is not available, skipping {recipe_name}")
316+
continue
317+
if recipe_name == "nvfp4" and not nvfp4_available:
318+
print(f"NVFP4 is not available, skipping {recipe_name}")
319+
continue
320+
321+
df = run_benchmark_linear(
322+
mkns,
323+
recipe_name,
324+
use_bias,
325+
fwd_only=args.fwd_only,
326+
)
327+
df_linears = pd.concat([df_linears, df])
328+
329+
print(df_linears)
330+
331+
if args.profile:
332+
profiler_ctx.__exit__(None, None, None)

build_tools/VERSION.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
2.14.0.dev0
1+
2.15.0.dev0

docs/debug/3_api_features.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ Debug features
1414
.. autoapiclass:: transformer_engine.debug.features.per_tensor_scaling.PerTensorScaling
1515
.. autoapiclass:: transformer_engine.debug.features.fake_quant.FakeQuant
1616
.. autoapiclass:: transformer_engine.debug.features.disable_fp8_gemm.DisableFP8GEMM
17-
.. autoapiclass:: transformer_engine.debug.features.disable_fp8_layer.DisableFP8Layer
17+
.. autoapiclass:: transformer_engine.debug.features.disable_fp8_layer.DisableFP8Layer
18+
.. autoapiclass:: transformer_engine.debug.features.dump_tensors.DumpTensors

0 commit comments

Comments
 (0)