11import warnings
2+ import functools
3+ import torch
24
35from . import backend , common , error
46from .backend .device import DeviceDetector
7+ from ..logging_utils import (
8+ precision_config ,
9+ get_precision_logger ,
10+ get_tensor_info ,
11+ get_call_location ,
12+ compare_outputs ,
13+ )
514
615
716class Register :
@@ -24,10 +33,7 @@ def __init__(
2433 self .reg_key = self .device .dispatch_key
2534 self .all_ops = []
2635 self .all_keys = []
27- if self .device .vendor == common .vendors .CAMBRICON :
28- # TODO: Cambricon specific, to avoid op deadlock question in libtuner.
29- # Should remove this in the future.
30- self .torch_ops_map = {}
36+
3137
3238 # optional mapping func_name -> list of config entries
3339 self .full_config_by_func = full_config_by_func
@@ -114,18 +120,7 @@ def register_impl(self, key, fn):
114120 device_key = self .reg_key
115121 self .all_ops .append (fn .__name__ )
116122 self .all_keys .append (key )
117- if self .device .vendor == common .vendors .CAMBRICON :
118- import torch
119-
120- try :
121- self .torch_ops_map ["aten::" + key ] = torch .library .get_kernel (
122- "aten::" + key , device_key
123- )
124- except Exception :
125- pass
126- self .lib .impl (key , fn , device_key , allow_override = True )
127- else :
128- self .lib .impl (key , fn , device_key )
123+ self .lib .impl (key , fn , device_key )
129124
130125 def for_each (self ):
131126 for key , func in self .config :
@@ -148,3 +143,171 @@ def get_vendor_name(self):
148143
149144 def get_current_device (self ):
150145 return self .device .name
146+
147+ # def _wrap_op_with_precision_check(op_key, fn):
148+ # @functools.wraps(fn)
149+ # def wrapper(*args, **kwargs):
150+ # fg_result = fn(*args, **kwargs)
151+
152+ # cfg = precision_config
153+ # if cfg["enabled"] and op_key not in cfg["logged_ops"]:
154+ # cfg["logged_ops"].add(op_key)
155+ # logger = get_precision_logger()
156+ # input_info = [get_tensor_info(a) for a in args if get_tensor_info(a)]
157+ # logger.info(f"Op: {op_key} | in: {input_info}")
158+
159+ # return fg_result
160+ # return wrapper
161+
162+ # 精度检查时允许的最大 tensor 元素数(超过则跳过,避免大 tensor 拷贝开销)
163+ _MAX_NUMEL_FOR_CHECK = 1 * 1024 * 1024 # 1M 元素
164+
165+
166+ def _get_dtype_tolerance (args , default_rtol , default_atol ):
167+ """根据输入 tensor 的 dtype 自动调整容差"""
168+ for a in args :
169+ if isinstance (a , torch .Tensor ) and a .is_floating_point ():
170+ if a .dtype in (torch .bfloat16 , torch .float16 ):
171+ return (max (default_rtol , 1e-2 ), max (default_atol , 1e-2 ))
172+ break
173+ return (default_rtol , default_atol )
174+
175+
176+ def _to_cpu (x ):
177+ """递归地将 tensor 移到 CPU"""
178+ if isinstance (x , torch .Tensor ):
179+ return x .detach ().cpu ()
180+ elif isinstance (x , (list , tuple )):
181+ return type (x )(_to_cpu (i ) for i in x )
182+ elif isinstance (x , dict ):
183+ return {k : _to_cpu (v ) for k , v in x .items ()}
184+ return x
185+
186+
187+ def _max_tensor_numel (args ):
188+ """返回参数中最大 tensor 的元素数"""
189+ max_n = 0
190+ for a in args :
191+ if isinstance (a , torch .Tensor ):
192+ max_n = max (max_n , a .numel ())
193+ return max_n
194+
195+
196+ def _wrap_op_with_precision_check (op_key , fn ):
197+ """包装 FlagGems 算子,在执行后与 PyTorch 原生 CPU 实现做精度对比。
198+
199+ 由于 FlagGems 替换了 CUDA dispatch,无法在 GPU 上调用原生实现,
200+ 因此将输入拷贝到 CPU 计算参考结果。通过以下方式控制性能开销:
201+ - max_checks: 每个算子只检查前 N 次调用(默认 10)
202+ - 跳过大 tensor(超过 1M 元素)
203+ - 一旦记录过失败就不再检查该算子
204+ """
205+ _call_count = 0
206+
207+ @functools .wraps (fn )
208+ def wrapper (* args , ** kwargs ):
209+ nonlocal _call_count
210+
211+ # 先执行 FlagGems 实现
212+ fg_result = fn (* args , ** kwargs )
213+
214+ cfg = precision_config
215+ if not cfg ["enabled" ]:
216+ return fg_result
217+
218+ # 已经记录过失败的算子不再检查
219+ if op_key in cfg ["logged_ops" ]:
220+ return fg_result
221+
222+ # 采样:每个算子只检查前 N 次调用
223+ _call_count += 1
224+ if _call_count > cfg .get ("max_checks" , 10 ):
225+ return fg_result
226+
227+ op_name = op_key .split ("::" )[- 1 ].split ("." )[0 ] if "::" in op_key else op_key .split ("." )[0 ]
228+
229+ # 跳过 out variant(参数签名不同,CPU 调用容易出错)
230+ overload_part = op_key .split ("." )[- 1 ] if "." in op_key else ""
231+ if overload_part == "out" or op_name .endswith ("_out" ):
232+ return fg_result
233+
234+ # 跳过不需要检查的算子
235+ skip_ops = {
236+ # 纯 layout / 内存操作
237+ 'copy_' , '_to_copy' , 'view' , 'reshape' , 'expand' , 'permute' ,
238+ 'transpose' , 'contiguous' , 'clone' , 'to' , 'empty' , 'zeros' ,
239+ 'ones' , 'full' , 'masked_fill_' ,
240+ # 随机采样算子(GPU/CPU 随机数生成器不同,结果必然不一致)
241+ 'exponential_' , 'normal_' , 'uniform_' , 'bernoulli_' , 'random_' ,
242+ 'multinomial' , 'randperm' ,
243+ # 排序/选择算子(相同值的排序顺序可能不同,不适合逐元素比较)
244+ 'sort' , 'topk' , 'argsort' ,
245+ }
246+ if op_name in skip_ops :
247+ return fg_result
248+
249+ # 跳过大 tensor,避免拷贝开销
250+ if _max_tensor_numel (args ) > _MAX_NUMEL_FOR_CHECK :
251+ return fg_result
252+
253+ try :
254+ # 解析 op_key 获取正确的 overload
255+ # op_key 形如 "add.Tensor", "mm.default", "softmax.int" 等
256+ parts = op_key .split ("." )
257+ base_name = parts [0 ]
258+ overload_name = parts [1 ] if len (parts ) > 1 else "default"
259+
260+ aten_packet = getattr (torch .ops .aten , base_name , None )
261+ if aten_packet is None :
262+ return fg_result
263+ aten_overload = getattr (aten_packet , overload_name , None )
264+ if aten_overload is None :
265+ return fg_result
266+
267+ # 将输入拷贝到 CPU,调用原生 aten 实现(CPU 上不受 FlagGems 影响)
268+ cpu_args = [_to_cpu (a ) for a in args ]
269+ cpu_kwargs = {k : _to_cpu (v ) for k , v in kwargs .items ()}
270+
271+ with torch .no_grad ():
272+ pt_result_cpu = aten_overload (* cpu_args , ** cpu_kwargs )
273+
274+ # 将 FlagGems 结果也拷贝到 CPU 做比较(避免把 CPU 结果搬回 GPU)
275+ fg_result_cpu = _to_cpu (fg_result )
276+
277+ # 根据 dtype 自动调整容差
278+ rtol , atol = _get_dtype_tolerance (args , cfg ["rtol" ], cfg ["atol" ])
279+
280+ is_close , info = compare_outputs (fg_result_cpu , pt_result_cpu , rtol , atol )
281+
282+ if not is_close :
283+ cfg ["logged_ops" ].add (op_key )
284+ logger = get_precision_logger ()
285+ input_info = [get_tensor_info (a ) for a in args if get_tensor_info (a )]
286+ output_info = get_tensor_info (fg_result )
287+
288+ msg = f"Op: { op_key } | FAIL | in: { input_info } | out: { output_info } "
289+ if "error" in info :
290+ msg += f" | { info ['error' ]} : fg={ info ['fg' ]} , pt={ info ['pt' ]} "
291+ else :
292+ msg += f" | max_abs: { info ['max_abs' ]:.6e} | max_rel: { info ['max_rel' ]:.6e} "
293+ msg += f" | rtol={ rtol } , atol={ atol } "
294+ logger .warning (msg )
295+
296+ except Exception :
297+ pass
298+
299+ return fg_result
300+
301+ return wrapper
302+
303+ class PrecisionCheckRegister (Register ):
304+ def register_impl (self , key , fn ):
305+ if self .lib is None :
306+ raise ValueError ("Library instance is not provided." )
307+
308+ wrapped_fn = _wrap_op_with_precision_check (key , fn )
309+
310+ device_key = self .reg_key
311+ self .all_ops .append (fn .__name__ )
312+ self .all_keys .append (key )
313+ self .lib .impl (key , wrapped_fn , device_key )
0 commit comments