Skip to content

Commit e37c6b0

Browse files
authored
Merge pull request #21 from INTERACT-LLM/gemma
Implement Gemma (code at the time of submission for review)
2 parents 005ea13 + f61d2d7 commit e37c6b0

7 files changed

Lines changed: 241 additions & 26 deletions

File tree

configs/models.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ name = "llama3.1:8b"
1010
mlx = "mlx-community/meta-Llama-3.1-8B-Instruct-4bit"
1111
hf = "meta-llama/Llama-3.1-8B-Instruct"
1212

13+
[[models]]
14+
name = "gemma3:12b"
15+
hf = "google/gemma-3-12b-it"
16+
1317
[[models]]
1418
name = "mistral:7b"
1519
mlx = "mlx-community/Mistral-7B-Instruct-v0.3-4bit"
16-
hf = "mistralai/Mistral-7B-Instruct-v0.3"
20+
hf = "mistralai/Mistral-7B-Instruct-v0.3"

pyproject.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ dependencies = [
2121
"torch>=2.6.0",
2222
"tqdm>=4.67.1",
2323
"transformers>=4.48.2",
24+
"pillow>=11.1.0",
25+
"torchvision>=0.21.0",
2426
]
2527

2628
[build-system]
@@ -35,3 +37,6 @@ dev = [
3537
[tool.ruff]
3638
src = ["src"]
3739
lint.select = ["F", "E", "W", "I001"] # F for pyflakes, E + W for pycodestyle errors + warnings, #I001 for isort
40+
41+
[tool.uv.sources]
42+
transformers = { git = "https://github.com/huggingface/transformers", rev = "v4.49.0-Gemma-3" }

src/interact_llm/llm/hf_gemma.py

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
"""
2+
HF wrapper for Gemma
3+
"""
4+
5+
from pathlib import Path
6+
from typing import Optional
7+
8+
import torch
9+
from transformers import AutoProcessor, Gemma3ForConditionalGeneration
10+
11+
from interact_llm.data_models.chat import ChatMessage
12+
13+
14+
class ChatHFGemma:
15+
"""
16+
Model wrapper for loading and using a HuggingFace causal language model with HF's own libraries
17+
"""
18+
19+
def __init__(
20+
self,
21+
model_id: str = "google/gemma-3-12b-it",
22+
cache_dir: Optional[Path] = None,
23+
sampling_params: Optional[dict] = None,
24+
penalty_params: Optional[dict] = None,
25+
max_memory: Optional[dict] = {0: "48GB", 1: "48GB"} # specify the amount of GPUs and their vrams - Gemma needs this to be able to use 2 gpus (it is too slow on a single)
26+
):
27+
self.model_id = model_id
28+
self.cache_dir = cache_dir
29+
self.processor = None # multi-modal model does not have a tokenizer, but a processor
30+
self.model = None
31+
self.sampling_params = sampling_params
32+
self.penalty_params = penalty_params
33+
self.max_memory = max_memory
34+
35+
def load(self) -> None:
36+
"""
37+
Lazy-loading (loads model and tokenizer if not already loaded)
38+
"""
39+
if self.processor is None:
40+
self.processor = AutoProcessor.from_pretrained(
41+
self.model_id, cache_dir=self.cache_dir, use_fast=True
42+
)
43+
44+
if self.model is None:
45+
try:
46+
self.model = Gemma3ForConditionalGeneration.from_pretrained(
47+
self.model_id, device_map="auto", cache_dir=self.cache_dir,
48+
max_memory=self.max_memory
49+
).eval()
50+
except Exception as e:
51+
print(f"Failed to load model with max_memory={self.max_memory}. Error: {e}")
52+
print("Retrying without max_memory...")
53+
try:
54+
self.model = Gemma3ForConditionalGeneration.from_pretrained(
55+
self.model_id, device_map="auto", cache_dir=self.cache_dir
56+
).eval()
57+
except Exception as e:
58+
print(f"Model loading failed even without max_memory. Error: {e}")
59+
raise
60+
61+
62+
63+
def format_params(self):
64+
if self.sampling_params:
65+
# normalise "temp" to "temperature" (ensures you can pass temp to the model as this is how MLX/HF defines it)
66+
if "temp" in self.sampling_params:
67+
self.sampling_params["temperature"] = self.sampling_params.pop("temp")
68+
69+
kwargs = self.sampling_params
70+
else:
71+
kwargs = {}
72+
73+
if self.penalty_params:
74+
kwargs.update(self.penalty_params)
75+
76+
return kwargs
77+
78+
def format_chat_for_gemma(self, chat: list[ChatMessage]) -> list[dict]:
79+
formatted_chat = []
80+
81+
for msg in chat.messages:
82+
formatted_chat.append({
83+
"role": msg.role,
84+
"content": [{"type": "text", "text": msg.content}]
85+
})
86+
87+
return formatted_chat
88+
89+
def generate(self, chat: list[ChatMessage], max_new_tokens: int = 3000):
90+
kwargs = self.format_params()
91+
92+
if len(kwargs) > 0:
93+
do_sample = True
94+
else:
95+
do_sample = False
96+
print(
97+
"[INFO:] No sampling parameters nor penalty parameters were passed. Setting do_sample to 'False'"
98+
)
99+
100+
self.processor.use_default_system_prompt = False # ensure no system prompt is there
101+
102+
formatted_chat = self.format_chat_for_gemma(chat)
103+
104+
model_inputs = self.processor.apply_chat_template(
105+
formatted_chat,
106+
tokenize=True,
107+
return_dict=True,
108+
add_generation_prompt=True,
109+
return_tensors = "pt",
110+
# params to fix flash attn error: https://github.com/google-deepmind/gemma/issues/169
111+
padding="longest",
112+
pad_to_multiple_of=8,
113+
).to(self.model.device, dtype=torch.bfloat16)
114+
115+
# fix flash attn error: https://github.com/google-deepmind/gemma/issues/169
116+
self.processor.tokenizer.padding_side = "left"
117+
118+
input_len = model_inputs["input_ids"].shape[-1]
119+
120+
with torch.inference_mode():
121+
output = self.model.generate(
122+
**model_inputs,
123+
124+
max_new_tokens=max_new_tokens,
125+
do_sample=do_sample,
126+
**kwargs,
127+
)
128+
129+
# chat (decoded output)
130+
response = self.processor.decode(output[0][input_len:], skip_special_tokens=True)
131+
132+
chat_message = ChatMessage(role="assistant", content=response)
133+
134+
return chat_message

src/interact_llm/utils/model_load.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from interact_llm.llm.hf_wrapper import ChatHF
1111
from interact_llm.llm.mlx_wrapper import ChatMLX
12+
from interact_llm.llm.hf_gemma import ChatHFGemma
1213

1314

1415
def get_model_id(
@@ -72,7 +73,7 @@ def load_model_backend(
7273
token_path: Path = Path(__file__).parents[3] / "tokens" / "hf_token.txt",
7374
cache_dir: Optional[Path] = None,
7475
**model_kwargs,
75-
) -> ChatHF | ChatMLX:
76+
) -> ChatHF | ChatMLX | ChatHFGemma:
7677
"""
7778
Loads a model based on the specified backend ("mlx" or "hf"). Will try to login to HF
7879
@@ -84,17 +85,25 @@ def load_model_backend(
8485
(e.g., sampling params, see documentation for ChatHF or ChatMLX)
8586
8687
Returns:
87-
ChatHF | ChatMLX: The loaded model object.
88+
ChatHF | ChatMLX | ChatGemma: The loaded model object.
8889
"""
8990
model_id = get_model_id(
9091
models_config_path=models_config_path, model_name=model_name, backend=backend
9192
)
9293

93-
# instantiate model based on backend
94-
if backend == "mlx":
95-
model = ChatMLX(model_id=model_id, **model_kwargs)
96-
elif backend == "hf":
97-
model = ChatHF(model_id=model_id, cache_dir=cache_dir, **model_kwargs)
94+
if "gemma" in model_name:
95+
if backend == "mlx":
96+
raise ValueError("Model is not supported in mlx yet")
97+
# Gemma should be returned immediately if `backend == "hf"`
98+
model = ChatHFGemma(model_id=model_id, cache_dir=cache_dir, **model_kwargs)
99+
else:
100+
# instantiate model based on backend
101+
if backend == "mlx":
102+
model = ChatMLX(model_id=model_id, **model_kwargs)
103+
elif backend == "hf":
104+
model = ChatHF(model_id=model_id, cache_dir=cache_dir, **model_kwargs)
105+
else:
106+
raise ValueError(f"Unsupported backend: {backend}")
98107

99108
try:
100109
model.load()
@@ -110,6 +119,6 @@ def load_model_backend(
110119
print(f"Model {model_name} loaded successfully using {backend} backend (model_id = {model_id})")
111120
else:
112121
print(f"Unexpected error occurred: {e}")
113-
raise #rReraise other unexpected errors
122+
raise # Reraise other unexpected errors
114123

115-
return model
124+
return model

src/scripts/simulate.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
"""
22
Simulate two chat LLMs talking to each other
3-
4-
Model tested currently:
5-
- mlx-community/Qwen2.5-7B-Instruct-1M-4bit
6-
- mlx-community/meta-Llama-3.1-8B-Instruct-4bit
7-
83
"""
94

105
import argparse

src/scripts/simulate.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/bin/bash
22

3-
models=("qwen2.5:7b" "llama3.1:8b" "mistral:7b")
3+
models=("qwen2.5:7b" "llama3.1:8b" "mistral:7b" "gemma3:12b")
44
prompt_ids=("A1" "B1" "C1")
55
backend="hf"
66

0 commit comments

Comments
 (0)