Skip to content

[fix](cli): do not compare a git SHA1 against a local SHA256 when verifying - #2142

Open
adityasingh2400 wants to merge 1 commit into
kvcache-ai:mainfrom
adityasingh2400:fix/hf-verify-non-lfs-blob-id
Open

[fix](cli): do not compare a git SHA1 against a local SHA256 when verifying#2142
adityasingh2400 wants to merge 1 commit into
kvcache-ai:mainfrom
adityasingh2400:fix/hf-verify-non-lfs-blob-id

Conversation

@adityasingh2400

Copy link
Copy Markdown

Related to #2100.

Summary

_fetch_from_huggingface builds the map of official digests that model verification compares against, but for any file without an lfs entry it falls back to blob_id:

result = {}
for file_info in paths_info:
    if hasattr(file_info, "lfs") and file_info.lfs is not None:
        sha256 = file_info.lfs.sha256
    else:
        sha256 = getattr(file_info, "blob_id", None)   # git SHA1, not SHA256
    result[file_info.path] = sha256

huggingface_hub populates RepoFile.blob_id from the Hub's oid, and for a plain git blob that oid is the git SHA1 of the object, 40 hex characters. The map is then compared against hashlib.sha256 output, 64 hex characters, so a non-LFS file can never match.

Every HF model repo keeps config.json, generation_config.json, tokenizer_config.json, vocab.json and any *.py as plain git blobs rather than LFS objects, so those files fail verification unconditionally wherever they are locally hashed.

Where it bites

Two paths hash .json and .py locally and then compare:

  • verify_model_integrity collects *.safetensors, *.json and *.py into local_files and passes them all to calculate_local_sha256, then compares every entry.
  • The kt model verify re-verify path in cli/commands/model.py builds files_to_hash from the same three patterns and passes it as files_list, so the .json and .py files are hashed and compared.

On that second path the user is offered a repair download, the files are re-fetched, re-verified, and mismatch again, because the mismatch is a units error rather than actual corruption. Answering yes cannot terminate.

On the very first kt model verify run the same files instead report (missing), because calculate_local_sha256 is called with files_list=None and falls back to the *.safetensors glob, so there is no local digest to compare at all. That is the (missing) half of #2100 and it is what #2104 addresses. Worth flagging: once #2104 makes the local scan cover .json and .py, this bug stops hiding behind (missing) and turns into a hard (hash mismatch) on those files. The two changes are complementary, and they do not overlap. #2104 touches model_verifier.py at lines 8, 33, 268, 808 and 825, while _fetch_from_huggingface sits at 330 to 390 and is untouched.

Fix

Return only digests that really are SHA256 and skip the rest, so those files are left unverified rather than reported as corrupt. This is confined to the one loop.

The sibling _fetch_from_modelscope returns a genuine SHA256 for every file, LFS or not, so the ModelScope path is unaffected and stays fully verified.

Verification

Confirmed against the live Hub before writing the fix, using this repo's own function on hf-internal-testing/tiny-random-gpt2, downloading each file and computing both digests locally:

file                     value returned by _fetch_from_huggingface   len
model.safetensors        8111d5afb0715dbf...20b4e500                  64   (lfs.sha256)
config.json              4ff64fe5192d88c0b5dbfc578c775c0ce05dd7d0     40   (blob_id)
tokenizer.json           980677bff66333505f8bea2719064a6e43f95314     40   (blob_id)
tokenizer_config.json    c92208ce39324aadabc239d73dd9bb3ec1cd45ca     40   (blob_id)
special_tokens_map.json  817762d631ad6f9c799f6b9dc713c46420e65546     40   (blob_id)
vocab.json               64bcba2cd1b7220fc0494c83f4effe8a6448b36c     40   (blob_id)

config.json
  returned by the code : 4ff64fe5192d88c0b5dbfc578c775c0ce05dd7d0
  local sha256         : 9ad49cd2ff58daa38243e4eeaac13b1259786d9418e45bfc01a1fd1fe1089e77
  git blob sha1        : 4ff64fe5192d88c0b5dbfc578c775c0ce05dd7d0
  equals local sha256 ? False        equals git blob sha1 ? True

Five of the six files can never pass. Each non-LFS value matched the git blob SHA1 of the same bytes exactly, which is what identifies the digest being returned. model.safetensors, the only LFS object, matched its local SHA256.

Test

kt-kernel/test/per_commit/test_model_verifier_hf_hashes.py, registered via register_cpu_ci(est_time=0.1, suite="default"). It follows the test_port_checker.py pattern of loading the module by file path with importlib, so it pulls in no torch and no network. huggingface_hub is stubbed in sys.modules because the real import is lazy and happens inside the function. The stub RepoFile mirrors the real shape, an lfs.sha256 for the weight file and a blob_id only for the rest, using the real digests observed above.

Three assertions: every returned digest is 64 characters, the LFS file keeps its SHA256, and the non-LFS files are absent rather than present with a git SHA1.

Verified against the base ref rather than by stashing:

# fix present
$ python -m unittest per_commit.test_model_verifier_hf_hashes
Ran 3 tests - OK

# source reverted to upstream/main, new test kept
$ git checkout upstream/main -- kt-kernel/python/cli/utils/model_verifier.py
$ python -m unittest per_commit.test_model_verifier_hf_hashes
Ran 3 tests - FAILED (failures=2)
AssertionError: 40 != 64 : config.json returned a 40-char digest, not a SHA256
AssertionError: 'config.json' unexpectedly found in {...'config.json': '4ff64fe5192d88c0b5dbfc578c775c0ce05dd7d0'...}

per_commit.test_port_checker still passes, as a check that the CI registration and module loading are intact.

Formatted with black at line-length 120 from kt-kernel/pyproject.toml. Note that black 26.x wants to reformat the existing test_port_checker.py too, so I matched the version the tree is actually formatted with, 24.10.0, under which both changed files and that sibling are all clean. Commit message follows the [type](scope): subject form the commit-msg hook enforces.

Possible follow-up

If losing hash coverage on config and tokenizer files is not acceptable, the alternative is to verify non-LFS files with the algorithm the Hub actually gives us, since the git blob SHA1 is cheap to reproduce locally as sha1(b"blob " + str(len(data)) + b"\0" + data). That needs the comparison site to know which algorithm applies per file, which reaches into the code #2104 is already refactoring, so I kept this change narrow. Happy to do it that way instead if you prefer.

…ifying

_fetch_from_huggingface falls back to file_info.blob_id when a file has no
lfs entry. huggingface_hub populates RepoFile.blob_id from the Hub's oid,
which for a plain git blob is the git SHA1 of the object, 40 hex chars. The
returned mapping is then compared against hashlib.sha256 output, 64 hex
chars, so any file that is not LFS-tracked can never match.

Every HF model repo keeps config.json, generation_config.json,
tokenizer_config.json, vocab.json and any *.py as plain git blobs, so those
files fail verification unconditionally wherever they are hashed locally.
verify_model_integrity does hash them, and so does the kt model verify
re-verify path, which then offers a repair download that cannot fix
anything because the comparison is between two different algorithms.

Return only digests that really are SHA256 and skip the rest, so those
files are left unverified instead of reported as corrupt. The sibling
ModelScope fetch returns a true SHA256 for every file and is unaffected.
@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown

Greptile Summary

The PR prevents Hugging Face Git blob SHA1 values from being compared with local SHA256 hashes.

  • Restricts the Hugging Face digest map to files exposing an LFS SHA256.
  • Adds isolated unit coverage confirming LFS hashes remain and non-LFS blob IDs are omitted.

Confidence Score: 5/5

The PR appears safe to merge because it removes guaranteed false mismatches and its deliberate reduction in non-LFS hash coverage is explicitly documented.

The changed fetch path now returns only genuine SHA256 values, and the tests directly cover both retained LFS hashes and omitted Git SHA1 blob identifiers; no unacknowledged actionable defect remains.

Important Files Changed

Filename Overview
kt-kernel/python/cli/utils/model_verifier.py Filters out non-LFS Hugging Face metadata rather than treating Git blob SHA1 identifiers as SHA256 digests; the resulting reduction in verification coverage is explicitly acknowledged.
kt-kernel/test/per_commit/test_model_verifier_hf_hashes.py Adds stub-based regression tests covering retained LFS SHA256 values and omitted non-LFS Git blob IDs.

Reviews (1): Last reviewed commit: "[fix](cli): do not compare a git SHA1 ag..." | Re-trigger Greptile

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant