Text Generation
Safetensors
GGUF
English
Chinese
qwen2
reasoning-compression
cjk
chain-of-thought
distillation
qwen2.5
conversational
Instructions to use ZelligeAI/tessera-compressor with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- llama-cpp-python
How to use ZelligeAI/tessera-compressor with llama-cpp-python:
# !pip install llama-cpp-python from llama_cpp import Llama llm = Llama.from_pretrained( repo_id="ZelligeAI/tessera-compressor", filename="gguf/compressor-v31-q8_0.gguf", )
llm.create_chat_completion( messages = [ { "role": "user", "content": "What is the capital of France?" } ] ) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use ZelligeAI/tessera-compressor with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf ZelligeAI/tessera-compressor:Q8_0 # Run inference directly in the terminal: llama cli -hf ZelligeAI/tessera-compressor:Q8_0
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf ZelligeAI/tessera-compressor:Q8_0 # Run inference directly in the terminal: llama cli -hf ZelligeAI/tessera-compressor:Q8_0
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf ZelligeAI/tessera-compressor:Q8_0 # Run inference directly in the terminal: ./llama-cli -hf ZelligeAI/tessera-compressor:Q8_0
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf ZelligeAI/tessera-compressor:Q8_0 # Run inference directly in the terminal: ./build/bin/llama-cli -hf ZelligeAI/tessera-compressor:Q8_0
Use Docker
docker model run hf.co/ZelligeAI/tessera-compressor:Q8_0
- LM Studio
- Jan
- vLLM
How to use ZelligeAI/tessera-compressor with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "ZelligeAI/tessera-compressor" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "ZelligeAI/tessera-compressor", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/ZelligeAI/tessera-compressor:Q8_0
- Ollama
How to use ZelligeAI/tessera-compressor with Ollama:
ollama run hf.co/ZelligeAI/tessera-compressor:Q8_0
- Unsloth Studio
How to use ZelligeAI/tessera-compressor with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for ZelligeAI/tessera-compressor to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for ZelligeAI/tessera-compressor to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for ZelligeAI/tessera-compressor to start chatting
- Pi
How to use ZelligeAI/tessera-compressor with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf ZelligeAI/tessera-compressor:Q8_0
Configure the model in Pi
# Install Pi: npm install -g @mariozechner/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "ZelligeAI/tessera-compressor:Q8_0" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Hermes Agent new
How to use ZelligeAI/tessera-compressor with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf ZelligeAI/tessera-compressor:Q8_0
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default ZelligeAI/tessera-compressor:Q8_0
Run Hermes
hermes
- Atomic Chat new
- OpenClaw new
How to use ZelligeAI/tessera-compressor with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf ZelligeAI/tessera-compressor:Q8_0
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "ZelligeAI/tessera-compressor:Q8_0" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
- Docker Model Runner
How to use ZelligeAI/tessera-compressor with Docker Model Runner:
docker model run hf.co/ZelligeAI/tessera-compressor:Q8_0
- Lemonade
How to use ZelligeAI/tessera-compressor with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull ZelligeAI/tessera-compressor:Q8_0
Run and chat with the model
lemonade run user.tessera-compressor-Q8_0
List all available models
lemonade list
Initial release: v3.1 merged weights, q8_0 GGUF, usage harness, card
Browse files- .gitattributes +2 -0
- README.md +93 -0
- banner.png +0 -0
- chat_template.jinja +53 -0
- config.json +62 -0
- generation_config.json +14 -0
- gguf/compressor-v31-q8_0.gguf +3 -0
- model.safetensors +3 -0
- scripts/compress.py +142 -0
- scripts/requirements.txt +2 -0
- scripts/segmenting.py +114 -0
- scripts/tokenmax.py +431 -0
- tokenizer.json +3 -0
- tokenizer_config.json +217 -0
.gitattributes
CHANGED
|
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
gguf/compressor-v31-q8_0.gguf filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
base_model: Qwen/Qwen2.5-Coder-1.5B-Instruct
|
| 4 |
+
language:
|
| 5 |
+
- en
|
| 6 |
+
- zh
|
| 7 |
+
pipeline_tag: text-generation
|
| 8 |
+
tags:
|
| 9 |
+
- reasoning-compression
|
| 10 |
+
- cjk
|
| 11 |
+
- chain-of-thought
|
| 12 |
+
- distillation
|
| 13 |
+
- qwen2.5
|
| 14 |
+
---
|
| 15 |
+
|
| 16 |
+

