Instructions to use mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("feature-extraction", model="mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoTokenizer, AutoModel tokenizer = AutoTokenizer.from_pretrained("mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless") model = AutoModel.from_pretrained("mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless", device_map="auto") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless 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 mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless:Q8_0 # Run inference directly in the terminal: llama cli -hf mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless:Q8_0
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless:Q8_0 # Run inference directly in the terminal: llama cli -hf mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless: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 mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless:Q8_0 # Run inference directly in the terminal: ./llama-cli -hf mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless: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 mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless:Q8_0 # Run inference directly in the terminal: ./build/bin/llama-cli -hf mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless:Q8_0
Use Docker
docker model run hf.co/mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless:Q8_0
- LM Studio
- Jan
- Ollama
How to use mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless with Ollama:
ollama run hf.co/mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless:Q8_0
- Unsloth Desktop
- Pi
How to use mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless:Q8_0
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless:Q8_0" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless with Docker Model Runner:
docker model run hf.co/mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless:Q8_0
- Lemonade
How to use mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless:Q8_0
Run and chat with the model
lemonade run user.qwen3.5-4l-vocab40k-en-ko-headless-Q8_0
List all available models
lemonade list
- Hermes Agent
How to use mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless: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 mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless:Q8_0
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless: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 "mp-juuuns/qwen3.5-4l-vocab40k-en-ko-headless: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"
Draw three tables the repository shipped but never showed
Browse filesWhere the parameters live: cutting 24L to 4L removes 84% of the layer stack and does not touch the embedding, so the embedding's share climbs from 33.8% to 76.1% — which is why the vocabulary was the next cut. At 39,866 entries it is back to 33.8%, the teacher's own balance.
The ladder's losses: not monotone as totals (0.176 / 0.034 / 0.047) and monotone rising per layer removed (0.0110 / 0.0169 / 0.0235). The last two layers cost 2.1x per layer what the first sixteen did.
Thresholds: the frozen grid selects a threshold that moves up to 4x between seeds of one arm, and every run over-predicts — 34.4% of pairs are positive, predictions land at 41-71%, and one control run returns 1.34 false positives per true one. Added with the advice not to inherit these thresholds.
README_KO.md mirrors all three. SHA256SUMS: 358 -> 362 entries.
- .gitattributes +3 -0
- README.md +57 -1
- README_KO.md +56 -1
- SHA256SUMS +6 -2
- benchmark/figures/20_where_the_parameters_live.png +3 -0
- benchmark/figures/21_ladder_loss.png +3 -0
- benchmark/figures/22_threshold_and_overprediction.png +3 -0
- benchmark/figures/make_structure_figures.py +234 -0
|
@@ -28,3 +28,6 @@ benchmark/figures/16_field_depth_effect.png filter=lfs diff=lfs merge=lfs -text
|
|
| 28 |
benchmark/figures/17_field_resolution_floor.png filter=lfs diff=lfs merge=lfs -text
|
| 29 |
benchmark/figures/18_vocabulary_cost_inversion.png filter=lfs diff=lfs merge=lfs -text
|
| 30 |
benchmark/figures/19_window_cost.png filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
benchmark/figures/17_field_resolution_floor.png filter=lfs diff=lfs merge=lfs -text
|
| 29 |
benchmark/figures/18_vocabulary_cost_inversion.png filter=lfs diff=lfs merge=lfs -text
|
| 30 |
benchmark/figures/19_window_cost.png filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
benchmark/figures/20_where_the_parameters_live.png filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
benchmark/figures/21_ladder_loss.png filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
benchmark/figures/22_threshold_and_overprediction.png filter=lfs diff=lfs merge=lfs -text
|
|
@@ -218,6 +218,30 @@ root. If you want this vocabulary, start from this root.
|
|
| 218 |
|
| 219 |
---
|
| 220 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 221 |
## Architecture
|
| 222 |
|
| 223 |
```
|
|
@@ -246,6 +270,38 @@ the map, that choice is in the weights. Cutting the vocabulary does not remove i
|
|
| 246 |
|
| 247 |
---
|
| 248 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 249 |
## The path, step by step
|
| 250 |
|
| 251 |
Everything below is measured. Each section is a **situation we assumed**, and it names the data
|
|
@@ -840,7 +896,7 @@ described at the top of this card.
|
|
| 840 |
| [`root_manifest.json`](root_manifest.json) | its vocabulary rule, layer maps, hashes, why this grid point |
|
| 841 |
| [`benchmark/full/`](benchmark/full/) | the 61-arm table, the grid, the same-host cuts |
|
| 842 |
| [`benchmark/cost_probe/`](benchmark/cost_probe/) | the tokenisation-cost and window-cost measurements, scripts and results |
|
| 843 |
-
| [`benchmark/figures/`](benchmark/figures/) | the
|
| 844 |
| [`runtime/`](runtime/) | the two relaxed sources of the dedicated runtime, and its own README |
|
| 845 |
| [`docker/`](docker/) | a container that runs the backbone with a head on it, CPU only |
|
| 846 |
| [`docker-train/`](docker-train/) | train a classifier on your own data, convert, serve |
|
|
|
|
| 218 |
|
| 219 |
---
|
| 220 |
|
| 221 |
+
|
| 222 |
+
### If you train a head, the threshold is yours to pick
|
| 223 |
+
|
| 224 |
+
One thing the benchmark's own tables show clearly, and this card had not: the decision threshold
|
| 225 |
+
the protocol lands on is unstable, and at whatever it lands on the head over-predicts.
|
| 226 |
+
|
| 227 |
+

|
| 228 |
+
|
| 229 |
+
Across three seeds of one arm the frozen 18-point grid selected 0.10, 0.15 and 0.30, and for
|
| 230 |
+
another it selected 0.05, 0.20 and 0.20 — a 3x and a 4x range for one model on one split, from
|
| 231 |
+
nothing but the seed. The task-blind arms behave the same way: across the thirteen task-blind runs
|
| 232 |
+
of the final test the selected threshold spans 0.05 to 0.45.
|
| 233 |
+
|
| 234 |
+
And every run predicts more positives than the data contains. 34.4% of the (document, label)
|
| 235 |
+
pairs are actually positive; the selected thresholds put predictions at 41% to 71%. One control
|
| 236 |
+
run returns 1.34 false positives for every true one. That is what a threshold picked to maximise
|
| 237 |
+
macro F1 on a 14-label problem with rare labels does — macro F1 rewards getting *something* right
|
| 238 |
+
on the rare labels, and the cheapest way to do that is to say yes more often.
|
| 239 |
+
|
| 240 |
+
None of this is a defect in the backbone; it is a property of the frozen protocol's selection
|
| 241 |
+
rule and of the split's label sparsity. It is here because it is the part of these numbers most
|
| 242 |
+
likely to surprise you when you attach your own head: **do not inherit these thresholds, and
|
| 243 |
+
calibrate on your own data for the precision/recall balance your application wants.**
|
| 244 |
+
|
| 245 |
## Architecture
|
| 246 |
|
| 247 |
```
|
|
|
|
| 270 |
|
| 271 |
---
|
| 272 |
|
| 273 |
+
|
| 274 |
+
### Where the parameters actually live
|
| 275 |
+
|
| 276 |
+
The two cuts had to happen in that order, and the reason is arithmetic rather than judgement.
|
| 277 |
+
|
| 278 |
+

|
| 279 |
+
|
| 280 |
+
At 24 layers the embedding table is a third of the model. Cutting to 4 layers removes 84% of the
|
| 281 |
+
layer stack — 498M parameters down to 80M — and touches the embedding not at all, so the model
|
| 282 |
+
only falls from 752M to 334M and the embedding's share climbs from 33.8% to **76.1%**. After the
|
| 283 |
+
depth cut, three quarters of what remained was a lookup table for 248,320 entries that this model
|
| 284 |
+
would never need, which is what made the vocabulary the next thing worth cutting.
|
| 285 |
+
|
| 286 |
+
Cutting it to 39,866 brings the embedding back to 33.8% of the model — the same balance the
|
| 287 |
+
24-layer teacher had — and the model to 120.6M. The two grid points below this root keep going:
|
| 288 |
+
at 15,380 entries the embedding is 16% and the layer stack is nearly everything left.
|
| 289 |
+
|
| 290 |
+
### What the ladder itself cost
|
| 291 |
+
|
| 292 |
+
Distillation is three stages, and the tables in this repository record each one's loss.
|
| 293 |
+
|
| 294 |
+

|
| 295 |
+
|
| 296 |
+
Read as totals the sequence is not monotone: 24→8 drops sixteen layers for 0.176, 8→6 drops two
|
| 297 |
+
for 0.034, and then 6→4 drops two for 0.047 — 39% more than the step before it for the same
|
| 298 |
+
number of layers. Read per layer removed it is monotone and rising: 0.0110, 0.0169, 0.0235. The
|
| 299 |
+
last two layers cost 2.1x per layer what the first sixteen did.
|
| 300 |
+
|
| 301 |
+
We do not read a mechanism into that. It is one run per stage with no spread, on a matching
|
| 302 |
+
objective that never sees a label, and the interface loss is larger than the final loss at every
|
| 303 |
+
stage, which is what one would expect when the boundaries being matched are the constraint.
|
| 304 |
+
|
| 305 |
## The path, step by step
|
| 306 |
|
| 307 |
Everything below is measured. Each section is a **situation we assumed**, and it names the data
|
|
|
|
| 896 |
| [`root_manifest.json`](root_manifest.json) | its vocabulary rule, layer maps, hashes, why this grid point |
|
| 897 |
| [`benchmark/full/`](benchmark/full/) | the 61-arm table, the grid, the same-host cuts |
|
| 898 |
| [`benchmark/cost_probe/`](benchmark/cost_probe/) | the tokenisation-cost and window-cost measurements, scripts and results |
|
| 899 |
+
| [`benchmark/figures/`](benchmark/figures/) | the twenty-two figures above |
|
| 900 |
| [`runtime/`](runtime/) | the two relaxed sources of the dedicated runtime, and its own README |
|
| 901 |
| [`docker/`](docker/) | a container that runs the backbone with a head on it, CPU only |
|
| 902 |
| [`docker-train/`](docker-train/) | train a classifier on your own data, convert, serve |
|
|
@@ -194,6 +194,30 @@ qwen35-distill finetune \
|
|
| 194 |
|
| 195 |
---
|
| 196 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 197 |
## 아키텍처
|
| 198 |
|
| 199 |
```
|
|
@@ -222,6 +246,37 @@ macro F1 기준 1위입니다. 이 저장소의 예전 `config.json` 파일들
|
|
| 222 |
|
| 223 |
---
|
| 224 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
## 경로, 단계별로
|
| 226 |
|
| 227 |
아래의 모든 것은 측정된 값입니다. 각 절은 **우리가 가정한 상황**이며, 그 상황을 세우기 위해 사용한
|
|
@@ -805,7 +860,7 @@ A/B/A, 스레드 수 1/3/4, 라이브 서비스 경로도 모두 일치했습니
|
|
| 805 |
| [`root_manifest.json`](root_manifest.json) | 그 어휘 규칙, 계층 맵, 해시, 왜 이 그리드 포인트인지 |
|
| 806 |
| [`benchmark/full/`](benchmark/full/) | 61개 arm 표, 그리드, 동일 호스트 절단 |
|
| 807 |
| [`benchmark/cost_probe/`](benchmark/cost_probe/) | 토크나이즈 비용과 윈도 비용 측정 스크립트와 결과 |
|
| 808 |
-
| [`benchmark/figures/`](benchmark/figures/) | 위의
|
| 809 |
| [`runtime/`](runtime/) | 전용 런타임의 완화된 두 소스와 그 자신의 README |
|
| 810 |
| [`docker/`](docker/) | 헤드를 얹�� 백본을 돌리는 컨테이너, CPU 전용 |
|
| 811 |
| [`docker-train/`](docker-train/) | 자기 데이터로 분류기를 학습시키고, 변환하고, 서빙 |
|
|
|
|
| 194 |
|
| 195 |
---
|
| 196 |
|
| 197 |
+
|
| 198 |
+
### 헤드를 학습시키신다면, 임계값은 직접 고르셔야 합니다
|
| 199 |
+
|
| 200 |
+
벤치마크 자신의 표가 분명히 보여 주는데 이 카드가 말하지 않은 것이 하나 있습니다. 프로토콜이
|
| 201 |
+
고르는 결정 임계값은 불안정하고, 무엇에 안착하든 헤드는 과다예측합니다.
|
| 202 |
+
|
| 203 |
+

|
| 204 |
+
|
| 205 |
+
한 arm의 세 시드에서 동결된 18점 그리드는 0.10, 0.15, 0.30을 골랐고, 다른 arm에서는 0.05, 0.20,
|
| 206 |
+
0.20을 골랐습니다. 한 모델, 한 분할에서 오직 시드만 달랐는데 3배와 4배의 범위입니다. task-blind
|
| 207 |
+
arm들도 같습니다. 최종 테스트의 task-blind 실행 열세 개에서 선택된 임계값은 0.05에서 0.45까지
|
| 208 |
+
퍼져 있습니다.
|
| 209 |
+
|
| 210 |
+
그리고 모든 실행이 데이터에 있는 것보다 더 많은 양성을 예측합니다. (문서, 레이블) 쌍의 34.4%가
|
| 211 |
+
실제 양성인데, 선택된 임계값들은 예측을 41%에서 71%에 놓습니다. 한 대조 실행은 참 양성 하나당 거짓
|
| 212 |
+
양성 1.34개를 돌려줍니다. 드문 레이블이 있는 14개 레이블 문제에서 macro F1을 최대화하도록 고른
|
| 213 |
+
임계값이 하는 일이 이것입니다 — macro F1은 드문 레이블에서 *무언가*를 맞히는 것을 보상하고, 그
|
| 214 |
+
가장 싼 방법은 더 자주 예라고 말하는 것입니다.
|
| 215 |
+
|
| 216 |
+
이 중 어느 것도 백본의 결함이 아니라 동결된 프로토콜의 선택 규칙과 이 분할의 레이블 희소성의
|
| 217 |
+
성질입니다. 이것을 여기 적는 이유는, 여러분이 자신의 헤드를 붙이실 때 이 수치들 중 가장 놀라기
|
| 218 |
+
쉬운 부분이기 때문입니다. **이 임계값들을 그대로 물려받지 마시고, 여러분의 응용이 원하는
|
| 219 |
+
정밀도/재현율 균형에 맞게 여러분의 데이터에서 캘리브레이션하십시오.**
|
| 220 |
+
|
| 221 |
## 아키텍처
|
| 222 |
|
| 223 |
```
|
|
|
|
| 246 |
|
| 247 |
---
|
| 248 |
|
| 249 |
+
|
| 250 |
+
### 파라미터는 실제로 어디에 있는가
|
| 251 |
+
|
| 252 |
+
두 번의 절단이 그 순서여야 했던 이유는 판단이 아니라 산수입니다.
|
| 253 |
+
|
| 254 |
+

|
| 255 |
+
|
| 256 |
+
24계층에서 임베딩 테이블은 모델의 3분의 1입니다. 4계층으로 자르면 계층 스택의 84% — 498M에서
|
| 257 |
+
80M으로 — 가 사라지지만 임베딩은 전혀 건드려지지 않으므로, 모델은 752M에서 334M으로만 줄고
|
| 258 |
+
임베딩의 비중은 33.8%에서 **76.1%**로 올라갑니다. 깊이를 자르고 나니 남은 것의 4분의 3이 이 모델이
|
| 259 |
+
결코 쓰지 않을 248,320개 항목의 참조표였고, 그래서 다음에 자를 가치가 있는 것이 어휘였습니다.
|
| 260 |
+
|
| 261 |
+
39,866으로 자르면 임베딩은 모델의 33.8%로 돌아오는데, 이는 24계층 교사가 가지고 있던 것과 같은
|
| 262 |
+
균형이며, 모델은 120.6M이 됩니다. 이 루트 아래의 두 그리드 포인트는 더 내려갑니다. 15,380 항목에서는
|
| 263 |
+
임베딩이 16%이고 남은 것의 거의 전부가 계층 스택입니다.
|
| 264 |
+
|
| 265 |
+
### 사다리 자체는 무엇을 치렀는가
|
| 266 |
+
|
| 267 |
+
증류는 세 단계이고, 이 저장소의 표가 각 단계의 손실을 기록하고 있습니다.
|
| 268 |
+
|
| 269 |
+

|
| 270 |
+
|
| 271 |
+
총합으로 읽으면 순서가 단조롭지 않습니다. 24→8은 열여섯 계층을 0.176에, 8→6은 두 계층을 0.034에,
|
| 272 |
+
그리고 6→4는 두 계층을 0.047에 — 같은 계층 수에 대해 바로 앞 단계보다 39% 더 — 떨어뜨립니다.
|
| 273 |
+
제거된 계층당으로 읽으면 단조롭게 올라갑니다. 0.0110, 0.0169, 0.0235. 마지막 두 계층은 처음 열여섯
|
| 274 |
+
계층이 치른 값의 계층당 2.1배를 치릅니다.
|
| 275 |
+
|
| 276 |
+
여기에서 기전을 읽어 내지는 않습니다. 단계당 한 번의 실행이라 산포가 없고, 레이블을 한 번도 보지
|
| 277 |
+
않는 정합 목적함수이며, 모든 단계에서 인터페이스 손실이 최종 손실보다 큰데 이는 정합 대상이 되는
|
| 278 |
+
경계가 제약일 때 예상되는 모습입니다.
|
| 279 |
+
|
| 280 |
## 경로, 단계별로
|
| 281 |
|
| 282 |
아래의 모든 것은 측정된 값입니다. 각 절은 **우리가 가정한 상황**이며, 그 상황을 세우기 위해 사용한
|
|
|
|
| 860 |
| [`root_manifest.json`](root_manifest.json) | 그 어휘 규칙, 계층 맵, 해시, 왜 이 그리드 포인트인지 |
|
| 861 |
| [`benchmark/full/`](benchmark/full/) | 61개 arm 표, 그리드, 동일 호스트 절단 |
|
| 862 |
| [`benchmark/cost_probe/`](benchmark/cost_probe/) | 토크나이즈 비용과 윈도 비용 측정 스크립트와 결과 |
|
| 863 |
+
| [`benchmark/figures/`](benchmark/figures/) | 위의 스물두 개 그림 |
|
| 864 |
| [`runtime/`](runtime/) | 전용 런타임의 완화된 두 소스와 그 자신의 README |
|
| 865 |
| [`docker/`](docker/) | 헤드를 얹�� 백본을 돌리는 컨테이너, CPU 전용 |
|
| 866 |
| [`docker-train/`](docker-train/) | 자기 데이터로 분류기를 학습시키고, 변환하고, 서빙 |
|
|
@@ -1,6 +1,6 @@
|
|
| 1 |
77fd4710def9ec3c0f6225800e0235f15a425abd4a8b03559127fcd782612049 LICENSE
|
| 2 |
-
|
| 3 |
-
|
| 4 |
bf4073de38c3905effd2dcce6f38894f325c8a0a79bedfb2779f0545c86b9c9f benchmark/BENCHMARK_CARD.md
|
| 5 |
e01bd7ac59a3212e44b77e5fcfbc7c41ec91d74c998306d2438209d6ef1a3aa7 benchmark/arm_summary.csv
|
| 6 |
2c544c3907c4f592d724f658850b8c6b9634845cb5b50a66ebd2ea6c5d48ce0b benchmark/compression_ladder.csv
|
|
@@ -39,6 +39,9 @@ cfb3071db4077bed7aee06c141f5f12873779a732516242580c889fb223c14fa benchmark/figu
|
|
| 39 |
dc621e266a694496ba30d9d5c03e89e4d6c65616f5c25df0216d012102cd3f53 benchmark/figures/17_field_resolution_floor.png
|
| 40 |
8941a6f6bcf3a354d5a7b3b53367c36508a2aed73c6094df7536401d41e0cfd7 benchmark/figures/18_vocabulary_cost_inversion.png
|
| 41 |
261f2fddb02eb02d2d7d696d650405cea153941eb6e93073bf21483732f9db0e benchmark/figures/19_window_cost.png
|
|
|
|
|
|
|
|
|
|
| 42 |
7c238ed969ed64ac7c63084c663ecc531b618622199a77b09d28f461db11a657 benchmark/figures/README.md
|
| 43 |
afd203646513c49140a25ef2a562a064286a4fa5efb495e6a73f83f0106e7933 benchmark/figures/cross_task/05_why_4l_edge_tradeoff.csv
|
| 44 |
22f531ea586f9c79ac8f114345cec56e74ea5ed97aa3726f7310018666ae6312 benchmark/figures/cross_task/05_why_4l_edge_tradeoff.pdf
|
|
@@ -54,6 +57,7 @@ c3312e98b8abbbdc488e5dfde77c4110732e8d08abe9ed931bd985ad799ecad8 benchmark/figu
|
|
| 54 |
d42a85f828b719a6b24e226013ae660f3ff88280b256490b9eb08e19616c96d7 benchmark/figures/make_cost_figures.py
|
| 55 |
3f6b1b82af1619afab8c753cd836040a7353a3226646b8b99fb641617e4ecec7 benchmark/figures/make_more_figures.py
|
| 56 |
5f6b1979dcb7ff4ee897659b2f902dd3cc36339987f42f681bff7be5ae06d304 benchmark/figures/make_readout_figures.py
|
|
|
|
| 57 |
078b4f34897e0a1b9bcff66ce21ba0feb28d72c86eaa8cef608f110c48f52f2e benchmark/figures/v128k_compression.csv
|
| 58 |
014da12a435e3404b31470e2642d180dc111eb608fb8513fd993097019698b7a benchmark/figures/v128k_quality_efficiency.csv
|
| 59 |
644e5b7469c0b4e0b63d5ecaa1c0f2fc8e07af8bb5d9e4d3ae5a88166de374f3 benchmark/figures/v128k_quality_storage.png
|
|
|
|
| 1 |
77fd4710def9ec3c0f6225800e0235f15a425abd4a8b03559127fcd782612049 LICENSE
|
| 2 |
+
92ea56cfedc17d67ad2dcd4fa3d40378ac6a8ed27456bb24721284030e3349b3 README.md
|
| 3 |
+
a62448e225d5b10ec7a8bc998be7cc7db209ba59f1ff6ce66e4b2293ddbaa2b6 README_KO.md
|
| 4 |
bf4073de38c3905effd2dcce6f38894f325c8a0a79bedfb2779f0545c86b9c9f benchmark/BENCHMARK_CARD.md
|
| 5 |
e01bd7ac59a3212e44b77e5fcfbc7c41ec91d74c998306d2438209d6ef1a3aa7 benchmark/arm_summary.csv
|
| 6 |
2c544c3907c4f592d724f658850b8c6b9634845cb5b50a66ebd2ea6c5d48ce0b benchmark/compression_ladder.csv
|
|
|
|
| 39 |
dc621e266a694496ba30d9d5c03e89e4d6c65616f5c25df0216d012102cd3f53 benchmark/figures/17_field_resolution_floor.png
|
| 40 |
8941a6f6bcf3a354d5a7b3b53367c36508a2aed73c6094df7536401d41e0cfd7 benchmark/figures/18_vocabulary_cost_inversion.png
|
| 41 |
261f2fddb02eb02d2d7d696d650405cea153941eb6e93073bf21483732f9db0e benchmark/figures/19_window_cost.png
|
| 42 |
+
87c6e141f8f28c293382e3198efe360fc4a4d6bdd42fc5b73bc2eb5a170eb5a3 benchmark/figures/20_where_the_parameters_live.png
|
| 43 |
+
8dc8781f83f6c3de22b287b86db544da986e0225345db5e43dd77e54ab4b1443 benchmark/figures/21_ladder_loss.png
|
| 44 |
+
b08436d88b8ad64dccebf9d04629fd1dfbd1e5f21e0908f2a5284f4b7243642d benchmark/figures/22_threshold_and_overprediction.png
|
| 45 |
7c238ed969ed64ac7c63084c663ecc531b618622199a77b09d28f461db11a657 benchmark/figures/README.md
|
| 46 |
afd203646513c49140a25ef2a562a064286a4fa5efb495e6a73f83f0106e7933 benchmark/figures/cross_task/05_why_4l_edge_tradeoff.csv
|
| 47 |
22f531ea586f9c79ac8f114345cec56e74ea5ed97aa3726f7310018666ae6312 benchmark/figures/cross_task/05_why_4l_edge_tradeoff.pdf
|
|
|
|
| 57 |
d42a85f828b719a6b24e226013ae660f3ff88280b256490b9eb08e19616c96d7 benchmark/figures/make_cost_figures.py
|
| 58 |
3f6b1b82af1619afab8c753cd836040a7353a3226646b8b99fb641617e4ecec7 benchmark/figures/make_more_figures.py
|
| 59 |
5f6b1979dcb7ff4ee897659b2f902dd3cc36339987f42f681bff7be5ae06d304 benchmark/figures/make_readout_figures.py
|
| 60 |
+
8acf0dcd72bb77dc650aa8253db35de2d31df0a67ef20f928075f82297242283 benchmark/figures/make_structure_figures.py
|
| 61 |
078b4f34897e0a1b9bcff66ce21ba0feb28d72c86eaa8cef608f110c48f52f2e benchmark/figures/v128k_compression.csv
|
| 62 |
014da12a435e3404b31470e2642d180dc111eb608fb8513fd993097019698b7a benchmark/figures/v128k_quality_efficiency.csv
|
| 63 |
644e5b7469c0b4e0b63d5ecaa1c0f2fc8e07af8bb5d9e4d3ae5a88166de374f3 benchmark/figures/v128k_quality_storage.png
|
|
Git LFS Details
|
|
Git LFS Details
|
|
Git LFS Details
|
|
@@ -0,0 +1,234 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Three figures from tables the card shipped but never drew.
|
| 2 |
+
|
| 3 |
+
20 where the parameters actually live, and why the vocabulary was cut after the layers
|
| 4 |
+
21 the distillation ladder's own losses: the difficulty is not monotone
|
| 5 |
+
22 the threshold the protocol selects moves between seeds, and every arm over-predicts
|
| 6 |
+
|
| 7 |
+
Run from benchmark/figures/ . Reads ../compression_ladder.csv and ../seed_metrics.csv,
|
| 8 |
+
both of which ship in this repository.
|
| 9 |
+
"""
|
| 10 |
+
import csv
|
| 11 |
+
import json
|
| 12 |
+
import os
|
| 13 |
+
import statistics as st
|
| 14 |
+
|
| 15 |
+
import matplotlib
|
| 16 |
+
matplotlib.use("Agg")
|
| 17 |
+
import matplotlib.pyplot as plt
|
| 18 |
+
from matplotlib.lines import Line2D
|
| 19 |
+
from matplotlib.patches import Patch
|
| 20 |
+
|
| 21 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 22 |
+
BENCH = os.path.join(HERE, "..") + os.sep # benchmark/ holds the CSVs this reads
|
| 23 |
+
OUT = HERE + os.sep
|
| 24 |
+
|
| 25 |
+
INK, MUTED, FAINT, GRID = "#1a1a1a", "#6a6a72", "#9a9aa2", "#e6e6ea"
|
| 26 |
+
ROOTC, GRIDC, QWENC, ENCC, DOWN = "#1a5496", "#0d7a52", "#b05512", "#9a9aa2", "#c0392b"
|
| 27 |
+
H = 1024 # hidden size, unchanged down the whole ladder
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
def style(ax, xgrid=True, ygrid=True):
|
| 31 |
+
ax.grid(axis="both" if (xgrid and ygrid) else ("x" if xgrid else "y"), color=GRID, lw=.8)
|
| 32 |
+
ax.set_axisbelow(True)
|
| 33 |
+
for s in ("top", "right"):
|
| 34 |
+
ax.spines[s].set_visible(False)
|
| 35 |
+
for s in ("bottom", "left"):
|
| 36 |
+
ax.spines[s].set_color("#c9c9cf")
|
| 37 |
+
ax.tick_params(length=0, colors="#4a4a52")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def footer(fig, text):
|
| 41 |
+
fig.text(.006, .012, text, fontsize=8.6, color=MUTED, ha="left")
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def fig_parameters():
|
| 45 |
+
"""Two cuts in sequence: depth first, then vocabulary. Embedding share is the story."""
|
| 46 |
+
ladder = list(csv.DictReader(open(BENCH + "compression_ladder.csv")))
|
| 47 |
+
emb248 = 248320 * H
|
| 48 |
+
depth = [(f"{r['layers']}L", int(r["parameters"]), emb248) for r in ladder]
|
| 49 |
+
layer_at_4l = depth[-1][1] - emb248
|
| 50 |
+
vocab = [("248,320", 248320), ("128,000", 128000), ("72,455", 72455),
|
| 51 |
+
("39,866", 39866), ("23,551", 23551), ("15,380", 15380)]
|
| 52 |
+
vocab = [(lab, v * H + layer_at_4l + 1024, v * H) for lab, v in vocab]
|
| 53 |
+
|
| 54 |
+
fig, (ax, ax2) = plt.subplots(1, 2, figsize=(14.6, 6.4))
|
| 55 |
+
fig.patch.set_facecolor("white")
|
| 56 |
+
|
| 57 |
+
for axis, data, xlab, title, sub in (
|
| 58 |
+
(ax, depth, "layers kept (vocabulary held at 248,320)",
|
| 59 |
+
"Cut 1 — depth: 24 layers to 4",
|
| 60 |
+
"the layer stack loses 84% of its parameters and the embedding does not move"),
|
| 61 |
+
(ax2, vocab, "vocabulary entries kept (depth held at 4 layers)",
|
| 62 |
+
"Cut 2 — vocabulary: 248,320 entries to 39,866",
|
| 63 |
+
"by then the embedding was three quarters of the model \u2014 which is why it came second")):
|
| 64 |
+
labs = [d[0] for d in data]
|
| 65 |
+
tot = [d[1] / 1e6 for d in data]
|
| 66 |
+
embm = [d[2] / 1e6 for d in data]
|
| 67 |
+
lays = [t - e for t, e in zip(tot, embm)]
|
| 68 |
+
axis.bar(labs, embm, width=.62, color=ROOTC, zorder=3, label="embedding table")
|
| 69 |
+
axis.bar(labs, lays, bottom=embm, width=.62, color=ENCC, zorder=3, label="layer stack")
|
| 70 |
+
for i, (t, e) in enumerate(zip(tot, embm)):
|
| 71 |
+
axis.text(i, t + max(tot) * .035, f"{t:,.0f}M", ha="center", fontsize=9.2,
|
| 72 |
+
color=INK, fontweight="bold")
|
| 73 |
+
axis.text(i, e / 2, f"{e/t:.0%}", ha="center", va="center", fontsize=9,
|
| 74 |
+
color="white", fontweight="bold")
|
| 75 |
+
axis.set_xlabel(xlab, fontsize=10)
|
| 76 |
+
axis.set_ylabel("parameters (millions)", fontsize=10)
|
| 77 |
+
axis.set_ylim(0, max(tot) * 1.24)
|
| 78 |
+
style(axis, xgrid=False)
|
| 79 |
+
axis.set_title(f"{title}\n{sub}", fontsize=11.5, loc="left", color=INK, pad=12)
|
| 80 |
+
|
| 81 |
+
# mark the two endpoints that actually shipped
|
| 82 |
+
ax.get_xticklabels()[-1].set_color(ROOTC)
|
| 83 |
+
ax.get_xticklabels()[-1].set_fontweight("bold")
|
| 84 |
+
ax2.get_xticklabels()[3].set_color(ROOTC)
|
| 85 |
+
ax2.get_xticklabels()[3].set_fontweight("bold")
|
| 86 |
+
ax2.annotate("this root", (3, vocab[3][1] / 1e6), textcoords="offset points",
|
| 87 |
+
xytext=(0, 52), ha="center", fontsize=9.6, color=ROOTC, fontweight="bold",
|
| 88 |
+
arrowprops=dict(arrowstyle="-|>", color=ROOTC, lw=1.3,
|
| 89 |
+
shrinkA=2, shrinkB=22))
|
| 90 |
+
ax.legend(handles=[Patch(color=ROOTC, label="embedding table"),
|
| 91 |
+
Patch(color=ENCC, label="layer stack")],
|
| 92 |
+
fontsize=9, frameon=False, loc="upper right")
|
| 93 |
+
|
| 94 |
+
footer(fig, "Percentages are the embedding's share of the model. Cutting depth alone raised that "
|
| 95 |
+
"share from 33.8% to 76.1%; cutting the vocabulary brought it back to 33.8%, the "
|
| 96 |
+
"same balance the 24-layer teacher had. Hidden size is 1,024 throughout.")
|
| 97 |
+
fig.tight_layout(rect=(0, .055, 1, 1))
|
| 98 |
+
fig.savefig(OUT + "20_where_the_parameters_live.png", dpi=170, facecolor="white")
|
| 99 |
+
plt.close(fig)
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def fig_ladder_loss():
|
| 103 |
+
rows = [r for r in csv.DictReader(open(BENCH + "compression_ladder.csv")) if r["kd_stage"]]
|
| 104 |
+
stages = [r["kd_stage"].replace("to", " → ") for r in rows]
|
| 105 |
+
tot = [float(r["mean_total_loss"]) for r in rows]
|
| 106 |
+
itf = [float(r["mean_interface_loss"]) for r in rows]
|
| 107 |
+
fin = [float(r["mean_final_loss"]) for r in rows]
|
| 108 |
+
dropped = [16, 2, 2]
|
| 109 |
+
|
| 110 |
+
fig, (ax, ax2) = plt.subplots(1, 2, figsize=(13.8, 5.8),
|
| 111 |
+
gridspec_kw={"width_ratios": [1.12, 1]})
|
| 112 |
+
fig.patch.set_facecolor("white")
|
| 113 |
+
|
| 114 |
+
x = range(len(stages))
|
| 115 |
+
w = .26
|
| 116 |
+
for off, vals, c, lab in ((-w, itf, ROOTC, "interface loss"),
|
| 117 |
+
(0.0, tot, GRIDC, "total loss"),
|
| 118 |
+
(w, fin, QWENC, "final-layer loss")):
|
| 119 |
+
ax.bar([i + off for i in x], vals, width=w * .92, color=c, zorder=3, label=lab)
|
| 120 |
+
for i, v in zip(x, vals):
|
| 121 |
+
ax.text(i + off, v + .004, f"{v:.3f}", ha="center", fontsize=8.4,
|
| 122 |
+
color=INK, fontweight="bold")
|
| 123 |
+
ax.set_xticks(list(x))
|
| 124 |
+
ax.set_xticklabels([f"{s}\n{d} layers removed" for s, d in zip(stages, dropped)], fontsize=9.4)
|
| 125 |
+
ax.set_ylabel("mean KD loss over the distillation run", fontsize=10)
|
| 126 |
+
ax.set_ylim(0, max(itf) * 1.22)
|
| 127 |
+
style(ax, xgrid=False)
|
| 128 |
+
ax.legend(fontsize=9, frameon=False, loc="upper right", ncol=1)
|
| 129 |
+
ax.set_title("The ladder's own losses are not monotone\n"
|
| 130 |
+
"24\u21928 drops 16 layers for 0.176; 8\u21926 drops 2 for 0.034; then 6\u21924 "
|
| 131 |
+
"drops 2 for 0.047",
|
| 132 |
+
fontsize=11.5, loc="left", color=INK, pad=12)
|
| 133 |
+
|
| 134 |
+
# per-layer-removed difficulty
|
| 135 |
+
per = [t / d for t, d in zip(tot, dropped)]
|
| 136 |
+
ax2.plot(list(x), per, "-o", color=ROOTC, lw=2.4, ms=11, mec="white", mew=1.7, zorder=4)
|
| 137 |
+
for i, v in zip(x, per):
|
| 138 |
+
ax2.annotate(f"{v:.4f}", (i, v), textcoords="offset points", xytext=(0, 14),
|
| 139 |
+
ha="center", fontsize=9.2, color=INK, fontweight="bold")
|
| 140 |
+
ax2.set_xticks(list(x))
|
| 141 |
+
ax2.set_xticklabels(stages, fontsize=9.6)
|
| 142 |
+
ax2.set_xlim(-.45, len(stages) - .55)
|
| 143 |
+
ax2.set_ylabel("total loss per layer removed", fontsize=10)
|
| 144 |
+
ax2.set_ylim(0, max(per) * 1.30)
|
| 145 |
+
style(ax2, xgrid=False)
|
| 146 |
+
ax2.set_title("Per layer removed, each step is harder than the last\n"
|
| 147 |
+
"the final two layers cost 2.1x per layer what the first sixteen did",
|
| 148 |
+
fontsize=11.5, loc="left", color=INK, pad=12)
|
| 149 |
+
|
| 150 |
+
footer(fig, "Interface loss matches hidden states at the kept-layer boundaries; final loss matches "
|
| 151 |
+
"the last hidden state. No logits and no labels enter either. One run per stage, so "
|
| 152 |
+
"there is no spread to report and no significance is claimed.")
|
| 153 |
+
fig.tight_layout(rect=(0, .055, 1, 1))
|
| 154 |
+
fig.savefig(OUT + "21_ladder_loss.png", dpi=170, facecolor="white")
|
| 155 |
+
plt.close(fig)
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def fig_thresholds():
|
| 159 |
+
rows = list(csv.DictReader(open(BENCH + "seed_metrics.csv")))
|
| 160 |
+
NAMES = {"task_agnostic_base": "task-free 4L root",
|
| 161 |
+
"structural_copy_control": "structural copy control",
|
| 162 |
+
"existing_specialized_separate_lineage": "specialised 4L, separate lineage"}
|
| 163 |
+
arms = list(dict.fromkeys(r["arm"] for r in rows))
|
| 164 |
+
actual = [(int(r["tp"]) + int(r["fn"])) / (55 * 14) for r in rows]
|
| 165 |
+
assert max(actual) - min(actual) < 1e-9
|
| 166 |
+
actual = actual[0]
|
| 167 |
+
|
| 168 |
+
fig, (ax, ax2) = plt.subplots(1, 2, figsize=(14.2, 5.8))
|
| 169 |
+
fig.patch.set_facecolor("white")
|
| 170 |
+
|
| 171 |
+
colours = {a: c for a, c in zip(arms, (ROOTC, QWENC, GRIDC))}
|
| 172 |
+
for a in arms:
|
| 173 |
+
rs = [r for r in rows if r["arm"] == a]
|
| 174 |
+
th = [float(r["threshold"]) for r in rs]
|
| 175 |
+
pp = [float(r["predicted_positive_rate"]) for r in rs]
|
| 176 |
+
ax.plot(th, pp, "o", ms=11, color=colours[a], mec="white", mew=1.6, zorder=4)
|
| 177 |
+
for r, t, p in zip(rs, th, pp):
|
| 178 |
+
ax.annotate(f"seed {r['seed']}", (t, p), textcoords="offset points",
|
| 179 |
+
xytext=(11, -3), fontsize=8.2, color=MUTED)
|
| 180 |
+
ax.axhline(actual, color=DOWN, ls=(0, (5, 3)), lw=1.6, zorder=3)
|
| 181 |
+
ax.text(.328, actual + .006, f"actual positive rate {actual:.3f}",
|
| 182 |
+
fontsize=8.8, color=DOWN, ha="right", va="bottom")
|
| 183 |
+
ax.set_xlabel("threshold the frozen grid selected for that run", fontsize=10)
|
| 184 |
+
ax.set_ylabel("fraction of (document, label) pairs predicted positive", fontsize=10)
|
| 185 |
+
ax.set_xlim(.02, .335)
|
| 186 |
+
ax.set_ylim(.335, .775)
|
| 187 |
+
style(ax)
|
| 188 |
+
ax.legend(handles=[Line2D([], [], marker="o", ls="", color=colours[a], ms=9, label=NAMES[a])
|
| 189 |
+
for a in arms], fontsize=8.8, frameon=False, loc="upper right")
|
| 190 |
+
ax.set_title("Every run predicts more positives than the data has\n"
|
| 191 |
+
"and the threshold the grid selects moves up to 4x between seeds of one arm",
|
| 192 |
+
fontsize=11.5, loc="left", color=INK, pad=12)
|
| 193 |
+
|
| 194 |
+
labels, tps, fps, fns, cols = [], [], [], [], []
|
| 195 |
+
for a in arms:
|
| 196 |
+
for r in [r for r in rows if r["arm"] == a]:
|
| 197 |
+
labels.append(f"{NAMES[a].split(',')[0]}\nseed {r['seed']} thr {r['threshold']}")
|
| 198 |
+
tps.append(int(r["tp"]))
|
| 199 |
+
fps.append(int(r["fp"]))
|
| 200 |
+
fns.append(int(r["fn"]))
|
| 201 |
+
cols.append(colours[a])
|
| 202 |
+
y = range(len(labels))
|
| 203 |
+
ax2.barh(list(y), tps, height=.6, color=ROOTC, zorder=3, label="true positives")
|
| 204 |
+
ax2.barh(list(y), fps, left=tps, height=.6, color=QWENC, zorder=3, label="false positives")
|
| 205 |
+
ax2.barh(list(y), fns, left=[t + f for t, f in zip(tps, fps)], height=.6,
|
| 206 |
+
color=ENCC, zorder=3, label="false negatives")
|
| 207 |
+
for i, (t, f) in enumerate(zip(tps, fps)):
|
| 208 |
+
ax2.text(t + f + 12, i, f"{f/t:.2f} fp per tp", va="center", fontsize=8.4,
|
| 209 |
+
color=DOWN if f > t else MUTED, fontweight="bold" if f > t else "normal")
|
| 210 |
+
ax2.set_yticks(list(y))
|
| 211 |
+
ax2.set_yticklabels(labels, fontsize=7.8)
|
| 212 |
+
ax2.set_xlabel("(document, label) pairs, out of 770", fontsize=10)
|
| 213 |
+
ax2.set_xlim(0, 720)
|
| 214 |
+
ax2.invert_yaxis()
|
| 215 |
+
style(ax2, xgrid=True, ygrid=False)
|
| 216 |
+
ax2.spines["left"].set_visible(False)
|
| 217 |
+
ax2.legend(fontsize=8.6, frameon=False, loc="lower right", ncol=1)
|
| 218 |
+
ax2.set_title("What that costs, run by run\n"
|
| 219 |
+
"one control run returns more false positives than true ones",
|
| 220 |
+
fontsize=11.5, loc="left", color=INK, pad=12)
|
| 221 |
+
|
| 222 |
+
footer(fig, "Opened 56-document calibration split, 55 scored articles x 14 labels. If you train a "
|
| 223 |
+
"head on this backbone, expect to pick the threshold yourself: the protocol's own grid "
|
| 224 |
+
"lands on a different one for every seed.")
|
| 225 |
+
fig.tight_layout(rect=(0, .055, 1, 1))
|
| 226 |
+
fig.savefig(OUT + "22_threshold_and_overprediction.png", dpi=170, facecolor="white")
|
| 227 |
+
plt.close(fig)
|
| 228 |
+
|
| 229 |
+
|
| 230 |
+
if __name__ == "__main__":
|
| 231 |
+
os.makedirs(OUT, exist_ok=True)
|
| 232 |
+
for fn in (fig_parameters, fig_ladder_loss, fig_thresholds):
|
| 233 |
+
fn()
|
| 234 |
+
print("ok", fn.__name__)
|