Skip to content

Commit 63bc638

Browse files
authored
Add ctc_loss operator (flagos-ai#2723)
1 parent f0aebe1 commit 63bc638

10 files changed

Lines changed: 2171 additions & 21 deletions

File tree

benchmark/core_shapes.yaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@ single_dim_shapes: &single_dim_shapes
1010
vdot:
1111
<<: *single_dim_shapes
1212

13+
ctc_loss:
14+
shapes:
15+
- [64, 4, 32, 16]
16+
- [256, 16, 64, 48]
17+
- [512, 32, 64, 48]
18+
- [1024, 32, 128, 96]
19+
shape_desc: "T, N, C, S"
20+
21+
ctc_loss_backward:
22+
shapes:
23+
- [64, 4, 32, 16]
24+
- [256, 16, 64, 48]
25+
- [512, 32, 64, 48]
26+
- [1024, 32, 128, 96]
27+
shape_desc: "T, N, C, S"
28+
1329
randperm:
1430
<<: *single_dim_shapes
1531

benchmark/test_ctc_loss.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import pytest
2+
import torch
3+
import torch.nn.functional as F
4+
5+
import flag_gems
6+
7+
from . import base, consts
8+
9+
CTC_DTYPES = [torch.float32, torch.float16]
10+
11+
12+
def _ctc_loss_reference(
13+
log_probs,
14+
targets,
15+
input_lengths,
16+
target_lengths,
17+
blank=0,
18+
reduction="mean",
19+
zero_infinity=False,
20+
):
21+
original_dtype = log_probs.dtype
22+
work_log_probs = log_probs
23+
if work_log_probs.dtype in (torch.float16, torch.bfloat16):
24+
work_log_probs = work_log_probs.float()
25+
out = F.ctc_loss(
26+
work_log_probs,
27+
targets,
28+
input_lengths,
29+
target_lengths,
30+
blank=blank,
31+
reduction=reduction,
32+
zero_infinity=zero_infinity,
33+
)
34+
return out.to(original_dtype) if out.dtype != original_dtype else out
35+
36+
37+
def _make_targets(batch, max_target, classes, device, target_layout):
38+
target_lengths = torch.empty(batch, device=device, dtype=torch.long)
39+
padded = torch.zeros(batch, max_target, device=device, dtype=torch.long)
40+
pieces = []
41+
for row in range(batch):
42+
length = max(1, max_target - (row % 5))
43+
target_lengths[row] = length
44+
values = (torch.arange(length, device=device, dtype=torch.long) + row) % (
45+
classes - 1
46+
)
47+
values = values + 1
48+
padded[row, :length] = values
49+
pieces.append(values)
50+
51+
if target_layout == "padded":
52+
targets = padded
53+
else:
54+
targets = torch.cat(pieces)
55+
return targets, target_lengths
56+
57+
58+
def ctc_loss_input_fn(shape, dtype, device):
59+
t_steps, batch, classes, max_target = shape
60+
raw = torch.randn(t_steps, batch, classes, dtype=torch.float32, device=device)
61+
log_probs = raw.log_softmax(-1).to(dtype)
62+
input_lengths = torch.full((batch,), t_steps, dtype=torch.long, device=device)
63+
64+
targets, target_lengths = _make_targets(
65+
batch, max_target, classes, device, "padded"
66+
)
67+
yield (
68+
log_probs,
69+
targets,
70+
input_lengths,
71+
target_lengths,
72+
{"blank": 0, "reduction": "mean", "zero_infinity": False},
73+
)
74+
75+
targets, target_lengths = _make_targets(
76+
batch, max_target, classes, device, "concatenated"
77+
)
78+
yield (
79+
log_probs,
80+
targets,
81+
input_lengths,
82+
target_lengths,
83+
{"blank": 0, "reduction": "mean", "zero_infinity": False},
84+
)
85+
86+
if base.Config.bench_level.value == consts.BenchLevel.COMPREHENSIVE.value:
87+
yield (
88+
log_probs,
89+
targets,
90+
input_lengths,
91+
target_lengths,
92+
{"blank": 0, "reduction": "sum", "zero_infinity": False},
93+
)
94+
95+
96+
class CtcLossBenchmark(base.GenericBenchmark):
97+
DEFAULT_SHAPES = [
98+
(64, 4, 32, 16),
99+
(256, 16, 64, 48),
100+
(512, 32, 64, 48),
101+
(1024, 32, 128, 96),
102+
]
103+
DEFAULT_SHAPE_DESC = "T, N, C, S"
104+
105+
def set_more_shapes(self):
106+
return []
107+
108+
109+
@pytest.mark.ctc_loss
110+
def test_ctc_loss():
111+
bench = CtcLossBenchmark(
112+
op_name="ctc_loss",
113+
input_fn=ctc_loss_input_fn,
114+
torch_op=_ctc_loss_reference,
115+
dtypes=CTC_DTYPES,
116+
)
117+
bench.set_gems(flag_gems.ctc_loss)
118+
bench.run()
119+
120+
121+
@pytest.mark.ctc_loss
122+
def test_ctc_loss_backward():
123+
bench = CtcLossBenchmark(
124+
op_name="ctc_loss",
125+
input_fn=ctc_loss_input_fn,
126+
torch_op=_ctc_loss_reference,
127+
dtypes=CTC_DTYPES,
128+
is_backward=True,
129+
)
130+
bench.set_gems(flag_gems.ctc_loss)
131+
bench.run()

benchmark/test_trunc_divide.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import pytest
22
import torch
33

4-
from . import base, consts, utils
4+
from . import base, utils
55

66

77
def _binary_input_fn(shape, dtype, device):

src/flag_gems/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
registrar = Register
2323
current_work_registrar = None
2424
runtime.replace_customized_ops(globals())
25+
AUTOGRAD_DISPATCH_KEY = torch._C.DispatchKey.Autograd.name
2526

2627

2728
def torch_ge(v):
@@ -184,6 +185,8 @@ def torch_ge(v):
184185
("copysign", copysign),
185186
("copysign.out", copysign_out),
186187
("count_nonzero", count_nonzero),
188+
("ctc_loss.IntList", ctc_loss, None, (AUTOGRAD_DISPATCH_KEY,)),
189+
("ctc_loss.Tensor", ctc_loss, None, (AUTOGRAD_DISPATCH_KEY,)),
187190
("cudnn_convolution", cudnn_convolution),
188191
("cummax", cummax),
189192
("cummin", cummin),

src/flag_gems/ops/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
from flag_gems.ops.cos import cos, cos_
9191
from flag_gems.ops.cosh import cosh, cosh_, cosh_out
9292
from flag_gems.ops.count_nonzero import count_nonzero
93+
from flag_gems.ops.ctc_loss import ctc_loss
9394
from flag_gems.ops.cudnn_convolution import cudnn_convolution
9495
from flag_gems.ops.cummax import cummax
9596
from flag_gems.ops.cummin import cummin
@@ -495,6 +496,7 @@
495496
"cosh_",
496497
"cosh_out",
497498
"count_nonzero",
499+
"ctc_loss",
498500
"cudnn_convolution",
499501
"cummax",
500502
"cummin",

0 commit comments

Comments
 (0)