|
| 17 |
+
|
| 18 |
+
# tessera-compressor
|
| 19 |
+
|
| 20 |
+
A 1.5B model that compresses English reasoning text into a telegraphic CJK/symbol register under deterministic fidelity gates. It minted the training data for [Tessera-Preview-9B](https://huggingface.co/ZelligeAI/tessera-preview-9b) and replaces the frontier-model teacher that originally produced the register: any English reasoning dataset becomes compressed-register training data at local-inference cost, with no API key and no external dependency.
|
| 21 |
+
|
| 22 |
+
Example (real training pair, 85 to 49 tokens):
|
| 23 |
+
|
| 24 |
+
```text
|
| 25 |
+
EN : So the classes are: - Integer (line 32) - Boolean (line 262) - BitString (line 341)
|
| 26 |
+
- OctetString (line 693) ... Let me look at the base class to see if it defines __mul__:
|
| 27 |
+
|
| 28 |
+
CJK: Integer(line32),Boolean(line262),BitString(line341),OctetString(line693). 查基类是否定义__mul__:
|
| 29 |
+
```
|
| 30 |
+
|
| 31 |
+
## How it works
|
| 32 |
+
|
| 33 |
+
The compressor operates on passages, not whole blocks. A reasoning block is segmented (code fences stay atomic), sentences are grouped into step-sized passages, each passage is classified as fact-dense or narrative, and the model compresses it against the tail of the chain built so far. Every model output then passes a deterministic gate: the sets of numbers, identifiers, and operators must survive, the output must not blow up in length, and it must beat a rules-only compression of the same passage in token count. A passage that fails any check falls back to the rules-only version, so a bad generation costs savings, never content.
|
| 34 |
+
|
| 35 |
+
## Acceptance record
|
| 36 |
+
|
| 37 |
+
Measured on 103 held-out reasoning blocks the model never trained on, under criteria fixed before evaluation:
|
| 38 |
+
|
| 39 |
+
| Criterion | Result |
|
| 40 |
+
| --- | --- |
|
| 41 |
+
| Per-passage fidelity gate (numbers, identifiers, operators survive) | 99.0% |
|
| 42 |
+
| Median per-passage compression ratio (output/input tokens) | 0.716 |
|
| 43 |
+
| CJK adoption | 98.9% of compressed passages |
|
| 44 |
+
| Judged semantic equivalence | 103/103 blocks (teacher references on the same blocks: 97.1%) |
|
| 45 |
+
| Degenerate outputs | 0 |
|
| 46 |
+
| Net corpus savings (after 24% rules-only fallback) | 30.4% |
|
| 47 |
+
|
| 48 |
+
On whole thinks in downstream production use (45,202 pairs), the compressed rendering costs a median 0.58x the tokens of its English source.
|
| 49 |
+
|
| 50 |
+
## Files
|
| 51 |
+
|
| 52 |
+
- Root: merged model, standard Hugging Face format (bf16). Base: Qwen2.5-Coder-1.5B-Instruct, LoRA r=16 merged in.
|
| 53 |
+
- `gguf/compressor-v31-q8_0.gguf`: llama.cpp quantization, validated behaviorally (scores 4/4 on the same acceptance suite). q4_k_m showed visible drift and is not published.
|
| 54 |
+
- `scripts/`: the complete usage harness. No tokens or keys required anywhere.
|
| 55 |
+
|
| 56 |
+
## Usage
|
| 57 |
+
|
| 58 |
+
Serve the model behind any OpenAI-compatible endpoint:
|
| 59 |
+
|
| 60 |
+
```bash
|
| 61 |
+
vllm serve ZelligeAI/tessera-compressor --port 8001
|
| 62 |
+
# or, CPU-friendly:
|
| 63 |
+
llama-server -m gguf/compressor-v31-q8_0.gguf --port 8001
|
| 64 |
+
```
|
| 65 |
+
|
| 66 |
+
Then run the harness:
|
| 67 |
+
|
| 68 |
+
```bash
|
| 69 |
+
cd scripts && pip install -r requirements.txt
|
| 70 |
+
|
| 71 |
+
# compress one reasoning block from a text file
|
| 72 |
+
python compress.py --in think.txt --endpoint http://localhost:8001/v1
|
| 73 |
+
|
| 74 |
+
# compress a corpus: {"id": ..., "text": ...} per JSONL line
|
| 75 |
+
python compress.py --in blocks.jsonl --out compressed.jsonl \
|
| 76 |
+
--endpoint http://localhost:8001/v1
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
Output records carry the compressed text, source and output token counts, and per-block harness stats (model-accepted vs rules-fallback passage counts).
|
| 80 |
+
|
| 81 |
+
`scripts/` contents:
|
| 82 |
+
|
| 83 |
+
- `compress.py`: the driver. Segment, classify, compress per passage with chain context, gate, fall back on failure.
|
| 84 |
+
- `segmenting.py`: segmentation, passage grouping, fact extraction, classification, and the fidelity gate. Pure text processing.
|
| 85 |
+
- `tokenmax.py`: deterministic token-saving substitutions, used as the rules-only fallback and as a post-processor.
|
| 86 |
+
|
| 87 |
+
One note on token counting: the gate compares token counts under a tokenizer you choose (`--tokenizer`, default this repo). To reproduce the acceptance harness exactly, point it at the tokenizer of the model you are minting data for (the acceptance run used the Qwen3.5 target tokenizer).
|
| 88 |
+
|
| 89 |
+
Throughput on the acceptance hardware was 19.6K blocks/hour on one GPU, which is why the marginal cost of minting compressed data rounds to zero.
|
| 90 |
+
|
| 91 |
+
## License
|
| 92 |
+
|
| 93 |
+
Apache-2.0, same as the base model.
|
banner.png
ADDED
|
chat_template.jinja
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{%- if tools %}
|
| 2 |
+
{{- '<|im_start|>system\n' }}
|
| 3 |
+
{%- if messages[0]['role'] == 'system' %}
|
| 4 |
+
{{- messages[0]['content'] }}
|
| 5 |
+
{%- else %}
|
| 6 |
+
{{- 'You are Qwen, created by Alibaba Cloud. You are a helpful assistant.' }}
|
| 7 |
+
{%- endif %}
|
| 8 |
+
{{- "\n\n# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
|
| 9 |
+
{%- for tool in tools %}
|
| 10 |
+
{{- "\n" }}
|
| 11 |
+
{{- tool | tojson }}
|
| 12 |
+
{%- endfor %}
|
| 13 |
+
{{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
|
| 14 |
+
{%- else %}
|
| 15 |
+
{%- if messages[0]['role'] == 'system' %}
|
| 16 |
+
{{- '<|im_start|>system\n' + messages[0]['content'] + '<|im_end|>\n' }}
|
| 17 |
+
{%- else %}
|
| 18 |
+
{{- '<|im_start|>system\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>\n' }}
|
| 19 |
+
{%- endif %}
|
| 20 |
+
{%- endif %}
|
| 21 |
+
{%- for message in messages %}
|
| 22 |
+
{%- if (message.role == "user") or (message.role == "system" and not loop.first) or (message.role == "assistant" and not message.tool_calls) %}
|
| 23 |
+
{{- '<|im_start|>' + message.role + '\n' + message.content + '<|im_end|>' + '\n' }}
|
| 24 |
+
{%- elif message.role == "assistant" %}
|
| 25 |
+
{{- '<|im_start|>' + message.role }}
|
| 26 |
+
{%- if message.content %}
|
| 27 |
+
{{- '\n' + message.content }}
|
| 28 |
+
{%- endif %}
|
| 29 |
+
{%- for tool_call in message.tool_calls %}
|
| 30 |
+
{%- if tool_call.function is defined %}
|
| 31 |
+
{%- set tool_call = tool_call.function %}
|
| 32 |
+
{%- endif %}
|
| 33 |
+
{{- '\n<tool_call>\n{"name": "' }}
|
| 34 |
+
{{- tool_call.name }}
|
| 35 |
+
{{- '", "arguments": ' }}
|
| 36 |
+
{{- tool_call.arguments | tojson }}
|
| 37 |
+
{{- '}\n</tool_call>' }}
|
| 38 |
+
{%- endfor %}
|
| 39 |
+
{{- '<|im_end|>\n' }}
|
| 40 |
+
{%- elif message.role == "tool" %}
|
| 41 |
+
{%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != "tool") %} {{- '<|im_start|>user' }}
|
| 42 |
+
{%- endif %}
|
| 43 |
+
{{- '\n<tool_response>\n' }}
|
| 44 |
+
{{- message.content }}
|
| 45 |
+
{{- '\n</tool_response>' }}
|
| 46 |
+
{%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
|
| 47 |
+
{{- '<|im_end|>\n' }}
|
| 48 |
+
{%- endif %}
|
| 49 |
+
{%- endif %}
|
| 50 |
+
{%- endfor %}
|
| 51 |
+
{%- if add_generation_prompt %}
|
| 52 |
+
{{- '<|im_start|>assistant\n' }}
|
| 53 |
+
{%- endif %}
|
config.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"architectures": [
|
| 3 |
+
"Qwen2ForCausalLM"
|
| 4 |
+
],
|
| 5 |
+
"attention_dropout": 0.0,
|
| 6 |
+
"bos_token_id": null,
|
| 7 |
+
"torch_dtype": "bfloat16",
|
| 8 |
+
"eos_token_id": 151645,
|
| 9 |
+
"hidden_act": "silu",
|
| 10 |
+
"hidden_size": 1536,
|
| 11 |
+
"initializer_range": 0.02,
|
| 12 |
+
"intermediate_size": 8960,
|
| 13 |
+
"layer_types": [
|
| 14 |
+
"full_attention",
|
| 15 |
+
"full_attention",
|
| 16 |
+
"full_attention",
|
| 17 |
+
"full_attention",
|
| 18 |
+
"full_attention",
|
| 19 |
+
"full_attention",
|
| 20 |
+
"full_attention",
|
| 21 |
+
"full_attention",
|
| 22 |
+
"full_attention",
|
| 23 |
+
"full_attention",
|
| 24 |
+
"full_attention",
|
| 25 |
+
"full_attention",
|
| 26 |
+
"full_attention",
|
| 27 |
+
"full_attention",
|
| 28 |
+
"full_attention",
|
| 29 |
+
"full_attention",
|
| 30 |
+
"full_attention",
|
| 31 |
+
"full_attention",
|
| 32 |
+
"full_attention",
|
| 33 |
+
"full_attention",
|
| 34 |
+
"full_attention",
|
| 35 |
+
"full_attention",
|
| 36 |
+
"full_attention",
|
| 37 |
+
"full_attention",
|
| 38 |
+
"full_attention",
|
| 39 |
+
"full_attention",
|
| 40 |
+
"full_attention",
|
| 41 |
+
"full_attention"
|
| 42 |
+
],
|
| 43 |
+
"max_position_embeddings": 32768,
|
| 44 |
+
"max_window_layers": 21,
|
| 45 |
+
"model_type": "qwen2",
|
| 46 |
+
"num_attention_heads": 12,
|
| 47 |
+
"num_hidden_layers": 28,
|
| 48 |
+
"num_key_value_heads": 2,
|
| 49 |
+
"pad_token_id": 151665,
|
| 50 |
+
"rms_norm_eps": 1e-06,
|
| 51 |
+
"rope_parameters": {
|
| 52 |
+
"rope_theta": 1000000.0,
|
| 53 |
+
"rope_type": "default"
|
| 54 |
+
},
|
| 55 |
+
"sliding_window": null,
|
| 56 |
+
"tie_word_embeddings": true,
|
| 57 |
+
"unsloth_fixed": true,
|
| 58 |
+
"unsloth_version": "2026.7.1",
|
| 59 |
+
"use_cache": true,
|
| 60 |
+
"use_sliding_window": false,
|
| 61 |
+
"vocab_size": 151936
|
| 62 |
+
}
|
generation_config.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"do_sample": true,
|
| 3 |
+
"eos_token_id": [
|
| 4 |
+
151645,
|
| 5 |
+
151643
|
| 6 |
+
],
|
| 7 |
+
"max_length": 32768,
|
| 8 |
+
"pad_token_id": 151665,
|
| 9 |
+
"repetition_penalty": 1.1,
|
| 10 |
+
"temperature": 0.7,
|
| 11 |
+
"top_k": 20,
|
| 12 |
+
"top_p": 0.8,
|
| 13 |
+
"transformers_version": "5.5.0"
|
| 14 |
+
}
|
gguf/compressor-v31-q8_0.gguf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ce8b7b729409c6ae35d8a81fb11fcc9c286c924ec4e7d0b4b052d742af285f4d
|
| 3 |
+
size 1646572480
|
model.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:5660fdaa65f175f6aafb2ebd35e7e6d24e535f0deecc949cd92cc7f7858a3524
|
| 3 |
+
size 3087467144
|
scripts/compress.py
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
compress.py — Compress English reasoning text into the telegraphic CJK register
|
| 4 |
+
using tessera-compressor behind any OpenAI-compatible endpoint (vLLM, llama.cpp
|
| 5 |
+
server, etc.). No API keys or HF token required; the endpoint is yours.
|
| 6 |
+
|
| 7 |
+
This is the same harness the compressor was accepted under: segment the block,
|
| 8 |
+
group sentences into step-sized passages, classify each passage, compress it
|
| 9 |
+
against the chain built so far, then run the deterministic fidelity gate. A
|
| 10 |
+
passage that fails the gate falls back to a rules-only compression, so a bad
|
| 11 |
+
model output costs savings, never content.
|
| 12 |
+
|
| 13 |
+
Serve the model first, e.g.:
|
| 14 |
+
vllm serve ZelligeAI/tessera-compressor --port 8001
|
| 15 |
+
or with the GGUF:
|
| 16 |
+
llama-server -m compressor-v31-q8_0.gguf --port 8001
|
| 17 |
+
|
| 18 |
+
Then:
|
| 19 |
+
# one block from a text file
|
| 20 |
+
python compress.py --in think.txt --endpoint http://localhost:8001/v1
|
| 21 |
+
|
| 22 |
+
# a JSONL corpus: {"id": ..., "text": ...} per line
|
| 23 |
+
python compress.py --in blocks.jsonl --out compressed.jsonl \
|
| 24 |
+
--endpoint http://localhost:8001/v1
|
| 25 |
+
|
| 26 |
+
Token counting: the fidelity gate compares token counts under a target
|
| 27 |
+
tokenizer. For results matching the accepted harness, point --tokenizer at the
|
| 28 |
+
model you are producing training data FOR (default: the compressor's own
|
| 29 |
+
tokenizer, which is close but not identical to the Qwen3.5 target used in the
|
| 30 |
+
acceptance run).
|
| 31 |
+
"""
|
| 32 |
+
import argparse
|
| 33 |
+
import json
|
| 34 |
+
import sys
|
| 35 |
+
|
| 36 |
+
from openai import OpenAI
|
| 37 |
+
from tokenizers import Tokenizer
|
| 38 |
+
|
| 39 |
+
from segmenting import segment, group_steps, classify_passage, facts, gate
|
| 40 |
+
from tokenmax import _apply_subs
|
| 41 |
+
|
| 42 |
+
PASSAGE_SYSTEM = (
|
| 43 |
+
"你是推理压缩器。Re-notate the NEXT PASSAGE of a reasoning chain into telegraphic "
|
| 44 |
+
"CJK/symbol notation. Every NEW logical step, fact, number and identifier must "
|
| 45 |
+
"survive — unless already stated in the chain. Never restate chain content. "
|
| 46 |
+
"[passage=load]: step-lossless telegraphic. [passage=narr]: minimal stubs "
|
| 47 |
+
"(试X→否). Output only the re-notated continuation."
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
MAX_NEW_TOKENS = 512
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def compress_block(text, client, model, ntok):
|
| 54 |
+
"""Compress one reasoning block. Returns (compressed_text, stats)."""
|
| 55 |
+
segs = group_steps(segment(text))
|
| 56 |
+
chain, seen = [], set()
|
| 57 |
+
stats = {"segments": len(segs), "model_ok": 0, "fallback": 0,
|
| 58 |
+
"narr_skipped": 0, "code": 0, "calls": 0}
|
| 59 |
+
|
| 60 |
+
for kind, s in segs:
|
| 61 |
+
if kind == "code":
|
| 62 |
+
chain.append(s)
|
| 63 |
+
seen |= facts(s)
|
| 64 |
+
stats["code"] += 1
|
| 65 |
+
continue
|
| 66 |
+
cls = classify_passage(s, seen, ntok)
|
| 67 |
+
novel = facts(s) - seen
|
| 68 |
+
rules_s, _ = _apply_subs(s)
|
| 69 |
+
if not rules_s.strip():
|
| 70 |
+
continue
|
| 71 |
+
tail = "\n".join(chain)[-500:] or "(start)"
|
| 72 |
+
stats["calls"] += 1
|
| 73 |
+
r = client.chat.completions.create(
|
| 74 |
+
model=model, temperature=0.0, max_tokens=MAX_NEW_TOKENS,
|
| 75 |
+
messages=[
|
| 76 |
+
{"role": "system", "content": PASSAGE_SYSTEM},
|
| 77 |
+
{"role": "user", "content": f"[passage={cls}]\n链:\n{tail}\n\n段:\n{s[:2000]}"},
|
| 78 |
+
],
|
| 79 |
+
extra_body={"repetition_penalty": 1.15},
|
| 80 |
+
)
|
| 81 |
+
out = (r.choices[0].message.content or "").strip()
|
| 82 |
+
|
| 83 |
+
if out == "∅" and cls == "narr" and not novel:
|
| 84 |
+
stats["narr_skipped"] += 1
|
| 85 |
+
seen |= facts(s)
|
| 86 |
+
continue
|
| 87 |
+
if gate(s, rules_s, out, ntok) is None:
|
| 88 |
+
chain.append(out)
|
| 89 |
+
stats["model_ok"] += 1
|
| 90 |
+
else:
|
| 91 |
+
chain.append(rules_s)
|
| 92 |
+
stats["fallback"] += 1
|
| 93 |
+
seen |= facts(s)
|
| 94 |
+
|
| 95 |
+
return "\n".join(chain), stats
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def main():
|
| 99 |
+
ap = argparse.ArgumentParser(description=__doc__,
|
| 100 |
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
| 101 |
+
ap.add_argument("--in", dest="inp", required=True,
|
| 102 |
+
help=".txt (one block) or .jsonl ({'id','text'} per line)")
|
| 103 |
+
ap.add_argument("--out", default=None, help="output JSONL (default: stdout)")
|
| 104 |
+
ap.add_argument("--endpoint", default="http://localhost:8001/v1")
|
| 105 |
+
ap.add_argument("--model", default="ZelligeAI/tessera-compressor",
|
| 106 |
+
help="served model name at the endpoint")
|
| 107 |
+
ap.add_argument("--tokenizer", default="ZelligeAI/tessera-compressor",
|
| 108 |
+
help="HF repo id or local tokenizer.json for gate token counts")
|
| 109 |
+
args = ap.parse_args()
|
| 110 |
+
|
| 111 |
+
if args.tokenizer.endswith(".json"):
|
| 112 |
+
tok = Tokenizer.from_file(args.tokenizer)
|
| 113 |
+
else:
|
| 114 |
+
tok = Tokenizer.from_pretrained(args.tokenizer)
|
| 115 |
+
|
| 116 |
+
def ntok(s):
|
| 117 |
+
return len(tok.encode(s).ids) if s else 0
|
| 118 |
+
|
| 119 |
+
client = OpenAI(base_url=args.endpoint, api_key="none")
|
| 120 |
+
|
| 121 |
+
if args.inp.endswith(".jsonl"):
|
| 122 |
+
rows = [json.loads(l) for l in open(args.inp) if l.strip()]
|
| 123 |
+
else:
|
| 124 |
+
rows = [{"id": args.inp, "text": open(args.inp).read()}]
|
| 125 |
+
|
| 126 |
+
sink = open(args.out, "w") if args.out else sys.stdout
|
| 127 |
+
for row in rows:
|
| 128 |
+
compressed, stats = compress_block(row["text"], client, args.model, ntok)
|
| 129 |
+
rec = {"id": row.get("id"), "compressed": compressed,
|
| 130 |
+
"src_tokens": ntok(row["text"]), "out_tokens": ntok(compressed),
|
| 131 |
+
"harness": stats}
|
| 132 |
+
sink.write(json.dumps(rec, ensure_ascii=False) + "\n")
|
| 133 |
+
sink.flush()
|
| 134 |
+
print(f"[{row.get('id')}] {rec['src_tokens']} -> {rec['out_tokens']} tokens "
|
| 135 |
+
f"(model_ok={stats['model_ok']} fallback={stats['fallback']})",
|
| 136 |
+
file=sys.stderr)
|
| 137 |
+
if args.out:
|
| 138 |
+
sink.close()
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
if __name__ == "__main__":
|
| 142 |
+
main()
|
scripts/requirements.txt
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
openai>=1.0
|
| 2 |
+
tokenizers>=0.15
|
scripts/segmenting.py
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
segmenting.py — Passage segmentation, classification, and fidelity gates for the
|
| 3 |
+
tessera-compressor harness.
|
| 4 |
+
|
| 5 |
+
Extracted from the harness the compressor was accepted under (same functions the
|
| 6 |
+
teacher mint used). Pure text processing: no network, no credentials.
|
| 7 |
+
|
| 8 |
+
Flow: segment -> group_steps -> classify_passage per passage -> model call ->
|
| 9 |
+
gate -> rules fallback on failure. A failed passage costs a few dozen tokens of
|
| 10 |
+
savings, never content.
|
| 11 |
+
"""
|
| 12 |
+
import re
|
| 13 |
+
|
| 14 |
+
CJK = re.compile(r'[一-鿿㐀-䶿]')
|
| 15 |
+
NUM = re.compile(r'\d+(?:\.\d+)?')
|
| 16 |
+
IDENT = re.compile(r'`[^`\n]+`|\b[A-Za-z]+(?:_[A-Za-z0-9]+)+\b|\b[a-z]+[A-Z][A-Za-z0-9]*\b')
|
| 17 |
+
FENCE = re.compile(r'```.*?```', re.DOTALL)
|
| 18 |
+
SENT_SPLIT = re.compile(r'(?<=[.!?;])\s+')
|
| 19 |
+
_LIST_MARKER = re.compile(r'(?:^|[\n\s(])(\d{1,2})[.)]\s')
|
| 20 |
+
_OPS = set('+-*/=<>≤≥≠∈∀∃¬→⇒%^{}[]')
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def segment(text):
|
| 24 |
+
"""Split a reasoning block into ordered segments; code fences are atomic and marked."""
|
| 25 |
+
segs = [] # (kind, text) kind ∈ {'code','prose'}
|
| 26 |
+
pos = 0
|
| 27 |
+
for m in FENCE.finditer(text):
|
| 28 |
+
before = text[pos:m.start()]
|
| 29 |
+
segs.extend(('prose', s) for s in _split_prose(before))
|
| 30 |
+
segs.append(('code', m.group(0)))
|
| 31 |
+
pos = m.end()
|
| 32 |
+
segs.extend(('prose', s) for s in _split_prose(text[pos:]))
|
| 33 |
+
return [(k, s) for k, s in segs if s.strip()]
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _split_prose(text):
|
| 37 |
+
out = []
|
| 38 |
+
for line in text.split('\n'):
|
| 39 |
+
line = line.strip()
|
| 40 |
+
if not line:
|
| 41 |
+
continue
|
| 42 |
+
out.extend(s.strip() for s in SENT_SPLIT.split(line) if s.strip())
|
| 43 |
+
return out
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def group_steps(segs, max_words=160, max_sents=10):
|
| 47 |
+
"""Merge consecutive prose sentences into step-sized passages; code stays atomic."""
|
| 48 |
+
out, buf, words = [], [], 0
|
| 49 |
+
|
| 50 |
+
def flush():
|
| 51 |
+
nonlocal buf, words
|
| 52 |
+
if buf:
|
| 53 |
+
out.append(('prose', ' '.join(buf)))
|
| 54 |
+
buf, words = [], 0
|
| 55 |
+
|
| 56 |
+
for kind, s in segs:
|
| 57 |
+
if kind == 'code':
|
| 58 |
+
flush()
|
| 59 |
+
out.append((kind, s))
|
| 60 |
+
continue
|
| 61 |
+
buf.append(s)
|
| 62 |
+
words += len(s.split())
|
| 63 |
+
if words >= max_words or len(buf) >= max_sents:
|
| 64 |
+
flush()
|
| 65 |
+
flush()
|
| 66 |
+
return out
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def facts(s):
|
| 70 |
+
"""Numbers + identifiers that must survive compression.
|
| 71 |
+
List-enumeration markers ("1. Load...") are structure, not facts."""
|
| 72 |
+
nums = set(NUM.findall(s)) - set(_LIST_MARKER.findall(s))
|
| 73 |
+
idents = set(i.strip('`') for i in IDENT.findall(s))
|
| 74 |
+
return nums | idents
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def facts_preserved(src, out):
|
| 78 |
+
"""Substring presence — regex \\b breaks against adjacent CJK chars.
|
| 79 |
+
Returns the list of MISSING facts (empty list = all preserved)."""
|
| 80 |
+
out_n = out.replace(',', '')
|
| 81 |
+
return [f for f in facts(src) if f.replace(',', '') not in out_n]
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def classify_passage(seg, seen_facts, ntok):
|
| 85 |
+
"""'load' = fact-dense or novel-fact-bearing (step-faithful treatment);
|
| 86 |
+
'narr' = search/narrative (stub treatment).
|
| 87 |
+
ntok is a callable: text -> token count under your target tokenizer."""
|
| 88 |
+
f = facts(seg)
|
| 89 |
+
novel = f - seen_facts
|
| 90 |
+
toks = max(ntok(seg), 1)
|
| 91 |
+
dens = (len(NUM.findall(seg)) + len(IDENT.findall(seg))
|
| 92 |
+
+ sum(seg.count(o) for o in _OPS)) / toks
|
| 93 |
+
if novel and (dens >= 0.08 or len(novel) >= 3):
|
| 94 |
+
return 'load'
|
| 95 |
+
if dens >= 0.15:
|
| 96 |
+
return 'load'
|
| 97 |
+
return 'narr'
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def gate(src_seg, rules_seg, out, ntok):
|
| 101 |
+
"""Deterministic per-passage fidelity gate.
|
| 102 |
+
Returns None if the model output is admissible, else a short fail-reason
|
| 103 |
+
string; on failure the caller uses rules_seg instead."""
|
| 104 |
+
if not out or not out.strip():
|
| 105 |
+
return "empty"
|
| 106 |
+
if '```' in out:
|
| 107 |
+
return "fence"
|
| 108 |
+
if len(out) > 2 * len(src_seg) + 40: # explanation/blow-up guard
|
| 109 |
+
return "blowup"
|
| 110 |
+
if facts_preserved(src_seg, out): # every fact survives (substring check)
|
| 111 |
+
return "facts"
|
| 112 |
+
if ntok(out) > ntok(rules_seg): # must beat the deterministic version
|
| 113 |
+
return "tokens"
|
| 114 |
+
return None
|
scripts/tokenmax.py
ADDED
|
@@ -0,0 +1,431 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
tokenmax.py — Deterministic token-maxing post-processor for compressed think blocks.
|
| 3 |
+
|
| 4 |
+
Applies ONLY substitutions that are verified to save tokens on the Qwen 248K tokenizer
|
| 5 |
+
(OmniCoder-9B / Qwen3.5). Every substitution was tested in-context (not isolation) to
|
| 6 |
+
confirm real token savings without boundary interference.
|
| 7 |
+
|
| 8 |
+
Design:
|
| 9 |
+
- LLM does semantic compression (what to keep vs drop)
|
| 10 |
+
- This code enforces consistent notation deterministically
|
| 11 |
+
- GUARD: only returns the processed version if ntok(result) < ntok(original)
|
| 12 |
+
- Idempotent: safe to run multiple times
|
| 13 |
+
|
| 14 |
+
Usage:
|
| 15 |
+
from caveman.compress.tokenmax import tokenmax, tokenmax_with_stats
|
| 16 |
+
compressed = tokenmax(think_text, tokenizer)
|
| 17 |
+
compressed, stats = tokenmax_with_stats(think_text, tokenizer)
|
| 18 |
+
|
| 19 |
+
Verified: 2026-05-31 on Qwen 248K vocab. 27/28 substitutions save in-context.
|
| 20 |
+
Zero false positives. One zero-effect (贪心 for "greedy" — boundary-dependent).
|
| 21 |
+
"""
|
| 22 |
+
|
| 23 |
+
import re
|
| 24 |
+
from typing import Optional
|
| 25 |
+
|
| 26 |
+
# ── Phase 1: Filler drops ──────────────────────────────────────────────
|
| 27 |
+
# Phrases that carry zero information in compressed reasoning.
|
| 28 |
+
# Only patterns that are NEVER load-bearing in a think block.
|
| 29 |
+
_FILLER_PATTERNS = [
|
| 30 |
+
# Metacognition (the model narrating its own process)
|
| 31 |
+
r"\bI need to\b",
|
| 32 |
+
r"\bwe need to\b",
|
| 33 |
+
r"\bI will\b",
|
| 34 |
+
r"\bI'll\b",
|
| 35 |
+
r"\blet me\b",
|
| 36 |
+
r"\blet's\b",
|
| 37 |
+
r"\bI want to\b",
|
| 38 |
+
r"\bI should\b",
|
| 39 |
+
r"\bwe should\b",
|
| 40 |
+
# Hedging
|
| 41 |
+
r"\bprobably\b",
|
| 42 |
+
r"\bbasically\b",
|
| 43 |
+
r"\bessentially\b",
|
| 44 |
+
r"\bit seems like\b",
|
| 45 |
+
# Filler transitions
|
| 46 |
+
r"\bin order to\b",
|
| 47 |
+
r"\bfirst of all\b",
|
| 48 |
+
r"\bin other words\b",
|
| 49 |
+
r"\bon the other hand\b",
|
| 50 |
+
r"\bmore specifically\b",
|
| 51 |
+
r"\bto be more precise\b",
|
| 52 |
+
r"\band so on\b",
|
| 53 |
+
# Conversational padding (require word boundary at end to avoid "Greatest", "Perfectly")
|
| 54 |
+
r"\bGreat\b[,!.]?\s*",
|
| 55 |
+
r"\bPerfect\b[,!.]?\s*",
|
| 56 |
+
# Obvious statements
|
| 57 |
+
r"\bAs (?:we|you) can see\b",
|
| 58 |
+
r"\bAs mentioned (?:above|earlier|before)\b",
|
| 59 |
+
]
|
| 60 |
+
|
| 61 |
+
# ── Phase 2: Phrase → cheapest token ───────────────────────────────────
|
| 62 |
+
# Ordered LONGEST FIRST to prevent partial matches.
|
| 63 |
+
# Each entry: (regex_pattern, replacement, category)
|
| 64 |
+
# Categories: 'cjk', 'symbol', 'abbrev' — for stats tracking.
|
| 65 |
+
_SUBSTITUTIONS = [
|
| 66 |
+
# ── COMPOUND PATTERNS FIRST (must fire before their components) ──
|
| 67 |
+
|
| 68 |
+
# Verbose comparison phrases (+5t savings)
|
| 69 |
+
(r'\bis\s+greater\s+than\s+or\s+equal\s+to\b', '≥', 'symbol'), # +5t
|
| 70 |
+
(r'\bis\s+less\s+than\s+or\s+equal\s+to\b', '≤', 'symbol'), # +5t
|
| 71 |
+
|
| 72 |
+
# Verbose discourse phrases (+3t savings)
|
| 73 |
+
(r'\bwe\s+can\s+see\s+that\b', '可知', 'cjk'), # +3t
|
| 74 |
+
(r'\bat\s+the\s+same\s+time\b', '同时', 'cjk'), # +3t
|
| 75 |
+
(r'\bthat\s+is\s+to\s+say\b', '即', 'cjk'), # +3t
|
| 76 |
+
|
| 77 |
+
# Multi-word phrases (+2t savings)
|
| 78 |
+
(r'\bin\s+this\s+case\b', '此时', 'cjk'), # +2t
|
| 79 |
+
(r'\bthe\s+number\s+of\b', '个数', 'cjk'), # +2t
|
| 80 |
+
(r'\bis\s+equal\s+to\b', '等于', 'cjk'), # +2t
|
| 81 |
+
|
| 82 |
+
# Complexity boilerplate (biggest per-occurrence savings)
|
| 83 |
+
(r'[Oo]\(n\)\s*time[,;]?\s*[Oo]\(n\)\s*space\.?', 'O(n|n).', 'abbrev'),
|
| 84 |
+
(r'[Oo]\(n\)\s*time[,;]?\s*[Oo]\(1\)\s*space\.?', 'O(n|1).', 'abbrev'),
|
| 85 |
+
(r'[Oo]\(n\s*log\s*n\)\s*time[,;]?\s*[Oo]\(n\)\s*space', 'O(n㏒n|n)', 'abbrev'),
|
| 86 |
+
(r'[Oo]\(n\s*log\s*n\)\s*time[,;]?\s*[Oo]\(1\)\s*space', 'O(n㏒n|1)', 'abbrev'),
|
| 87 |
+
(r'[Tt]ime\s*complexity[:\s]+', 'T=', 'abbrev'),
|
| 88 |
+
(r'[Ss]pace\s*complexity[:\s]+', 'S=', 'abbrev'),
|
| 89 |
+
|
| 90 |
+
# Multi-word compounds (BEFORE their single-word components)
|
| 91 |
+
(r'\bassume without loss of generality\b', '设 不妨', 'cjk'), # before "assume"
|
| 92 |
+
(r'\bproof by contradiction\b', '反证', 'cjk'), # before "proof", "contradiction"
|
| 93 |
+
(r'\bnecessary and sufficient\b', '充要', 'cjk'), # before "sufficient"
|
| 94 |
+
(r'\bnot equal(?:\s+to)?\b', '≠', 'symbol'), # before "is not", "does not"
|
| 95 |
+
(r'\bif and only if\b', 'iff', 'abbrev'), # before "for all"
|
| 96 |
+
(r'\bmuch greater than\b', '≫', 'symbol'), # before "greater than"
|
| 97 |
+
(r'\bkeep track(?:\s+of)?\b', '记录', 'cjk'), # before article strip
|
| 98 |
+
(r'\bin ascending order\b', 'asc', 'abbrev'),
|
| 99 |
+
(r'\bin descending order\b', 'desc', 'abbrev'),
|
| 100 |
+
(r'\bmaximum value\b', '最大值', 'cjk'),
|
| 101 |
+
(r'\bminimum value\b', '最小值', 'cjk'),
|
| 102 |
+
(r'\breturn value\b', '返回値', 'cjk'),
|
| 103 |
+
(r'\brather than\b', '而非', 'cjk'),
|
| 104 |
+
(r'\baccording to\b', '按照', 'cjk'),
|
| 105 |
+
|
| 106 |
+
# DS compounds (before components)
|
| 107 |
+
(r'\bdoubly linked list\b', 'DLL', 'abbrev'), # BEFORE "linked list"
|
| 108 |
+
(r'\bbinary indexed tree\b', 'BIT', 'abbrev'), # BEFORE "binary"
|
| 109 |
+
(r'\bminimum spanning tree\b', 'MST', 'abbrev'),
|
| 110 |
+
(r'\bdepth[- ]first search\b', 'DFS', 'abbrev'),
|
| 111 |
+
(r'\bbreadth[- ]first search\b', 'BFS', 'abbrev'),
|
| 112 |
+
(r'\bdynamic programming\b', 'DP', 'abbrev'),
|
| 113 |
+
(r'\bdivide and conquer\b', '分治', 'cjk'),
|
| 114 |
+
(r'\bmonot(?:onic|one)\s*stack\b', '单调栈', 'cjk'),
|
| 115 |
+
(r'\btime limit exceeded\b', '超时', 'cjk'),
|
| 116 |
+
(r'\bout of bounds\b', '越界', 'cjk'),
|
| 117 |
+
(r'\bremove duplicates?\b', '去重', 'cjk'),
|
| 118 |
+
(r'\benumerate all\b', '穷举', 'cjk'),
|
| 119 |
+
(r'\bbinary search\b', '二分', 'cjk'),
|
| 120 |
+
(r'\bsliding window\b', 'sw', 'abbrev'),
|
| 121 |
+
(r'\bunion[- ]find\b', 'UF', 'abbrev'),
|
| 122 |
+
(r'\btopological sort\b', '拓扑序', 'cjk'),
|
| 123 |
+
(r'\bshortest path\b', 'sp', 'abbrev'),
|
| 124 |
+
(r'\blinked list\b', 'LL', 'abbrev'),
|
| 125 |
+
(r'\bpriority queue\b', 'heap', 'abbrev'),
|
| 126 |
+
(r'\bprefix sum\b', 'ps', 'abbrev'),
|
| 127 |
+
(r'\bbrute force\b', '暴力', 'cjk'),
|
| 128 |
+
(r'\bno solution\b', '无解', 'cjk'),
|
| 129 |
+
(r'\bedge cases?\b', '边界', 'cjk'),
|
| 130 |
+
(r'\bbase case\b', 'bc', 'abbrev'),
|
| 131 |
+
(r'\bworst case\b', 'wc', 'abbrev'),
|
| 132 |
+
|
| 133 |
+
# ── SINGLE-WORD SUBSTITUTIONS (safe after compounds consumed) ──
|
| 134 |
+
|
| 135 |
+
# +4t savings
|
| 136 |
+
(r'\bobviously\b', '显然', 'cjk'),
|
| 137 |
+
# +3t savings
|
| 138 |
+
(r'\bredundant\b', '冗余', 'cjk'),
|
| 139 |
+
(r'\bsatisf(?:y|ies|ied)\b', '满足', 'cjk'), # +2t, 333x in data
|
| 140 |
+
(r'\bunsorted\b', '无序', 'cjk'),
|
| 141 |
+
(r'\bdue to\b', '由于', 'cjk'),
|
| 142 |
+
(r'\bhence\b', '故', 'cjk'),
|
| 143 |
+
(r'\bnamely\b', '即', 'cjk'),
|
| 144 |
+
(r'\bassume\b', '设', 'cjk'),
|
| 145 |
+
(r'\bsuppose\b', '设', 'cjk'),
|
| 146 |
+
(r'\bderive\b', '推导', 'cjk'),
|
| 147 |
+
# +2t savings
|
| 148 |
+
(r'\bmonotone\b', '单调', 'cjk'),
|
| 149 |
+
(r'\bconvergent\b', '收敛', 'cjk'),
|
| 150 |
+
(r'\bdivergent\b', '发散', 'cjk'),
|
| 151 |
+
(r'\bcommutative\b', '交换', 'cjk'),
|
| 152 |
+
(r'\bdeterministic\b', '确定', 'cjk'),
|
| 153 |
+
(r'\bprobabilistic\b', '概率', 'cjk'),
|
| 154 |
+
(r'\bprove\b', '证明', 'cjk'),
|
| 155 |
+
(r'\bproof\b', '证明', 'cjk'),
|
| 156 |
+
(r'\bflip\b', '翻转', 'cjk'),
|
| 157 |
+
(r'\bsorted\b(?!\s*[=(\[])', '有序', 'cjk'), # not before = ( [ (assignment/call)
|
| 158 |
+
# +1t savings
|
| 159 |
+
(r'\bbacktrack(?:ing)?\b', '回溯', 'cjk'),
|
| 160 |
+
(r'\btravers(?:e|al|ing)\b', '遍历', 'cjk'),
|
| 161 |
+
(r'\brecursi(?:on|ve|vely)\b', '递归', 'cjk'),
|
| 162 |
+
(r'\bcontradiction\b', '矛盾', 'cjk'),
|
| 163 |
+
(r'\bsufficient\b', '充分', 'cjk'),
|
| 164 |
+
(r'\bequivalent\b', '等价', 'cjk'),
|
| 165 |
+
(r'\bsymmetric\b', '对称', 'cjk'),
|
| 166 |
+
(r'\binvariant\b', '不变', 'cjk'),
|
| 167 |
+
(r'\bexponential\b', '指数', 'cjk'),
|
| 168 |
+
(r'\bpermutation\b', '排列', 'cjk'),
|
| 169 |
+
(r'\badjacent\b', '相邻', 'cjk'),
|
| 170 |
+
(r'\boptimal\b', '最优', 'cjk'),
|
| 171 |
+
(r'\bfeasible\b', '可行', 'cjk'),
|
| 172 |
+
(r'\binduction\b', '归纳', 'cjk'),
|
| 173 |
+
(r'\bmaintain\b', '维护', 'cjk'),
|
| 174 |
+
(r'\bswap\b(?!\s*[=(\[])', '交换', 'cjk'), # not before = ( [ (assignment/call)
|
| 175 |
+
(r'\bcumulative\b', '累积', 'cjk'),
|
| 176 |
+
(r'\bquotient\b', '商', 'cjk'),
|
| 177 |
+
(r'\bmemoiz(?:ation|e)\b', 'memo', 'abbrev'),
|
| 178 |
+
# +2t savings (mined from v19 data)
|
| 179 |
+
(r'\bmathematical\b', '数学', 'cjk'),
|
| 180 |
+
(r'\bcorresponding(?:ly)?\b', '对应', 'cjk'),
|
| 181 |
+
(r'\brequirement\b', '需求', 'cjk'),
|
| 182 |
+
# +1t savings (mined from v19 data)
|
| 183 |
+
(r'\bcomplexity\b', '复杂度', 'cjk'),
|
| 184 |
+
(r'\bsimilarly\b', '同理', 'cjk'),
|
| 185 |
+
(r'\bsubstitut(?:e|ion)\b', '代入', 'cjk'),
|
| 186 |
+
(r'\bincreasing(?:ly)?\b', '递增', 'cjk'),
|
| 187 |
+
(r'\bdecreasing(?:ly)?\b', '递减', 'cjk'),
|
| 188 |
+
(r'\brespectively\b', '分别', 'cjk'),
|
| 189 |
+
(r'\bnecessarily\b', '必然', 'cjk'),
|
| 190 |
+
(r'\btransformation\b', '变换', 'cjk'),
|
| 191 |
+
(r'\bprerequisite\b', '前提', 'cjk'),
|
| 192 |
+
(r'\bconsequently\b', '从而', 'cjk'),
|
| 193 |
+
(r'\boverlapping\b', '重叠', 'cjk'),
|
| 194 |
+
(r'\bcontribut(?:e|ion)\b', '贡献', 'cjk'),
|
| 195 |
+
(r'\bindependent(?:ly)?\b', '独立', 'cjk'),
|
| 196 |
+
(r'\bimpossible\b', '不可能', 'cjk'),
|
| 197 |
+
(r'\biterat(?:e|ion|ing)\b', '迭代', 'cjk'),
|
| 198 |
+
(r'\benumerat(?:e|ion|ing)\b', '枚举', 'cjk'),
|
| 199 |
+
|
| 200 |
+
# ── LOGIC SYMBOLS ──
|
| 201 |
+
(r'\btherefore\b', '⇒', 'symbol'),
|
| 202 |
+
(r'\bthus\b', '⇒', 'symbol'),
|
| 203 |
+
(r'\bsuch that\b', 'st', 'abbrev'),
|
| 204 |
+
(r'\bthere exists?\b', '∃', 'symbol'),
|
| 205 |
+
(r'\bfor each\b', '∀', 'symbol'),
|
| 206 |
+
(r'\bfor every\b', '∀', 'symbol'),
|
| 207 |
+
(r'\bfor all\b', '∀', 'symbol'),
|
| 208 |
+
(r'\bdoes not\b', '¬', 'symbol'),
|
| 209 |
+
(r"\bdoesn't\b", '¬', 'symbol'),
|
| 210 |
+
(r'\bis not\b(?!\s+(?:None|null|undefined|empty|zero|0))', '非', 'cjk'), # protect "is not None" etc
|
| 211 |
+
(r'\bat least\b', '≥', 'symbol'),
|
| 212 |
+
(r'\bat most\b', '≤', 'symbol'),
|
| 213 |
+
(r'\bgreater than\b', '>', 'symbol'),
|
| 214 |
+
(r'\bless than\b', '<', 'symbol'),
|
| 215 |
+
|
| 216 |
+
# ── ARTICLE STRIPPING (last — lowest priority) ──
|
| 217 |
+
(r'\bthe\b\s+(?!(?:same|only|first|last|next|other)\b)', '', 'filler'), # protect "the same", "the only" etc
|
| 218 |
+
(r'\ba\b\s+(?=[bcdfghjklmnpqrstvwxyz])', '', 'filler'),
|
| 219 |
+
(r'\ban\b\s+', '', 'filler'),
|
| 220 |
+
]
|
| 221 |
+
|
| 222 |
+
# ── Compile once ───────────────────────────���───────────────────────────
|
| 223 |
+
_FILLER_COMPILED = [(re.compile(p, re.IGNORECASE), '') for p in _FILLER_PATTERNS]
|
| 224 |
+
_SUBS_COMPILED = [(re.compile(p, re.IGNORECASE), r, cat) for p, r, cat in _SUBSTITUTIONS]
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def _ntok(text: str, tokenizer) -> int:
|
| 228 |
+
"""Token count using the provided tokenizer."""
|
| 229 |
+
return len(tokenizer.encode(text, add_special_tokens=False))
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def _protect_code_fences(text: str) -> tuple[str, list]:
|
| 233 |
+
"""Extract code-fenced blocks, replace with placeholders.
|
| 234 |
+
Returns (text_with_placeholders, list_of_extracted_blocks)."""
|
| 235 |
+
blocks = []
|
| 236 |
+
def _replace(m):
|
| 237 |
+
blocks.append(m.group(0))
|
| 238 |
+
return f'\x00CODEFENCE{len(blocks)-1}\x00'
|
| 239 |
+
# Match ```...``` and inline `...` (non-greedy)
|
| 240 |
+
protected = re.sub(r'```.*?```|`[^`\n]+`', _replace, text, flags=re.DOTALL)
|
| 241 |
+
return protected, blocks
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
def _restore_code_fences(text: str, blocks: list) -> str:
|
| 245 |
+
"""Restore code-fenced blocks from placeholders."""
|
| 246 |
+
for i, block in enumerate(blocks):
|
| 247 |
+
text = text.replace(f'\x00CODEFENCE{i}\x00', block)
|
| 248 |
+
return text
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
def _apply_subs(text: str) -> tuple[str, dict]:
|
| 252 |
+
"""Apply all substitutions, return (result, stats).
|
| 253 |
+
Code fences (``` and inline `) are protected from substitution."""
|
| 254 |
+
stats = {'filler_drops': 0, 'cjk': 0, 'symbol': 0, 'abbrev': 0, 'total_subs': 0}
|
| 255 |
+
|
| 256 |
+
# Phase 0: protect code fences from substitution
|
| 257 |
+
text, code_blocks = _protect_code_fences(text)
|
| 258 |
+
|
| 259 |
+
# Phase 1: filler drops
|
| 260 |
+
for pat, repl in _FILLER_COMPILED:
|
| 261 |
+
text, n = pat.subn(repl, text)
|
| 262 |
+
if n:
|
| 263 |
+
stats['filler_drops'] += n
|
| 264 |
+
stats['total_subs'] += n
|
| 265 |
+
|
| 266 |
+
# Phase 2: substitutions
|
| 267 |
+
for pat, repl, cat in _SUBS_COMPILED:
|
| 268 |
+
text, n = pat.subn(repl, text)
|
| 269 |
+
if n:
|
| 270 |
+
stats[cat] = stats.get(cat, 0) + n
|
| 271 |
+
stats['total_subs'] += n
|
| 272 |
+
|
| 273 |
+
# Phase 3: restore code fences
|
| 274 |
+
text = _restore_code_fences(text, code_blocks)
|
| 275 |
+
|
| 276 |
+
# Phase 4: whitespace normalization
|
| 277 |
+
text = re.sub(r'[ \t]+', ' ', text)
|
| 278 |
+
text = re.sub(r'\n{3,}', '\n\n', text)
|
| 279 |
+
text = re.sub(r' *\n *', '\n', text)
|
| 280 |
+
text = text.strip()
|
| 281 |
+
|
| 282 |
+
return text, stats
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def tokenmax(text: str, tokenizer, force_cjk: bool = False) -> str:
|
| 286 |
+
"""Apply token-maxing. Returns original if no savings achieved.
|
| 287 |
+
|
| 288 |
+
Args:
|
| 289 |
+
text: The think block content (without <think> tags).
|
| 290 |
+
tokenizer: A HuggingFace tokenizer with .encode() method.
|
| 291 |
+
force_cjk: If True, always return the processed version when CJK
|
| 292 |
+
substitutions were applied, even if total token count increased.
|
| 293 |
+
Use this to maximize CJK adoption in training data.
|
| 294 |
+
|
| 295 |
+
Returns:
|
| 296 |
+
The token-maxed text, or the original if processing didn't save tokens
|
| 297 |
+
(unless force_cjk=True and CJK subs were applied).
|
| 298 |
+
"""
|
| 299 |
+
if not text or not text.strip():
|
| 300 |
+
return text
|
| 301 |
+
|
| 302 |
+
original_tokens = _ntok(text, tokenizer)
|
| 303 |
+
result, stats = _apply_subs(text)
|
| 304 |
+
result_tokens = _ntok(result, tokenizer)
|
| 305 |
+
|
| 306 |
+
# GUARD: only return processed version if it actually saves tokens
|
| 307 |
+
# OVERRIDE: force_cjk bypasses the guard when CJK substitutions were made
|
| 308 |
+
if result_tokens < original_tokens:
|
| 309 |
+
return result
|
| 310 |
+
if force_cjk and stats.get('cjk', 0) > 0:
|
| 311 |
+
return result
|
| 312 |
+
return text
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
def tokenmax_with_stats(text: str, tokenizer, force_cjk: bool = False) -> tuple[str, dict]:
|
| 316 |
+
"""Like tokenmax() but also returns substitution statistics.
|
| 317 |
+
|
| 318 |
+
Args:
|
| 319 |
+
force_cjk: If True, always apply when CJK substitutions were made,
|
| 320 |
+
even if total token count increased. Prioritizes CJK adoption
|
| 321 |
+
over token savings.
|
| 322 |
+
|
| 323 |
+
Returns:
|
| 324 |
+
(processed_text, stats_dict) where stats_dict contains:
|
| 325 |
+
- original_tokens: token count before processing
|
| 326 |
+
- result_tokens: token count after processing
|
| 327 |
+
- saved: tokens saved (negative = token increase; check forced_cjk)
|
| 328 |
+
- applied: whether the processed version was used
|
| 329 |
+
- forced_cjk: True when force_cjk override caused acceptance despite no savings
|
| 330 |
+
- filler_drops, cjk, symbol, abbrev: substitution counts by category
|
| 331 |
+
- total_subs: total substitutions applied
|
| 332 |
+
"""
|
| 333 |
+
if not text or not text.strip():
|
| 334 |
+
return text, {'original_tokens': 0, 'result_tokens': 0, 'saved': 0,
|
| 335 |
+
'applied': False, 'forced_cjk': False, 'total_subs': 0}
|
| 336 |
+
|
| 337 |
+
original_tokens = _ntok(text, tokenizer)
|
| 338 |
+
result, stats = _apply_subs(text)
|
| 339 |
+
result_tokens = _ntok(result, tokenizer)
|
| 340 |
+
saved = original_tokens - result_tokens
|
| 341 |
+
|
| 342 |
+
stats['original_tokens'] = original_tokens
|
| 343 |
+
stats['result_tokens'] = result_tokens
|
| 344 |
+
stats['saved'] = saved
|
| 345 |
+
stats['forced_cjk'] = False
|
| 346 |
+
|
| 347 |
+
if saved > 0:
|
| 348 |
+
stats['applied'] = True
|
| 349 |
+
return result, stats
|
| 350 |
+
if force_cjk and stats.get('cjk', 0) > 0:
|
| 351 |
+
stats['applied'] = True
|
| 352 |
+
stats['forced_cjk'] = True
|
| 353 |
+
return result, stats
|
| 354 |
+
stats['applied'] = False
|
| 355 |
+
return text, stats
|
| 356 |
+
|
| 357 |
+
|
| 358 |
+
# ── CLI: batch process a JSONL file ────────────────────────────────────
|
| 359 |
+
if __name__ == '__main__':
|
| 360 |
+
import json, sys, argparse
|
| 361 |
+
from transformers import AutoTokenizer
|
| 362 |
+
|
| 363 |
+
parser = argparse.ArgumentParser(description='Token-max post-processor for think blocks')
|
| 364 |
+
parser.add_argument('--input', required=True, help='Input JSONL (messages format)')
|
| 365 |
+
parser.add_argument('--output', help='Output JSONL (default: dry run, stats only)')
|
| 366 |
+
parser.add_argument('--tokenizer', default='assets/omnicoder-tokenizer',
|
| 367 |
+
help='Path to tokenizer')
|
| 368 |
+
parser.add_argument('--force-cjk', action='store_true',
|
| 369 |
+
help='Force CJK substitutions even if total tokens increase. '
|
| 370 |
+
'Prioritizes CJK adoption over token savings.')
|
| 371 |
+
args = parser.parse_args()
|
| 372 |
+
|
| 373 |
+
tok = AutoTokenizer.from_pretrained(args.tokenizer, trust_remote_code=True)
|
| 374 |
+
|
| 375 |
+
total_before = total_after = applied = skipped = forced = 0
|
| 376 |
+
|
| 377 |
+
out_lines = []
|
| 378 |
+
with open(args.input) as f:
|
| 379 |
+
for line in f:
|
| 380 |
+
line = line.strip()
|
| 381 |
+
if not line:
|
| 382 |
+
continue
|
| 383 |
+
rec = json.loads(line)
|
| 384 |
+
for m in rec.get('messages', rec.get('conversations', [])):
|
| 385 |
+
role = m.get('role', m.get('from', ''))
|
| 386 |
+
if role not in ('assistant', 'gpt'):
|
| 387 |
+
continue
|
| 388 |
+
content_key = 'content' if 'content' in m else 'value'
|
| 389 |
+
c = m.get(content_key, '') or ''
|
| 390 |
+
if '<think>' not in c or '</think>' not in c:
|
| 391 |
+
continue
|
| 392 |
+
|
| 393 |
+
# Extract think content, preserving prefix before <think> and suffix after </think>
|
| 394 |
+
think_start = c.index('<think>') + len('<think>')
|
| 395 |
+
think_end = c.index('</think>')
|
| 396 |
+
prefix = c[:think_start - len('<think>')]
|
| 397 |
+
think = c[think_start:think_end]
|
| 398 |
+
suffix = c[think_end + len('</think>'):]
|
| 399 |
+
|
| 400 |
+
maxed, stats = tokenmax_with_stats(think, tok, force_cjk=args.force_cjk)
|
| 401 |
+
|
| 402 |
+
total_before += stats['original_tokens']
|
| 403 |
+
if stats['applied']:
|
| 404 |
+
applied += 1
|
| 405 |
+
total_after += stats['result_tokens']
|
| 406 |
+
m[content_key] = f'{prefix}<think>{maxed}</think>{suffix}'
|
| 407 |
+
if stats.get('forced_cjk'):
|
| 408 |
+
forced += 1
|
| 409 |
+
else:
|
| 410 |
+
skipped += 1
|
| 411 |
+
total_after += stats['original_tokens']
|
| 412 |
+
|
| 413 |
+
out_lines.append(json.dumps(rec, ensure_ascii=False))
|
| 414 |
+
|
| 415 |
+
if args.output:
|
| 416 |
+
with open(args.output, 'w') as f:
|
| 417 |
+
for line in out_lines:
|
| 418 |
+
f.write(line + '\n')
|
| 419 |
+
|
| 420 |
+
total = applied + skipped
|
| 421 |
+
saved = total_before - total_after
|
| 422 |
+
if total > 0:
|
| 423 |
+
print(f'Processed {total} think blocks')
|
| 424 |
+
print(f' Applied: {applied} ({100*applied/total:.0f}%)')
|
| 425 |
+
if forced:
|
| 426 |
+
print(f' Forced CJK: {forced} (applied despite no token savings)')
|
| 427 |
+
print(f' Skipped (no savings): {skipped}')
|
| 428 |
+
pct = f'{100*saved/total_before:.1f}' if total_before > 0 else '0.0'
|
| 429 |
+
print(f' Tokens: {total_before} → {total_after} = {saved:+d} ({pct}%)')
|
| 430 |
+
else:
|
| 431 |
+
print(f'No think blocks found in {args.input}')
|
tokenizer.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:ea43b288542655d72d632195ab9b58ca2cd9532c292bf6667827ce899ad196bc
|
| 3 |
+
size 11422082
|
tokenizer_config.json
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_prefix_space": false,
|
| 3 |
+
"backend": "tokenizers",
|
| 4 |
+
"bos_token": null,
|
| 5 |
+
"clean_up_tokenization_spaces": false,
|
| 6 |
+
"eos_token": "<|im_end|>",
|
| 7 |
+
"errors": "replace",
|
| 8 |
+
"extra_special_tokens": [
|
| 9 |
+
"<|im_start|>",
|
| 10 |
+
"<|im_end|>",
|
| 11 |
+
"<|object_ref_start|>",
|
| 12 |
+
"<|object_ref_end|>",
|
| 13 |
+
"<|box_start|>",
|
| 14 |
+
"<|box_end|>",
|
| 15 |
+
"<|quad_start|>",
|
| 16 |
+
"<|quad_end|>",
|
| 17 |
+
"<|vision_start|>",
|
| 18 |
+
"<|vision_end|>",
|
| 19 |
+
"<|vision_pad|>",
|
| 20 |
+
"<|image_pad|>",
|
| 21 |
+
"<|video_pad|>"
|
| 22 |
+
],
|
| 23 |
+
"is_local": false,
|
| 24 |
+
"model_max_length": 32768,
|
| 25 |
+
"pad_token": "<|PAD_TOKEN|>",
|
| 26 |
+
"padding_side": "left",
|
| 27 |
+
"split_special_tokens": false,
|
| 28 |
+
"tokenizer_class": "Qwen2Tokenizer",
|
| 29 |
+
"unk_token": null,
|
| 30 |
+
"added_tokens_decoder": {
|
| 31 |
+
"151643": {
|
| 32 |
+
"content": "<|endoftext|>",
|
| 33 |
+
"single_word": false,
|
| 34 |
+
"lstrip": false,
|
| 35 |
+
"rstrip": false,
|
| 36 |
+
"normalized": false,
|
| 37 |
+
"special": true
|
| 38 |
+
},
|
| 39 |
+
"151644": {
|
| 40 |
+
"content": "<|im_start|>",
|
| 41 |
+
"single_word": false,
|
| 42 |
+
"lstrip": false,
|
| 43 |
+
"rstrip": false,
|
| 44 |
+
"normalized": false,
|
| 45 |
+
"special": true
|
| 46 |
+
},
|
| 47 |
+
"151645": {
|
| 48 |
+
"content": "<|im_end|>",
|
| 49 |
+
"single_word": false,
|
| 50 |
+
"lstrip": false,
|
| 51 |
+
"rstrip": false,
|
| 52 |
+
"normalized": false,
|
| 53 |
+
"special": true
|
| 54 |
+
},
|
| 55 |
+
"151646": {
|
| 56 |
+
"content": "<|object_ref_start|>",
|
| 57 |
+
"single_word": false,
|
| 58 |
+
"lstrip": false,
|
| 59 |
+
"rstrip": false,
|
| 60 |
+
"normalized": false,
|
| 61 |
+
"special": true
|
| 62 |
+
},
|
| 63 |
+
"151647": {
|
| 64 |
+
"content": "<|object_ref_end|>",
|
| 65 |
+
"single_word": false,
|
| 66 |
+
"lstrip": false,
|
| 67 |
+
"rstrip": false,
|
| 68 |
+
"normalized": false,
|
| 69 |
+
"special": true
|
| 70 |
+
},
|
| 71 |
+
"151648": {
|
| 72 |
+
"content": "<|box_start|>",
|
| 73 |
+
"single_word": false,
|
| 74 |
+
"lstrip": false,
|
| 75 |
+
"rstrip": false,
|
| 76 |
+
"normalized": false,
|
| 77 |
+
"special": true
|
| 78 |
+
},
|
| 79 |
+
"151649": {
|
| 80 |
+
"content": "<|box_end|>",
|
| 81 |
+
"single_word": false,
|
| 82 |
+
"lstrip": false,
|
| 83 |
+
"rstrip": false,
|
| 84 |
+
"normalized": false,
|
| 85 |
+
"special": true
|
| 86 |
+
},
|
| 87 |
+
"151650": {
|
| 88 |
+
"content": "<|quad_start|>",
|
| 89 |
+
"single_word": false,
|
| 90 |
+
"lstrip": false,
|
| 91 |
+
"rstrip": false,
|
| 92 |
+
"normalized": false,
|
| 93 |
+
"special": true
|
| 94 |
+
},
|
| 95 |
+
"151651": {
|
| 96 |
+
"content": "<|quad_end|>",
|
| 97 |
+
"single_word": false,
|
| 98 |
+
"lstrip": false,
|
| 99 |
+
"rstrip": false,
|
| 100 |
+
"normalized": false,
|
| 101 |
+
"special": true
|
| 102 |
+
},
|
| 103 |
+
"151652": {
|
| 104 |
+
"content": "<|vision_start|>",
|
| 105 |
+
"single_word": false,
|
| 106 |
+
"lstrip": false,
|
| 107 |
+
"rstrip": false,
|
| 108 |
+
"normalized": false,
|
| 109 |
+
"special": true
|
| 110 |
+
},
|
| 111 |
+
"151653": {
|
| 112 |
+
"content": "<|vision_end|>",
|
| 113 |
+
"single_word": false,
|
| 114 |
+
"lstrip": false,
|
| 115 |
+
"rstrip": false,
|
| 116 |
+
"normalized": false,
|
| 117 |
+
"special": true
|
| 118 |
+
},
|
| 119 |
+
"151654": {
|
| 120 |
+
"content": "<|vision_pad|>",
|
| 121 |
+
"single_word": false,
|
| 122 |
+
"lstrip": false,
|
| 123 |
+
"rstrip": false,
|
| 124 |
+
"normalized": false,
|
| 125 |
+
"special": true
|
| 126 |
+
},
|
| 127 |
+
"151655": {
|
| 128 |
+
"content": "<|image_pad|>",
|
| 129 |
+
"single_word": false,
|
| 130 |
+
"lstrip": false,
|
| 131 |
+
"rstrip": false,
|
| 132 |
+
"normalized": false,
|
| 133 |
+
"special": true
|
| 134 |
+
},
|
| 135 |
+
"151656": {
|
| 136 |
+
"content": "<|video_pad|>",
|
| 137 |
+
"single_word": false,
|
| 138 |
+
"lstrip": false,
|
| 139 |
+
"rstrip": false,
|
| 140 |
+
"normalized": false,
|
| 141 |
+
"special": true
|
| 142 |
+
},
|
| 143 |
+
"151657": {
|
| 144 |
+
"content": "<tool_call>",
|
| 145 |
+
"single_word": false,
|
| 146 |
+
"lstrip": false,
|
| 147 |
+
"rstrip": false,
|
| 148 |
+
"normalized": false,
|
| 149 |
+
"special": false
|
| 150 |
+
},
|
| 151 |
+
"151658": {
|
| 152 |
+
"content": "</tool_call>",
|
| 153 |
+
"single_word": false,
|
| 154 |
+
"lstrip": false,
|
| 155 |
+
"rstrip": false,
|
| 156 |
+
"normalized": false,
|
| 157 |
+
"special": false
|
| 158 |
+
},
|
| 159 |
+
"151659": {
|
| 160 |
+
"content": "<|fim_prefix|>",
|
| 161 |
+
"single_word": false,
|
| 162 |
+
"lstrip": false,
|
| 163 |
+
"rstrip": false,
|
| 164 |
+
"normalized": false,
|
| 165 |
+
"special": false
|
| 166 |
+
},
|
| 167 |
+
"151660": {
|
| 168 |
+
"content": "<|fim_middle|>",
|
| 169 |
+
"single_word": false,
|
| 170 |
+
"lstrip": false,
|
| 171 |
+
"rstrip": false,
|
| 172 |
+
"normalized": false,
|
| 173 |
+
"special": false
|
| 174 |
+
},
|
| 175 |
+
"151661": {
|
| 176 |
+
"content": "<|fim_suffix|>",
|
| 177 |
+
"single_word": false,
|
| 178 |
+
"lstrip": false,
|
| 179 |
+
"rstrip": false,
|
| 180 |
+
"normalized": false,
|
| 181 |
+
"special": false
|
| 182 |
+
},
|
| 183 |
+
"151662": {
|
| 184 |
+
"content": "<|fim_pad|>",
|
| 185 |
+
"single_word": false,
|
| 186 |
+
"lstrip": false,
|
| 187 |
+
"rstrip": false,
|
| 188 |
+
"normalized": false,
|
| 189 |
+
"special": false
|
| 190 |
+
},
|
| 191 |
+
"151663": {
|
| 192 |
+
"content": "<|repo_name|>",
|
| 193 |
+
"single_word": false,
|
| 194 |
+
"lstrip": false,
|
| 195 |
+
"rstrip": false,
|
| 196 |
+
"normalized": false,
|
| 197 |
+
"special": false
|
| 198 |
+
},
|
| 199 |
+
"151664": {
|
| 200 |
+
"content": "<|file_sep|>",
|
| 201 |
+
"single_word": false,
|
| 202 |
+
"lstrip": false,
|
| 203 |
+
"rstrip": false,
|
| 204 |
+
"normalized": false,
|
| 205 |
+
"special": false
|
| 206 |
+
},
|
| 207 |
+
"151665": {
|
| 208 |
+
"content": "<|PAD_TOKEN|>",
|
| 209 |
+
"single_word": false,
|
| 210 |
+
"lstrip": false,
|
| 211 |
+
"rstrip": false,
|
| 212 |
+
"normalized": false,
|
| 213 |
+
"special": true
|
| 214 |
+
}
|
| 215 |
+
},
|
| 216 |
+
"chat_template": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0]['role'] == 'system' %}\n {{- messages[0]['content'] }}\n {%- else %}\n {{- 'You are Qwen, created by Alibaba Cloud. You are a helpful assistant.' }}\n {%- endif %}\n {{- \"\\n\\n# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0]['role'] == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0]['content'] + '<|im_end|>\\n' }}\n {%- else %}\n {{- '<|im_start|>system\\nYou are Qwen, created by Alibaba Cloud. You are a helpful assistant.<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- for message in messages %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) or (message.role == \"assistant\" and not message.tool_calls) %}\n {{- '<|im_start|>' + message.role + '\\n' + message.content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role }}\n {%- if message.content %}\n {{- '\\n' + message.content }}\n {%- endif %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '\\n<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {{- tool_call.arguments | tojson }}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if (loop.index0 == 0) or (messages[loop.index0 - 1].role != \"tool\") %} {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- message.content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n"
|
| 217 |
+
}
|