Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add llamafile-convert command #112

Merged
merged 3 commits into from
Jan 1, 2024
Merged

Conversation

chand1012
Copy link
Contributor

This adds a simple command for converting GGUF files and URLs to GGUF files to llamafiles quickly and with low friction. Usage as follows:

llamafile-convert <gguf path or url> [cli|server|both]

If the second argument is not specified, both is assumed. Tested on macOS and Linux, not tested on Windows.

@jart jart self-assigned this Dec 28, 2023
Copy link
Collaborator

@jart jart left a comment

Choose a reason for hiding this comment

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

Thanks for sending this Chandler! I notice this script has already gotten four upvotes. So I think the convenient interface you've designed is worth having. I've left a lot of comments, with the intention of providing you with the best advice possible for a program that's destined to be installed on a large number of machines.

#!/bin/bash
FILE=$1
TYPE=${2:-both}
SCRIPTNAME=$(basename $0)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Here's the POSIX builtin syntax for doing this:

SCRIPTNAME=${0##*/}

@@ -0,0 +1,90 @@
#!/bin/bash
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please use /bin/sh. If you're using Linux then most systems should have that configured to be the Almquist Shell, which is a minimal POSIX compliant shell that's ultra fast.

fi

# if the file starts with http
if [[ $FILE == http* ]]; then
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think glob equals is a bash extension. The POSIX way to do this would be:

if [ x"$FILE" != x"${FILE#http*}" ]; then  # FILE.startswith("http")

Which tests that $FILE is not equal to itself stripped of the http prefix.

Even though spaces technically aren't allowed in URLs, it's safer in general to have the quotes here, since it's user input. Many would view that as good from a security standpoint too.

# get the filename
FILENAME=$(echo $FILE | sed 's/.*\///g')
echo "Downloading $FILENAME"
wget -O $FILENAME $FILE
Copy link
Collaborator

Choose a reason for hiding this comment

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

Some stock systems don't have wget but have curl. With other systems, it's the other way around. I recommend copying and pasting the downloader detection code I wrote in this script:

if WGET=$(command -v wget 2>/dev/null); then
DOWNLOAD=$WGET
DOWNLOAD_ARGS=-O
elif CURL=$(command -v curl 2>/dev/null); then
DOWNLOAD=$CURL
DOWNLOAD_ARGS=-fLo
else
printf '%s\n' "$0: fatal error: you need to install either wget or curl" >&2
printf '%s\n' "please download https://cosmo.zip/pub/cosmos/bin/wget and put it on the system path" >&2
abort
fi

You can then say:

"${DOWNLOAD}" ${DOWNLOAD_ARGS} "$FILENAME" "$FILE"

FILE=$(echo $FILE | sed 's/?download=true//g')
# get the filename
FILENAME=$(echo $FILE | sed 's/.*\///g')
echo "Downloading $FILENAME"
Copy link
Collaborator

Choose a reason for hiding this comment

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

This line should end with >&2 so it gets printed to standard error.

LLAMAFILE_NAME=$(echo $FILE | sed 's/.gguf/.llamafile/g')
# replace .gguf with -server.llamafile
LLAMAFILE_SERVER_NAME=$(echo $FILE | sed 's/.gguf/-server.llamafile/g')
LLAMAFILE_PATH=$(which llamafile)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Please use command -v instead of which because the prior is a POSIX-compliant shell builtin that's faster.

LLAMAFILE_SERVER_NAME=$(echo $FILE | sed 's/.gguf/-server.llamafile/g')
LLAMAFILE_PATH=$(which llamafile)
LLAMAFILE_SERVER_PATH=$(which llamafile-server)
CLI_ARGS=$(cat <<EOF
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can do this using builtins as follows:

CLI_ARGS="-m
$FILE
...
"

Then, rather than echo'ing it later on, do this:

printf %s "$CLI_ARGS" >.args


cleanup() {
echo "Cleaning up"
# remove .args
Copy link
Collaborator

Choose a reason for hiding this comment

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

This comment doesn't add value. Comments should be used to explain things that aren't obvious from reading the code.

cleanup() {
echo "Cleaning up"
# remove .args
rm .args
Copy link
Collaborator

Choose a reason for hiding this comment

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

Some environments theoretically map rm to rm -i so it asks an interactive question before destroying data. To work around that, it's recommended that rm -f be favored in scripts.

case $TYPE in
cli)
echo "Building CLI llamafile"
convert_to_cli
Copy link
Collaborator

Choose a reason for hiding this comment

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

It's important to check the return code of processes launched by the script. For example, here, you could say convert_to_cli || abort where abort could be defined as a shell function similar to my script here:

abort() {
printf '%s\n' "download terminated." >&2
exit 1
}

@chand1012 chand1012 requested a review from jart January 1, 2024 22:45
Copy link
Collaborator

@jart jart left a comment

Choose a reason for hiding this comment

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

Thanks again Chandler. Quick drive by review.

SCRIPTNAME=${0##*/}

if [ -z "$FILE" ]; then
echo "Usage: $SCRIPTNAME <gguf file or url> [cli|server|both]"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you remove the [cli|server|both] argument? The llamafile and llamafile-server commands are now unified at head. It's no longer needed.

@chand1012 chand1012 requested a review from jart January 1, 2024 23:16
@chand1012
Copy link
Contributor Author

New usage as follows.

llamafile-convert <gguf path or url>

Copy link
Collaborator

@jart jart left a comment

Choose a reason for hiding this comment

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

Looks good. Thank you.

One thing you might want to do, is have an optional [-o OUTPUT] flag for overriding the generated pathname. If you want to code it, feel free to do so in a follow-up PR.

@jart jart merged commit 1dcf274 into Mozilla-Ocho:main Jan 1, 2024
@stlhood
Copy link
Collaborator

stlhood commented Jan 2, 2024

Thank you @chand1012 for this very useful contribution!

@jart jart mentioned this pull request Jan 2, 2024
@chand1012 chand1012 deleted the chand1012/convert branch January 3, 2024 14:51
@ProtoBelisarius
Copy link

Is there a way to use convert for multimodal models like llava? Because those not only need the .gguf but also the mmproj model. How do I include both models? ShareGPT4V is better than llava and works the same with llama.cpp so it should work.
This is an example command to run it with llama.cpp if you dont believe me:
./server -m models/ShareGPT4V-7B_Q5_K_M.gguf -t 6 -c 4096 --mmproj models/mmproj-model-f16.gguf

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

Successfully merging this pull request may close these issues.

4 participants