已合并
[feat][compilation]按torch版本兼容rms_norm/rope pattern的dtype cast分解 #519
zqxu创建于 8月18日
[feat][compilation]按torch版本兼容rms_norm/rope pattern的dtype cast分解 #519
已合并
zqxu创建于 8月18日
共 4 个文件变更+149-32
@@ -79,7 +79,54 @@ private:
79 79 
80 #if defined(__NPU_ARCH__) && (__NPU_ARCH__ == 3510)80 #if defined(__NPU_ARCH__) && (__NPU_ARCH__ == 3510)
81 __aicore__ inline void DoMulAddVfCall(const LocalTensor<float> &x, uint32_t heads);81 __aicore__ inline void DoMulAddVfCall(const LocalTensor<float> &x, uint32_t heads);
82- __simd_vf__ inline void DoMulAddVf(__ubuf__ float* xBuf, __ubuf__ float* weightBuf, __ubuf__ float* biasBuf, uint32_t heads);82+ __simd_vf__ static inline void DoMulAddVf(__ubuf__ float* xBuf, __ubuf__ float* weightBuf,
83+ __ubuf__ float* biasBuf, uint32_t heads, uint32_t alignedNormDim)
84+ {
85+ RegTensor<float> xRegTensor;
86+ RegTensor<float> weightRegTensor;
87+ RegTensor<float> biasRegTensor;
88+ 
89+ uint32_t oneRepeatSize = AscendC::GetVecLen() / sizeof(float);
90+ uint32_t repeatTimes = (alignedNormDim + oneRepeatSize - 1) / oneRepeatSize;
91+ 
92+ for (uint32_t i = 0; i < heads; ++i) {
93+ uint32_t len = alignedNormDim;
94+ __ubuf__ float* tempXBuf = xBuf + i * len;
95+ __ubuf__ float* tempWeightBuf = weightBuf;
96+ __ubuf__ float* tempBiasBuf = biasBuf;
97+ for (uint32_t j = 0; j < repeatTimes; ++j) {
98+ MaskReg maskReg = UpdateMask<float>(len);
99+ LoadAlign(xRegTensor, tempXBuf + j * oneRepeatSize);
100+ LoadAlign(weightRegTensor, tempWeightBuf + j * oneRepeatSize);
101+ LoadAlign(biasRegTensor, tempBiasBuf + j * oneRepeatSize);
102+ MulDstAdd(xRegTensor, weightRegTensor, biasRegTensor, maskReg);
103+ StoreAlign(tempXBuf + j * oneRepeatSize, xRegTensor, maskReg);
104+ }
105+ }
106+ }
107+ __aicore__ inline void DoMulVfCall(const LocalTensor<float> &x, uint32_t heads);
108+ __simd_vf__ static inline void DoMulVf(__ubuf__ float* xBuf, __ubuf__ float* weightBuf, uint32_t heads,
109+ uint32_t alignedNormDim)
110+ {
111+ RegTensor<float> xRegTensor;
112+ RegTensor<float> weightRegTensor;
113+ 
114+ uint32_t oneRepeatSize = AscendC::GetVecLen() / sizeof(float);
115+ uint32_t repeatTimes = (alignedNormDim + oneRepeatSize - 1) / oneRepeatSize;
116+ 
117+ for (uint32_t i = 0; i < heads; ++i) {
118+ uint32_t len = alignedNormDim;
119+ __ubuf__ float* tempXBuf = xBuf + i * len;
120+ __ubuf__ float* tempWeightBuf = weightBuf;
121+ for (uint32_t j = 0; j < repeatTimes; ++j) {
122+ MaskReg maskReg = UpdateMask<float>(len);
123+ LoadAlign(xRegTensor, tempXBuf + j * oneRepeatSize);
124+ LoadAlign(weightRegTensor, tempWeightBuf + j * oneRepeatSize);
125+ Mul(xRegTensor, xRegTensor, weightRegTensor, maskReg);
126+ StoreAlign(tempXBuf + j * oneRepeatSize, xRegTensor, maskReg);
127+ }
128+ }
129+ }
83 #endif130 #endif
84private:131private:
85 TQue<QuePosition::VECIN, DOUBLE_BUFFER> inQue_;132 TQue<QuePosition::VECIN, DOUBLE_BUFFER> inQue_;
@@ -128,38 +175,21 @@ __aicore__ inline void NormOperationForward<isTraining>::DoMulAdd(const LocalTen
128}175}
129 176 
130#if defined(__NPU_ARCH__) && (__NPU_ARCH__ == 3510)177#if defined(__NPU_ARCH__) && (__NPU_ARCH__ == 3510)
131- template <bool isTraining>
132- __simd_vf__ inline void NormOperationForward<isTraining>::DoMulAddVf(
133- __ubuf__ float* xBuf, __ubuf__ float* weightBuf, __ubuf__ float* biasBuf, uint32_t heads) {
134- RegTensor<float> xRegTensor;
135- RegTensor<float> weightRegTensor;
136- RegTensor<float> biasRegTensor;
137- 
138- uint32_t oneRepeatSize = AscendC::GetVecLen() / sizeof(float);
139- uint32_t repeatTimes = (this->alignedNormDim_ + oneRepeatSize - 1) / oneRepeatSize;
140- for (uint32_t i = 0; i < heads; ++i) {
141- uint32_t len = this->alignedNormDim_;
142- __ubuf__ float* tempXBuf = xBuf + i * len;
143- __ubuf__ float* tempWeightBuf = weightBuf;
144- __ubuf__ float* tempBiasBuf = biasBuf;
145- for (uint32_t j = 0; j < repeatTimes; ++j) {
146- MaskReg maskReg = UpdateMask<float>(len);
147- LoadAlign(xRegTensor, tempXBuf + j * oneRepeatSize);
148- LoadAlign(weightRegTensor, tempWeightBuf + j * oneRepeatSize);
149- LoadAlign(biasRegTensor, tempBiasBuf + j * oneRepeatSize);
150- MulDstAdd(xRegTensor, weightRegTensor, biasRegTensor, maskReg);
151- StoreAlign(tempXBuf + j * oneRepeatSize, xRegTensor, maskReg);
152- }
153- }
154- }
155- 
156 template <bool isTraining>178 template <bool isTraining>
157 __aicore__ inline void NormOperationForward<isTraining>::DoMulAddVfCall(const LocalTensor<float> &x, uint32_t heads)179 __aicore__ inline void NormOperationForward<isTraining>::DoMulAddVfCall(const LocalTensor<float> &x, uint32_t heads)
158 {180 {
159 __ubuf__ float* xBuf = (__ubuf__ float*)x.GetPhyAddr();181 __ubuf__ float* xBuf = (__ubuf__ float*)x.GetPhyAddr();
160 __ubuf__ float* weightBuf = (__ubuf__ float*)weight_.GetPhyAddr();182 __ubuf__ float* weightBuf = (__ubuf__ float*)weight_.GetPhyAddr();
161 __ubuf__ float* biasBuf = (__ubuf__ float*)bias_.GetPhyAddr();183 __ubuf__ float* biasBuf = (__ubuf__ float*)bias_.GetPhyAddr();
162- DoMulAddVf(xBuf, weightBuf, biasBuf, heads);184+ DoMulAddVf(xBuf, weightBuf, biasBuf, heads, this->alignedNormDim_);
185+ }
186+ 
187+ template <bool isTraining>
188+ __aicore__ inline void NormOperationForward<isTraining>::DoMulVfCall(const LocalTensor<float> &x, uint32_t heads)
189+ {
190+ __ubuf__ float* xBuf = (__ubuf__ float*)x.GetPhyAddr();
191+ __ubuf__ float* weightBuf = (__ubuf__ float*)weight_.GetPhyAddr();
192+ DoMulVf(xBuf, weightBuf, heads, this->alignedNormDim_);
163 }193 }
164#endif194#endif
165 195 
@@ -323,7 +353,11 @@ __aicore__ inline void NormOperationForward<isTraining>::DoRMSNorm(const LocalTe
323 Div(x, x, tmp0, size);353 Div(x, x, tmp0, size);
324 PipeBarrier<PIPE_V>();354 PipeBarrier<PIPE_V>();
325 if constexpr (normType == NormType::RMS_NORM_AFFINE) {355 if constexpr (normType == NormType::RMS_NORM_AFFINE) {
326- DoMul(x, heads);356+ #if defined(__NPU_ARCH__) && (__NPU_ARCH__ == 3510)
357+ DoMulVfCall(x, heads);
358+ #else
359+ DoMul(x, heads);
360+ #endif
327 }361 }
328 PipeBarrier<PIPE_V>();362 PipeBarrier<PIPE_V>();
329}363}
@@ -10,22 +10,34 @@
10# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.10# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
11# See the Mulan PSL v2 for more details.11# See the Mulan PSL v2 for more details.
12 12 
13+import re
14+ 
13import torch15import torch
16+ 
14from ..passes.register_pattern_to_pass import PatternBase17from ..passes.register_pattern_to_pass import PatternBase
15 18 
16if hasattr(torch.npu, "is_available"):19if hasattr(torch.npu, "is_available"):
17 npu_available = torch.npu.is_available()20 npu_available = torch.npu.is_available()
18if npu_available:21if npu_available:
19 import torch_npu22 import torch_npu
23+ 
20 import mindiesd24 import mindiesd
21 25 
26+_torch_version_tuple = tuple(int(x) for x in re.match(r"(\d+)\.(\d+)", torch.__version__).groups())
lijinxi
lijinxilijinxi8月19日

