Gemma 270Mはどんな感じなのか
最近、Googleが公開しているGemmaシリーズ。その中でも最小サイズのGemma 3 270MをGoogle Colabで動かしてみました。
「とりあえずどんな感じで動くのか」「小さいモデルってどんな挙動になるのか」という実験です。
結論から言うと
動く。ちゃんと答えは返ってくる。けど、内容はかなりズレる。
それが逆に面白い、という結果になりました。
Gemma3 270Mとは
Gemma3 270Mとは、Googleが公開した大規模言語モデルで最小サイズのモデルです。
パラメータ数はわずか2億7千万(270M)。
近年主流の数十億〜数百億規模のLLMと比べると圧倒的に小型です。
そのため動作は軽く、Google Colabの無料環境やメモリの限られたPCでも動かせる点が大きな特徴となっています。
ただし、モデルサイズが小さい分、知識量や推論力はかなり限定的で、事実ベースの質問に対しては誤答やトンチンカンな返答をすることも多いです。
一方で、言語の形式はそれなりに整っており、短文での応答や独特の言い回しは可能です。
この特性を活かし、知識ベースの利用ではなく「遊び用キャラ」や「特定の口調・スタイルに特化したボット」としてファインチューンする用途に向いているモデルといえます。
google Colabでの実行方法
Colabでは以下の流れで実行できます。
1. Hugging Faceのアカウントを作り、Gemmaのモデルページで利用規約に同意
2. Colab上でtransformersやhuggingface_hubをインストール
3. Hugging Faceトークンを入力してモデルをダウンロード
4. テキストボックスUIを作って、質問文を入力して対話
実際に作ったUIは、ブラウザ上でチャット風に会話できるスタイルにしました。
「モデル準備」ボタンでGemma3 270Mをロードし、その下に質問用テキストエリアを配置。
送信すると返答が吹き出しで表示されるという、簡易チャットアプリのような仕組みです。
この時点で「Gemma3 270MをColabに落として、ユーザーの入力に答えさせる」という部分は問題なく動作しました。

