Transformers.js v4 の概要
以下の記事が面白かったので、簡単にまとめました。
1. はじめに
Hugging Faceが開発する JavaScript向けAIライブラリ「Transformers.js v4 Preview」が公開されました。これまでのv3系から大きく進化し、WebGPU対応やモデル追加、実行速度の向上など、多くの改善ポイントが盛り込まれています
2. Transformers.jsとは?
「Transformers.js」は、元々Pythonで人気の高い機械学習ライブラリ「Huggin Face Transformers」を JavaScript(ブラウザ・Node.js)に最適化したものです。
特徴は、次のとおりです。
・ブラウザ/Node.jsでのAIモデル推論が可能
・Python版APIと似たインターフェース
・NLP・画像・音声モデル対応
・WebGPU や WASM を使って高速実行
Transformer.js v4 で gpt-oss-20b をおためし中
— 布留川英一 / Hidekazu Furukawa (@npaka123) February 14, 2026
(macOSのChrome) pic.twitter.com/OWUbfzPpMs
3. v4 の主な改善ポイント
3-1. WebGPUカスタムランタイムの採用
v4では 新しい WebGPUランタイムが C++で構築され、パフォーマンスと互換性が大幅に向上しました。
これにより、
・ブラウザだけでなく Node/Bun/Deno でも GPUアクセラレーションが使える
・より多くのモデルを高速かつ効率的に実行可能
といった大きな進化を果たしています。
3-2. モノレポ化 & コード品質向上
プロジェクト構成が pnpmワークスペースベースに再構成され、コードがモジュール化されました。
これにより、
・各機能ごとに独立したパッケージが作れる
・コードの見通しが良くなり、拡張がしやすい
・モデル定義が整理され、開発の効率が向上
といったメリットがあります。
3-3. 新モデル & アーキテクチャ対応
v4では、多様な最新モデルをサポートしています。
・GPT-OSS 系モデル
・Chatterbox
・Olmo3
・FalconH1 など
新しい注意メカニズムや MoE(Mixture of Experts)などもWebGPU上で動作するようになりました。
3-4. ビルドシステムを esbuild に移行
従来の Webpack からesbuild ベースに刷新しました。
これによって、
・ビルド時間が約10倍速い
・バンドルサイズが小さくなり、読み込みが高速に
・主要なライブラリ(例:transformers.web.js)は 53% ダウン
といった効果が出ています。
3-5. Tokenizers.js として分離
これまでライブラリ内部にあった トークナイザー部分を独立パッケージ化 (@huggingface/tokenizers) しました
・gzipped 約8.8KB
・依存関係なし
・ブラウザやサーバー上でも軽量に高速トークナイズ可能
となり、より柔軟に使えるようになりました。
3-6. タイプ安全性 & 開発体験の向上
TypeScriptユーザー向けに型安全性が強化され、pipeline() の戻り値型なども 入力に応じて柔軟に推論されるようになっています。
4. まとめ
「Transformers.js v4」は、JavaScriptによるAIモデル実行の可能性を大きく広げるアップデートです。ブラウザ・Node.js両方でGPUアクセラレーションが使え、モデル対応や開発体験も進化します。今後のAI×Webアプリケーションでの活用がますます加速しそうです。
【おまけ】 Codex 製のサンプルコード
・index.html
<!doctype html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="data:," />
<title>Transformers.js Browser Demo</title>
<style>
body {
font-family: sans-serif;
max-width: 860px;
margin: 32px auto;
padding: 0 16px;
line-height: 1.5;
}
button {
padding: 8px 14px;
font-size: 14px;
}
textarea {
width: 100%;
max-width: 100%;
min-height: 96px;
font-size: 14px;
padding: 10px;
box-sizing: border-box;
}
pre {
white-space: pre-wrap;
background: #f6f8fa;
padding: 12px;
border-radius: 8px;
}
#status {
color: #444;
}
</style>
</head>
<body>
<h1>Transformers.js (Browser)</h1>
<p>初回はモデルダウンロードのため時間がかかります。</p>
<label for="systemPrompt">システムプロンプト</label>
<textarea id="systemPrompt">あなたは日本語のアシスタントです。常に短い文章で簡潔に返答してください。</textarea>
<label for="prompt">ユーザーメッセージ</label>
<textarea id="prompt">こんにちは。自己紹介してください。</textarea>
<button id="run">生成する</button>
<p id="status">待機中</p>
<pre id="result"></pre>
<script type="module" src="./browser.js"></script>
</body>
</html>
・browser.js
import { pipeline, TextStreamer } from "./node_modules/@huggingface/transformers/dist/transformers.min.js";
const runButton = document.getElementById("run");
const statusEl = document.getElementById("status");
const resultEl = document.getElementById("result");
const promptEl = document.getElementById("prompt");
const systemPromptEl = document.getElementById("systemPrompt");
let generatorPromise = null;
function pickFinalAnswer(text) {
if (!text) return "";
// Handle leaked role/style wrappers like:
// analysis ... assistantfinal ...
const markers = ["assistantfinal", "final", "assistant\nfinal"];
for (const marker of markers) {
const idx = text.toLowerCase().lastIndexOf(marker.toLowerCase());
if (idx !== -1) {
const sliced = text.slice(idx + marker.length).trim();
if (sliced) return sliced;
}
}
return text.trim();
}
async function getGenerator() {
if (!generatorPromise) {
statusEl.textContent = "モデルを初期化中...";
const onProgress = (info) => {
if (info?.status === "progress") {
const name = info.file ?? "model";
const pct = Number.isFinite(info.progress) ? info.progress.toFixed(1) : "?";
statusEl.textContent = `ダウンロード中: ${name} (${pct}%)`;
} else if (info?.status) {
statusEl.textContent = `準備中: ${info.status}`;
}
};
generatorPromise = (async () => {
try {
return await pipeline("text-generation", "onnx-community/gpt-oss-20b-ONNX", {
device: "webgpu",
progress_callback: onProgress,
});
} catch {
statusEl.textContent = "WebGPUが使えないためWASM(CPU)で初期化中...";
return await pipeline("text-generation", "onnx-community/gpt-oss-20b-ONNX", {
device: "wasm",
progress_callback: onProgress,
});
}
})();
}
return generatorPromise;
}
runButton.addEventListener("click", async () => {
runButton.disabled = true;
resultEl.textContent = "";
try {
const prompt = promptEl.value.trim();
const systemPrompt = systemPromptEl.value.trim();
if (!prompt) {
statusEl.textContent = "入力してください";
return;
}
const generator = await getGenerator();
statusEl.textContent = "生成中...";
let streamedText = "";
const streamer = new TextStreamer(generator.tokenizer, {
skip_prompt: true,
skip_special_tokens: true,
callback_function: (text) => {
streamedText += text;
resultEl.textContent = pickFinalAnswer(streamedText);
},
});
let output;
try {
const messages = [];
if (systemPrompt) messages.push({ role: "system", content: systemPrompt });
messages.push({ role: "user", content: prompt });
output = await generator(messages, { max_new_tokens: 512, streamer });
} catch {
const mergedPrompt = systemPrompt ? `${systemPrompt}\n\nユーザー: ${prompt}` : prompt;
output = await generator(mergedPrompt, { max_new_tokens: 512, streamer });
}
if (!resultEl.textContent.trim()) {
const rawText =
output?.[0]?.generated_text?.at?.(-1)?.content ??
output?.[0]?.generated_text ??
"";
resultEl.textContent = pickFinalAnswer(rawText);
} else {
resultEl.textContent = pickFinalAnswer(resultEl.textContent);
}
statusEl.textContent = "完了";
} catch (error) {
statusEl.textContent = "エラー";
resultEl.textContent = String(error);
generatorPromise = null;
} finally {
runButton.disabled = false;
}
});