抽取公共逻辑,在后续整改https://gitcode.com/Ascend/MindIE-SD/issues/315

likedislike
27+# torch_npu >= 2.9 移除 Tensor.to 的 NPU 过适配(MR 30358)后,
28+# rms_norm 分解中的 dtype cast 由 npu._npu_dtype_cast 变为 aten._to_copy。
29+IS_TORCH_GE_29 = _torch_version_tuple >= (2, 9)
30+ 
22 31 
23def create(dtype, epsilon=1e-6):32def create(dtype, epsilon=1e-6):
24 if "2.6.0" in torch.__version__:33 if "2.6.0" in torch.__version__:
25 _dtype_cast_func = torch.ops.npu.npu_dtype_cast.default34 _dtype_cast_func = torch.ops.npu.npu_dtype_cast.default
26 _eps_in_bf16 = torch.tensor(epsilon, dtype=torch.bfloat16, device="cpu").item()35 _eps_in_bf16 = torch.tensor(epsilon, dtype=torch.bfloat16, device="cpu").item()
27 else:36 else:
28- _dtype_cast_func = torch.ops.npu._npu_dtype_cast.default37+ if IS_TORCH_GE_29:
38+ _dtype_cast_func = lambda x, dtype: torch.ops.aten._to_copy.default(x, dtype=dtype)
39+ else:
40+ _dtype_cast_func = torch.ops.npu._npu_dtype_cast.default
29 _eps_in_fp32 = torch.tensor(epsilon, dtype=torch.float32, device="cpu").item()41 _eps_in_fp32 = torch.tensor(epsilon, dtype=torch.float32, device="cpu").item()
30 42 
31 43 
@@ -10,21 +10,33 @@
10# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.10# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
11# See the Mulan PSL v2 for more details.11# See the Mulan PSL v2 for more details.
12 12 
13+import re
14+ 
13import torch15import torch
16+ 
14from ..passes.register_pattern_to_pass import PatternBase17from ..passes.register_pattern_to_pass import PatternBase
15 18 
16if hasattr(torch.npu, "is_available"):19if hasattr(torch.npu, "is_available"):
17 npu_available = torch.npu.is_available()20 npu_available = torch.npu.is_available()
18if npu_available:21if npu_available:
19 import torch_npu22 import torch_npu
23+ 
20 import mindiesd24 import mindiesd
21 25 
26+_torch_version_tuple = tuple(int(x) for x in re.match(r"(\d+)\.(\d+)", torch.__version__).groups())
27+# torch_npu >= 2.9 移除 Tensor.to 的 NPU 过适配(MR 30358)后,
28+# dtype cast 由 npu._npu_dtype_cast 变为 aten._to_copy。
29+IS_TORCH_GE_29 = _torch_version_tuple >= (2, 9)
30+ 
22 31 
23def create(dtype):32def create(dtype):
24 if "2.6.0" in torch.__version__:33 if "2.6.0" in torch.__version__:
25 _dtype_cast_func = torch.ops.npu.npu_dtype_cast.default34 _dtype_cast_func = torch.ops.npu.npu_dtype_cast.default
26 else:35 else:
27- _dtype_cast_func = torch.ops.npu._npu_dtype_cast.default36+ if IS_TORCH_GE_29:
37+ _dtype_cast_func = lambda x, dtype: torch.ops.aten._to_copy.default(x, dtype=dtype)
38+ else:
39+ _dtype_cast_func = torch.ops.npu._npu_dtype_cast.default
28 40 
29 41 
30 class RopePattern(PatternBase):42 class RopePattern(PatternBase):
@@ -51,8 +63,7 @@ def create(dtype):
51 cos_part = _dtype_cast_func(x, dtype) * cos63 cos_part = _dtype_cast_func(x, dtype) * cos
52 sin_part = _dtype_cast_func(x_rotated, dtype) * sin64 sin_part = _dtype_cast_func(x_rotated, dtype) * sin
53 x_out = cos_part + sin_part65 x_out = cos_part + sin_part
54- x_out.type_as(x)66+ return torch.ops.aten._to_copy.default(x_out, dtype=x.dtype)
55- return x_out
56 return func(x, cos, sin)67 return func(x, cos, sin)
57 68 
58 @staticmethod69 @staticmethod
@@ -0,0 +1,60 @@
1+import os
2+import unittest
3+ 
4+import torch
5+from torch.fx.experimental.proxy_tensor import make_fx
6+ 
7+from mindiesd.compilation.patterns.rms_norm_pattern import (
8+ IS_TORCH_GE_29,
9+)
10+from mindiesd.compilation.patterns.rms_norm_pattern import (
11+ create as create_rmsnorm_pattern,
12+)
13+from mindiesd.compilation.patterns.rope_pattern import create as create_rope_pattern
14+ 
15+ 
16+def _trace(pattern, args):
17+ return make_fx(pattern)(*args)
18+ 
19+ 
20+@unittest.skipIf(
21+ os.environ.get("MINDIE_TEST_MODE", "ALL") == "CPU",
22+ "Skip NPU-dependent tests when MINDIE_TEST_MODE is CPU.",
23+)
24+@unittest.skipUnless(IS_TORCH_GE_29, "dtype cast 适配分支仅对 torch >= 2.9 生效")
C
Cchangetheway8月19日

