Skip to content

Commit b5d1da8

Browse files
committed
Add fp16 support for GPT2
1 parent 099f0d4 commit b5d1da8

7 files changed

Lines changed: 107 additions & 21 deletions

File tree

dist_ir/backend/torch.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from time import perf_counter
77
from traceback import print_exc
88
from typing import Any, Dict, Iterable, List, NamedTuple, Sequence, Tuple
9+
from warnings import warn
910

1011
import torch
1112
import torch.distributed as dist
@@ -238,8 +239,7 @@ def _slice(x, starts, ends, axes, steps=None, ctx=None):
238239

239240

240241
def _softmax(x, axis, ctx=None):
241-
exp = torch.exp(x)
242-
return exp / torch.sum(exp, dim=axis, keepdim=True)
242+
return torch.nn.functional.softmax(x, dim=axis)
243243

244244

245245
def _split(x, axis, split, ctx=None):
@@ -417,8 +417,12 @@ def print_memory_usage():
417417
assert isinstance(output, tuple)
418418
for i, v in enumerate(op.outputs):
419419
value_map[v] = output[i]
420+
if torch.any(torch.isnan(output[i])):
421+
warn(f"NaNs in op {op} output {i}")
420422
elif len(op.outputs) == 1:
421423
value_map[op.outputs[0]] = output
424+
if torch.any(torch.isnan(output)):
425+
warn(f"NaNs in op {op.name} output {0}")
422426

423427
# Free tensors that are not used again
424428
for v in op.inputs:

dist_ir/executor/numpy_register.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import numpy as np
2+
import scipy
23

34

45
def _handle_negative_axis(axis, tensor_rank):
@@ -372,8 +373,7 @@ def slice_conc(op, x, starts, ends, axes, steps=None):
372373

373374
def softmax(op, x):
374375
axis = op.attributes["axis"]
375-
exp = np.exp(x)
376-
return exp / np.sum(exp, axis=axis, keepdims=True)
376+
return scipy.special.softmax(x, axis=axis)
377377

378378

379379
def softmax_grad(op, dY, Y):

