Why
We use Qwen3.8-27B for English-language legal drafting. In that setting the CJK part of the vocabulary does no useful work, and sometimes does harm: the base model occasionally emits a stray Chinese character mid-sentence in English output. Removing those entries:
- makes stray CJK output impossible through the normal vocabulary;
- shrinks both vocabulary tables by 27%, which saves memory and a little output-head compute on every token;
- is the first step of a planned domain-vocabulary experiment (see "What this release is not").
It is a subtractive, verifiable change, so it can be proven not to alter English behaviour.
What was removed and what was kept
An entry was removed when any text containing its bytes must contain a CJK code point. That covers:
- entries holding a complete CJK character;
- byte fragments whose leading or trailing partial UTF-8 sequence can only complete to a CJK character. This was checked by exhaustive enumeration over all code points.
Table with columns: Removed, Entries| Removed | Entries |
|---|
| Han (Chinese characters, incl. Japanese kanji) | 54,715 |
| Hangul (Korean) | 6,807 |
| Hiragana | 2,487 |
| Katakana | 1,678 |
| CJK and fullwidth punctuation/forms | ~250 |
| Byte fragments that can only complete to CJK | 601 |
| Total | 66,533 |
The CJK script set covers Hangul Jamo, CJK radicals, ideographic description characters, CJK symbols and punctuation, kana, Bopomofo, enclosed CJK letters, CJK compatibility, CJK Unified Ideographs with extensions A–H, compatibility ideographs and forms, and halfwidth/fullwidth forms.
Kept, deliberately:
- all 256 single-byte entries, so any UTF-8 text can still be encoded;
- every special, control, chat, thinking, tool, image, video and vision token;
§, ¶, curly quotes, en/em dashes, accented Latin letters, Greek, math symbols and digits. ASCII-only trims lose these, and they matter in legal text;
- Cyrillic, Arabic, Hebrew, Thai, Indic and every other non-CJK script.
A broader trim that also removed the other non-Latin scripts would have dropped 103,447 entries (2.12 GB) and changed the tokenization of none of our English test documents. We chose not to do it, so the model keeps its other languages.
Dependency closure: the tokenizer is byte-level BPE, so a kept entry can depend on a merge that passes through a removed one. We checked every retained merge. None needed a removed entry: 0 entries had to be restored and there were 0 closure errors. The merge list is rebuilt without the removed merges, and no library helper that renumbers IDs was used.
How it was made
All work ran on one NVIDIA DGX Spark (GB10), using torch 2.11.0+cu130, transformers 5.18.0 and tokenizers 0.23.2. The base is Qwen/Qwen3.8-27B at revision 1d4bf0f2ff6012fd82039f2fa52739d0dd7c60c0. All 19 files were checked against their SHA-256 hashes before and after; the base was only read.
- Tokenizer surgery. The removal set was classified from the decoded byte-level vocabulary. The removed entries and their merges were deleted, and the kept entries were renumbered in their original order.
tokenizer.json, vocab.json, merges.txt and tokenizer_config.json (including added_tokens_decoder) were rewritten.
- ID remap.
remap/id-remap.json holds the full new_to_old map for all 181,760 rows. The 216 padding rows are the base's own padding rows.
- Checkpoint export (streaming). 16 of the 18 safetensors shards are byte-for-byte copies. The two shards holding
model.language_model.embed_tokens.weight and lm_head.weight (both [181760, 5120]; the tables are untied) were rewritten with the kept rows in new-ID order.
- Config rebuild. and every special ID were updated in and : bos, eos, pad, image, video, and vision start and end. Exactly 7 config keys differ from the base, all expected. Each special ID still resolves to the same token string as in the base, and the chat template's token IDs map exactly.
The surgery, export and proof code is in trim-code/ (tokenizer_surgery.py, export_trim.py, prove_trim.py). provenance.json records the base revision, the tokenizer hash and the SHA-256 of every shard.
Evaluation (inference only)
The test text was a private corpus of about 3,500 English legal documents: pleadings, motions, affidavits, orders, agreements and correspondence, plus treatise and statute text. It is not released.
Table with columns: Check, Result| Check | Result |
|---|
| Tokenization of English legal text | 3,526 / 3,526 documents tokenize identically after the remap |
| Byte-exact decode round trips | 3,540 / 3,540 |
| Logits vs. base (32 held-out excerpts, ≤384 tokens each) | bit-identical: max abs difference 0.0, KL 0.0 |
| Greedy generations vs. base (24 English prompts) | 24 / 24 token-for-token identical |
| CJK characters in English output | 0 |
| Chinese-language prompts | Base answered in Chinese; the trimmed model answered in English with 0 CJK characters |
§, ¶, curly quotes, dashes, accented names |
The logits are bit-identical because the removed rows only ever competed in the output softmax. On text where the base never chose them, every remaining logit is computed by exactly the same weights.
Usage
The interface is the same as the base model's:
from transformers import AutoProcessor, AutoModelForImageTextToText model_id = "ProprietaryLegal/Qwen3.8-27B-CJK-Trim"processor = AutoProcessor.from_pretrained(model_id)model = AutoModelForImageTextToText.from_pretrained(model_id, dtype="bfloat16", device_map="auto") messages = [{"role": "user", "content": [{"type": "text", "text": "Explain what a pendente lite order is."}]}]inputs = processor.apply_chat_template(messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt").to(model.device)out = model.generate(**inputs, max_new_tokens=512)print(processor.decode(out[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True))
We tested it with transformers 5.18. vLLM, llama.cpp and other runtimes should work, because the architecture is unchanged and only vocab_size differs, but we have not tested them yet.
Limitations and compatibility
- Chinese, Japanese and Korean are gone from the vocabulary. CJK input still encodes, through single-byte entries at about 3 tokens per character, but the model reads such text poorly and answers in English. Do not use this model for any CJK-language task.
- Token IDs differ from the base. Use only the tokenizer shipped in this repo. Any cached token IDs, logit-bias tables or stop-token ID lists built for the base must be remapped with
remap/id-remap.json.
- Adapters and derived copies:
- LoRA adapters trained on the base that do not touch
embed_tokens or lm_head apply unchanged, because every other weight is identical. Adapters that do touch them need their rows remapped.
- Quantized copies (GGUF, AWQ, NVFP4 and the like) must be made from this checkpoint, not converted from a base quant.
- The size and speed gain is modest: about 2.5% of parameters, and slightly less output-head work per token. The main benefit is the guarantee against stray CJK output.
What this release is not
- Not fine-tuned. No training, distillation or data of any kind went into it. It is a structural subset of the base weights.
- Not a legal-domain model. Its legal knowledge and style are exactly the base model's.
- No new vocabulary added. A separate, unreleased experiment recycles never-reachable vocabulary slots for legal-English word pieces. Nothing from that experiment is in this repository.
Prior work
License
Apache 2.0, inherited from Qwen/Qwen3.8-27B. The original LICENSE file is included unchanged. All credit for the model itself belongs to the Qwen team.