1. はじめに
Qwen3.5-4Bは、4B(40億パラメータ)という軽量サイズでありながら、回答前に推論過程を生成する Thinkingモード(o1系のような思考プロセス)と通常の応答モードを使い分けられるハイブリッド推論モデルです。
従来のLLMは「チャットモデル」と「推論専用モデル(Reasoning Model)」が別々に存在していました。Qwen3/3.5の Hybrid Reasoning Model は、この2つを単一のモデルウェイトに統合した設計です。
モード制御はAPIまたはchat templateの enable_thinking パラメータで行います。なお、Qwen3で提供されていた /think・/no_think プロンプトスイッチは Qwen3.5では公式にサポートされていません。
今回は、Google Colabの無料GPU枠で実際に動かしながら、Qwen3.5-4Bの組み込みIoT分野の知識を検証してみました。
なぜGoogle Colabなのか?
GPUが搭載されていないPCで簡単に試してみたかったためです。Google Colabであれば無料でNVIDIA T4などの強力なGPUが利用でき、ブラウザを開くだけで即座に環境が手に入ります。今回はGoogle Colabの無料GPU枠を活用し、4bit量子化を用いて簡単に動かしてみます。
2. 準備:ライブラリのインストールとモデルのロード
まずはGoogle Colabのノートブックを開き、「ランタイム」→「ランタイムのタイプを変更」からハードウェアアクセラレータを「T4 GPU」に設定します。
次に以下のコードを実行します。T4のVRAMは16GBですが、bitsandbytes による4bit量子化で消費VRAMを大幅に削減でき、Qwen3.5-4Bも余裕をもって動かすことができます。
!pip install -q -U "transformers>=4.57.0" accelerate bitsandbytes
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
model_name = "Qwen/Qwen3.5-4B"
quantization_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True
)
print("モデルとトークナイザーをダウンロード・読み込み中...")
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
device_map="auto",
torch_dtype=torch.float16,
quantization_config=quantization_config,
low_cpu_mem_usage=True
)
3. 実践:Qwenの「論理的思考・説明力」を測定する
モデルが無事に読み込めたら、チャットテンプレートを利用して推論テストをしてみます。
「IoTの実装アドバイスができるか?改善提案は的確か?」という観点でLLMの能力を評価するため、意図的にロバストでないESP32のWiFi接続スケッチを提示し、改善策を提案させてみました。正解の方向性が明確なので、コードを動かさなくても出力の品質をすぐ評価できるのがポイントです。
ThinkingモードとサンプリングパラメータのTips
コードを実行する前に、Qwen3.5特有の重要なポイントを2つ押さえておきます。
1. Thinkingモード(デフォルト: ON)
Qwen3.5はデフォルトで Thinkingモードが有効です。このモードでは回答の前に <think>...</think> タグ内に推論過程(Chain-of-Thought)を出力します。数学・複雑なロジックには有効ですが、「整理された説明文をサクッと出力してほしい」場面ではノイズになります。
<think> ブロックは「内部処理の覗き見」ではない点に注意が必要です。<think> ブロックはモデルが内部で行った計算を後から表示しているのではなく、実際にトークンとして生成された出力です。つまりモデルは <think> に「思考を書き出す」ことで推論の精度を高めており、書かずに直接答えるより正確になります。これはOpenAI o1・DeepSeek-R1と同じ設計思想(Chain-of-Thought推論の外部化)です。
Qwen3.5はQwen3の /think /no_think ソフトスイッチに非対応です。Qwen3ではプロンプトに /think・/no_think を書けばモード切り替えできましたが、Qwen3.5ではこの方法は公式にサポートされていません。apply_chat_template の enable_thinking 引数で明示的に指定するのが唯一の確実な方法です。
2. サンプリングパラメータは公式推奨値を使う
今回は2つのモードをそれぞれ最適パラメータで比較します。
| モード | temperature | top_p | top_k |
|---|---|---|---|
| 非Thinking | 0.7 | 0.8 | 20 |
| Thinking(一般タスク) | 1.0 | 0.95 | 20 |
| Thinking(精密なコーディング) | 0.6 | 0.95 | 20 |
top_k=20 は必ず指定しましょう。省略すると語彙全体からサンプリングされ、繰り返しや品質低下が起きやすくなります。
今回のタスクは「コードレビューと改善提案」なので、Thinkingモードは精密なコーディング向け推奨値(temperature=0.6)を使います。
3-1. 非Thinkingモードで実行(簡潔・高速な回答)
enable_thinking=False を指定し、<think> ブロックなしで直接回答を生成します。
# ===== プロンプト定義(2つのコードで共通) =====
system_prompt = """
あなたはESP32・M5Stackなど組み込みIoTデバイスの開発に詳しいエキスパートエンジニアです。
コードの問題点を的確に指摘し、ロバスト(堅牢)で実用的な改善策をわかりやすく提案します。
回答は必ず日本語で行ってください。
"""
user_prompt = """
以下のESP32のWiFi接続スケッチは動作するものの、ロバストではありません。
どのような問題点があるか指摘し、より堅牢な接続処理にするための改善策を提案してください。
#include <WiFi.h>
const char* ssid = "MyNetwork";
const char* password = "password123";
void setup() {
Serial.begin(115200);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
Serial.println("WiFi connected");
}
void loop() {
// センサーデータの送信処理など
}
以下の観点で評価し、改善後のコード例も示してください。
1. 接続失敗時の処理
2. タイムアウト処理の有無
3. loop()内での切断検知・再接続処理
4. その他、実運用で問題になりうる点
"""
messages = [
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
# ===== 非Thinkingモード実行 =====
# enable_thinking=False:<think>ブロックを生成しない
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=False # 非Thinkingモード
)
model_inputs = tokenizer([text], return_tensors="pt").to(model.device)
print("【非Thinkingモード】AIが回答を生成中...")
generated_ids = model.generate(
**model_inputs,
max_new_tokens=1500, # 改善コード含む回答を想定してやや多めに
temperature=0.7, # 非Thinkingモード 公式推奨値
top_p=0.8, # 非Thinkingモード 公式推奨値
top_k=20, # 全モード共通 公式推奨値
do_sample=True,
)
generated_ids = [
output_ids[len(input_ids):]
for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response_no_think = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
print("===== 非Thinkingモード 回答 =====")
print(response_no_think)
3-2. Thinkingモードで実行(推論過程を可視化)
enable_thinking=True(デフォルト)を指定し、<think>...</think> ブロックで推論過程を確認した上で回答を生成します。タスクが「コードレビュー」なので、精密なコーディング向けの公式推奨値(temperature=0.6)を用います。
# ===== Thinkingモード実行 =====
# プロンプトは3-1と同じものを再利用
# enable_thinking=True(デフォルト値):<think>ブロックで推論過程を出力
text = tokenizer.apply_chat_template(
messages,
tokenize=False,
add_generation_prompt=True,
enable_thinking=True # Thinkingモード(デフォルト)
)
model_inputs = tokenizer([text], return_tensors="pt").to(model.device)
print("【Thinkingモード】AIが回答を生成中(時間がかかります)...")
generated_ids = model.generate(
**model_inputs,
max_new_tokens=3000, # <think>ブロック分を含むため多めに確保
temperature=0.6, # 精密なコーディングタスク向け 公式推奨値
top_p=0.95, # Thinkingモード 公式推奨値
top_k=20, # 全モード共通 公式推奨値
do_sample=True,
)
generated_ids = [
output_ids[len(input_ids):]
for input_ids, output_ids in zip(model_inputs.input_ids, generated_ids)
]
response_think = tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
print("===== Thinkingモード 回答(<think>ブロック含む) =====")
print(response_think)
出力の読み方:Thinkingモードでは <think> と </think> で囲まれたブロックがまず出力され、その後に最終回答が続きます。<think> ブロックはモデルの内部推論(Chain-of-Thought)であり、ユーザーへの回答ではありません。最終的に読むべき回答は </think> タグ以降の部分です。
4. 結果と考察
4-1. 非Thinkingモードの出力
非Thinkingモードでは、<think> ブロックなしで即座に構造化された回答が得られました。
出力の特徴:
- 4つの問題点(①無限ループ、②タイムアウト欠如、③切断検知なし、④その他)を見出し付きで整理
-
millis()を用いたタイムアウト処理、WiFi.reconnect()を含む再接続処理を含む改善コードを提示 - 状態遷移(IDLE / CONNECTING / CONNECTED / RECONNECTING)という列挙型による状態管理という実践的なパターンも提案
評価:問題点の列挙・改善提案のコードともに、IoT実装として的確な回答でした。
4-2. Thinkingモードの出力
Thinkingモードでは <think> ブロックの推論過程を経た上で、最終回答が出力されました。
The user is asking me to analyze an ESP32 WiFi connection sketch...
Let me identify the issues...
1. No connection timeout
2. No error handling
...
</think>
システムプロンプトで「回答は必ず日本語で行ってください」と指示しましたが、Thinkingモードの内部推論(Chain-of-Thought)は英語で実行されました。最終回答(</think> タグ以降)は指示通り日本語で出力されています。内部思考言語と出力言語は別物ですね。
出力の特徴:
- 非Thinkingモードより包括的な構成(絵文字・表・追加提案セクション付き)
- Watchdog Timer、OTA更新対応、バッテリー管理まで言及した「5つの追加ロバスト化対策」を提示
- 改善ポイントを比較表でまとめており、可読性が高い
コードの軽微な問題:
// バグ:演算子の優先度が原因でロジックが反転している
if (!WiFi.status() == WL_CONNECTED) { ... }
// 正しくはこちら
if (WiFi.status() != WL_CONNECTED) { ... }
Thinkingモードが優れた推論をしても、コード生成部分に軽微なバグが混入していました。(必ずコードをレビューする習慣が大切)。
4-3. 2モードの比較まとめ
| 比較項目 | 非Thinkingモード | Thinkingモード |
|---|---|---|
| 生成速度 | 速い | 遅い(<think>ブロック分だけトークン増加) |
| 回答の簡潔さ | 必要十分・コンパクト | 包括的だがやや冗長 |
| 構成の豊かさ | 箇条書き中心 | 表・絵文字・追加提案あり |
| コードの質 | 実用的・バグなし | 実用的だが軽微なバグあり |
| 思考プロセス | 非公開 |
<think>ブロックで可視化(英語) |
| 向いてる場面 | 説明・要約・コードレビュー | 数学・複雑なアルゴリズム・論理パズル |
総評:今回のような「コードレビュー+改善提案」というタスクでは、非Thinkingモードの方が実用的でした。Thinkingモードは回答の"深さ"よりも"広さ"が増す傾向があり、タスクの複雑さに応じて使い分けるのが賢明です。
4-4. Thinkingモードが真価を発揮する場面
今回のテストでは非Thinkingモードに軍配が上がりましたが、Thinkingモードが本領を発揮するのは「段階的な推論が必要な問題」です。
<think> ブロックは単なるログではなく、モデルが正確な答えを出すための「思考メモ」として機能します。人間が難しい数学の問題を「頭の中だけ」で解くより、途中式を紙に書きながら解く方が正確になるのと同じ原理(Chain-of-Thought推論)です。
| タスクの種類 | Thinking効果 | 理由 |
|---|---|---|
| 数学・論理パズル | 非常に高い | 段階的な計算過程が精度に直結する |
| 複雑なアルゴリズム設計 | 高い | 条件の整理と検証が多い |
| バグ原因の特定(デバッグ) | 高い |
<think>で推論のズレを検出できる |
| コードレビュー(今回) | やや高い | 直感でも可。Thinkingで網羅性が増すが冗長になりやすい |
| 単純な説明・要約・翻訳 | 低い | 推論の余地がなく、速度コストが無駄になる |
デバッグ用途としてのThinkingモード:AIが誤った回答を出した場合、<think> ブロックを読むと「どのステップで推論がズレたか」を追跡できます。Non-Thinkingモードでは内部推論が非公開のため、この検証ができません。精度よりも説明責任(なぜその答えか)が重要な場面では、Thinkingモードが有効です。
5. まとめ
今回は、Google Colabの無料GPU枠を活用し、LLM「Qwen3.5-4B」を非ThinkingモードとThinkingモードの2通りで動かし、同一プロンプトへの出力を比較しました。
- 非Thinkingモード:簡潔・高速で実用的。日常的なコードレビューや説明タスクに最適
- Thinkingモード:包括的で多角的。ただし
<think>ブロックは英語で動作し、コードに軽微なバグが混入することも - 共通:4Bという軽量サイズながら、組み込みIoT分野の知識も含む実用的な回答を日本語で生成できる
「環境構築に手間をかけたくない」という方に、Google Colab + 4bit量子化の組み合わせはちょっと試すには便利な選択肢です。