Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 9 additions & 1 deletion flashinfer/artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,15 @@ def get_checksums(subdirs):
FLASHINFER_CUBINS_REPOSITORY, safe_urljoin(subdir, "checksums.txt")
)
checksum_path = FLASHINFER_CUBIN_DIR / safe_urljoin(subdir, "checksums.txt")
download_file(uri, checksum_path)
if not download_file(uri, checksum_path) and not checksum_path.is_file():
# Without this the next open() fails with a bare FileNotFoundError on
# the local cache path, which hides the real cause: the artifact pin
# is unreachable (typo'd/unpublished pin, or network/mirror failure).
raise RuntimeError(
f"Failed to fetch the checksum manifest for artifact pin '{subdir}' "
f"from {uri}. Check that the pin exists in "
f"{FLASHINFER_CUBINS_REPOSITORY} and is reachable."
)
with open(checksum_path, "r") as f:
for line in f:
sha256, filename = line.strip().split()
Expand Down
74 changes: 74 additions & 0 deletions tests/test_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
get_subdir_file_list,
)

import pytest
import responses

from flashinfer.jit.cubin_loader import safe_urljoin
Expand Down Expand Up @@ -239,6 +240,12 @@ def _mock_file_index_responses():
responses.add(
responses.GET, gemm_rubin_source, body=success_gemm_response, status=200
)
deepgemm_rubin_source = safe_urljoin(
test_cubin_repository, artifact_paths.DEEPGEMM_RUBIN
)
responses.add(
responses.GET, deepgemm_rubin_source, body=success_deepgemm_response, status=200
)


@responses.activate
Expand Down Expand Up @@ -293,6 +300,50 @@ def test_get_available_cubin_files_non_200_response():
assert available_cubin_files == ()


def test_get_checksums_unreachable_pin_raises(monkeypatch, tmp_path):
"""An artifact pin whose checksums.txt cannot be fetched must fail loudly.

Guards the diagnosis path exercised by #4280: a pin added to `cubin_dirs`
without a published (or, in tests, mocked) manifest used to surface as a bare
FileNotFoundError on a local cache path, which reads like a corrupt cache
rather than an unreachable pin. `download_file` is stubbed rather than mocked
over HTTP so the test does not pay its 4 retries of exponential backoff.
"""
from flashinfer import artifacts

monkeypatch.setattr(artifacts, "FLASHINFER_CUBIN_DIR", tmp_path / "cubins")
monkeypatch.setattr(artifacts, "download_file", lambda *args, **kwargs: False)

with pytest.raises(RuntimeError) as excinfo:
artifacts.get_checksums([artifact_paths.DEEPGEMM_RUBIN])
# The pin must be named -- that is the whole point of the error.
assert artifact_paths.DEEPGEMM_RUBIN in str(excinfo.value)
Comment on lines +317 to +320

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.

🎯 Functional Correctness | 🟑 Minor | ⚑ Quick win

Assert the manifest URL as well as the pin.

The RuntimeError contract includes both values. This test checks only DEEPGEMM_RUBIN. An error that omits the URL still passes. Build the expected URI with the same safe_urljoin expression as flashinfer/artifacts.py:222-249 and assert that it is present.

Proposed test assertion
     assert artifact_paths.DEEPGEMM_RUBIN in str(excinfo.value)
+    expected_uri = safe_urljoin(
+        artifacts.FLASHINFER_CUBINS_REPOSITORY,
+        safe_urljoin(artifact_paths.DEEPGEMM_RUBIN, "checksums.txt"),
+    )
+    assert expected_uri in str(excinfo.value)
πŸ“ Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
with pytest.raises(RuntimeError) as excinfo:
artifacts.get_checksums([artifact_paths.DEEPGEMM_RUBIN])
# The pin must be named -- that is the whole point of the error.
assert artifact_paths.DEEPGEMM_RUBIN in str(excinfo.value)
with pytest.raises(RuntimeError) as excinfo:
artifacts.get_checksums([artifact_paths.DEEPGEMM_RUBIN])
# The pin must be named -- that is the whole point of the error.
assert artifact_paths.DEEPGEMM_RUBIN in str(excinfo.value)
expected_uri = safe_urljoin(
artifacts.FLASHINFER_CUBINS_REPOSITORY,
safe_urljoin(artifact_paths.DEEPGEMM_RUBIN, "checksums.txt"),
)
assert expected_uri in str(excinfo.value)
πŸ€– Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_artifacts.py` around lines 317 - 320, Update the RuntimeError
assertion in the checksum test around artifacts.get_checksums to also construct
the expected manifest URI using the same safe_urljoin expression used by the
artifact implementation, then assert that URI appears in str(excinfo.value)
alongside DEEPGEMM_RUBIN.



def test_get_checksums_falls_back_to_cached_manifest(monkeypatch, tmp_path):
"""A failed refresh must not invalidate an already-cached manifest.

Offline / FLASHINFER_NO_DOWNLOAD setups rely on the on-disk copy.
"""
from flashinfer import artifacts

cubin_dir = tmp_path / "cubins"
monkeypatch.setattr(artifacts, "FLASHINFER_CUBIN_DIR", cubin_dir)
monkeypatch.setattr(artifacts, "download_file", lambda *args, **kwargs: False)

cached = cubin_dir / safe_urljoin(artifact_paths.DEEPGEMM_RUBIN, "checksums.txt")
cached.parent.mkdir(parents=True)
cached.write_text("abc123 kernel.fp8_m_grouped_gemm.007d9ebdca7e.cubin\n")

checksums = artifacts.get_checksums([artifact_paths.DEEPGEMM_RUBIN])
assert checksums == {
safe_urljoin(
artifact_paths.DEEPGEMM_RUBIN,
"kernel.fp8_m_grouped_gemm.007d9ebdca7e.cubin",
): "abc123"
}


@responses.activate
def test_get_subdir_file_list(monkeypatch, tmp_path):
_mock_file_index_responses()
Expand Down Expand Up @@ -347,6 +398,13 @@ def test_get_subdir_file_list(monkeypatch, tmp_path):
d7e8f9a0b1c2 kernel.fp8_m_grouped_gemm.007d9ebdca7e.cubin
e8f9a0b1c2d3 kernel.fp8_m_grouped_gemm.02acb2ba71fd.cubin
f9a0b1c2d3e4 kernel.fp8_m_grouped_gemm.0457375eb02f.cubin
"""

checksums_deepgemm_rubin = """3333333333333333333333333333333333333333333333333333333333333333 kernel_map.json
1111aaaabbbbcccc kernel.fp8_m_grouped_gemm.007404769193.cubin
2222aaaabbbbcccc kernel.fp8_m_grouped_gemm.007d9ebdca7e.cubin
3333aaaabbbbcccc kernel.fp8_m_grouped_gemm.02acb2ba71fd.cubin
4444aaaabbbbcccc kernel.fp8_m_grouped_gemm.0457375eb02f.cubin
"""

# Add mock responses for checksums.txt files
Expand Down Expand Up @@ -391,6 +449,17 @@ def test_get_subdir_file_list(monkeypatch, tmp_path):
responses.GET, deepgemm_checksums_url, body=checksums_deepgemm, status=200
)

deepgemm_rubin_checksums_url = safe_urljoin(
test_cubin_repository,
safe_urljoin(artifact_paths.DEEPGEMM_RUBIN, "checksums.txt"),
)
responses.add(
responses.GET,
deepgemm_rubin_checksums_url,
body=checksums_deepgemm_rubin,
status=200,
)

# Mock DSL_FMHA checksums + directory index for the host cpu_arch.
# Pin to x86_64 so the test is deterministic regardless of the runner arch.
monkeypatch.setattr(artifacts, "_get_host_cpu_arch", lambda: "x86_64")
Expand Down Expand Up @@ -480,6 +549,11 @@ def test_get_subdir_file_list(monkeypatch, tmp_path):
artifact_paths.TRTLLM_GEN_GEMM,
artifact_paths.TRTLLM_GEN_GEMM_RUBIN,
),
(
"kernel.fp8_m_grouped_gemm.007d9ebdca7e.cubin",
artifact_paths.DEEPGEMM,
artifact_paths.DEEPGEMM_RUBIN,
),
):
plain_path = safe_urljoin(plain_dir, shared_name)
rubin_path = safe_urljoin(rubin_dir, shared_name)
Expand Down
Loading