Skip to content

Commit 28c38ad

Browse files
[KMCompiler][Ascend] Add operator linalg_lu_factor for ascend backend. (flagos-ai#5382)
1 parent cefb52b commit 28c38ad

5 files changed

Lines changed: 1136 additions & 47 deletions

File tree

benchmark/core_shapes.yaml

Lines changed: 0 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -65,42 +65,6 @@ svd:
6565
- [256, 256]
6666
shape_desc: "(*B), M, N"
6767

68-
linalg_lu_factor:
69-
shapes:
70-
- [16, 16]
71-
- [32, 32]
72-
- [64, 64]
73-
- [128, 128]
74-
- [256, 256]
75-
- [1024, 512]
76-
- [32, 16]
77-
- [16, 32]
78-
- [128, 64]
79-
- [64, 128]
80-
- [4, 32, 32]
81-
- [128, 16, 16]
82-
- [1024, 512, 512]
83-
- [4096, 512, 512]
84-
shape_desc: "input shape"
85-
86-
linalg_lu_factor_out:
87-
shapes:
88-
- [16, 16]
89-
- [32, 32]
90-
- [64, 64]
91-
- [128, 128]
92-
- [256, 256]
93-
- [1024, 512]
94-
- [32, 16]
95-
- [16, 32]
96-
- [128, 64]
97-
- [64, 128]
98-
- [4, 32, 32]
99-
- [128, 16, 16]
100-
- [1024, 512, 512]
101-
- [4096, 512, 512]
102-
shape_desc: "input shape"
103-
10468
segment_reduce: &segment_reduce_shapes
10569
shapes:
10670
- [1048576]

benchmark/test_linalg_lu_factor.py

Lines changed: 139 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
from collections import namedtuple
2+
13
import pytest
24
import torch
35

46
import flag_gems
57

6-
from . import base
8+
from . import base, consts
9+
from .conftest import Config
710

811
DEVICE = flag_gems.device
912
VENDOR = flag_gems.vendor_name
@@ -19,13 +22,141 @@
1922
else:
2023
_PIVOT_VALUES = [True]
2124

25+
if VENDOR == "ascend":
26+
Config.mode = consts.BenchMode.OPERATOR
27+
28+
LINALG_LU_FACTOR_SHAPE = [
29+
[16, 16],
30+
[32, 32],
31+
[64, 64],
32+
[128, 128],
33+
[256, 256],
34+
[1024, 512],
35+
[32, 16],
36+
[16, 32],
37+
[128, 64],
38+
[64, 128],
39+
[4, 32, 32],
40+
[128, 16, 16],
41+
[1024, 512, 512],
42+
]
43+
44+
LinalgLUFactorResult = namedtuple("LinalgLUFactorResult", ["LU", "pivots"])
45+
46+
47+
def _swap_rows(lu, i, pivot_row):
48+
*batch_shape, m, n = lu.shape
49+
device = lu.device
50+
51+
rows = torch.arange(m, device=device).expand(*batch_shape, -1)
52+
53+
mask_i = (rows == i).float().unsqueeze(-1)
54+
mask_p = (rows == pivot_row.unsqueeze(-1)).float().unsqueeze(-1)
55+
56+
row_i_vals = (lu * mask_i).sum(dim=-2, keepdim=True)
57+
row_p_vals = (lu * mask_p).sum(dim=-2, keepdim=True)
58+
59+
mask_i_full = mask_i.expand(*batch_shape, m, n)
60+
mask_p_full = mask_p.expand(*batch_shape, m, n)
61+
diff_ip = (row_p_vals - row_i_vals).expand(*batch_shape, m, n)
62+
diff_pi = (row_i_vals - row_p_vals).expand(*batch_shape, m, n)
63+
64+
lu = lu + mask_i_full * diff_ip
65+
lu = lu + mask_p_full * diff_pi
66+
return lu
67+
68+
69+
def _lu_factor_pivot(lu, m, n, k):
70+
*batch_shape, _, _ = lu.shape
71+
device = lu.device
72+
pivots = torch.empty((*batch_shape, k), dtype=torch.int32, device=device)
73+
74+
for i in range(k):
75+
col = lu[..., i:, i].abs()
76+
pivot_rel = torch.argmax(col, dim=-1)
77+
pivot_row = pivot_rel + i
78+
pivots[..., i] = (pivot_row + 1).to(torch.int32)
79+
80+
lu = _swap_rows(lu, i, pivot_row)
81+
82+
pivot_val = lu[..., i, i]
83+
lu[..., i + 1 :, i] = lu[..., i + 1 :, i] / pivot_val.unsqueeze(-1)
84+
85+
if i + 1 < m and i + 1 < n:
86+
l_col = lu[..., i + 1 :, i].unsqueeze(-1)
87+
u_row = lu[..., i : i + 1, i + 1 :]
88+
lu[..., i + 1 :, i + 1 :] = lu[..., i + 1 :, i + 1 :] - l_col @ u_row
89+
90+
return lu, pivots
91+
92+
93+
def _lu_factor_no_pivot(lu, m, n, k):
94+
*batch_shape, _, _ = lu.shape
95+
device = lu.device
96+
pivots = torch.empty((*batch_shape, k), dtype=torch.int32, device=device)
97+
98+
for i in range(k):
99+
pivots[..., i] = i + 1
100+
pivot_val = lu[..., i, i]
101+
lu[..., i + 1 :, i] = lu[..., i + 1 :, i] / pivot_val.unsqueeze(-1)
102+
103+
if i + 1 < m and i + 1 < n:
104+
l_col = lu[..., i + 1 :, i].unsqueeze(-1)
105+
u_row = lu[..., i : i + 1, i + 1 :]
106+
lu[..., i + 1 :, i + 1 :] = lu[..., i + 1 :, i + 1 :] - l_col @ u_row
107+
108+
return lu, pivots
109+
110+
111+
def ops_lu_factor(input, *, pivot=True):
112+
if input.dim() < 2:
113+
raise RuntimeError(
114+
"torch.linalg.lu_factor: Expected input to have at least 2 dimensions"
115+
)
116+
if input.dtype != torch.float32:
117+
raise NotImplementedError("Only float32 is supported")
118+
m, n = input.shape[-2], input.shape[-1]
119+
if m == 0 or n == 0:
120+
raise NotImplementedError("Empty matrices are not supported")
121+
if pivot not in (True, False):
122+
raise TypeError(f"pivot must be a bool, got {type(pivot)}")
123+
124+
input_contiguous = input.contiguous()
125+
m, n = input_contiguous.shape[-2], input_contiguous.shape[-1]
126+
k = min(m, n)
127+
lu = input_contiguous.clone()
128+
129+
if pivot:
130+
lu, pivots = _lu_factor_pivot(lu, m, n, k)
131+
else:
132+
lu, pivots = _lu_factor_no_pivot(lu, m, n, k)
133+
134+
return LinalgLUFactorResult(lu, pivots)
135+
136+
137+
if VENDOR == "ascend":
138+
139+
def _torch_lu_factor(inp, *, pivot=True, out=None):
140+
"""Vendor-aware wrapper: uses torch ops on Ascend, native on other vendors."""
141+
res = ops_lu_factor(inp, pivot=pivot)
142+
if out is not None:
143+
lu_out, piv_out = out
144+
lu_out.copy_(res.LU)
145+
piv_out.copy_(res.pivots)
146+
return lu_out, piv_out
147+
return res.LU, res.pivots
148+
149+
else:
150+
_torch_lu_factor = torch.linalg.lu_factor
151+
22152

23153
class LinalgLuFactorBenchmark(base.Benchmark):
24154
DEFAULT_SHAPE_DESC = "input shape, pivot"
25155
DEFAULT_DTYPES = _TEST_DTYPES
156+
# mode = base.Config.mode if VENDOR != "ascend" else consts.BenchMode.OPERATOR
26157

27158
def get_input_iter(self, dtype):
28-
for inp_shape in self.shapes:
159+
for inp_shape in LINALG_LU_FACTOR_SHAPE:
29160
inp_shape = tuple(inp_shape)
30161
for pivot in _PIVOT_VALUES:
31162
inp = torch.randn(inp_shape, dtype=dtype, device=self.device)
@@ -36,10 +167,10 @@ def get_input_iter(self, dtype):
36167
def test_linalg_lu_factor():
37168
bench = LinalgLuFactorBenchmark(
38169
op_name="linalg_lu_factor",
39-
torch_op=torch.linalg.lu_factor,
170+
torch_op=_torch_lu_factor,
171+
gems_op=flag_gems.linalg_lu_factor,
40172
dtypes=_TEST_DTYPES,
41173
)
42-
bench.set_gems(flag_gems.linalg_lu_factor)
43174
bench.run()
44175

45176

@@ -48,7 +179,7 @@ class LinalgLuFactorOutBenchmark(base.Benchmark):
48179
DEFAULT_DTYPES = _TEST_DTYPES
49180

50181
def get_input_iter(self, dtype):
51-
for inp_shape in self.shapes:
182+
for inp_shape in LINALG_LU_FACTOR_SHAPE:
52183
inp_shape = tuple(inp_shape)
53184
for pivot in _PIVOT_VALUES:
54185
k = min(inp_shape[-2], inp_shape[-1])
@@ -65,7 +196,9 @@ def get_input_iter(self, dtype):
65196
def test_linalg_lu_factor_out():
66197
bench = LinalgLuFactorOutBenchmark(
67198
op_name="linalg_lu_factor_out",
68-
torch_op=torch.linalg.lu_factor,
199+
torch_op=_torch_lu_factor,
69200
dtypes=_TEST_DTYPES,
70201
)
202+
if VENDOR == "ascend":
203+
bench.gems_op = flag_gems.linalg_lu_factor_out
71204
bench.run()

src/flag_gems/runtime/backend/_ascend/ops/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
from .index_select import index_select
5656
from .isin import isin
5757
from .linalg_lstsq import linalg_lstsq
58+
from .linalg_lu_factor import linalg_lu_factor, linalg_lu_factor_out
5859
from .linspace import linspace
5960
from .log_softmax import log_softmax, log_softmax_backward, log_softmax_out
6061
from .masked_fill import masked_fill, masked_fill_
@@ -159,6 +160,8 @@
159160
"index_select",
160161
"isin",
161162
"linalg_lstsq",
163+
"linalg_lu_factor",
164+
"linalg_lu_factor_out",
162165
"linspace",
163166
"log_softmax",
164167
"log_softmax_backward",

0 commit comments

Comments
 (0)