Skip to content
Merged
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

- feat: add Gemma 4 multimodal chat support by @abetlen in #2241
- feat(ci): add CUDA 13.0 and 13.2 wheel builds by @abetlen in #2239
- feat(ci): add CUDA 11.8 wheel builds by @abetlen in #2238
- fix(ci): add Pascal compute capability targets to CUDA wheel builds by @abetlen in #2237
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,7 @@ Below are the supported multi-modal models and their respective chat handlers (P
| [llama-3-vision-alpha](https://huggingface.co/abetlen/llama-3-vision-alpha-gguf) | `Llama3VisionAlphaChatHandler` | `llama-3-vision-alpha` |
| [minicpm-v-2.6](https://huggingface.co/openbmb/MiniCPM-V-2_6-gguf) | `MiniCPMv26ChatHandler` | `minicpm-v-2.6` |
| [qwen2.5-vl](https://huggingface.co/unsloth/Qwen2.5-VL-3B-Instruct-GGUF) | `Qwen25VLChatHandler` | `qwen2.5-vl` |
| [gemma-4](https://huggingface.co/unsloth/gemma-4-E4B-it-GGUF) | `Gemma4ChatHandler` | `gemma4` |

Then you'll need to use a custom chat handler to load the clip model and process the chat messages and images.

Expand Down
44 changes: 44 additions & 0 deletions llama_cpp/llama_chat_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -3265,6 +3265,50 @@ def from_pretrained(
)


class Gemma4ChatHandler(Llava15ChatHandler):
DEFAULT_SYSTEM_MESSAGE = None

CHAT_FORMAT = (
"{% if messages and messages[0]['role'] == 'system' %}"
"{% if messages[0]['content'] is string %}"
"{% set first_user_prefix = messages[0]['content'] + '\n\n' %}"
"{% else %}"
"{% set first_user_prefix = messages[0]['content'][0]['text'] + '\n\n' %}"
"{% endif %}"
"{% set loop_messages = messages[1:] %}"
"{% else %}"
"{% set first_user_prefix = '' %}"
"{% set loop_messages = messages %}"
"{% endif %}"
"{% for message in loop_messages %}"
"{% if (message['role'] == 'user') != (loop.index0 % 2 == 0) %}"
"{{ raise_exception('Conversation roles must alternate user/assistant/user/assistant/...') }}"
"{% endif %}"
"{% set role = 'model' if message['role'] == 'assistant' else message['role'] %}"
"{{ '<start_of_turn>' + role + '\n' + (first_user_prefix if loop.first else '') }}"
"{% if message['content'] is string %}"
"{{ message['content'] | trim }}"
"{% elif message['content'] is iterable %}"
"{% for item in message['content'] %}"
"{% if item['type'] == 'image_url' and item['image_url'] is string %}"
"{{ '\n\n' + item['image_url'] + '\n\n' }}"
"{% elif item['type'] == 'image_url' and item['image_url'] is mapping %}"
"{{ '\n\n' + item['image_url']['url'] + '\n\n' }}"
"{% elif item['type'] == 'text' %}"
"{{ item['text'] | trim }}"
"{% endif %}"
"{% endfor %}"
"{% else %}"
"{{ raise_exception('Invalid content type') }}"
"{% endif %}"
"{{ '<end_of_turn>\n' }}"
"{% endfor %}"
"{% if add_generation_prompt %}"
"{{ '<start_of_turn>model\n' }}"
"{% endif %}"
)


class ObsidianChatHandler(Llava15ChatHandler):
# Prompt Format
# The model followed ChatML format. However, with ### as the separator
Expand Down
14 changes: 14 additions & 0 deletions llama_cpp/server/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,20 @@ def load_llama_from_model_settings(settings: ModelSettings) -> llama_cpp.Llama:
chat_handler = llama_cpp.llama_chat_format.Llava16ChatHandler(
clip_model_path=settings.clip_model_path, verbose=settings.verbose
)
elif settings.chat_format == "gemma4":
assert settings.clip_model_path is not None, "clip model not found"
if settings.hf_model_repo_id is not None:
chat_handler = (
llama_cpp.llama_chat_format.Gemma4ChatHandler.from_pretrained(
repo_id=settings.hf_model_repo_id,
filename=settings.clip_model_path,
verbose=settings.verbose,
)
)
else:
chat_handler = llama_cpp.llama_chat_format.Gemma4ChatHandler(
clip_model_path=settings.clip_model_path, verbose=settings.verbose
)
elif settings.chat_format == "moondream":
assert settings.clip_model_path is not None, "clip model not found"
if settings.hf_model_repo_id is not None:
Expand Down
Loading