已合并
[feat] fusedsgd #5277
culechan创建于 6月26日
[feat] fusedsgd #5277
已合并
culechan创建于 6月26日
4 个文件变更+417-0
Mop_plugin/config/op_plugin_functions.yaml+7-0
@@ -5569,6 +5569,13 @@ official:
5569 - func: _scaled_grouped_mm_v2(Tensor mat_a, Tensor mat_b, Tensor[] scale_a, int[] recipe_a, int[] swizzle_a, Tensor[] scale_b, int[] recipe_b, int[] swizzle_b, Tensor? offs=None, Tensor? bias=None, ScalarType? out_dtype=None, int[] contraction_dim=[], bool use_fast_accum=False) -> Tensor5569 - func: _scaled_grouped_mm_v2(Tensor mat_a, Tensor mat_b, Tensor[] scale_a, int[] recipe_a, int[] swizzle_a, Tensor[] scale_b, int[] recipe_b, int[] swizzle_b, Tensor? offs=None, Tensor? bias=None, ScalarType? out_dtype=None, int[] contraction_dim=[], bool use_fast_accum=False) -> Tensor
5570 op_api: [v2.10, newest]5570 op_api: [v2.10, newest]
5571 5571 
5572+ - func: _fused_sgd_(Tensor(a!)[] self, Tensor(b!)[] grads, Tensor(c!)[] momentum_buffer_list, *, float weight_decay, float momentum, float lr, float dampening, bool nesterov, bool maximize, bool is_first_step, Tensor? grad_scale=None, Tensor? found_inf=None) -> ()
D
Ddaifu12345676月30日

需要加meta注册吗

likedislike
culechan
culechan
6月30日 评论:
5573+ op_api: [v2.7, newest]
5574+ 
5575+ - func: _fused_sgd_.tensor_lr(Tensor(a!)[] self, Tensor(b!)[] grads, Tensor(c!)[] momentum_buffer_list, *, float weight_decay, float momentum, Tensor lr, float dampening, bool nesterov, bool maximize, bool is_first_step, Tensor? grad_scale=None, Tensor? found_inf=None) -> ()
5576+ op_api: [v2.7, newest]
5577+ device_check: NoCheck
5578+ 
5572autograd:5579autograd:
5573 - func: _thnn_fused_gru_cell(Tensor input_gates, Tensor hidden_gates, Tensor hx, Tensor? input_bias=None, Tensor? hidden_bias=None) -> (Tensor, Tensor)5580 - func: _thnn_fused_gru_cell(Tensor input_gates, Tensor hidden_gates, Tensor hx, Tensor? input_bias=None, Tensor? hidden_bias=None) -> (Tensor, Tensor)
5574 op_api: [v2.5, newest]5581 op_api: [v2.5, newest]
Aop_plugin/ops/opapi/FusedSgdKernelOpApi.cpp+90-0
@@ -0,0 +1,90 @@
1+// Copyright (c) 2026 Huawei Technologies Co., Ltd
2+// All rights reserved.
3+//
4+// Licensed under the BSD 3-Clause License (the "License");
5+// you may not use this file except in compliance with the License.
6+// You may obtain a copy of the License at
7+//
8+// https://opensource.org/licenses/BSD-3-Clause
9+//
10+// Unless required by applicable law or agreed to in writing, software
11+// distributed under the License is distributed on an "AS IS" BASIS,
12+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+// See the License for the specific language governing permissions and
14+// limitations under the License.
15+ 
16+#include "op_plugin/OpApiInterface.h"
17+#include "op_plugin/utils/OpAdapter.h"
18+#include "op_plugin/utils/op_api_common.h"
19+#include <ATen/native/ForeachUtils.h>
20+ 
21+namespace op_api {
22+ 
23+void _fused_sgd_(at::TensorList params, at::TensorList grads, at::TensorList momentum_buffer_list,
24+ const double weight_decay, const double momentum, const double lr, const double dampening,
25+ const bool nesterov, const bool maximize, const bool is_first_step,
26+ const std::optional<at::Tensor>& grad_scale,
27+ const std::optional<at::Tensor>& found_inf)
28+{
29+ if (found_inf.has_value()) {
30+ at::Tensor found_inf_real = found_inf.value();
31+ if (found_inf_real.item().toFloat() == 1) {
32+ return;
33+ }
34+ }
35+ const float momentum_real = float(momentum);
36+ TORCH_CHECK(momentum_real > 0, "momentum must be positive, but got ", momentum_real);
37+ TORCH_CHECK(at::native::check_fast_path_restrictions({params, grads}));
38+ if (is_first_step && momentum_buffer_list.empty()) {
39+ TORCH_WARN_ONCE(
40+ "`is_first_step` argument has no effect when `momentum_buffer_list` is empty");
41+ }
42+ const float weight_decay_real = float(weight_decay);
43+ const float lr_real = float(lr);
44+ const float dampening_real = float(dampening);
45+ const at::Tensor grad_scale_real = grad_scale.value_or(at::Tensor());
46+ EXEC_NPU_CMD(aclnnFusedSgd, params, grads, momentum_buffer_list, grad_scale_real, weight_decay_real,
47+ momentum_real, lr_real, dampening_real, nesterov, maximize, is_first_step);
48+}
49+ 
50+void _fused_sgd_(at::TensorList params, at::TensorList grads, at::TensorList momentum_buffer_list,
51+ const double weight_decay, const double momentum, const at::Tensor& lr, const double dampening,
52+ const bool nesterov, const bool maximize, const bool is_first_step,
53+ const std::optional<at::Tensor>& grad_scale,
54+ const std::optional<at::Tensor>& found_inf)
55+{
56+ TORCH_CHECK(at::native::check_fast_path_restrictions({params, grads}));
57+ if (is_first_step && momentum_buffer_list.empty()) {
58+ TORCH_WARN_ONCE(
59+ "`is_first_step` argument has no effect when `momentum_buffer_list` is empty");
60+ }
61+ if (grad_scale.has_value()) {
62+ TORCH_CHECK(
63+ grad_scale->device() == params[0].device(),
64+ "grad_scale must be on the same NPU device as the params");
C
Cchuboning7月1日

缺少OPS_ERROR错误码,下面多处同样问题

likedislike
culechan
culechan
7月1日 评论:
65+ }
66+ if (found_inf.has_value()) {
67+ TORCH_CHECK(
68+ found_inf->device() == params[0].device(),
69+ "found_inf must be on the same NPU device as the params");
70+ }
71+ TORCH_CHECK(
72+ lr.device() == params[0].device(),
73+ "lr must be on the same NPU device as the params");
74+ if (found_inf.has_value()) {
75+ at::Tensor found_inf_real = found_inf.value();
76+ if (found_inf_real.item().toFloat() == 1) {
77+ return;
78+ }
79+ }
80+ const float momentum_real = float(momentum);
81+ TORCH_CHECK(momentum_real > 0, "momentum must be positive, but got ", momentum_real);
82+ const float weight_decay_real = float(weight_decay);
83+ const float dampening_real = float(dampening);
84+ const float lr_value = lr.item().toFloat();
85+ const at::Tensor grad_scale_real = grad_scale.value_or(at::Tensor());
86+ EXEC_NPU_CMD(aclnnFusedSgd, params, grads, momentum_buffer_list, grad_scale_real, weight_decay_real,
87+ momentum_real, lr_value, dampening_real, nesterov, maximize, is_first_step);
88+}
89+ 
90+} // namespace op_api
Mtest/core_tests/torch_npu_OpApi_schema_all.json+6-0
@@ -5044,5 +5044,11 @@
5044 },5044 },
5045 "func: _scaled_grouped_mm_v2(Tensor mat_a, Tensor mat_b, Tensor[] scale_a, int[] recipe_a, int[] swizzle_a, Tensor[] scale_b, int[] recipe_b, int[] swizzle_b, Tensor? offs=None, Tensor? bias=None, ScalarType? out_dtype=None, int[] contraction_dim=[], bool use_fast_accum=False) -> Tensor": {5045 "func: _scaled_grouped_mm_v2(Tensor mat_a, Tensor mat_b, Tensor[] scale_a, int[] recipe_a, int[] swizzle_a, Tensor[] scale_b, int[] recipe_b, int[] swizzle_b, Tensor? offs=None, Tensor? bias=None, ScalarType? out_dtype=None, int[] contraction_dim=[], bool use_fast_accum=False) -> Tensor": {
5046 "version": ["v2.10", "newest"]5046 "version": ["v2.10", "newest"]
5047+ },
5048+ "func: _fused_sgd_(Tensor(a!)[] self, Tensor(b!)[] grads, Tensor(c!)[] momentum_buffer_list, *, float weight_decay, float momentum, float lr, float dampening, bool nesterov, bool maximize, bool is_first_step, Tensor? grad_scale=None, Tensor? found_inf=None) -> ()": {
5049+ "version": ["v2.7", "newest"]
5050+ },
5051+ "func: _fused_sgd_.tensor_lr(Tensor(a!)[] self, Tensor(b!)[] grads, Tensor(c!)[] momentum_buffer_list, *, float weight_decay, float momentum, Tensor lr, float dampening, bool nesterov, bool maximize, bool is_first_step, Tensor? grad_scale=None, Tensor? found_inf=None) -> ()": {
5052+ "version": ["v2.7", "newest"]
5047 }5053 }
5048}5054}
Atest/test_base_ops/test_fused_sgd.py+314-0
@@ -0,0 +1,314 @@
1+import unittest
2+import copy
3+import torch
4+import torch_npu
5+from torch_npu.testing.testcase import TestCase, run_tests
6+ 
7+@unittest.skip("skip now")
8+def fused_sgd_ref(params, grads, momentum_buffer_list, weight_decay, momentum, lr,
9+ dampening, nesterov, maximize, is_first_step,
10+ grad_scale=None, found_inf=None):
11+ if found_inf is not None and int(found_inf.item()) == 1:
12+ return
13+ grad_scale_val = grad_scale.item() if grad_scale is not None else 1.0
14+ for i in range(len(params)):
15+ d_p = grads[i].clone()
16+ if grad_scale is not None:
17+ d_p = d_p / grad_scale_val
18+ if weight_decay != 0:
19+ d_p = d_p.add(params[i], alpha=weight_decay)
20+ if momentum != 0:
21+ buf = momentum_buffer_list[i]
22+ if is_first_step:
23+ buf.copy_(d_p)
24+ else:
25+ buf.mul_(momentum).add_(d_p, alpha=1 - dampening)
26+ if nesterov:
27+ d_p = d_p.add(buf, alpha=momentum)
28+ else:
29+ d_p = buf
30+ if maximize:
31+ params[i].add_(d_p, alpha=lr)
32+ else:
33+ params[i].add_(d_p, alpha=-lr)
34+ 
35+@unittest.skip("skip now")
36+class TestFusedSgd(TestCase):
37+ 
38+ def _gen_tensors(self, shapes, dtype=torch.float32, seed=42):
39+ torch.manual_seed(seed)
40+ cpu_params = [torch.randn(s, dtype=dtype) for s in shapes]
41+ cpu_grads = [torch.randn(s, dtype=dtype) for s in shapes]
42+ cpu_bufs = [torch.randn(s, dtype=dtype) for s in shapes]
43+ npu_params = [p.npu() for p in cpu_params]
44+ npu_grads = [g.npu() for g in cpu_grads]
45+ npu_bufs = [b.npu() for b in cpu_bufs]
46+ cpu_params = [p.clone() for p in cpu_params]
47+ cpu_grads = [g.clone() for g in cpu_grads]
48+ cpu_bufs = [b.clone() for b in cpu_bufs]
49+ return cpu_params, cpu_grads, cpu_bufs, npu_params, npu_grads, npu_bufs
50+ 
51+ def _run_and_compare(self, shapes, dtype, weight_decay, momentum, lr,
52+ dampening, nesterov, maximize, is_first_step,
53+ grad_scale=None, found_inf=None, prec=None):
54+ cpu_p, cpu_g, cpu_b, npu_p, npu_g, npu_b = self._gen_tensors(shapes, dtype)
55+ 
56+ torch._fused_sgd_(cpu_p, cpu_g, cpu_b, weight_decay=weight_decay,
57+ momentum=momentum, lr=lr, dampening=dampening,
58+ nesterov=nesterov, maximize=maximize,
59+ is_first_step=is_first_step,
60+ grad_scale=grad_scale, found_inf=found_inf)
61+ 
62+ npu_grad_scale = grad_scale.npu() if grad_scale is not None else None
63+ npu_found_inf = found_inf.npu() if found_inf is not None else None
64+ torch._fused_sgd_(npu_p, npu_g, npu_b, weight_decay=weight_decay,
65+ momentum=momentum, lr=lr, dampening=dampening,
66+ nesterov=nesterov, maximize=maximize,
67+ is_first_step=is_first_step,
68+ grad_scale=npu_grad_scale, found_inf=npu_found_inf)
69+ 
70+ for c_p, n_p, c_b, n_b in zip(cpu_p, npu_p, cpu_b, npu_b):
71+ self.assertRtolEqual(c_p, n_p.cpu(), prec=1e-3)
72+ self.assertRtolEqual(c_b, n_b.cpu(), prec=1e-3)
73+ 
74+ def test_fused_sgd_first_step(self):
75+ self._run_and_compare(
76+ shapes=[(4, 4), (8, 8)], dtype=torch.float32,
77+ weight_decay=0.0, momentum=0.9, lr=0.01,
78+ dampening=0.0, nesterov=False, maximize=False,
79+ is_first_step=True)
80+ 
81+ def test_fused_sgd_subsequent_step(self):
82+ self._run_and_compare(
83+ shapes=[(4, 4), (8, 8)], dtype=torch.float32,
84+ weight_decay=0.0, momentum=0.9, lr=0.01,
85+ dampening=0.0, nesterov=False, maximize=False,
86+ is_first_step=False)
87+ 
88+ def test_fused_sgd_weight_decay(self):
89+ self._run_and_compare(
90+ shapes=[(4, 4), (2, 3, 3)], dtype=torch.float32,
91+ weight_decay=0.01, momentum=0.9, lr=0.01,
92+ dampening=0.0, nesterov=False, maximize=False,
93+ is_first_step=False)
94+ 
95+ def test_fused_sgd_nesterov(self):
96+ self._run_and_compare(
97+ shapes=[(4, 4), (8, 8)], dtype=torch.float32,
98+ weight_decay=0.0, momentum=0.9, lr=0.01,
99+ dampening=0.0, nesterov=True, maximize=False,
100+ is_first_step=False)
101+ 
102+ def test_fused_sgd_maximize(self):
103+ self._run_and_compare(
104+ shapes=[(4, 4), (8, 8)], dtype=torch.float32,
105+ weight_decay=0.0, momentum=0.9, lr=0.01,
106+ dampening=0.0, nesterov=False, maximize=True,
107+ is_first_step=False)
108+ 
109+ def test_fused_sgd_dampening(self):
110+ self._run_and_compare(
111+ shapes=[(4, 4), (8, 8)], dtype=torch.float32,
112+ weight_decay=0.0, momentum=0.9, lr=0.01,
113+ dampening=0.5, nesterov=False, maximize=False,
114+ is_first_step=False)
115+ 
116+ def test_fused_sgd_nesterov_with_weight_decay(self):
117+ self._run_and_compare(
118+ shapes=[(4, 4), (8, 8)], dtype=torch.float32,
119+ weight_decay=0.001, momentum=0.9, lr=0.01,
120+ dampening=0.0, nesterov=True, maximize=True,
121+ is_first_step=False)
122+ 
123+ def test_fused_sgd_found_inf_skip(self):
124+ cpu_p, cpu_g, cpu_b, npu_p, npu_g, npu_b = self._gen_tensors(
125+ [(4, 4), (8, 8)], torch.float32)
126+ cpu_p_before = [p.clone() for p in cpu_p]
127+ cpu_b_before = [b.clone() for b in cpu_b]
128+ npu_p_before = [p.clone() for p in npu_p]
129+ npu_b_before = [b.clone() for b in npu_b]
130+ 
131+ found_inf = torch.tensor(1, dtype=torch.int32)
132+ fused_sgd_ref(cpu_p, cpu_g, cpu_b, weight_decay=0.0, momentum=0.9, lr=0.01, dampening= 0.0,
133+ nesterov=False, maximize=False, is_first_step=False, grad_scale=None, found_inf=found_inf)
134+ torch._fused_sgd_(npu_p, npu_g, npu_b, weight_decay=0.0,
135+ momentum=0.9, lr=0.01, dampening=0.0,
136+ nesterov=False, maximize=False,
137+ is_first_step=False, grad_scale=None,
138+ found_inf=found_inf.npu())
139+ 
140+ for c_before, c_after in zip(cpu_p_before, cpu_p):
141+ self.assertRtolEqual(c_before, c_after, prec=1e-2)
142+ for n_before, n_after in zip(npu_p_before, npu_p):
143+ self.assertRtolEqual(n_before.cpu(), n_after.cpu(), prec=1e-2)
144+ 
145+ def test_fused_sgd_found_inf_zero(self):
146+ found_inf = torch.tensor(0, dtype=torch.float32)
147+ self._run_and_compare(
148+ shapes=[(4, 4), (8, 8)], dtype=torch.float32,
149+ weight_decay=0.0, momentum=0.9, lr=0.01,
150+ dampening=0.0, nesterov=False, maximize=False,
151+ is_first_step=False, found_inf=found_inf)
152+ 
153+ def test_fused_sgd_grad_scale(self):
154+ grad_scale = torch.tensor(4.0, dtype=torch.float32)
155+ self._run_and_compare(
156+ shapes=[(4, 4), (8, 8)], dtype=torch.float32,
157+ weight_decay=0.0, momentum=0.9, lr=0.01,
158+ dampening=0.0, nesterov=False, maximize=False,
159+ is_first_step=False, grad_scale=grad_scale)
160+ 
161+ def test_fused_sgd_grad_scale_with_weight_decay(self):
162+ grad_scale = torch.tensor(2.0, dtype=torch.float32)
163+ self._run_and_compare(
164+ shapes=[(4, 4), (8, 8)], dtype=torch.float32,
165+ weight_decay=0.01, momentum=0.9, lr=0.01,
166+ dampening=0.0, nesterov=True, maximize=False,
167+ is_first_step=False, grad_scale=grad_scale)
168+ 
169+ def test_fused_sgd_single_tensor(self):
170+ self._run_and_compare(
171+ shapes=[(16, 16)], dtype=torch.float32,
172+ weight_decay=0.01, momentum=0.9, lr=0.05,
173+ dampening=0.1, nesterov=False, maximize=False,
174+ is_first_step=False)
175+ 
176+ def test_fused_sgd_multi_tensor(self):
177+ self._run_and_compare(
178+ shapes=[(2, 2), (3, 3), (4, 4), (5, 5), (6, 6)], dtype=torch.float32,
179+ weight_decay=0.001, momentum=0.9, lr=0.01,
180+ dampening=0.0, nesterov=False, maximize=False,
181+ is_first_step=False)
182+ 
183+ def test_fused_sgd_momentum_non_positive_error(self):
184+ npu_p = [torch.randn(4, 4, dtype=torch.float32).npu()]
185+ npu_g = [torch.randn(4, 4, dtype=torch.float32).npu()]
186+ npu_b = [torch.randn(4, 4, dtype=torch.float32).npu()]
187+ with self.assertRaises(RuntimeError):
188+ torch._fused_sgd_(npu_p, npu_g, npu_b, weight_decay=0.0,
189+ momentum=0.0, lr=0.01, dampening=0.0,
190+ nesterov=False, maximize=False,
191+ is_first_step=True)
192+ 
193+@unittest.skip("skip now")
194+class TestFusedSgdTensorLr(TestCase):
195+ 
196+ def _gen_tensors(self, shapes, dtype=torch.float32, seed=42):
197+ torch.manual_seed(seed)
198+ cpu_params = [torch.randn(s, dtype=dtype) for s in shapes]
199+ cpu_grads = [torch.randn(s, dtype=dtype) for s in shapes]
200+ cpu_bufs = [torch.randn(s, dtype=dtype) for s in shapes]
201+ npu_params = [p.npu() for p in cpu_params]
202+ npu_grads = [g.npu() for g in cpu_grads]
203+ npu_bufs = [b.npu() for b in cpu_bufs]
204+ cpu_params = [p.clone() for p in cpu_params]
205+ cpu_grads = [g.clone() for g in cpu_grads]
206+ cpu_bufs = [b.clone() for b in cpu_bufs]
207+ return cpu_params, cpu_grads, cpu_bufs, npu_params, npu_grads, npu_bufs
208+ 
209+ def _run_and_compare(self, shapes, dtype, weight_decay, momentum, lr_val,
210+ dampening, nesterov, maximize, is_first_step,
211+ grad_scale=None, found_inf=None, prec=None):
212+ cpu_p, cpu_g, cpu_b, npu_p, npu_g, npu_b = self._gen_tensors(shapes, dtype)
213+ 
214+ cpu_lr = torch.tensor(lr_val, dtype=torch.float32)
215+ torch._fused_sgd_(cpu_p, cpu_g, cpu_b, weight_decay=weight_decay, momentum=momentum, lr=cpu_lr,
216+ dampening=dampening, nesterov=nesterov, maximize=maximize, is_first_step=is_first_step,
217+ grad_scale=grad_scale, found_inf=found_inf)
218+ 
219+ npu_lr = cpu_lr.npu()
220+ npu_grad_scale = grad_scale.npu() if grad_scale is not None else None
221+ npu_found_inf = found_inf.npu() if found_inf is not None else None
222+ torch._fused_sgd_(npu_p, npu_g, npu_b, weight_decay=weight_decay,
223+ momentum=momentum, lr=npu_lr,
224+ dampening=dampening, nesterov=nesterov,
225+ maximize=maximize, is_first_step=is_first_step,
226+ grad_scale=npu_grad_scale,
227+ found_inf=npu_found_inf)
228+ 
229+ for c_p, n_p, c_b, n_b in zip(cpu_p, npu_p, cpu_b, npu_b):
230+ self.assertRtolEqual(c_p, n_p.cpu(), prec=1e-3)
231+ self.assertRtolEqual(c_b, n_b.cpu(), prec=1e-3)
232+ 
233+ def test_fused_sgd_tensor_lr_first_step(self):
234+ self._run_and_compare(
235+ shapes=[(4, 4), (8, 8)], dtype=torch.float32,
236+ weight_decay=0.0, momentum=0.9, lr_val=0.01,
237+ dampening=0.0, nesterov=False, maximize=False,
238+ is_first_step=True)
239+ 
240+ def test_fused_sgd_tensor_lr_subsequent_step(self):
241+ self._run_and_compare(
242+ shapes=[(4, 4), (8, 8)], dtype=torch.float32,
243+ weight_decay=0.01, momentum=0.9, lr_val=0.01,
244+ dampening=0.0, nesterov=False, maximize=False,
245+ is_first_step=False)
246+ 
247+ def test_fused_sgd_tensor_lr_nesterov(self):
248+ self._run_and_compare(
249+ shapes=[(4, 4), (8, 8)], dtype=torch.float32,
250+ weight_decay=0.001, momentum=0.9, lr_val=0.05,
251+ dampening=0.0, nesterov=True, maximize=False,
252+ is_first_step=False)
253+ 
254+ def test_fused_sgd_tensor_lr_maximize(self):
255+ self._run_and_compare(
256+ shapes=[(4, 4), (8, 8)], dtype=torch.float32,
257+ weight_decay=0.0, momentum=0.9, lr_val=0.01,
258+ dampening=0.0, nesterov=False, maximize=True,
259+ is_first_step=False)
260+ 
261+ def test_fused_sgd_tensor_lr_found_inf_skip(self):
262+ cpu_p, cpu_g, cpu_b, npu_p, npu_g, npu_b = self._gen_tensors(
263+ [(4, 4), (8, 8)], torch.float32)
264+ cpu_p_before = [p.clone() for p in cpu_p]
265+ npu_p_before = [p.clone() for p in npu_p]
266+ 
267+ found_inf = torch.tensor(1, dtype=torch.float32)
268+ torch._fused_sgd_(cpu_p, cpu_g, cpu_b, weight_decay=0.0, momentum=0.9, lr=0.01, dampening=0.0,
269+ nesterov=False, maximize=False, is_first_step=False, grad_scale=None, found_inf=found_inf)
270+ npu_lr = torch.tensor(0.01, dtype=torch.float32).npu()
271+ torch._fused_sgd_(npu_p, npu_g, npu_b, weight_decay=0.0,
272+ momentum=0.9, lr=npu_lr, dampening=0.0,
273+ nesterov=False, maximize=False,
274+ is_first_step=False, grad_scale=None,
275+ found_inf=found_inf.npu())
276+ 
277+ for c_before, c_after in zip(cpu_p_before, cpu_p):
278+ self.assertRtolEqual(c_before, c_after)
279+ for n_before, n_after in zip(npu_p_before, npu_p):
280+ self.assertRtolEqual(n_before.cpu(), n_after.cpu())
281+ 
282+ def test_fused_sgd_tensor_lr_grad_scale(self):
283+ grad_scale = torch.tensor([4.0], dtype=torch.float32)
284+ self._run_and_compare(
285+ shapes=[(4, 4), (8, 8)], dtype=torch.float32,
286+ weight_decay=0.0, momentum=0.9, lr_val=0.01,
287+ dampening=0.0, nesterov=False, maximize=False,
288+ is_first_step=False, grad_scale=grad_scale)
289+ 
290+ def test_fused_sgd_tensor_lr_momentum_non_positive_error(self):
291+ npu_p = [torch.randn(4, 4, dtype=torch.float32).npu()]
292+ npu_g = [torch.randn(4, 4, dtype=torch.float32).npu()]
293+ npu_b = [torch.randn(4, 4, dtype=torch.float32).npu()]
294+ npu_lr = torch.tensor(0.01, dtype=torch.float32).npu()
295+ with self.assertRaises(RuntimeError):
296+ torch._fused_sgd_(npu_p, npu_g, npu_b, weight_decay=0.0,
297+ momentum=0.0, lr=npu_lr,
298+ dampening=0.0, nesterov=False,
299+ maximize=False, is_first_step=True)
300+ 
301+ def test_fused_sgd_tensor_lr_device_check(self):
302+ npu_p = [torch.randn(4, 4, dtype=torch.float32).npu()]
303+ npu_g = [torch.randn(4, 4, dtype=torch.float32).npu()]
304+ npu_b = [torch.randn(4, 4, dtype=torch.float32).npu()]
305+ cpu_lr = torch.tensor(0.01, dtype=torch.float32)
306+ with self.assertRaises(RuntimeError):
307+ torch._fused_sgd_(npu_p, npu_g, npu_b, weight_decay=0.0,
308+ momentum=0.9, lr=cpu_lr,
309+ dampening=0.0, nesterov=False,
310+ maximize=False, is_first_step=True)
311+ 
312+ 
313+if __name__ == "__main__":
314+ run_tests()