kernels-bot commited on
Commit
74a1379
·
verified ·
1 Parent(s): 03b2bb8

Uploaded using `kernel-builder`.

Browse files
README.md CHANGED
@@ -23,6 +23,7 @@ matmul_2d(...)
23
  - `matmul_grouped`
24
  - `moe_fused_batched`
25
  - `moe_fused_grouped`
 
26
  - `backward`
27
  - `get_supported_act_fns`
28
  - `get_supported_norms`
 
23
  - `matmul_grouped`
24
  - `moe_fused_batched`
25
  - `moe_fused_grouped`
26
+ - `embedding_lookup`
27
  - `backward`
28
  - `get_supported_act_fns`
29
  - `get_supported_norms`
build/torch-cuda/__init__.py CHANGED
@@ -17,6 +17,7 @@ from .matmul import matmul_2d
17
  from .batched import matmul_batched
18
  from .grouped import matmul_grouped
19
  from .moe import moe_fused_batched, moe_fused_grouped
 
20
  from . import backward
21
  from .formats import get_supported_act_fns
22
  from .norm import get_supported_norms, rms_norm_rows
@@ -32,6 +33,8 @@ __all__ = [
32
  # the fused MoE forwards over them
33
  "moe_fused_batched",
34
  "moe_fused_grouped",
 
 
35
  # importing it registers the dgrad formulas on the ops, so a forward call differentiates
36
  "backward",
37
  "get_supported_act_fns",
 
17
  from .batched import matmul_batched
18
  from .grouped import matmul_grouped
19
  from .moe import moe_fused_batched, moe_fused_grouped
20
+ from .embedding import embedding_lookup
21
  from . import backward
22
  from .formats import get_supported_act_fns
23
  from .norm import get_supported_norms, rms_norm_rows
 
33
  # the fused MoE forwards over them
34
  "moe_fused_batched",
35
  "moe_fused_grouped",
36
+ # selected-row embedding lookup
37
+ "embedding_lookup",
38
  # importing it registers the dgrad formulas on the ops, so a forward call differentiates
39
  "backward",
40
  "get_supported_act_fns",
build/torch-cuda/_ops.py CHANGED
@@ -25,7 +25,7 @@ def get_backend() -> str:
25
 
26
  def _find_ops_name() -> str:
27
  kernel_name = "finegrained_kernels"
28
- unique_id = "43f1581"
29
  backend = get_backend()
30
  return f"_{kernel_name}_{backend}_{unique_id}"
31
 
 
25
 
26
  def _find_ops_name() -> str:
27
  kernel_name = "finegrained_kernels"
28
+ unique_id = "ec5cc90"
29
  backend = get_backend()
30
  return f"_{kernel_name}_{backend}_{unique_id}"
31
 
build/torch-cuda/autotune_config.py ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 The HuggingFace Inc. team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """``FINEGRAINED_AUTOTUNE_CONFIG``: the one runtime control over every tuned kernel.
16
+
17
+ Its value is a mode name (``fast``), inline JSON, or a path to a JSON file. JSON keys are the
18
+ tuner's own options; ``"mode"`` there applies a preset whose keys explicit ones override.
19
+
20
+ The tuner reads this when each kernel is decorated, i.e. when the package is imported.
21
+ """
22
+
23
+ import json
24
+ import os
25
+ from pathlib import Path
26
+
27
+ ENV_VAR = "FINEGRAINED_AUTOTUNE_CONFIG"
28
+
29
+ # Tuner kwargs by name, plus the debugging outputs (``key_log_path``, ``call_count_path``,
30
+ # ``tracebacks``) and the cross-rank tune lock wait.
31
+ KEYS = {
32
+ "n_trials": int, "strict": bool, "runoff": bool, "basin_probes": bool,
33
+ "max_refine_iters": int, "log_path": str,
34
+ "key_log_path": str, "call_count_path": str, "tracebacks": bool, "lock_timeout": float,
35
+ }
36
+
37
+ MODES = {
38
+ # one measured config, no search phases: correctness runs, not performance
39
+ "fast": {"n_trials": 1, "runoff": False, "basin_probes": False, "max_refine_iters": 0},
40
+ }
41
+
42
+
43
+ def _read(value: str) -> dict:
44
+ """The raw config, a mode name kept as ``{"mode": ...}`` so explicit keys compose with it."""
45
+ if not value:
46
+ return {}
47
+ if value.lstrip().startswith("{"):
48
+ return json.loads(value)
49
+ if os.path.isfile(value):
50
+ return json.loads(Path(value).read_text())
51
+ return {"mode": value}
52
+
53
+
54
+ def load_autotune_config() -> dict:
55
+ """Validated overrides from the environment: the mode's preset, then explicit keys."""
56
+ config = _read(os.environ.get(ENV_VAR, ""))
57
+ mode = config.pop("mode", None)
58
+ if mode is not None:
59
+ if mode not in MODES:
60
+ raise ValueError(
61
+ f"{ENV_VAR} mode {mode!r} is not one of {sorted(MODES)} (or a JSON object / JSON file path)"
62
+ )
63
+ config = {**MODES[mode], **config}
64
+ unknown = set(config) - set(KEYS)
65
+ if unknown:
66
+ raise ValueError(
67
+ f"{ENV_VAR} has unknown keys {sorted(unknown)}; expected a subset of {sorted(KEYS)} or \"mode\""
68
+ )
69
+ return {key: KEYS[key](value) for key, value in config.items()}
70
+
build/torch-cuda/bayesian_autotuner.py CHANGED
@@ -37,6 +37,7 @@ import random
37
  import time
38
  from collections import defaultdict
39
  from contextlib import contextmanager
 
40
 
41
  import torch
42
 
@@ -53,19 +54,24 @@ from triton.runtime.autotuner import (
53
  triton_key,
54
  )
55
 
 
 
56
  logger = logging.getLogger(__name__)
57
 
58
- # Env-gated host-execution counter (FINEGRAINED_AUTOTUNE_CALLCOUNT=<path>): every
59
- # BayesianAutotuner.run() call increments its kernel's tally, dumped to <path> at exit.
 
 
 
60
  # Discriminates cudagraph REPLAY (host code runs only at capture) from per-token
61
  # re-execution — the decode-gap forensics tool.
62
  _CALL_COUNTS = None
63
- if os.environ.get("FINEGRAINED_AUTOTUNE_CALLCOUNT"):
64
  import atexit
65
 
66
  _CALL_COUNTS = {}
67
 
68
- def _dump_call_counts(path=os.environ["FINEGRAINED_AUTOTUNE_CALLCOUNT"]):
69
  with open(path, "w") as f:
70
  json.dump(_CALL_COUNTS, f, indent=1, sort_keys=True)
71
 
@@ -84,48 +90,58 @@ class BayesianAutotuner(Autotuner):
84
  max_failures: int | None = None,
85
  n_startup_trials: int = 12,
86
  gamma: float = 0.25,
87
- refine: bool = True,
 
88
  max_refine_iters: int = 5,
89
  log_path: str | None = None,
90
  path_anchor_axes: tuple[str, ...] = (),
 
91
  finite_check_args: tuple[str, ...] = (),
92
  **kwargs,
93
  ):
94
  super().__init__(*args, **kwargs)
95
  self.fn_name = getattr(self.fn, "__name__", str(self.fn))
96
- # Bayesian trial budget — the per-decorator default, overridable via the
97
- # FINEGRAINED_AUTOTUNE_TRIALS env var (quick sweeps / exhaustive runs without touching
98
- # the decorators; set it >= grid size to fall back to stock exhaustive bench-all).
99
- self.n_trials = int(os.environ.get("FINEGRAINED_AUTOTUNE_TRIALS") or n_trials)
 
100
  # A tune aborts once this many configs have failed to compile/run without the measured
101
  # budget being met. Defaults to n_trials: a grid that cannot land its measurements without
102
  # that many rejects has a pruner gap, and the fix is to fence the dead region rather than
103
  # to tolerate the compiles. Its own axis so a kernel can raise it deliberately.
104
- # an explicit argument wins over the env override, which wins over the default; `or`
105
- # would drop a deliberate `max_failures=0`, the strictest setting there is
106
- if max_failures is None:
107
- env_max_failures = os.environ.get("FINEGRAINED_AUTOTUNE_MAX_FAILURES")
108
- max_failures = env_max_failures if env_max_failures is not None else self.n_trials
109
  self.max_failures = int(max_failures)
110
  self.n_startup_trials = n_startup_trials
111
  # top fraction of measured configs the TPE treats as "good"
112
  self.gamma = gamma
113
- self.refine = refine
114
- self.max_refine_iters = max_refine_iters
 
115
  # Branch axes (by kwarg name) that relocate the TILE optimum — the caller's
116
  # declaration, since it is configuration knowledge (the tuner never hardcodes
117
  # axis names). Each distinct value combination of these axes gets one guaranteed
118
  # max-tile anchor; empty = median anchors only.
119
  self.path_anchor_axes = tuple(path_anchor_axes)
 
 
 
 
 
 
120
  # Output arg NAMES (a declaration — the tuner never hardcodes kernel details) whose
121
  # values are checked for NaN/inf after each config's bench: a config that writes
122
  # non-finite output scores inf and can never be crowned. The tuner times, it never
123
  # validates — this veto closes the NaN half of the "wrong-answer configs win
124
  # tunes" class at negligible cost (one isfinite pass per benched config).
125
  self.finite_check_args = tuple(finite_check_args)
126
- # JSONL log of every benched (key, config, ms) — set here or via the
127
- # FINEGRAINED_AUTOTUNE_LOG env var. Analyse offline to prune bad configs.
128
- self.log_path = log_path or os.environ.get("FINEGRAINED_AUTOTUNE_LOG")
 
 
129
  # steady-state launch support: key extraction without dict builds (eager decode
130
  # is host-bound; the cache-hit path of run() is on the per-token critical path)
131
  self._arg_position = {name: i for i, name in enumerate(self.arg_names)}
@@ -195,7 +211,7 @@ class BayesianAutotuner(Autotuner):
195
  if root is not e:
196
  msg += f" || root: {type(root).__name__}: {str(root)[-300:]}"
197
  err = f"{type(e).__name__}: {msg}"
