Skip to content
This repository was archived by the owner on Apr 23, 2026. It is now read-only.

adds early-out for model type checking based on path type - #1791

Closed
JamesKunstle wants to merge 1 commit into
instructlab:mainfrom
JamesKunstle:fix-identify-backends
Closed

adds early-out for model type checking based on path type#1791
JamesKunstle wants to merge 1 commit into
instructlab:mainfrom
JamesKunstle:fix-identify-backends

Conversation

@JamesKunstle

Copy link
Copy Markdown
Contributor

is_model_safetensors and is_model_gguf were raising exceptions rather than returning False for common file-based failure cases.

@JamesKunstle
JamesKunstle requested review from jaideepr97 and leseb and removed request for leseb July 18, 2024 21:36
@mergify mergify Bot added the ci-failure PR has at least one CI failure label Jul 18, 2024
@JamesKunstle
JamesKunstle force-pushed the fix-identify-backends branch from 4a277c7 to 7be48a9 Compare July 18, 2024 21:44
@mergify mergify Bot added ci-failure PR has at least one CI failure and removed ci-failure PR has at least one CI failure labels Jul 18, 2024
requires_files = {
"config.json",
"tokenizer.json",
"tokenizer.model",

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

removing this causes test to break because this is the only file that the test checks for.

@JamesKunstle
JamesKunstle force-pushed the fix-identify-backends branch from 7be48a9 to aeac457 Compare July 18, 2024 22:33
@mergify mergify Bot removed the ci-failure PR has at least one CI failure label Jul 18, 2024
@JamesKunstle
JamesKunstle requested review from russellb and tiran July 18, 2024 22:35
leseb
leseb previously requested changes Jul 19, 2024
Comment thread src/instructlab/model/backends/backends.py Outdated
Comment thread src/instructlab/model/backends/backends.py Outdated

if not model_path.is_file():
logging.debug(
"The path to the model %s is not a file, and therefore cannot be a model in .gguf format.",

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.

Also the repo tends to do: logger.debug(f"Message: {var}") for formatting.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Linter wants me to use lazy logging via %s rather than f-strings

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.

lazy linter! let's stay consistent :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Okay no problem, just changed over. In the future we may want to standardize on lazy logging instead- I looked into it a bit and it seems to be a bit faster.

Comment thread src/instructlab/model/backends/backends.py Outdated

@jaideepr97 jaideepr97 left a comment

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.

+1 to seb and ali's comments

@leseb leseb left a comment

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.

EOD for me here, assuming my last comment is addressed (change to use logger.debug(f"Message: {var}"), feel free to dismiss my review and merge. Thanks!

@ktam3 ktam3 added this to the 0.18.0 milestone Jul 19, 2024
@JamesKunstle
JamesKunstle force-pushed the fix-identify-backends branch from 6eb25d4 to 35163b5 Compare July 19, 2024 17:17
@JamesKunstle
JamesKunstle requested a review from leseb July 19, 2024 17:18
@nathan-weinberg

Copy link
Copy Markdown
Contributor

Funny, these changes are very similar to something I was dealing with in #1795

Returns:
bool: True if the model is a safetensors model, False otherwise.
"""
if not model_path.is_dir():

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.

Just as a note - if a Path type isn't passed here, this still throw an exception as is_dir() is a method of the Path class: https://kodify.net/python/check-path-is-directory/#use-pathis_dir-or-ospathisdir

Not blocking, but you may also consider os.path.isdir() - the differences in behavior are outlined in that link above.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Looks good, though this will throw an exception if that casting fails

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.

Wouldn't NotADirectoryError handler catch the situation? Why do we need to handle this explicitly here, again?

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.

With my suggestion above, we ensure a Path type is being passed, so agreed with @booxter here

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think you're right @booxter. Explicitly checking if a path is a directory above is redundant since we catch an informative exception. I'll make a change reflective of that.

# Third Party
from gguf.constants import GGUF_MAGIC

if not model_path.is_file():

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.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Same comment as above

@nathan-weinberg nathan-weinberg Jul 19, 2024

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.

Same code snippet suggestion here, given the above conversation

if not isinstance(model_path, pathlib.Path):
    raise TypeError("'model_path' must be of type 'pathlib.Path')

@nathan-weinberg nathan-weinberg Jul 19, 2024

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.

Then below, can do something similar to the safetensors func

try:
    with open(model_path, "rb") as f:
        # Memory-map the file on the first 4 bytes (this is where the magic number is)
        mmapped_file = mmap.mmap(f.fileno(), length=4, access=mmap.ACCESS_READ)
        # Read the first 4 bytes
        first_four_bytes = mmapped_file.read(4)
        # Convert the first four bytes to an integer
        first_four_bytes_int = int(struct.unpack("<I", first_four_bytes)[0])
        # Close the memory-mapped file
        mmapped_file.close()
        return first_four_bytes_int == GGUF_MAGIC
except (FileNotFoundError, PermissionError) as e:
    logger.debug("Failed to read file: %s", e)
    return False

@booxter let me know your thoughts here as well ^^

@JamesKunstle

JamesKunstle commented Jul 19, 2024

Copy link
Copy Markdown
Contributor Author

@nathan-weinberg Very good point. Since the function definition requires a pathlib.Path object, we could:

  1. cast model_path to Path if it's str by checking isinstance(model_path, str)
  2. assert the model_path type (not something I assume we'd want to do)

I'm going to do the first thing just in case.

is_model_safetensors and is_model_gguf were raising exceptions rather than returning False for common failure cases.

Signed-off-by: James Kunstle <jkunstle@redhat.com>
@JamesKunstle
JamesKunstle force-pushed the fix-identify-backends branch from 35163b5 to fa9f21e Compare July 19, 2024 19:05
@JamesKunstle

Copy link
Copy Markdown
Contributor Author

@nathan-weinberg would you mind please dismissing requested changes- I made the requested change.

@nathan-weinberg

Copy link
Copy Markdown
Contributor

@nathan-weinberg would you mind please dismissing requested changes- I made the requested change.

Those aren't mine, they're @leseb

@RobotSail RobotSail left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@mergify mergify Bot added the one-approval PR has one approval from a maintainer label Jul 19, 2024
@JamesKunstle

Copy link
Copy Markdown
Contributor Author

@nathan-weinberg He OK'ed dismissing if I updated the changes, I don't want to dismiss them myself as bad practice

@nathan-weinberg
nathan-weinberg dismissed leseb’s stale review July 19, 2024 19:52

Comments were addressed


# guards against users passing str-type paths if not
# statically analyzing.
if isinstance(model_path, str):

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.

I disagree with the premise of this change - assuming callers may pass something but a Path. Instead of adding ad-hoc conversion code like proposed here, the callers should be forced to pass the expected types, if not already. The latter can also be enforced by adding more typing checks up the call stack.

Otherwise, why not handling model_path being a None, or some other type? This is a slippery slope.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Definitely see your point. We can drop this if it's truly a bad route. It's a modest degree of leniency in this codebase because there's some heterogeneity on path handling- some modules might want to call this method that use str's representing paths. Since those representations are very close, I'm inclined to massage the input into a format that this function wants. Otherwise, if it's not something that's reasonably handle-able, it's rejected because it's NotADirectory.

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.

Elsewhere, we were ripping off some of these "special handling for string paths", so I'd like to avoid this push-pull. The strategic direction I think is to convert all code that attempts to pass strings where Paths are expected to actually pass Paths. (Same for other types.) These are code bugs, so they should be fixed in-place, not worked around.

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.

I propose the following snippet

if not isinstance(model_path, pathlib.Path):
    raise TypeError("'model_path' must be of type 'pathlib.Path')

This remains in the spirit of my comment, James's implementation, and Ihar's feedback.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I thought a bit about this offline- I think @booxter's route of trusting the function definition (that the input is a pathlib.Path) is the right way to go, otherwise we'd be writing input type-validation code for every function. We're using type annotations so anyone who passes a str to this method should be warned by their development environment that this isn't a good way to do things.

Returns:
bool: True if the model is a safetensors model, False otherwise.
"""
if not model_path.is_dir():

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.

Wouldn't NotADirectoryError handler catch the situation? Why do we need to handle this explicitly here, again?

@JamesKunstle

Copy link
Copy Markdown
Contributor Author

@booxter's feedback is solid, doing what I'm suggesting here wouldn't really help. I'll close this PR and we can reopen if there are objections.

logger.debug("'model_path' was passed as 'str'. Casting to pathlib.Path")
model_path = pathlib.Path(model_path)

if not model_path.is_file():

@booxter booxter Jul 20, 2024

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.

@JamesKunstle I think this change may actually be useful, since right now if a directory is passed, the function will raise an exception (on open). The rest of changes here, as you confirmed, are probably not a good idea. (Thanks.)


An alternative to is_file check could be catching misc exceptions expected from open, and returning False on any of them.

@ktam3 ktam3 modified the milestones: 0.18.0, 0.18.0a4 Jul 22, 2024
@JamesKunstle
JamesKunstle deleted the fix-identify-backends branch August 3, 2024 00:32
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

one-approval PR has one approval from a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants