Uh oh!
There was an error while loading. Please reload this page.
Use FIPS-safe hashes for program cache keys - #2087
Conversation
Signed-off-by: Aryan <aryansputta@gmail.com>
Signed-off-by: Aryan <aryansputta@gmail.com>
leofang
left a comment
There was a problem hiding this comment.
Thanks for tackling the FIPS issue — the core fix (blake2b → sha256, bump _KEY_SCHEMA_VERSION) is the right direction. A few changes requested:
- Remove
test_program_cache_fips.py— the elaborate module-stubbing doesn't test real behavior (see file-level comment for details). - Add
usedforsecurity=Falseto bothhashlib.sha256()call sites — documents intent and can unlock faster code paths. - Add inline FIPS comments at both hash usage sites so future maintainers understand the constraint.
- Benchmark FIPS-compliant alternatives before settling on SHA-256 — on 64-bit systems
sha512is often faster, and security doesn't matter here, only collision resistance and speed.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
leofang
commented
May 14, 2026
To clarify: All FIPS-compliant algorithms should be considered and benchmarked. We are not bound to the 256-bit key size. Either shorter or longer is OK, as long as it is fast and has a low chance to collide. |
Signed-off-by: Aryan <aryansputta@gmail.com>
Signed-off-by: Aryan <aryansputta@gmail.com>
d737296 to
c08fc21Comparearyanputta
commented
May 14, 2026
Addressed your requested changes.
|
leofang
commented
May 14, 2026
Could you share your benchmark script? |
aryanputta
commented
May 14, 2026
Sure. I turned the local benchmark into a standalone stdlib-only script that mirrors both hash sites plus the coupled end-to-end path where On this x86_64 host, the SHA-2 family clearly beat the SHA-3 family for these workloads. For the coupled end-to-end cases, A representative run from
Benchmark script#!/usr/bin/env python3# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.## SPDX-License-Identifier: Apache-2.0"""Benchmark FIPS-approved hashlib candidates for cuda.core program-cache use.This mirrors the two relevant call sites:* ``FileStreamProgramCache._path_for_key()``: hash a cache key to a stable filename component via ``hexdigest()``.* ``make_program_cache_key()``: incrementally build the digest from labeled payload chunks and return ``digest()``.The benchmark is intentionally stdlib-only so reviewers can run it directly."""from __future__ importannotationsimportargparseimporthashlibimportinspectimportstatisticsimportsysimporttimefromdataclassesimportdataclassfromtypingimportCallable_DEFAULT_ALGORITHMS= (
"sha224",
"sha256",
"sha384",
"sha512",
"sha3_224",
"sha3_256",
"sha3_384",
"sha3_512",
)
@dataclass(frozen=True)classHashCase:
name: strrunner: Callable[[Callable[..., object]], None]
def_supports_usedforsecurity(constructor: Callable[..., object]) ->bool:
try:
signature=inspect.signature(constructor)
except (TypeError, ValueError):
returnFalsereturn"usedforsecurity"insignature.parametersdef_make_constructor(name: str) ->Callable[..., object]:
constructor=getattr(hashlib, name)
if_supports_usedforsecurity(constructor):
returnlambdadata=b"": constructor(data, usedforsecurity=False)
returnconstructordef_file_stream_case(name: str, key: bytes) ->HashCase:
def_runner(constructor: Callable[..., object]) ->None:
constructor(key).hexdigest()
returnHashCase(name, _runner)
def_program_cache_case(name: str, payloads: tuple[tuple[str, bytes], ...]) ->HashCase:
def_runner(constructor: Callable[..., object]) ->None:
hasher=constructor()
forlabel, payloadinpayloads:
hasher.update(label.encode("ascii"))
hasher.update(len(payload).to_bytes(8, "big"))
hasher.update(payload)
hasher.digest()
returnHashCase(name, _runner)
def_end_to_end_case(name: str, payloads: tuple[tuple[str, bytes], ...]) ->HashCase:
def_runner(constructor: Callable[..., object]) ->None:
hasher=constructor()
forlabel, payloadinpayloads:
hasher.update(label.encode("ascii"))
hasher.update(len(payload).to_bytes(8, "big"))
hasher.update(payload)
key=hasher.digest()
constructor(key).hexdigest()
returnHashCase(name, _runner)
def_sample_cases() ->tuple[HashCase, ...]:
file_stream_key=bytes.fromhex("ab"*48)
long_file_stream_key= (b"cuda-core-cache-key-"*128)[:4096]
source=b"""extern "C" __global__ void saxpy(float a, const float* x, float* y) { const int i = blockIdx.x * blockDim.x + threadIdx.x; y[i] = a * x[i] + y[i];}""".strip()
ptx=b""".version 8.0.target sm_90.address_size 64.visible .entry saxpy() { ret; }""".strip()
option_bytes= (
b"name='saxpy'",
b"arch='sm_90'",
b"max_register_count=None",
b"time=False",
b"link_time_optimization=False",
b"debug=False",
b"lineinfo=False",
b"ftz=None",
b"prec_div=None",
b"prec_sqrt=None",
b"fma=None",
b"split_compile=None",
b"ptxas_options=None",
b"no_cache=False",
)
names= (b"saxpy", b"_Z5saxpyv")
extra_digest=bytes.fromhex("cd"*32)
cpp_payloads= (
("schema", b"2"),
("nvrtc", b"13.2"),
("code_type", b"c++"),
("target_type", b"cubin"),
("code", source),
("option_count", str(len(option_bytes)).encode("ascii")),
*tuple(("option", item) foriteminoption_bytes),
("names_count", str(len(names)).encode("ascii")),
*tuple(("name", item) foriteminnames),
("options_name", b"saxpy"),
("extra_digest", extra_digest),
)
ptx_payloads= (
("schema", b"2"),
("linker", b"nvJitLink-13.2"),
("code_type", b"ptx"),
("target_type", b"cubin"),
("code", ptx),
("option_count", str(len(option_bytes)).encode("ascii")),
*tuple(("option", item) foriteminoption_bytes),
("names_count", b"0"),
("extra_digest", extra_digest),
)
return (
_file_stream_case("file_stream_key_48b", file_stream_key),
_file_stream_case("file_stream_key_4k", long_file_stream_key),
_program_cache_case("program_cache_cpp", cpp_payloads),
_program_cache_case("program_cache_ptx", ptx_payloads),
_end_to_end_case("end_to_end_cpp", cpp_payloads),
_end_to_end_case("end_to_end_ptx", ptx_payloads),
)
def_benchmark_case(
case: HashCase,
constructor: Callable[..., object],
*,
loops: int,
repeat: int,
) ->tuple[float, float]:
samples_ns: list[float] = []
for_inrange(repeat):
start=time.perf_counter_ns()
for_inrange(loops):
case.runner(constructor)
elapsed=time.perf_counter_ns() -startsamples_ns.append(elapsed/loops)
returnstatistics.mean(samples_ns), min(samples_ns)
def_format_ns(value: float) ->str:
returnf"{value:,.1f}"def_write_line(text: str="") ->None:
sys.stdout.write(text+"\n")
defmain() ->None:
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--loops",
type=int,
default=200_000,
help="Iterations per repeat for each algorithm/case pair.",
)
parser.add_argument(
"--repeat",
type=int,
default=7,
help="Independent timing repeats for each algorithm/case pair.",
)
parser.add_argument(
"--algorithms",
nargs="+",
default=list(_DEFAULT_ALGORITHMS),
help="hashlib algorithm names to benchmark.",
)
args=parser.parse_args()
cases=_sample_cases()
widths= {
"algorithm": max(len("Algorithm"), max(len(name) fornameinargs.algorithms)),
"case": max(len(case.name) forcaseincases),
}
_write_line(
f"{'Algorithm':<{widths['algorithm']}} "f"{'Case':<{widths['case']}}{'mean ns/op':>12}{'best ns/op':>12}"
)
_write_line("-"* (widths["algorithm"] +widths["case"] +28))
foralgorithminargs.algorithms:
constructor=_make_constructor(algorithm)
forcaseincases:
mean_ns, best_ns=_benchmark_case(case, constructor, loops=args.loops, repeat=args.repeat)
_write_line(
f"{algorithm:<{widths['algorithm']}} "f"{case.name:<{widths['case']}} "f"{_format_ns(mean_ns):>12}{_format_ns(best_ns):>12}"
)
if__name__=="__main__":
main() |
leofang
commented
May 15, 2026
Benchmark: FIPS-approved hash algorithms for program cacheI re-ran the benchmark with proper parameters ( Results (end-to-end case, best ns/op)All FIPS-approved algorithms available in
Analysis
RecommendationPlease switch back from
When running the benchmark, please use the default parameters ( |
aryanputta
commented
May 15, 2026
Follow-up on the hash choice: the earlier I have switched both runtime call sites back to For reproducibility, the PR now carries |
leofang
commented
May 15, 2026
Please kindly push your local changes, thanks! |
leofang
left a comment
There was a problem hiding this comment.
Thanks, @aryanputta! We're almost ready to run the CI. Please fix the linter errors.
Uh oh!
There was an error while loading. Please reload this page.
leofang
commented
May 16, 2026
/ok to test 404db9a |
This comment has been minimized.
This comment has been minimized.
Head branch was pushed to by a user without write access
aryanputta
commented
May 16, 2026
Following up here because while I was rereading your feedback on the other PR and then looking more closely at this branch, I noticed these two Windows MCDM failures were not coming from the FIPS hash change itself. They are both hitting the same pinned-memory setup path with a CUDA_ERROR_OUT_OF_MEMORY during PinnedMemoryResource(...) construction, so I updated the two NUMA tests to use the existing create_pinned_memory_resource_or_xfail(...) helper that the test suite already uses for known Windows MCDM mempool setup failures. I also pushed the formatter-only cleanup needed for pre-commit.ci. |
rwgk
commented
May 16, 2026
I didn't compare byte-for-byte, but it looks like my #2096. Where did you see the error? Here in the CI? (My PR addresses test failures outside our CI; found by our QA team). |
leofang
commented
May 16, 2026
This is a scope-creep and please revert the recent changes. Irrelevant test failures will be triaged by the team and if any action is needed we'll let you know. In fact, the OOM issue is being addressed (#2096). |
leofang
commented
May 16, 2026
I think it's referring to https://github.com/NVIDIA/cuda-python/actions/runs/25950797754/job/76288908185. |
rwgk
commented
May 16, 2026
Oh awesome, so we're seeing the same flakiness that QA observed also in our CI now. |
aryanputta
commented
May 16, 2026
Yes, that was the CI job I was referring to. I agree this should stay out of |
leofang
commented
May 16, 2026
Yes. I added dual-GPU runners yesterday (#2090; see also team channel). |
aryanputta
commented
May 16, 2026
@rwgk@leofang Thanks, that helps clarify it. My read is that the CI failure looks more like the same Windows MCDM pinned-memory OOM behavior QA saw than something specific to the NUMA test logic itself. A possible fault boundary to check in A simple validation path might be:
I reverted my unrelated test changes from |
leofang
commented
May 16, 2026
@aryanputta could you please force-push to keep 404db9a as the last commit? We were about to merge but all the changes would require re-running the full CI, which is a waste of resource (even for NVIDIA, GPUs are scarce resources). If you revert, we just need to re-run the failing pipeline. |
e84d143 to
404db9aComparearyanputta
commented
May 16, 2026
@rwgk@leofang I did a follow-up CLI pass just to understand the failure surface more concretely. I am not proposing a change on What I checked:
What seems to be happening:
So my current read is that this is less “the NUMA logic is broken” and more “these two tests are still exposing the raw constructor OOM on the new H100 x2 MCDM lane.” I could not do a full local repro on my side because this environment is missing |
leofang
commented
May 16, 2026
/ok to test 6a9c413 |
Uh oh!
There was an error while loading. Please reload this page.
|
Closes#2043.
Summary
_KEY_SCHEMA_VERSION = 2so pre-FIPS cache entries remain unreachable after the hash-family changeusedforsecurity=Falseat both hashlib call sites so the cache remains usable on FIPS-enforcing systemsTesting
cuda_coreprogram-cache tests in a repo-managed environment