已合并
[fix][inductor] fix master triton acc-check bug #43556
lv-kaimeng创建于 8月1日
[fix][inductor] fix master triton acc-check bug #43556
已合并
lv-kaimeng创建于 8月1日
9 个文件变更+266-29
@@ -318,9 +318,9 @@ def _patch_baseview_realize_hint(self):
318 else:318 else:
319 return self.data.realize_hint()319 return self.data.realize_hint()
320 320 
321-def _patch_mark_reuse(self, users):321+def _patch_mark_reuse(self, users, **kwargs):
322 if hasattr(self, 'traced_graph') and self.traced_graph is not None:322 if hasattr(self, 'traced_graph') and self.traced_graph is not None:
323- r = self.data.mark_reuse(users)323+ r = self.data.mark_reuse(users, **kwargs)
324 buffer = try_get_buffer(self)324 buffer = try_get_buffer(self)
325 if not buffer:325 if not buffer:
326 return r326 return r
@@ -340,7 +340,7 @@ def _patch_mark_reuse(self, users):
340 self._post_init_setattr("traced_graph", new_traced_graph)340 self._post_init_setattr("traced_graph", new_traced_graph)
341 return r341 return r
342 else:342 else:
343- return self.data.mark_reuse(users)343+ return self.data.mark_reuse(users, **kwargs)
344 344 
345@classmethod345@classmethod
346def _patch_expandview_create(cls, x, new_size, traced_graph=None, node_name=None):346def _patch_expandview_create(cls, x, new_size, traced_graph=None, node_name=None):
@@ -159,6 +159,7 @@ class NPUTritonScheduling(TritonScheduling):
159 V.graph.inplaced_to_remove |= kernel.inplaced_to_remove159 V.graph.inplaced_to_remove |= kernel.inplaced_to_remove
160 160 
161 traced_graph_hash = None161 traced_graph_hash = None
162+ kernel_graph_hash = None
162 if npu_config.dump_fx_graph:163 if npu_config.dump_fx_graph:
163 if not npu_config.traced_fx_graph_cache:164 if not npu_config.traced_fx_graph_cache:
164 npu_config.traced_fx_graph_cache = os.path.join(os.getenv("TORCHINDUCTOR_CACHE_DIR"),165 npu_config.traced_fx_graph_cache = os.path.join(os.getenv("TORCHINDUCTOR_CACHE_DIR"),
@@ -168,9 +169,11 @@ class NPUTritonScheduling(TritonScheduling):
168 if traced_graph is None:169 if traced_graph is None:
169 log.warning("For nodes %s, could not gen fx graph while dump-graph.", nodes)170 log.warning("For nodes %s, could not gen fx graph while dump-graph.", nodes)
170 else:171 else:
171- traced_graph_hash = code_hash(src_code + traced_graph.code)172+ fx_arg_shapes_str = str([getattr(a, "shape", a) for a in fx_args])
173+ traced_graph_hash = code_hash(src_code + traced_graph.code + fx_arg_shapes_str)
174+ kernel_graph_hash = code_hash(src_code + traced_graph.code)
172 175 
173- kernel_name, src_code = self.define_kernel(src_code, node_schedule, kernel, traced_graph_hash)176+ kernel_name, src_code = self.define_kernel(src_code, node_schedule, kernel, traced_graph_hash, kernel_graph_hash)
174 177 
175 kernel.kernel_name = kernel_name178 kernel.kernel_name = kernel_name
176 kernel.code_hash = code_hash(src_code)179 kernel.code_hash = code_hash(src_code)
@@ -366,10 +369,11 @@ class NPUTritonScheduling(TritonScheduling):
366 kernel_code_list.append((src_code, kernel, node_group))369 kernel_code_list.append((src_code, kernel, node_group))
367 return kernel_code_list370 return kernel_code_list
368 371 
369- def define_kernel(self, src_code, node_schedule, kernel, traced_graph_hash: str):372+ def define_kernel(self, src_code, node_schedule, kernel, traced_graph_hash: str, kernel_graph_hash: str = None):
370 wrapper = V.graph.wrapper_code373 wrapper = V.graph.wrapper_code
371- if (src_code, traced_graph_hash) in wrapper.src_to_kernel:374+ kernel_cache_key = (src_code, kernel_graph_hash)
372- kernel_name = wrapper.src_to_kernel[(src_code, traced_graph_hash)]375+ if kernel_cache_key in wrapper.src_to_kernel:
376+ kernel_name = wrapper.src_to_kernel[kernel_cache_key]
373 if npu_config.dump_fx_graph:377 if npu_config.dump_fx_graph:
374 src_code = src_code.replace(str(Placeholder.DESCRIPTIVE_NAME), kernel_name)378 src_code = src_code.replace(str(Placeholder.DESCRIPTIVE_NAME), kernel_name)
375 subs_name = kernel_name if config.triton.unique_kernel_names else "triton_"379 subs_name = kernel_name if config.triton.unique_kernel_names else "triton_"
@@ -390,7 +394,7 @@ class NPUTritonScheduling(TritonScheduling):
390 ["triton", kernel_category, fused_name, wrapper.next_kernel_suffix()]394 ["triton", kernel_category, fused_name, wrapper.next_kernel_suffix()]
391 )395 )
392 # use the original src_code as the key396 # use the original src_code as the key
393- wrapper.src_to_kernel[(src_code, traced_graph_hash)] = kernel_name397+ wrapper.src_to_kernel[kernel_cache_key] = kernel_name
394 subs_name = kernel_name if config.triton.unique_kernel_names else "triton_"398 subs_name = kernel_name if config.triton.unique_kernel_names else "triton_"
395 399 
396 # DESCRIPTIVE_NAME is used for profiling purposes; it shows the full kernel name400 # DESCRIPTIVE_NAME is used for profiling purposes; it shows the full kernel name
@@ -5,6 +5,7 @@ import functools
5import itertools5import itertools
6import math6import math
7import operator7import operator
8+import os
8import re9import re
9from collections.abc import Callable, Iterable, Sequence10from collections.abc import Callable, Iterable, Sequence
10from typing import Any, cast, Optional11from typing import Any, cast, Optional
@@ -4261,6 +4262,11 @@ class NPUIndexTritonKernel(TritonKernel):
4261 return sympy_var4262 return sympy_var
4262 4263 
4263 if var.bounds.lower < 0: # type: ignore[operator]4264 if var.bounds.lower < 0: # type: ignore[operator]
4265+ if (
4266+ os.environ.get("INDUCTOR_ASCEND_DUMP_FX_GRAPH")
4267+ or os.environ.get("INDUCTOR_ASCEND_CHECK_ACCURACY")
4268+ ):
4269+ wrap_neg = False
4264 if wrap_neg:4270 if wrap_neg:
4265 stm = ops.add(var, ops.index_expr(size, torch.long))4271 stm = ops.add(var, ops.index_expr(size, torch.long))
4266 # Mixed negative and non-negative4272 # Mixed negative and non-negative
@@ -72,8 +72,10 @@ from torch._inductor.scheduler import Scheduler
72 72 
73from .codegen.triton_utils import NPUKernelType73from .codegen.triton_utils import NPUKernelType
74from .ir import IndexputTemplate, ScatterTemplate74from .ir import IndexputTemplate, ScatterTemplate
75-from .lowering_common import (75+from .lowering_common import ( # noqa: F401 re-export for codegen/ir_fx.py
76 add_overload as _add_overload,76 add_overload as _add_overload,
77+ create_fake_input,
78+ subtract_graph,
77 enable_full_lowering_fallback as enable_full_lowering_fallback_common,79 enable_full_lowering_fallback as enable_full_lowering_fallback_common,
78 resolve_op_from_name,80 resolve_op_from_name,
79)81)
@@ -83,10 +85,15 @@ from .config import inductor_indirect_memory_mode, log, is_ascend950, enable_ful
83from .lowering_fallback_list import FALLBACK_LIST, NPU_EXTRA_FALLBACK_LIST85from .lowering_fallback_list import FALLBACK_LIST, NPU_EXTRA_FALLBACK_LIST
84 86 
85from . import config as npu_config87from . import config as npu_config
86-from .lowering_fx import (88+from .lowering_fx import ( # noqa: F401 re-export for codegen/scheduling.py & ir_fx.py
87 fetch_graphs,89 fetch_graphs,
88 merge_traced_graphs,90 merge_traced_graphs,
89 node_id,91 node_id,
92+ create_fx_from_snodes_by_traced_graph,
93+ create_compile_kwargs,
94+ generate_fx_graph_code,
95+ dump_fx_graph_code,
96+ snodes_to_fx,
90)97)
91 98 
92def npu_make_fallback(op, layout_constraint=None, warn=True, override_decomp=False):99def npu_make_fallback(op, layout_constraint=None, warn=True, override_decomp=False):
@@ -323,7 +323,8 @@ def fetch_graphs(
323 *,323 *,
324 use_npu_meta: bool = False,324 use_npu_meta: bool = False,
325):325):
326- if isinstance(inputs, (TensorBox, ir.StorageBox, ir.View, sympy.Symbol, ir.Constant, ir.ReinterpretView)):326+ if isinstance(inputs, (TensorBox, ir.StorageBox, ir.View, ir.ExpandView, ir.PermuteView, ir.SliceView,
327+ sympy.Symbol, ir.Constant, ir.ReinterpretView)):
327 inputs = [inputs]328 inputs = [inputs]
328 input_graphs = []329 input_graphs = []
329 for inp in inputs:330 for inp in inputs:
@@ -351,7 +352,19 @@ def fetch_graphs(
351 continue352 continue
352 name = inp.get_name()353 name = inp.get_name()
353 traced_graph = inp.get_traced_graph()354 traced_graph = inp.get_traced_graph()
354- if traced_graph is not None:355+ if (
356+ traced_graph is not None
357+ and not isinstance(inp, ir.ConcatKernel)
358+ and not (
359+ hasattr(inp, 'data')
360+ and isinstance(inp.data, ir.ConcatKernel)
361+ )
362+ and not (
363+ hasattr(inp, 'data')
364+ and hasattr(inp.data, 'data')
365+ and isinstance(inp.data.data, ir.ConcatKernel)
366+ )
367+ ):
355 input_graphs.append(traced_graph)368 input_graphs.append(traced_graph)
356 continue369 continue
357 traced_graph = TracedGraph()370 traced_graph = TracedGraph()
@@ -445,11 +445,16 @@ def create_compile_kwargs(final_kernel, fx_call_args, fx_args):
445 fx_call_args[idx] = final_kernel.args.inplace_buffers[call_arg].other_names[-1]445 fx_call_args[idx] = final_kernel.args.inplace_buffers[call_arg].other_names[-1]
446 fx_arg_shapes = [fx_arg.shape if isinstance(fx_arg, torch.Tensor) else ['ushape'] for fx_arg in fx_args]446 fx_arg_shapes = [fx_arg.shape if isinstance(fx_arg, torch.Tensor) else ['ushape'] for fx_arg in fx_args]
447 447 
448+ # add_numel 前移: 让 numel 表达式参与后续匹配, SymInt 标量输入有机会匹配到 kernel numel arg
449+ final_kernel.add_numel_to_call_args(final_kernel.kernel_name, kernel_call_args, arg_types)
450+ # add_numel 后 kernel_call_args 可能含 SymbolicCallArg (unhashable), 统一用 str 比较
451+ kernel_call_args_str = [str(arg) for arg in kernel_call_args]
452+ 
448 fx_call_args_no_dy = [arg for arg in fx_call_args if not (arg.startswith(("_", "u")))]453 fx_call_args_no_dy = [arg for arg in fx_call_args if not (arg.startswith(("_", "u")))]
449 454 
450- if set(kernel_call_args) != set(fx_call_args_no_dy):455+ if set(kernel_call_args_str) != set(fx_call_args_no_dy):
451 # Handle NonOwningLayout alias456 # Handle NonOwningLayout alias
452- kernel_call_args_set = set(kernel_call_args)457+ kernel_call_args_set = set(kernel_call_args_str)
453 for idx, call_arg in enumerate(fx_call_args):458 for idx, call_arg in enumerate(fx_call_args):
454 if call_arg in kernel_call_args_set:459 if call_arg in kernel_call_args_set:
455 continue460 continue
@@ -462,9 +467,22 @@ def create_compile_kwargs(final_kernel, fx_call_args, fx_args):
462 break467 break
463 468 
464 fx_call_args_no_dy = [arg for arg in fx_call_args if not (arg.startswith(("_", "u")))]469 fx_call_args_no_dy = [arg for arg in fx_call_args if not (arg.startswith(("_", "u")))]
465- if set(kernel_call_args) != set(fx_call_args_no_dy):470+ kernel_call_args_str_set = set(kernel_call_args_str)
466- return None471+ if kernel_call_args_str_set != set(fx_call_args_no_dy):
467- final_kernel.add_numel_to_call_args(final_kernel.kernel_name, kernel_call_args, arg_types)472+ # 区分: 不匹配的是 "标量输入" (可恢复) 还是 "buffer" (应放弃)
473+ fx_only = set(fx_call_args_no_dy) - kernel_call_args_str_set
474+ scalar_only_mismatch = True
475+ for buf_name in fx_only:
476+ buf = V.graph.try_get_buffer(buf_name)
477+ if buf is not None:
478+ scalar_only_mismatch = False
479+ break
480+ if not scalar_only_mismatch:
481+ log.warning("[ACC-DEBUG] kernel=%s skipped: buffer mismatch (fx_only=%s)",
482+ final_kernel.kernel_name, fx_only)
483+ return None
484+ # scalar_only_mismatch: SymInt 标量输入没有对应 kernel arg, 用 -1 哨兵保留位置,
485+ # 运行期从输出 tensor shape 反推其值 (见 npu_compare.check_accuracy_triton)
468 486 
469 kernel_call_args = [487 kernel_call_args = [
470 map_operators_to_strings(str(item.inner_expr)) if isinstance(item, torch._inductor.codegen.wrapper.SymbolicCallArg)488 map_operators_to_strings(str(item.inner_expr)) if isinstance(item, torch._inductor.codegen.wrapper.SymbolicCallArg)
@@ -472,11 +490,75 @@ def create_compile_kwargs(final_kernel, fx_call_args, fx_args):
472 for item in kernel_call_args490 for item in kernel_call_args
473 ]491 ]
474 index_map = {str(element): idx for idx, element in enumerate(kernel_call_args)}492 index_map = {str(element): idx for idx, element in enumerate(kernel_call_args)}
475- call_args_mapping = [index_map[str(element)] for element in fx_call_args if str(element) in index_map]493+ # Prefer the runtime kernel argument for every exact match, including
494+ # symbolic scalar arguments such as a numel expression.
495+ call_args_mapping = []
496+ for i, element in enumerate(fx_call_args):
497+ element_str = str(element)
498+ if element_str in index_map:
499+ call_args_mapping.append(index_map[element_str])
500+ elif i < len(fx_args) and not isinstance(fx_args[i], torch.Tensor):
501+ # Only scalars absent from the kernel signature need a shape recipe.
502+ call_args_mapping.append(-1)
503+ else:
504+ return None
505+ 
506+ # 精确配方: 对每个 -1 标量输入, 编译期求解它等于哪个 kernel arg tensor 的第几维。
507+ # 序列化为具体 int (kernel_arg_idx, dim), 运行期 args[kidx].shape[dim] 精确反推,
508+ # 不依赖 "FX 标量位置 == 输出维度序号" 的假设 (比 shape[pos] 猜测更普适)。
509+ def _to_sympy_expr(val):
510+ if val is None:
511+ return None
512+ if isinstance(val, (int, sympy.Integer)):
513+ return sympy.sympify(val)
514+ if hasattr(val, "_sympy_"):
515+ try:
516+ return val._sympy_()
517+ except Exception:
518+ return None
519+ try:
520+ return sympy.sympify(val)
521+ except Exception:
522+ return None
523+ 
524+ scalar_resolved_from = {}
525+ for i, idx in enumerate(call_args_mapping):
526+ if idx != -1:
527+ continue
528+ scalar_expr = _to_sympy_expr(fx_args[i])
529+ if scalar_expr is None:
530+ return None
531+ found = None
532+ for j, fa in enumerate(fx_args):
533+ if not isinstance(fa, torch.Tensor):
534+ continue
535+ kidx = call_args_mapping[j]
536+ if kidx < 0:
537+ continue
538+ for d, dim_val in enumerate(fa.shape):
539+ dim_expr = _to_sympy_expr(dim_val)
540+ if dim_expr is None:
541+ continue
542+ if scalar_expr == dim_expr or sympy.simplify(scalar_expr - dim_expr) == 0:
543+ found = (kidx, d)
544+ break
545+ if found is not None:
546+ break
547+ if found is None:
548+ # 无法从任何 tensor 的 shape 反推该 SymInt 标量 → 跳过该 kernel 精度检查 (安全, 不猜)
549+ log.warning("[ACC-DEBUG] kernel=%s skipped: cannot resolve scalar at pos=%s from any tensor shape",
550+ final_kernel.kernel_name, i)
551+ return None
552+ scalar_resolved_from[i] = found
476 mismatch_indices_shapes = {}553 mismatch_indices_shapes = {}
477 554 
478 def is_dynamic_shape_dim(d):555 def is_dynamic_shape_dim(d):
479- if str(type(d)).find("torch.fx.experimental.symbolic_shapes") != -1:556+ type_str = str(type(d))
557+ if type_str.find("torch.fx.experimental.symbolic_shapes") != -1:
558+ return True
559+ if type_str.find("torch.SymInt") != -1:
560+ return True
561+ if type_str.find("sympy") != -1:
480 return True562 return True
481 s = str(d).strip()563 s = str(d).strip()
482 if s.startswith("u") or s.startswith("i") or s in ("-1", "?"):564 if s.startswith("u") or s.startswith("i") or s in ("-1", "?"):
@@ -487,15 +569,28 @@ def create_compile_kwargs(final_kernel, fx_call_args, fx_args):
487 return True569 return True
488 570 
489 for i in range(len(fx_call_args)):571 for i in range(len(fx_call_args)):
490- if any(is_dynamic_shape_dim(dim) for dim in fx_arg_shapes[i]):572+ if not isinstance(fx_args[i], torch.Tensor):
491 continue573 continue
492- mismatch_indices_shapes[i] = fx_arg_shapes[i]574+ reshape_shape = []
575+ dynamic_dim_count = 0
576+ for dim in fx_arg_shapes[i]:
577+ if is_dynamic_shape_dim(dim):
578+ dynamic_dim_count += 1
579+ reshape_shape.append(-1)
580+ else:
581+ reshape_shape.append(int(dim))
582+ # torch.reshape can infer one dynamic dimension from the runtime numel.
583+ if dynamic_dim_count > 1:
584+ continue
585+ mismatch_indices_shapes[i] = torch.Size(reshape_shape)
493 586 
494 return {587 return {
495 "call_args_mapping": call_args_mapping,588 "call_args_mapping": call_args_mapping,
496 "mismatch_indices_shapes": mismatch_indices_shapes,589 "mismatch_indices_shapes": mismatch_indices_shapes,
590+ "scalar_resolved_from": scalar_resolved_from,
497 }591 }
498 592 
593+ 
499def generate_fx_graph_code(code, kernel_code, kernel_name, compile_kwargs):594def generate_fx_graph_code(code, kernel_code, kernel_name, compile_kwargs):
500 code = textwrap.indent(code, ' ')595 code = textwrap.indent(code, ' ')
501 code_template = f"""596 code_template = f"""
@@ -546,6 +641,7 @@ num_inputs = {compile_kwargs['num_inputs']}
546num_outputs = {compile_kwargs['num_outputs']}641num_outputs = {compile_kwargs['num_outputs']}
547non_contiguous_indices = {compile_kwargs['non_contiguous_indices']}642non_contiguous_indices = {compile_kwargs['non_contiguous_indices']}
548mismatch_indices_shapes = {compile_kwargs['mismatch_indices_shapes']}643mismatch_indices_shapes = {compile_kwargs['mismatch_indices_shapes']}
644+scalar_resolved_from = {compile_kwargs.get('scalar_resolved_from', {})}
549 645 
550async_compile = AsyncCompile()646async_compile = AsyncCompile()
551{kernel_name} = async_compile.triton('{kernel_name}', '''647{kernel_name} = async_compile.triton('{kernel_name}', '''
@@ -567,7 +663,12 @@ def run():
567 args = [arg.npu() if isinstance(arg, torch.Tensor) else arg for arg in args]663 args = [arg.npu() if isinstance(arg, torch.Tensor) else arg for arg in args]
568 664 
569 fx_args = []665 fx_args = []
570- for idx in call_args_mapping:666+ for pos, idx in enumerate(call_args_mapping):
667+ if idx == -1:
668+ # SymInt 标量输入: 用编译期记录的精确配方 (kernel_arg_idx, dim) 反推
669+ kidx, d = scalar_resolved_from[pos]
670+ fx_args.append(int(args[kidx].shape[d]))
671+ continue
571 arg = args[idx]672 arg = args[idx]
572 if isinstance(arg, int):673 if isinstance(arg, int):
573 fx_args.append(arg)674 fx_args.append(arg)
@@ -7,10 +7,10 @@
7# independently.7# independently.
8 8 
9from __future__ import annotations9from __future__ import annotations
10- 
11import copy10import copy
12import functools11import functools
13import importlib12import importlib
13+import os
14from collections.abc import Iterable, Mapping14from collections.abc import Iterable, Mapping
15from dataclasses import dataclass, field15from dataclasses import dataclass, field
16from typing import Any, Callable, Optional16from typing import Any, Callable, Optional
@@ -156,6 +156,15 @@ def _uses_device_lowering(
156 )156 )
157 if layout_device_type is not None:157 if layout_device_type is not None:
158 return layout_device_type == device_type158 return layout_device_type == device_type
159+ if os.environ.get("INDUCTOR_ASCEND_DUMP_FX_GRAPH") or os.environ.get("INDUCTOR_ASCEND_CHECK_ACCURACY"):
160+ device_kwarg = kwargs.get("device")
161+ if isinstance(device_kwarg, (torch.device, str)):
162+ try:
163+ kwarg_device_type = torch.device(device_kwarg).type
164+ except Exception:
165+ kwarg_device_type = None
166+ if kwarg_device_type is not None:
167+ return kwarg_device_type in ("npu", "cpu")
159 if device_type in _iter_ir_device_types((args, kwargs)):168 if device_type in _iter_ir_device_types((args, kwargs)):
160 return True169 return True
161 return extra_device_predicate is not None and extra_device_predicate(170 return extra_device_predicate is not None and extra_device_predicate(
@@ -1,12 +1,19 @@
1import importlib1import importlib
2+import logging
2import os3import os
3import sys4import sys
5+from collections import defaultdict
4from typing import Any, Iterable, Mapping6from typing import Any, Iterable, Mapping
5 7 
6import torch8import torch
7from torch._inductor.compile_fx import clone_preserve_strides9from torch._inductor.compile_fx import clone_preserve_strides
8 10 
9 11 
12+log = logging.getLogger(__name__)
13+ 
14+_call_counter: dict = defaultdict(int)
15+ 
16+ 
10def clone_for_accuracy(arg):17def clone_for_accuracy(arg):
11 if not isinstance(arg, torch.Tensor):18 if not isinstance(arg, torch.Tensor):
12 return arg19 return arg
@@ -51,14 +58,27 @@ def _report_mismatch(idx, actual, expected, matches, rtol, atol, kernel_name, du
51 rel_diff.masked_fill_(matches, 0)58 rel_diff.masked_fill_(matches, 0)
52 number_of_elements = matches.numel()59 number_of_elements = matches.numel()
53 total_mismatches = number_of_elements - int(torch.sum(matches))60 total_mismatches = number_of_elements - int(torch.sum(matches))
61+ mismatch_mask = ~matches
62+ mismatch_indices = torch.nonzero(mismatch_mask.flatten())[:5].reshape(-1)
63+ sample_info = ""
64+ if mismatch_indices.numel() > 0:
65+ idx_flat = mismatch_indices[:3].tolist()
66+ idx_unflat = [list(torch.unravel_index(torch.tensor(i), actual.shape)) for i in idx_flat]
67+ samples = "; ".join(
68+ f"pos{idx_unflat[j]}: actual={actual.flatten()[i].item():.6e}, expected={expected.flatten()[i].item():.6e}"
69+ for j, i in enumerate(idx_flat)
70+ )
71+ sample_info = f", Sample values: [{samples}]"
54 msg = (72 msg = (
55 "CHECK ACCURACY FAILED! "73 "CHECK ACCURACY FAILED! "
56 f"Kernel: {kernel_name}, Output idx: {idx}, "74 f"Kernel: {kernel_name}, Output idx: {idx}, "
75+ f"actual_shape={list(actual.shape)}, actual_dtype={actual.dtype}, "
76+ f"expected_shape={list(expected.shape)}, expected_dtype={expected.dtype}, "
57 f"Mismatched: {total_mismatches}/{number_of_elements} "77 f"Mismatched: {total_mismatches}/{number_of_elements} "
58 f"({total_mismatches / number_of_elements:.1%}), "78 f"({total_mismatches / number_of_elements:.1%}), "
59 f"Greatest Rel Diff: {rel_diff.max().item()}, "79 f"Greatest Rel Diff: {rel_diff.max().item()}, "
60 f"Greatest Abs Diff: {abs_diff.max().item()}, "80 f"Greatest Abs Diff: {abs_diff.max().item()}, "
61- f"rtol: {rtol}, atol: {atol}"81+ f"rtol: {rtol}, atol: {atol}{sample_info}"
62 )82 )
63 if dump_path:83 if dump_path:
64 msg += f", dump_path: {dump_path}"84 msg += f", dump_path: {dump_path}"
@@ -117,8 +137,23 @@ def check_accuracy_triton(*args, launcher, grid, stream, inductor_meta, **kwargs
117 return None137 return None
118 call_outputs_indices = fx_module.call_args_mapping[fx_module.num_inputs:]138 call_outputs_indices = fx_module.call_args_mapping[fx_module.num_inputs:]
119 139 
140+ _call_counter[kernel_name] += 1
141+ invocation = _call_counter[kernel_name]
142+ scalar_resolved_from = getattr(fx_module, "scalar_resolved_from", {})
143+ 
144+ # 诊断: 记录配方路径的解析值, 供 compare_outputs 失败时打印 (区分误报)
145+ _recipe_used = bool(scalar_resolved_from)
146+ _recipe_resolved = {}
120 fx_args = []147 fx_args = []
121- for idx in fx_module.call_args_mapping:148+ for pos, idx in enumerate(fx_module.call_args_mapping):
149+ if idx == -1:
150+ # SymInt 标量输入: 用编译期记录的精确配方 (kernel_arg_idx, dim) 反推其值。
151+ # 配方在 create_compile_kwargs 求解, 序列化为具体 int, 不依赖 pos==dim 假设。
152+ kidx, d = scalar_resolved_from[pos]
153+ _resolved = int(args[kidx].shape[d])
154+ _recipe_resolved[pos] = (scalar_resolved_from[pos], _resolved)
155+ fx_args.append(_resolved)
156+ continue
122 arg = args[idx]157 arg = args[idx]
123 if isinstance(arg, int):158 if isinstance(arg, int):
124 fx_args.append(arg)159 fx_args.append(arg)
@@ -130,11 +165,24 @@ def check_accuracy_triton(*args, launcher, grid, stream, inductor_meta, **kwargs
130 arg)165 arg)
131 fx_args.append(fx_arg)166 fx_args.append(fx_arg)
132 167 
168+ input_indices = fx_module.call_args_mapping[:fx_module.num_inputs]
169+ input_shapes = {f"args[{i}]": list(args[i].shape) if isinstance(args[i], torch.Tensor) else type(args[i]).__name__
170+ for i in input_indices}
171+ output_indices = call_outputs_indices
172+ output_shapes = {f"args[{i}]": list(args[i].shape) if isinstance(args[i], torch.Tensor) else type(args[i]).__name__
173+ for i in output_indices}
174+ log.debug("[ACC_DEBUG] kernel=%s invocation=#%s inputs=%s outputs=%s",
175+ kernel_name, invocation, input_shapes, output_shapes)
176+ _input_snapshot = {}
177+ for idx in fx_module.call_args_mapping[:fx_module.num_inputs]:
178+ if isinstance(args[idx], torch.Tensor):
179+ _input_snapshot[idx] = args[idx].cpu()
180+ 
133 fx_graph_call(*fx_args)181 fx_graph_call(*fx_args)
134 182 
135 launcher(*args, **kwargs, stream=stream)183 launcher(*args, **kwargs, stream=stream)
136 184 
137- compare_outputs(185+ passed = compare_outputs(
138 [args[i] for i in call_outputs_indices],186 [args[i] for i in call_outputs_indices],
139 fx_args[fx_module.num_inputs:],187 fx_args[fx_module.num_inputs:],
140 kernel_name=kernel_name,188 kernel_name=kernel_name,
@@ -142,6 +190,33 @@ def check_accuracy_triton(*args, launcher, grid, stream, inductor_meta, **kwargs
142 dump_path=dump_path,190 dump_path=dump_path,
143 )191 )
144 192 
193+ if not passed and dump_path:
194+ fail_path = os.path.join(dump_path, f'data_fail_{invocation}.pth')
195+ fail_args = list(args)
196+ for idx, cpu_tensor in _input_snapshot.items():
197+ fail_args[idx] = cpu_tensor
198+ torch.save(fail_args, fail_path)
199+ log.warning("[ACC_DEBUG] kernel=%s invocation=#%s FAIL: saved input snapshot to data_fail_%s.pth",
200+ kernel_name, invocation, invocation)
201+ 
202+ if not passed and _recipe_used:
203+ kernel_int_values = [arg for arg in args if isinstance(arg, int)]
204+ for pos, ((kidx, d), val) in _recipe_resolved.items():
205+ if val in kernel_int_values:
206+ log.warning(
207+ "[ACC_DEBUG] kernel=%s FAIL: scalar pos=%s resolved=%s "
208+ "(from args[%s].shape[%s]), matches kernel int arg. "
209+ "Likely a real kernel bug. dump_path=%s",
210+ kernel_name, pos, val, kidx, d, dump_path,
211+ )
212+ else:
213+ log.warning(
214+ "[ACC_DEBUG] kernel=%s FAIL: scalar pos=%s resolved=%s "
215+ "(from args[%s].shape[%s]), NOT found in kernel int args %s. "
216+ "Likely a tool artifact (scalar resolution mismatch). dump_path=%s",
217+ kernel_name, pos, val, kidx, d, kernel_int_values, dump_path,
218+ )
219+ 
145 for arg in fx_args:220 for arg in fx_args:
146 del arg221 del arg
147 return True222 return True
@@ -1790,8 +1790,23 @@ class NPUCachingAutotuner(CachingAutotuner):
1790 if dump_path is None:1790 if dump_path is None:
1791 log.warning("data dump for kernel %s failed, no valid dump_path is supplied.", self.get_fn_name())1791 log.warning("data dump for kernel %s failed, no valid dump_path is supplied.", self.get_fn_name())
1792 return False1792 return False
1793+ if not hasattr(self, '_dump_counter'):
1794+ self._dump_counter = 0
1795+ 
1793 data_dump_path = os.path.join(dump_path, 'data.pth')1796 data_dump_path = os.path.join(dump_path, 'data.pth')
1794- torch.save(args, data_dump_path)1797+ input_info = {
1798+ i: list(arg.shape) if isinstance(arg, torch.Tensor) else type(arg).__name__
1799+ for i, arg in enumerate(args)
1800+ }
1801+ 
1802+ if self._dump_counter == 0:
1803+ log.info("[ACC_DEBUG] %s invocation=#1 saving args=%s to data.pth",
1804+ self.get_fn_name(), input_info)
1805+ torch.save(args, data_dump_path)
1806+ self._dump_counter += 1
1807+ else:
1808+ log.debug("[ACC_DEBUG] %s invocation=#%s skip save (data.pth preserved from invocation #1, args=%s)",
1809+ self.get_fn_name(), self._dump_counter + 1, input_info)
1795 return True1810 return True
1796 1811 
1797 def get_fn_name(self):1812 def get_fn_name(self):
@@ -1820,7 +1835,14 @@ class NPUCachingAutotuner(CachingAutotuner):
1820 "Please disable aclgraph before enabling INDUCTOR_ASCEND_CHECK_ACCURACY "1835 "Please disable aclgraph before enabling INDUCTOR_ASCEND_CHECK_ACCURACY "
1821 "/ INDUCTOR_ASCEND_DUMP_FX_GRAPH, or unset these environment variables."1836 "/ INDUCTOR_ASCEND_DUMP_FX_GRAPH, or unset these environment variables."
1822 )1837 )
1823- _ = self.data_dump(*args)1838+ # Strip runtime_block args from the end before saving for replay.
1839+ # data_dump receives *launch_args (= kernel_args + runtime_blocks),
1840+ # but the replayed script will call run() which re-appends runtime_blocks
1841+ # via _build_runtime_launch_args. Saving only kernel_args avoids a
1842+ # double-append that overflows positional parameters into "stream".
1843+ num_rb = len(self.runtime_block_arg_names or ())
1844+ args_for_dump = args[:-num_rb] if num_rb else args
1845+ _ = self.data_dump(*args_for_dump)
1824 1846 
1825 if npu_config.check_accuracy:1847 if npu_config.check_accuracy:
1826 if check_accuracy_triton(1848 if check_accuracy_triton(