--- language: - en - es - fr - de - it - pt - nl - pl - ru - uk - tr - ar - he - hi - id - vi - th - zh - ja - ko - sv - da - no - fi - cs - ro - hu - el - ca - fil - ms - bn - ta - fa - ur - sw - hr - sr - sk - bg - lt - lv - et - sl - is - ka - hy - az - kk - uz - mn - km - my - si - ne - gu - mr - te - kn - ml - pa - cy - ga - eu - gl - eo - af - ht - mi - sm - zu - xh - jv - su - ceb - yo - ig - ha - am - ku - ps - tg - ky - lo - dv - od - as - sd - rn - lg - ny - sn - st - tn - ts - mg - fj - to - haw - tk - tt - ba - ce - os - kmr - gn - qu license: gemma base_model: Qrzysztof/functiongemma-270m-it-prepaid-cards-v2 base_model_relation: quantized datasets: - Qrzysztof/functiongemma-prepaid-cards-tool-calling-v2 library_name: gguf pipeline_tag: text-generation tags: - function-calling - tool-calling - functiongemma - prepaid-cards - gguf - quantized model-index: - name: Qrzysztof/functiongemma-270m-it-prepaid-cards-v2-gguf results: - task: type: text-generation dataset: name: prepaid-cards-tool-calling-v2 (held-out test subset, N=40) type: Qrzysztof/functiongemma-prepaid-cards-tool-calling-v2 metrics: - name: Tool-call success rate (greedy, Q8_0) type: tool-call-success-rate value: 100 - name: Tool-call success rate (greedy, f16) type: tool-call-success-rate value: TODO --- # FunctionGemma 270M IT — Prepaid Cards Tool-Calling (v2, GGUF) ## Model description GGUF conversion of [`Qrzysztof/functiongemma-270m-it-prepaid-cards-v2`](https://huggingface.co/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. Runnable with [llama.cpp](https://github.com/ggml-org/llama.cpp) and any GGUF-compatible runtime (llama-cpp-python, LM Studio, Ollama, …). ## Files | 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) | ## How to use ```bash # CLI (llama.cpp) llama-cli -m model-Q8_0.gguf -cnv -p "I want to buy a $20 card" ``` ```python # llama-cpp-python — feed the FunctionGemma-rendered prompt from llama_cpp import Llama llm = Llama(model_path="model-Q8_0.gguf", n_ctx=4096, n_gpu_layers=0, verbose=False) prompt = "developer\nYou are a model that can do function calling with the following functions..." # tokenizer.apply_chat_template(messages, tools=tools, add_generation_prompt=True) print(llm(prompt, max_tokens=96, temperature=0.0)["choices"][0]["text"]) # call:purchase_card{"amount": 20, "card_type": "digital_prepaid_visa", ...}... ``` Note: `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. ## Intended uses & limitations Same as the parent model (see the [SafeTensors card](https://huggingface.co/Qrzysztof/functiongemma-270m-it-prepaid-cards-v2)): synthetic data, uneven language quality, no backend — plus the usual quantization caveats below. ## How it was made ```bash # 1. conversion (llama.cpp convert_hf_to_gguf.py; needs the llama.cpp repo layout) python3 convert_hf_to_gguf.py --outfile model-f16.gguf --outtype f16 # 2. 8-bit quantization llama-quantize model-f16.gguf model-Q8_0.gguf Q8_0 ``` **Gemma3 270M gotcha:** the HF tokenizer reports 262,146 tokens (two model-specific vision tokens added at load). `convert_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. ## Evaluation Same prompts & greedy decoding as the other formats, over the held-out v2 test subset (N=40). | Format | Success rate | |---|---| | SafeTensors (reference) | 40/40 = 100% | | GGUF f16 | TODO | | GGUF Q8_0 | **40/40 = 100%** | Q8_0 shows no measurable drop on tool-name selection in this subset; arg wording can differ slightly from f16 in rare cases. ## 8-bit / low-end hardware notes - Q8_0 file is 300 MB; runtime RSS ≈ 350–450 MB with a 4K context → runs on 2 GB-RAM devices; CPU-only inference works (slow but usable) on Raspberry-Pi-class hardware. - Want even smaller? `llama-quantize model-f16.gguf model-Q4_K_M.gguf Q4_K_M` (≈170 MB) trades a bit more accuracy. ## Fine-tuning from this model This model was fine-tuned with the tutorial below; you can use it as the starting point for a new tool set (or fine-tune `google/functiongemma-270m-it` directly). ## Fine-tuning tutorial A complete, minimal fine-tune of a FunctionGemma-class model on this data (follows the official [FunctionGemma fine-tuning guide](https://ai.google.dev/gemma/docs/functiongemma/finetuning-with-functiongemma)). ### 1. Setup ```bash pip install torch transformers trl datasets accelerate huggingface-cli login # accept the gemma license for google/functiongemma-270m-it ``` ### 2. Load the dataset and normalize messages The Hub dataset stores `messages`/`tools` as JSON strings (Arrow cannot infer the nested schema), and TRL's `SFTTrainer` needs a uniform struct schema, so normalize first: ```python import json from datasets import load_dataset from transformers import AutoModelForCausalLM, AutoTokenizer def normalize_messages(msgs): out = [] for m in msgs: n = {"role": m["role"], "content": m.get("content") or "", "name": None, "tool_call_id": m.get("tool_call_id"), "tool_calls": None} if m["role"] == "tool": n["name"] = m["content"]["name"] n["content"] = json.dumps(m["content"]["response"], ensure_ascii=False) if m.get("tool_calls"): n["tool_calls"] = [{"id": tc.get("id"), "type": tc.get("type", "function"), "function": {"name": tc["function"]["name"], "arguments": json.dumps(tc["function"]["arguments"], ensure_ascii=False)}} for tc in m["tool_calls"]] out.append(n) return out def rows_to_dataset(rows): from datasets import Dataset return Dataset.from_list([{ "messages": normalize_messages(r["messages"]), "tools": json.dumps(r["tools"], ensure_ascii=False), } for r in rows]) ds = load_dataset("Qrzysztof/ecommerce-chat-tool-calling", token=HF_TOKEN)["train"] train_rows = [{"messages": json.loads(r["messages_json"]), "tools": json.loads(r["tools_json"])} for r in ds if r["split"] == "train"] train_ds = rows_to_dataset(train_rows) ``` ### 3. Train ```python import torch from transformers import AutoModelForCausalLM from trl import SFTConfig, SFTTrainer model = AutoModelForCausalLM.from_pretrained("google/functiongemma-270m-it", dtype=torch.bfloat16, attn_implementation="eager") tokenizer = AutoTokenizer.from_pretrained("google/functiongemma-270m-it") trainer = SFTTrainer( model=model, args=SFTConfig( output_dir="functiongemma-ecommerce", max_length=1024, # covers the longest sample + margin packing=False, # keep tool calls intact (no cross-sample packing) num_train_epochs=3, per_device_train_batch_size=8, learning_rate=5e-5, lr_scheduler_type="constant", warmup_steps=50, bf16=True, # or fp16 on non-Ampere GPUs eval_strategy="epoch", report_to="none", ), train_dataset=train_ds, processing_class=tokenizer, ) trainer.train() ``` TRL applies the FunctionGemma chat template with the per-sample `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. ### 4. Evaluate (greedy success rate) ```python ok = 0 for item in test_rows: inputs = tokenizer.apply_chat_template(item["messages"][:-1], tools=item["tools"], add_generation_prompt=True, return_tensors="pt") out = model.generate(**inputs, max_new_tokens=256) output = tokenizer.decode(out[0][len(inputs["input_ids"][0]):], skip_special_tokens=False) expected = ok += expected-tool-in-output and no-other-tool-in-output ``` ### 5. Push ```python trainer.push_to_hub("YOUR_USER/functiongemma-ecommerce") ``` ## Best practices **Data** - Keep noise **digit-safe**: never corrupt the values the model must extract (prices, ids). The `noise.py` engine skips any token containing digits. - Use **deterministic train/test splits** (by `template_id`) and hold out whole languages + (for the e-commerce set) whole *schemas* — that is the only honest way to measure generalization. - **Balance** the training subset per (language, intent) — cap the big buckets instead of letting English dominate. **Training** - `packing=False` for tool-calling data; packed sequences splice mid-call. - `max_length` ≥ longest sample + a margin; ~1024 covers these datasets. - Constant LR + short warmup (the official guide's defaults) work well. - Upload a checkpoint to the Hub after **every epoch** — Colab VMs die mid-run, and the last good epoch is always recoverable. **Evaluation** - Always evaluate with **greedy decoding** for comparability across formats and runs. - Score two things separately: tool-name selection and argument fidelity (query + every filter key:value pair). - Compare every exported format (SafeTensors / GGUF / MLX / ONNX) on the same prompts — quantization changes results. **Deployment** - Validate tool arguments server-side before executing anything (a small model can garble a card number under heavy noise). - In a live agent, follow the FunctionGemma full loop: model call → backend executes → tool response → model continues; never let the model see or emit secrets. - For browser deployment use the fp16 ONNX file; for low-end hardware the Q8_0 GGUF or MLX 8-bit; for exact reference behavior the SafeTensors model. ## Related - Parent: [`...-v2`](https://huggingface.co/Qrzysztof/functiongemma-270m-it-prepaid-cards-v2) (SafeTensors) - Siblings: [MLX 8-bit](https://huggingface.co/Qrzysztof/functiongemma-270m-it-prepaid-cards-v2-mlx) · [ONNX](https://huggingface.co/Qrzysztof/functiongemma-270m-it-prepaid-cards-v2-onnx) - Dataset: [`...-tool-calling-v2`](https://huggingface.co/datasets/Qrzysztof/functiongemma-prepaid-cards-tool-calling-v2)