⚫︎pythonコードを貼っておきます。
google colabで一発で動く版。
有料版のT4でも質問からの返答まで結構時間かかります。
無料版のcpuだとだいぶかかるかと。
使う時はHugging faceのアクセストークンが要ります。
またモデルページで同意しないとダウンロードできないやつです。
(わからない人は、google検索 or AIに聞いてください)
# ✅ Google Colab ワンセル版:Gemma 3 270MをローカルDLして連続チャット(安全/事実モード付き)
# - このセルをそのまま実行 → 画面のUIでHFトークン入力→「モデル準備」→下の欄で継続対話
# - モデルは /content/models/ に保存(再起動まで保持)
# - 既定モデル:google/gemma-3-270m-it(指示追従)
# - 「安全モード」= FP32 & 貪欲生成(CUDA assert回避)、「事実質問モード」= do_sample=False
!pip -q install "transformers>=4.43.0" "accelerate>=0.33.0" huggingface_hub ipywidgets > /dev/null
import os, shutil, time, html, torch, textwrap
import ipywidgets as widgets
from IPython.display import display, Markdown, clear_output
from huggingface_hub import login, snapshot_download
from transformers import AutoTokenizer, AutoModelForCausalLM
# =========================
# UI: モデル準備(上段)
# =========================
hf_token_box = widgets.Password(
description='HF Token',
placeholder='hf_xxx...(Hugging Face アクセストークン)',
layout=widgets.Layout(width='96%')
)
model_box = widgets.Dropdown(
options=[
('google/gemma-3-270m-it(指示追従・推奨)', 'google/gemma-3-270m-it'),
('google/gemma-3-270m(素のPT)', 'google/gemma-3-270m'),
],
value='google/gemma-3-270m-it',
description='Model',
layout=widgets.Layout(width='96%')
)
system_box = widgets.Textarea(
description='システム',
value="日本語で、簡潔かつ正確に回答してください。事実質問では推測せず、わからない場合はわからないと答えてください。",
placeholder='(任意)アシスタントのキャラや方針。',
layout=widgets.Layout(width='96%', height='70px')
)
max_new_tokens_box = widgets.IntSlider(
description='max_new',
value=256, min=32, max=1024, step=32, continuous_update=False
)
temperature_box = widgets.FloatSlider(
description='temp',
value=0.3, min=0.0, max=1.5, step=0.1, readout_format='.1f', continuous_update=False
)
safe_mode_chk = widgets.Checkbox(
description='安全モード(FP32/貪欲)※落ちる時はON',
value=True
)
factual_mode_chk = widgets.Checkbox(
description='事実質問モード(do_sample=False)',
value=True
)
fresh_download_chk = widgets.Checkbox(
description='強制再ダウンロード(既存を削除)',
value=False
)
prepare_btn = widgets.Button(description='モデル準備', button_style='primary')
prep_out = widgets.Output()
header = widgets.HTML("
<h3>Gemma 3 270M(Colabローカル保存・連続チャット)</h3>
")
row1 = widgets.HBox([hf_token_box])
row2 = widgets.HBox([model_box])
row3 = widgets.HBox([system_box])
row4 = widgets.HBox([max_new_tokens_box, temperature_box, safe_mode_chk, factual_mode_chk, fresh_download_chk, prepare_btn])
display(header, row1, row2, row3, row4, prep_out)
# =========================
# UI: チャット(下段)
# =========================
chat_html = widgets.HTML(
value="""
<div id="chat" style="font-family: ui-sans-serif,System-ui,-apple-system; background: #111; color: #eee; padding: 12px; border-radius: 12px; height: 360px; overflow: auto;">
<div style="opacity: .7;">💬 ここに対話ログが表示されます</div>
</div>
""",
layout=widgets.Layout(width='100%')
)
user_box = widgets.Textarea(
description='あなた',
placeholder='ここに質問を入力(例:日本で3番目に高い山は?)',
layout=widgets.Layout(width='96%', height='90px'),
disabled=True
)
send_btn = widgets.Button(description='送信', button_style='success', disabled=True)
clear_btn = widgets.Button(description='履歴クリア', button_style='', disabled=True)
chat_out = widgets.Output()
display(chat_html, user_box, widgets.HBox([send_btn, clear_btn]), chat_out)
# =========================
# 内部状態・ヘルパ
# =========================
def local_model_dir(model_id: str) -> str:
safe = model_id.replace("/", "__")
return f"/content/models/{safe}"
def ensure_model_local(model_id: str, hf_token: str, force_redownload: bool=False) -> str:
target_dir = local_model_dir(model_id)
if force_redownload and os.path.isdir(target_dir):
shutil.rmtree(target_dir)
if not os.path.isdir(target_dir) or len(os.listdir(target_dir)) == 0:
login(token=hf_token)
os.makedirs(target_dir, exist_ok=True)
snapshot_download(
repo_id=model_id,
local_dir=target_dir,
local_dir_use_symlinks=False,
token=hf_token,
)
return target_dir
_model_cache = {"key": None, "tok": None, "mdl": None}
_messages = [] # [{"role":"system"|"user"|"assistant","content":str},...]
def escape_html(s: str) -> str:
return html.escape(s).replace("\n", "
")
def render_chat():
parts = []
for m in _messages:
if m["role"] == "user":
parts.append(f"""
<div style="margin: 8px 0; text-align: right;">
<div style="display: inline-block; background: #2b6cb0; color: white; padding: 8px 10px; border-radius: 10px; max-width: 80%;">{escape_html(m['content'])}</div>
</div>
""")
elif m["role"] == "assistant":
parts.append(f"""
<div style="margin: 8px 0; text-align: left;">
<div style="display: inline-block; background: #2d2d2d; color: #eee; padding: 8px 10px; border-radius: 10px; max-width: 80%;">{escape_html(m['content'])}</div>
</div>
""")
elif m["role"] == "system":
parts.append(f"""
<div style="margin: 8px 0; text-align: center; opacity: .75;">
<div style="display: inline-block; background: #333; color: #ddd; padding: 6px 8px; border-radius: 10px; max-width: 80%;">{escape_html(m['content'])}</div>
</div>
""")
if not parts:
parts = ["
<div style="opacity: .7;">💬 ここに対話ログが表示されます</div>
"]
chat_html.value = f"""
<div id="chat" style="font-family: ui-sans-serif,System-ui,-apple-system; background: #111; color: #eee; padding: 12px; border-radius: 12px; height: 360px; overflow: auto;">{''.join(parts)}</div>
"""
def load_from_local(local_dir: str, safe_mode: bool):
"""
safe_mode=True: FP32でロード(GPUでもfp32固定)
safe_mode=False: CUDAあればfp16
"""
key = (local_dir, "fp32" if safe_mode else "fp16auto")
if _model_cache["key"] == key and _model_cache["tok"] and _model_cache["mdl"]:
return _model_cache["tok"], _model_cache["mdl"]
kwargs = dict(trust_remote_code=True, low_cpu_mem_usage=True, device_map="auto")
if torch.cuda.is_available() and not safe_mode:
kwargs.update(torch_dtype=torch.float16)
else:
kwargs.update(torch_dtype=torch.float32)
tok = AutoTokenizer.from_pretrained(local_dir)
mdl = AutoModelForCausalLM.from_pretrained(local_dir, **kwargs).eval()
_model_cache.update({"key": key, "tok": tok, "mdl": mdl})
return tok, mdl
def build_prompt(tok, messages):
# Gemma 3 は chat_template 対応。失敗時はフォールバック。
try:
return tok.apply_chat_template(messages, add_generation_prompt=True, tokenize=False)
except Exception:
sys_txt = ""
for m in messages:
if m["role"] == "system":
sys_txt = m["content"] + "\n\n"
last_user = [m["content"] for m in messages if m["role"] == "user"][-1]
return f"{sys_txt}User: {last_user}\nAssistant:"
def generate_once(tok, mdl, prompt: str, max_new: int, temp: float, safe_mode: bool, factual_mode: bool):
"""
生成部:
- factual_mode or safe_mode: do_sample=False(貪欲)で安定重視
- otherwise: サンプリング(創作・発想向け)
- 例外時はFP32/貪欲にフォールバック
"""
inputs = tok(prompt, return_tensors="pt").to(mdl.device)
gen_base = dict(
max_new_tokens=max_new,
eos_token_id=tok.eos_token_id,
pad_token_id=tok.eos_token_id,
)
if factual_mode or safe_mode:
with torch.no_grad():
out_ids = mdl.generate(**inputs, do_sample=False, **gen_base)
return tok.decode(out_ids[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
try:
with torch.no_grad():
out_ids = mdl.generate(
**inputs,
do_sample=True,
temperature=max(0.0, min(1.5, temp)),
top_p=0.9,
repetition_penalty=1.05,
**gen_base
)
return tok.decode(out_ids[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
except RuntimeError:
with torch.no_grad():
out_ids = mdl.generate(**inputs, do_sample=False, **gen_base)
return tok.decode(out_ids[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True)
def trim_history(messages, max_turns=12):
# システム1 + 直近の往復を最大max_turnsまで
sys_msgs = [m for m in messages if m["role"] == "system"]
others = [m for m in messages if m["role"] != "system"]
if len(others) <= max_turns * 2:
return sys_msgs + others
return sys_msgs + others[-max_turns*2:]
# =========================
# 動作:モデル準備ボタン
# =========================
@prep_out.capture(clear_output=True)
def on_prepare(_):
token = hf_token_box.value.strip()
if not token:
print("❗Hugging Face のアクセストークンを入力してください(モデルページで利用規約に同意が必要な場合あり)。")
return
model_id = model_box.value
safe_mode = bool(safe_mode_chk.value)
force = bool(fresh_download_chk.value)
_messages.clear()
sys_txt = system_box.value.strip()
if sys_txt:
_messages.append({"role": "system", "content": sys_txt})
render_chat()
try:
print(f"📥 モデルをローカルへ準備中: {model_id}")
local_dir = ensure_model_local(model_id, token, force_redownload=force)
print(f" → 保存先: {local_dir}")
print(f"⏳ モデル読込中...({'安全' if safe_mode else '通常'}モード)")
tok, mdl = load_from_local(local_dir, safe_mode=safe_mode)
print("✅ 準備完了。下の入力欄から送信できます。")
user_box.disabled = False
send_btn.disabled = False
clear_btn.disabled = False
except Exception as e:
print(f"❌ 準備に失敗しました: {e}")
user_box.disabled = True
send_btn.disabled = True
clear_btn.disabled = True
prepare_btn.on_click(on_prepare)
# =========================
# 動作:送信ボタン
# =========================
@chat_out.capture(clear_output=False)
def on_send(_):
if send_btn.disabled:
return
user_text = user_box.value.strip()
if not user_text:
return
# 送信 → 表示
_messages.append({"role": "user", "content": user_text})
render_chat()
user_box.value = ""
# 推論
try:
local_dir = local_model_dir(model_box.value)
# 既存キャッシュ(prepare時にロード済みのはず)
tok, mdl = _model_cache["tok"], _model_cache["mdl"]
if tok is None or mdl is None:
# 念のため再ロード
tok, mdl = load_from_local(local_dir, safe_mode=bool(safe_mode_chk.value))
msgs = trim_history(_messages, max_turns=12)
prompt = build_prompt(tok, msgs)
reply = generate_once(
tok, mdl, prompt,
max_new=max_new_tokens_box.value,
temp=temperature_box.value,
safe_mode=bool(safe_mode_chk.value),
factual_mode=bool(factual_mode_chk.value)
)
except Exception as e:
reply = f"(エラーが発生しました) {e}"
_messages.append({"role": "assistant", "content": reply})
render_chat()
send_btn.on_click(on_send)
# =========================
# 動作:履歴クリア
# =========================
def on_clear(_):
sys_txt = system_box.value.strip()
_messages.clear()
if sys_txt:
_messages.append({"role": "system", "content": sys_txt})
render_chat()
clear_btn.on_click(on_clear)
# 初期描画
render_chat()
Gemma 270Mの返答の中身は?
では、実際にどんな返答をしたのか。
例えばこんなやり取り。
質問:「日本で3番目に高い山は?」
返答:「はい、日本で3番目にたかい山は『たかい山』です。」
思わず笑ってしまいました。
「それっぽい答え」は返してくるけど、事実は全く外れている。
この“ズレ”が小型モデルならではのおかしさです。
他にも、
「明日はどっちですか?」と聞くと、
「明日はどこですか?」と返してきたり。
要するに日本語はそれっぽくつなげられるけど、意味理解や知識は弱いということです。

なぜこうなる?
Gemma3 270Mは、パラメータ数がわずか270M。
これは一般的な最新LLM(数十Bクラス)と比べると、文字通り100分の1以下の規模です。
そのため:
* 言語の形式は整えられる
* でも事実知識はほぼ持っていない
* 推論力も弱いので質問を理解しきれない
といった挙動になります。
モデルが悪いのではなく、小さすぎるから当然というわけです。
このモデルをどう利用するか?
じゃあ「役立たない」かというと、そうでもありません。
小型モデルは“キャラ特化”に向いているのではと。
例えば:
* 大阪弁キャラ
* 冗談やツッコミ専用ボット
* 語尾変換(〜でござる、〜やで)
* 決めフレーズを連発する面白キャラ
こういったスタイルを付与するなら、270Mでも十分にファインチューニングできます。
むしろ小さいからこそ短時間・低コストで微調整できるのが魅力です。
Gemma3 270Mをファインチューニングするには
「Gemma3 270Mを自分好みのキャラに染める」方法です。
やり方はざっくり次の通り。
データを作る
まずは学習データ。
形式はHugging Faceのchat形式JSONLが便利です。
jsonl
{"messages":[
{"role":"system","content":"大阪弁で、短く面白く答える。"},
{"role":"user","content":"自己紹介して"},
{"role":"assistant","content":"どーも、関西ノリの小型モデルやで。軽いけどキレ味出すで。"}
]}
{"messages":[
{"role":"user","content":"ボケて"},
{"role":"assistant","content":"リンゴ三分の一しか残ってへん。え、計算できへんの?おもろいやろ。"}
]}
GTP5曰く、数百〜数千件あれば十分とのこと。
重要なのは一貫した口調と決めフレーズを盛り込むことだそう。
学習レシピ(LoRAで軽く)
Colabで動かすならLoRAが無難。
手順イメージはこんな感じかと(GPT5先生作)
!pip -q install transformers peft accelerate datasets trl
from datasets import load_dataset
from transformers import AutoTokenizer, AutoModelForCausalLM
from peft import LoraConfig, get_peft_model
from trl import SFTTrainer, SFTConfig
import torch
base_model = "google/gemma-3-270m-it"
tok = AutoTokenizer.from_pretrained(base_model, use_fast=True)
tok.pad_token = tok.eos_token
ds = load_dataset("json", data_files="data.jsonl")["train"]
peft_cfg = LoraConfig(
r=16, lora_alpha=32, lora_dropout=0.05,
target_modules=["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"]
)
mdl = AutoModelForCausalLM.from_pretrained(
base_model,
torch_dtype=torch.bfloat16 if torch.cuda.is_available() else torch.float32,
device_map="auto",
)
mdl = get_peft_model(mdl, peft_cfg)
train_cfg = SFTConfig(
output_dir="./gemma270m-osaka-lora",
per_device_train_batch_size=2,
gradient_accumulation_steps=16,
learning_rate=2e-4,
num_train_epochs=2,
max_seq_length=256,
)
trainer = SFTTrainer(
model=mdl,
tokenizer=tok,
train_dataset=ds,
formatting_func=lambda batch: [tok.apply_chat_template(x["messages"], tokenize=False) for x in batch],
args=train_cfg,
)
trainer.train()
筆者曰く。
多分簡単には想定通りに動かないでしょう。
手間がかかる分、あれかなと。
時間のある方はチャレンジしてみてください。
エラーが出たら、コピペしてGTP5あたりに投げれば、回答してくれます。
ファイトー!
プロンプトチューニングでキャラ弁できるのか
ファインチューニングは手間がかかるので、今回はざっくりとプロンプトチューニングで成功するのかを試しました。
下記が入れるプロンプトです。
****
「あなたは大阪弁で会話する関西キャラのアシスタントです。
常にフレンドリーでユーモラスに話し、軽くツッコミを入れたり冗談を交えることを意識してください。
一人称は「ワイ」、相手は「アンタ」と呼んでもいいです。
文章はなるべく短めでテンポよく、最後に「やで」「やんけ」「知らんけど」などの大阪弁らしい語尾を入れてください。
わからない質問が来たら、正直に「そんなん知らんわ〜」と笑いながら答えてください。
[例]
ユーザー: 自己紹介して
アシスタント: ワイは軽量やけどノリのええLLMやで。おもろい話なら任せとき!
ユーザー: ボケて
アシスタント: リンゴ三分の一しか残ってへんねん。計算できへんのかい!知らんけど。
ユーザー: 今日の天気は?
アシスタント: えー、そこはネット繋がってへんから詳しくは知らんわ〜。けどワイの心は快晴やで。」
このプロンプトをsystemに入れて、通常の質問と回答は下記です。
****
質問
こんにちは。今日の大阪の天気はどんな感じですか?
回答
はい、承知いたしました。大阪の天気について、フレンドリーでユーモアのある会話を心がけました。
質問
大阪といえば、野球はどこチーム?
回答
大阪といえば、野球はどこチーム?
****
んーん。意味不明ですね。
やはり270Mですから、こんなもんなんでしょう。
それと英語ベースでの学習されているみたいなので、日本語はイマイチっぽいです。
(とはいえ、英語も試しましたが…。
てな感じです。日本語よりは良かったけど。
まあ、仕方ない)

Gemma3 270Mをgoogle colabで動かすのまとめ
* Gemma3 270MはColabでもサクッと動く
* ただし事実知識は弱く、質問応答はトンチンカン
* でも逆にそのズレが面白い
* 「遊び用キャラ」にファインチューニング成功すれば、なんとか使えるかも。
*プロンプトチューニングもイマイチ。まずは日本語学習が先決か。
*知識蒸留すると違うのかな。今回はやってないので、時間あればやりたいですね。
でもどうせやるなら、1B以上ないときついかなと。
つまり、Gemma3 270Mは真面目なQA用ではなく、遊び心あるキャラボットを作成する勉強ツールとして使うと楽しいかもです。
****************
最近のデジタルアート作品を掲載!
X 旧ツイッターもやってます。
https://x.com/ison1232
インスタグラムはこちら
https://www.instagram.com/nanahati555/
****************