examples/gpt2.py

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
ConcreteValue,
1414
)
1515
from dist_ir.importer import import_from_onnx
16-
from dist_ir.ir import FunctionMaker, Op, get_uniform_topology
17-
from dist_ir.ir.type import Tensor, Type, abstract_values
16+
from dist_ir.ir import FunctionMaker, Op, Value, get_uniform_topology
17+
from dist_ir.ir.type import Float16, Tensor, Type, abstract_values
1818
from dist_ir.transforms import (
1919
gpt2_dhp_transform,
2020
sanitize_unhashable_attributes,
@@ -31,6 +31,42 @@ def _to_numpy(x):
3131
return x
3232

3333

34+
def _cast_to_fp16(function):
35+
is_weight = lambda x: "weight" in x or "bias" in x
36+
37+
fp16_function = FunctionMaker(function.name)
38+
value_map = {}
39+
for i, inp in enumerate(function.inputs):
40+
if is_weight(inp.name):
41+
fp16_inp = fp16_function.add_input_value(
42+
inp.name,
43+
Tensor(
44+
shape=inp.type.shape,
45+
device=inp.type.device,
46+
dtype=Float16(device=inp.type.dtype.device),
47+
),
48+
)
49+
else:
50+
fp16_inp = fp16_function.add_input_value(inp.name, inp.type)
51+
value_map[inp] = fp16_inp
52+
for op in function.ops:
53+
inputs = [value_map[inp] for inp in op.inputs]
54+
fp16_op = Op(
55+
op_type=op.op_type,
56+
name=op.name,
57+
inputs=tuple(value_map[inp] for inp in op.inputs),
58+
attributes=op.attributes,
59+
subfunctions=op.subfunctions,
60+
output_names=tuple(output.name for output in op.outputs),
61+
output_types=tuple(None for output in op.outputs),
62+
# output_types=tuple(output.type for output in op.outputs),
63+
)
64+
fp16_function.ops.append(fp16_op)
65+
for output, fp16_output in zip(op.outputs, fp16_op.outputs):
66+
value_map[output] = fp16_output
67+
return fp16_function.finalize()
68+
69+
3470
def _filter_extra_outputs(function):
3571
function, attribute_map = sanitize_unhashable_attributes(function)
3672

@@ -349,8 +385,11 @@ def _get_stats(function):
349385
def import_function_and_get_input_data(
350386
model_path,
351387
default_device,
388+
dtype,
352389
use_real_weights=False,
353390
):
391+
is_input_or_weight = lambda x: "input" in x or "weight" in x or "bias" in x
392+
354393
function, input_data_map = import_from_onnx(
355394
model_path,
356395
name="GPT-2",
@@ -360,10 +399,15 @@ def import_function_and_get_input_data(
360399

361400
function = _filter_extra_outputs(function)
362401

363-
if not use_real_weights:
364-
for inp in input_data_map:
365-
if "input" in inp.name or "weight" in inp.name or "bias" in inp.name:
402+
if dtype == "fp16":
403+
function = _cast_to_fp16(function)
404+
405+
for inp in input_data_map:
406+
if is_input_or_weight(inp.name):
407+
if not use_real_weights:
366408
input_data_map[inp] = inp.type
409+
elif dtype == "fp16" and "input" not in inp.name:
410+
input_data_map[inp] = input_data_map[inp].astype(np.float16)
367411
input_data = list(input_data_map.values())
368412

369413
return function, input_data
@@ -488,6 +532,7 @@ def transform(
488532

489533
def get_transformed_function_and_input_data(
490534
model_path,
535+
dtype,
491536
device_throughput,
492537
dram_bandwidth,
493538
kernel_launch_overhead,
@@ -515,6 +560,7 @@ def get_transformed_function_and_input_data(
515560
function, input_data = import_function_and_get_input_data(
516561
model_path,
517562
default_device=topology.devices[0],
563+
dtype=dtype,
518564
use_real_weights=use_real_weights,
519565
)
520566

@@ -554,7 +600,9 @@ def simulate(function, input_data, topology, allreduce_parameters=None):
554600
return simulation
555601

556602

557-
def run_pytorch(function, input_data, world_size, use_gpu=True, debug_stacktrace=False):
603+
def run_pytorch(
604+
function, input_data, world_size, use_gpu=False, debug_stacktrace=False
605+
):
558606
# TODO: Move this to a utils file
559607
def _resolve_dtype(dtype):
560608
if dtype == np.int32:
@@ -618,6 +666,7 @@ def main(args):
618666
topology,
619667
) = get_transformed_function_and_input_data(
620668
args.model_path,
669+
args.dtype,
621670
args.device_throughput,
622671
args.dram_bandwidth,
623672
args.kernel_launch_overhead,

examples/gpt2_grid_search.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ def __init__(
5353
self.base_model, self.base_input_data = gpt2.import_function_and_get_input_data(
5454
self.model_path,
5555
self.topology.devices[0],
56+
self.dtype,
5657
use_real_weights=(self.backend == "pytorch"),
5758
)
5859
self.models_and_input_data = {}

examples/parser.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def add_backend_config_arguments(self):
9090
self.add_argument(
9191
"--use_gpu",
9292
action="store_true",
93-
default=torch.cuda.is_available(),
93+
default=False,
9494
help="Use GPU with PyTorch backend",
9595
)
9696

test/test_gpt2_dhp_transform.py

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717

1818
def _run_gpt(
19+
dtype="fp32",
1920
device_throughput=constants.DEFAULT_DEVICE_THROUGHPUT,
2021
dram_bandwidth=constants.DEFAULT_DRAM_BANDWIDTH,
2122
kernel_launch_overhead=constants.DEFAULT_KERNEL_LAUNCH_OVERHEAD,
@@ -38,6 +39,7 @@ def _run_gpt(
3839
topology,
3940
) = get_transformed_function_and_input_data(
4041
MODEL_PATH,
42+
dtype,
4143
device_throughput,
4244
dram_bandwidth,
4345
kernel_launch_overhead,
@@ -77,6 +79,7 @@ def _run_gpt(
7779

7880
def _test(
7981
original_outputs,
82+
dtype,
8083
dp_degree=1,
8184
hp_degree=1,
8285
pp_degree=1,
@@ -86,6 +89,7 @@ def _test(
8689

8790
# Test with real weights
8891
transformed_outputs = _run_gpt(
92+
dtype=dtype,
8993
dp_degree=dp_degree,
9094
hp_degree=hp_degree,
9195
pp_degree=pp_degree,
@@ -95,13 +99,18 @@ def _test(
9599
assert len(transformed_outputs) == dp_degree * hp_degree
96100
for i in range(len(transformed_outputs)):
97101
np.testing.assert_array_almost_equal(
98-
original_outputs[0].val, transformed_outputs[i].val, decimal=2
102+
original_outputs[0].val,
103+
transformed_outputs[i].val,
104+
decimal=(2 if dtype == "fp32" else 1),
99105
)
100106

101107

102108
@pytest.fixture(scope="session")
103109
def original_outputs():
104-
return _run_gpt()
110+
return {
111+
"fp16": _run_gpt(dtype="fp16", use_pytorch_backend=True),
112+
"fp32": _run_gpt(dtype="fp32", use_pytorch_backend=True),
113+
}
105114

106115

107116
@pytest.mark.parametrize(
@@ -110,7 +119,8 @@ def original_outputs():
110119
)
111120
def test_reference_execution(original_outputs, dp_degree, hp_degree, pp_degree):
112121
_test(
113-
original_outputs,
122+
original_outputs["fp32"],
123+
dtype="fp32",
114124
dp_degree=dp_degree,
115125
hp_degree=hp_degree,
116126
pp_degree=pp_degree,
@@ -119,12 +129,23 @@ def test_reference_execution(original_outputs, dp_degree, hp_degree, pp_degree):
119129

120130

121131
@pytest.mark.parametrize(
122-
("dp_degree", "hp_degree", "pp_degree"),
123-
list(itertools.product([1, 2], [1, 2], [1, 2])),
132+
("dtype", "dp_degree", "hp_degree", "pp_degree"),
133+
list(
134+
itertools.product(
135+
["fp16", "fp32"] if torch.cuda.is_available() else ["fp32"],
136+
[1, 2],
137+
[1, 2],
138+
[1, 2],
139+
)
140+
),
124141
)
125-
def test_pytorch_backend(original_outputs, dp_degree, hp_degree, pp_degree):
142+
def test_pytorch_backend(original_outputs, dtype, dp_degree, hp_degree, pp_degree):
143+
world_size = dp_degree * hp_degree * pp_degree
144+
if dtype == "fp16" and world_size > torch.cuda.device_count():
145+
pytest.skip("Not enough GPUs available")
126146
_test(
127-
original_outputs,
147+
original_outputs[dtype],
148+
dtype,
128149
dp_degree=dp_degree,
129150
hp_degree=hp_degree,
130151
pp_degree=pp_degree,
@@ -134,14 +155,24 @@ def test_pytorch_backend(original_outputs, dp_degree, hp_degree, pp_degree):
134155

135156

136157
@pytest.mark.parametrize(
137-
("dp_degree", "hp_degree", "pp_degree"),
138-
list(itertools.product([1, 2], [1, 2], [1, 2])),
158+
("dtype", "dp_degree", "hp_degree", "pp_degree"),
159+
list(itertools.product(["fp16", "fp32"], [1, 2], [1, 2], [1, 2])),
139160
)
140-
def test_mixed_simulation(dp_degree, hp_degree, pp_degree):
161+
def test_mixed_simulation(dtype, dp_degree, hp_degree, pp_degree):
141162
_run_gpt(
163+
dtype=dtype,
142164
dp_degree=dp_degree,
143165
hp_degree=hp_degree,
144166
pp_degree=pp_degree,
145167
num_microbatches=pp_degree,
146168
use_real_weights=False,
147169
)
170+
171+
if __name__=="__main__":
172+
original_outputs = {
173+
"fp16": _run_gpt(dtype="fp16", use_pytorch_backend=True),
174+
"fp32": _run_gpt(dtype="fp32", use_pytorch_backend=True),
175+
}
176+
for dtype, dp_degree, hp_degree, pp_degree in list(itertools.product(["fp16", "fp32"], [1, 2], [1, 2], [1, 2])):
177+
print(f"dtype={dtype}, dp_degree={dp_degree}, hp_degree={hp_degree}, pp_degree={pp_degree}")
178+
test_pytorch_backend(original_outputs, dtype, dp_degree, hp_degree, pp_degree)

test/test_grid_search.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ def test_gpt_grid_search(backend, dtype):
156156
topology,
157157
) = gpt2.get_transformed_function_and_input_data(
158158
model_path=GPT2_MODEL_PATH,
159+
dtype=dtype,
159160
device_throughput=constants.DEFAULT_DEVICE_THROUGHPUT,
160161
dram_bandwidth=constants.DEFAULT_DRAM_BANDWIDTH,
161162
kernel_launch_overhead=constants.DEFAULT_KERNEL_LAUNCH_OVERHEAD,

0 commit comments

Comments
 (0)