198
- if os.environ.get("FINEGRAINED_AUTOTUNE_TRACEBACKS"):
199
  logger.warning(
200
  "[autotune] %s failed on %s:\n%s",
201
  self.fn_name,
@@ -460,9 +476,22 @@ class BayesianAutotuner(Autotuner):
460
  self.nargs = None
461
  return ret
462
 
 
 
 
 
 
 
 
 
 
 
 
 
 
463
  def _bayesian_search(self, configs: List[Config], args, kwargs, key) -> Config:
464
  timings: Dict[int, float] = {}
465
- sigs = [tuple(sorted(c.all_kwargs().items())) for c in configs]
466
 
467
  def bench_idx(idx: int) -> float:
468
  if idx in timings:
@@ -489,8 +518,8 @@ class BayesianAutotuner(Autotuner):
489
  # per-dimension model can write off a whole axis it never saw succeed (two dsv4 tunes
490
  # shipped 25-60% slow winners because their random seeds only sampled a basin's dead
491
  # configs), and coordinate descent can't recover a winner two coupled flips away.
492
- n_startup = max(2, min(self.n_startup_trials, self.n_trials))
493
- anchors = self._basin_anchor_indices(configs)
494
  order = list(range(len(configs)))
495
  random.Random(0).shuffle(order)
496
  warm_idx = self._warm_start_index(configs, key)
@@ -574,22 +603,21 @@ class BayesianAutotuner(Autotuner):
574
 
575
  # Coordinate-descent refinement: try single-dim perturbations around
576
  # the current best until no neighbor improves.
577
- if self.refine:
578
- for _ in range(self.max_refine_iters):
579
- best_idx = min(timings, key=timings.get)
580
- best_sig = sigs[best_idx]
581
- best_ms = timings[best_idx]
582
- improved = False
583
- for i, s in enumerate(sigs):
584
- if i in timings:
585
- continue
586
- diff = sum(1 for (_, a), (_, b) in zip(best_sig, s) if a != b)
587
- if diff != 1:
588
- continue
589
- if bench_idx(i) < best_ms:
590
- improved = True
591
- if not improved:
592
- break
593
 
594
  # Crown RUNOFF: the search's single-shot timings carry enough noise to flip crowns
595
  # between same-code tune rolls (gpt-oss prefill grouped rolled 1727-2150us on identical
@@ -597,21 +625,24 @@ class BayesianAutotuner(Autotuner):
597
  # the top candidates back-to-back — same warmed process, adjacent in time, so they see
598
  # the same clocks — and crown the winner of THAT comparison. Costs ~3 extra benches.
599
  finite = sorted((t, i) for i, t in timings.items() if t != float("inf"))
600
- if len(finite) > 1:
601
- for _, i in finite[:3]:
 
 
602
  timings.pop(i, None)
603
  bench_idx(i) # memoizing closure: re-benches and re-records the popped entry
604
 
605
  self.configs_timings = {configs[i]: t for i, t in timings.items()}
606
- best = min(timings, key=timings.get)
 
607
  if timings[best] == float("inf"):
608
  # crowning an arbitrary broken config would persist it as `best` on disk and
609
  # every later healthy process would replay it without re-tuning — the exact
610
  # sticky-contaminated-context scenario inf-forgiveness exists to survive
611
  self._report_bench_failures()
612
  raise RuntimeError(
613
- f"[bayesian-autotune] {self.fn_name}: every benched config failed "
614
- "(all-inf tune) — nothing correct to crown or persist; the context may "
615
  "be trap-contaminated (retry in a fresh process) or the grid is broken "
616
  "for this launch (see the failure report above)."
617
  )
@@ -639,12 +670,8 @@ class BayesianAutotuner(Autotuner):
639
  pass
640
 
641
  def _basin_anchor_indices(self, configs: List[Config]) -> List[int]:
642
- """One representative config index per (COMPUTE_MODE, SWAP_AB) basin — the MEDIAN in
643
- tile-sort order, a mid-sized tile with mid warps/stages. Not the smallest: a basin's
644
- minimal corner (min BK x 2 warps) can be latency-bound pathological (a 131µs anchor in
645
- a basin whose peak is 41µs re-poisons the axis it was meant to protect). Coordinate
646
- descent climbs BN/BK/warps/stages from wherever the TPE lands within the basin.
647
- Returns [] when the grid has no such axes (single basin)."""
648
  # Basin axes are DERIVED, not declared: a config kwarg with string or boolean
649
  # values is a branch axis (different code path — compute mode, operand swap,
650
  # warp specialization), which partitions the grid into disjoint performance
@@ -670,41 +697,46 @@ class BayesianAutotuner(Autotuner):
670
  if len(groups) <= 1:
671
  return []
672
 
673
- def tile_order(i):
674
- return (
675
- configs[i].kwargs.get("BLOCK_SIZE_N", 0),
676
- configs[i].kwargs.get("BLOCK_SIZE_K", 0),
677
- configs[i].num_warps,
678
- configs[i].num_stages,
679
- )
680
-
681
- # Median anchor per basin, PLUS one max-tile anchor per declared PATH-axis combo
682
- # (``path_anchor_axes``). The median alone left the big-tile end unsampled: TPE
683
- # densities lock onto the first-timed mid-tile region and coordinate descent cannot
684
- # cross DIAGONAL ridges — dsv4 mxfp8 prefill gate_up's BN=128/BK=256 winner (840us)
685
- # needs BN and warps to move together, so descent from the BN=64 crown rejects the
686
- # lone BN step and n_trials 100/200/400 all missed it; one guaranteed big-tile
687
- # sample fixed it (crown 958 -> 880 at the default budget). The max anchor rides
688
- # the declared COARSE grouping, NOT the full branch-axis cross: per-basin max
689
- # anchors on the weight-only grouped grid (32 basins: modes x WS x PS x 2 memory
690
- # modes each way) put 64 forced trials into a 100-trial budget and starved the
691
- # search — gpt-oss prefill regressed 3463 -> 4099us on a quiet box before this was
692
- # scoped. Which axes qualify is the KERNEL's declaration (configuration knowledge);
693
- # axes absent from this grid are ignored.
694
  anchors = []
695
  coarse_seen = set()
696
  coarse_axes = [k for k in self.path_anchor_axes if k in basin_axes]
697
  for basin, idxs in groups.items():
698
- ordered = sorted(idxs, key=tile_order)
699
- anchors.append(ordered[len(ordered) // 2])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
700
  if not self.path_anchor_axes:
701
- continue # no declaration -> median anchors only
702
- # declared axes absent from THIS grid still coarse-group to () — one global
703
- # max-tile anchor, the big-tile guarantee without per-basin anchor spam
704
  coarse = tuple(basin[basin_axes.index(k)] for k in coarse_axes)
705
- if coarse not in coarse_seen and ordered[-1] != anchors[-1]:
706
  coarse_seen.add(coarse)
707
- anchors.append(ordered[-1])
 
 
 
 
 
 
 
 
 
 
708
  return anchors
709
 
710
  def _warm_start_index(self, configs: List[Config], key=None):
@@ -729,8 +761,8 @@ class BayesianAutotuner(Autotuner):
729
  """Persist the tuned best config to Triton's on-disk cache so a later run (or process)
730
  skips the whole minutes-long search+compile — most of which is Triton codegen, not
731
  benching. Keyed exactly like Triton's own autotune cache (triton version + backend +
732
- kernel source hash + invalidating env + key + the full config grid), so any of those
733
- changing re-tunes.
734
 
735
  Unlike Triton's stock version we do NOT bail when configs carry a ``pre_hook`` (ours
736
  always do): we persist each config's ``all_kwargs()`` — serializable and pre_hook-free —
@@ -750,19 +782,22 @@ class BayesianAutotuner(Autotuner):
750
  triton_key(),
751
  make_backend(driver.active.get_current_target()).hash(),
752
  fn.cache_key,
 
753
  str(sorted(env_vars.items())),
754
  # Key NAMES and the search budget are determinants too: tuning_key holds
755
  # values only (renaming a key or swapping two same-typed keys would collide),
756
- # and FINEGRAINED_AUTOTUNE_TRIALS is not in Triton's invalidating-env list —
757
  # without n_trials a 3-trial exploratory winner gets replayed at 100 trials.
758
  str(self.keys),
759
- f"n_trials={self.n_trials},n_startup_trials={self.n_startup_trials},gamma={self.gamma}",
 
 
760
  str(tuning_key),
761
  ] + [str(c) for c in configs] # str(Config) is pre_hook-free and process-stable
762
  cache_key = hashlib.sha256("-".join(cache_key).encode("utf-8")).hexdigest()
763
  cache = get_cache_manager(cache_key)
764
  file_name = f"{fn.__name__[:150]}.bayes_autotune.json"
765
- keylog = os.environ.get("FINEGRAINED_AUTOTUNE_KEYLOG")
766
  if keylog:
767
  with open(keylog, "a") as f:
768
  f.write(json.dumps({
@@ -840,7 +875,7 @@ def _tuning_lock(cache, file_name: str):
840
  Held while a key is tuned and its crown published, so concurrent TP/EP ranks converge on the
841
  same config instead of each crowning its own (see ``check_disk_cache``). Advisory ``flock`` on
842
  a sidecar file next to the crown; entirely best-effort — no cache dir, no ``fcntl``, or a lock
843
- that cannot be taken within ``FINEGRAINED_TUNE_LOCK_TIMEOUT`` (default 30 min, since the holder
844
  is doing a real tune) all fall through to tuning locally. Losing the lock costs redundant work,
845
  never correctness."""
846
  lock_file = None
@@ -850,9 +885,7 @@ def _tuning_lock(cache, file_name: str):
850
  cache_dir = getattr(cache, "cache_dir", None)
851
  if cache_dir:
852
  os.makedirs(cache_dir, exist_ok=True)
853
- deadline = time.time() + float(
854
- os.environ.get("FINEGRAINED_TUNE_LOCK_TIMEOUT") or 1800
855
- )
856
  lock_file = open(os.path.join(cache_dir, f"{file_name}.lock"), "w")
857
  while True:
858
  try:
@@ -892,10 +925,12 @@ def bayesian_autotune(
892
  max_failures: int | None = None,
893
  n_startup_trials: int = 12,
894
  gamma: float = 0.25,
895
- refine: bool = True,
 
896
  max_refine_iters: int = 5,
897
  log_path: str | None = None,
898
  path_anchor_axes: tuple[str, ...] = (),
 
899
  finite_check_args: tuple[str, ...] = (),
900
  cache_results: bool = True,
901
  reset_to_zero=None,
@@ -905,19 +940,27 @@ def bayesian_autotune(
905
  """Decorator mirroring ``@triton.autotune``. Extra kwargs:
906
  n_trials: successfully measured configs per key (TPE budget; configs
907
  that fail to compile/run are skipped without consuming it)
 
908
  n_startup_trials: random seed configs before the TPE model kicks in
909
  gamma: top fraction of measured configs treated as "good"
910
- refine, max_refine_iters: coordinate-descent refinement after the TPE
 
 
 
 
 
911
  path_anchor_axes: branch axes (kwarg names) whose values relocate the tile
912
  optimum — one guaranteed max-tile anchor per value combo
913
  (see _basin_anchor_indices); the kernel declares them, the
914
  tuner stays independent of configuration details
 
 
915
  max_failures: abort the tune once this many configs fail to compile/run before
916
- the measured budget is met (default n_trials; env override
917
- FINEGRAINED_AUTOTUNE_MAX_FAILURES). Without it a broadly broken
918
  grid spins: failures don't consume the trial budget, so the search
919
  keeps picking and each pick costs an O(configs) rescan.
920
- log_path: JSONL of benched configs (or FINEGRAINED_AUTOTUNE_LOG)
921
  cache_results: persist the tuned best config to disk (on by default) so later
922
  runs skip the search+compile — see BayesianAutotuner.check_disk_cache"""
923
 
@@ -933,10 +976,12 @@ def bayesian_autotune(
933
  max_failures=max_failures,
934
  n_startup_trials=n_startup_trials,
935
  gamma=gamma,
936
- refine=refine,
 
937
  max_refine_iters=max_refine_iters,
938
  log_path=log_path,
939
  path_anchor_axes=path_anchor_axes,
 
940
  finite_check_args=finite_check_args,
941
  cache_results=cache_results,
942
  **kwargs,
 
37
  import time
38
  from collections import defaultdict
39
  from contextlib import contextmanager
40
+ from pathlib import Path
41
 
42
  import torch
43
 
 
54
  triton_key,
55
  )
56
 
57
+ from .autotune_config import load_autotune_config
58
+
59
  logger = logging.getLogger(__name__)
60
 
61
+ # Include the tuner implementation alongside Triton's kernel-source hash.
62
+ _TUNER_SOURCE_HASH = hashlib.sha256(Path(__file__).read_bytes()).hexdigest()
63
+
64
+ # Host-execution counter (``call_count_path`` in FINEGRAINED_AUTOTUNE_CONFIG): every
65
+ # BayesianAutotuner.run() call increments its kernel's tally, dumped to that path at exit.
66
  # Discriminates cudagraph REPLAY (host code runs only at capture) from per-token
67
  # re-execution — the decode-gap forensics tool.
68
  _CALL_COUNTS = None
69
+ if load_autotune_config().get("call_count_path"):
70
  import atexit
71
 
72
  _CALL_COUNTS = {}
73
 
74
+ def _dump_call_counts(path=load_autotune_config()["call_count_path"]):
75
  with open(path, "w") as f:
76
  json.dump(_CALL_COUNTS, f, indent=1, sort_keys=True)
77
 
 
90
  max_failures: int | None = None,
91
  n_startup_trials: int = 12,
92
  gamma: float = 0.25,
93
+ runoff: bool = True,
94
+ basin_probes: bool = True,
95
  max_refine_iters: int = 5,
96
  log_path: str | None = None,
97
  path_anchor_axes: tuple[str, ...] = (),
98
+ coupled_axes: tuple[tuple[str, ...], ...] = (),
99
  finite_check_args: tuple[str, ...] = (),
100
  **kwargs,
101
  ):
102
  super().__init__(*args, **kwargs)
103
  self.fn_name = getattr(self.fn, "__name__", str(self.fn))
104
+ overrides = load_autotune_config()
105
+ # Bayesian trial budget — the per-decorator default, overridable via ``n_trials`` in
106
+ # FINEGRAINED_AUTOTUNE_CONFIG (quick sweeps / exhaustive runs without touching the
107
+ # decorators; set it >= grid size to fall back to stock exhaustive bench-all).
108
+ self.n_trials = overrides.get("n_trials", n_trials)
109
  # A tune aborts once this many configs have failed to compile/run without the measured
110
  # budget being met. Defaults to n_trials: a grid that cannot land its measurements without
111
  # that many rejects has a pruner gap, and the fix is to fence the dead region rather than
112
  # to tolerate the compiles. Its own axis so a kernel can raise it deliberately.
113
+ # an explicit argument wins over the config override, which wins over the default
114
+ if max_failures is None: # ``strict`` aborts on the first failure
115
+ max_failures = 0 if overrides.get("strict") else self.n_trials
 
 
116
  self.max_failures = int(max_failures)
117
  self.n_startup_trials = n_startup_trials
118
  # top fraction of measured configs the TPE treats as "good"
119
  self.gamma = gamma
120
+ self.runoff = overrides.get("runoff", runoff)
121
+ self.basin_probes = overrides.get("basin_probes", basin_probes)
122
+ self.max_refine_iters = overrides.get("max_refine_iters", max_refine_iters)
123
  # Branch axes (by kwarg name) that relocate the TILE optimum — the caller's
124
  # declaration, since it is configuration knowledge (the tuner never hardcodes
125
  # axis names). Each distinct value combination of these axes gets one guaranteed
126
  # max-tile anchor; empty = median anchors only.
127
  self.path_anchor_axes = tuple(path_anchor_axes)
128
+ # Joint choices share one TPE density and one coordinate-refinement step.
129
+ self.coupled_axes = tuple(tuple(group) for group in coupled_axes)
130
+ coupled_names = [name for group in self.coupled_axes for name in group]
131
+ if any(len(group) < 2 for group in self.coupled_axes) or len(set(coupled_names)) != len(coupled_names):
132
+ raise ValueError("coupled_axes must contain disjoint groups of at least two configuration fields")
133
+ self._coupled_names = frozenset(coupled_names)
134
  # Output arg NAMES (a declaration — the tuner never hardcodes kernel details) whose
135
  # values are checked for NaN/inf after each config's bench: a config that writes
136
  # non-finite output scores inf and can never be crowned. The tuner times, it never
137
  # validates — this veto closes the NaN half of the "wrong-answer configs win
138
  # tunes" class at negligible cost (one isfinite pass per benched config).
139
  self.finite_check_args = tuple(finite_check_args)
140
+ # JSONL log of every benched (key, config, ms) — set here or via ``log_path`` in
141
+ # FINEGRAINED_AUTOTUNE_CONFIG. Analyse offline to prune bad configs.
142
+ self.log_path = log_path or overrides.get("log_path")
143
+ self.key_log_path = overrides.get("key_log_path")
144
+ self.tracebacks = overrides.get("tracebacks", False)
145
  # steady-state launch support: key extraction without dict builds (eager decode
146
  # is host-bound; the cache-hit path of run() is on the per-token critical path)
147
  self._arg_position = {name: i for i, name in enumerate(self.arg_names)}
 
211
  if root is not e:
212
  msg += f" || root: {type(root).__name__}: {str(root)[-300:]}"
213
  err = f"{type(e).__name__}: {msg}"
214
+ if self.tracebacks:
215
  logger.warning(
216
  "[autotune] %s failed on %s:\n%s",
217
  self.fn_name,
 
476
  self.nargs = None
477
  return ret
478
 
479
+ def _search_signatures(self, configs: List[Config]):
480
+ signatures = []
481
+ for config in configs:
482
+ values = config.all_kwargs()
483
+ signature = {(name,): value for name, value in values.items() if name not in self._coupled_names}
484
+ for group in self.coupled_axes:
485
+ try:
486
+ signature[group] = tuple(values[name] for name in group)
487
+ except KeyError as exc:
488
+ raise ValueError(f"Unknown coupled_axes configuration field: {exc.args[0]}") from exc
489
+ signatures.append(tuple(sorted(signature.items())))
490
+ return signatures
491
+
492
  def _bayesian_search(self, configs: List[Config], args, kwargs, key) -> Config:
493
  timings: Dict[int, float] = {}
494
+ sigs = self._search_signatures(configs)
495
 
496
  def bench_idx(idx: int) -> float:
497
  if idx in timings:
 
518
  # per-dimension model can write off a whole axis it never saw succeed (two dsv4 tunes
519
  # shipped 25-60% slow winners because their random seeds only sampled a basin's dead
520
  # configs), and coordinate descent can't recover a winner two coupled flips away.
521
+ n_startup = min(self.n_trials, max(2, self.n_startup_trials))
522
+ anchors = self._basin_anchor_indices(configs) if self.basin_probes else []
523
  order = list(range(len(configs)))
524
  random.Random(0).shuffle(order)
525
  warm_idx = self._warm_start_index(configs, key)
 
603
 
604
  # Coordinate-descent refinement: try single-dim perturbations around
605
  # the current best until no neighbor improves.
606
+ for _ in range(self.max_refine_iters):
607
+ best_idx = min(timings, key=timings.get)
608
+ best_sig = sigs[best_idx]
609
+ best_ms = timings[best_idx]
610
+ improved = False
611
+ for i, s in enumerate(sigs):
612
+ if i in timings:
613
+ continue
614
+ diff = sum(1 for (_, a), (_, b) in zip(best_sig, s) if a != b)
615
+ if diff != 1:
616
+ continue
617
+ if bench_idx(i) < best_ms:
618
+ improved = True
619
+ if not improved:
620
+ break
 
621
 
622
  # Crown RUNOFF: the search's single-shot timings carry enough noise to flip crowns
623
  # between same-code tune rolls (gpt-oss prefill grouped rolled 1727-2150us on identical
 
625
  # the top candidates back-to-back — same warmed process, adjacent in time, so they see
626
  # the same clocks — and crown the winner of THAT comparison. Costs ~3 extra benches.
627
  finite = sorted((t, i) for i, t in timings.items() if t != float("inf"))
628
+ finalists = list(timings)
629
+ if self.runoff and len(finite) > 1:
630
+ finalists = [i for _, i in finite[:3]]
631
+ for i in finalists:
632
  timings.pop(i, None)
633
  bench_idx(i) # memoizing closure: re-benches and re-records the popped entry
634
 
635
  self.configs_timings = {configs[i]: t for i, t in timings.items()}
636
+ # Other candidates still carry search-time samples, not comparable runoff measurements.
637
+ best = min(finalists, key=timings.get)
638
  if timings[best] == float("inf"):
639
  # crowning an arbitrary broken config would persist it as `best` on disk and
640
  # every later healthy process would replay it without re-tuning — the exact
641
  # sticky-contaminated-context scenario inf-forgiveness exists to survive
642
  self._report_bench_failures()
643
  raise RuntimeError(
644
+ f"[bayesian-autotune] {self.fn_name}: every final candidate failed "
645
+ "— nothing correct to crown or persist; the context may "
646
  "be trap-contaminated (retry in a fresh process) or the grid is broken "
647
  "for this launch (see the failure report above)."
648
  )
 
670
  pass
671
 
672
  def _basin_anchor_indices(self, configs: List[Config]) -> List[int]:
673
+ """Interior representatives of categorical basins, plus a wide output tile
674
+ per declared compute path. All anchors come from the pruned grid."""
 
 
 
 
675
  # Basin axes are DERIVED, not declared: a config kwarg with string or boolean
676
  # values is a branch axis (different code path — compute mode, operand swap,
677
  # warp specialization), which partitions the grid into disjoint performance
 
697
  if len(groups) <= 1:
698
  return []
699
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
700
  anchors = []
701
  coarse_seen = set()
702
  coarse_axes = [k for k in self.path_anchor_axes if k in basin_axes]
703
  for basin, idxs in groups.items():
704
+ # Choose the middle of every ordinal axis together. Lexicographic sorting
705
+ # ignores M and can put the "median" at extreme K/warps/stages. Each axis is
706
+ # scored by its value's rank normalized to [0, 1], so axes with many values
707
+ # do not outweigh axes with few; the anchor is the config closest to 0.5 on
708
+ # all of them (smallest squared distance), i.e. the basin's interior.
709
+ values = [configs[i].all_kwargs() for i in idxs]
710
+ axes = {
711
+ k: sorted({v[k] for v in values})
712
+ for k, value in values[0].items()
713
+ if isinstance(value, (int, float)) and not isinstance(value, bool)
714
+ }
715
+
716
+ def distance(i):
717
+ args = configs[i].all_kwargs()
718
+ return sum(
719
+ (options.index(args[k]) / max(1, len(options) - 1) - 0.5) ** 2
720
+ for k, options in axes.items()
721
+ )
722
+
723
+ anchors.append(min(idxs, key=distance))
724
  if not self.path_anchor_axes:
725
+ continue
 
 
726
  coarse = tuple(basin[basin_axes.index(k)] for k in coarse_axes)
727
+ if coarse not in coarse_seen:
728
  coarse_seen.add(coarse)
729
+ # The existing wide-tile anchor protects coupled output-tile ridges,
730
+ # with interior contraction/warps/stages rather than their largest corner.
731
+
732
+ def wide_order(i):
733
+ # largest output tile first; interior on the other axes breaks ties
734
+ kwargs = configs[i].kwargs
735
+ return -kwargs.get("BLOCK_SIZE_M", 1) * kwargs.get("BLOCK_SIZE_N", 1), distance(i)
736
+
737
+ wide = min(idxs, key=wide_order)
738
+ if wide != anchors[-1]:
739
+ anchors.append(wide)
740
  return anchors
741
 
742
  def _warm_start_index(self, configs: List[Config], key=None):
 
761
  """Persist the tuned best config to Triton's on-disk cache so a later run (or process)
762
  skips the whole minutes-long search+compile — most of which is Triton codegen, not
763
  benching. Keyed exactly like Triton's own autotune cache (triton version + backend +
764
+ kernel/tuner source hashes + invalidating env + key + the full config grid), so any
765
+ of those changing re-tunes.
766
 
767
  Unlike Triton's stock version we do NOT bail when configs carry a ``pre_hook`` (ours
768
  always do): we persist each config's ``all_kwargs()`` — serializable and pre_hook-free —
 
782
  triton_key(),
783
  make_backend(driver.active.get_current_target()).hash(),
784
  fn.cache_key,
785
+ _TUNER_SOURCE_HASH,
786
  str(sorted(env_vars.items())),
787
  # Key NAMES and the search budget are determinants too: tuning_key holds
788
  # values only (renaming a key or swapping two same-typed keys would collide),
789
+ # and the n_trials override is not in Triton's invalidating-env list —
790
  # without n_trials a 3-trial exploratory winner gets replayed at 100 trials.
791
  str(self.keys),
792
+ f"n_trials={self.n_trials},n_startup_trials={self.n_startup_trials},gamma={self.gamma},"
793
+ f"runoff={self.runoff},basin_probes={self.basin_probes},max_refine_iters={self.max_refine_iters},"
794
+ f"coupled_axes={self.coupled_axes}",
795
  str(tuning_key),
796
  ] + [str(c) for c in configs] # str(Config) is pre_hook-free and process-stable
797
  cache_key = hashlib.sha256("-".join(cache_key).encode("utf-8")).hexdigest()
798
  cache = get_cache_manager(cache_key)
799
  file_name = f"{fn.__name__[:150]}.bayes_autotune.json"
800
+ keylog = self.key_log_path
801
  if keylog:
802
  with open(keylog, "a") as f:
803
  f.write(json.dumps({
 
875
  Held while a key is tuned and its crown published, so concurrent TP/EP ranks converge on the
876
  same config instead of each crowning its own (see ``check_disk_cache``). Advisory ``flock`` on
877
  a sidecar file next to the crown; entirely best-effort — no cache dir, no ``fcntl``, or a lock
878
+ that cannot be taken within ``lock_timeout`` seconds (FINEGRAINED_AUTOTUNE_CONFIG; default 30 min, since the holder
879
  is doing a real tune) all fall through to tuning locally. Losing the lock costs redundant work,
880
  never correctness."""
881
  lock_file = None
 
885
  cache_dir = getattr(cache, "cache_dir", None)
886
  if cache_dir:
887
  os.makedirs(cache_dir, exist_ok=True)
888
+ deadline = time.time() + load_autotune_config().get("lock_timeout", 1800.0)
 
 
889
  lock_file = open(os.path.join(cache_dir, f"{file_name}.lock"), "w")
890
  while True:
891
  try:
 
925
  max_failures: int | None = None,
926
  n_startup_trials: int = 12,
927
  gamma: float = 0.25,
928
+ runoff: bool = True,
929
+ basin_probes: bool = True,
930
  max_refine_iters: int = 5,
931
  log_path: str | None = None,
932
  path_anchor_axes: tuple[str, ...] = (),
933
+ coupled_axes: tuple[tuple[str, ...], ...] = (),
934
  finite_check_args: tuple[str, ...] = (),
935
  cache_results: bool = True,
936
  reset_to_zero=None,
 
940
  """Decorator mirroring ``@triton.autotune``. Extra kwargs:
941
  n_trials: successfully measured configs per key (TPE budget; configs
942
  that fail to compile/run are skipped without consuming it)
943
+ (runtime override: ``n_trials`` in FINEGRAINED_AUTOTUNE_CONFIG)
944
  n_startup_trials: random seed configs before the TPE model kicks in
945
  gamma: top fraction of measured configs treated as "good"
946
+ runoff: remeasure the top candidates before choosing the winner
947
+ (runtime override: ``runoff``)
948
+ basin_probes: measure categorical basin anchors before the TPE
949
+ (runtime override: ``basin_probes``)
950
+ max_refine_iters: coordinate-descent iterations after the TPE (0 disables)
951
+ (runtime override: ``max_refine_iters``)
952
  path_anchor_axes: branch axes (kwarg names) whose values relocate the tile
953
  optimum — one guaranteed max-tile anchor per value combo
954
  (see _basin_anchor_indices); the kernel declares them, the
955
  tuner stays independent of configuration details
956
+ coupled_axes: groups of config fields treated as joint choices by TPE and
957
+ refinement, including Config fields such as num_stages
958
  max_failures: abort the tune once this many configs fail to compile/run before
959
+ the measured budget is met (default n_trials; runtime override
960
+ ``strict`` in FINEGRAINED_AUTOTUNE_CONFIG sets 0). Without it a broadly broken
961
  grid spins: failures don't consume the trial budget, so the search
962
  keeps picking and each pick costs an O(configs) rescan.
963
+ log_path: JSONL of benched configs (or ``log_path`` in FINEGRAINED_AUTOTUNE_CONFIG)
964
  cache_results: persist the tuned best config to disk (on by default) so later
965
  runs skip the search+compile — see BayesianAutotuner.check_disk_cache"""
966
 
 
976
  max_failures=max_failures,
977
  n_startup_trials=n_startup_trials,
978
  gamma=gamma,
979
+ runoff=runoff,
980
+ basin_probes=basin_probes,
981
  max_refine_iters=max_refine_iters,
982
  log_path=log_path,
983
  path_anchor_axes=path_anchor_axes,
984
+ coupled_axes=coupled_axes,
985
  finite_check_args=finite_check_args,
986
  cache_results=cache_results,
987
  **kwargs,
build/torch-cuda/compat.py CHANGED
@@ -502,6 +502,18 @@ def is_sm10x() -> bool:
502
  return torch.cuda.is_available() and torch.cuda.get_device_capability()[0] == 10
503
 
504
 
 
 
 
 
 
 
 
 
 
 
 
 
505
  @functools.cache
506
  def is_sm90() -> bool:
507
  """Whether the CURRENT device is Hopper (sm_90) — the target the measured NVIDIA-backend
 
502
  return torch.cuda.is_available() and torch.cuda.get_device_capability()[0] == 10
503
 
504
 
505
+ @functools.cache
506
+ def is_blackwell() -> bool:
507
+ """Whether the CURRENT device is any Blackwell family: sm_10x (datacenter), sm_11x
508
+ (Jetson Thor) or sm_12x (consumer). Wider than ``is_sm10x``: these share the FP4
509
+ conversion instructions but not TMEM scaled-MMA."""
510
+ return (
511
+ torch.cuda.is_available()
512
+ and torch.version.hip is None
513
+ and torch.cuda.get_device_capability()[0] in (10, 11, 12)
514
+ )
515
+
516
+
517
  @functools.cache
518
  def is_sm90() -> bool:
519
  """Whether the CURRENT device is Hopper (sm_90) — the target the measured NVIDIA-backend
build/torch-cuda/embedding.py ADDED
@@ -0,0 +1,306 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2026 The HuggingFace Inc. team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Selected-row lookup for floating-point and integer quantized embedding tables."""
16
+
17
+ import torch
18
+ import triton
19
+ import triton.language as tl
20
+
21
+ from .compat import (
22
+ FP8_DTYPE,
23
+ MX_SCALE_GROUP_K,
24
+ NVFP4_SCALE_GROUP_K,
25
+ add_op_namespace_prefix,
26
+ compile_time_only_triton_op,
27
+ compile_time_only_triton_wrap,
28
+ device_context,
29
+ )
30
+ from .formats import ue8m0_as_uint8
31
+ from .loading.scales import decode_group_scale
32
+ from .quant import e2m1_cols_to_f32
33
+
34
+
35
+ @triton.jit
36
+ def _decode_weight(value, row, FORMAT: tl.constexpr):
37
+ """Stored weight elements to fp32, before scaling. FP4 unpacks two columns per byte;
38
+ INT4 takes the looked-up row's nibble (even rows low, odd rows high)."""
39
+ if FORMAT == "nvfp4" or FORMAT == "mxfp4":
40
+ value = e2m1_cols_to_f32(value.to(tl.uint8))
41
+ elif FORMAT == "int4":
42
+ nibble = (value.to(tl.uint8).to(tl.int32) >> ((row % 2) * 4)) & 15
43
+ value = ((nibble ^ 8) - 8).to(tl.float32)
44
+ else:
45
+ value = value.to(tl.float32)
46
+ return value
47
+
48
+
49
+ @triton.jit
50
+ def _load_scale(
51
+ S,
52
+ program,
53
+ row,
54
+ col,
55
+ valid,
56
+ row_valid,
57
+ D: tl.constexpr,
58
+ S_ROW: tl.constexpr,
59
+ S_COL: tl.constexpr,
60
+ SCALE_BLOCK_ROW: tl.constexpr,
61
+ SCALE_BLOCK_COL: tl.constexpr,
62
+ FORMAT: tl.constexpr,
63
+ BLOCK: tl.constexpr,
64
+ PACKING: tl.constexpr,
65
+ ):
66
+ """Decoded fp32 scale for each of the tile's ``BLOCK`` output values."""
67
+ if (
68
+ D % BLOCK == 0
69
+ and S_COL != 0
70
+ and BLOCK % SCALE_BLOCK_COL == 0
71
+ and FORMAT != "mxfp8"
72
+ ):
73
+ # Load one scale per group; MXFP8 favors its existing flat byte loads.
74
+ groups = (program % (D // BLOCK)) * (BLOCK // SCALE_BLOCK_COL)
75
+ groups += tl.arange(0, BLOCK // SCALE_BLOCK_COL)
76
+ scale = tl.load(
77
+ S + (row // SCALE_BLOCK_ROW) * S_ROW + groups * S_COL,
78
+ row_valid,
79
+ other=0.0,
80
+ )
81
+ scale = decode_group_scale(scale).to(tl.float32)
82
+ scale = tl.broadcast_to(
83
+ scale[:, None], (BLOCK // SCALE_BLOCK_COL, SCALE_BLOCK_COL)
84
+ ).reshape((BLOCK,))
85
+ else:
86
+ scale_offsets = (row // SCALE_BLOCK_ROW) * S_ROW
87
+ if S_COL != 0:
88
+ scale_offsets += (col * PACKING // SCALE_BLOCK_COL) * S_COL
89
+ scale = tl.load(
90
+ S + scale_offsets,
91
+ row_valid if S_COL == 0 else valid,
92
+ other=0.0,
93
+ )
94
+ scale = decode_group_scale(scale).to(tl.float32)
95
+ if PACKING == 2:
96
+ scale = tl.broadcast_to(scale, (BLOCK // PACKING,))
97
+ scale = tl.interleave(scale, scale)
98
+ return scale
99
+
100
+
101
+ @triton.jit
102
+ def _embedding_kernel(
103
+ W,
104
+ Ids,
105
+ S,
106
+ G,
107
+ Out,
108
+ N: tl.constexpr,
109
+ D: tl.constexpr,
110
+ V: tl.constexpr,
111
+ W_ROW: tl.constexpr,
112
+ W_COL: tl.constexpr,
113
+ S_ROW: tl.constexpr,
114
+ S_COL: tl.constexpr,
115
+ SCALE_BLOCK_ROW: tl.constexpr,
116
+ SCALE_BLOCK_COL: tl.constexpr,
117
+ FORMAT: tl.constexpr,
118
+ BLOCK: tl.constexpr,
119
+ ):
120
+ # FP4 packs adjacent columns; INT4 packs adjacent vocabulary rows.
121
+ packing: tl.constexpr = 2 if (FORMAT == "nvfp4" or FORMAT == "mxfp4") else 1
122
+ program = tl.program_id(0).to(tl.int64)
123
+ offsets = program * (BLOCK // packing) + tl.arange(0, BLOCK // packing)
124
+ valid = offsets < N * (D // packing)
125
+ # Tiles contained in one row share its index and per-row scale across all lanes.
126
+ if D % BLOCK == 0:
127
+ indices = program // (D // BLOCK)
128
+ else:
129
+ indices = offsets // (D // packing)
130
+ valid_rows = indices < N
131
+ row = tl.load(Ids + indices, valid_rows, other=0).to(tl.int64)
132
+ in_bounds = (row >= 0) & (row < V)
133
+ tl.device_assert(in_bounds | ~valid_rows, "embedding index out of range")
134
+ valid = valid & in_bounds
135
+ col = offsets % (D // packing)
136
+ weight_row = row // 2 if FORMAT == "int4" else row
137
+ value = tl.load(W + weight_row * W_ROW + col * W_COL, valid, other=0.0)
138
+ value = _decode_weight(value, row, FORMAT)
139
+ scale = _load_scale(
140
+ S, program, row, col, valid, valid_rows & in_bounds, D, S_ROW, S_COL,
141
+ SCALE_BLOCK_ROW, SCALE_BLOCK_COL, FORMAT, BLOCK, packing,
142
+ )
143
+ value = value * scale
144
+ if FORMAT == "nvfp4":
145
+ value = value * tl.load(G).to(tl.float32)
146
+ output_offsets = program * BLOCK + tl.arange(0, BLOCK)
147
+ tl.store(Out + output_offsets, value, output_offsets < N * D)
148
+
149
+
150
+ def _check_inputs(weight, indices, scale, global_scale, output_dtype, weight_format, block_size):
151
+ """Validate an ``embedding_lookup`` call and resolve its format, its scale (UE8M0 viewed
152
+ as bytes), the ``(width, vocab, scale strides, scale blocks)`` kernel layout and the
153
+ output dtype. See ``embedding_lookup`` for the accepted layouts."""
154
+ if weight_format is None:
155
+ if global_scale is not None:
156
+ weight_format = "nvfp4"
157
+ else:
158
+ weight_format = "int8" if weight.dtype == torch.int8 else "fp8"
159
+ if weight_format not in ("fp8", "nvfp4", "mxfp8", "mxfp4", "int8", "int4"):
160
+ raise ValueError(f"Unsupported embedding weight format: {weight_format}.")
161
+ if weight.ndim != 2 or indices.dtype not in (torch.int32, torch.int64):
162
+ raise ValueError("Expected a 2D embedding table and int32/int64 indices.")
163
+ if weight.device != indices.device or weight.device != scale.device:
164
+ raise ValueError("Embedding table, indices, and scales must share a device.")
165
+ packed_columns = weight_format in ("nvfp4", "mxfp4")
166
+ width = weight.shape[1] * (2 if packed_columns else 1)
167
+ vocab = weight.shape[0] * (2 if weight_format == "int4" else 1)
168
+ if width == 0:
169
+ raise ValueError("Embedding width must be positive.")
170
+ float_dtypes = (torch.float16, torch.bfloat16, torch.float32)
171
+ scale_row = scale_col = 0
172
+ row_block = col_block = 1
173
+ if weight_format in ("fp8", "int8"):
174
+ expected_dtype = FP8_DTYPE if weight_format == "fp8" else torch.int8
175
+ if weight.dtype != expected_dtype or scale.dtype not in float_dtypes:
176
+ raise ValueError(
177
+ f"{weight_format.upper()} requires {expected_dtype} weights and floating scales."
178
+ )
179
+ if scale.numel() == 1 and weight_format == "fp8":
180
+ pass
181
+ elif scale.shape in ((vocab,), (vocab, 1)):
182
+ scale_row = scale.stride(0)
183
+ elif (
184
+ weight_format == "fp8"
185
+ and block_size > 0
186
+ and scale.shape
187
+ == (triton.cdiv(vocab, block_size), triton.cdiv(width, block_size))
188
+ ):
189
+ row_block = col_block = block_size
190
+ scale_row, scale_col = scale.stride()
191
+ else:
192
+ raise ValueError(
193
+ "FP8 scales must be scalar, per-row, or a block grid; INT8 scales must be per-row."
194
+ )
195
+ else:
196
+ if weight_format == "mxfp8":
197
+ if weight.dtype != FP8_DTYPE:
198
+ raise ValueError("MXFP8 requires E4M3 weights.")
199
+ elif weight.dtype not in (torch.int8, torch.uint8):
200
+ raise ValueError("FP4 and INT4 weights must be packed int8/uint8 bytes.")
201
+ if weight_format == "nvfp4":
202
+ col_block = NVFP4_SCALE_GROUP_K
203
+ scale_dtypes = (FP8_DTYPE,)
204
+ elif weight_format in ("mxfp8", "mxfp4"):
205
+ col_block = MX_SCALE_GROUP_K
206
+ scale = ue8m0_as_uint8(scale)
207
+ scale_dtypes = (torch.uint8,)
208
+ else:
209
+ if scale.ndim != 2 or scale.shape[1] == 0 or width % scale.shape[1]:
210
+ raise ValueError(
211
+ "INT4 scales must be a per-row grid of equal column groups."
212
+ )
213
+ col_block = width // scale.shape[1]
214
+ scale_dtypes = float_dtypes
215
+ if (
216
+ width % col_block
217
+ or scale.shape != (vocab, width // col_block)
218
+ or scale.dtype not in scale_dtypes
219
+ ):
220
+ raise ValueError(
221
+ f"{weight_format.upper()} requires a ({vocab}, {width // col_block}) scale grid with dtype in {scale_dtypes}."
222
+ )
223
+ scale_row, scale_col = scale.stride()
224
+ if weight_format == "nvfp4":
225
+ if (
226
+ global_scale is None
227
+ or global_scale.dtype != torch.float32
228
+ or global_scale.numel() != 1
229
+ or global_scale.device != weight.device
230
+ ):
231
+ raise ValueError(
232
+ "NVFP4 requires one FP32 global multiplier on the table's device."
233
+ )
234
+ elif global_scale is not None:
235
+ raise ValueError("Only NVFP4 embeddings use a global multiplier.")
236
+ dtype = output_dtype or (
237
+ torch.bfloat16 if weight_format in ("nvfp4", "mxfp8", "mxfp4") else scale.dtype
238
+ )
239
+ if dtype not in float_dtypes:
240
+ raise ValueError("Embedding output must be float16, bfloat16, or float32.")
241
+ return weight_format, scale, (width, vocab, scale_row, scale_col, row_block, col_block), dtype
242
+
243
+
244
+ @compile_time_only_triton_op(add_op_namespace_prefix("embedding_lookup"))
245
+ def embedding_lookup(
246
+ weight: torch.Tensor,
247
+ indices: torch.Tensor,
248
+ scale: torch.Tensor,
249
+ global_scale: torch.Tensor | None = None,
250
+ output_dtype: torch.dtype | None = None,
251
+ *,
252
+ weight_format: str | None = None,
253
+ block_size: int = 128,
254
+ ) -> torch.Tensor:
255
+ """Gather and dequantize selected rows without expanding the embedding table.
256
+
257
+ ``weight_format`` selects ``fp8``, ``nvfp4``, ``mxfp8``, ``mxfp4``, ``int8``, or ``int4``.
258
+ By default, a global scale selects NVFP4, signed int8 weights select INT8, and otherwise
259
+ FP8 is used. FP8 accepts scalar, per-row, or square block scales (``block_size``).
260
+ NVFP4 uses E4M3 group-16 scales and one FP32 multiplier; MX formats use UE8M0 group-32
261
+ scales. FP4 packs adjacent columns, low nibble first. INT8 uses per-row scales; INT4
262
+ packs adjacent vocabulary rows and uses per-row column groups inferred from its scale
263
+ grid. Integer values are signed, symmetric, and have no zero point.
264
+
265
+ Outputs default to the floating scale dtype, or bf16 for NVFP4/MX. Table/grid strides
266
+ may describe TP slices. Indices follow ``torch.nn.functional.embedding``: negative and
267
+ out-of-range values fail; padding indices look up their stored row. Empty, repeated,
268
+ and noncontiguous indices work.
269
+ """
270
+ weight_format, scale, layout, dtype = _check_inputs(
271
+ weight, indices, scale, global_scale, output_dtype, weight_format, block_size
272
+ )
273
+ width, vocab, scale_row, scale_col, row_block, col_block = layout
274
+ out = torch.empty((*indices.shape, width), dtype=dtype, device=weight.device)
275
+ rows, numel = indices.numel(), out.numel()
276
+ if rows == 0:
277
+ return out
278
+ # Keep small lookups parallel; larger tiles amortize scheduling for bulk lookups.
279
+ block = 256
280
+ if numel >= 256 * 1024:
281
+ block = 1024
282
+ if numel >= 4 * 1024 * 1024:
283
+ block = 4096
284
+ with device_context(weight.device):
285
+ compile_time_only_triton_wrap(_embedding_kernel)[(triton.cdiv(numel, block),)](
286
+ weight,
287
+ indices.reshape(-1),
288
+ scale,
289
+ global_scale,
290
+ out,
291
+ rows,
292
+ width,
293
+ vocab,
294
+ weight.stride(0),
295
+ weight.stride(1),
296
+ scale_row,
297
+ scale_col,
298
+ row_block,
299
+ col_block,
300
+ weight_format,
301
+ block,
302
+ num_warps=4,
303
+ enable_fp_fusion=False,
304
+ debug=True,
305
+ )
306
+ return out
build/torch-cuda/metadata.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "name": "finegrained-kernels",
3
- "id": "_finegrained_kernels_cuda_43f1581",
4
  "version": 0,
5
  "kernels-minver": "0.14.0",
6
  "license": "Apache-2.0",
@@ -12,13 +12,15 @@
12
  "digest": {
13
  "algorithm": "sha256",
14
  "files": {
15
- "__init__.py": "43nBPHxybam5ChWZmsz1Ji+pjJ6K6zF5lzwbqPcMwE4=",
16
- "_ops.py": "OSOJZM6uXgutp0kDIGNXhiJDHbTZg6slWOmcmwDfRB4=",
 
17
  "backward.py": "K1S6KD3J/12SthXiqBcmNO9kUikctQpccsbxsO8lpRg=",
18
  "batched.py": "U4zfmMlnQjr4j+kiIxzzohKmIXG0rtQX38c3i+aBgT8=",
19
- "bayesian_autotuner.py": "63cy/g/2qR2hwuRYgIoM05Ylhq7QCncazpaBTHd4iGs=",
20
- "compat.py": "4z8Q3urWNhfDM7qeg9gSZslVk9bQl5e46Mc2lT8SwpQ=",
21
  "descriptors.py": "4acRMazGPFlbydNt1Hbo8RpAqb3fOtnOQBv5yb4onqw=",
 
22
  "epilogue.py": "vIeMt0EqWgpvadDw5r38/5TVRMMzIMthfXGHPa3lTeY=",
23
  "formats.py": "rtzJgKnIK1PV/sqNnPGO67Ve6czzx1ldyIhs9HhydSk=",
24
  "grouped.py": "jv+qffb9l+MRDbo9UdA1yV15o8QcgNvE6ZYzCpPrpYM=",
@@ -29,11 +31,11 @@
29
  "mma.py": "gLkCU5QjhQVMacixNR24Yi07aNelQyN+I0pJOuRxE4E=",
30
  "moe.py": "4n/TzWy0kRXAL9qGY/aChbdSdCpahVVmMUKCfd6/zF0=",
31
  "norm.py": "VvE6GUE0Oztf7PDcviLCcB4fhnXt7/zgdE+aApv6DBM=",
32
- "pruners.py": "CzVRCpThHvmt4N3YH0GJ+JV/Cx6c+bV6vJm1lXyCdtM=",
33
- "quant.py": "Vqon6Gl/VsWZ+5ej1FDhD9aqFQUBgUb0HKXqrk5CoFQ=",
34
  "scheduling.py": "cxwoL5NTJxARVxLStDsNalm/jqbkCObm8uyLRFL/yYc=",
35
  "swizzle.py": "ueIJ9FsO8QZhVt3nMFlA+8b+U9/ZHabHFlhufsONV04=",
36
- "symbols.json": "pajgmx0vGKA1tt5P+W3f88iV3+m3SWIYcnFhZ+YrLBI="
37
  }
38
  },
39
  "provenance": {
@@ -43,7 +45,7 @@
43
  "dirty": false
44
  },
45
  "kernel": {
46
- "commit": "43f15810b3acaad1d17f67745f0676896b6fcf0d",
47
  "dirty": false
48
  }
49
  }
 
1
  {
2
  "name": "finegrained-kernels",
3
+ "id": "_finegrained_kernels_cuda_ec5cc90",
4
  "version": 0,
5
  "kernels-minver": "0.14.0",
6
  "license": "Apache-2.0",
 
12
  "digest": {
13
  "algorithm": "sha256",
14
  "files": {
15
+ "__init__.py": "xFRwaLTont8ZLTxS5ajuiDqM9gWwXAU4xap2prqdhrY=",
16
+ "_ops.py": "DwdUBjZmrNFv1HEdJDNcSfPqBc4da6D3M8v2dUzUA3g=",
17
+ "autotune_config.py": "d5fb2Vqss0fgC9TZbarrmQIInL1n1L7rHz6o3Gqv6cI=",
18
  "backward.py": "K1S6KD3J/12SthXiqBcmNO9kUikctQpccsbxsO8lpRg=",
19
  "batched.py": "U4zfmMlnQjr4j+kiIxzzohKmIXG0rtQX38c3i+aBgT8=",
20
+ "bayesian_autotuner.py": "NDRUQrhk+bEEvrOkIilWJPfqdKpcsd4E4QQ9AHzylKI=",
21
+ "compat.py": "K3RCq1ETi80duzv3zSldIwrL5e46vZAWtLxLlg1LXvM=",
22
  "descriptors.py": "4acRMazGPFlbydNt1Hbo8RpAqb3fOtnOQBv5yb4onqw=",
23
+ "embedding.py": "k7jiz2vtEJuYKo+w6TuS/4WO1D4Bv96O5DMFjEpYUGo=",
24
  "epilogue.py": "vIeMt0EqWgpvadDw5r38/5TVRMMzIMthfXGHPa3lTeY=",
25
  "formats.py": "rtzJgKnIK1PV/sqNnPGO67Ve6czzx1ldyIhs9HhydSk=",
26
  "grouped.py": "jv+qffb9l+MRDbo9UdA1yV15o8QcgNvE6ZYzCpPrpYM=",
 
31
  "mma.py": "gLkCU5QjhQVMacixNR24Yi07aNelQyN+I0pJOuRxE4E=",
32
  "moe.py": "4n/TzWy0kRXAL9qGY/aChbdSdCpahVVmMUKCfd6/zF0=",
33
  "norm.py": "VvE6GUE0Oztf7PDcviLCcB4fhnXt7/zgdE+aApv6DBM=",
34
+ "pruners.py": "5kZlpQiAvhdy8tiD8+tsy3YLDwYpHGY/aKAsxIVkTRw=",
35
+ "quant.py": "1fNRvb63DAOcFNLfKw3cSwMqSwLua4smI+xvhlsUk3k=",
36
  "scheduling.py": "cxwoL5NTJxARVxLStDsNalm/jqbkCObm8uyLRFL/yYc=",
37
  "swizzle.py": "ueIJ9FsO8QZhVt3nMFlA+8b+U9/ZHabHFlhufsONV04=",
38
+ "symbols.json": "sVGkXI22bMHsNCUTsKl1Hj70D7j8D+/KhJ2CS1JhIPA="
39
  }
40
  },
41
  "provenance": {
 
45
  "dirty": false
46
  },
47
  "kernel": {
48
+ "commit": "ec5cc90e1657566c66c62b5021435edbcfa348c3",
49
  "dirty": false
50
  }
51
  }
build/torch-cuda/metadata.json.sigstore CHANGED
@@ -1 +1 @@
1
- {"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json","verificationMaterial":{"certificate":{"rawBytes":"MIIHKjCCBrGgAwIBAgIUYjRfDt64EwMm2w5mvJf2snFI03gwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjYxMDIwMDc0NTA4WhcNMjYxMDIwMDc1NTA4WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEVosgukER4CXhkkdfSXj6fQjeiWig2uOUbciDoDxHWLHj/LYrEK37NIX0hVju279Sg0dyPpiIctPzCStUj3ecUaOCBdAwggXMMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAfBgNVHSMEGDAWgBTf0+nPViQRlvmo2OkoVaLGLhhkPzBrBgNVHREBAf8EYTBfhl1odHRwczovL2dpdGh1Yi5jb20vaHVnZ2luZ2ZhY2Uva2VybmVscy1jb21tdW5pdHkvLmdpdGh1Yi93b3JrZmxvd3MvYnVpbGQueWFtbEByZWZzL2hlYWRzL21haW4wOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAfBgorBgEEAYO/MAECBBF3b3JrZmxvd19kaXNwYXRjaDA2BgorBgEEAYO/MAEDBCg0M2YxNTgxMGIzYWNhYWQxZDE3ZjY3NzQ1ZjA2NzY4OTZiNmZjZjBkMBMGCisGAQQBg78wAQQEBUJ1aWxkMCsGCisGAQQBg78wAQUEHWh1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5MB0GCisGAQQBg78wAQYED3JlZnMvaGVhZHMvbWFpbjA7BgorBgEEAYO/MAEIBC0MK2h0dHBzOi8vdG9rZW4uYWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20wbQYKKwYBBAGDvzABCQRfDF1odHRwczovL2dpdGh1Yi5jb20vaHVnZ2luZ2ZhY2Uva2VybmVscy1jb21tdW5pdHkvLmdpdGh1Yi93b3JrZmxvd3MvYnVpbGQueWFtbEByZWZzL2hlYWRzL21haW4wOAYKKwYBBAGDvzABCgQqDCg0M2YxNTgxMGIzYWNhYWQxZDE3ZjY3NzQ1ZjA2NzY4OTZiNmZjZjBkMBsGCisGAQQBg78wAQsEDQwLc2VsZi1ob3N0ZWQwQAYKKwYBBAGDvzABDAQyDDBodHRwczovL2dpdGh1Yi5jb20vaHVnZ2luZ2ZhY2Uva2VybmVscy1jb21tdW5pdHkwOAYKKwYBBAGDvzABDQQqDCg0M2YxNTgxMGIzYWNhYWQxZDE3ZjY3NzQ1ZjA2NzY4OTZiNmZjZjBkMB8GCisGAQQBg78wAQ4EEQwPcmVmcy9oZWFkcy9tYWluMBoGCisGAQQBg78wAQ8EDAwKMTA3MTQ3NTUyOTAuBgorBgEEAYO/MAEQBCAMHmh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZTAYBgorBgEEAYO/MAERBAoMCDI1NzIwNzQzMG0GCisGAQQBg78wARIEXwxdaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5Ly5naXRodWIvd29ya2Zsb3dzL2J1aWxkLnlhbWxAcmVmcy9oZWFkcy9tYWluMDgGCisGAQQBg78wARMEKgwoNDNmMTU4MTBiM2FjYWFkMWQxN2Y2Nzc0NWYwNjc2ODk2YjZmY2YwZDAhBgorBgEEAYO/MAEUBBMMEXdvcmtmbG93X2Rpc3BhdGNoMGQGCisGAQQBg78wARUEVgxUaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5L2FjdGlvbnMvcnVucy8zOTM4Nzk5OTk0Ny9hdHRlbXB0cy8xMBYGCisGAQQBg78wARYECAwGcHVibGljMEYGCisGAQQBg78wARgEOAw2cmVwbzpodWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eTpyZWY6cmVmcy9oZWFkcy9tYWluMIGJBgorBgEEAdZ5AgQCBHsEeQB3AHUA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGhWEVw1gAABAMARjBEAiBQIWsZSWB9SJnGRw0TAla0o5FB0zYAm05drx5ceF8hZwIgcJXjQg1k+AOW7IQ8jSl/xqcqBqMtOZZ4ZDUyAW74/3EwCgYIKoZIzj0EAwMDZwAwZAIwQUUPe2Wu9HMn7udhGmysCY3wmjvsq5XAq26BaF2dsHqb654f7eVjlzgv69NyN+INAjBS+0uAAoIQtkuKGmdxafnxXTlaVHKjkgGeHzCOM9vQ2d1M5Pe6SMCzIJX6Be0kZv8="},"tlogEntries":[{"logIndex":"3333276781","logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="},"kindVersion":{"kind":"hashedrekord","version":"0.0.1"},"integratedTime":"1792482308","inclusionPromise":{"signedEntryTimestamp":"MEQCIBFaOIQqgtBjrkEZL6N9d+ec1YTSMPhobCkwlT0b0kSCAiA1RnJFqAyg2V4NirN3+K9sq3AlLM6Fr7y0yLF5WUaPJg=="},"inclusionProof":{"logIndex":"3211372519","rootHash":"AAuNOep3f6B6LEiLxEJLdf3MrE+3zqg7zm9NZq+xwfg=","treeSize":"3211372521","hashes":["kSi6E1FLwjGxB6aGLjryVwfihvJqSpRS8HNrf320SFM=","KpdKzAVftSQYDDBeFgeUVygXUujlCunLuEufgAMcTCs=","HsnVOmvwSYcTBx5ev7Cwx82EDIzB4cL99kQg3bhkmk8=","ZO4WKHe6e+kjduSIqG+P39aU3jpzqV5WS46QbUt/1lM=","DSCf6YnSfwKGZbSQHikozOUYjJVNknivnUg1PC0RRns=","OMrFmT5b3Baa5tQ18hoVjZoPhI3C49iB09L+Byp3z2o=","/2z6rGxIRvEQCFmmSVuv3acTBVHwi852tRBHlC4/vSE=","aHvYjdQ34DQYMV9K0YMPPiIHQk74oGCBXn6ekwMGA3U=","pPbxA70P/0HkEiYY2jZB61Bpv0868ThbYHS5oemCUxo=","eKD1kuBv/uuXm3Q+ddDIoOM07eGyxtScSe41MjUUwEA=","35bnJ5pQ9Q/ajuvM0i7P6+YcssTBPuuptjhAKxuEmfo=","OcDrZ0RIsie8PY2uv3Av6cECp65xfWyxKOKcM1/c3sc=","zj28xnvSQbA7lgK1R588ZHvZYy6Gy03wZvsJNVfxG9o=","2ljWFy3+P4yeeVU2ALgF4M45of0N5Rv4W1Z/jm/9n5w=","3aUq4N47Z1/AszcODIxRgusLXNRFZAnHFg6Vur7t22o=","PwdgHXfl6JMsozFa2gp9OjWOM8+7eYRrqPJa9FEI6k4=","HovTPBWzoErvUg1u5sIi3x1ZonZ7yJekVsBrmpomCIA=","G+nk7mgE5h6dUmv2UY7NDJrY3WvrZY31raaO1AETt+o=","qlTWNDzERnTBQVcHHXXVPjiUP0SsikGxfcxVlgqVFDo=","gg9GNApNke3+zNhl4tsWtmIycCFrOMCB3lys6XuSQNQ=","NlOclldmKPA1tMKIskqHAwuQDYHp31YwpK+OOMrMccc=","qxzHanAzz57SDdmJe0B7bJK72NTIBbwEMGBBKvDOROw=","xH/DCseLHr9eKoYT8qsORZK7zVdEGYWHuVtsVrD95wY="],"checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n3211372521\nAAuNOep3f6B6LEiLxEJLdf3MrE+3zqg7zm9NZq+xwfg=\n\n— rekor.sigstore.dev wNI9ajBGAiEAiHqnOXsqc9SUah3w9DEbIpUb2csqmTT7SpdakR2wOmYCIQDAikNdot3q0+e2IXLza+Diw4Tn08v5eti/aHYhGvKzTg==\n"}},"canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJlMTE4OGEyZmEzYWM5NGJkYTFmMzEzMDQ1NDU2YzFhYmNmZTcwNmM1ZDU4YmMwN2RjZjA5Yjg3NmZlZjM0N2UwIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJUUNscUdyeDRIRTRNdG1Uc24rZFZWTVEyWUEweTRjUGpLT0xBY1E3emlENGNnSWdBOEt4YU5qYTV5RlZkcXoxRG5qM0tpbE8yZDg3WHdpWmU2NExMd3B3c2FFPSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVaExha05EUW5KSFowRjNTVUpCWjBsVldXcFNaa1IwTmpSRmQwMXRNbmMxYlhaS1pqSnpia1pKTURObmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFplRTFFU1hkTlJHTXdUbFJCTkZkb1kwNU5hbGw0VFVSSmQwMUVZekZPVkVFMFYycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZXYjNObmRXdEZValJEV0docmEyUm1VMWhxTm1aUmFtVnBWMmxuTW5WUFZXSmphVVFLYjBSNFNGZE1TR292VEZseVJVc3pOMDVKV0RCb1ZtcDFNamM1VTJjd1pIbFFjR2xKWTNSUWVrTlRkRlZxTTJWalZXRlBRMEprUVhkbloxaE5UVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXWkNaMDVXU0ZOTlJVZEVRVmRuUWxSbUNqQXJibEJXYVZGU2JIWnRiekpQYTI5V1lVeEhUR2hvYTFCNlFuSkNaMDVXU0ZKRlFrRm1PRVZaVkVKbWFHd3hiMlJJVW5kamVtOTJUREprY0dSSGFERUtXV2sxYW1JeU1IWmhTRlp1V2pKc2RWb3lXbWhaTWxWMllUSldlV0p0Vm5OamVURnFZakl4ZEdSWE5YQmtTR3QyVEcxa2NHUkhhREZaYVRrellqTktjZ3BhYlhoMlpETk5kbGx1Vm5CaVIxRjFaVmRHZEdKRlFubGFWMXA2VERKb2JGbFhVbnBNTWpGb1lWYzBkMDlSV1V0TGQxbENRa0ZIUkhaNlFVSkJVVkZ5Q21GSVVqQmpTRTAyVEhrNU1HSXlkR3hpYVRWb1dUTlNjR0l5TlhwTWJXUndaRWRvTVZsdVZucGFXRXBxWWpJMU1GcFhOVEJNYlU1MllsUkJaa0puYjNJS1FtZEZSVUZaVHk5TlFVVkRRa0pHTTJJelNuSmFiWGgyWkRFNWEyRllUbmRaV0ZKcVlVUkJNa0puYjNKQ1owVkZRVmxQTDAxQlJVUkNRMmN3VFRKWmVBcE9WR2Q0VFVkSmVsbFhUbWhaVjFGNFdrUkZNMXBxV1ROT2VsRXhXbXBCTWs1NldUUlBWRnBwVG0xYWFscHFRbXROUWsxSFEybHpSMEZSVVVKbk56aDNDa0ZSVVVWQ1ZVb3hZVmQ0YTAxRGMwZERhWE5IUVZGUlFtYzNPSGRCVVZWRlNGZG9NVm95WkhCaWJXUnRXVmRPYkV3eWRHeGpiVFZzWWtoTmRGa3lPWFFLWWxoV2RXRllValZOUWpCSFEybHpSMEZSVVVKbk56aDNRVkZaUlVRelNteGFiazEyWVVkV2FGcElUWFppVjBad1ltcEJOMEpuYjNKQ1owVkZRVmxQTHdwTlFVVkpRa013VFVzeWFEQmtTRUo2VDJrNGRtUkhPWEphVnpSMVdWZE9NR0ZYT1hWamVUVnVZVmhTYjJSWFNqRmpNbFo1V1RJNWRXUkhWblZrUXpWcUNtSXlNSGRpVVZsTFMzZFpRa0pCUjBSMmVrRkNRMUZTWmtSR01XOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhbUl5TUhaaFNGWnVXakpzZFZveVdtZ0tXVEpWZG1FeVZubGliVlp6WTNreGFtSXlNWFJrVnpWd1pFaHJka3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsbHVWbkJpUjFGMVpWZEdkQXBpUlVKNVdsZGFla3d5YUd4WlYxSjZUREl4YUdGWE5IZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1EyZFJjVVJEWnpCTk1sbDRUbFJuZUUxSFNYcFpWMDVvQ2xsWFVYaGFSRVV6V21wWk0wNTZVVEZhYWtFeVRucFpORTlVV21sT2JWcHFXbXBDYTAxQ2MwZERhWE5IUVZGUlFtYzNPSGRCVVhORlJGRjNUR015Vm5NS1dta3hiMkl6VGpCYVYxRjNVVUZaUzB0M1dVSkNRVWRFZG5wQlFrUkJVWGxFUkVKdlpFaFNkMk42YjNaTU1tUndaRWRvTVZscE5XcGlNakIyWVVoV2JncGFNbXgxV2pKYWFGa3lWWFpoTWxaNVltMVdjMk41TVdwaU1qRjBaRmMxY0dSSWEzZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1JGRlJjVVJEWnpCTk1sbDRDazVVWjNoTlIwbDZXVmRPYUZsWFVYaGFSRVV6V21wWk0wNTZVVEZhYWtFeVRucFpORTlVV21sT2JWcHFXbXBDYTAxQ09FZERhWE5IUVZGUlFtYzNPSGNLUVZFMFJVVlJkMUJqYlZadFkzazViMXBYUm10amVUbDBXVmRzZFUxQ2IwZERhWE5IUVZGUlFtYzNPSGRCVVRoRlJFRjNTMDFVUVROTlZGRXpUbFJWZVFwUFZFRjFRbWR2Y2tKblJVVkJXVTh2VFVGRlVVSkRRVTFJYldnd1pFaENlazlwT0haYU1td3dZVWhXYVV4dFRuWmlVemx2WkZka2JtRlhOVzVhYlVacUNscFVRVmxDWjI5eVFtZEZSVUZaVHk5TlFVVlNRa0Z2VFVORVNURk9la2wzVG5wUmVrMUhNRWREYVhOSFFWRlJRbWMzT0hkQlVrbEZXSGQ0WkdGSVVqQUtZMGhOTmt4NU9XNWhXRkp2WkZkSmRWa3lPWFJNTW1neFdqSmtjR0p0WkcxWlYwNXNUREowYkdOdE5XeGlTRTEwV1RJNWRHSllWblZoV0ZJMVRIazFiZ3BoV0ZKdlpGZEpkbVF5T1hsaE1scHpZak5rZWt3eVNqRmhWM2hyVEc1c2FHSlhlRUZqYlZadFkzazViMXBYUm10amVUbDBXVmRzZFUxRVowZERhWE5IQ2tGUlVVSm5OemgzUVZKTlJVdG5kMjlPUkU1dFRWUlZORTFVUW1sTk1rWnFXVmRHYTAxWFVYaE9NbGt5VG5wak1FNVhXWGRPYW1NeVQwUnJNbGxxV20wS1dUSlpkMXBFUVdoQ1oyOXlRbWRGUlVGWlR5OU5RVVZWUWtKTlRVVllaSFpqYlhSdFlrYzVNMWd5VW5Cak0wSm9aRWRPYjAxSFVVZERhWE5IUVZGUlFncG5OemgzUVZKVlJWWm5lRlZoU0ZJd1kwaE5Oa3g1T1c1aFdGSnZaRmRKZFZreU9YUk1NbWd4V2pKa2NHSnRaRzFaVjA1c1RESjBiR050Tld4aVNFMTBDbGt5T1hSaVdGWjFZVmhTTlV3eVJtcGtSMngyWW01TmRtTnVWblZqZVRoNlQxUk5ORTU2YXpWUFZHc3dUbms1YUdSSVVteGlXRUl3WTNrNGVFMUNXVWNLUTJselIwRlJVVUpuTnpoM1FWSlpSVU5CZDBkalNGWnBZa2RzYWsxRldVZERhWE5IUVZGUlFtYzNPSGRCVW1kRlQwRjNNbU50Vm5kaWVuQnZaRmRrYmdwaFZ6VnVXbTFHYWxwVE9YSmFXRXAxV2xkNGVreFhUblppVnpFeFltMXNNR1ZVY0hsYVYxazJZMjFXYldONU9XOWFWMFpyWTNrNWRGbFhiSFZOU1VkS0NrSm5iM0pDWjBWRlFXUmFOVUZuVVVOQ1NITkZaVkZDTTBGSVZVRXpWREIzWVhOaVNFVlVTbXBIVWpSamJWZGpNMEZ4U2t0WWNtcGxVRXN6TDJnMGNIa0taME00Y0Rkdk5FRkJRVWRvVjBWV2R6Rm5RVUZDUVUxQlVtcENSVUZwUWxGSlYzTmFVMWRDT1ZOS2JrZFNkekJVUVd4aE1HODFSa0l3ZWxsQmJUQTFaQXB5ZURWalpVWTRhRnAzU1dkalNsaHFVV2N4YXl0QlQxYzNTVkU0YWxOc0wzaHhZM0ZDY1UxMFQxcGFORnBFVlhsQlZ6YzBMek5GZDBObldVbExiMXBKQ25wcU1FVkJkMDFFV25kQmQxcEJTWGRSVlZWUVpUSlhkVGxJVFc0M2RXUm9SMjE1YzBOWk0zZHRhblp6Y1RWWVFYRXlOa0poUmpKa2MwaHhZalkxTkdZS04yVldhbXg2WjNZMk9VNTVUaXRKVGtGcVFsTXJNSFZCUVc5SlVYUnJkVXRIYldSNFlXWnVlRmhVYkdGV1NFdHFhMmRIWlVoNlEwOU5PWFpSTW1ReFRRbzFVR1UyVTAxRGVrbEtXRFpDWlRCclduWTRQUW90TFMwdExVVk9SQ0JEUlZKVVNVWkpRMEZVUlMwdExTMHRDZz09In19fX0="}],"timestampVerificationData":{"rfc3161Timestamps":[{"signedTimestamp":"MIICyjADAgEAMIICwQYJKoZIhvcNAQcCoIICsjCCAq4CAQMxDTALBglghkgBZQMEAgEwgbgGCyqGSIb3DQEJEAEEoIGoBIGlMIGiAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgY3k0z/Mt7jMBEhOjvjf2G4CnoToAJ6VFGWkxAh1uoOYCFQCJXH8pBrNsoDup37wFNKA026PnoBgPMjAyNjEwMjAwNzQ1MDhaMAMCAQGgMqQwMC4xFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEVMBMGA1UEAxMMc2lnc3RvcmUtdHNhoAAxggHbMIIB1wIBATBRMDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQCFDoTVC8MkGHuvMFDL8uKjosqI4sMMAsGCWCGSAFlAwQCAaCB/DAaBgkqhkiG9w0BCQMxDQYLKoZIhvcNAQkQAQQwHAYJKoZIhvcNAQkFMQ8XDTI2MTAyMDA3NDUwOFowLwYJKoZIhvcNAQkEMSIEIF71Fa9Y2eBvEIVLLboLwnryQ7XILn3/JFn/+1wF+4ddMIGOBgsqhkiG9w0BCRACLzF/MH0wezB5BCCF+Se8B6tiysO0Q1bBDvyBssaIP9p6uebYcNnROs0FtzBVMD2kOzA5MRUwEwYDVQQKEwxzaWdzdG9yZS5kZXYxIDAeBgNVBAMTF3NpZ3N0b3JlLXRzYS1zZWxmc2lnbmVkAhQ6E1QvDJBh7rzBQy/Lio6LKiOLDDAKBggqhkjOPQQDAgRnMGUCMQDjXGEzJIPO0LhMOvGESo6oPJAF8cA/W/LS4FxKIXt2lA+oMCsX4UlFfHVmyUHEi8MCMGGJHGkEaw0eWLT2aTvt7BArmK0TWJXsTkbdnXPHHieoVQ6XUIfg+a76JBGvPIZN0w=="}]}},"messageSignature":{"messageDigest":{"algorithm":"SHA2_256","digest":"4RiKL6OslL2h8xMEVFbBq8/nBsXVi8B9zwm4dv7zR+A="},"signature":"MEUCIQClqGrx4HE4MtmTsn+dVVMQ2YA0y4cPjKOLAcQ7ziD4cgIgA8KxaNja5yFVdqz1Dnj3KilO2d87XwiZe64LLwpwsaE="}}
 
1
+ {"mediaType":"application/vnd.dev.sigstore.bundle.v0.3+json", "verificationMaterial":{"certificate":{"rawBytes":"MIIHKzCCBrGgAwIBAgIUP/mmZM1RBNZy+2BaxIPboKaANfUwCgYIKoZIzj0EAwMwNzEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MR4wHAYDVQQDExVzaWdzdG9yZS1pbnRlcm1lZGlhdGUwHhcNMjYxMDIwMDk1MzU0WhcNMjYxMDIwMTAwMzU0WjAAMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcYog7arHY0Y9LB1dbz4EMiisjAeogYrVaM/azAT620TxPmQwSkpj3gM5WuntjYLVKH7v6Y/ZJKbtEH3Iyz0eQqOCBdAwggXMMA4GA1UdDwEB/wQEAwIHgDATBgNVHSUEDDAKBggrBgEFBQcDAzAfBgNVHSMEGDAWgBTf0+nPViQRlvmo2OkoVaLGLhhkPzBrBgNVHREBAf8EYTBfhl1odHRwczovL2dpdGh1Yi5jb20vaHVnZ2luZ2ZhY2Uva2VybmVscy1jb21tdW5pdHkvLmdpdGh1Yi93b3JrZmxvd3MvYnVpbGQueWFtbEByZWZzL2hlYWRzL21haW4wOQYKKwYBBAGDvzABAQQraHR0cHM6Ly90b2tlbi5hY3Rpb25zLmdpdGh1YnVzZXJjb250ZW50LmNvbTAfBgorBgEEAYO/MAECBBF3b3JrZmxvd19kaXNwYXRjaDA2BgorBgEEAYO/MAEDBChlYzVjYzkwZTE2NTc1NjZjNjZjNjJiNTAyMTQzNWVkYmNmYTM0OGMzMBMGCisGAQQBg78wAQQEBUJ1aWxkMCsGCisGAQQBg78wAQUEHWh1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5MB0GCisGAQQBg78wAQYED3JlZnMvaGVhZHMvbWFpbjA7BgorBgEEAYO/MAEIBC0MK2h0dHBzOi8vdG9rZW4uYWN0aW9ucy5naXRodWJ1c2VyY29udGVudC5jb20wbQYKKwYBBAGDvzABCQRfDF1odHRwczovL2dpdGh1Yi5jb20vaHVnZ2luZ2ZhY2Uva2VybmVscy1jb21tdW5pdHkvLmdpdGh1Yi93b3JrZmxvd3MvYnVpbGQueWFtbEByZWZzL2hlYWRzL21haW4wOAYKKwYBBAGDvzABCgQqDChlYzVjYzkwZTE2NTc1NjZjNjZjNjJiNTAyMTQzNWVkYmNmYTM0OGMzMBsGCisGAQQBg78wAQsEDQwLc2VsZi1ob3N0ZWQwQAYKKwYBBAGDvzABDAQyDDBodHRwczovL2dpdGh1Yi5jb20vaHVnZ2luZ2ZhY2Uva2VybmVscy1jb21tdW5pdHkwOAYKKwYBBAGDvzABDQQqDChlYzVjYzkwZTE2NTc1NjZjNjZjNjJiNTAyMTQzNWVkYmNmYTM0OGMzMB8GCisGAQQBg78wAQ4EEQwPcmVmcy9oZWFkcy9tYWluMBoGCisGAQQBg78wAQ8EDAwKMTA3MTQ3NTUyOTAuBgorBgEEAYO/MAEQBCAMHmh0dHBzOi8vZ2l0aHViLmNvbS9odWdnaW5nZmFjZTAYBgorBgEEAYO/MAERBAoMCDI1NzIwNzQzMG0GCisGAQQBg78wARIEXwxdaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5Ly5naXRodWIvd29ya2Zsb3dzL2J1aWxkLnlhbWxAcmVmcy9oZWFkcy9tYWluMDgGCisGAQQBg78wARMEKgwoZWM1Y2M5MGUxNjU3NTY2YzY2YzYyYjUwMjE0MzVlZGJjZmEzNDhjMzAhBgorBgEEAYO/MAEUBBMMEXdvcmtmbG93X2Rpc3BhdGNoMGQGCisGAQQBg78wARUEVgxUaHR0cHM6Ly9naXRodWIuY29tL2h1Z2dpbmdmYWNlL2tlcm5lbHMtY29tbXVuaXR5L2FjdGlvbnMvcnVucy8zOTQwMzQ1ODE5OS9hdHRlbXB0cy8xMBYGCisGAQQBg78wARYECAwGcHVibGljMEYGCisGAQQBg78wARgEOAw2cmVwbzpodWdnaW5nZmFjZS9rZXJuZWxzLWNvbW11bml0eTpyZWY6cmVmcy9oZWFkcy9tYWluMIGJBgorBgEEAdZ5AgQCBHsEeQB3AHUA3T0wasbHETJjGR4cmWc3AqJKXrjePK3/h4pygC8p7o4AAAGhWLtUgAAABAMARjBEAiAe/acaJEWfp83gcFkDKvoFLXrzTxjWugLIDNpSFeLusgIgI6Pe4FEqWllfViiFTAQVVYJoN4GbF+Zqar2IqNRKXywwCgYIKoZIzj0EAwMDaAAwZQIwTGwKwrp9v3bnF+jcmcxJDQlNH+G/X094ztAojPb9XMoglDopWFLG5CCHJwTVDBcxAjEAwAHPN6qnPDCH1MsFN1mUa25cXwEcCnBKYfOKJuuV4GuJ48l7lBLHt/XgHMSKAp0o"}, "tlogEntries":[{"logIndex":"3335086358", "logId":{"keyId":"wNI9atQGlz+VWfO6LRygH4QUfY/8W4RFwiT5i5WRgB0="}, "kindVersion":{"kind":"hashedrekord", "version":"0.0.1"}, "integratedTime":"1792490034", "inclusionPromise":{"signedEntryTimestamp":"MEQCIDh5l4rIduFEIESVLdxB7yNjOmzLB6wHWBP+dLlyoALjAiBlffoVN9/Y6hMJ3jh8k9Tx78uilgTkTl+MFakp/tnDEw=="}, "inclusionProof":{"logIndex":"3213182096", "rootHash":"ETOUthJxn7Z36oDbegdN4ukdMMDtHTet6l/TBkV1Hww=", "treeSize":"3213182167", "hashes":["N4zOdeYV9bT+mL8n5/1CGmSuA2VV+UhN9MDbMneloJw=", "mj7NwwalWVWDX0Kh+to0lJbH1rGZDbNf4HcF9eItBVM=", "fwnSmRcMGxhsozTLY4LcxDpo08kcNy/vEPBGeeBSFbk=", "Cj68CvJffFHJ7/61leQDCepCbXGoBCS1YUvSGi3bm/Q=", "lfASF7qNkK6GWuggPpPvvjXCM94C8NJa0Wot/arlKXc=", "Yt7IuEE3A6hytfQiIcf8HBbHvrwdyXrEKDoszquugMM=", "Wtj/zv8pbDRBXP70LrlX25kih3b6H7UquUi/a+h1UG0=", "MgGbTQdHMx9a+WZnHvQoG7hXqexepfCSgGbCVHQdTME=", "5VvnPwgJg7mEz2CpXW/bEuCEagORQRlWh9Ndtihxwi0=", "PHxQAH8g/7NVsymUZ0Te3puxjBd3DEJNVas6rZCulgs=", "rYA6lUjt9opyITSlS0ZGyFtQo1JeGpc94KBmcxBHawE=", "Zz0ghh+5MBl4Vee4jXWbIrdb3i68SxmtMzhdMeM0hOg=", "6Rb7uOgk2v5YKcFSbAPOpLlWAIN1ciDPdlp4J/CUu5k=", "HovTPBWzoErvUg1u5sIi3x1ZonZ7yJekVsBrmpomCIA=", "G+nk7mgE5h6dUmv2UY7NDJrY3WvrZY31raaO1AETt+o=", "qlTWNDzERnTBQVcHHXXVPjiUP0SsikGxfcxVlgqVFDo=", "gg9GNApNke3+zNhl4tsWtmIycCFrOMCB3lys6XuSQNQ=", "NlOclldmKPA1tMKIskqHAwuQDYHp31YwpK+OOMrMccc=", "qxzHanAzz57SDdmJe0B7bJK72NTIBbwEMGBBKvDOROw=", "xH/DCseLHr9eKoYT8qsORZK7zVdEGYWHuVtsVrD95wY="], "checkpoint":{"envelope":"rekor.sigstore.dev - 1193050959916656506\n3213182167\nETOUthJxn7Z36oDbegdN4ukdMMDtHTet6l/TBkV1Hww=\n\n— rekor.sigstore.dev wNI9ajBGAiEA136ugN4DB8HrrSrQe1IkuD6gcEvjcYgnISn5rjXpld4CIQC11ZBPQ1jo/Uz+0/lcjMNx+Lkg4q0KC5vUVkoOyQojQA==\n"}}, "canonicalizedBody":"eyJhcGlWZXJzaW9uIjoiMC4wLjEiLCJraW5kIjoiaGFzaGVkcmVrb3JkIiwic3BlYyI6eyJkYXRhIjp7Imhhc2giOnsiYWxnb3JpdGhtIjoic2hhMjU2IiwidmFsdWUiOiJhNDA1MWRmMTQ5MDNiM2Y4OTZmMzg3NWQyOTlmNTU4OTU5YjZmZWVjNzc5YmQwOWJjM2M5YWUxYjUxNmZhNjhjIn19LCJzaWduYXR1cmUiOnsiY29udGVudCI6Ik1FVUNJUUQ4RVVFMEhWUHRzdGlVdDRFR0pnVG5ocitmUU9uRTg4SnpMYmVQN1M3MmV3SWdIZ0xaUy9lZFRDM1dMTmsrazdHQUtyUTJxeVdHZk1od3NVaXVQd2tEQVQ0PSIsInB1YmxpY0tleSI6eyJjb250ZW50IjoiTFMwdExTMUNSVWRKVGlCRFJWSlVTVVpKUTBGVVJTMHRMUzB0Q2sxSlNVaExla05EUW5KSFowRjNTVUpCWjBsVlVDOXRiVnBOTVZKQ1RscDVLekpDWVhoSlVHSnZTMkZCVG1aVmQwTm5XVWxMYjFwSmVtb3dSVUYzVFhjS1RucEZWazFDVFVkQk1WVkZRMmhOVFdNeWJHNWpNMUoyWTIxVmRWcEhWakpOVWpSM1NFRlpSRlpSVVVSRmVGWjZZVmRrZW1SSE9YbGFVekZ3WW01U2JBcGpiVEZzV2tkc2FHUkhWWGRJYUdOT1RXcFplRTFFU1hkTlJHc3hUWHBWTUZkb1kwNU5hbGw0VFVSSmQwMVVRWGROZWxVd1YycEJRVTFHYTNkRmQxbElDa3R2V2tsNmFqQkRRVkZaU1V0dldrbDZhakJFUVZGalJGRm5RVVZqV1c5bk4yRnlTRmt3V1RsTVFqRmtZbm8wUlUxcGFYTnFRV1Z2WjFseVZtRk5MMkVLZWtGVU5qSXdWSGhRYlZGM1UydHdhak5uVFRWWGRXNTBhbGxNVmt0SU4zWTJXUzlhU2t0aWRFVklNMGw1ZWpCbFVYRlBRMEprUVhkbloxaE5UVUUwUndwQk1WVmtSSGRGUWk5M1VVVkJkMGxJWjBSQlZFSm5UbFpJVTFWRlJFUkJTMEpuWjNKQ1owVkdRbEZqUkVGNlFXWkNaMDVXU0ZOTlJVZEVRVmRuUWxSbUNqQXJibEJXYVZGU2JIWnRiekpQYTI5V1lVeEhUR2hvYTFCNlFuSkNaMDVXU0ZKRlFrRm1PRVZaVkVKbWFHd3hiMlJJVW5kamVtOTJUREprY0dSSGFERUtXV2sxYW1JeU1IWmhTRlp1V2pKc2RWb3lXbWhaTWxWMllUSldlV0p0Vm5OamVURnFZakl4ZEdSWE5YQmtTR3QyVEcxa2NHUkhhREZaYVRrellqTktjZ3BhYlhoMlpETk5kbGx1Vm5CaVIxRjFaVmRHZEdKRlFubGFWMXA2VERKb2JGbFhVbnBNTWpGb1lWYzBkMDlSV1V0TGQxbENRa0ZIUkhaNlFVSkJVVkZ5Q21GSVVqQmpTRTAyVEhrNU1HSXlkR3hpYVRWb1dUTlNjR0l5TlhwTWJXUndaRWRvTVZsdVZucGFXRXBxWWpJMU1GcFhOVEJNYlU1MllsUkJaa0puYjNJS1FtZEZSVUZaVHk5TlFVVkRRa0pHTTJJelNuSmFiWGgyWkRFNWEyRllUbmRaV0ZKcVlVUkJNa0puYjNKQ1owVkZRVmxQTDAxQlJVUkNRMmhzV1hwV2FncFplbXQzV2xSRk1rNVVZekZPYWxwcVRtcGFhazVxU21sT1ZFRjVUVlJSZWs1WFZtdFpiVTV0V1ZSTk1FOUhUWHBOUWsxSFEybHpSMEZSVVVKbk56aDNDa0ZSVVVWQ1ZVb3hZVmQ0YTAxRGMwZERhWE5IUVZGUlFtYzNPSGRCVVZWRlNGZG9NVm95WkhCaWJXUnRXVmRPYkV3eWRHeGpiVFZzWWtoTmRGa3lPWFFLWWxoV2RXRllValZOUWpCSFEybHpSMEZSVVVKbk56aDNRVkZaUlVRelNteGFiazEyWVVkV2FGcElUWFppVjBad1ltcEJOMEpuYjNKQ1owVkZRVmxQTHdwTlFVVkpRa013VFVzeWFEQmtTRUo2VDJrNGRtUkhPWEphVnpSMVdWZE9NR0ZYT1hWamVUVnVZVmhTYjJSWFNqRmpNbFo1V1RJNWRXUkhWblZrUXpWcUNtSXlNSGRpVVZsTFMzZFpRa0pCUjBSMmVrRkNRMUZTWmtSR01XOWtTRkozWTNwdmRrd3laSEJrUjJneFdXazFhbUl5TUhaaFNGWnVXakpzZFZveVdtZ0tXVEpWZG1FeVZubGliVlp6WTNreGFtSXlNWFJrVnpWd1pFaHJka3h0WkhCa1IyZ3hXV2s1TTJJelNuSmFiWGgyWkROTmRsbHVWbkJpUjFGMVpWZEdkQXBpUlVKNVdsZGFla3d5YUd4WlYxSjZUREl4YUdGWE5IZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1EyZFJjVVJEYUd4WmVsWnFXWHByZDFwVVJUSk9WR014Q2s1cVdtcE9hbHBxVG1wS2FVNVVRWGxOVkZGNlRsZFdhMWx0VG0xWlZFMHdUMGROZWsxQ2MwZERhWE5IUVZGUlFtYzNPSGRCVVhORlJGRjNUR015Vm5NS1dta3hiMkl6VGpCYVYxRjNVVUZaUzB0M1dVSkNRVWRFZG5wQlFrUkJVWGxFUkVKdlpFaFNkMk42YjNaTU1tUndaRWRvTVZscE5XcGlNakIyWVVoV2JncGFNbXgxV2pKYWFGa3lWWFpoTWxaNVltMVdjMk41TVdwaU1qRjBaRmMxY0dSSWEzZFBRVmxMUzNkWlFrSkJSMFIyZWtGQ1JGRlJjVVJEYUd4WmVsWnFDbGw2YTNkYVZFVXlUbFJqTVU1cVdtcE9hbHBxVG1wS2FVNVVRWGxOVkZGNlRsZFdhMWx0VG0xWlZFMHdUMGROZWsxQ09FZERhWE5IUVZGUlFtYzNPSGNLUVZFMFJVVlJkMUJqYlZadFkzazViMXBYUm10amVUbDBXVmRzZFUxQ2IwZERhWE5IUVZGUlFtYzNPSGRCVVRoRlJFRjNTMDFVUVROTlZGRXpUbFJWZVFwUFZFRjFRbWR2Y2tKblJVVkJXVTh2VFVGRlVVSkRRVTFJYldnd1pFaENlazlwT0haYU1td3dZVWhXYVV4dFRuWmlVemx2WkZka2JtRlhOVzVhYlVacUNscFVRVmxDWjI5eVFtZEZSVUZaVHk5TlFVVlNRa0Z2VFVORVNURk9la2wzVG5wUmVrMUhNRWREYVhOSFFWRlJRbWMzT0hkQlVrbEZXSGQ0WkdGSVVqQUtZMGhOTmt4NU9XNWhXRkp2WkZkSmRWa3lPWFJNTW1neFdqSmtjR0p0WkcxWlYwNXNUREowYkdOdE5XeGlTRTEwV1RJNWRHSllWblZoV0ZJMVRIazFiZ3BoV0ZKdlpGZEpkbVF5T1hsaE1scHpZak5rZWt3eVNqRmhWM2hyVEc1c2FHSlhlRUZqYlZadFkzazViMXBYUm10amVUbDBXVmRzZFUxRVowZERhWE5IQ2tGUlVVSm5OemgzUVZKTlJVdG5kMjlhVjAweFdUSk5OVTFIVlhoT2FsVXpUbFJaTWxsNldUSlplbGw1V1dwVmQwMXFSVEJOZWxac1drZEthbHB0UlhvS1RrUm9hazE2UVdoQ1oyOXlRbWRGUlVGWlR5OU5RVVZWUWtKTlRVVllaSFpqYlhSdFlrYzVNMWd5VW5Cak0wSm9aRWRPYjAxSFVVZERhWE5IUVZGUlFncG5OemgzUVZKVlJWWm5lRlZoU0ZJd1kwaE5Oa3g1T1c1aFdGSnZaRmRKZFZreU9YUk1NbWd4V2pKa2NHSnRaRzFaVjA1c1RESjBiR050Tld4aVNFMTBDbGt5T1hSaVdGWjFZVmhTTlV3eVJtcGtSMngyWW01TmRtTnVWblZqZVRoNlQxUlJkMDE2VVRGUFJFVTFUMU01YUdSSVVteGlXRUl3WTNrNGVFMUNXVWNLUTJselIwRlJVVUpuTnpoM1FWSlpSVU5CZDBkalNGWnBZa2RzYWsxRldVZERhWE5IUVZGUlFtYzNPSGRCVW1kRlQwRjNNbU50Vm5kaWVuQnZaRmRrYmdwaFZ6VnVXbTFHYWxwVE9YSmFXRXAxV2xkNGVreFhUblppVnpFeFltMXNNR1ZVY0hsYVYxazJZMjFXYldONU9XOWFWMFpyWTNrNWRGbFhiSFZOU1VkS0NrSm5iM0pDWjBWRlFXUmFOVUZuVVVOQ1NITkZaVkZDTTBGSVZVRXpWREIzWVhOaVNFVlVTbXBIVWpSamJWZGpNMEZ4U2t0WWNtcGxVRXN6TDJnMGNIa0taME00Y0Rkdk5FRkJRVWRvVjB4MFZXZEJRVUZDUVUxQlVtcENSVUZwUVdVdllXTmhTa1ZYWm5BNE0yZGpSbXRFUzNadlJreFljbnBVZUdwWGRXZE1TUXBFVG5CVFJtVk1kWE5uU1dkSk5sQmxORVpGY1Zkc2JHWldhV2xHVkVGUlZsWlpTbTlPTkVkaVJpdGFjV0Z5TWtseFRsSkxXSGwzZDBObldVbExiMXBKQ25wcU1FVkJkMDFFWVVGQmQxcFJTWGRVUjNkTGQzSndPWFl6WW01R0sycGpiV040U2tSUmJFNUlLMGN2V0RBNU5IcDBRVzlxVUdJNVdFMXZaMnhFYjNBS1YwWk1SelZEUTBoS2QxUldSRUpqZUVGcVJVRjNRVWhRVGpaeGJsQkVRMGd4VFhOR1RqRnRWV0V5TldOWWQwVmpRMjVDUzFsbVQwdEtkWFZXTkVkMVNnbzBPR3czYkVKTVNIUXZXR2RJVFZOTFFYQXdid290TFMwdExVVk9SQ0JEUlZKVVNVWkpRMEZVUlMwdExTMHRDZz09In19fX0="}], "timestampVerificationData":{"rfc3161Timestamps":[{"signedTimestamp":"MIICyTADAgEAMIICwAYJKoZIhvcNAQcCoIICsTCCAq0CAQMxDTALBglghkgBZQMEAgEwgbcGCyqGSIb3DQEJEAEEoIGnBIGkMIGhAgEBBgkrBgEEAYO/MAIwMTANBglghkgBZQMEAgEFAAQgfFMO/p/NSekrYglMmlihkzviE2/TMjI7I9gvE+jPVZwCFHtSTryVjqRDdM+RmwtYaQ+zgsgnGA8yMDI2MTAyMDA5NTM1NFowAwIBAaAypDAwLjEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MRUwEwYDVQQDEwxzaWdzdG9yZS10c2GgADGCAdswggHXAgEBMFEwOTEVMBMGA1UEChMMc2lnc3RvcmUuZGV2MSAwHgYDVQQDExdzaWdzdG9yZS10c2Etc2VsZnNpZ25lZAIUOhNULwyQYe68wUMvy4qOiyojiwwwCwYJYIZIAWUDBAIBoIH8MBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAcBgkqhkiG9w0BCQUxDxcNMjYxMDIwMDk1MzU0WjAvBgkqhkiG9w0BCQQxIgQg3f6Q/nF2OKTQsvL152ZutBpL80L7gdl0XBjdZCcouJ8wgY4GCyqGSIb3DQEJEAIvMX8wfTB7MHkEIIX5J7wHq2LKw7RDVsEO/IGyxog/2nq55thw2dE6zQW3MFUwPaQ7MDkxFTATBgNVBAoTDHNpZ3N0b3JlLmRldjEgMB4GA1UEAxMXc2lnc3RvcmUtdHNhLXNlbGZzaWduZWQCFDoTVC8MkGHuvMFDL8uKjosqI4sMMAoGCCqGSM49BAMCBGcwZQIxAKnEXDu2NvqZyacw0ZGY4AbtlHSBesVRge3qaPnSC2bWDBInFL34StY/aRcVjYTU0AIwZZOS9rSnZ5Y96uJwvpNIowj14zfaPu6vOZLCfkwkTorS0cyiyGuRF8lzjClRukKm"}]}}, "messageSignature":{"messageDigest":{"algorithm":"SHA2_256", "digest":"pAUd8UkDs/iW84ddKZ9ViVm2/ux3m9Cbw8muG1Fvpow="}, "signature":"MEUCIQD8EUE0HVPtstiUt4EGJgTnhr+fQOnE88JzLbeP7S72ewIgHgLZS/edTC3WLNk+k7GAKrQ2qyWGfMhwsUiuPwkDAT4="}}
build/torch-cuda/pruners.py CHANGED
@@ -253,7 +253,7 @@ def gated_pointer_weight_warp_spec_pruner():
253
  sub-128 M tile — plain pointer/pointer at BM=128 and w4/w8 compiles and is often the winner.
254
 
255
  Region measured by tuning the GPT-OSS W4A16 problem (N=K=2880) with a fresh Triton cache and
256
- ``FINEGRAINED_AUTOTUNE_LOG``: of 129 WS configs, the 12 that failed are exactly the ones this
257
  predicate names, and it drops none of the 117 that compiled. Every failure sat in the GATE
258
  shape — the ungated shape's 63 WS configs all compiled. Unfenced these are wasted compiles per
259
  new shape (the tuner memoizes them only after paying once). The ``BM < 128`` clause rests on a
 
253
  sub-128 M tile — plain pointer/pointer at BM=128 and w4/w8 compiles and is often the winner.
254
 
255
  Region measured by tuning the GPT-OSS W4A16 problem (N=K=2880) with a fresh Triton cache and
256
+ an autotune ``log_path``: of 129 WS configs, the 12 that failed are exactly the ones this
257
  predicate names, and it drops none of the 117 that compiled. Every failure sat in the GATE
258
  shape — the ungated shape's 63 WS configs all compiled. Unfenced these are wasted compiles per
259
  new shape (the tuner memoizes them only after paying once). The ``BM < 128`` clause rests on a
build/torch-cuda/quant.py CHANGED
@@ -19,7 +19,7 @@ from triton.language.extra.cuda import gdc_launch_dependents, gdc_wait
19
 
20
  from .bayesian_autotuner import bayesian_autotune
21
  from .formats import global_scale_stride, is_per_expert_global
22
- from .compat import FP8_DTYPE, MX_SCALE_GROUP_K, NVFP4_SCALE_GROUP_K, compile_time_only_triton_wrap, device_context, is_sm10x, pdl_launch_kwargs
23
  from .swizzle import swizzle_store_block
24
  from .scheduling import build_tile_layout, resolve_tile_inline
25
 
@@ -61,16 +61,14 @@ def fp8_act_quant_inline(
61
  return a_fp8, a_scale
62
 
63
 
64
- # cvt.e2m1x2.f32 (hardware FP4 pack) exists only on sm_100 (Blackwell). Resolved once at
65
- # import as a compile-time constexpr for the jit helper below; the ALU fallback compiles
66
- # everywhere else. ``is_sm10x`` is driverless-safe, so no import-time guard is needed.
67
- _E2M1_HW_CVT = tl.constexpr(is_sm10x())
68
 
69
 
70
  @triton.jit
71
  def _quant_e2m1_packed(v, BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_K: tl.constexpr):
72
  """Pack signed, pre-scaled values on the E2M1 grid to ``(M, K//2)`` uint8 (first value
73
- of each pair in the low nibble). On sm_100 the Blackwell hardware convert
74
  ``cvt.rn.satfinite.e2m1x2.f32`` (two f32 → one packed byte, first operand → HIGH nibble)
75
  does it in one instruction; elsewhere a ``>=``-threshold bucketize builds the code. The
76
  two agree except at exact E2M1 midpoints (0.25, 0.75, …), where the hardware rounds to
@@ -187,7 +185,7 @@ def _e2m1_code_to_bf16_bits(code):
187
  @triton.jit
188
  def _e2m1_pair_to_f16(packed):
189
  """One packed E2M1 byte -> the two fp16 values it holds, as a uint32 (low half = low nibble),
190
- through the sm_100 CVT unit: ONE instruction per pair against the bit-twiddle form's ~10
191
  integer ops. Every E2M1 magnitude is exact in fp16, so this is a value-identical decode, and
192
  the direction of the unpack is irrelevant to the cost — the arithmetic is the whole bill
193
  (measured on the gpt-oss W4A16 decode weight stream: 39.6us bit-twiddle, either axis, vs
@@ -210,7 +208,7 @@ def e2m1_to_bf16(b_packed):
210
  interleave along K with a SINGLE 3D axis-swap (``join`` -> ``[R, C, 2]`` -> swap last two ->
211
  reshape) instead of the three 2D transposes ``e2m1_to_e4m3`` needs — the weight-only unpack hot path
212
  (fp4 weight upcast in-loop, the caller widening to the activation dtype). K order low-nibble-first, matching
213
- ``e2m1_to_e4m3``: ``[byte0_lo, byte0_hi, byte1_lo, ...]``. On sm_100 the codes decode through
214
  the hardware CVT (``_e2m1_pair_to_f16``, fp16 being exact for the grid); elsewhere the integer
215
  bit-build. Only the taken arm compiles."""
216
  if _E2M1_HW_CVT:
@@ -288,7 +286,7 @@ def e2m1_cols_to_f32(packed):
288
  def e2m1_cols_to_e4m3(packed):
289
  """Column-unpack packed E2M1 (two nibbles per byte along the last dim, low nibble
290
  first) to E4M3: ``(..., C) uint8 -> (..., 2C)`` — the column-axis counterpart of the
291
- row-doubling ``e2m1_to_e4m3``; lossless (every E2M1 value is exact in E4M3). sm_100
292
  decodes through the CVT unit (``_e2m1_pair_to_e4m3``), elsewhere the integer bit-build;
293
  either way the bytes are bitcast once. Only the taken arm compiles."""
294
  if _E2M1_HW_CVT:
 
19
 
20
  from .bayesian_autotuner import bayesian_autotune
21
  from .formats import global_scale_stride, is_per_expert_global
22
+ from .compat import FP8_DTYPE, MX_SCALE_GROUP_K, NVFP4_SCALE_GROUP_K, compile_time_only_triton_wrap, device_context, is_blackwell, pdl_launch_kwargs
23
  from .swizzle import swizzle_store_block
24
  from .scheduling import build_tile_layout, resolve_tile_inline
25
 
 
61
  return a_fp8, a_scale
62
 
63
 
64
+ # Hardware FP4 conversions exist on every Blackwell family, independently of TMEM MMA.
65
+ _E2M1_HW_CVT = tl.constexpr(is_blackwell())
 
 
66
 
67
 
68
  @triton.jit
69
  def _quant_e2m1_packed(v, BLOCK_SIZE_M: tl.constexpr, BLOCK_SIZE_K: tl.constexpr):
70
  """Pack signed, pre-scaled values on the E2M1 grid to ``(M, K//2)`` uint8 (first value
71
+ of each pair in the low nibble). On Blackwell the hardware convert
72
  ``cvt.rn.satfinite.e2m1x2.f32`` (two f32 → one packed byte, first operand → HIGH nibble)
73
  does it in one instruction; elsewhere a ``>=``-threshold bucketize builds the code. The
74
  two agree except at exact E2M1 midpoints (0.25, 0.75, …), where the hardware rounds to
 
185
  @triton.jit
186
  def _e2m1_pair_to_f16(packed):
187
  """One packed E2M1 byte -> the two fp16 values it holds, as a uint32 (low half = low nibble),
188
+ through the Blackwell CVT unit: ONE instruction per pair against the bit-twiddle form's ~10
189
  integer ops. Every E2M1 magnitude is exact in fp16, so this is a value-identical decode, and
190
  the direction of the unpack is irrelevant to the cost — the arithmetic is the whole bill
191
  (measured on the gpt-oss W4A16 decode weight stream: 39.6us bit-twiddle, either axis, vs
 
208
  interleave along K with a SINGLE 3D axis-swap (``join`` -> ``[R, C, 2]`` -> swap last two ->
209
  reshape) instead of the three 2D transposes ``e2m1_to_e4m3`` needs — the weight-only unpack hot path
210
  (fp4 weight upcast in-loop, the caller widening to the activation dtype). K order low-nibble-first, matching
211
+ ``e2m1_to_e4m3``: ``[byte0_lo, byte0_hi, byte1_lo, ...]``. On Blackwell the codes decode through
212
  the hardware CVT (``_e2m1_pair_to_f16``, fp16 being exact for the grid); elsewhere the integer
213
  bit-build. Only the taken arm compiles."""
214
  if _E2M1_HW_CVT:
 
286
  def e2m1_cols_to_e4m3(packed):
287
  """Column-unpack packed E2M1 (two nibbles per byte along the last dim, low nibble
288
  first) to E4M3: ``(..., C) uint8 -> (..., 2C)`` — the column-axis counterpart of the
289
+ row-doubling ``e2m1_to_e4m3``; lossless (every E2M1 value is exact in E4M3). Blackwell
290
  decodes through the CVT unit (``_e2m1_pair_to_e4m3``), elsewhere the integer bit-build;
291
  either way the bytes are bitcast once. Only the taken arm compiles."""
292
  if _E2M1_HW_CVT:
build/torch-cuda/symbols.json CHANGED
@@ -1,11 +1,11 @@
1
  {
2
  "schema_version": 1,
3
- "module": "_finegrained_kernels_cuda_43f1581",
4
  "functions": [
5
  {
6
  "name": "matmul_2d",
7
  "qualname": "matmul_2d",
8
- "module": "_finegrained_kernels_cuda_43f1581.matmul",
9
  "kind": "function",
10
  "doc": "Dense (2D) quantized matmul dispatcher (W8A8 FP8, W4A8/W4A4 FP4) — the single-GEMM sibling of\n``matmul_grouped``/``matmul_batched``, taking the same ``As``/``Bs`` scale spec,\n``activation_format`` / gate|up fusion kwargs, and inferred quant block (no ``block_size`` argument — it\nfalls out of the weight-scale shape, so the data can't disagree with a parameter).\n\n``As`` is the activation block-scale spec, same as the routed ops: ``None`` → the op quantizes raw\n``A`` dynamically; a per-tensor scalar → static (calibrated) FP8 activation scale; block scales →\npre-quantized ``A``. ``Bs`` is the weight block scale. ``a_global_scale``/``b_global_scale`` are the\ntwo-level NVFP4 per-tensor second-level scales (calibrated ``input_scale`` ``g_a`` and\n``weight_scale_2`` ``g_b``) — provided, never computed; the op folds ``g_a·g_b`` onto the\naccumulator (``a_global_scale`` rides raw ``A`` or a pre-quantized ``As`` alike).\n``output_global_scale`` (NVFP4 requant only) is the NEXT proj's provided ``input_scale``:\nthe fused requant normalizes the GLU intermediate by it, returning ``(C, Cs)`` the down-proj\nconsumes as ``As=Cs, a_global_scale=output_global_scale``.\n\n``activation_format`` is the activations' quantization: ``None`` (default) = the weights' own\nformat, ``\"bf16\"`` = weight-only (no activation quant), or an explicit ``\"fp8\"`` / ``\"mxfp8\"`` /\n``\"mxfp4\"`` / ``\"nvfp4\"`` (``\"mxfp8\"`` on MXFP4 weights = the W4A8 chain; ``check_activation_format``\nhas the per-family matrix). ``gate=True`` reads ``B`` as the ``(2N, K)`` gate|up stack and fuses\nthe ``act_fn`` GLU (``swiglu_alpha``/``swiglu_limit`` its clamped variant), returning the\n``[..., N]`` intermediate; ``quantize_output=True`` (MX weights only) stores that intermediate in\n``activation_format`` and returns ``(C, Cs)`` for the next op.\n\nRoutes by weight dtype + inferred block: ``Bs`` None → full-precision; MX/NVFP4 (int8/E4M3 weights\nwith UE8M0 group-32 or E4M3 group-16 ``Bs``) → ``mx_dynamic_matmul``; tensor-wide FP8 (scalar\n``Bs``) → ``w8a8_tensor_dynamic_fp8_matmul``; block FP8 → the static variant when ``As`` is a\nper-tensor scalar, else dynamic.",
11
  "signature": {
@@ -113,7 +113,7 @@
113
  {
114
  "name": "matmul_batched",
115
  "qualname": "matmul_batched",
116
- "module": "_finegrained_kernels_cuda_43f1581.batched",
117
  "kind": "function",
118
  "doc": "Batched matmul dispatcher (W8A8 FP8, W4A8/W4A4 FP4, or full-precision). Routes one\nprogram per routed row (``expert_ids`` gives its expert).\n\n``As`` marks ``A`` as already quantized (framework-precomputed scales, or a requantized\nintermediate handed to the down projection); a per-tensor scalar ``As`` is instead the static\n(calibrated) activation scale for block-scale FP8 weights — the op quantizes raw ``A`` against\nit; ``None`` = raw ``A``, quantized dynamically by the op in ``activation_format`` (``None`` =\nthe weights' own format — fp8/E4M3 offline for bd/tensor, inline for MX; ``\"bf16\"`` = weight-only;\n``\"mxfp4\"`` = packed E2M1 activations). ``Bs`` ``None`` = unquantized BF16/FP16 weights.\n``quantize_output`` requantizes the output into ``activation_format`` — the return is then\n``(C, Cs)``. ``gate``/``act_fn``/``swiglu_alpha``/``swiglu_limit`` fuse the gate|up GLU into the\nepilogue. ``As``/``Bs`` are each a bare block-scale tensor; the two-level NVFP4\nsecond-level scales ride the separate ``a_global_scale``/``b_global_scale`` (fp32 per-tensor,\nweights per-expert ``(E,)``; from ``nvfp4_quantize_two_level``), and the op folds ``g_a · g_b``\nonto the accumulator. The activation global ``g_a`` is CALIBRATED (the checkpoint's\n``input_scale``): ``a_global_scale=g_a`` with a raw ``A`` has the op quantize ``A / g_a`` per\nblock, and rides a pre-quantized ``As`` the same way. Under an NVFP4 requant the\nfused requant normalizes the GLU intermediate by the PROVIDED ``output_global_scale`` (the next\nproj's calibrated ``input_scale``) before the block quant and returns ``[C, Cs]``; the down\nconsumes it as ``As=Cs, a_global_scale=output_global_scale``. ``gather_idx``/``scatter_idx`` (each None or a ``(S,)`` map)\naddress the source row of ``A`` / destination row of ``C`` per program — None means row\n``s``; the gather lets the gate_up read unexpanded activations (source row\n``s // num_top_k``) with no copy.\n\nRoutes by what the weight tensors themselves say (there is no ``block_size``\nparameter — the quantization block is derived from the scale shape,\n``weight_block_size``):\n- ``Bs`` None → ``full_precision_matmul_batched`` (plain dot, no scales anywhere).\n- MX weights — ``int8`` (packed E2M1) or ``float8_e4m3fn`` (E4M3) with UE8M0\n group-32 ``Bs`` → ``mx_dynamic_matmul_batched``.\n- one scale per expert (``Bs`` ``(E,)``/``(E, 1, 1)``) →\n ``w8a8_tensor_dynamic_fp8_matmul_batched``.\n- block scales (``Bs`` ``(E, N/bn, K/bk)``) → ``w8a8_block_dynamic_fp8_matmul_batched``.",
119
  "signature": {
@@ -239,7 +239,7 @@
239
  {
240
  "name": "matmul_grouped",
241
  "qualname": "matmul_grouped",
242
- "module": "_finegrained_kernels_cuda_43f1581.grouped",
243
  "kind": "function",
244
  "doc": "Grouped matmul dispatcher (W8A8 FP8, W4A8/W4A4 FP4, or full-precision).\n``expert_start`` is the ``(E+1,)`` tiling schedule from one ``compute_grouped_scheduling``\npass, shared by every grouped GEMM of the layer (the expert sort is virtual — nothing is\nphysically permuted).\n\n``As`` marks ``A`` as already quantized (framework-precomputed scales, or a requantized\nintermediate handed to the down projection); a per-tensor scalar ``As`` is instead the static\n(calibrated) activation scale for block-scale FP8 weights — the op quantizes raw ``A`` against\nit; ``None`` = raw ``A``, quantized dynamically by the op in ``activation_format`` (``None`` =\nthe weights' own format — fp8/E4M3, or the fp4 chains; ``\"bf16\"`` = weight-only; ``\"mxfp4\"`` =\npacked E2M1 activations). ``Bs`` ``None`` = unquantized BF16/FP16 weights.\n``quantize_output`` requantizes the output into ``activation_format`` — the return is then\n``(C, Cs)``. ``gate``/``act_fn``/``swiglu_alpha``/``swiglu_limit`` fuse the gate|up GLU into the\nepilogue.\n``As``/``Bs`` are each a bare block-scale tensor; the two-level NVFP4 second-level scales ride\nthe separate ``a_global_scale``/``b_global_scale`` (fp32 per-tensor, weights per-expert ``(E,)``;\nfrom ``nvfp4_quantize_two_level``), and the op folds ``g_a · g_b`` onto the accumulator. The\nactivation global ``g_a`` is CALIBRATED (the checkpoint's ``input_scale``): ``a_global_scale=g_a``\nwith a raw ``A`` has the op quantize ``A / g_a`` per block, and rides a pre-quantized ``As`` the\nsame way. Under an NVFP4 requant the fused requant normalizes the GLU intermediate by the\nPROVIDED ``output_global_scale`` (the next proj's calibrated ``input_scale``) before the block\nquant and returns ``[C, Cs]``; the down consumes it as ``As=Cs, a_global_scale=output_global_scale``.\nRow order is carried by the standalone maps: ``gather_idx`` gathers ``A`` (``None`` ->\nalready expert-ordered), ``scatter_idx`` scatters the output. The fused MoE chain is one\nscheduling pass: gate_up with ``scatter_idx=None`` + ``gate=True`` + ``quantize_output=True``,\nthen down with ``gather_idx=None`` and the\nintermediate's scales as ``As``. EP-sentinel routes sit past ``expert_start[-1]``, which\nno tile reaches.\n\nRoutes by what the weight tensors themselves say (there is no ``block_size``\nparameter — the quantization block is derived from the scale shape,\n``weight_block_size``):\n- ``Bs`` None → ``full_precision_matmul_grouped`` (plain dot, no scales anywhere).\n- MX weights — ``int8`` (packed E2M1) or ``float8_e4m3fn`` (E4M3) with UE8M0\n group-32 ``Bs`` → ``mx_dynamic_matmul_grouped``.\n- one scale per expert (``Bs`` ``(E,)``/``(E, 1, 1)``) →\n ``w8a8_tensor_dynamic_fp8_matmul_grouped``.\n- block scales (``Bs`` ``(E, N/bn, K/bk)``) → ``w8a8_block_dynamic_fp8_matmul_grouped``.",
245
  "signature": {
@@ -365,7 +365,7 @@
365
  {
366
  "name": "moe_fused_batched",
367
  "qualname": "moe_fused_batched",
368
- "module": "_finegrained_kernels_cuda_43f1581.moe",
369
  "kind": "function",
370
  "doc": "Fused batched MoE (decode): gate_up + SiLU + requant epilogue → per-row quantized\nintermediate → batched down → routing-weighted top-k reduce. Returns\n``(num_tokens, hidden_dim)``. The base ops dispatch on the weight dtypes / scale\nlayout (block-dynamic FP8, MXFP8/MXFP4, NVFP4 — decode runs the software/swap arms\nbelow the native mxf4nvf4 M=128 staging); ``activation_format`` names the activation\nquantization for the whole block — activations and the fused intermediate requant\ncarry it (``\"mxfp4\"`` runs the all-fp4 W4A4 chain, ``\"bf16\"`` is weight-only); ``None``\nfollows the weight format, and the ops validate the pairing. The\n``*_proj_input_global_scale`` pair is the NVFP4 activation second level (a checkpoint's\ncalibrated per-projection ``input_scale``): the gate_up quantizes hidden against its own,\nrequants the intermediate against the down's, and the down consumes that as its activation\nglobal. ``None`` is dynamic quant everywhere else. ``simulate_unfused`` (testing) rounds each\nstep through the activation dtype so the output matches the unfused reference to\nreduce order. ``act_fn`` is a\n``get_supported_act_fns()`` name (fused into the gate_up epilogue where the forward fuses) or any\ncallable applied on the host to the raw gate_up output; ``gate=False`` runs an ungated\nprojection. Scales are affine or pre-swizzled (``SWIZZLE_32_4_4``, self-describing 5-D)\n— the swizzled layout is for the dot_scaled arm; weight-only chains take affine.",
371
  "signature": {
@@ -521,7 +521,7 @@
521
  {
522
  "name": "moe_fused_grouped",
523
  "qualname": "moe_fused_grouped",
524
- "module": "_finegrained_kernels_cuda_43f1581.moe",
525
  "kind": "function",
526
  "doc": "Fused grouped MoE (prefill): gather gate_up + SiLU + requant epilogue → quantized\nexpert-ordered intermediate → grouped down → routing-weighted top-k reduce. Returns\n``(num_tokens, hidden_dim)``. The base ops dispatch on the weight dtypes / scale\nlayout (block-dynamic FP8, MXFP8/MXFP4, NVFP4); ``activation_format`` names the activation\nquantization for the whole block — activations and the fused intermediate requant\ncarry it (``\"mxfp4\"``/``\"nvfp4\"`` run all-fp4 W4A4 chains, ``\"bf16\"`` is weight-only);\n``None`` follows the weight format, and the ops validate the pairing. The\n``*_proj_input_global_scale`` pair is the NVFP4 activation second level (a checkpoint's\ncalibrated per-projection ``input_scale``): the gate_up quantizes hidden against its own,\nrequants the intermediate against the down's, and the down consumes that as its activation\nglobal. ``None`` is dynamic quant everywhere else. ``simulate_unfused`` (testing) rounds each step through\nthe activation dtype so the output matches the unfused reference to reduce order. ``act_fn`` is a\n``get_supported_act_fns()`` name (fused into the gate_up epilogue where the forward fuses) or any\ncallable applied on the host to the raw gate_up output; ``gate=False`` runs an ungated\nprojection. Scales are affine or pre-swizzled (``SWIZZLE_32_4_4``, self-describing 5-D)\n— the swizzled layout is for the dot_scaled arm; weight-only chains take affine.",
527
  "signature": {
@@ -674,10 +674,64 @@
674
  "return_annotation": "torch.Tensor"
675
  }
676
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
677
  {
678
  "name": "get_supported_act_fns",
679
  "qualname": "get_supported_act_fns",
680
- "module": "_finegrained_kernels_cuda_43f1581.formats",
681
  "kind": "function",
682
  "doc": "The activation names the gate|up epilogue fuses in-kernel; any other activation is applied\non the host by the MoE forwards (``act_fn`` as a callable).",
683
  "signature": {
@@ -688,7 +742,7 @@
688
  {
689
  "name": "get_supported_norms",
690
  "qualname": "get_supported_norms",
691
- "module": "_finegrained_kernels_cuda_43f1581.norm",
692
  "kind": "function",
693
  "doc": "The norm names the kernels fuse (``post_expert_norm``), each ``x * factor * rsqrt(mean +\neps)`` over the row: ``\"rms_norm\"`` scales the normalized row by ``weight``,\n``\"centered_rms_norm\"`` by ``1 + weight`` (the zero-centered parameterization), and\n``\"input_scaled_rms_norm\"`` scales by ``1 + weight`` BEFORE normalizing, so the mean square\nis taken on the scaled row. Anything else stays a host callable.",
694
  "signature": {
@@ -699,7 +753,7 @@
699
  {
700
  "name": "rms_norm_rows",
701
  "qualname": "rms_norm_rows",
702
- "module": "_finegrained_kernels_cuda_43f1581.norm",
703
  "kind": "function",
704
  "doc": "RMS-normalize ``(S, H)`` rows in one launch, in the ``get_supported_norms()`` form named by\n``norm`` (fp32 accumulate, input dtype out). The MoE chain folds this into\n``weighted_reduce`` instead; this is the standalone form.",
705
  "signature": {
@@ -735,7 +789,7 @@
735
  {
736
  "name": "swizzle_mx_scales",
737
  "qualname": "swizzle_mx_scales",
738
- "module": "_finegrained_kernels_cuda_43f1581.swizzle",
739
  "kind": "function",
740
  "doc": "Reorder a block-scale tensor into the ``SWIZZLE_32_4_4`` layout the Blackwell tcgen05\nscaled-MMA consumes, one triton launch per matrix (``_swizzle_scales_kernel`` — no torch\npermute/transpose copies). The scale values are unchanged — this only rearranges them from\nplain row-major into the swizzled order the tensor core reads directly (the same layout\ncuBLAS/CUTLASS require for MXFP8/NVFP4 ``scaled_mm``). Plain row-major forces a gather that\ncaps the scaled dot below the fp8/fp4 peak; the swizzle removes it. Apply to weight scales\nonce at model load; the ops accept the result directly.\n\n``scale`` is the per-block scale grid — UE8M0 (``float8_e8m0fnu``, group-32 MXFP8/MXFP4) or\nE4M3 (``float8_e4m3fn``, group-16 NVFP4); the reorder is dtype-agnostic (moves bytes):\n\n- 2D ``(rows, K // group)``: one matrix (a dense weight's scale).\n- 3D ``(E, rows, K // group)``: an expert stack, each expert swizzled independently so its\n blocks stay ``ceil(rows/128)``-aligned (the reader indexes expert ``e`` at\n ``e * ceil(rows/128)``; byte-identical to a flat swizzle when ``rows % 128 == 0``).\n\n``gather_idx`` (2D only): a 1-D ``(padded_rows,)`` index mapping each output (sorted) row to\nits source row in ``scale`` (``-1`` = padding → zero row), folded into the kernel's load — the\nrouted/expert-sorted, per-tile-padded layout a grouped GEMM reads affine per BM=128 tile.\n``padded_rows`` must be a multiple of 128.\n\n``rows``/``cols`` are zero-padded to (128, 4) multiples; returns the 5D\n``(G, row_blocks, ceil(cols/4), 2, 256)`` artifact the ops read — ``G`` the expert count (1 for\na matrix), so an expert stack shards/gathers on its leading dim. Bit-identical to CUTLASS's\npacker (verified).",
741
  "signature": {
@@ -759,7 +813,7 @@
759
  {
760
  "name": "unswizzle_mx_scales",
761
  "qualname": "unswizzle_mx_scales",
762
- "module": "_finegrained_kernels_cuda_43f1581.swizzle",
763
  "kind": "function",
764
  "doc": "Inverse of ``swizzle_mx_scales``: a SWIZZLE_32_4_4 artifact back to the plain row-major\n``(rows, cols)`` grid — or ``(num_experts, rows, cols)`` for an expert stack — with the\n(128, 4) padding trimmed. Pure reshape/permute on the byte view (no kernel); it runs at\nsave/dequantize time, never on a forward, so the torch-level cost is irrelevant.\n\n``rows``/``cols`` are the LOGICAL dims of ONE matrix (for a gate|up slab, the full ``2N``);\nboth are trimmed back out of the (128, 4) padding. ``num_experts`` selects the stacked\n``(E, rows, cols)`` result and must match the artifact's leading dim.",
765
  "signature": {
@@ -795,7 +849,7 @@
795
  {
796
  "name": "mxfp8_act_quant",
797
  "qualname": "mxfp8_act_quant",
798
- "module": "_finegrained_kernels_cuda_43f1581.quant",
799
  "kind": "function",
800
  "doc": "Quantize ``(T, K)`` activations to MX once (E4M3 values + UE8M0 group-32 uint8 scales)\ninstead of inline per weight-tile — the fused gate_up re-ran the inline quant per N-tile\n(16x redundant amax/convert ALU + 2x act bytes), which held it at ~380 TFLOPS while the\npre-quantized down kernel ran at ~1080. One pass costs ~50µs at 8k tokens. Bit-exact with\nthe inline form (same group boundaries). ``swizzled=True`` emits the scale directly in\nSWIZZLE_32_4_4 for the tcgen05 fast path (same dense grid, per-element store).",
801
  "signature": {
@@ -819,7 +873,7 @@
819
  {
820
  "name": "mxfp4_act_quant",
821
  "qualname": "mxfp4_act_quant",
822
- "module": "_finegrained_kernels_cuda_43f1581.quant",
823
  "kind": "function",
824
  "doc": "Quantize ``(T, K)`` activations to MXFP4 in one kernel pass: packed-E2M1 values\n(``(T, K//2)`` int8, two codes per byte, first value in the low nibble) + UE8M0\ngroup-32 uint8 scales (``(T, K//32)``, amax/6 ceil'd to a power of two). Bit-identical\nto the fused epilogues' inline form (shared ``mx_act_quant_inline`` arm). Feeds the\nW4A4 arm of the MX matmul ops; quantizing activations to fp4 at runtime is an\naccuracy call the caller owns — the ops never do it implicitly. ``swizzled=True`` emits the\nscale directly in SWIZZLE_32_4_4 for the tcgen05 fast path.",
825
  "signature": {
@@ -843,7 +897,7 @@
843
  {
844
  "name": "nvfp4_act_quant",
845
  "qualname": "nvfp4_act_quant",
846
- "module": "_finegrained_kernels_cuda_43f1581.quant",
847
  "kind": "function",
848
  "doc": "Quantize ``(T, K)`` activations to NVFP4 in one kernel pass: packed-E2M1 values\n(``(T, K//2)`` int8) + E4M3 group-16 block scales (``(T, K//16)`` — ``amax/6`` rounded to\nE4M3, NOT a power of two; values divide by the DECODED scale before the E2M1 grid,\nthe standard two-step). ``global_scale`` is the CALIBRATED per-tensor second level\n(fp32, the checkpoint's ``input_scale``): values are normalized by it before\nthe block quant, so block scales stay in e4m3 range regardless of the activation's\ndynamic range — the canonical two-level format. The GEMM folds ``g_a·g_b`` back onto\nthe accumulator (pass ``As=scales`` with ``a_global_scale=global_scale``). Per-expert globals\nneed each row's expert, which ``expert_index`` gives where the caller already holds that map\n(the batched op's ``expert_ids``, sentinels and all) — it is read only when the globals are\nper expert, and ``mx_act_quant_grouped`` needs none at all, its tiles carrying an expert.\n``None`` = single-level (``g_a = 1``). ``swizzled=True`` emits the scale directly in\nSWIZZLE_32_4_4 for the tcgen05 fast path.",
849
  "signature": {
 
1
  {
2
  "schema_version": 1,
3
+ "module": "_finegrained_kernels_cuda_ec5cc90",
4
  "functions": [
5
  {
6
  "name": "matmul_2d",
7
  "qualname": "matmul_2d",
8
+ "module": "_finegrained_kernels_cuda_ec5cc90.matmul",
9
  "kind": "function",
10
  "doc": "Dense (2D) quantized matmul dispatcher (W8A8 FP8, W4A8/W4A4 FP4) — the single-GEMM sibling of\n``matmul_grouped``/``matmul_batched``, taking the same ``As``/``Bs`` scale spec,\n``activation_format`` / gate|up fusion kwargs, and inferred quant block (no ``block_size`` argument — it\nfalls out of the weight-scale shape, so the data can't disagree with a parameter).\n\n``As`` is the activation block-scale spec, same as the routed ops: ``None`` → the op quantizes raw\n``A`` dynamically; a per-tensor scalar → static (calibrated) FP8 activation scale; block scales →\npre-quantized ``A``. ``Bs`` is the weight block scale. ``a_global_scale``/``b_global_scale`` are the\ntwo-level NVFP4 per-tensor second-level scales (calibrated ``input_scale`` ``g_a`` and\n``weight_scale_2`` ``g_b``) — provided, never computed; the op folds ``g_a·g_b`` onto the\naccumulator (``a_global_scale`` rides raw ``A`` or a pre-quantized ``As`` alike).\n``output_global_scale`` (NVFP4 requant only) is the NEXT proj's provided ``input_scale``:\nthe fused requant normalizes the GLU intermediate by it, returning ``(C, Cs)`` the down-proj\nconsumes as ``As=Cs, a_global_scale=output_global_scale``.\n\n``activation_format`` is the activations' quantization: ``None`` (default) = the weights' own\nformat, ``\"bf16\"`` = weight-only (no activation quant), or an explicit ``\"fp8\"`` / ``\"mxfp8\"`` /\n``\"mxfp4\"`` / ``\"nvfp4\"`` (``\"mxfp8\"`` on MXFP4 weights = the W4A8 chain; ``check_activation_format``\nhas the per-family matrix). ``gate=True`` reads ``B`` as the ``(2N, K)`` gate|up stack and fuses\nthe ``act_fn`` GLU (``swiglu_alpha``/``swiglu_limit`` its clamped variant), returning the\n``[..., N]`` intermediate; ``quantize_output=True`` (MX weights only) stores that intermediate in\n``activation_format`` and returns ``(C, Cs)`` for the next op.\n\nRoutes by weight dtype + inferred block: ``Bs`` None → full-precision; MX/NVFP4 (int8/E4M3 weights\nwith UE8M0 group-32 or E4M3 group-16 ``Bs``) → ``mx_dynamic_matmul``; tensor-wide FP8 (scalar\n``Bs``) → ``w8a8_tensor_dynamic_fp8_matmul``; block FP8 → the static variant when ``As`` is a\nper-tensor scalar, else dynamic.",
11
  "signature": {
 
113
  {
114
  "name": "matmul_batched",
115
  "qualname": "matmul_batched",
116
+ "module": "_finegrained_kernels_cuda_ec5cc90.batched",
117
  "kind": "function",
118
  "doc": "Batched matmul dispatcher (W8A8 FP8, W4A8/W4A4 FP4, or full-precision). Routes one\nprogram per routed row (``expert_ids`` gives its expert).\n\n``As`` marks ``A`` as already quantized (framework-precomputed scales, or a requantized\nintermediate handed to the down projection); a per-tensor scalar ``As`` is instead the static\n(calibrated) activation scale for block-scale FP8 weights — the op quantizes raw ``A`` against\nit; ``None`` = raw ``A``, quantized dynamically by the op in ``activation_format`` (``None`` =\nthe weights' own format — fp8/E4M3 offline for bd/tensor, inline for MX; ``\"bf16\"`` = weight-only;\n``\"mxfp4\"`` = packed E2M1 activations). ``Bs`` ``None`` = unquantized BF16/FP16 weights.\n``quantize_output`` requantizes the output into ``activation_format`` — the return is then\n``(C, Cs)``. ``gate``/``act_fn``/``swiglu_alpha``/``swiglu_limit`` fuse the gate|up GLU into the\nepilogue. ``As``/``Bs`` are each a bare block-scale tensor; the two-level NVFP4\nsecond-level scales ride the separate ``a_global_scale``/``b_global_scale`` (fp32 per-tensor,\nweights per-expert ``(E,)``; from ``nvfp4_quantize_two_level``), and the op folds ``g_a · g_b``\nonto the accumulator. The activation global ``g_a`` is CALIBRATED (the checkpoint's\n``input_scale``): ``a_global_scale=g_a`` with a raw ``A`` has the op quantize ``A / g_a`` per\nblock, and rides a pre-quantized ``As`` the same way. Under an NVFP4 requant the\nfused requant normalizes the GLU intermediate by the PROVIDED ``output_global_scale`` (the next\nproj's calibrated ``input_scale``) before the block quant and returns ``[C, Cs]``; the down\nconsumes it as ``As=Cs, a_global_scale=output_global_scale``. ``gather_idx``/``scatter_idx`` (each None or a ``(S,)`` map)\naddress the source row of ``A`` / destination row of ``C`` per program — None means row\n``s``; the gather lets the gate_up read unexpanded activations (source row\n``s // num_top_k``) with no copy.\n\nRoutes by what the weight tensors themselves say (there is no ``block_size``\nparameter — the quantization block is derived from the scale shape,\n``weight_block_size``):\n- ``Bs`` None → ``full_precision_matmul_batched`` (plain dot, no scales anywhere).\n- MX weights — ``int8`` (packed E2M1) or ``float8_e4m3fn`` (E4M3) with UE8M0\n group-32 ``Bs`` → ``mx_dynamic_matmul_batched``.\n- one scale per expert (``Bs`` ``(E,)``/``(E, 1, 1)``) →\n ``w8a8_tensor_dynamic_fp8_matmul_batched``.\n- block scales (``Bs`` ``(E, N/bn, K/bk)``) → ``w8a8_block_dynamic_fp8_matmul_batched``.",
119
  "signature": {
 
239
  {
240
  "name": "matmul_grouped",
241
  "qualname": "matmul_grouped",
242
+ "module": "_finegrained_kernels_cuda_ec5cc90.grouped",
243
  "kind": "function",
244
  "doc": "Grouped matmul dispatcher (W8A8 FP8, W4A8/W4A4 FP4, or full-precision).\n``expert_start`` is the ``(E+1,)`` tiling schedule from one ``compute_grouped_scheduling``\npass, shared by every grouped GEMM of the layer (the expert sort is virtual — nothing is\nphysically permuted).\n\n``As`` marks ``A`` as already quantized (framework-precomputed scales, or a requantized\nintermediate handed to the down projection); a per-tensor scalar ``As`` is instead the static\n(calibrated) activation scale for block-scale FP8 weights — the op quantizes raw ``A`` against\nit; ``None`` = raw ``A``, quantized dynamically by the op in ``activation_format`` (``None`` =\nthe weights' own format — fp8/E4M3, or the fp4 chains; ``\"bf16\"`` = weight-only; ``\"mxfp4\"`` =\npacked E2M1 activations). ``Bs`` ``None`` = unquantized BF16/FP16 weights.\n``quantize_output`` requantizes the output into ``activation_format`` — the return is then\n``(C, Cs)``. ``gate``/``act_fn``/``swiglu_alpha``/``swiglu_limit`` fuse the gate|up GLU into the\nepilogue.\n``As``/``Bs`` are each a bare block-scale tensor; the two-level NVFP4 second-level scales ride\nthe separate ``a_global_scale``/``b_global_scale`` (fp32 per-tensor, weights per-expert ``(E,)``;\nfrom ``nvfp4_quantize_two_level``), and the op folds ``g_a · g_b`` onto the accumulator. The\nactivation global ``g_a`` is CALIBRATED (the checkpoint's ``input_scale``): ``a_global_scale=g_a``\nwith a raw ``A`` has the op quantize ``A / g_a`` per block, and rides a pre-quantized ``As`` the\nsame way. Under an NVFP4 requant the fused requant normalizes the GLU intermediate by the\nPROVIDED ``output_global_scale`` (the next proj's calibrated ``input_scale``) before the block\nquant and returns ``[C, Cs]``; the down consumes it as ``As=Cs, a_global_scale=output_global_scale``.\nRow order is carried by the standalone maps: ``gather_idx`` gathers ``A`` (``None`` ->\nalready expert-ordered), ``scatter_idx`` scatters the output. The fused MoE chain is one\nscheduling pass: gate_up with ``scatter_idx=None`` + ``gate=True`` + ``quantize_output=True``,\nthen down with ``gather_idx=None`` and the\nintermediate's scales as ``As``. EP-sentinel routes sit past ``expert_start[-1]``, which\nno tile reaches.\n\nRoutes by what the weight tensors themselves say (there is no ``block_size``\nparameter — the quantization block is derived from the scale shape,\n``weight_block_size``):\n- ``Bs`` None → ``full_precision_matmul_grouped`` (plain dot, no scales anywhere).\n- MX weights — ``int8`` (packed E2M1) or ``float8_e4m3fn`` (E4M3) with UE8M0\n group-32 ``Bs`` → ``mx_dynamic_matmul_grouped``.\n- one scale per expert (``Bs`` ``(E,)``/``(E, 1, 1)``) →\n ``w8a8_tensor_dynamic_fp8_matmul_grouped``.\n- block scales (``Bs`` ``(E, N/bn, K/bk)``) → ``w8a8_block_dynamic_fp8_matmul_grouped``.",
245
  "signature": {
 
365
  {
366
  "name": "moe_fused_batched",
367
  "qualname": "moe_fused_batched",
368
+ "module": "_finegrained_kernels_cuda_ec5cc90.moe",
369
  "kind": "function",
370
  "doc": "Fused batched MoE (decode): gate_up + SiLU + requant epilogue → per-row quantized\nintermediate → batched down → routing-weighted top-k reduce. Returns\n``(num_tokens, hidden_dim)``. The base ops dispatch on the weight dtypes / scale\nlayout (block-dynamic FP8, MXFP8/MXFP4, NVFP4 — decode runs the software/swap arms\nbelow the native mxf4nvf4 M=128 staging); ``activation_format`` names the activation\nquantization for the whole block — activations and the fused intermediate requant\ncarry it (``\"mxfp4\"`` runs the all-fp4 W4A4 chain, ``\"bf16\"`` is weight-only); ``None``\nfollows the weight format, and the ops validate the pairing. The\n``*_proj_input_global_scale`` pair is the NVFP4 activation second level (a checkpoint's\ncalibrated per-projection ``input_scale``): the gate_up quantizes hidden against its own,\nrequants the intermediate against the down's, and the down consumes that as its activation\nglobal. ``None`` is dynamic quant everywhere else. ``simulate_unfused`` (testing) rounds each\nstep through the activation dtype so the output matches the unfused reference to\nreduce order. ``act_fn`` is a\n``get_supported_act_fns()`` name (fused into the gate_up epilogue where the forward fuses) or any\ncallable applied on the host to the raw gate_up output; ``gate=False`` runs an ungated\nprojection. Scales are affine or pre-swizzled (``SWIZZLE_32_4_4``, self-describing 5-D)\n— the swizzled layout is for the dot_scaled arm; weight-only chains take affine.",
371
  "signature": {
 
521
  {
522
  "name": "moe_fused_grouped",
523
  "qualname": "moe_fused_grouped",
524
+ "module": "_finegrained_kernels_cuda_ec5cc90.moe",
525
  "kind": "function",
526
  "doc": "Fused grouped MoE (prefill): gather gate_up + SiLU + requant epilogue → quantized\nexpert-ordered intermediate → grouped down → routing-weighted top-k reduce. Returns\n``(num_tokens, hidden_dim)``. The base ops dispatch on the weight dtypes / scale\nlayout (block-dynamic FP8, MXFP8/MXFP4, NVFP4); ``activation_format`` names the activation\nquantization for the whole block — activations and the fused intermediate requant\ncarry it (``\"mxfp4\"``/``\"nvfp4\"`` run all-fp4 W4A4 chains, ``\"bf16\"`` is weight-only);\n``None`` follows the weight format, and the ops validate the pairing. The\n``*_proj_input_global_scale`` pair is the NVFP4 activation second level (a checkpoint's\ncalibrated per-projection ``input_scale``): the gate_up quantizes hidden against its own,\nrequants the intermediate against the down's, and the down consumes that as its activation\nglobal. ``None`` is dynamic quant everywhere else. ``simulate_unfused`` (testing) rounds each step through\nthe activation dtype so the output matches the unfused reference to reduce order. ``act_fn`` is a\n``get_supported_act_fns()`` name (fused into the gate_up epilogue where the forward fuses) or any\ncallable applied on the host to the raw gate_up output; ``gate=False`` runs an ungated\nprojection. Scales are affine or pre-swizzled (``SWIZZLE_32_4_4``, self-describing 5-D)\n— the swizzled layout is for the dot_scaled arm; weight-only chains take affine.",
527
  "signature": {
 
674
  "return_annotation": "torch.Tensor"
675
  }
676
  },
677
+ {
678
+ "name": "embedding_lookup",
679
+ "qualname": "embedding_lookup",
680
+ "module": "_finegrained_kernels_cuda_ec5cc90.embedding",
681
+ "kind": "function",
682
+ "doc": "Gather and dequantize selected rows without expanding the embedding table.\n\n``weight_format`` selects ``fp8``, ``nvfp4``, ``mxfp8``, ``mxfp4``, ``int8``, or ``int4``.\nBy default, a global scale selects NVFP4, signed int8 weights select INT8, and otherwise\nFP8 is used. FP8 accepts scalar, per-row, or square block scales (``block_size``).\nNVFP4 uses E4M3 group-16 scales and one FP32 multiplier; MX formats use UE8M0 group-32\nscales. FP4 packs adjacent columns, low nibble first. INT8 uses per-row scales; INT4\npacks adjacent vocabulary rows and uses per-row column groups inferred from its scale\ngrid. Integer values are signed, symmetric, and have no zero point.\n\nOutputs default to the floating scale dtype, or bf16 for NVFP4/MX. Table/grid strides\nmay describe TP slices. Indices follow ``torch.nn.functional.embedding``: negative and\nout-of-range values fail; padding indices look up their stored row. Empty, repeated,\nand noncontiguous indices work.",
683
+ "signature": {
684
+ "parameters": [
685
+ {
686
+ "name": "weight",
687
+ "kind": "POSITIONAL_OR_KEYWORD",
688
+ "default": null,
689
+ "annotation": "torch.Tensor"
690
+ },
691
+ {
692
+ "name": "indices",
693
+ "kind": "POSITIONAL_OR_KEYWORD",
694
+ "default": null,
695
+ "annotation": "torch.Tensor"
696
+ },
697
+ {
698
+ "name": "scale",
699
+ "kind": "POSITIONAL_OR_KEYWORD",
700
+ "default": null,
701
+ "annotation": "torch.Tensor"
702
+ },
703
+ {
704
+ "name": "global_scale",
705
+ "kind": "POSITIONAL_OR_KEYWORD",
706
+ "default": "None",
707
+ "annotation": "torch.Tensor | None"
708
+ },
709
+ {
710
+ "name": "output_dtype",
711
+ "kind": "POSITIONAL_OR_KEYWORD",
712
+ "default": "None",
713
+ "annotation": "torch.dtype | None"
714
+ },
715
+ {
716
+ "name": "weight_format",
717
+ "kind": "KEYWORD_ONLY",
718
+ "default": "None",
719
+ "annotation": "str | None"
720
+ },
721
+ {
722
+ "name": "block_size",
723
+ "kind": "KEYWORD_ONLY",
724
+ "default": "128",
725
+ "annotation": "int"
726
+ }
727
+ ],
728
+ "return_annotation": "torch.Tensor"
729
+ }
730
+ },
731
  {
732
  "name": "get_supported_act_fns",
733
  "qualname": "get_supported_act_fns",
734
+ "module": "_finegrained_kernels_cuda_ec5cc90.formats",
735
  "kind": "function",
736
  "doc": "The activation names the gate|up epilogue fuses in-kernel; any other activation is applied\non the host by the MoE forwards (``act_fn`` as a callable).",
737
  "signature": {
 
742
  {
743
  "name": "get_supported_norms",
744
  "qualname": "get_supported_norms",
745
+ "module": "_finegrained_kernels_cuda_ec5cc90.norm",
746
  "kind": "function",
747
  "doc": "The norm names the kernels fuse (``post_expert_norm``), each ``x * factor * rsqrt(mean +\neps)`` over the row: ``\"rms_norm\"`` scales the normalized row by ``weight``,\n``\"centered_rms_norm\"`` by ``1 + weight`` (the zero-centered parameterization), and\n``\"input_scaled_rms_norm\"`` scales by ``1 + weight`` BEFORE normalizing, so the mean square\nis taken on the scaled row. Anything else stays a host callable.",
748
  "signature": {
 
753
  {
754
  "name": "rms_norm_rows",
755
  "qualname": "rms_norm_rows",
756
+ "module": "_finegrained_kernels_cuda_ec5cc90.norm",
757
  "kind": "function",
758
  "doc": "RMS-normalize ``(S, H)`` rows in one launch, in the ``get_supported_norms()`` form named by\n``norm`` (fp32 accumulate, input dtype out). The MoE chain folds this into\n``weighted_reduce`` instead; this is the standalone form.",
759
  "signature": {
 
789
  {
790
  "name": "swizzle_mx_scales",
791
  "qualname": "swizzle_mx_scales",
792
+ "module": "_finegrained_kernels_cuda_ec5cc90.swizzle",
793
  "kind": "function",
794
  "doc": "Reorder a block-scale tensor into the ``SWIZZLE_32_4_4`` layout the Blackwell tcgen05\nscaled-MMA consumes, one triton launch per matrix (``_swizzle_scales_kernel`` — no torch\npermute/transpose copies). The scale values are unchanged — this only rearranges them from\nplain row-major into the swizzled order the tensor core reads directly (the same layout\ncuBLAS/CUTLASS require for MXFP8/NVFP4 ``scaled_mm``). Plain row-major forces a gather that\ncaps the scaled dot below the fp8/fp4 peak; the swizzle removes it. Apply to weight scales\nonce at model load; the ops accept the result directly.\n\n``scale`` is the per-block scale grid — UE8M0 (``float8_e8m0fnu``, group-32 MXFP8/MXFP4) or\nE4M3 (``float8_e4m3fn``, group-16 NVFP4); the reorder is dtype-agnostic (moves bytes):\n\n- 2D ``(rows, K // group)``: one matrix (a dense weight's scale).\n- 3D ``(E, rows, K // group)``: an expert stack, each expert swizzled independently so its\n blocks stay ``ceil(rows/128)``-aligned (the reader indexes expert ``e`` at\n ``e * ceil(rows/128)``; byte-identical to a flat swizzle when ``rows % 128 == 0``).\n\n``gather_idx`` (2D only): a 1-D ``(padded_rows,)`` index mapping each output (sorted) row to\nits source row in ``scale`` (``-1`` = padding → zero row), folded into the kernel's load — the\nrouted/expert-sorted, per-tile-padded layout a grouped GEMM reads affine per BM=128 tile.\n``padded_rows`` must be a multiple of 128.\n\n``rows``/``cols`` are zero-padded to (128, 4) multiples; returns the 5D\n``(G, row_blocks, ceil(cols/4), 2, 256)`` artifact the ops read — ``G`` the expert count (1 for\na matrix), so an expert stack shards/gathers on its leading dim. Bit-identical to CUTLASS's\npacker (verified).",
795
  "signature": {
 
813
  {
814
  "name": "unswizzle_mx_scales",
815
  "qualname": "unswizzle_mx_scales",
816
+ "module": "_finegrained_kernels_cuda_ec5cc90.swizzle",
817
  "kind": "function",
818
  "doc": "Inverse of ``swizzle_mx_scales``: a SWIZZLE_32_4_4 artifact back to the plain row-major\n``(rows, cols)`` grid — or ``(num_experts, rows, cols)`` for an expert stack — with the\n(128, 4) padding trimmed. Pure reshape/permute on the byte view (no kernel); it runs at\nsave/dequantize time, never on a forward, so the torch-level cost is irrelevant.\n\n``rows``/``cols`` are the LOGICAL dims of ONE matrix (for a gate|up slab, the full ``2N``);\nboth are trimmed back out of the (128, 4) padding. ``num_experts`` selects the stacked\n``(E, rows, cols)`` result and must match the artifact's leading dim.",
819
  "signature": {
 
849
  {
850
  "name": "mxfp8_act_quant",
851
  "qualname": "mxfp8_act_quant",
852
+ "module": "_finegrained_kernels_cuda_ec5cc90.quant",
853
  "kind": "function",
854
  "doc": "Quantize ``(T, K)`` activations to MX once (E4M3 values + UE8M0 group-32 uint8 scales)\ninstead of inline per weight-tile — the fused gate_up re-ran the inline quant per N-tile\n(16x redundant amax/convert ALU + 2x act bytes), which held it at ~380 TFLOPS while the\npre-quantized down kernel ran at ~1080. One pass costs ~50µs at 8k tokens. Bit-exact with\nthe inline form (same group boundaries). ``swizzled=True`` emits the scale directly in\nSWIZZLE_32_4_4 for the tcgen05 fast path (same dense grid, per-element store).",
855
  "signature": {
 
873
  {
874
  "name": "mxfp4_act_quant",
875
  "qualname": "mxfp4_act_quant",
876
+ "module": "_finegrained_kernels_cuda_ec5cc90.quant",
877
  "kind": "function",
878
  "doc": "Quantize ``(T, K)`` activations to MXFP4 in one kernel pass: packed-E2M1 values\n(``(T, K//2)`` int8, two codes per byte, first value in the low nibble) + UE8M0\ngroup-32 uint8 scales (``(T, K//32)``, amax/6 ceil'd to a power of two). Bit-identical\nto the fused epilogues' inline form (shared ``mx_act_quant_inline`` arm). Feeds the\nW4A4 arm of the MX matmul ops; quantizing activations to fp4 at runtime is an\naccuracy call the caller owns — the ops never do it implicitly. ``swizzled=True`` emits the\nscale directly in SWIZZLE_32_4_4 for the tcgen05 fast path.",
879
  "signature": {
 
897
  {
898
  "name": "nvfp4_act_quant",
899
  "qualname": "nvfp4_act_quant",
900
+ "module": "_finegrained_kernels_cuda_ec5cc90.quant",
901
  "kind": "function",
902
  "doc": "Quantize ``(T, K)`` activations to NVFP4 in one kernel pass: packed-E2M1 values\n(``(T, K//2)`` int8) + E4M3 group-16 block scales (``(T, K//16)`` — ``amax/6`` rounded to\nE4M3, NOT a power of two; values divide by the DECODED scale before the E2M1 grid,\nthe standard two-step). ``global_scale`` is the CALIBRATED per-tensor second level\n(fp32, the checkpoint's ``input_scale``): values are normalized by it before\nthe block quant, so block scales stay in e4m3 range regardless of the activation's\ndynamic range — the canonical two-level format. The GEMM folds ``g_a·g_b`` back onto\nthe accumulator (pass ``As=scales`` with ``a_global_scale=global_scale``). Per-expert globals\nneed each row's expert, which ``expert_index`` gives where the caller already holds that map\n(the batched op's ``expert_ids``, sentinels and all) — it is read only when the globals are\nper expert, and ``mx_act_quant_grouped`` needs none at all, its tiles carrying an expert.\n``None`` = single-level (``g_a = 1``). ``swizzled=True`` emits the scale directly in\nSWIZZLE_32_4_4 for the tcgen05 fast path.",
903
  "signature": {