这里参考其他测试用例补充一下测试设备分类

likedislike
zqxu
8月19日 评论:
25+class TestPatternDtypeCastCompat(unittest.TestCase):
26+ """回归测试:torch_npu >= 2.9 移除 Tensor.to 的 NPU 过适配(MR 30358)后,
27+ AOT 分解中的 dtype cast 由 npu._npu_dtype_cast 变为 aten._to_copy。
28+ 本测试验证 pattern 期望图已适配为 _to_copy, 否则 RMSNorm/RoPE
29+ 融合在 torch 2.9+ 环境会全部失配。
30+ """
31+ 
32+ def test_rmsnorm_pattern_first_cast_is_to_copy(self):
33+ pattern_cls = create_rmsnorm_pattern(dtype=torch.bfloat16, epsilon=1e-6)
34+ hidden_states = torch.empty(2, 2, 2, 2, dtype=torch.bfloat16, device="meta")
35+ weight = torch.empty(2, dtype=torch.bfloat16, device="meta")
36+ 
37+ gm = _trace(pattern_cls.pattern, (hidden_states, weight))
38+ call_targets = [n.target for n in gm.graph.nodes if n.op == "call_function"]
39+ 
40+ # rms_norm 分解序列的第一个 op 必须是 _to_copy(fp32 提升)
41+ self.assertEqual(call_targets[0], torch.ops.aten._to_copy.default)
42+ 
43+ def test_rope_pattern_casts_are_to_copy(self):
44+ pattern_cls = create_rope_pattern(torch.float32)
45+ x = torch.empty(2, 2, 2, 2, dtype=torch.bfloat16, device="meta")
46+ cos = torch.empty(1, 2, 1, 2, dtype=torch.float32, device="meta")
47+ sin = torch.empty(1, 2, 1, 2, dtype=torch.float32, device="meta")
48+ 
49+ gm = _trace(pattern_cls.pattern, (x, cos, sin))
50+ call_targets = [n.target for n in gm.graph.nodes if n.op == "call_function"]
51+ 
52+ to_copy_count = sum(t == torch.ops.aten._to_copy.default for t in call_targets)
53+ # x 与 x_rotated 的 fp32 cast + 末尾 .to(x.dtype) cast
54+ self.assertGreaterEqual(to_copy_count, 3)
55+ # 最后一个 op 必须是 _to_copy(对应真实图 apply_rotary_emb 的 .to(x.dtype))
56+ self.assertEqual(call_targets[-1], torch.ops.aten._to_copy.default)
57+ 
58+ 
59+if __name__ == "__main__":
60+ unittest.main()