Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7fbbcd3
initial changes
jimmyzho Dec 17, 2025
ece1cd7
address issues
jimmyzho Jan 28, 2026
8e5bfc8
Merge branch 'flashinfer-ai:main' into headers
jimmyzho Feb 4, 2026
e6350d0
initial changes
jimmyzho Dec 17, 2025
16172a9
address issues
jimmyzho Jan 28, 2026
a2f4f52
refactor symlink and artifact hash check
jimmyzho Feb 9, 2026
670df3d
refactor symlink and artifact hash check
jimmyzho Feb 9, 2026
4176fa4
Merge branch 'flashinfer-ai:main' into headers
jimmyzho Feb 9, 2026
4be0702
gitignore + cleanup
jimmyzho Feb 9, 2026
5ffb1d6
Merge branch 'headers' of github.com:jimmyzho/flashinfer into headers
jimmyzho Feb 9, 2026
61a7ecf
undo kernelrunner.h and gitignore
jimmyzho Feb 9, 2026
16252e7
runtimeerror string
jimmyzho Feb 9, 2026
df3babf
runtimeerror string
jimmyzho Feb 9, 2026
96e1b7c
update artifact hash
jimmyzho Feb 11, 2026
de30cfe
modify include path
jimmyzho Feb 11, 2026
baf8716
refactor and add to moe_utils for cutedsl
jimmyzho Feb 12, 2026
a0aaa98
refactor and add to moe_utils for cutedsl
jimmyzho Feb 12, 2026
22c4213
fix str bug
jimmyzho Feb 12, 2026
7c4672a
disable gemm low latency
jimmyzho Feb 21, 2026
ee89fa0
Merge branch 'main' into headers
jimmyzho Feb 21, 2026
2b4034e
add back gemm headers
jimmyzho Feb 21, 2026
4f43409
Merge branch 'headers' of github.com:jimmyzho/flashinfer into headers
jimmyzho Feb 21, 2026
e51719d
supplement gemm del
jimmyzho Feb 21, 2026
fe51e7d
revert gemm runner, update artifact hash
jimmyzho Feb 24, 2026
c8cdc5d
cicd
jimmyzho Mar 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ flashinfer/_build_meta.py
flashinfer/data/
flashinfer/jit/aot_config.py
csrc/aot_default_additional_params.h
flashinfer/trtllm/gemm/trtllmGen_gemm_export
flashinfer/trtllm/batched_gemm/trtllmGen_bmm_export

