feat(qwen3): bundle Qwen3 tokenizer for offline single-file/GGUF encoders - #9338
Conversation
…ders" -m "Single-file (safetensors) and GGUF Qwen3 encoder checkpoints used by Anima (0.6B) and Z-Image (4B/8B) ship weights only — no tokenizer files. The loader pulled the tokenizer from Qwen/Qwen3-4B on HuggingFace, which fails offline / airgapped and whenever the HF cache is not persisted (e.g. Docker without a cache volume). Vendor the self-contained Qwen3 fast tokenizer (Apache-2.0, from Qwen/Qwen3-4B) in the package and load it locally, mirroring the bundled T5-XXL tokenizer (invoke-ai#9244). The Qwen3 BPE tokenizer is identical across the 0.6B/4B/8B variants, so a single copy serves every Qwen3 encoder. Removes the HuggingFace download path from both the checkpoint and GGUF loaders.
The vendored Qwen3 tokenizer.json is ~11MB, over the repo's 10MB lfs-warning threshold, failing the "lfs checks" CI job. Git LFS is unsuitable here since the file must ship inside the wheel for offline use. Vendor it gzip-compressed (~2MB) instead and decompress into a temp dir at load time. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…kenizer # Conflicts: # pyproject.toml
|
Pushed two fixes: 1. LFS check failure ( 2. Merge conflict with main ( |
The single-file/GGUF Qwen3 loaders now use the vendored tokenizer, but the call-site comments still described the removed HuggingFace download path and the method was still named _load_tokenizer_with_offline_fallback despite having no fallback. Rename to _load_bundled_tokenizer and update the comments to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
Pushed a small follow-up cleanup (
Purely comments + a private method rename — no behavior change. Left the identically-named method in |
The vendored tokenizer_config.json was missing the chat_template that
Qwen/Qwen3-4B ships. The Z-Image text encoder formats prompts via
tokenizer.apply_chat_template(), which raises
ValueError: Cannot use chat template functions because
tokenizer.chat_template is not set ...
so GGUF/single-file Qwen3 encoders failed at encode time. The old
HF-download path pulled the full config (template included), so this
was a regression introduced by bundling. Restore the exact upstream
Qwen3-4B chat_template and add a regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
lstein
left a comment
There was a problem hiding this comment.
Approving — tested the fully-offline path end to end (Z-Image Turbo GGUF Q4_K + Qwen3 4B GGUF Q6_K encoder, HF cache removed, HF_HUB_OFFLINE=1): generation succeeds with no network access.
I pushed a few follow-up commits while reviewing:
1. LFS check fix (2d5314e903) — tokenizer.json was ~11 MB, over the repo's 10 MB lfs-warning threshold, failing the lfs checks job. LFS is unsuitable here (the file must ship inside the wheel for offline use), so it's now vendored gzip-compressed (tokenizer.json.gz, ~2 MB) and decompressed into a temp dir at load time. pyproject.toml package-data updated accordingly.
2. Merge with main (680d698e85) — resolved a pyproject.toml package-data conflict (main added a t5 tokenizer entry; kept all three: anima/qwen3/t5).
3. Loader comment/name cleanup (666723c216) — renamed _load_tokenizer_with_offline_fallback → _load_bundled_tokenizer (no fallback remains) and fixed two stale call-site comments still referencing the removed HF-download path.
4. Restore chat_template (71a988faf3) — the real fix for a runtime crash. The vendored tokenizer_config.json shipped without the chat_template that Qwen/Qwen3-4B includes inline. The Z-Image encoder formats prompts via tokenizer.apply_chat_template(), which raised:
ValueError: Cannot use chat template functions because tokenizer.chat_template is not set ...
so GGUF/single-file encoders crashed at encode time. This was a regression from bundling — the old HF-download path pulled the full config. I restored the exact upstream Qwen3-4B chat_template (verified the config was otherwise byte-identical to the vendored one) and added a regression test so it can't be dropped again. All 6 tokenizer tests pass, ruff clean.
Note: I deliberately did not widen the encoder's except (AttributeError, TypeError) to swallow the ValueError — for Z-Image the chat template materially affects output quality, so shipping the correct template is the right fix rather than silently falling back to the raw prompt.
Summary
Single-file (safetensors) and GGUF Qwen3 encoder checkpoints used by Anima
(0.6B) and Z-Image (4B/8B) ship weights only — no tokenizer files. The loader
pulled the tokenizer from
Qwen/Qwen3-4Bon HuggingFace, which fails offline /airgapped and whenever the HF cache is not persisted (e.g. Docker without a
cache volume).
Vendor the self-contained Qwen3 fast tokenizer (Apache-2.0, from
Qwen/Qwen3-4B)in the package and load it locally, mirroring the bundled T5-XXL tokenizer
(#9244). The Qwen3 BPE tokenizer is identical across the 0.6B/4B/8B variants, so
a single copy serves every Qwen3 encoder. Removes the HuggingFace download path
from both the checkpoint and GGUF loaders.
Changes
invokeai/backend/qwen3/tokenizer/(tokenizer.json,tokenizer_config.json,special_tokens_map.json).load_bundled_qwen3_tokenizer()helper (invokeai/backend/qwen3/qwen3_tokenizer.py), mirroringanima/t5_tokenizer.py.Qwen3EncoderCheckpointLoaderandQwen3EncoderGGUFLoaderat the bundled tokenizer; drop the deadDEFAULT_TOKENIZER_SOURCEHF-download fallback.pyproject.tomlso they ship in the wheel.Related Issues / Discussions
Follows the same approach as #9244 (bundled T5-XXL tokenizer for Anima). No linked issue.
A follow-up will apply the same fix to the Qwen2.5-VL encoder (Qwen-Image / Qwen-Image-Edit), which has the identical single-file problem for its tokenizer, config, and image processor.
QA Instructions
Automated: run the new offline test — fully passes without network:
Manual (offline single-file encoder):
~/.cache/huggingface/hub/models--Qwen--Qwen3-4B.HF_HUB_OFFLINE=1)..safetensors) and run a generation.OSError/ HF download error.Regression: confirm a diffusers-folder Qwen3 encoder (with its own
tokenizer/) and the GGUF encoder still load and generate correctly.Merge Plan
Standard merge — no DB/schema changes. Note: adds ~11 MB of vendored tokenizer JSON to the package (same rationale/precedent as the bundled T5 tokenizer in #9244).
Checklist
What's Newcopy (if doing a release after this PR) — N/A