Commit
·
212e389
1
Parent(s):
afa3fe8
Update index.html
Browse filesFix Qwen3 load error by forcing Transformers.js to use Qwen2 class
- Switched Qwen3-0.6B to remote-only (onnx-community/Qwen3-0.6B-ONNX)
- Added config override (model_type=qwen2, Qwen2ForCausalLM) to bypass
"Unsupported model type: qwen3" error
- index.html +55 -38
index.html
CHANGED
|
@@ -10,7 +10,7 @@
|
|
| 10 |
header { position:sticky; top:0; z-index:5; display:flex; gap:12px; align-items:center; padding:12px 16px; background:#0e1629; border-bottom:1px solid #1c2945; }
|
| 11 |
h1 { font-size:16px; font-weight:600; margin:0; letter-spacing:.2px; }
|
| 12 |
main { padding:14px; }
|
| 13 |
-
.grid { display:grid; gap:14px; grid-template-columns: 0.35fr 0.65fr; }
|
| 14 |
@media (max-width: 1000px){ .grid { grid-template-columns:1fr; } }
|
| 15 |
|
| 16 |
.card { background:#0e162b; border:1px solid #1c2945; border-radius:14px; padding:12px; }
|
|
@@ -40,7 +40,7 @@
|
|
| 40 |
.small { font-size:12px; }
|
| 41 |
</style>
|
| 42 |
|
| 43 |
-
<!-- Transformers.js for browsers (CDN). The npm snippet
|
| 44 |
<script type="module">
|
| 45 |
import {
|
| 46 |
env,
|
|
@@ -68,7 +68,7 @@
|
|
| 68 |
<div class="inline">
|
| 69 |
<span class="muted small">Model:</span>
|
| 70 |
<select id="model" class="select">
|
| 71 |
-
<option value="qwen" selected>Qwen3-0.6B (
|
| 72 |
<option value="distilgpt2">distilgpt2 (local → Hub fallback)</option>
|
| 73 |
</select>
|
| 74 |
</div>
|
|
@@ -116,14 +116,14 @@
|
|
| 116 |
<script type="module">
|
| 117 |
const { env, AutoTokenizer, AutoModelForCausalLM } = window.HF;
|
| 118 |
|
| 119 |
-
|
| 120 |
env.useBrowserCache = true;
|
| 121 |
env.backends.onnx.wasm.proxy = true;
|
| 122 |
env.backends.onnx.wasm.numThreads = Math.min(
|
| 123 |
4, Math.max(1, Math.floor((navigator.hardwareConcurrency || 4)/2))
|
| 124 |
);
|
| 125 |
|
| 126 |
-
|
| 127 |
const $ = (s) => document.querySelector(s);
|
| 128 |
const statusEl = $('#status'), barEl = $('#bar'), errEl = $('#error');
|
| 129 |
const textEl = $('#text'), klistEl = $('#klist'), timeEl = $('#time');
|
|
@@ -132,6 +132,7 @@
|
|
| 132 |
const embCanvas = $('#embCanvas'), embCtx = embCanvas.getContext('2d');
|
| 133 |
const embStatus = $('#embStatus');
|
| 134 |
|
|
|
|
| 135 |
function setStatus(t){ if(statusEl) statusEl.textContent = t; }
|
| 136 |
function onProgress(evt){
|
| 137 |
if (!barEl) return;
|
|
@@ -148,38 +149,36 @@
|
|
| 148 |
function showToken(s){ if (s === "\n") return "⏎"; if (s.trim() === "") return `␣${s.length>1 ? "×"+s.length : ""}`; return s; }
|
| 149 |
const PUNC_ONLY = /^[\s.,;:!?—-]+$/;
|
| 150 |
|
| 151 |
-
|
| 152 |
-
const LOCAL = (p) => new URL(p, window.location.href).href;
|
| 153 |
-
|
| 154 |
const MODELS = {
|
| 155 |
qwen: {
|
| 156 |
remote: "onnx-community/Qwen3-0.6B-ONNX",
|
| 157 |
dtype: "int8",
|
| 158 |
emb: {
|
| 159 |
-
coords:
|
| 160 |
-
nbrs:
|
| 161 |
}
|
| 162 |
},
|
| 163 |
distilgpt2: {
|
| 164 |
-
local:
|
| 165 |
remote: "Xenova/distilgpt2",
|
| 166 |
dtype: undefined,
|
| 167 |
emb: {
|
| 168 |
-
coords:
|
| 169 |
-
nbrs:
|
| 170 |
}
|
| 171 |
}
|
| 172 |
};
|
| 173 |
|
| 174 |
-
|
| 175 |
const Emb = (() => {
|
| 176 |
let coordsPath = "", nbrsPath = "";
|
| 177 |
let points = [], index = new Map(), neighbors = new Map();
|
| 178 |
let baseDrawn = false;
|
| 179 |
|
| 180 |
function setSources(modelKey){
|
| 181 |
-
coordsPath = MODELS
|
| 182 |
-
nbrsPath = MODELS
|
| 183 |
}
|
| 184 |
async function load(){
|
| 185 |
baseDrawn = false; index.clear(); points = []; neighbors.clear();
|
|
@@ -209,34 +208,51 @@
|
|
| 209 |
return { setSources, load, drawBase, highlight };
|
| 210 |
})();
|
| 211 |
|
| 212 |
-
|
| 213 |
let tokenizer=null, model=null;
|
| 214 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
async function loadModel(key){
|
| 216 |
const cfg = MODELS[key];
|
| 217 |
|
|
|
|
| 218 |
Emb.setSources(key);
|
| 219 |
try { await Emb.load(); } catch { embStatus.textContent = "Map failed to load"; }
|
| 220 |
|
| 221 |
-
setErr(""); setStatus("Loading tokenizer…"); barEl.style.width = "0%";
|
| 222 |
-
try {
|
| 223 |
-
tokenizer = await AutoTokenizer.from_pretrained(MODELS.qwen.remote, { progress_callback: onProgress });
|
| 224 |
-
} catch (e1) {
|
| 225 |
-
console.warn("Local tokenizer failed; falling back to Hub", e1);
|
| 226 |
-
tokenizer = await AutoTokenizer.from_pretrained(cfg.remote, { progress_callback: onProgress });
|
| 227 |
-
}
|
| 228 |
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
}
|
| 236 |
|
| 237 |
-
if (!tokenizer || !model) {
|
| 238 |
setStatus("Load failed");
|
| 239 |
-
setErr("Couldn’t load model. Check
|
| 240 |
return;
|
| 241 |
}
|
| 242 |
|
|
@@ -246,8 +262,9 @@
|
|
| 246 |
setStatus("Ready");
|
| 247 |
}
|
| 248 |
|
|
|
|
| 249 |
async function greedyNext(text, topK=10){
|
| 250 |
-
if (!tokenizer || !model) { setErr("Model not loaded yet — check the status bar
|
| 251 |
const enc = await tokenizer(text || " ", { add_special_tokens:false });
|
| 252 |
const t0 = performance.now();
|
| 253 |
const out = await model(enc.input_ids, { attention_mask: enc.attention_mask });
|
|
@@ -269,8 +286,8 @@
|
|
| 269 |
}
|
| 270 |
|
| 271 |
function renderRows(rows){
|
| 272 |
-
klistEl.innerHTML = "";
|
| 273 |
const hide = hidePunc.checked;
|
|
|
|
| 274 |
for (const r of rows){
|
| 275 |
if (hide && PUNC_ONLY.test(r.token)) continue;
|
| 276 |
const row = document.createElement('div'); row.className='tokrow';
|
|
@@ -297,7 +314,7 @@
|
|
| 297 |
}
|
| 298 |
}
|
| 299 |
|
| 300 |
-
|
| 301 |
predictBtn.addEventListener('click', predict);
|
| 302 |
textEl.addEventListener('input', (() => { let to; return () => { clearTimeout(to); to = setTimeout(predict, 250); }; })());
|
| 303 |
hidePunc.addEventListener('change', predict);
|
|
@@ -305,12 +322,12 @@
|
|
| 305 |
demoBtn.addEventListener('click', () => { textEl.value = "Twinkle, twinkle, little "; predict(); });
|
| 306 |
modelSel.addEventListener('change', async (e) => { await loadModel(e.target.value); predict(); });
|
| 307 |
|
| 308 |
-
|
| 309 |
(async function init(){
|
| 310 |
-
await loadModel(modelSel.value);
|
| 311 |
if (!textEl.value) textEl.value = "Twinkle, twinkle, little ";
|
| 312 |
await predict();
|
| 313 |
})();
|
| 314 |
</script>
|
| 315 |
</body>
|
| 316 |
-
</html>
|
|
|
|
| 10 |
header { position:sticky; top:0; z-index:5; display:flex; gap:12px; align-items:center; padding:12px 16px; background:#0e1629; border-bottom:1px solid #1c2945; }
|
| 11 |
h1 { font-size:16px; font-weight:600; margin:0; letter-spacing:.2px; }
|
| 12 |
main { padding:14px; }
|
| 13 |
+
.grid { display:grid; gap:14px; grid-template-columns: 0.35fr 0.65fr; } /* fixed 'fr' spacing */
|
| 14 |
@media (max-width: 1000px){ .grid { grid-template-columns:1fr; } }
|
| 15 |
|
| 16 |
.card { background:#0e162b; border:1px solid #1c2945; border-radius:14px; padding:12px; }
|
|
|
|
| 40 |
.small { font-size:12px; }
|
| 41 |
</style>
|
| 42 |
|
| 43 |
+
<!-- Transformers.js for browsers (CDN). The npm snippet is for bundlers; this is correct for Spaces. -->
|
| 44 |
<script type="module">
|
| 45 |
import {
|
| 46 |
env,
|
|
|
|
| 68 |
<div class="inline">
|
| 69 |
<span class="muted small">Model:</span>
|
| 70 |
<select id="model" class="select">
|
| 71 |
+
<option value="qwen" selected>Qwen3-0.6B (Hub, int8)</option>
|
| 72 |
<option value="distilgpt2">distilgpt2 (local → Hub fallback)</option>
|
| 73 |
</select>
|
| 74 |
</div>
|
|
|
|
| 116 |
<script type="module">
|
| 117 |
const { env, AutoTokenizer, AutoModelForCausalLM } = window.HF;
|
| 118 |
|
| 119 |
+
/* ---------- Environment tuning ---------- */
|
| 120 |
env.useBrowserCache = true;
|
| 121 |
env.backends.onnx.wasm.proxy = true;
|
| 122 |
env.backends.onnx.wasm.numThreads = Math.min(
|
| 123 |
4, Math.max(1, Math.floor((navigator.hardwareConcurrency || 4)/2))
|
| 124 |
);
|
| 125 |
|
| 126 |
+
/* ---------- DOM ---------- */
|
| 127 |
const $ = (s) => document.querySelector(s);
|
| 128 |
const statusEl = $('#status'), barEl = $('#bar'), errEl = $('#error');
|
| 129 |
const textEl = $('#text'), klistEl = $('#klist'), timeEl = $('#time');
|
|
|
|
| 132 |
const embCanvas = $('#embCanvas'), embCtx = embCanvas.getContext('2d');
|
| 133 |
const embStatus = $('#embStatus');
|
| 134 |
|
| 135 |
+
/* ---------- Progress ---------- */
|
| 136 |
function setStatus(t){ if(statusEl) statusEl.textContent = t; }
|
| 137 |
function onProgress(evt){
|
| 138 |
if (!barEl) return;
|
|
|
|
| 149 |
function showToken(s){ if (s === "\n") return "⏎"; if (s.trim() === "") return `␣${s.length>1 ? "×"+s.length : ""}`; return s; }
|
| 150 |
const PUNC_ONLY = /^[\s.,;:!?—-]+$/;
|
| 151 |
|
| 152 |
+
/* ---------- Model registry (Qwen = remote-only) ---------- */
|
|
|
|
|
|
|
| 153 |
const MODELS = {
|
| 154 |
qwen: {
|
| 155 |
remote: "onnx-community/Qwen3-0.6B-ONNX",
|
| 156 |
dtype: "int8",
|
| 157 |
emb: {
|
| 158 |
+
coords: "assets/embeddings/qwen_pca_top5k_coords.json",
|
| 159 |
+
nbrs: "assets/embeddings/qwen_neighbors_top5k_k40.json"
|
| 160 |
}
|
| 161 |
},
|
| 162 |
distilgpt2: {
|
| 163 |
+
local: new URL("./assets/models/distilgpt2/", window.location.href).href,
|
| 164 |
remote: "Xenova/distilgpt2",
|
| 165 |
dtype: undefined,
|
| 166 |
emb: {
|
| 167 |
+
coords: "assets/embeddings/pca_top5k_coords.json",
|
| 168 |
+
nbrs: "assets/embeddings/neighbors_top5k_k40.json"
|
| 169 |
}
|
| 170 |
}
|
| 171 |
};
|
| 172 |
|
| 173 |
+
/* ---------- Embedding viewer ---------- */
|
| 174 |
const Emb = (() => {
|
| 175 |
let coordsPath = "", nbrsPath = "";
|
| 176 |
let points = [], index = new Map(), neighbors = new Map();
|
| 177 |
let baseDrawn = false;
|
| 178 |
|
| 179 |
function setSources(modelKey){
|
| 180 |
+
coordsPath = MODELS[modelKey].emb.coords;
|
| 181 |
+
nbrsPath = MODELS[modelKey].emb.nbrs;
|
| 182 |
}
|
| 183 |
async function load(){
|
| 184 |
baseDrawn = false; index.clear(); points = []; neighbors.clear();
|
|
|
|
| 208 |
return { setSources, load, drawBase, highlight };
|
| 209 |
})();
|
| 210 |
|
| 211 |
+
/* ---------- Core model state ---------- */
|
| 212 |
let tokenizer=null, model=null;
|
| 213 |
|
| 214 |
+
// Small config shim: tell Transformers.js to treat Qwen3 as Qwen2
|
| 215 |
+
const QWEN3_CONFIG_FIX = {
|
| 216 |
+
model_type: "qwen2",
|
| 217 |
+
architectures: ["Qwen2ForCausalLM"]
|
| 218 |
+
};
|
| 219 |
+
|
| 220 |
async function loadModel(key){
|
| 221 |
const cfg = MODELS[key];
|
| 222 |
|
| 223 |
+
// Load embeddings for this model
|
| 224 |
Emb.setSources(key);
|
| 225 |
try { await Emb.load(); } catch { embStatus.textContent = "Map failed to load"; }
|
| 226 |
|
| 227 |
+
setErr(""); setStatus("Loading tokenizer…"); if (barEl) barEl.style.width = "0%";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
|
| 229 |
+
if (key === "qwen") {
|
| 230 |
+
// --- Remote-only for Qwen (Hub) ---
|
| 231 |
+
tokenizer = await AutoTokenizer.from_pretrained(cfg.remote, { progress_callback: onProgress });
|
| 232 |
+
setStatus("Loading model…");
|
| 233 |
+
model = await AutoModelForCausalLM.from_pretrained(cfg.remote, {
|
| 234 |
+
dtype: cfg.dtype,
|
| 235 |
+
progress_callback: onProgress,
|
| 236 |
+
config: QWEN3_CONFIG_FIX // <-- the crucial override
|
| 237 |
+
});
|
| 238 |
+
} else {
|
| 239 |
+
// --- distilgpt2: prefer local, fallback to Hub ---
|
| 240 |
+
try {
|
| 241 |
+
tokenizer = await AutoTokenizer.from_pretrained(cfg.local, { progress_callback: onProgress });
|
| 242 |
+
} catch {
|
| 243 |
+
tokenizer = await AutoTokenizer.from_pretrained(cfg.remote, { progress_callback: onProgress });
|
| 244 |
+
}
|
| 245 |
+
setStatus("Loading model…");
|
| 246 |
+
try {
|
| 247 |
+
model = await AutoModelForCausalLM.from_pretrained(cfg.local, { dtype: cfg.dtype, progress_callback: onProgress });
|
| 248 |
+
} catch {
|
| 249 |
+
model = await AutoModelForCausalLM.from_pretrained(cfg.remote, { dtype: cfg.dtype, progress_callback: onProgress });
|
| 250 |
+
}
|
| 251 |
}
|
| 252 |
|
| 253 |
+
if (!tokenizer || !model) {
|
| 254 |
setStatus("Load failed");
|
| 255 |
+
setErr("Couldn’t load model. Check console for details.");
|
| 256 |
return;
|
| 257 |
}
|
| 258 |
|
|
|
|
| 262 |
setStatus("Ready");
|
| 263 |
}
|
| 264 |
|
| 265 |
+
/* ---------- Next-token logic ---------- */
|
| 266 |
async function greedyNext(text, topK=10){
|
| 267 |
+
if (!tokenizer || !model) { setErr("Model not loaded yet — check the status bar."); return {rows:[],dt:0}; }
|
| 268 |
const enc = await tokenizer(text || " ", { add_special_tokens:false });
|
| 269 |
const t0 = performance.now();
|
| 270 |
const out = await model(enc.input_ids, { attention_mask: enc.attention_mask });
|
|
|
|
| 286 |
}
|
| 287 |
|
| 288 |
function renderRows(rows){
|
|
|
|
| 289 |
const hide = hidePunc.checked;
|
| 290 |
+
klistEl.innerHTML = "";
|
| 291 |
for (const r of rows){
|
| 292 |
if (hide && PUNC_ONLY.test(r.token)) continue;
|
| 293 |
const row = document.createElement('div'); row.className='tokrow';
|
|
|
|
| 314 |
}
|
| 315 |
}
|
| 316 |
|
| 317 |
+
/* ---------- UI ---------- */
|
| 318 |
predictBtn.addEventListener('click', predict);
|
| 319 |
textEl.addEventListener('input', (() => { let to; return () => { clearTimeout(to); to = setTimeout(predict, 250); }; })());
|
| 320 |
hidePunc.addEventListener('change', predict);
|
|
|
|
| 322 |
demoBtn.addEventListener('click', () => { textEl.value = "Twinkle, twinkle, little "; predict(); });
|
| 323 |
modelSel.addEventListener('change', async (e) => { await loadModel(e.target.value); predict(); });
|
| 324 |
|
| 325 |
+
/* ---------- Boot ---------- */
|
| 326 |
(async function init(){
|
| 327 |
+
await loadModel(modelSel.value); // defaults to 'qwen' (remote-only + config override)
|
| 328 |
if (!textEl.value) textEl.value = "Twinkle, twinkle, little ";
|
| 329 |
await predict();
|
| 330 |
})();
|
| 331 |
</script>
|
| 332 |
</body>
|
| 333 |
+
</html>
|