# DS_Store files
.DS_store
Expand Down
10 changes: 6 additions & 4 deletions csrc/trtllm_batched_gemm_runner.cu
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,9 @@ void TrtllmGenBatchedGemmRunner::run(
gemmData.mProblemDimensions.mM = mOptions.transposeMmaOutput ? n : m;
gemmData.mProblemDimensions.mN = mOptions.transposeMmaOutput ? m : n;
gemmData.mProblemDimensions.mK = k;
gemmData.mProblemDimensions.mValidM = gemmData.mProblemDimensions.mM;
gemmData.mProblemDimensions.mValidN = gemmData.mProblemDimensions.mN;
gemmData.mProblemDimensions.mValidK = gemmData.mProblemDimensions.mK;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
gemmData.mProblemDimensions.mRank = 0;
gemmData.mProblemDimensions.mWorldSize = 1;

Expand Down Expand Up @@ -252,10 +255,6 @@ void TrtllmGenBatchedGemmRunner::run(
int32_t multiProcessorCount;
cudaDeviceGetAttribute(&multiProcessorCount, cudaDevAttrMultiProcessorCount, device);

gemmData.mProblemDimensions.mValidM = gemmData.mProblemDimensions.mM;
gemmData.mProblemDimensions.mValidN = gemmData.mProblemDimensions.mN;
gemmData.mProblemDimensions.mValidK = gemmData.mProblemDimensions.mK;

// FIXME once we start using all-reduce in the epilogue of the bmm this can be moved elsewhere
bmm.runInitBeforeWorldSync(config, gemmData, static_cast<void*>(stream));

Expand Down Expand Up @@ -449,6 +448,9 @@ bool TrtllmGenBatchedGemmRunner::isValidConfigIndex(int32_t configIndex, int32_t
gemmData.mProblemDimensions.mM = mOptions.transposeMmaOutput ? n : m;
gemmData.mProblemDimensions.mN = mOptions.transposeMmaOutput ? m : n;
gemmData.mProblemDimensions.mK = k;
gemmData.mProblemDimensions.mValidM = gemmData.mProblemDimensions.mM;
gemmData.mProblemDimensions.mValidN = gemmData.mProblemDimensions.mN;
gemmData.mProblemDimensions.mValidK = gemmData.mProblemDimensions.mK;
gemmData.mProblemDimensions.mRank = 0;
gemmData.mProblemDimensions.mWorldSize = 1;
gemmData.mProblemDimensions.mMaxNumCtasInTokenDim = maxNumCtasInBatchDim;
Expand Down
9 changes: 9 additions & 0 deletions csrc/trtllm_gemm_runner.cu
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ class TrtllmGenGemmRunner {
gemmData.mProblemDimensions.mM = mOptions.transposeMmaOutput ? n : m;
gemmData.mProblemDimensions.mN = mOptions.transposeMmaOutput ? m : n;
gemmData.mProblemDimensions.mK = k;
gemmData.mProblemDimensions.mValidM = gemmData.mProblemDimensions.mM;
gemmData.mProblemDimensions.mValidN = gemmData.mProblemDimensions.mN;
gemmData.mProblemDimensions.mValidK = gemmData.mProblemDimensions.mK;
gemmData.mProblemDimensions.mRank = 0;
gemmData.mProblemDimensions.mWorldSize = 1;

Expand All @@ -139,6 +142,9 @@ class TrtllmGenGemmRunner {
gemmData.mProblemDimensions.mM = mOptions.transposeMmaOutput ? n : m;
gemmData.mProblemDimensions.mN = mOptions.transposeMmaOutput ? m : n;
gemmData.mProblemDimensions.mK = k;
gemmData.mProblemDimensions.mValidM = gemmData.mProblemDimensions.mM;
gemmData.mProblemDimensions.mValidN = gemmData.mProblemDimensions.mN;
gemmData.mProblemDimensions.mValidK = gemmData.mProblemDimensions.mK;
gemmData.mProblemDimensions.mRank = 0;
gemmData.mProblemDimensions.mWorldSize = 1;

Expand Down Expand Up @@ -187,6 +193,9 @@ class TrtllmGenGemmRunner {
gemmData.mProblemDimensions.mM = mOptions.transposeMmaOutput ? n : m;
gemmData.mProblemDimensions.mN = mOptions.transposeMmaOutput ? m : n;
gemmData.mProblemDimensions.mK = k;
gemmData.mProblemDimensions.mValidM = gemmData.mProblemDimensions.mM;
gemmData.mProblemDimensions.mValidN = gemmData.mProblemDimensions.mN;
gemmData.mProblemDimensions.mValidK = gemmData.mProblemDimensions.mK;
gemmData.mProblemDimensions.mRank = 0;
gemmData.mProblemDimensions.mWorldSize = 1;

Expand Down
3 changes: 3 additions & 0 deletions csrc/trtllm_low_latency_gemm_runner.cu
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ gemm::gemm::GemmData createGemmData(int64_t m, int64_t n, int64_t k) {
gemmData.mProblemDimensions.mM = n;
gemmData.mProblemDimensions.mN = m;
gemmData.mProblemDimensions.mK = k;
gemmData.mProblemDimensions.mValidM = gemmData.mProblemDimensions.mM;
gemmData.mProblemDimensions.mValidN = gemmData.mProblemDimensions.mN;
gemmData.mProblemDimensions.mValidK = gemmData.mProblemDimensions.mK;
gemmData.mProblemDimensions.mRank = 0;
gemmData.mProblemDimensions.mWorldSize = 1;

Expand Down
53 changes: 52 additions & 1 deletion flashinfer/artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,54 @@ def get_available_cubin_files(
return tuple()


def get_available_header_files(
source: str, retries: int = 3, delay: int = 5, timeout: int = 10
) -> tuple[str, ...]:
"""
Recursively navigates through child directories (e.g., include/) and finds
all *.h header files, returning them as a tuple of relative paths.
"""
result: list[str] = []

def fetch_directory(url: str, prefix: str = "") -> None:
for attempt in range(1, retries + 1):
try:
response = requests.get(url, timeout=timeout)
response.raise_for_status()

# Find all .h header files in this directory
header_hrefs = re.findall(r'<a href="([^"]+\.h)">', response.text)
for h in header_hrefs:
result.append(prefix + h if prefix else h)

# Find all subdirectories (links ending with /)
dir_hrefs = re.findall(r'<a href="([^"]+/)">', response.text)
for d in dir_hrefs:
# Skip parent directory links
if d == "../" or d.startswith(".."):
continue
subdir_url = safe_urljoin(url, d)
subdir_prefix = prefix + d if prefix else d
fetch_directory(subdir_url, subdir_prefix)

return # Success, exit retry loop

except requests.exceptions.RequestException as e:
logger.warning(
f"Fetching available header files {url}: attempt {attempt} failed: {e}"
)

if attempt < retries:
logger.info(f"Retrying in {delay} seconds...")
time.sleep(delay)

logger.error(f"Max retries reached for {url}. Fetch failed.")

fetch_directory(source)
logger.info(f"result: {result}")
return tuple(result)
Comment on lines +82 to +127

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This new function get_available_header_files has very similar logic to the existing get_available_cubin_files function. To improve maintainability and reduce code duplication, consider refactoring them into a single, more generic function. This new function could accept the file extension (e.g., .h or .cubin) as a parameter.

Comment on lines +82 to +127

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Silent failures in recursive directory traversal may cause incomplete results.

The nested fetch_directory function logs errors but doesn't propagate failures - it silently continues, potentially returning incomplete results. If a subdirectory fails to fetch after retries, callers won't know some headers are missing. Consider either:

  1. Raising an exception after max retries
  2. Returning a success indicator along with results
Proposed fix to raise on failure
             if attempt < retries:
                 logger.info(f"Retrying in {delay} seconds...")
                 time.sleep(delay)

-    logger.error(f"Max retries reached for {url}. Fetch failed.")
+        logger.error(f"Max retries reached for {url}. Fetch failed.")
+        raise RuntimeError(f"Failed to fetch header files from {url}")

     fetch_directory(source)
🧰 Tools
πŸͺ› Ruff (0.14.14)

112-112: Consider moving this statement to an else block

(TRY300)

πŸ€– Prompt for AI Agents
In `@flashinfer/artifacts.py` around lines 82 - 127, The recursive fetch_directory
inside get_available_header_files currently swallows failures after max retries,
causing silent incomplete results; modify fetch_directory to raise an exception
when max retries are reached (include the URL and the last caught exception)
instead of just logging and returning, and allow that exception to propagate out
of get_available_header_files so callers are aware of failures (capture the last
requests.exceptions.RequestException in the except block and raise a
RuntimeError or re-raise the original exception with contextual message).



@dataclass(frozen=True)
class ArtifactPath:
"""
Expand Down Expand Up @@ -182,6 +230,9 @@ def get_subdir_file_list() -> Generator[tuple[str, str], None, None]:
yield (checksum_path, CheckSumHash.map_checksums[checksum_path])
for name in get_available_cubin_files(safe_urljoin(base, cubin_dir)):
yield (safe_urljoin(cubin_dir, name), checksums[name])
for name in get_available_header_files(safe_urljoin(base, cubin_dir)):
full_path = safe_urljoin(cubin_dir, name)
yield (full_path, checksums[full_path])


def download_artifacts() -> None:
Expand All @@ -190,7 +241,7 @@ def download_artifacts() -> None:
# use a shared session to make use of HTTP keep-alive and reuse of
# HTTPS connections.
session = requests.Session()
cubin_files = list(get_subdir_file_list())
cubin_files = list[tuple[str, str]](get_subdir_file_list())
num_threads = int(os.environ.get("FLASHINFER_CUBIN_DOWNLOAD_THREADS", "4"))
with tqdm_logging_redirect(
total=len(cubin_files), desc="Downloading cubins"
Expand Down
108 changes: 106 additions & 2 deletions flashinfer/jit/cubin_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@
import ctypes
import hashlib
import os
import pathlib
from urllib.parse import urljoin
import shutil
import time
from typing import Union
import uuid

import filelock

from .utils import write_if_different
from .core import logger
from .env import FLASHINFER_CUBIN_DIR

Expand Down Expand Up @@ -136,14 +139,20 @@ def download_file(
return False


def get_meta_hash(checksums_bytes: bytes) -> str:
def get_meta_hash(
checksums_bytes: bytes, target_file: str = "flashinferMetaInfo.h"
) -> str:
"""
Parse the checksums.txt file and get the hash of corresponding flashinferMetaInfo.h file
"""
checksums_lines = checksums_bytes.decode("utf-8").splitlines()
for line in checksums_lines:
sha256, filename = line.strip().split()
if ".h" in filename:
# Match on path segment boundary to avoid substring collisions
# (e.g. "Enums.h" must not match "BatchedGemmEnums.h")
if filename.lower() == target_file.lower() or filename.lower().endswith(
"/" + target_file.lower()
):
return sha256
raise ValueError("Invalid checksums.txt, no flashinferMetaInfo.h found")
Comment on lines +142 to 157

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Update error message to include the actual target file.

The error message on line 151 is hardcoded to mention flashinferMetaInfo.h, but the function now accepts a configurable target_file parameter. When searching for a different file (e.g., "GemmInterface.h"), the error message will be misleading.

Proposed fix
-    raise ValueError("Invalid checksums.txt, no flashinferMetaInfo.h found")
+    raise ValueError(f"Invalid checksums.txt, no {target_file} found")
🧰 Tools
πŸͺ› Ruff (0.14.14)

151-151: Avoid specifying long messages outside the exception class

(TRY003)

πŸ€– Prompt for AI Agents
In `@flashinfer/jit/cubin_loader.py` around lines 139 - 151, The ValueError in
get_meta_hash incorrectly hardcodes "flashinferMetaInfo.h" in its message even
though the function accepts a target_file parameter; update the exception raised
in get_meta_hash to include the actual target_file (e.g., use the target_file
variable in the message) so the error reflects which filename was not found and
aids debugging.


Expand Down Expand Up @@ -189,6 +198,27 @@ def load_cubin(cubin_path: str, sha256: str) -> bytes:
return b""


def get_file(
uri_path: str,
sha256: str,
file_path: str,
session=None,
) -> bytes:
"""
Load a file from local cache directory {file_path}, ensure that the sha256 signature matches.
Otherwise, download the file from {uri_path} and write to {file_path}.
"""

file = load_cubin(file_path, sha256)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The function load_cubin is now being used to load generic files, not just cubins. This makes the function name misleading. While load_cubin is not modified in this PR, it would be good to rename it to something more generic like _load_file_with_checksum in a follow-up PR to improve code clarity.

if file:
return file
os.makedirs(os.path.dirname(file_path), exist_ok=True)
uri = safe_urljoin(FLASHINFER_CUBINS_REPOSITORY, uri_path)
logger.info(f"Fetching file from {uri}")
download_file(uri, file_path, session=session)
return load_cubin(file_path, sha256)
Comment on lines +201 to +219

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

get_file silently returns empty bytes on download failure.

If download_file fails (returns False), the subsequent load_cubin on line 227 will also fail and return b"". The caller receives falsy empty bytes with no exception or log message indicating the download itself failed. In gen_trtllm_gen_gemm_module (line 538 of core.py), the return value isn't even checked, so a failed header download would silently produce a broken compilation.

Consider raising on download failure:

Proposed fix
     file = load_cubin(file_path, sha256)
     if file:
         return file
     os.makedirs(os.path.dirname(file_path), exist_ok=True)
     uri = safe_urljoin(FLASHINFER_CUBINS_REPOSITORY, uri_path)
     logger.info(f"Fetching file from {uri}")
-    download_file(uri, file_path, session=session)
-    return load_cubin(file_path, sha256)
+    if not download_file(uri, file_path, session=session):
+        raise RuntimeError(f"Failed to download {uri}")
+    result = load_cubin(file_path, sha256)
+    if not result:
+        raise RuntimeError(f"Downloaded file failed integrity check: {file_path}")
+    return result
πŸ€– Prompt for AI Agents
In `@flashinfer/jit/cubin_loader.py` around lines 209 - 227, get_file currently
returns empty bytes silently when download_file fails or when load_cubin still
can't load the file; change it to explicitly detect failures and raise
informative exceptions: after calling download_file(uri, file_path,
session=session) check its return value and if falsy raise an
IOError/RuntimeError including the uri and file_path; additionally verify the
final load_cubin(file_path, sha256) result and raise if it returns empty bytes
(include sha256 in the message). Reference the get_file function, download_file,
and load_cubin so callers (e.g., gen_trtllm_gen_gemm_module) will get a clear
exception instead of a silent falsy value.



def get_cubin(file_name: str, sha256: str, session=None) -> bytes:
"""
Load a cubin from the local cache directory with {file_name} and
Expand All @@ -211,6 +241,80 @@ def get_cubin(file_name: str, sha256: str, session=None) -> bytes:
return load_cubin(cubin_path, sha256)


def download_trtllm_headers(
op: str,
header_dest_dir: Union[str, pathlib.Path],
header_path: str,
artifact_path: str,
checksum: bytes,
):
header_dest_dir = pathlib.Path(header_dest_dir)

if op == "bmm":
header_files = [
"BatchedGemmEnums.h",
"BatchedGemmInterface.h",
"BatchedGemmOptions.h",
"Enums.h",
"GemmGatedActOptions.h",
"GemmOptions.h",
"KernelParams.h",
"KernelParamsDecl.h",
"KernelTraits.h",
"TmaDescriptor.h",
"trtllm/gen/CommonUtils.h",
"trtllm/gen/CudaArchDecl.h",
"trtllm/gen/CudaKernelLauncher.h",
"trtllm/gen/DtypeDecl.h",
"trtllm/gen/MmaDecl.h",
"trtllm/gen/SfLayoutDecl.h",
"trtllm/gen/SparsityDecl.h",
]

else:
header_files = [
"GemmInterface.h",
"GemmOptions.h",
"Enums.h",
"KernelTraits.h",
"KernelParams.h",
"KernelParamsDecl.h",
"TmaDescriptor.h",
"trtllm/gen/CommonUtils.h",
"trtllm/gen/CudaKernelLauncher.h",
"trtllm/gen/DtypeDecl.h",
"trtllm/gen/MmaDecl.h",
"trtllm/gen/SfLayoutDecl.h",
"trtllm/gen/CudaArchDecl.h",
]

artifact_hash_path = header_dest_dir / ".artifact_hash"

# Check if cached headers are from a different artifact version (e.g. after git checkout)
if artifact_hash_path.exists():
with open(artifact_hash_path, "r") as f:
cached_hash = f.read().strip()
if cached_hash != artifact_path:
raise RuntimeError(
f"Detected inconsistent cached artifacts. "
f"(Cached trtllm headers were downloaded for artifact "
f"'{cached_hash}', but current code expects "
f"'{artifact_path}'). "
f"Please clear the cache to confirm and allow the new headers to be downloaded: "
f"rm -rf {header_dest_dir}."
)

for file in header_files:
uri_path = f"{header_path}/{file}"
file_hash = get_meta_hash(checksum, file)
file_path = str(header_dest_dir / file)
result = get_file(uri_path, file_hash, file_path)
assert result, f"{file} not found"

# Record which artifact version these headers belong to
write_if_different(artifact_hash_path, artifact_path)


def convert_to_ctypes_char_p(data: bytes):
return ctypes.c_char_p(data)

Expand Down
17 changes: 15 additions & 2 deletions flashinfer/jit/fused_moe.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
sm89_nvcc_flags,
)
from .cpp_ext import is_cuda_version_at_least
from .cubin_loader import get_cubin, get_meta_hash
from .cubin_loader import get_cubin, get_meta_hash, download_trtllm_headers
from .gemm.cutlass.generate_kernels import generate_gemm_operations


Expand Down Expand Up @@ -233,6 +233,19 @@ def gen_trtllm_gen_fused_moe_sm100_module() -> JitSpec:
# make sure "flashinferMetaInfo.h" is downloaded or cached
assert metainfo, f"{header_name}.h not found"

header_path = f"{include_path}/trtllmGen_bmm_export"
header_dest_dir = (
jit_env.FLASHINFER_CUBIN_DIR
/ "flashinfer"
/ "trtllm"
/ "batched_gemm"
/ "trtllmGen_bmm_export"
)

download_trtllm_headers(
"bmm", header_dest_dir, header_path, ArtifactPath.TRTLLM_GEN_BMM, checksum
)

# currently only support Blackwell
nvcc_flags = current_compilation_context.get_nvcc_flags_list(
supported_major_versions=[10]
Expand Down Expand Up @@ -265,7 +278,7 @@ def gen_trtllm_gen_fused_moe_sm100_module() -> JitSpec:
]
+ nvcc_flags,
extra_include_paths=[
# link "include" sub-directory in cache
jit_env.FLASHINFER_CUBIN_DIR,
jit_env.FLASHINFER_CUBIN_DIR / include_path,
jit_env.FLASHINFER_CSRC_DIR / "nv_internal",
jit_env.FLASHINFER_CSRC_DIR / "nv_internal/include",
Expand Down
Loading
Loading