scripts/triage_kernel_helpers.py
scripts/triage_kernel_helpers.pyBrowse 18 files
22,446 tokens
93,873 bytes
Token encoding: o200k_base
Snapshot a9fb1c3
← Back to SKILL.md
1"""Internal kernel attribution helpers for triage-only torch-profiler analysis."""2 3from __future__ import annotations4 5import json6import re7from bisect import bisect_right8from collections import Counter, defaultdict9from dataclasses import dataclass, field10from functools import lru_cache11from pathlib import Path12from typing import DefaultDict, Dict, Iterable, List, Optional, Sequence, Tuple13 14from profile_common import (15 coerce_optional_int,16 contains_any_keyword,17 extract_trace_events,18 has_stream_marker,19 is_annotation_event,20 is_complete_duration_event,21 is_non_kernel_trace_category,22 is_trace_metadata_name,23 looks_like_python_scope_name,24 normalize_repo_relative_path,25 normalize_text,26 select_heaviest_pid,27)28 29CATEGORY_PATTERNS: List[Tuple[str, Tuple[str, ...]]] = [30 (31 "hybrid_linear",32 (33 "gdn",34 "gated_delta",35 "mamba",36 "selective_scan",37 "ssd",38 "causal_conv",39 "ssm",40 ),41 ),42 (43 "attention",44 (45 "flash_attn",46 "flashattention",47 "flash_attention",48 "fmha",49 "attention",50 "mla",51 "paged_attention",52 "decode_attention",53 ),54 ),55 (56 "moe",57 (58 "fused_moe",59 "grouped_mm",60 "groupgemm",61 "group_gemm",62 "moe",63 "expert",64 "groupproblemshape",65 ),66 ),67 (68 "gemm",69 (70 "gemm",71 "gemv",72 "matmul",73 "cublas",74 "cutlass",75 "wgmma",76 "mma",77 "bmm",78 "nvjet",79 ),80 ),81 (82 "norm",83 (84 "rmsnorm",85 "layernorm",86 "_norm_",87 " norm",88 "normkernel",89 ),90 ),91 ("rope", ("rotary", "rope", "mrope")),92 ("softmax", ("softmax",)),93 ("activation", ("silu", "gelu", "relu", "act_and_mul", "sigmoid")),94 ("quantize", ("quant", "fp8", "mxfp", "nvfp4", "dequant", "cvt")),95 (96 "reduce_topk",97 ("topk", "reduce", "argmax", "argtopk", "sampling", "multinomial"),98 ),99 (100 "sampling_io",101 (102 "prepare_inputs",103 "write_req_to",104 "catarraybatched",105 "prepare_next",106 "copy_next",107 ),108 ),109 (110 "elementwise",111 (112 "elementwise",113 "vectorized_elementwise_kernel",114 "unrolled_elementwise_kernel",115 "gpu_kernel_impl",116 "binary_internal",117 "unaryfunctor",118 "add_kernel",119 "sub_kernel",120 "mul_kernel",121 "div_",122 "floor_kernel",123 "log_kernel",124 "neg_kernel",125 ),126 ),127]128 129COMMUNICATION_STRONG_KEYWORDS = (130 "nccl",131 "allreduce",132 "all_reduce",133 "reduce_scatter",134 "allgather",135 "all_gather",136 "alltoall",137 "all_to_all",138 "cross_device_reduce",139 "deepep",140 "mooncake",141)142 143COMMUNICATION_WEAK_KEYWORDS = (144 "broadcast",145 "dispatch",146 "combine",147)148 149MEMORY_STRONG_KEYWORDS = (150 "memcpy",151 "memset",152 "dma",153 "prefetch",154)155 156MEMORY_WEAK_KEYWORDS = (157 "copy",158 "fill",159)160 161COMPUTE_HINT_KEYWORDS = (162 "gemm",163 "gemv",164 "matmul",165 "cublas",166 "cutlass",167 "wgmma",168 "mma",169 "bmm",170 "nvjet",171 "fmha",172 "attention",173 "flash_attn",174 "flashattention",175 "flash_attention",176 "grouped_mm",177 "groupgemm",178 "moe",179 "expert",180)181 182NOISE_FRAME_PREFIXES = (183 "threading.py(",184 "multiprocessing/",185 "contextlib.py(",186 "torch/utils/_contextlib.py(",187 "runpy.py(",188 "asyncio/",189 "selectors.py(",190 "queue.py(",191 "socket.py(",192 "tqdm/_monitor.py(",193 "<string>(",194 "<built-in method ",195)196 197LOW_LEVEL_FRAME_PREFIXES = (198 "triton/runtime/",199 "triton/backends/",200 "torch/_ops.py",201 "torch/nn/modules/module.py",202)203 204LOW_SIGNAL_FUNCTION_TOKENS = (205 "__torch_function__",206 "__torch_dispatch__",207 "__call__",208 "_call_impl",209 "_wrapped_call_impl",210)211 212LOW_SIGNAL_PATH_TOKENS = (213 "model_executor/parameter.py:",214 "model_executor/cuda_graph_runner.py:",215 "compilation/cuda_graph.py:",216 "pyexecutor/cuda_graph_runner.py:",217 "pyexecutor/py_executor.py:",218 "_torch/utils.py:",219 "torch/fx/graph_module.py:",220)221 222 223@dataclass224class KernelEvent:225 name: str226 canonical_name: str227 category: str228 stage: str229 pid: str230 tid: str231 ts: float232 dur: float233 external_id: Optional[int]234 correlation: Optional[int] = None235 236 237@dataclass238class CpuOpEvent:239 name: str240 pid: str241 tid: str242 ts: float243 dur: float244 external_id: int245 246 247@dataclass248class LaunchEvent:249 name: str250 pid: str251 tid: str252 ts: float253 dur: float254 correlation: int255 256 257@dataclass258class PythonFrame:259 name: str260 normalized_name: str261 pid: str262 tid: str263 ts: float264 dur: float265 python_id: Optional[int]266 parent_id: Optional[int]267 end_ts: float268 priority: int269 270 271@dataclass272class TimedEventIndex:273 events: List[object]274 start_ts: List[float]275 276 277@dataclass278class FrameResolution:279 location: str280 stack: str281 282 283@dataclass(frozen=True)284class StageAnnotation:285 stage: str286 ts: float287 end_ts: float288 external_id: Optional[int]289 is_gpu: bool290 291 292@dataclass(frozen=True)293class StageWindow:294 stage: str295 ts: float296 end_ts: float297 298 299@dataclass300class Aggregate:301 total_us: float = 0.0302 count: int = 0303 max_us: float = 0.0304 305 @property306 def avg_us(self) -> float:307 return self.total_us / self.count if self.count else 0.0308 309 310@dataclass311class MappingSiteAggregate:312 total_us: float = 0.0313 count: int = 0314 cpu_ops: Counter = field(default_factory=Counter)315 stacks: Counter = field(default_factory=Counter)316 317 318@dataclass319class KernelRow:320 name: str321 category: str322 aggregate: Aggregate323 location: str324 cpu_op: str325 entry: Optional[dict]326 327 @property328 def total_us(self) -> float:329 return self.aggregate.total_us330 331 332@dataclass333class FusionOpportunity:334 pattern: str335 status: str336 confidence: str337 related_us: float338 evidence: str339 current_locations: str340 candidate_path: str341 rationale: str342 covered_row_keys: Tuple[Tuple[str, str, str], ...] = field(343 default_factory=tuple, repr=False344 )345 pattern_span: int = field(default=1, repr=False)346 has_active_match: bool = field(default=False, repr=False)347 priority: int = field(default=0, repr=False)348 subsumes: Tuple[str, ...] = field(default_factory=tuple, repr=False)349 350 351@dataclass(frozen=True)352class FusionPatternSpec:353 pattern: str354 candidate_path: str355 active_keywords: Tuple[str, ...] = ()356 split_groups: Tuple[Tuple[str, ...], ...] = ()357 rationale_hint: str = ""358 origin: str = "mainline"359 model_include: Tuple[str, ...] = ()360 model_exclude: Tuple[str, ...] = ()361 min_tp_size: int = 1362 require_tp: bool = False363 min_share: float = 0.25364 likely_share: float = 3.0365 priority: int = 0366 subsumes: Tuple[str, ...] = ()367 368 369FUSION_PATTERN_REGISTRY: Tuple[FusionPatternSpec, ...] = (370 FusionPatternSpec(371 pattern="Fused residual add + RMSNorm",372 candidate_path=(373 "python/sglang/srt/layers/layernorm.py"374 "<br>python/sglang/srt/layers/quantization/modelslim/modelslim.py"375 ),376 active_keywords=(377 "fused_add_rmsnorm",378 "gemma_fused_add_rmsnorm",379 "npu_add_rms_norm",380 "add_rmsnorm_bias",381 ),382 rationale_hint=(383 "Residual add plus RMSNorm already has fused implementations across"384 " several backends."385 ),386 min_share=0.1,387 likely_share=1.0,388 ),389 FusionPatternSpec(390 pattern="FlashInfer unified allreduce_fusion",391 candidate_path=(392 "python/sglang/srt/layers/flashinfer_comm_fusion.py"393 "<br>python/sglang/srt/layers/layernorm.py"394 "<br>python/sglang/srt/layers/communicator.py"395 ),396 active_keywords=(397 "allreduce_fusion",398 "fusedaddrmsnormkernel",399 "flashinfer_comm_fusion.py",400 ),401 split_groups=(402 (403 "cross_device_reduce",404 "allreduce",405 "all_reduce",406 "custom_all_reduce_ops.py",407 ),408 ("rmsnorm", "layernorm", "fused_add_rmsnorm", "layernorm.py"),409 ),410 rationale_hint=(411 "FlashInfer has a TP all-reduce plus residual/RMSNorm fusion path."412 ),413 require_tp=True,414 min_tp_size=2,415 min_share=0.5,416 likely_share=4.0,417 ),418 FusionPatternSpec(419 pattern="AITER allreduce fusion",420 candidate_path=(421 "python/sglang/srt/distributed/communication_op.py"422 "<br>python/sglang/srt/layers/communicator.py"423 "<br>python/sglang/srt/layers/layernorm.py"424 ),425 active_keywords=(426 "tensor_model_parallel_fused_allreduce_rmsnorm",427 "apply_aiter_all_reduce_fusion",428 "custom_fused_ar_rms",429 ),430 split_groups=(431 ("allreduce", "all_reduce", "cross_device_reduce"),432 ("rmsnorm", "layernorm"),433 ),434 rationale_hint=(435 "ROCm already has an AITER fused all-reduce plus RMSNorm family."436 ),437 require_tp=True,438 min_tp_size=2,439 min_share=0.5,440 likely_share=4.0,441 ),442 FusionPatternSpec(443 pattern="Fused activation-and-mul (SwiGLU / GeGLU)",444 candidate_path="python/sglang/srt/layers/activation.py",445 active_keywords=("silu_and_mul", "gelu_and_mul", "npu_swiglu"),446 rationale_hint=(447 "Packed MLP activation and multiply already has dedicated fused ops."448 ),449 min_share=0.1,450 likely_share=1.0,451 ),452 FusionPatternSpec(453 pattern="In-place QK RMSNorm",454 candidate_path=(455 "python/sglang/srt/models/utils.py"456 "<br>python/sglang/kernels/ops/layernorm/norm.py"457 ),458 active_keywords=("fused_inplace_qknorm", "minimaxm2rmsnormtp"),459 split_groups=(("apply_qk_norm", "q_norm", "k_norm", "qknorm"),),460 rationale_hint=(461 "Q/K normalization already has in-place or model-specific fused"462 " implementations."463 ),464 min_share=0.3,465 likely_share=2.0,466 ),467 FusionPatternSpec(468 pattern="Fused QK RMSNorm + RoPE",469 candidate_path=(470 "python/sglang/kernels/ops/attention/fused_qknorm_rope.py"471 "<br>python/sglang/srt/models/qwen3_moe.py"472 ),473 active_keywords=("fused_qknorm_rope", "fused_qk_norm_rope"),474 split_groups=(475 ("apply_qk_norm", "q_norm", "k_norm", "qknorm"),476 ("apply_rope", "rotary", "rope", "mrope"),477 ),478 rationale_hint=("SGLang has a fused QK-norm plus RoPE kernel family."),479 min_share=0.3,480 likely_share=2.0,481 priority=30,482 ),483 FusionPatternSpec(484 pattern="Fused QK RoPE reshape + KV cache write",485 candidate_path="python/sglang/srt/layers/attention/utils.py",486 active_keywords=("fused_qk_rope_reshape_and_cache",),487 split_groups=(488 ("rotary", "rope", "mrope"),489 ("reshape", "set_kv", "kv_cache", "cache write", "paged kv"),490 ),491 rationale_hint=(492 "Attention prep already has a fused RoPE plus reshape plus cache"493 " write path."494 ),495 min_share=0.4,496 likely_share=2.0,497 priority=40,498 subsumes=("Fused RoPE + KV cache store",),499 ),500 FusionPatternSpec(501 pattern="Fused RoPE + KV cache store",502 candidate_path=(503 "python/sglang/kernels/ops/attention/rope.py"504 "<br>python/sglang/srt/models/utils.py"505 ),506 active_keywords=("fused_set_kv_buffer",),507 split_groups=(508 ("rotary", "rope", "mrope"),509 ("set_kv_buffer", "kv cache write", "paged kv", "cache write"),510 ),511 rationale_hint=(512 "RoPE application and KV cache storage already have fused fast"513 " paths in several models."514 ),515 min_share=0.3,516 likely_share=1.5,517 priority=20,518 ),519 FusionPatternSpec(520 pattern="Fused decode metadata setup",521 candidate_path=("python/sglang/srt/layers/attention/flashattention_backend.py"),522 active_keywords=(523 "normal_decode_set_metadata",524 "cache_seqlens_int32",525 "cu_seqlens_k",526 "swa_page_table",527 ),528 rationale_hint=(529 "Decode metadata setup already has a fused Triton preparation path."530 ),531 min_share=0.05,532 likely_share=0.5,533 ),534 FusionPatternSpec(535 pattern="NSA fused metadata copy for graph replay",536 candidate_path="python/sglang/kernels/ops/attention/fused_metadata_copy.py",537 active_keywords=(538 "fused_metadata_copy",539 "fused_metadata_copy_multi",540 "fused_nsa_cache_seqlens",541 "fused_flashmla_metadata",542 ),543 rationale_hint=(544 "NSA replay metadata copies are already fused into one-kernel families."545 ),546 min_share=0.02,547 likely_share=0.2,548 ),549 FusionPatternSpec(550 pattern="DeepSeek MLA fused projection + norm + RoPE",551 candidate_path=(552 "python/sglang/srt/models/deepseek_common/attention_forward_methods/"553 "forward_mla_fused_rope_cpu.py"554 "<br>python/sglang/srt/models/deepseek_common/attention_forward_methods/"555 "forward_mla_fused_rope_rocm.py"556 ),557 active_keywords=(558 "qkv_proj_with_rope_fused_weight",559 "fused_qkv_a_proj_with_mqa",560 "forward_absorb_fused_mla_rope",561 ),562 split_groups=(563 ("mla", "qkv_a_proj", "q_a_proj"),564 ("qknorm", "rmsnorm", "apply_qk_norm"),565 ("rope", "rotary"),566 ),567 rationale_hint=(568 "DeepSeek MLA has backend-specific fused projection, norm, and"569 " RoPE prep paths."570 ),571 model_include=("deepseek", "glm"),572 min_share=0.4,573 likely_share=2.0,574 priority=80,575 subsumes=("Fused QK RMSNorm + RoPE",),576 ),577 FusionPatternSpec(578 pattern="Fused QK RoPE concat + MLA cache write",579 candidate_path=(580 "python/sglang/srt/layers/rocm_linear_utils.py"581 "<br>python/sglang/srt/models/deepseek_common/attention_forward_methods/"582 "forward_mla.py"583 ),584 active_keywords=("fused_qk_rope_cat_and_cache_mla", "set_mla_kv_buffer"),585 split_groups=(586 ("mla", "rope", "rotary"),587 ("cache", "kv_buffer", "concat"),588 ),589 rationale_hint=(590 "MLA RoPE packing and cache write already have fused backend paths."591 ),592 model_include=("deepseek", "glm"),593 min_share=0.3,594 likely_share=1.5,595 priority=85,596 subsumes=("Fused RoPE + KV cache store",),597 ),598 FusionPatternSpec(599 pattern="Qwen3 decode fused QK norm + 3D mRoPE + KV cache write",600 candidate_path="python/sglang/srt/models/qwen3.py",601 active_keywords=("fused_qk_norm_mrope_3d_cache_pts_quant_shuffle",),602 split_groups=(603 ("apply_qk_norm", "q_norm", "k_norm", "qknorm"),604 ("mrope", "3d rope", "rotary"),605 ("cache", "kv_buffer", "paged kv", "cache write"),606 ),607 rationale_hint=(608 "Qwen3-style decode already has a fused QK-norm plus 3D mRoPE plus"609 " cache-write path."610 ),611 model_include=("qwen3",),612 model_exclude=("qwen3.5", "qwen3_5"),613 min_share=0.4,614 likely_share=2.0,615 priority=90,616 subsumes=(617 "Fused QK RMSNorm + RoPE",618 "Fused QK RoPE reshape + KV cache write",619 "Fused RoPE + KV cache store",620 ),621 ),622 FusionPatternSpec(623 pattern="Fused MoE router / top-k / softcapping",624 candidate_path="python/sglang/srt/layers/moe/router.py",625 active_keywords=("fusedmoerouter", "fused_moe_router"),626 split_groups=(627 ("router", "gate", "router logits"),628 ("topk", "softmax", "softcap", "tanh"),629 ),630 rationale_hint=(631 "MoE routing already has fused router, softcap, and top-k kernels."632 ),633 min_share=0.3,634 likely_share=1.5,635 priority=30,636 ),637 FusionPatternSpec(638 pattern="Fused MoE grouped-topk / gate kernels",639 candidate_path="python/sglang/srt/layers/moe/topk.py",640 active_keywords=(641 "fused_topk_deepseek",642 "moe_fused_gate",643 "aiter_fused_topk",644 "kimi_k2_moe_fused_gate",645 ),646 split_groups=(647 ("grouped_topk", "topk", "biased_grouped_topk"),648 ("gate", "router", "renorm", "routed scaling"),649 ),650 rationale_hint=(651 "Grouped-topk, bias handling, and routed scaling already have fused"652 " gate kernels."653 ),654 min_share=0.3,655 likely_share=1.5,656 priority=50,657 subsumes=("Fused MoE router / top-k / softcapping",),658 ),659 FusionPatternSpec(660 pattern="Qwen-style shared-expert append into routed top-k output",661 candidate_path=(662 "python/sglang/srt/models/qwen2_moe.py"663 "<br>python/sglang/srt/layers/moe/moe_runner/triton_utils/"664 "fused_moe_triton_kernels.py"665 ),666 active_keywords=(667 "_append_shared_to_topk_output",668 "fused_append_shared_experts_with_weights",669 "_fused_append_shared_experts_with_weights_kernel",670 ),671 split_groups=(672 ("_append_shared_to_topk_output", "topk", "grouped_topk"),673 ("shared_expert", "shared_expert_gate", "sigmoid"),674 ),675 rationale_hint=(676 "Qwen-style shared experts can already be appended into routed top-k"677 " output in one Triton prep kernel before fused MoE execution."678 ),679 min_share=0.05,680 likely_share=0.5,681 priority=55,682 ),683 FusionPatternSpec(684 pattern="Fused MoE sum + all-reduce",685 candidate_path=("python/sglang/srt/layers/moe/fused_moe_triton/fused_moe.py"),686 active_keywords=("fuse_sum_all_reduce", "enable_fused_moe_sum_all_reduce"),687 split_groups=(688 ("fused_moe", "expert", "moe"),689 ("allreduce", "all_reduce", "cross_device_reduce"),690 ),691 rationale_hint=(692 "The second MoE GEMM already has a fused sum-plus-all-reduce path."693 ),694 require_tp=True,695 min_tp_size=2,696 min_share=0.4,697 likely_share=2.0,698 ),699 FusionPatternSpec(700 pattern="Fused MoE activation + quant / re-quant",701 candidate_path=(702 "python/sglang/srt/layers/moe/ep_moe/kernels.py"703 "<br>python/sglang/kernels/ops/quantization/nvfp4_gemm_swiglu_nvfp4_quant.py"704 "<br>python/sglang/srt/layers/moe/cutlass_w4a8_moe.py"705 ),706 active_keywords=(707 "silu_and_mul_scaled_fp4",708 "npu_dequant_swiglu_quant",709 "swiglu_quant",710 ),711 split_groups=(712 ("silu", "gelu", "act_and_mul"),713 ("quant", "fp8", "mxfp", "nvfp4", "dequant"),714 ),715 rationale_hint=(716 "Quantized MoE backends already fuse activation with re-quantization."717 ),718 min_share=0.3,719 likely_share=1.5,720 ),721 FusionPatternSpec(722 pattern="DeepSeek comm-prep fused RMSNorm + quant / flatten-quant",723 candidate_path=(724 "python/sglang/srt/layers/communicator.py"725 "<br>python/sglang/srt/models/deepseek_common/attention_forward_methods/"726 "forward_mla.py"727 "<br>python/sglang/srt/models/deepseek_common/attention_forward_methods/"728 "forward_mha.py"729 ),730 active_keywords=(731 "fused_rms_fp8_group_quant",732 "fused_rms_mxfp4_quant",733 "fused_flatten_fp8_group_quant",734 "fused_flatten_mxfp4_quant",735 ),736 split_groups=(737 ("rmsnorm", "layernorm", "flatten"),738 ("fp8", "mxfp4", "quant"),739 ),740 rationale_hint=(741 "DeepSeek comm preparation already fuses norm or flatten work with"742 " quantization."743 ),744 model_include=("deepseek", "glm"),745 min_share=0.3,746 likely_share=1.5,747 ),748 FusionPatternSpec(749 pattern="NSA fused top-k transform / page-table build",750 candidate_path="python/sglang/srt/layers/attention/nsa_backend.py",751 active_keywords=(752 "fast_topk_transform_fused",753 "fast_topk_transform_ragged_fused",754 ),755 rationale_hint=(756 "NSA top-k metadata preparation already has fused transform kernels."757 ),758 min_share=0.05,759 likely_share=0.3,760 ),761 FusionPatternSpec(762 pattern="NSA fused quantize + indexed K-cache store",763 candidate_path=(764 "python/sglang/kernels/ops/attention/fused_store_index_cache.py"765 "<br>python/sglang/srt/layers/attention/nsa/nsa_indexer.py"766 ),767 active_keywords=("fused_store_index_k_cache",),768 split_groups=(769 ("act_quant", "quant", "scale_buffer"),770 ("index_k", "cache", "store"),771 ),772 rationale_hint=(773 "NSA already has a fused quantize-and-indexed-store kernel family."774 ),775 min_share=0.2,776 likely_share=1.0,777 ),778 FusionPatternSpec(779 pattern="Fused sampling temperature + softmax",780 candidate_path=(781 "python/sglang/srt/layers/fused_sampling.py"782 "<br>python/sglang/srt/layers/sampler.py"783 ),784 active_keywords=("fused_temperature_softmax",),785 split_groups=(786 ("temperature", "temp_scale"),787 ("softmax", "sampling"),788 ),789 rationale_hint=(790 "Decode-time sampling already has fused temperature and softmax kernels."791 ),792 min_share=0.05,793 likely_share=0.5,794 ),795 FusionPatternSpec(796 pattern="Fused logit softcap",797 candidate_path=(798 "python/sglang/srt/layers/elementwise.py"799 "<br>python/sglang/srt/layers/logits_processor.py"800 ),801 active_keywords=("fused_softcap", "final_logit_softcapping"),802 rationale_hint=(803 "Logit softcap math already has dedicated fused elementwise kernels."804 ),805 min_share=0.02,806 likely_share=0.2,807 ),808 FusionPatternSpec(809 pattern="PR #20667 Qwen3.5 fused QK norm + RoPE + KV cache write",810 candidate_path=(811 "PR #20667"812 "<br>python/sglang/srt/models/qwen3_5.py"813 "<br>python/sglang/srt/models/utils.py"814 ),815 active_keywords=(816 "fused_qk_norm_rope_cache_pts_quant_shuffle",817 "fused_qk_norm_mrope_3d_cache_pts_quant_shuffle",818 ),819 split_groups=(820 ("apply_qk_norm", "qknorm", "q_norm", "k_norm"),821 ("rotary", "rope", "mrope"),822 ("cache", "kv_buffer", "cache write"),823 ),824 rationale_hint=(825 "Open SGLang ROCm PR wires a fused QK-norm plus RoPE plus KV-cache"826 " family for Qwen3.5."827 ),828 origin="inflight",829 model_include=("qwen3.5", "qwen3_5"),830 min_share=0.4,831 likely_share=2.0,832 priority=100,833 subsumes=(834 "Fused QK RMSNorm + RoPE",835 "Fused QK RoPE reshape + KV cache write",836 "Fused RoPE + KV cache store",837 ),838 ),839 FusionPatternSpec(840 pattern="PR #22392 CUTLASS FP8 scaled MM replacing nvjet",841 candidate_path=(842 "PR #22392"843 "<br>python/sglang/kernels/aot/python/sgl_kernel/gemm.py"844 "<br>python/sglang/srt/layers/quantization/fp8_utils.py"845 ),846 active_keywords=("cutlass_scaled_mm", "fp8_scaled_mm"),847 split_groups=(848 ("nvjet", "_scaled_mm"),849 ("memset", "memcpy128"),850 ),851 rationale_hint=(852 "Open SGLang PR replaces nvjet FP8 GEMM with CUTLASS to remove"853 " memset bubbles and extra copies."854 ),855 origin="inflight",856 min_share=0.2,857 likely_share=1.0,858 priority=90,859 ),860 FusionPatternSpec(861 pattern="SGLang LTX2 fused Ada values",862 candidate_path=(863 "PR #29390"864 "<br>python/sglang/kernels/ops/diffusion/triton/ltx2_ada_values.py"865 "<br>python/sglang/multimodal_gen/runtime/models/dits/ltx_2.py"866 ),867 active_keywords=(868 "ltx2_ada_values9",869 "ltx2_ada_values",870 "LTX2TransformerBlock",871 ),872 split_groups=(873 ("scale_shift_table", "timestep", "reshape"),874 ("get_ada_values", "ada", "adaln"),875 ("slice", "split", "unbind"),876 ),877 rationale_hint=(878 "SGLang mainline fuses LTX-2.3 Ada value materialization for"879 " video/audio streams; split Ada table add/reshape/slice ladders"880 " should be checked against this diffusion Triton kernel first."881 ),882 origin="upstream",883 model_include=("ltx", "ltx-2", "ltx2"),884 min_share=0.2,885 likely_share=1.0,886 ),887 FusionPatternSpec(888 pattern="SGLang LTX2 residual-gate add CUDA fast path",889 candidate_path=(890 "PR #29361"891 "<br>python/sglang/kernels/ops/diffusion/residual_gate_add.py"892 "<br>python/sglang/kernels/jit/csrc/diffusion/residual_gate_add.cuh"893 "<br>python/sglang/multimodal_gen/runtime/models/dits/ltx_2.py"894 ),895 active_keywords=(896 "diffusion_residual_gate_add",897 "residual_gate_add",898 "_ltx2_residual_gate_add",899 ),900 split_groups=(901 ("add", "mul", "gate"),902 ("residual", "update", "gate"),903 ("hidden_states", "attn_hidden_states", "gate"),904 ),905 rationale_hint=(906 "SGLang mainline fuses LTX2 residual + update * gate sites into"907 " a CUDA custom op; split add/mul gate ladders should be checked"908 " against this path before proposing a new diffusion elementwise"909 " fusion."910 ),911 origin="upstream",912 model_include=("ltx", "ltx-2", "ltx2"),913 min_share=0.2,914 likely_share=1.0,915 ),916 FusionPatternSpec(917 pattern="TokenSpeed CuTe DSL MLA prefill / decode",918 candidate_path=(919 "python/tokenspeed/runtime/layers/attention/backends/tokenspeed_mla.py"920 "<br>tokenspeed-mla/python/tokenspeed_mla/mla_decode.py"921 "<br>tokenspeed-mla/python/tokenspeed_mla/mla_prefill.py"922 "<br>tokenspeed-kernel/python/tokenspeed_kernel/ops/attention/"923 "tokenspeed_mla/__init__.py"924 ),925 active_keywords=(926 "tokenspeed_mla_decode",927 "tokenspeed_mla_prefill",928 "BlackwellMultiHeadLatentAttentionForward",929 ),930 split_groups=(931 ("mla", "flashmla", "attention", "fmha"),932 ("prefill", "decode", "verify"),933 ("fp8", "kv_cache", "page_table"),934 ),935 rationale_hint=(936 "TokenSpeed ships Blackwell CuTe DSL MLA prefill/decode kernels;"937 " split MLA support kernels should be checked against backend"938 " selection before being called novel."939 ),940 origin="upstream",941 model_include=("deepseek", "kimi", "qwen3.5", "qwen3_5"),942 min_share=0.4,943 likely_share=2.0,944 ),945 FusionPatternSpec(946 pattern="TokenSpeed MLA KV pack + FP8 quantize",947 candidate_path=(948 "tokenspeed-mla/python/tokenspeed_mla/mla_kv_pack_quantize_fp8.py"949 "<br>tokenspeed-kernel/python/tokenspeed_kernel/ops/attention/"950 "tokenspeed_mla/__init__.py"951 ),952 active_keywords=(953 "_mla_kv_pack_quantize_fp8_kernel",954 "mla_kv_pack_quantize_fp8",955 ),956 split_groups=(957 ("k_nope", "k_pe", "cat", "concat", "pack"),958 ("quant", "fp8", "float8"),959 ("v", "kv", "cache"),960 ),961 rationale_hint=(962 "TokenSpeed fuses MLA K/V pack, concat, and FP8 quantization into"963 " one Triton kernel for chunked prefill."964 ),965 origin="upstream",966 model_include=("deepseek", "kimi", "qwen3.5", "qwen3_5"),967 min_share=0.2,968 likely_share=1.0,969 ),970 FusionPatternSpec(971 pattern="TokenSpeed fused top-k + top-p sampling",972 candidate_path=(973 "tokenspeed-kernel/python/tokenspeed_kernel/thirdparty/cuda/" # codespell:ignore thirdparty974 "fused_topk_topp.py"975 "<br>tokenspeed-kernel/python/tokenspeed_kernel/thirdparty/cuda/" # codespell:ignore thirdparty976 "csrc/fused_topk_topp/fused_topk_topp.cu"977 ),978 active_keywords=("fused_topk_topp", "fused_topk_topp_renorm"),979 split_groups=(980 ("topk", "top_k"),981 ("topp", "top_p"),982 ("sampling", "renorm", "softmax"),983 ),984 rationale_hint=(985 "TokenSpeed has a fused top-k/top-p renormalization path for"986 " decode sampling."987 ),988 origin="upstream",989 min_share=0.1,990 likely_share=0.8,991 ),992 FusionPatternSpec(993 pattern="TokenSpeed persistent lm_head GEMM",994 candidate_path=(995 "tokenspeed-kernel/python/tokenspeed_kernel/thirdparty/cuda/" # codespell:ignore thirdparty996 "lm_head_gemm.py"997 "<br>tokenspeed-kernel/python/tokenspeed_kernel/thirdparty/cuda/" # codespell:ignore thirdparty998 "csrc/lm_head_gemm.cu"999 ),1000 active_keywords=("lm_head_gemm",),1001 split_groups=(1002 ("lm_head", "logits", "vocab"),1003 ("gemm", "matmul", "linear"),1004 ),1005 rationale_hint=(1006 "TokenSpeed has a shape-gated persistent lm_head GEMM path; visible"1007 " lm_head matmul ladders should be compared against it."1008 ),1009 origin="upstream",1010 model_include=("kimi", "qwen"),1011 min_share=0.2,1012 likely_share=1.0,1013 ),1014 FusionPatternSpec(1015 pattern="TokenSpeed NVFP4 GEMM + SwiGLU + quant",1016 candidate_path=(1017 "tokenspeed-kernel/python/tokenspeed_kernel/thirdparty/cute_dsl/" # codespell:ignore thirdparty1018 "nvfp4_gemm_swiglu_nvfp4_quant.py"1019 ),1020 active_keywords=("nvfp4_gemm_swiglu_nvfp4_quant",),1021 split_groups=(1022 ("gemm", "nvfp4", "fp4"),1023 ("swiglu", "silu", "activation", "mul"),1024 ("quant", "scale", "sfc"),1025 ),1026 rationale_hint=(1027 "TokenSpeed's CuTe DSL kernel fuses NVFP4 GEMM, SwiGLU, and"1028 " optional output quantization in one expert-style path."1029 ),1030 origin="upstream",1031 min_share=0.3,1032 likely_share=1.5,1033 ),1034 FusionPatternSpec(1035 pattern="vLLM-origin Attention + Quantization",1036 candidate_path=(1037 "vllm/compilation/passes/fusion/attn_quant_fusion.py"1038 "<br>vllm/v1/attention/ops/merge_attn_states.py"1039 "<br>vllm/csrc/attention/merge_attn_states.cu"1040 "<br>vllm/docs/design/fusions.md"1041 ),1042 active_keywords=(1043 "merge_attn_states",1044 "attn_quant_fusion",1045 "output_scale",1046 "output_group_scale",1047 ),1048 split_groups=(1049 ("attention", "flash_attn", "flashattention", "mla"),1050 ("quant", "fp8", "nvfp4", "group_scale"),1051 ),1052 rationale_hint=(1053 "vLLM combines attention merge with attention-epilogue quantization."1054 ),1055 origin="upstream",1056 min_share=0.3,1057 likely_share=1.5,1058 ),1059 FusionPatternSpec(1060 pattern="vLLM-origin DSV3.2 fused indexer projections",1061 candidate_path=(1062 "vllm/model_executor/models/deepseek_v2.py"1063 "<br>vllm/model_executor/models/deepseek_mtp.py"1064 ),1065 active_keywords=("wk_weights_proj",),1066 split_groups=(1067 ("wk_weights_proj", "wk", "weights_proj"),1068 ("mergedcolumnparallellinear", "gemm", "matmul"),1069 ),1070 rationale_hint=(1071 "vLLM already fuses the paired `wk` and `weights_proj` indexer"1072 " projections into one DSV3.2 linear family."1073 ),1074 origin="upstream",1075 min_share=0.2,1076 likely_share=1.0,1077 ),1078 FusionPatternSpec(1079 pattern="vLLM-origin RMSNorm + Quantization",1080 candidate_path=(1081 "vllm/compilation/passes/fusion/rms_quant_fusion.py"1082 "<br>vllm/docs/design/fusions.md"1083 ),1084 active_keywords=(1085 "fused_add_rms_norm_static_fp8_quant",1086 "rms_quant_fusion",1087 "norm_quant",1088 ),1089 split_groups=(1090 ("rmsnorm", "layernorm", "fused_add_rms_norm"),1091 ("quant", "fp8", "fp4", "per-group"),1092 ),1093 rationale_hint=(1094 "vLLM already has a compile-time norm-plus-quant fusion family."1095 ),1096 origin="upstream",1097 min_share=0.3,1098 likely_share=1.5,1099 ),1100 FusionPatternSpec(1101 pattern="vLLM-origin SiLU+Mul + Quantization",1102 candidate_path=(1103 "vllm/compilation/passes/fusion/act_quant_fusion.py"1104 "<br>vllm/docs/design/fusions.md"1105 ),1106 active_keywords=(1107 "silu_mul_quant_fp4",1108 "fused_silu_mul_block_quant",1109 "act_quant_fusion",1110 ),1111 split_groups=(1112 ("silu", "gelu", "act_and_mul"),1113 ("quant", "fp8", "fp4", "block_quant"),1114 ),1115 rationale_hint=("vLLM has an activation-plus-quant fusion family."),1116 origin="upstream",1117 min_share=0.3,1118 likely_share=1.5,1119 ),1120 FusionPatternSpec(1121 pattern="vLLM-origin DSV3 router GEMM",1122 candidate_path=(1123 "vllm/model_executor/layers/fused_moe/router/gate_linear.py"1124 "<br>vllm/csrc/moe/dsv3_router_gemm_entry.cu"1125 ),1126 active_keywords=("dsv3_router_gemm", "fp32_router_gemm"),1127 split_groups=(1128 ("router", "gate", "router logits"),1129 ("gemm", "matmul", "cublas", "cutlass"),1130 ),1131 rationale_hint=(1132 "vLLM has a specialized DeepSeek router GEMM family for small"1133 " decode batches."1134 ),1135 origin="upstream",1136 min_share=0.3,1137 likely_share=1.5,1138 ),1139 FusionPatternSpec(1140 pattern="vLLM-origin GPT-OSS router GEMM",1141 candidate_path=(1142 "vllm/_custom_ops.py"1143 "<br>vllm/model_executor/layers/fused_moe/router/gate_linear.py"1144 "<br>vllm/csrc/moe/gpt_oss_router_gemm.cu"1145 ),1146 active_keywords=("gpt_oss_router_gemm",),1147 split_groups=(1148 ("router", "gate", "router logits", "gpt_oss"),1149 ("gemm", "matmul", "cublas", "cutlass"),1150 ),1151 rationale_hint=("vLLM has a GPT-OSS-specific router GEMM path."),1152 origin="upstream",1153 model_include=("gpt-oss", "gpt_oss"),1154 min_share=0.3,1155 likely_share=1.5,1156 ),1157 FusionPatternSpec(1158 pattern="vLLM-origin DeepSeek min-latency fused QKV-A projection",1159 candidate_path=(1160 "vllm/model_executor/models/deepseek_v2.py"1161 "<br>vllm/csrc/dsv3_fused_a_gemm.cu"1162 ),1163 active_keywords=("dsv3_fused_a_gemm", "fused_qkv_a_proj"),1164 split_groups=(1165 ("q_a_proj", "kv_a_proj", "weights_proj"),1166 ("gemm", "matmul", "cutlass", "cublas"),1167 ),1168 rationale_hint=(1169 "vLLM has a fused DeepSeek QKV-A projection family for decode"1170 " latency reduction."1171 ),1172 origin="upstream",1173 model_include=("deepseek", "glm"),1174 min_share=0.3,1175 likely_share=1.5,1176 ),1177 FusionPatternSpec(1178 pattern="PR #38621 fused QK norm + RoPE + cache + quant",1179 candidate_path=(1180 "PR #38621"1181 "<br>vllm/csrc/fused_qk_norm_rope_cache_quant.cu"1182 "<br>vllm/compilation/passes/fusion/qk_norm_rope_cache_quant_fusion.py"1183 ),1184 active_keywords=("fused_qk_norm_rope_cache_quant",),1185 split_groups=(1186 ("qknorm", "q_norm", "k_norm"),1187 ("rope", "rotary", "mrope"),1188 ("cache", "kv_buffer", "cache write"),1189 ("quant", "fp8", "nvfp4"),1190 ),1191 rationale_hint=(1192 "Open vLLM PR covers QK-norm plus RoPE plus cache plus quant as"1193 " one fusion family."1194 ),1195 origin="inflight",1196 min_share=0.4,1197 likely_share=2.0,1198 priority=100,1199 subsumes=("vLLM-origin Attention + Quantization",),1200 ),1201 FusionPatternSpec(1202 pattern="vLLM-origin MiniMax allreduce_rms kernels",1203 candidate_path="vllm/model_executor/models/minimax_m2.py",1204 active_keywords=("minimax_allreduce_rms", "minimax_allreduce_rmsnorm"),1205 split_groups=(1206 ("q_norm", "k_norm", "rmsnorm", "minimax"),1207 ("allreduce", "all_reduce", "cross_device_reduce"),1208 ),1209 rationale_hint=(1210 "vLLM includes the TRTLLM-derived MiniMax allreduce-plus-RMSNorm"1211 " kernel family."1212 ),1213 origin="upstream",1214 model_include=("minimax",),1215 min_share=0.3,1216 likely_share=1.5,1217 ),1218 FusionPatternSpec(1219 pattern="vLLM fused residual add + RMSNorm",1220 candidate_path=(1221 "vllm/_custom_ops.py<br>vllm/compilation/passes/fusion/rms_quant_fusion.py"1222 ),1223 active_keywords=(1224 "fused_add_rms_norm",1225 "fused_add_rms_norm_static_fp8_quant",1226 ),1227 rationale_hint=(1228 "vLLM exposes fused residual-add-plus-RMSNorm kernels and matching"1229 " compile-time hooks."1230 ),1231 origin="upstream",1232 min_share=0.1,1233 likely_share=1.0,1234 ),1235 FusionPatternSpec(1236 pattern="vLLM fused activation-and-mul",1237 candidate_path=(1238 "vllm/_custom_ops.py<br>vllm/compilation/passes/fusion/act_quant_fusion.py"1239 ),1240 active_keywords=(1241 "silu_and_mul",1242 "silu_and_mul_quant",1243 "silu_and_mul_per_block_quant",1244 "act_and_mul",1245 ),1246 rationale_hint=(1247 "vLLM ships fused activation-and-multiply kernels plus quantized"1248 " variants for the MLP epilogue."1249 ),1250 origin="upstream",1251 min_share=0.1,1252 likely_share=1.0,1253 ),1254 FusionPatternSpec(1255 pattern="TensorRT-LLM FlashInfer residual add + RMSNorm",1256 candidate_path=(1257 "tensorrt_llm/_torch/custom_ops/flashinfer_custom_ops.py"1258 "<br>tensorrt_llm/_torch/modules/rms_norm.py"1259 "<br>tensorrt_llm/_torch/auto_deploy/transform/library/fused_add_rms_norm.py"1260 ),1261 active_keywords=(1262 "flashinfer_fused_add_rmsnorm",1263 "flashinfer_gemma_fused_add_rmsnorm",1264 "flashinfer::norm::FusedAddRMSNormKernel",1265 "FusedAddRMSNormKernel",1266 "auto_deploy::flashinfer_fused_add_rms_norm_inplace",1267 ),1268 rationale_hint=(1269 "TensorRT-LLM exposes a FlashInfer fused residual-add plus RMSNorm"1270 " family, including AutoDeploy rewrites."1271 ),1272 origin="upstream",1273 min_share=0.1,1274 likely_share=1.0,1275 ),1276 FusionPatternSpec(1277 pattern="TensorRT-LLM Triton fused residual add + RMSNorm + FP8 quant",1278 candidate_path=(1279 "tensorrt_llm/_torch/auto_deploy/custom_ops/normalization/"1280 "triton_fused_add_rms_norm_quant_fp8.py"1281 "<br>tensorrt_llm/_torch/auto_deploy/transform/library/"1282 "fuse_rmsnorm_quant_fp8.py"1283 ),1284 active_keywords=(1285 "triton_fused_add_rms_norm_quant_fp8",1286 "fuse_rmsnorm_quant_fp8",1287 ),1288 rationale_hint=(1289 "TensorRT-LLM mainline has a Triton residual-add plus RMSNorm plus"1290 " FP8-quant family in AutoDeploy."1291 ),1292 origin="upstream",1293 min_share=0.2,1294 likely_share=1.0,1295 priority=20,1296 ),1297 FusionPatternSpec(1298 pattern="TensorRT-LLM FlashInfer RMSNorm family",1299 candidate_path=(1300 "tensorrt_llm/_torch/custom_ops/flashinfer_custom_ops.py"1301 "<br>tensorrt_llm/_torch/modules/rms_norm.py"1302 "<br>tensorrt_llm/_torch/auto_deploy/custom_ops/normalization/rms_norm.py"1303 ),1304 active_keywords=(1305 "flashinfer_rmsnorm",1306 "flashinfer_gemma_rmsnorm",1307 "auto_deploy::flashinfer_rms_norm",1308 ),1309 rationale_hint=(1310 "TensorRT-LLM lowers RMSNorm-style ladders to FlashInfer kernels"1311 " and AutoDeploy custom ops."1312 ),1313 origin="upstream",1314 min_share=0.1,1315 likely_share=1.0,1316 ),1317 FusionPatternSpec(1318 pattern="TensorRT-LLM FlashInfer activation / gate epilogues",1319 candidate_path=(1320 "tensorrt_llm/_torch/custom_ops/flashinfer_custom_ops.py"1321 "<br>tensorrt_llm/_torch/auto_deploy/transform/library/fuse_silu_mul.py"1322 "<br>tensorrt_llm/_torch/models/modeling_gemma3.py"1323 ),1324 active_keywords=(1325 "flashinfer_silu_and_mul",1326 "flashinfer_gelu_tanh_and_mul",1327 "auto_deploy::silu_and_mul",1328 ),1329 rationale_hint=(1330 "TensorRT-LLM already rewrites gate activation plus multiply"1331 " ladders into FlashInfer epilogue kernels."1332 ),1333 origin="upstream",1334 min_share=0.1,1335 likely_share=1.0,1336 ),1337)1338 1339 1340def short_name(name: str, max_len: int = 96) -> str:1341 text = normalize_text(name)1342 if len(text) <= max_len:1343 return text1344 return text[: max_len - 3] + "..."1345 1346 1347@lru_cache(maxsize=65536)1348def canonicalize_name(name: str) -> str:1349 text = normalize_text(name)1350 text = re.sub(r"0x[0-9a-fA-F]+", "0xADDR", text)1351 if text.startswith("void ") and text.endswith(")"):1352 depth = 01353 split_idx: Optional[int] = None1354 for idx in range(len(text) - 1, -1, -1):1355 char = text[idx]1356 if char == ")":1357 depth += 11358 elif char == "(":1359 depth -= 11360 if depth == 0:1361 split_idx = idx1362 break1363 if split_idx is not None:1364 text = text[:split_idx]1365 return text1366 1367 1368@lru_cache(maxsize=65536)1369def classify_kernel(name: str) -> str:1370 # Keep the matching order explicit: strong communication/memory signals win1371 # first, then we fall back to weaker category hints.1372 lowered = name.lower()1373 if contains_any_keyword(lowered, COMMUNICATION_STRONG_KEYWORDS):1374 return "communication"1375 if contains_any_keyword(lowered, MEMORY_STRONG_KEYWORDS):1376 return "memory"1377 looks_compute_like = contains_any_keyword(lowered, COMPUTE_HINT_KEYWORDS)1378 if contains_any_keyword(lowered, MEMORY_WEAK_KEYWORDS) and not looks_compute_like:1379 return "memory"1380 for category, keywords in CATEGORY_PATTERNS:1381 if contains_any_keyword(lowered, keywords):1382 return category1383 if (1384 contains_any_keyword(lowered, COMMUNICATION_WEAK_KEYWORDS)1385 and not looks_compute_like1386 ):1387 return "communication"1388 return "other"1389 1390 1391@lru_cache(maxsize=65536)1392def normalize_source_location(name: str) -> str:1393 text = normalize_text(name)1394 match = re.match(r"(?P<path>.+?)\((?P<line>\d+)\): (?P<func>.+)$", text)1395 if not match:1396 return text1397 path = normalize_repo_relative_path(match.group("path"))1398 return f"{path}:{match.group('line')} {match.group('func')}"1399 1400 1401def source_location_priority(location: str) -> int:1402 text = str(location).strip()1403 if not text or text == "unresolved":1404 return -1001405 penalty = 80 if is_low_signal_source_location(text) else 01406 if text.startswith("python/sglang/"):1407 return 300 - penalty1408 if text.startswith("sglang/"):1409 return 290 - penalty1410 if text.startswith("vllm/"):1411 return 285 - penalty1412 if text.startswith("python/tokenspeed/") or text.startswith("tokenspeed/"):1413 return 283 - penalty1414 if text.startswith("tensorrt_llm/"):1415 return 280 - penalty1416 if text.startswith("sgl_kernel/"):1417 return 260 - penalty1418 if text.startswith("python/"):1419 return 180 - penalty1420 if text.startswith("torch/") or "/torch/" in text:1421 return 201422 if ".py:" in text:1423 return 120 - penalty1424 return 01425 1426 1427def is_preferred_source_location(location: str) -> bool:1428 text = str(location).strip()1429 return (1430 text.startswith("python/sglang/")1431 or text.startswith("sglang/")1432 or text.startswith("vllm/")1433 or text.startswith("python/tokenspeed/")1434 or text.startswith("tokenspeed/")1435 or text.startswith("tensorrt_llm/")1436 or text.startswith("sgl_kernel/")1437 )1438 1439 1440def extract_preferred_stack_location(stack: Optional[str]) -> Optional[str]:1441 if not stack:1442 return None1443 parts = [str(part).strip() for part in str(stack).split("->")]1444 ranked: List[Tuple[int, int, str]] = []1445 for index, part in enumerate(parts):1446 normalized = normalize_source_location(part)1447 priority = source_location_priority(normalized)1448 if priority <= 0:1449 continue1450 ranked.append((priority, index, normalized))1451 if not ranked:1452 return None1453 ranked.sort(key=lambda item: (item[0], item[1]), reverse=True)1454 return ranked[0][2]1455 1456 1457def site_display_location(site: dict) -> str:1458 location = str(site.get("location") or "unresolved").strip()1459 if is_preferred_source_location(location) and not is_low_signal_source_location(1460 location1461 ):1462 return location1463 stack_location = extract_preferred_stack_location(site.get("stack"))1464 if stack_location:1465 return stack_location1466 return location1467 1468 1469def choose_best_location(locations: Dict[str, MappingSiteAggregate]) -> str:1470 if not locations:1471 return "unresolved"1472 ranked = sorted(1473 locations.items(),1474 key=lambda pair: (1475 source_location_priority(pair[0]),1476 pair[1].total_us,1477 pair[1].count,1478 ),1479 reverse=True,1480 )1481 return ranked[0][0]1482 1483 1484@lru_cache(maxsize=65536)1485def frame_priority(frame_name: str) -> int:1486 raw_text = str(frame_name).strip()1487 normalized_text = normalize_source_location(raw_text)1488 penalty = 80 if is_low_signal_source_location(normalized_text) else 01489 if raw_text.startswith(NOISE_FRAME_PREFIXES):1490 return -201491 if normalized_text.startswith("python/sglang/"):1492 return 300 - penalty1493 if normalized_text.startswith("sglang/"):1494 return 290 - penalty1495 if normalized_text.startswith("vllm/"):1496 return 285 - penalty1497 if normalized_text.startswith("python/tokenspeed/") or normalized_text.startswith(1498 "tokenspeed/"1499 ):1500 return 283 - penalty1501 if normalized_text.startswith("tensorrt_llm/"):1502 return 280 - penalty1503 if normalized_text.startswith("sgl_kernel/"):1504 return 260 - penalty1505 if normalized_text.startswith("triton_kernels/"):1506 return 220 - penalty1507 if normalized_text.startswith(LOW_LEVEL_FRAME_PREFIXES):1508 return 01509 if raw_text.startswith("/data/") or raw_text.startswith("/Users/"):1510 if "/sglang/" in raw_text:1511 return 1201512 if "/vllm/" in raw_text:1513 return 1181514 if "/tokenspeed/" in raw_text or "/TokenSpeed/" in raw_text:1515 return 1171516 if "/TensorRT-LLM/" in raw_text or "/tensorrt_llm/" in raw_text:1517 return 1161518 return 1001519 if ".py(" in raw_text and "/sglang/" in raw_text:1520 return 1101521 if ".py(" in raw_text and "/vllm/" in raw_text:1522 return 1081523 if ".py(" in raw_text and (1524 "/tokenspeed/" in raw_text or "/TokenSpeed/" in raw_text1525 ):1526 return 1071527 if ".py(" in raw_text and (1528 "/TensorRT-LLM/" in raw_text or "/tensorrt_llm/" in raw_text1529 ):1530 return 1061531 if ".py:" in normalized_text and (1532 "site-packages" in raw_text or normalized_text.startswith("torch/")1533 ):1534 return 451535 if ".py:" in normalized_text:1536 return 351537 if raw_text.startswith("<built-in method "):1538 return -101539 return 01540 1541 1542@lru_cache(maxsize=65536)1543def is_low_signal_source_location(location: str) -> bool:1544 lowered = str(location).strip().lower()1545 if not lowered:1546 return False1547 return any(token in lowered for token in LOW_SIGNAL_FUNCTION_TOKENS) or any(1548 token in lowered for token in LOW_SIGNAL_PATH_TOKENS1549 )1550 1551 1552def stage_label(stage: str) -> str:1553 if stage == "extend":1554 return "extend/prefill"1555 return stage1556 1557 1558def stage_aliases(stage: str) -> List[str]:1559 if stage == "extend":1560 return ["extend", "prefill", "all"]1561 if stage == "prefill":1562 return ["prefill", "extend", "all"]1563 if stage == "decode":1564 return ["decode", "all"]1565 return [stage, "all"]1566 1567 1568def escape_md_cell(text: str) -> str:1569 return str(text).replace("|", "\\|").replace("\n", "<br>")1570 1571 1572def pct(part: float, whole: float) -> float:1573 return 100.0 * part / whole if whole else 0.01574 1575 1576def format_ms(value_us: float) -> str:1577 return f"{value_us / 1000.0:.2f} ms"1578 1579 1580@lru_cache(maxsize=16384)1581def _is_cuda_launch_event_cached(name: str, cat: str) -> bool:1582 lowered_name = normalize_text(name).lower()1583 lowered_cat = normalize_text(cat).lower()1584 if lowered_cat not in {"cuda_runtime", "cuda_driver"}:1585 return False1586 return "launch" in lowered_name1587 1588 1589def is_cuda_launch_event(name: str, cat: str) -> bool:1590 return _is_cuda_launch_event_cached(str(name), str(cat))1591 1592 1593def is_gpu_kernel_event(event: dict) -> bool:1594 # Be conservative here: first drop trace metadata / Python scopes /1595 # annotations, then only accept entries with clear GPU-kernel markers.1596 if not is_complete_duration_event(event):1597 return False1598 name = normalize_text(event.get("name", ""))1599 if is_trace_metadata_name(name):1600 return False1601 cat = normalize_text(event.get("cat", "")).lower()1602 args = event.get("args") or {}1603 if is_non_kernel_trace_category(cat):1604 return False1605 if is_annotation_event(name, cat):1606 return False1607 if "kernel" in cat or cat.startswith("gpu_"):1608 return True1609 if looks_like_python_scope_name(name):1610 return False1611 return has_stream_marker(args)1612 1613 1614def infer_stage_from_annotation_name(name: str) -> Optional[str]:1615 lowered = normalize_text(name).lower()1616 if not lowered:1617 return None1618 if "generation_1" in lowered or "decode" in lowered:1619 return "decode"1620 if "generation_0" in lowered or "prefill" in lowered:1621 return "extend"1622 return None1623 1624 1625def build_stage_annotations(1626 raw_events: Sequence[dict],1627) -> Tuple[1628 Dict[int, StageAnnotation],1629 List[StageWindow],1630 List[StageWindow],1631]:1632 by_external_id: Dict[int, StageAnnotation] = {}1633 gpu_annotations: List[StageAnnotation] = []1634 cpu_annotations: List[StageAnnotation] = []1635 1636 def should_replace(current: StageAnnotation, candidate: StageAnnotation) -> bool:1637 if candidate.is_gpu != current.is_gpu:1638 return candidate.is_gpu1639 return (candidate.end_ts - candidate.ts) > (current.end_ts - current.ts)1640 1641 for event in raw_events:1642 if not is_complete_duration_event(event):1643 continue1644 category = normalize_text(event.get("cat", "")).lower()1645 if category not in {"user_annotation", "gpu_user_annotation"}:1646 continue1647 stage = infer_stage_from_annotation_name(str(event.get("name", "")))1648 if not stage:1649 continue1650 annotation = StageAnnotation(1651 stage=stage,1652 ts=float(event.get("ts", 0.0)),1653 end_ts=float(event.get("ts", 0.0)) + float(event.get("dur", 0.0)),1654 external_id=coerce_optional_int(1655 (event.get("args") or {}).get("External id")1656 ),1657 is_gpu=(category == "gpu_user_annotation"),1658 )1659 if annotation.external_id is not None:1660 existing = by_external_id.get(annotation.external_id)1661 if existing is None or should_replace(existing, annotation):1662 by_external_id[annotation.external_id] = annotation1663 if annotation.is_gpu:1664 gpu_annotations.append(annotation)1665 else:1666 cpu_annotations.append(annotation)1667 1668 gpu_annotations.sort(key=lambda item: (item.ts, item.end_ts))1669 cpu_annotations.sort(key=lambda item: (item.ts, item.end_ts))1670 return (1671 by_external_id,1672 merge_stage_windows(gpu_annotations),1673 merge_stage_windows(cpu_annotations),1674 )1675 1676 1677def merge_stage_windows(annotations: Sequence[StageAnnotation]) -> List[StageWindow]:1678 merged: List[StageWindow] = []1679 for annotation in annotations:1680 if (1681 merged1682 and merged[-1].stage == annotation.stage1683 and annotation.ts <= merged[-1].end_ts + 1e-31684 ):1685 merged[-1] = StageWindow(1686 stage=merged[-1].stage,1687 ts=merged[-1].ts,1688 end_ts=max(merged[-1].end_ts, annotation.end_ts),1689 )1690 continue1691 merged.append(1692 StageWindow(1693 stage=annotation.stage,1694 ts=annotation.ts,1695 end_ts=annotation.end_ts,1696 )1697 )1698 return merged1699 1700 1701def resolve_stage_from_windows(1702 probe_ts: float,1703 windows: Sequence[StageWindow],1704) -> Tuple[Optional[str], Optional[float]]:1705 nearest_stage: Optional[str] = None1706 nearest_gap: Optional[float] = None1707 for window in windows:1708 if window.ts <= probe_ts <= window.end_ts + 1e-3:1709 return window.stage, 0.01710 gap = min(abs(probe_ts - window.ts), abs(probe_ts - window.end_ts))1711 if nearest_gap is None or gap < nearest_gap:1712 nearest_gap = gap1713 nearest_stage = window.stage1714 return nearest_stage, nearest_gap1715 1716 1717def resolve_kernel_stage(1718 *,1719 kernel_ts: float,1720 external_id: Optional[int],1721 annotations_by_external_id: Dict[int, StageAnnotation],1722 gpu_annotations: Sequence[StageWindow],1723 cpu_annotations: Sequence[StageWindow],1724) -> str:1725 if external_id is not None:1726 annotation = annotations_by_external_id.get(external_id)1727 if annotation is not None:1728 return annotation.stage1729 probe_ts = kernel_ts + 1e-31730 nearest_stage: Optional[str] = None1731 nearest_gap: Optional[float] = None1732 for windows in (gpu_annotations, cpu_annotations):1733 stage, gap = resolve_stage_from_windows(probe_ts, windows)1734 if gap == 0.0 and stage is not None:1735 return stage1736 if stage is not None and (1737 nearest_gap is None or (gap is not None and gap < nearest_gap)1738 ):1739 nearest_stage = stage1740 nearest_gap = gap1741 if (1742 nearest_stage is not None1743 and nearest_gap is not None1744 and nearest_gap <= 20_000.01745 ):1746 return nearest_stage1747 return "all"1748 1749 1750def extract_trace_data(1751 trace: dict,1752) -> Tuple[1753 List[KernelEvent],1754 List[CpuOpEvent],1755 Dict[Tuple[str, str], List[PythonFrame]],1756 List[LaunchEvent],1757 Optional[str],1758 float,1759]:1760 # Build the basic trace views in one pass so later stages can stay simple:1761 # GPU kernels for ranking, CPU ops for External-id mapping, Python frames for1762 # source attribution, and CUDA launch calls for correlation-based fallback.1763 raw_events = extract_trace_events(trace)1764 correlation_external = build_correlation_external_lookup(raw_events)1765 (1766 annotations_by_external_id,1767 gpu_stage_annotations,1768 cpu_stage_annotations,1769 ) = build_stage_annotations(raw_events)1770 chosen_pid = select_heaviest_pid(1771 raw_events,1772 is_gpu_kernel_event,1773 preferred_substrings=("TP00", "TP-0"),1774 )1775 1776 kernels: List[KernelEvent] = []1777 cpu_ops: List[CpuOpEvent] = []1778 launches: List[LaunchEvent] = []1779 python_frames: DefaultDict[Tuple[str, str], List[PythonFrame]] = defaultdict(list)1780 min_ts = None1781 max_end = None1782 1783 for event in raw_events:1784 if event.get("ph") != "X":1785 continue1786 1787 pid = str(event.get("pid"))1788 tid = str(event.get("tid"))1789 ts = float(event.get("ts", 0.0))1790 dur = float(event.get("dur", 0.0))1791 cat = str(event.get("cat", ""))1792 args = event.get("args") or {}1793 name = str(event.get("name", ""))1794 1795 if cat == "python_function":1796 python_frames[(pid, tid)].append(1797 PythonFrame(1798 name=name,1799 normalized_name=normalize_source_location(name),1800 pid=pid,1801 tid=tid,1802 ts=ts,1803 dur=dur,1804 python_id=coerce_optional_int(args.get("Python id")),1805 parent_id=coerce_optional_int(args.get("Python parent id")),1806 end_ts=ts + dur,1807 priority=frame_priority(name),1808 )1809 )1810 1811 correlation = coerce_optional_int(args.get("correlation"))1812 external_id = coerce_optional_int(args.get("External id"))1813 if external_id is None and correlation is not None:1814 external_id = correlation_external.get(correlation)1815 if cat == "cpu_op" and external_id is not None:1816 cpu_ops.append(1817 CpuOpEvent(1818 name=name,1819 pid=pid,1820 tid=tid,1821 ts=ts,1822 dur=dur,1823 external_id=external_id,1824 )1825 )1826 if is_cuda_launch_event(name, cat) and correlation is not None:1827 launches.append(1828 LaunchEvent(1829 name=name,1830 pid=pid,1831 tid=tid,1832 ts=ts,1833 dur=dur,1834 correlation=correlation,1835 )1836 )1837 1838 if chosen_pid is None or not is_gpu_kernel_event(event) or pid != chosen_pid:1839 continue1840 1841 min_ts = ts if min_ts is None else min(min_ts, ts)1842 max_end = ts + dur if max_end is None else max(max_end, ts + dur)1843 kernels.append(1844 KernelEvent(1845 name=name,1846 canonical_name=canonicalize_name(name),1847 category=classify_kernel(name),1848 stage=resolve_kernel_stage(1849 kernel_ts=ts,1850 external_id=external_id,1851 annotations_by_external_id=annotations_by_external_id,1852 gpu_annotations=gpu_stage_annotations,1853 cpu_annotations=cpu_stage_annotations,1854 ),1855 pid=pid,1856 tid=tid,1857 ts=ts,1858 dur=dur,1859 external_id=external_id,1860 correlation=correlation,1861 )1862 )1863 1864 for frames in python_frames.values():1865 frames.sort(key=lambda item: (item.ts, item.end_ts))1866 1867 window_us = 0.0 if min_ts is None or max_end is None else max_end - min_ts1868 return kernels, cpu_ops, dict(python_frames), launches, chosen_pid, window_us1869 1870 1871def build_correlation_external_lookup(raw_events: Sequence[dict]) -> Dict[int, int]:1872 lookup: Dict[int, int] = {}1873 for event in raw_events:1874 args = event.get("args", {}) or {}1875 correlation = coerce_optional_int(args.get("correlation"))1876 external_id = coerce_optional_int(args.get("External id"))1877 if correlation is not None and external_id is not None:1878 lookup[correlation] = external_id1879 return lookup1880 1881 1882def build_timed_event_index(events: Sequence[object]) -> TimedEventIndex:1883 ordered = list(events)1884 ordered.sort(key=lambda item: item.ts)1885 return TimedEventIndex(1886 events=ordered,1887 start_ts=[float(item.ts) for item in ordered],1888 )1889 1890 1891def build_cpu_op_index(cpu_ops: Sequence[CpuOpEvent]) -> Dict[int, TimedEventIndex]:1892 output: DefaultDict[int, List[CpuOpEvent]] = defaultdict(list)1893 for cpu_op in cpu_ops:1894 output[cpu_op.external_id].append(cpu_op)1895 return {1896 external_id: build_timed_event_index(items)1897 for external_id, items in output.items()1898 }1899 1900 1901def match_cpu_op(1902 kernel: KernelEvent, cpu_ops_by_external_id: Dict[int, TimedEventIndex]1903) -> Optional[CpuOpEvent]:1904 if kernel.external_id is None:1905 return None1906 return match_timed_event(1907 cpu_ops_by_external_id.get(kernel.external_id, []), kernel.ts1908 )1909 1910 1911def build_launch_index(1912 launch_events: Sequence[LaunchEvent],1913) -> Dict[int, TimedEventIndex]:1914 output: DefaultDict[int, List[LaunchEvent]] = defaultdict(list)1915 for launch in launch_events:1916 output[launch.correlation].append(launch)1917 return {1918 correlation: build_timed_event_index(items)1919 for correlation, items in output.items()1920 }1921 1922 1923def match_launch_event(1924 kernel: KernelEvent, launches_by_correlation: Dict[int, TimedEventIndex]1925) -> Optional[LaunchEvent]:1926 if kernel.correlation is None:1927 return None1928 return match_timed_event(1929 launches_by_correlation.get(kernel.correlation, []), kernel.ts1930 )1931 1932 1933def match_timed_event(index: object, probe_ts: float):1934 if not index:1935 return None1936 if isinstance(index, TimedEventIndex):1937 events = index.events1938 if not events:1939 return None1940 right = bisect_right(index.start_ts, probe_ts + 1e-3)1941 candidates: List[object] = []1942 if right > 0:1943 candidates.extend(events[max(0, right - 4) : right])1944 if right < len(events):1945 candidates.extend(events[right : min(len(events), right + 2)])1946 if not candidates:1947 return None1948 earlier = [item for item in candidates if item.ts <= probe_ts + 1e-3]1949 if earlier:1950 return min(earlier, key=lambda item: abs((item.ts + item.dur) - probe_ts))1951 return min(candidates, key=lambda item: abs(item.ts - probe_ts))1952 events = list(index)1953 if not events:1954 return None1955 earlier = [item for item in events if item.ts <= probe_ts + 1e-3]1956 if earlier:1957 return min(earlier, key=lambda item: abs((item.ts + item.dur) - probe_ts))1958 return min(events, key=lambda item: abs(item.ts - probe_ts))1959 1960 1961def resolve_active_frames_linear(1962 frames: Sequence[PythonFrame], probe_ts: float1963) -> List[PythonFrame]:1964 active = [item for item in frames if item.ts <= probe_ts <= item.end_ts]1965 active.sort(key=lambda item: (item.ts, item.end_ts))1966 return active1967 1968 1969def thread_has_crossing_frames(frames: Sequence[PythonFrame]) -> bool:1970 ordered_frames = sorted(frames, key=lambda item: (item.ts, -item.end_ts))1971 stack: List[PythonFrame] = []1972 for frame in ordered_frames:1973 while stack and stack[-1].end_ts < frame.ts:1974 stack.pop()1975 if stack and frame.end_ts > stack[-1].end_ts + 1e-3:1976 return True1977 stack.append(frame)1978 return False1979 1980 1981def render_frame_resolution(1982 active_frames: Sequence[PythonFrame],1983) -> Optional[FrameResolution]:1984 if not active_frames:1985 return None1986 chosen_frame = choose_mapping_frame(active_frames)1987 if chosen_frame is None:1988 return None1989 return FrameResolution(1990 location=chosen_frame.normalized_name,1991 stack=build_stack_display(active_frames),1992 )1993 1994 1995def resolve_thread_query_times(1996 frames: Sequence[PythonFrame], query_times: Sequence[float]1997) -> Dict[float, Optional[FrameResolution]]:1998 if not frames or not query_times:1999 return {}2000 ordered_frames = sorted(frames, key=lambda item: (item.ts, -item.end_ts))2001 ordered_queries = sorted(set(float(ts) for ts in query_times))2002 results: Dict[float, Optional[FrameResolution]] = {}2003 active_frames: List[PythonFrame] = []2004 frame_idx = 02005 total_frames = len(ordered_frames)2006 2007 for ts in ordered_queries:2008 while frame_idx < total_frames and ordered_frames[frame_idx].ts <= ts:2009 active_frames.append(ordered_frames[frame_idx])2010 frame_idx += 12011 if active_frames:2012 active_frames = [2013 frame for frame in active_frames if frame.end_ts >= ts - 1e-32014 ]2015 results[ts] = render_frame_resolution(active_frames)2016 return results2017 2018 2019def build_frame_resolution_index(2020 python_frames: Dict[Tuple[str, str], List[PythonFrame]],2021 query_times_by_thread: Dict[Tuple[str, str], Sequence[float]],2022) -> Dict[Tuple[str, str], Dict[float, Optional[FrameResolution]]]:2023 output: Dict[Tuple[str, str], Dict[float, Optional[FrameResolution]]] = {}2024 for thread_key, query_times in query_times_by_thread.items():2025 frames = python_frames.get(thread_key, [])2026 output[thread_key] = resolve_thread_query_times(frames, query_times)2027 return output2028 2029 2030def find_active_python_frames(2031 cpu_op: CpuOpEvent,2032 python_frames: Dict[Tuple[str, str], List[PythonFrame]],2033) -> List[PythonFrame]:2034 frames = python_frames.get((cpu_op.pid, cpu_op.tid), [])2035 if not frames:2036 return []2037 probe_ts = cpu_op.ts + min(cpu_op.dur * 0.5, 1.0)2038 return resolve_active_frames_linear(frames, probe_ts)2039 2040 2041def find_active_python_frames_at_ts(2042 *,2043 pid: str,2044 tid: str,2045 ts: float,2046 python_frames: Dict[Tuple[str, str], List[PythonFrame]],2047) -> List[PythonFrame]:2048 frames = python_frames.get((pid, tid), [])2049 if not frames:2050 return []2051 return resolve_active_frames_linear(frames, ts)2052 2053 2054def render_kernel_site(2055 active_frames: Sequence[PythonFrame], cpu_op_name: str2056) -> Tuple[str, str, str]:2057 chosen_frame = choose_mapping_frame(active_frames)2058 if chosen_frame is None:2059 return "unresolved", "", cpu_op_name2060 return chosen_frame.normalized_name, build_stack_display(active_frames), cpu_op_name2061 2062 2063def resolve_kernel_site_context(2064 kernel: KernelEvent,2065 cpu_ops_by_external_id: Dict[int, TimedEventIndex],2066 python_frames: Dict[Tuple[str, str], List[PythonFrame]],2067 launches_by_correlation: Dict[int, TimedEventIndex],2068 frame_resolution_index: Optional[2069 Dict[Tuple[str, str], Dict[float, Optional[FrameResolution]]]2070 ] = None,2071) -> Tuple[str, str, str]:2072 # Prefer the normal External-id path first. If the kernel dropped that link,2073 # fall back to the correlated CUDA launch and reuse the Python frames that2074 # were active when the launch happened.2075 cpu_op = match_cpu_op(kernel, cpu_ops_by_external_id)2076 if cpu_op is not None:2077 probe_ts = cpu_op.ts + min(cpu_op.dur * 0.5, 1.0)2078 if frame_resolution_index is not None:2079 resolved = frame_resolution_index.get((cpu_op.pid, cpu_op.tid), {}).get(2080 probe_ts2081 )2082 if resolved is not None:2083 return resolved.location, resolved.stack, cpu_op.name2084 active_frames = find_active_python_frames(cpu_op, python_frames)2085 if active_frames:2086 return render_kernel_site(active_frames, cpu_op.name)2087 2088 launch_event = match_launch_event(kernel, launches_by_correlation)2089 if launch_event is not None:2090 if frame_resolution_index is not None:2091 resolved = frame_resolution_index.get(2092 (launch_event.pid, launch_event.tid), {}2093 ).get(launch_event.ts)2094 if resolved is not None:2095 cpu_op_name = cpu_op.name if cpu_op is not None else launch_event.name2096 return resolved.location, resolved.stack, cpu_op_name2097 active_frames = find_active_python_frames_at_ts(2098 pid=launch_event.pid,2099 tid=launch_event.tid,2100 ts=launch_event.ts,2101 python_frames=python_frames,2102 )2103 if active_frames:2104 cpu_op_name = cpu_op.name if cpu_op is not None else launch_event.name2105 return render_kernel_site(active_frames, cpu_op_name)2106 return "unresolved", "", launch_event.name2107 2108 cpu_op_name = cpu_op.name if cpu_op is not None else ""2109 return "unresolved", "", cpu_op_name2110 2111 2112def choose_mapping_frame(active_frames: Sequence[PythonFrame]) -> Optional[PythonFrame]:2113 if not active_frames:2114 return None2115 best = active_frames[0]2116 best_key = (best.priority, best.ts, -best.dur)2117 for item in active_frames[1:]:2118 key = (item.priority, item.ts, -item.dur)2119 if key > best_key:2120 best = item2121 best_key = key2122 return best2123 2124 2125def build_stack_display(active_frames: Sequence[PythonFrame]) -> str:2126 if not active_frames:2127 return ""2128 filtered = [item.normalized_name for item in active_frames if item.priority > 0]2129 if not filtered:2130 filtered = [active_frames[-1].normalized_name]2131 return " -> ".join(filtered[-4:])2132 2133 2134def aggregate(events: Iterable[KernelEvent], key_fn) -> Dict[str, Aggregate]:2135 output: Dict[str, Aggregate] = defaultdict(Aggregate)2136 for event in events:2137 key = key_fn(event)2138 item = output[key]2139 item.total_us += event.dur2140 item.count += 12141 item.max_us = max(item.max_us, event.dur)2142 return output2143 2144 2145def group_kernels_by_stage(2146 kernels: Sequence[KernelEvent], default_stage: str2147) -> Dict[str, List[KernelEvent]]:2148 grouped: DefaultDict[str, List[KernelEvent]] = defaultdict(list)2149 for kernel in kernels:2150 stage = default_stage if default_stage != "all" else (kernel.stage or "all")2151 grouped[stage].append(kernel)2152 return dict(grouped)2153 2154 2155def aggregate_kernel_sites(2156 kernels: Sequence[KernelEvent],2157 cpu_ops_by_external_id: Dict[int, TimedEventIndex],2158 python_frames: Dict[Tuple[str, str], List[PythonFrame]],2159 launches_by_correlation: Optional[Dict[int, TimedEventIndex]] = None,2160 site_context_cache: Optional[2161 Dict[Tuple[str, str, float, Optional[int], Optional[int]], Tuple[str, str, str]]2162 ] = None,2163) -> Dict[str, Dict[str, MappingSiteAggregate]]:2164 # Each kernel is mapped independently so the fallback behavior stays easy to2165 # reason about and easy to regression-test.2166 output: DefaultDict[str, DefaultDict[str, MappingSiteAggregate]] = defaultdict(2167 lambda: defaultdict(MappingSiteAggregate)2168 )2169 launch_index = launches_by_correlation or {}2170 query_times_by_thread: DefaultDict[Tuple[str, str], List[float]] = defaultdict(list)2171 for kernel in kernels:2172 cpu_op = match_cpu_op(kernel, cpu_ops_by_external_id)2173 if cpu_op is not None:2174 query_times_by_thread[(cpu_op.pid, cpu_op.tid)].append(2175 cpu_op.ts + min(cpu_op.dur * 0.5, 1.0)2176 )2177 launch_event = match_launch_event(kernel, launch_index)2178 if launch_event is not None:2179 query_times_by_thread[(launch_event.pid, launch_event.tid)].append(2180 launch_event.ts2181 )2182 frame_resolution_index = build_frame_resolution_index(2183 python_frames, query_times_by_thread2184 )2185 resolved_cache = site_context_cache if site_context_cache is not None else {}2186 for kernel in kernels:2187 cache_key = (2188 kernel.pid,2189 kernel.tid,2190 kernel.ts,2191 kernel.external_id,2192 kernel.correlation,2193 )2194 cached = resolved_cache.get(cache_key)2195 if cached is None:2196 cached = resolve_kernel_site_context(2197 kernel,2198 cpu_ops_by_external_id,2199 python_frames,2200 launch_index,2201 frame_resolution_index=frame_resolution_index,2202 )2203 resolved_cache[cache_key] = cached2204 location, stack, cpu_op_name = cached2205 2206 item = output[kernel.canonical_name][location]2207 item.total_us += kernel.dur2208 item.count += 12209 if cpu_op_name:2210 item.cpu_ops[cpu_op_name] += 12211 if stack:2212 item.stacks[stack] += 12213 return {kernel_name: dict(locations) for kernel_name, locations in output.items()}2214 2215 2216def merge_site_stats(2217 destination: DefaultDict[str, DefaultDict[str, MappingSiteAggregate]],2218 source: Dict[str, Dict[str, MappingSiteAggregate]],2219) -> None:2220 for kernel_name, locations in source.items():2221 for location, aggregate_item in locations.items():2222 target = destination[kernel_name][location]2223 target.total_us += aggregate_item.total_us2224 target.count += aggregate_item.count2225 target.cpu_ops.update(aggregate_item.cpu_ops)2226 target.stacks.update(aggregate_item.stacks)2227 2228 2229def build_stage_payload(2230 site_stats: Dict[str, Dict[str, MappingSiteAggregate]],2231 kernel_categories: Dict[str, str],2232) -> Dict[str, dict]:2233 kernels_payload: Dict[str, dict] = {}2234 for kernel_name, locations in sorted(site_stats.items()):2235 total_us = sum(item.total_us for item in locations.values())2236 sites = []2237 for location, aggregate_item in sorted(2238 locations.items(),2239 key=lambda pair: pair[1].total_us,2240 reverse=True,2241 ):2242 sites.append(2243 {2244 "location": location,2245 "display_location": extract_preferred_stack_location(2246 aggregate_item.stacks.most_common(1)[0][0]2247 if aggregate_item.stacks2248 else None2249 )2250 or location,2251 "launches": aggregate_item.count,2252 "total_us": round(aggregate_item.total_us, 3),2253 "share_pct_within_kernel": round(2254 pct(aggregate_item.total_us, total_us), 32255 ),2256 "top_cpu_op": (2257 aggregate_item.cpu_ops.most_common(1)[0][0]2258 if aggregate_item.cpu_ops2259 else None2260 ),2261 "stack": (2262 aggregate_item.stacks.most_common(1)[0][0]2263 if aggregate_item.stacks2264 else None2265 ),2266 }2267 )2268 sites.sort(2269 key=lambda site: (2270 source_location_priority(site_display_location(site)),2271 float(site.get("total_us", 0.0)),2272 int(site.get("launches", 0)),2273 ),2274 reverse=True,2275 )2276 kernels_payload[kernel_name] = {2277 "category": kernel_categories.get(kernel_name, "other"),2278 "sites": sites,2279 "best_location": (2280 site_display_location(sites[0])2281 if sites2282 else choose_best_location(locations)2283 ),2284 }2285 return {"kernels": kernels_payload}2286 2287 2288def load_kernel_map(path: Path) -> dict:2289 with open(path, "r", encoding="utf-8") as handle:2290 return json.load(handle)2291 2292 2293def relaxed_kernel_entry_lookup(2294 kernels: Dict[str, dict], kernel_name: str2295) -> Optional[dict]:2296 if kernel_name in kernels:2297 return kernels[kernel_name]2298 lowered = kernel_name.lower()2299 best_key = None2300 best_score = -12301 for candidate_key in kernels:2302 candidate_lowered = candidate_key.lower()2303 if candidate_lowered.startswith(lowered) or lowered.startswith(2304 candidate_lowered2305 ):2306 score = min(len(candidate_lowered), len(lowered))2307 elif candidate_lowered in lowered or lowered in candidate_lowered:2308 score = min(len(candidate_lowered), len(lowered)) // 22309 else:2310 continue2311 if score > best_score:2312 best_key = candidate_key2313 best_score = score2314 if best_key:2315 return kernels.get(best_key)2316 2317 # Long auto-generated kernels such as CUTLASS / FlashAttention templates can2318 # differ in the middle of the symbol while still sharing the same high-level2319 # family. Fall back to a conservative common-prefix match so we can still2320 # recover the higher-level Python callsite from the mapping trace.2321 lowered_compact = normalize_match_text(kernel_name)2322 if len(lowered_compact) < 96:2323 return alias_kernel_entry_lookup(kernels, kernel_name)2324 2325 def common_prefix_len(left: str, right: str) -> int:2326 count = 02327 for left_ch, right_ch in zip(left, right):2328 if left_ch != right_ch:2329 break2330 count += 12331 return count2332 2333 best_key = None2334 best_score = -12335 for candidate_key in kernels:2336 candidate_compact = normalize_match_text(candidate_key)2337 if len(candidate_compact) < 96:2338 continue2339 prefix_len = common_prefix_len(lowered_compact, candidate_compact)2340 shorter_len = min(len(lowered_compact), len(candidate_compact))2341 if prefix_len < 64 or prefix_len < int(shorter_len * 0.4):2342 continue2343 score = prefix_len2344 if lowered_compact.startswith(2345 "voidcutlassdevicekernelflash"2346 ) and candidate_compact.startswith("voidcutlassdevicekernelflash"):2347 score += 322348 if score > best_score:2349 best_key = candidate_key2350 best_score = score2351 if best_key:2352 return kernels.get(best_key)2353 return alias_kernel_entry_lookup(kernels, kernel_name)2354 2355 2356def lookup_kernel_map_entry(2357 kernel_map: dict, stage: str, kernel_name: str2358) -> Optional[dict]:2359 stage_map = kernel_map.get("stages", {})2360 for candidate_stage in stage_aliases(stage):2361 entry = relaxed_kernel_entry_lookup(2362 stage_map.get(candidate_stage, {}).get("kernels", {}),2363 kernel_name,2364 )2365 if entry:2366 return entry2367 return relaxed_kernel_entry_lookup(2368 kernel_map.get("global", {}).get("kernels", {}), kernel_name2369 )2370 2371 2372def best_site_summary(kernel_entry: Optional[dict]) -> Tuple[str, str]:2373 if not kernel_entry:2374 return "unresolved", "-"2375 sites = kernel_entry.get("sites") or []2376 if not sites:2377 return kernel_entry.get("best_location", "unresolved"), "-"2378 preferred_sites = [2379 site2380 for site in sites2381 if is_preferred_source_location(site_display_location(site))2382 ]2383 candidate_sites = preferred_sites or sites2384 rendered_locations = []2385 rendered_cpu_ops = []2386 for site in candidate_sites[:2]:2387 location = site_display_location(site)2388 share = site.get("share_pct_within_kernel")2389 if len(candidate_sites) > 1 and share is not None:2390 rendered_locations.append(f"{location} (site share {share:.0f}%)")2391 else:2392 rendered_locations.append(location)2393 cpu_op = site.get("top_cpu_op")2394 if cpu_op:2395 rendered_cpu_ops.append(cpu_op)2396 return "<br>".join(rendered_locations), (2397 "<br>".join(rendered_cpu_ops) if rendered_cpu_ops else "-"2398 )2399 2400 2401def resolve_kernel_entry(2402 stage: str,2403 kernel_name: str,2404 local_stage_payload: dict,2405 external_kernel_map: Optional[dict],2406) -> Optional[dict]:2407 if external_kernel_map:2408 kernel_entry = lookup_kernel_map_entry(external_kernel_map, stage, kernel_name)2409 if kernel_entry:2410 return kernel_entry2411 return relaxed_kernel_entry_lookup(2412 local_stage_payload.get("kernels", {}), kernel_name2413 )2414 2415 2416def build_kernel_rows(2417 stage: str,2418 kernel_stats: Dict[str, Aggregate],2419 kernel_categories: Dict[str, str],2420 local_stage_payload: dict,2421 external_kernel_map: Optional[dict],2422) -> List[KernelRow]:2423 rows: List[KernelRow] = []2424 for kernel_name, aggregate_item in sorted(2425 kernel_stats.items(),2426 key=lambda pair: pair[1].total_us,2427 reverse=True,2428 ):2429 kernel_entry = resolve_kernel_entry(2430 stage, kernel_name, local_stage_payload, external_kernel_map2431 )2432 location, cpu_op = best_site_summary(kernel_entry)2433 rows.append(2434 KernelRow(2435 name=kernel_name,2436 category=kernel_categories.get(kernel_name, "other"),2437 aggregate=aggregate_item,2438 location=location,2439 cpu_op=cpu_op,2440 entry=kernel_entry,2441 )2442 )2443 return rows2444 2445 2446def limit_kernel_rows(rows: Sequence[KernelRow], table_limit: int) -> List[KernelRow]:2447 if table_limit <= 0:2448 return list(rows)2449 return list(rows[:table_limit])2450 2451 2452def entry_sites(kernel_entry: Optional[dict]) -> List[dict]:2453 if not kernel_entry:2454 return []2455 sites = kernel_entry.get("sites") or []2456 return [site for site in sites if site.get("location")]2457 2458 2459def ordered_unique(values: Iterable[str], limit: int = 4) -> List[str]:2460 output: List[str] = []2461 seen = set()2462 for value in values:2463 item = str(value).strip()2464 if not item or item in seen:2465 continue2466 seen.add(item)2467 output.append(item)2468 if len(output) >= limit:2469 break2470 return output2471 2472 2473def kernel_row_locations(row: KernelRow, limit: int = 4) -> List[str]:2474 values = [site_display_location(site) for site in entry_sites(row.entry)]2475 if not values and row.location and row.location != "unresolved":2476 values = [fragment.strip() for fragment in row.location.split("<br>")]2477 return ordered_unique(values, limit=limit)2478 2479 2480def format_location_for_fusion_display(location: str) -> str:2481 text = normalize_text(location)2482 match = re.match(r"(?P<path>.+?):(?P<line>\d+)\s+(?P<func>.+)$", text)2483 if not match:2484 return text2485 return f"{match.group('func')} @ {match.group('path')}:{match.group('line')}"2486 2487 2488def normalize_match_text(text: object) -> str:2489 return re.sub(r"[^0-9A-Za-z]+", "", normalize_text(text)).lower()2490 2491 2492def kernel_entry_total_us(entry: Optional[dict]) -> float:2493 if not entry:2494 return 0.02495 return sum(float(site.get("total_us", 0.0)) for site in entry.get("sites", []))2496 2497 2498def kernel_entry_lookup_text(kernel_name: str, entry: Optional[dict]) -> str:2499 parts = [kernel_name]2500 if entry:2501 parts.append(str(entry.get("best_location") or ""))2502 for site in entry.get("sites", [])[:4]:2503 parts.append(str(site.get("location") or ""))2504 parts.append(str(site.get("display_location") or ""))2505 parts.append(str(site.get("top_cpu_op") or ""))2506 parts.append(str(site.get("stack") or ""))2507 return normalize_match_text(" ".join(parts))2508 2509 2510def kernel_alias_token_groups(kernel_name: str) -> List[Tuple[str, ...]]:2511 lowered = normalize_match_text(kernel_name)2512 groups: List[Tuple[str, ...]] = []2513 if "flashattnfwdcombine" in lowered:2514 groups.append(2515 (2516 "flashattnfwdsm90",2517 "flashattnvarlenfunc",2518 "vllmflashattnflashattninterface",2519 "vllmfa3cfwd",2520 )2521 )2522 if "kernelmha" in lowered:2523 groups.append(2524 (2525 "maskedmultiheadattentionkernel",2526 "attentioninplace",2527 "attentionbackendtrtllm",2528 )2529 )2530 if "applybiasropeupdatekvcachev2" in lowered:2531 groups.append(2532 (2533 "fusedqknormropekernel",2534 "applyqknormrope",2535 "modelingqwen3py98applyqknormrope",2536 )2537 )2538 if lowered.startswith("memset"):2539 groups.append(("memset",))2540 return groups2541 2542 2543def alias_kernel_entry_lookup(2544 kernels: Dict[str, dict], kernel_name: str2545) -> Optional[dict]:2546 alias_groups = kernel_alias_token_groups(kernel_name)2547 if not alias_groups:2548 return None2549 2550 best_key = None2551 best_score = -12552 for candidate_key, entry in kernels.items():2553 candidate_text = kernel_entry_lookup_text(candidate_key, entry)2554 score = 02555 for group_index, group in enumerate(alias_groups):2556 group_score = max(2557 (len(token) for token in group if token in candidate_text),2558 default=0,2559 )2560 if group_score:2561 score += 1000 * (group_index + 1) + group_score2562 if score <= 0:2563 continue2564 score += max(2565 source_location_priority(str(entry.get("best_location") or "")),2566 source_location_priority(best_site_summary(entry)[0]),2567 )2568 score += int(kernel_entry_total_us(entry) // 10)2569 if score > best_score:2570 best_key = candidate_key2571 best_score = score2572 return kernels.get(best_key) if best_key else None2573 2574 2575def row_matches(row: KernelRow, *needles: str) -> bool:2576 lowered = " ".join([row.name, row.location, row.cpu_op]).lower()2577 lowered_compact = normalize_match_text(lowered)2578 for needle in needles:2579 needle_lowered = needle.lower()2580 if needle_lowered in lowered:2581 return True2582 needle_compact = normalize_match_text(needle)2583 if needle_compact and needle_compact in lowered_compact:2584 return True2585 return False2586 2587 2588def summarize_text(values: Iterable[str], limit: int = 4) -> str:2589 items = ordered_unique(values, limit=limit)2590 return "<br>".join(items) if items else "-"2591 2592 2593def summarize_locations(values: Iterable[str], limit: int = 4) -> str:2594 items = ordered_unique(2595 (format_location_for_fusion_display(value) for value in values),2596 limit=limit,2597 )2598 return "<br>".join(items) if items else "-"2599 2600 2601def summarize_evidence(2602 rows: Sequence[KernelRow],2603 total_us: float,2604 limit: int = 3,2605 min_share_pct: float = 1.0,2606) -> str:2607 items = []2608 for row in rows:2609 share = pct(row.total_us, total_us)2610 if share < min_share_pct:2611 continue2612 items.append(f"{row.name} ({share:.1f}%)")2613 if len(items) >= limit:2614 break2615 return "<br>".join(items) if items else "-"2616 2617 2618def model_path_from_server_args(server_args: Optional[dict]) -> str:2619 if not isinstance(server_args, dict):2620 return ""2621 return str(server_args.get("model_path") or server_args.get("model") or "")2622 2623 2624def fusion_framework_hints(spec: FusionPatternSpec) -> set[str]:2625 text = normalize_text(spec.candidate_path).lower()2626 hints: set[str] = set()2627 if "vllm/" in text:2628 hints.add("vllm")2629 if any(token in text for token in ("tokenspeed/", "tokenspeed-", "tokenspeed_")):2630 hints.add("tokenspeed")2631 if "tensorrt_llm/" in text:2632 hints.add("trtllm")2633 if any(token in text for token in ("python/sglang/", "sgl_kernel/")):2634 hints.add("sglang")2635 return hints2636 2637 2638def pattern_supports_framework(2639 spec: FusionPatternSpec, framework: Optional[str]2640) -> bool:2641 normalized = normalize_text(framework).lower()2642 if not normalized or normalized == "auto":2643 return True2644 hints = fusion_framework_hints(spec)2645 if not hints:2646 return True2647 return normalized in hints2648 2649 2650def matching_rows_for_keywords(2651 kernel_rows: Sequence[KernelRow],2652 keywords: Sequence[str],2653) -> List[KernelRow]:2654 if not keywords:2655 return []2656 return [row for row in kernel_rows if row_matches(row, *keywords)]2657 2658 2659def row_identity(row: KernelRow) -> Tuple[str, str, str]:2660 return (row.name, row.location, row.cpu_op)2661 2662 2663def merge_kernel_rows(*groups: Sequence[KernelRow]) -> List[KernelRow]:2664 output: List[KernelRow] = []2665 seen = set()2666 for group in groups:2667 for row in group:2668 row_key = row_identity(row)2669 if row_key in seen:2670 continue2671 seen.add(row_key)2672 output.append(row)2673 return output2674 2675 2676def pattern_model_matches(spec: FusionPatternSpec, model_path: str) -> bool:2677 if spec.model_include and not any(2678 token in model_path for token in spec.model_include2679 ):2680 return False2681 if spec.model_exclude and any(token in model_path for token in spec.model_exclude):2682 return False2683 return True2684 2685 2686def pattern_status(spec: FusionPatternSpec, has_active_match: bool) -> str:2687 if spec.origin == "mainline":2688 return "mainline direct" if has_active_match else "mainline split"2689 if spec.origin == "upstream":2690 return "upstream direct" if has_active_match else "upstream split"2691 return "pending direct" if has_active_match else "pending split"2692 2693 2694def build_pattern_rationale(2695 spec: FusionPatternSpec,2696 has_active_match: bool,2697 related_us: float,2698 total_us: float,2699) -> str:2700 share = pct(related_us, total_us)2701 if spec.origin == "mainline":2702 if has_active_match:2703 return (2704 f"`{spec.pattern}` is present in this trace ({share:.1f}% related GPU time). "2705 f"{spec.rationale_hint}"2706 )2707 return (2708 f"Split kernels in this family take {share:.1f}% of GPU time. "2709 f"This tree already has a matching path. {spec.rationale_hint}"2710 )2711 if spec.origin == "upstream":2712 return (2713 f"Matches an upstream path ({share:.1f}% related GPU time). "2714 f"{spec.rationale_hint}"2715 )2716 return (2717 f"Matches an open upstream path ({share:.1f}% related GPU time). "2718 f"{spec.rationale_hint}"2719 )2720 2721 2722def pattern_span(spec: FusionPatternSpec) -> int:2723 return max(len(spec.split_groups), 1 if spec.active_keywords else 0)2724 2725 2726def fusion_priority_key(item: FusionOpportunity) -> Tuple[int, int, int, float]:2727 return (2728 item.priority,2729 item.pattern_span,2730 len(item.covered_row_keys),2731 item.related_us,2732 )2733 2734 2735def detect_pattern_match(2736 spec: FusionPatternSpec,2737 kernel_rows: Sequence[KernelRow],2738 total_us: float,2739 model_path: str,2740 tp_size: int,2741 framework: Optional[str],2742) -> Optional[FusionOpportunity]:2743 if total_us <= 0:2744 return None2745 if not pattern_supports_framework(spec, framework):2746 return None2747 if spec.require_tp and tp_size < spec.min_tp_size:2748 return None2749 if not pattern_model_matches(spec, model_path):2750 return None2751 2752 active_rows = matching_rows_for_keywords(kernel_rows, spec.active_keywords)2753 split_groups = [2754 matching_rows_for_keywords(kernel_rows, keywords)2755 for keywords in spec.split_groups2756 ]2757 has_active_match = bool(active_rows)2758 has_split_match = bool(split_groups) and all(split_groups)2759 if not has_active_match and not has_split_match:2760 return None2761 2762 related_rows = merge_kernel_rows(active_rows, *split_groups)2763 related_us = sum(row.total_us for row in related_rows)2764 if related_us <= 0:2765 return None2766 if not has_active_match and pct(related_us, total_us) < spec.min_share:2767 return None2768 2769 return FusionOpportunity(2770 pattern=spec.pattern,2771 status=pattern_status(spec, has_active_match),2772 confidence=(2773 "Confirmed"2774 if has_active_match or pct(related_us, total_us) >= spec.likely_share2775 else "Candidate"2776 ),2777 related_us=related_us,2778 evidence=summarize_evidence(related_rows, total_us),2779 current_locations=summarize_locations(2780 location for row in related_rows for location in kernel_row_locations(row)2781 ),2782 candidate_path=spec.candidate_path,2783 rationale=build_pattern_rationale(2784 spec=spec,2785 has_active_match=has_active_match,2786 related_us=related_us,2787 total_us=total_us,2788 ),2789 covered_row_keys=tuple(row_identity(row) for row in related_rows),2790 pattern_span=pattern_span(spec),2791 has_active_match=has_active_match,2792 priority=spec.priority,2793 subsumes=spec.subsumes,2794 )2795 2796 2797def detect_fusion_opportunities(2798 kernel_rows: Sequence[KernelRow],2799 total_us: float,2800 server_args: Optional[dict],2801 framework: Optional[str] = None,2802) -> List[FusionOpportunity]:2803 opportunities: List[FusionOpportunity] = []2804 if total_us <= 0:2805 return opportunities2806 2807 model_path = model_path_from_server_args(server_args).lower()2808 tp_size = 12809 if isinstance(server_args, dict):2810 tp_size = int(server_args.get("tp_size") or 1)2811 2812 raw_matches: List[FusionOpportunity] = []2813 for spec in FUSION_PATTERN_REGISTRY:2814 opportunity = detect_pattern_match(2815 spec=spec,2816 kernel_rows=kernel_rows,2817 total_us=total_us,2818 model_path=model_path,2819 tp_size=tp_size,2820 framework=framework,2821 )2822 if opportunity is not None:2823 raw_matches.append(opportunity)2824 2825 raw_matches.sort(key=fusion_priority_key, reverse=True)2826 consumed_row_keys = set()2827 blocked_patterns = set()2828 for opportunity in raw_matches:2829 if opportunity.pattern in blocked_patterns:2830 continue2831 if any(2832 row_key in consumed_row_keys for row_key in opportunity.covered_row_keys2833 ):2834 continue2835 opportunities.append(opportunity)2836 consumed_row_keys.update(opportunity.covered_row_keys)2837 blocked_patterns.update(opportunity.subsumes)2838 return opportunities2839