Views
No views yet
Qrzysztof/functiongemma-270m-it-prepaid-cards-v2
(SafeTensors), a google/functiongemma-270m-it fine-tune that buys prepaid
cards, checks balances, and lists transactions via tool calls in 107 languages
with realistic noisy/multi-turn input.| File | Size | Description |
|---|---|---|
model-f16.gguf | 551 MB | Full-precision weights |
model-Q8_0.gguf | 300 MB | 8-bit quantization — low-end hardware (see below) |
1# CLI (llama.cpp)
2llama-cli -m model-Q8_0.gguf -cnv -p "I want to buy a $20 card"1# llama-cpp-python — feed the FunctionGemma-rendered prompt
2from llama_cpp import Llama
3
4llm = Llama(model_path="model-Q8_0.gguf", n_ctx=4096, n_gpu_layers=0, verbose=False)
5prompt = "<bos><start_of_turn>developer\nYou are a model that can do function calling with the following functions<start_function_declaration>..." # tokenizer.apply_chat_template(messages, tools=tools, add_generation_prompt=True)
6print(llm(prompt, max_tokens=96, temperature=0.0)["choices"][0]["text"])
7# <start_function_call>call:purchase_card{"amount": 20, "card_type": "digital_prepaid_visa", ...}<end_function_call>...llama-cli (master) had a REPL quirk when passing prompts with -p/-f
at the time of writing; llama-cpp-python (same backend) is the tested path.1# 1. conversion (llama.cpp convert_hf_to_gguf.py; needs the llama.cpp repo layout)
2python3 convert_hf_to_gguf.py <hf_model_dir> --outfile model-f16.gguf --outtype f16
3# 2. 8-bit quantization
4llama-quantize model-f16.gguf model-Q8_0.gguf Q8_0convert_hf_to_gguf.py asserts
max(vocab) < vocab_size and fails. Fix: in a conversion copy, drop the
*_token / model_specific_special_tokens keys from tokenizer_config.json
and prune added tokens with id ≥ 262144 from tokenizer.json → exactly
262,144 tokens, identical text behavior.| Format | Success rate |
|---|---|
| SafeTensors (reference) | 40/40 = 100% |
| GGUF f16 | TODO |
| GGUF Q8_0 | 40/40 = 100% |
llama-quantize model-f16.gguf model-Q4_K_M.gguf Q4_K_M
(≈170 MB) trades a bit more accuracy.google/functiongemma-270m-it directly).1pip install torch transformers trl datasets accelerate
2huggingface-cli login # accept the gemma license for google/functiongemma-270m-itmessages/tools as JSON strings (Arrow cannot infer
the nested schema), and TRL's SFTTrainer needs a uniform struct schema, so
normalize first:1import json
2from datasets import load_dataset
3from transformers import AutoModelForCausalLM, AutoTokenizer
4
5def normalize_messages(msgs):
6 out = []
7 for m in msgs:
8 n = {"role": m["role"], "content": m.get("content") or "", "name": None,
9 "tool_call_id": m.get("tool_call_id"), "tool_calls": None}
10 if m["role"] == "tool":
11 n["name"] = m["content"]["name"]
12 n["content"] = json.dumps(m["content"]["response"], ensure_ascii=False)
13 if m.get("tool_calls"):
14 n["tool_calls"] = [{"id": tc.get("id"), "type": tc.get("type", "function"),
15 "function": {"name": tc["function"]["name"],
16 "arguments": json.dumps(tc["function"]["arguments"], ensure_ascii=False)}}
17 for tc in m["tool_calls"]]
18 out.append(n)
19 return out
20
21def rows_to_dataset(rows):
22 from datasets import Dataset
23 return Dataset.from_list([{
24 "messages": normalize_messages(r["messages"]),
25 "tools": json.dumps(r["tools"], ensure_ascii=False),
26 } for r in rows])
27
28ds = load_dataset("Qrzysztof/ecommerce-chat-tool-calling", token=HF_TOKEN)["train"]
29train_rows = [{"messages": json.loads(r["messages_json"]), "tools": json.loads(r["tools_json"])}
30 for r in ds if r["split"] == "train"]
31train_ds = rows_to_dataset(train_rows)1import torch
2from transformers import AutoModelForCausalLM
3from trl import SFTConfig, SFTTrainer
4
5model = AutoModelForCausalLM.from_pretrained("google/functiongemma-270m-it",
6 dtype=torch.bfloat16, attn_implementation="eager")
7tokenizer = AutoTokenizer.from_pretrained("google/functiongemma-270m-it")
8
9trainer = SFTTrainer(
10 model=model,
11 args=SFTConfig(
12 output_dir="functiongemma-ecommerce",
13 max_length=1024, # covers the longest sample + margin
14 packing=False, # keep tool calls intact (no cross-sample packing)
15 num_train_epochs=3,
16 per_device_train_batch_size=8,
17 learning_rate=5e-5,
18 lr_scheduler_type="constant",
19 warmup_steps=50,
20 bf16=True, # or fp16 on non-Ampere GPUs
21 eval_strategy="epoch",
22 report_to="none",
23 ),
24 train_dataset=train_ds,
25 processing_class=tokenizer,
26)
27trainer.train()tools
column; assistant_only_loss=True (default) masks everything but the model's
own turns, so it learns to emit tool calls — not to copy the schema.1ok = 0
2for item in test_rows:
3 inputs = tokenizer.apply_chat_template(item["messages"][:-1], tools=item["tools"],
4 add_generation_prompt=True, return_tensors="pt")
5 out = model.generate(**inputs, max_new_tokens=256)
6 output = tokenizer.decode(out[0][len(inputs["input_ids"][0]):], skip_special_tokens=False)
7 expected = <expected tool name / args from expected_json>
8 ok += expected-tool-in-output and no-other-tool-in-outputtrainer.push_to_hub("YOUR_USER/functiongemma-ecommerce")noise.py engine skips any token containing digits.template_id) and hold out
whole languages + (for the e-commerce set) whole schemas — that is the
only honest way to measure generalization.packing=False for tool-calling data; packed sequences splice mid-call.max_length ≥ longest sample + a margin; ~1024 covers these datasets....-v2 (SafeTensors)...-tool-calling-v2