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"
Add the sealed final test and expand the 65-arm field with seven figures
Browse filesNew section 'The final test': the 2026-09-23 fixed-protocol evaluation of the SemEval-2020 Task 11 v2 re-split, reported in full including the two arms that fell. Prior test exposure is disclosed; this is not a never-seen holdout.
'Beside other models' gains four figures on the whole 65-arm field: the Pareto frontier on latency and energy, the metric spans (macro F1 1.2x against 64.4x/42.9x/40.3x on file/energy/latency), depth within each encoder family (7 of 12 peak below full depth), and all 65 arms with seed SDs against the all-positive floor.
README_KO.md mirrors both. SHA256SUMS regenerated: 343 -> 351 entries.
- .gitattributes +7 -0
- README.md +121 -3
- README_KO.md +119 -3
- SHA256SUMS +10 -2
- benchmark/figures/11_final_test_calibration_to_test.png +3 -0
- benchmark/figures/12_final_test_per_label.png +3 -0
- benchmark/figures/13_final_test_seed_spread.png +3 -0
- benchmark/figures/14_field_quality_vs_cost.png +3 -0
- benchmark/figures/15_field_metric_spans.png +3 -0
- benchmark/figures/16_field_depth_effect.png +3 -0
- benchmark/figures/17_field_resolution_floor.png +3 -0
- benchmark/figures/make_readout_figures.py +517 -0
|
@@ -19,3 +19,10 @@ docker-train/vendor/runtime_source.tar.gz filter=lfs diff=lfs merge=lfs -text
|
|
| 19 |
models/semeval-propaganda/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
| 20 |
models/semeval-propaganda/seeds/seed42/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
| 21 |
models/semeval-propaganda/seeds/seed43/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
models/semeval-propaganda/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
| 20 |
models/semeval-propaganda/seeds/seed42/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
| 21 |
models/semeval-propaganda/seeds/seed43/tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
benchmark/figures/11_final_test_calibration_to_test.png filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
benchmark/figures/12_final_test_per_label.png filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
benchmark/figures/13_final_test_seed_spread.png filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
benchmark/figures/14_field_quality_vs_cost.png filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
benchmark/figures/15_field_metric_spans.png filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
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
|
|
@@ -95,6 +95,10 @@ architecture is what costs the time, not the vocabulary cut or the depth — but
|
|
| 95 |
that, so it stays a guess. What the cut and the depth did move is the file and the energy, and
|
| 96 |
those are visible in the panels above.
|
| 97 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
---
|
| 99 |
|
| 100 |
## Run it in a container
|
|
@@ -438,8 +442,120 @@ Full table: [`benchmark/full/61_arm_bf16.csv`](benchmark/full/61_arm_bf16.csv).
|
|
| 438 |

|
| 439 |

|
| 440 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 441 |
---
|
| 442 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 443 |
## Two things we checked because they would have changed the reading
|
| 444 |
|
| 445 |
### Does the host matter?
|
|
@@ -637,8 +753,10 @@ described at the top of this card.
|
|
| 637 |
|
| 638 |
## What this does not tell you
|
| 639 |
|
| 640 |
-
- **
|
| 641 |
-
|
|
|
|
|
|
|
| 642 |
- **The evaluation is small.** 56 documents, 14 labels, and the rarest label appears in 6 of
|
| 643 |
them, so one fourteenth of macro F1 rides on six samples. This is the most likely source of the
|
| 644 |
0.005–0.039 seed SDs.
|
|
@@ -661,7 +779,7 @@ described at the top of this card.
|
|
| 661 |
| root | the 39,866-entry headless backbone |
|
| 662 |
| [`root_manifest.json`](root_manifest.json) | its vocabulary rule, layer maps, hashes, why this grid point |
|
| 663 |
| [`benchmark/full/`](benchmark/full/) | the 61-arm table, the grid, the same-host cuts |
|
| 664 |
-
| [`benchmark/figures/`](benchmark/figures/) | the
|
| 665 |
| [`runtime/`](runtime/) | the two relaxed sources of the dedicated runtime, and its own README |
|
| 666 |
| [`docker/`](docker/) | a container that runs the backbone with a head on it, CPU only |
|
| 667 |
| [`docker-train/`](docker-train/) | train a classifier on your own data, convert, serve |
|
|
|
|
| 95 |
that, so it stays a guess. What the cut and the depth did move is the file and the energy, and
|
| 96 |
those are visible in the panels above.
|
| 97 |
|
| 98 |
+
Everything in this table is the opened calibration split. One fixed-protocol test evaluation was
|
| 99 |
+
run on 2026-09-23 against a re-split this project had used before — not a never-seen holdout — and
|
| 100 |
+
it is reported in full, decreases included, under [**The final test**](#the-final-test).
|
| 101 |
+
|
| 102 |
---
|
| 103 |
|
| 104 |
## Run it in a container
|
|
|
|
| 442 |

|
| 443 |

|
| 444 |
|
| 445 |
+
### What the whole field looks like
|
| 446 |
+
|
| 447 |
+

|
| 448 |
+
|
| 449 |
+
Both panels hold the same 65 arms; only the cost axis changes. The dashed line is the Pareto
|
| 450 |
+
frontier — the arms that no other arm beats on quality and cost at the same time. Seven arms hold
|
| 451 |
+
it on latency, eight on energy, and the RoBERTa family holds the cheap half of both. This root
|
| 452 |
+
sits at the top of the quality axis and pays about 2.7x the latency of `roberta-base` to be there.
|
| 453 |
+
|
| 454 |
+

|
| 455 |
+
|
| 456 |
+
Laid beside each other, the spans are the finding. Across the whole field macro F1 moves **1.2x**
|
| 457 |
+
— 0.4756 to 0.5781 — while the weight file moves **64.4x**, GPU energy **42.9x** and document
|
| 458 |
+
latency **40.3x**. The quality span is narrower than even that suggests: its bottom is exactly the
|
| 459 |
+
all-positive predictor, so the entire field fits inside 0.093 of usable range above a model that
|
| 460 |
+
answers yes to everything. Peak RSS barely moves, because it is dominated by the harness rather
|
| 461 |
+
than by the weights.
|
| 462 |
+
|
| 463 |
+

|
| 464 |
+
|
| 465 |
+
Every encoder family was cut to 4, 6 and 8 layers under the same protocol, and its own full-depth
|
| 466 |
+
checkpoint was kept beside them. **In 7 of the 12 families the best arm is not the deepest one.**
|
| 467 |
+
`bert-base`, `mbert` and `electra-base` peak at 4 layers, `minilm-multilingual` at 6, `mpnet-base`
|
| 468 |
+
and `mdeberta-v3` at 8. The five families where depth does pay — roberta, xlmr, deberta-v3,
|
| 469 |
+
albert, modernbert — pay for it in latency and file size. Three arms did not train at all under
|
| 470 |
+
the shared protocol and sit exactly on the floor.
|
| 471 |
+
|
| 472 |
+
This is an observation about this task at this resolution. It is not a claim that depth does not
|
| 473 |
+
matter, and it is not why this root has four layers — section 3 is.
|
| 474 |
+
|
| 475 |
+

|
| 476 |
+
|
| 477 |
+
The last figure is the honest summary of the ordering. The median three-seed SD across the field
|
| 478 |
+
is 0.0116, and six arms sit within one such SD of the best. Several SD bars are wider than the
|
| 479 |
+
whole gap between the top ten arms. The ranking is reported because it was measured, not because
|
| 480 |
+
the measurement resolves it.
|
| 481 |
+
|
| 482 |
---
|
| 483 |
|
| 484 |
+
---
|
| 485 |
+
|
| 486 |
+
## The final test
|
| 487 |
+
|
| 488 |
+
On 2026-09-23 one fixed-protocol evaluation was run against the SemEval-2020 Task 11 **v2 test
|
| 489 |
+
re-split**, under a preregistration frozen before any test byte was read.
|
| 490 |
+
|
| 491 |
+
**It is not a never-seen holdout.** This project has used the v2 split before, and the frozen
|
| 492 |
+
document records prior test exposure as `true`. It is not the official leaderboard test, there is
|
| 493 |
+
no secure-harness attestation, and nothing in it establishes Korean quality or general
|
| 494 |
+
intent-classification behaviour. An earlier draft of the preregistration wrongly claimed the split
|
| 495 |
+
had never been opened; that draft is kept beside the corrected one rather than deleted.
|
| 496 |
+
|
| 497 |
+
What the freeze fixed in advance: the roster of eleven arms, the decision threshold for every
|
| 498 |
+
arm/seed/precision — each read from the arm's existing calibration report and never recomputed on
|
| 499 |
+
test — the aggregation rule, the metric set, and the rule that every result would be published
|
| 500 |
+
whatever it said. The archived payload had to match SHA-256 `ed224269dc…` before extraction. It
|
| 501 |
+
did. 31 of 31 jobs completed, 55 documents, 14 labels, zero failures, no training and no threshold
|
| 502 |
+
search. All 31 per-run metrics were then recomputed from the saved probabilities by a separate
|
| 503 |
+
standard-library implementation and agreed with the evaluator.
|
| 504 |
+
|
| 505 |
+

|
| 506 |
+
|
| 507 |
+
| Arm | Precision | Calibration | Test | Δ |
|
| 508 |
+
|---|---|---:|---:|---:|
|
| 509 |
+
| **task-blind 4L N=32,768 — this root's classifier** | BF16 | 0.5719 ± 0.0092 | **0.5988 ± 0.0103** | **+0.0269** |
|
| 510 |
+
| task-blind 4L N=32,768 | Q8_0 | 0.5849 (seed 41) | 0.5862 (seed 41) | +0.0013 |
|
| 511 |
+
| task-blind 4L N=65,536 | BF16 | 0.5775 ± 0.0076 | 0.5832 ± 0.0047 | +0.0057 |
|
| 512 |
+
| task-blind 4L N=16,384 | BF16 | 0.5787 ± 0.0021 | 0.5379 ± 0.0496 | −0.0408 |
|
| 513 |
+
| task-blind 4L N=8,192 | BF16 | 0.5699 ± 0.0160 | 0.5555 ± 0.0070 | −0.0144 |
|
| 514 |
+
| historical 4L v128k | BF16 | 0.5637 ± 0.0321 | 0.5747 ± 0.0183 | +0.0110 |
|
| 515 |
+
| historical 8L v248k | BF16 | 0.5686 ± 0.0212 | 0.5718 ± 0.0213 | +0.0032 |
|
| 516 |
+
| historical 4L v248k | BF16 | 0.5602 ± 0.0391 | 0.5707 ± 0.0081 | +0.0105 |
|
| 517 |
+
| historical 24L v248k | BF16 | 0.5487 ± 0.0228 | 0.5612 ± 0.0102 | +0.0125 |
|
| 518 |
+
| roberta-base 12L | BF16 | 0.5524 ± 0.0143 | 0.5883 ± 0.0109 | +0.0359 |
|
| 519 |
+
| roberta-base 6L | BF16 | 0.5427 ± 0.0139 | 0.5839 ± 0.0152 | +0.0411 |
|
| 520 |
+
| all-positive predictor | — | 0.4756 | **0.4866** | — |
|
| 521 |
+
|
| 522 |
+
Quantization cost 0.0013 macro F1 on test, which is the same "nothing measurable" that section 4
|
| 523 |
+
saw on calibration, now seen on a split the thresholds were not fitted to.
|
| 524 |
+
|
| 525 |
+
**The +0.0269 is not a result about this model.** Nine of the eleven arms moved up, the eleven-arm
|
| 526 |
+
mean move is +0.0085, and the two RoBERTa references moved up the *most* of all. Whatever makes
|
| 527 |
+
this test split score higher than the calibration split applies to the whole board, so the move is
|
| 528 |
+
a property of the split pair rather than of any arm on it.
|
| 529 |
+
|
| 530 |
+
Neither does any arm separate from another. This root's mean ± SD interval overlaps `roberta-base`
|
| 531 |
+
(0.5883 ± 0.0109), `roberta-base-6l` (0.5839 ± 0.0152) and both historical 4L arms. Where it does
|
| 532 |
+
clear an interval it clears N=65,536 by 0.0006 and N=16,384 by 0.0010 — gaps one to two orders of
|
| 533 |
+
magnitude below the spread of the design that produced them.
|
| 534 |
+
|
| 535 |
+
Two arms moved down. N=8,192 fell 0.0144, and N=16,384 fell 0.0408 while carrying the widest
|
| 536 |
+
spread in the table.
|
| 537 |
+
|
| 538 |
+

|
| 539 |
+
|
| 540 |
+
Every arm and every seed lands inside a 0.103-wide band on 55 documents, and for most arms the
|
| 541 |
+
distance between their own three seeds is larger than the distance to their neighbours. N=16,384
|
| 542 |
+
is the extreme: one seed at 0.5951 against two near 0.51.
|
| 543 |
+
|
| 544 |
+

|
| 545 |
+
|
| 546 |
+
Per label, F1 tracks support almost monotonically — `Loaded_Language` at 45 supporting documents
|
| 547 |
+
scores 0.918, and the six labels with 14 or fewer land between 0.446 and 0.481. Recall exceeds
|
| 548 |
+
precision on all fourteen labels: at the frozen thresholds, which run from 0.05 to 0.3, the head
|
| 549 |
+
over-predicts everywhere. A head trained for a deployment would be calibrated differently.
|
| 550 |
+
|
| 551 |
+
The v2 split is now retired from tuning and model selection under this protocol. v1 was not
|
| 552 |
+
opened, and its never-used status is not asserted.
|
| 553 |
+
|
| 554 |
+
Evidence: `REPORT.md` lines 1–89, `RESULTS.json` lines 1–742 (SHA-256
|
| 555 |
+
`0d2da5e2e8b250671bf3b19be0e7bce49f14fe492f9c71ce75717b0eee02dec7`), `PER_LABEL.csv`, and the
|
| 556 |
+
frozen preregistration (SHA-256 `85ec92a0be…`). All six are registered in this project's research
|
| 557 |
+
store, and the run is recorded there as `artifact_final_test_20260923_v1`.
|
| 558 |
+
|
| 559 |
## Two things we checked because they would have changed the reading
|
| 560 |
|
| 561 |
### Does the host matter?
|
|
|
|
| 753 |
|
| 754 |
## What this does not tell you
|
| 755 |
|
| 756 |
+
- **The one test evaluation is not a holdout.** The 2026-09-23 run above used the v2 re-split,
|
| 757 |
+
which this project had used before; its frozen preregistration records that prior exposure.
|
| 758 |
+
Every other number on this card is the opened 56-document calibration split, and there is no
|
| 759 |
+
`test.jsonl` in this repository.
|
| 760 |
- **The evaluation is small.** 56 documents, 14 labels, and the rarest label appears in 6 of
|
| 761 |
them, so one fourteenth of macro F1 rides on six samples. This is the most likely source of the
|
| 762 |
0.005–0.039 seed SDs.
|
|
|
|
| 779 |
| root | the 39,866-entry headless backbone |
|
| 780 |
| [`root_manifest.json`](root_manifest.json) | its vocabulary rule, layer maps, hashes, why this grid point |
|
| 781 |
| [`benchmark/full/`](benchmark/full/) | the 61-arm table, the grid, the same-host cuts |
|
| 782 |
+
| [`benchmark/figures/`](benchmark/figures/) | the seventeen figures above |
|
| 783 |
| [`runtime/`](runtime/) | the two relaxed sources of the dedicated runtime, and its own README |
|
| 784 |
| [`docker/`](docker/) | a container that runs the backbone with a head on it, CPU only |
|
| 785 |
| [`docker-train/`](docker-train/) | train a classifier on your own data, convert, serve |
|
|
@@ -71,6 +71,10 @@ Qwen3.5-0.8B에서 증류한 **헤드 없는 4계층 텍스트 백본**이며,
|
|
| 71 |
분리해 내지는 않았으므로, 그것은 짐작으로 남습니다. 절단과 깊이가 실제로 움직인 것은 파일과
|
| 72 |
에너지이며, 그것은 위의 패널에 보입니다.
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
---
|
| 75 |
|
| 76 |
## 컨테이너로 실행하기
|
|
@@ -415,8 +419,118 @@ p50은 40x(5.7 ms에서 228.9 ms), 가중치 파일은 64x(22 MiB에서 1,435 Mi
|
|
| 415 |

|
| 416 |

|
| 417 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 418 |
---
|
| 419 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 420 |
## 해석을 바꿀 수 있었기에 확인한 두 가지
|
| 421 |
|
| 422 |
### 호스트가 영향을 주는가?
|
|
@@ -609,8 +723,10 @@ A/B/A, 스레드 수 1/3/4, 라이브 서비스 경로도 모두 일치했습니
|
|
| 609 |
|
| 610 |
## 이것이 말해 주지 않는 것
|
| 611 |
|
| 612 |
-
- **홀드아웃
|
| 613 |
-
|
|
|
|
|
|
|
| 614 |
- **평가가 작습니다.** 문서 56개, 레이블 14개, 그리고 가장 드문 레이블은 그중 6개에 나타나므로,
|
| 615 |
macro F1의 14분의 1이 표본 여섯 개에 걸려 있습니다. 이것이 0.005–0.039 시드 SD의 가장 유력한
|
| 616 |
원인입니다.
|
|
@@ -632,7 +748,7 @@ A/B/A, 스레드 수 1/3/4, 라이브 서비스 경로도 모두 일치했습니
|
|
| 632 |
| root | 39,866항목 헤드 없는 백본 |
|
| 633 |
| [`root_manifest.json`](root_manifest.json) | 그 어휘 규칙, 계층 맵, 해시, 왜 이 그리드 포인트인지 |
|
| 634 |
| [`benchmark/full/`](benchmark/full/) | 61개 arm 표, 그리드, 동일 호스트 절단 |
|
| 635 |
-
| [`benchmark/figures/`](benchmark/figures/) | 위의 열 개 그림 |
|
| 636 |
| [`runtime/`](runtime/) | 전용 런타임의 완화된 두 소스와 그 자신의 README |
|
| 637 |
| [`docker/`](docker/) | 헤드를 얹은 백본을 돌리는 컨테이너, CPU 전용 |
|
| 638 |
| [`docker-train/`](docker-train/) | 자기 데이터로 분류기를 학습시키고, 변환하고, 서빙 |
|
|
|
|
| 71 |
분리해 내지는 않았으므로, 그것은 짐작으로 남습니다. 절단과 깊이가 실제로 움직인 것은 파일과
|
| 72 |
에너지이며, 그것은 위의 패널에 보입니다.
|
| 73 |
|
| 74 |
+
이 표의 모든 값은 열어 본 캘리브레이션 분할입니다. 고정 프로토콜 테스트 평가는 2026-09-23에 한 번,
|
| 75 |
+
이 프로젝트가 전에 사용한 적 있는 재분할을 대상으로 수행했으며 — 한 번도 본 적 없는 홀드아웃이
|
| 76 |
+
아닙니다 — 내려간 값까지 포함해 [**최종 테스트**](#최종-테스트)에 전부 보고했습니다.
|
| 77 |
+
|
| 78 |
---
|
| 79 |
|
| 80 |
## 컨테이너로 실행하기
|
|
|
|
| 419 |

|
| 420 |

|
| 421 |
|
| 422 |
+
### 전체 판이 어떻게 생겼는가
|
| 423 |
+
|
| 424 |
+

|
| 425 |
+
|
| 426 |
+
두 패널은 같은 65개 arm을 담고 있으며, 비용 축만 다릅니다. 점선은 파레토 경계 — 품질과 비용을
|
| 427 |
+
동시에 이기는 다른 arm이 없는 arm들 — 입니다. 지연 기준으로는 일곱 개가, 에너지 기준으로는 여덟
|
| 428 |
+
개가 경계를 이루고, 두 경계의 싼 쪽 절반은 모두 RoBERTa 계열이 차지합니다. 이 루트는 품질 축의
|
| 429 |
+
위쪽에 있고, 그 자리에 있기 위해 `roberta-base`의 약 2.7배 지연을 지불합니다.
|
| 430 |
+
|
| 431 |
+

|
| 432 |
+
|
| 433 |
+
나란히 놓으면 이 폭 자체가 결과입니다. 판 전체에서 macro F1은 **1.2배** — 0.4756에서 0.5781까지 —
|
| 434 |
+
움직이는 동안, 가중치 파일은 **64.4배**, GPU 에너지는 **42.9배**, 문서 지연은 **40.3배**
|
| 435 |
+
움직입니다. 품질의 폭은 그 숫자가 보여 주는 것보다도 좁습니다. 그 아래 끝이 정확히 전부 양성으로
|
| 436 |
+
답하는 예측기이므로, 판 전체가 "무엇이든 예"라고 답하는 모델 위 0.093의 유효 범위 안에 들어갑니다.
|
| 437 |
+
peak RSS는 거의 움직이지 않는데, 이는 가중치가 아니라 하네스가 그 값을 지배하기 때문입니다.
|
| 438 |
+
|
| 439 |
+

|
| 440 |
+
|
| 441 |
+
모든 인코더 계열을 같은 프로토콜로 4, 6, 8 계층으로 잘랐고, 각 계열의 전층 체크포인트를 그 옆에
|
| 442 |
+
그대로 두었습니다. **12개 계열 중 7개에서 가장 좋은 arm은 가장 깊은 것이 아닙니다.** `bert-base`,
|
| 443 |
+
`mbert`, `electra-base`는 4계층에서, `minilm-multilingual`은 6계층에서, `mpnet-base`와
|
| 444 |
+
`mdeberta-v3`는 8계층에서 정점을 찍습니다. 깊이가 값을 하는 다섯 계열 — roberta, xlmr, deberta-v3,
|
| 445 |
+
albert, modernbert — 은 그 대가를 지연과 파일 크기로 치릅니다. 세 개의 arm은 공유 프로토콜 아래에서
|
| 446 |
+
아예 학습되지 않았고 정확히 바닥에 앉아 있습니다.
|
| 447 |
+
|
| 448 |
+
이것은 이 과제를, 이 해상도에서 관찰한 결과입니다. 깊이가 중요하지 않다는 주장이 아니며, 이 루트가
|
| 449 |
+
네 계층인 이유도 아닙니다. 그 이유는 3절에 있습니다.
|
| 450 |
+
|
| 451 |
+

|
| 452 |
+
|
| 453 |
+
마지막 그림이 이 순위에 대한 정직한 요약입니다. 판 전체의 3시드 SD 중앙값은 0.0116이고, 여섯 개의
|
| 454 |
+
arm이 최고점으로부터 그 한 개 SD 안에 들어 있습니다. 몇몇 SD 막대는 상위 열 개 arm 사이의 간격
|
| 455 |
+
전체보다도 넓습니다. 이 순위는 측정했기 때문에 보고하는 것이지, 측정이 그것을 가려냈기 때문이
|
| 456 |
+
아닙니다.
|
| 457 |
+
|
| 458 |
---
|
| 459 |
|
| 460 |
+
---
|
| 461 |
+
|
| 462 |
+
## 최종 테스트
|
| 463 |
+
|
| 464 |
+
2026-09-23에 SemEval-2020 Task 11 **v2 테스트 재분할**을 대상으로 고정 프로토콜 평가를 한 번
|
| 465 |
+
수행했습니다. 사전등록 문서는 테스트 바이트를 한 개도 읽기 전에 동결되었습니다.
|
| 466 |
+
|
| 467 |
+
**이것은 한 번도 본 적 없는 홀드아웃이 아닙니다.** 이 프로젝트는 v2 분할을 전에 사용한 적이 있고,
|
| 468 |
+
동결된 문서는 이전 테스트 노출을 `true`로 기록하고 있습니다. 공식 리더보드 테스트가 아니며, 보안
|
| 469 |
+
하네스 증명도 없고, 여기서 한국어 품질이나 일반적인 의도 분류 성능이 확립되지도 않습니다.
|
| 470 |
+
사전등록 초안은 이 분할이 한 번도 열린 적 없다고 잘못 적었는데, 그 초안은 삭제하지 않고 수정본
|
| 471 |
+
옆에 그대로 보관하고 있습니다.
|
| 472 |
+
|
| 473 |
+
동결이 미리 고정한 것은 다음과 같습니다. 열한 개 arm의 명단, arm·시드·정밀도별 결정 임계값 — 각각
|
| 474 |
+
그 arm의 기존 캘리브레이션 보고서에서 읽어 온 값이며 테스트에서 다시 계산하지 않았습니다 — 집계
|
| 475 |
+
규칙, 지표 집합, 그리고 어떤 결과가 나오든 전부 공개한다는 규칙입니다. 보관된 페이로드는 추출
|
| 476 |
+
전에 SHA-256 `ed224269dc…`와 일치해야 했고, 일치했습니다. 31개 작업 중 31개가 완료되었으며, 문서
|
| 477 |
+
55개, 레이블 14개, 실패 0건, 학습 없음, 임계값 탐색 없음입니다. 이후 31개 실행의 지표 전부를 저장된
|
| 478 |
+
확률로부터 별도의 표준 라이브러리 구현으로 다시 계산했고, 평가기와 일치했습니다.
|
| 479 |
+
|
| 480 |
+

|
| 481 |
+
|
| 482 |
+
| arm | 정밀도 | 캘리브레이션 | 테스트 | Δ |
|
| 483 |
+
|---|---|---:|---:|---:|
|
| 484 |
+
| **task-blind 4L N=32,768 — 이 루트의 분류기** | BF16 | 0.5719 ± 0.0092 | **0.5988 ± 0.0103** | **+0.0269** |
|
| 485 |
+
| task-blind 4L N=32,768 | Q8_0 | 0.5849 (시드 41) | 0.5862 (시드 41) | +0.0013 |
|
| 486 |
+
| task-blind 4L N=65,536 | BF16 | 0.5775 ± 0.0076 | 0.5832 ± 0.0047 | +0.0057 |
|
| 487 |
+
| task-blind 4L N=16,384 | BF16 | 0.5787 ± 0.0021 | 0.5379 ± 0.0496 | −0.0408 |
|
| 488 |
+
| task-blind 4L N=8,192 | BF16 | 0.5699 ± 0.0160 | 0.5555 ± 0.0070 | −0.0144 |
|
| 489 |
+
| 과거 4L v128k | BF16 | 0.5637 ± 0.0321 | 0.5747 ± 0.0183 | +0.0110 |
|
| 490 |
+
| 과거 8L v248k | BF16 | 0.5686 ± 0.0212 | 0.5718 ± 0.0213 | +0.0032 |
|
| 491 |
+
| 과거 4L v248k | BF16 | 0.5602 ± 0.0391 | 0.5707 ± 0.0081 | +0.0105 |
|
| 492 |
+
| 과거 24L v248k | BF16 | 0.5487 ± 0.0228 | 0.5612 ± 0.0102 | +0.0125 |
|
| 493 |
+
| roberta-base 12L | BF16 | 0.5524 ± 0.0143 | 0.5883 ± 0.0109 | +0.0359 |
|
| 494 |
+
| roberta-base 6L | BF16 | 0.5427 ± 0.0139 | 0.5839 ± 0.0152 | +0.0411 |
|
| 495 |
+
| 전부 양성 예측기 | — | 0.4756 | **0.4866** | — |
|
| 496 |
+
|
| 497 |
+
양자화는 테스트에서 macro F1 0.0013을 치렀습니다. 4절이 캘리브레이션에서 본 "측정되지 않는 정도"와
|
| 498 |
+
같은 값이며, 이번에는 임계값을 맞추지 않은 분할에서 본 것입니다.
|
| 499 |
+
|
| 500 |
+
**+0.0269은 이 모델에 관한 결과가 아닙니다.** 열한 개 arm 중 아홉 개가 올랐고, 열한 개의 평균
|
| 501 |
+
이동은 +0.0085이며, 가장 많이 오른 것은 RoBERTa 레퍼런스 *둘*입니다. 이 테스트 분할이 캘리브레이션
|
| 502 |
+
분할보다 높게 나오게 만드는 무언가는 판 전체에 적용되므로, 이 이동은 어느 arm의 성질이 아니라
|
| 503 |
+
분할 쌍의 성질입니다.
|
| 504 |
+
|
| 505 |
+
어느 arm도 다른 arm과 분리되지 않습니다. 이 루트의 평균 ± SD 구간은 `roberta-base`(0.5883 ±
|
| 506 |
+
0.0109), `roberta-base-6l`(0.5839 ± 0.0152), 그리고 과거 4L arm 둘과 겹칩니다. 구간을 벗어나는
|
| 507 |
+
경우에도 N=65,536과는 0.0006, N=16,384와는 0.0010 차이인데, 이는 그 값을 만들어 낸 설계의 산포보다
|
| 508 |
+
한두 자릿수 작은 간격입니다.
|
| 509 |
+
|
| 510 |
+
내려간 arm도 둘 있습니다. N=8,192는 0.0144, N=16,384는 0.0408 떨어졌고, 후자는 표에서 가장 넓은
|
| 511 |
+
산포를 함께 지고 있습니다.
|
| 512 |
+
|
| 513 |
+

|
| 514 |
+
|
| 515 |
+
모든 arm의 모든 시드가 문서 55개 위에서 폭 0.103의 띠 안에 들어옵니다. 그리고 대부분의 arm에서는
|
| 516 |
+
자기 시드 세 개 사이의 거리가 이웃 arm까지의 거리보다 큽니다. N=16,384가 극단적인 사례로, 한 시드는
|
| 517 |
+
0.5951인데 나머지 둘은 0.51 부근입니다.
|
| 518 |
+
|
| 519 |
+

|
| 520 |
+
|
| 521 |
+
레이블별로 보면 F1은 거의 단조롭게 support를 따라갑니다. 지지 문서가 45개인 `Loaded_Language`는
|
| 522 |
+
0.918이고, 14개 이하인 여섯 레이블은 0.446에서 0.481 사이에 놓입니다. 그리고 열네 개 레이블 전부에서
|
| 523 |
+
recall이 precision을 넘습니다. 0.05에서 0.3에 걸친 동결 임계값 아래에서 이 헤드는 어디서나
|
| 524 |
+
과다예측합니다. 실제 배포를 위해 학습하는 헤드라면 다르게 캘리브레이션하실 것입니다.
|
| 525 |
+
|
| 526 |
+
v2 분할은 이 프로토콜 아래에서 튜닝과 모델 선택 용도로 이제 은퇴했습니다. v1은 열지 않았으며,
|
| 527 |
+
v1이 한 번도 쓰이지 않았다고 주장하지는 않습니다.
|
| 528 |
+
|
| 529 |
+
증거: `REPORT.md` 1–89행, `RESULTS.json` 1–742행(SHA-256
|
| 530 |
+
`0d2da5e2e8b250671bf3b19be0e7bce49f14fe492f9c71ce75717b0eee02dec7`), `PER_LABEL.csv`, 그리고
|
| 531 |
+
동결된 사전등록 문서(SHA-256 `85ec92a0be…`). 여섯 건 모두 이 프로젝트의 리서치 스토어에 등록되어
|
| 532 |
+
있고, 이 실행은 그곳에 `artifact_final_test_20260923_v1`로 기록되어 있습니다.
|
| 533 |
+
|
| 534 |
## 해석을 바꿀 수 있었기에 확인한 두 가지
|
| 535 |
|
| 536 |
### 호스트가 영향을 주는가?
|
|
|
|
| 723 |
|
| 724 |
## 이것이 말해 주지 않는 것
|
| 725 |
|
| 726 |
+
- **단 한 번의 테스트 평가는 홀드아웃이 아닙니다.** 위 2026-09-23 실행은 v2 재분할을 사용했고,
|
| 727 |
+
이 프로젝트는 그 분할을 전에 사용한 적이 있으며, 동결된 사전등록 문서가 그 이전 노출을 기록하고
|
| 728 |
+
있습니다. 이 카드의 다른 모든 수치는 열어 본 56개 문서 캘리브레이션 분할이고, 이 저장소에는
|
| 729 |
+
`test.jsonl`이 없습니다.
|
| 730 |
- **평가가 작습니다.** 문서 56개, 레이블 14개, 그리고 가장 드문 레이블은 그중 6개에 나타나므로,
|
| 731 |
macro F1의 14분의 1이 표본 여섯 개에 걸려 있습니다. 이것이 0.005–0.039 시드 SD의 가장 유력한
|
| 732 |
원인입니다.
|
|
|
|
| 748 |
| root | 39,866항목 헤드 없는 백본 |
|
| 749 |
| [`root_manifest.json`](root_manifest.json) | 그 어휘 규칙, 계층 맵, 해시, 왜 이 그리드 포인트인지 |
|
| 750 |
| [`benchmark/full/`](benchmark/full/) | 61개 arm 표, 그리드, 동일 호스트 절단 |
|
| 751 |
+
| [`benchmark/figures/`](benchmark/figures/) | 위의 열일곱 개 그림 |
|
| 752 |
| [`runtime/`](runtime/) | 전용 런타임의 완화된 두 소스와 그 자신의 README |
|
| 753 |
| [`docker/`](docker/) | 헤드를 얹은 백본을 돌리는 컨테이너, CPU 전용 |
|
| 754 |
| [`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
|
|
@@ -26,6 +26,13 @@ c9c09f7f91a79d992434262ba50e0f2cb92d34058da6be7e3468a3eef60fe131 benchmark/figu
|
|
| 26 |
9519e737b99e44271434e9ea45c5fac583dc0ef16ecf3bc3deb3f0c71a39618e benchmark/figures/08_long_context_window.png
|
| 27 |
c28ed833aef0025646d3d6c9163fbafeec84712e6941fb1c4590b9a47d55c84b benchmark/figures/09_tokenizer_cost.png
|
| 28 |
56a7353a71dd727077a420fd39a97b21742b8af87fabcbd4d4cdbfe09f5a0b40 benchmark/figures/10_root_on_every_metric.png
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 29 |
7c238ed969ed64ac7c63084c663ecc531b618622199a77b09d28f461db11a657 benchmark/figures/README.md
|
| 30 |
afd203646513c49140a25ef2a562a064286a4fa5efb495e6a73f83f0106e7933 benchmark/figures/cross_task/05_why_4l_edge_tradeoff.csv
|
| 31 |
22f531ea586f9c79ac8f114345cec56e74ea5ed97aa3726f7310018666ae6312 benchmark/figures/cross_task/05_why_4l_edge_tradeoff.pdf
|
|
@@ -39,6 +46,7 @@ aac7d3400cf8dcb3ac8b6fc317c27ded5343231c93476997011a3bb56b5e4d6c benchmark/figu
|
|
| 39 |
f3e79965f14ec0fd20d11c361d25c3a3abc4c9a20b5dbce9fac0660bb12789f0 benchmark/figures/cross_task/README.md
|
| 40 |
c3312e98b8abbbdc488e5dfde77c4110732e8d08abe9ed931bd985ad799ecad8 benchmark/figures/fig10.py
|
| 41 |
3f6b1b82af1619afab8c753cd836040a7353a3226646b8b99fb641617e4ecec7 benchmark/figures/make_more_figures.py
|
|
|
|
| 42 |
078b4f34897e0a1b9bcff66ce21ba0feb28d72c86eaa8cef608f110c48f52f2e benchmark/figures/v128k_compression.csv
|
| 43 |
014da12a435e3404b31470e2642d180dc111eb608fb8513fd993097019698b7a benchmark/figures/v128k_quality_efficiency.csv
|
| 44 |
644e5b7469c0b4e0b63d5ecaa1c0f2fc8e07af8bb5d9e4d3ae5a88166de374f3 benchmark/figures/v128k_quality_storage.png
|
|
|
|
| 1 |
77fd4710def9ec3c0f6225800e0235f15a425abd4a8b03559127fcd782612049 LICENSE
|
| 2 |
+
95f3404c3d41a691e4de347b7cf5b510769a23763808dc9a9a713b72340e765c README.md
|
| 3 |
+
d3aa51ff96c38b1bfb6fa2391591f7a4cf95357418c43a95f88ef9545d372608 README_KO.md
|
| 4 |
bf4073de38c3905effd2dcce6f38894f325c8a0a79bedfb2779f0545c86b9c9f benchmark/BENCHMARK_CARD.md
|
| 5 |
e01bd7ac59a3212e44b77e5fcfbc7c41ec91d74c998306d2438209d6ef1a3aa7 benchmark/arm_summary.csv
|
| 6 |
2c544c3907c4f592d724f658850b8c6b9634845cb5b50a66ebd2ea6c5d48ce0b benchmark/compression_ladder.csv
|
|
|
|
| 26 |
9519e737b99e44271434e9ea45c5fac583dc0ef16ecf3bc3deb3f0c71a39618e benchmark/figures/08_long_context_window.png
|
| 27 |
c28ed833aef0025646d3d6c9163fbafeec84712e6941fb1c4590b9a47d55c84b benchmark/figures/09_tokenizer_cost.png
|
| 28 |
56a7353a71dd727077a420fd39a97b21742b8af87fabcbd4d4cdbfe09f5a0b40 benchmark/figures/10_root_on_every_metric.png
|
| 29 |
+
71aa42d84f71dfc975a6906fc84e6f39ed934bf3ed3038aadbc1ad924ca2bf21 benchmark/figures/11_final_test_calibration_to_test.png
|
| 30 |
+
cfb3071db4077bed7aee06c141f5f12873779a732516242580c889fb223c14fa benchmark/figures/12_final_test_per_label.png
|
| 31 |
+
4a9704a2f64b58f43b7feab7b845c0fd860f45fc3a2a2ea6ae996473c9eafff4 benchmark/figures/13_final_test_seed_spread.png
|
| 32 |
+
5d2963b051a74a71c63ed0d226352b207ca501d3a356d99c32e832217fc51f05 benchmark/figures/14_field_quality_vs_cost.png
|
| 33 |
+
4639fc800d53bec745bc9f1d5df37a6177f06116689798738c3677b2f05d303a benchmark/figures/15_field_metric_spans.png
|
| 34 |
+
5da006b4d603cc858c480a3cff94d9584327170933268fbac8ce8f131db87fcf benchmark/figures/16_field_depth_effect.png
|
| 35 |
+
dc621e266a694496ba30d9d5c03e89e4d6c65616f5c25df0216d012102cd3f53 benchmark/figures/17_field_resolution_floor.png
|
| 36 |
7c238ed969ed64ac7c63084c663ecc531b618622199a77b09d28f461db11a657 benchmark/figures/README.md
|
| 37 |
afd203646513c49140a25ef2a562a064286a4fa5efb495e6a73f83f0106e7933 benchmark/figures/cross_task/05_why_4l_edge_tradeoff.csv
|
| 38 |
22f531ea586f9c79ac8f114345cec56e74ea5ed97aa3726f7310018666ae6312 benchmark/figures/cross_task/05_why_4l_edge_tradeoff.pdf
|
|
|
|
| 46 |
f3e79965f14ec0fd20d11c361d25c3a3abc4c9a20b5dbce9fac0660bb12789f0 benchmark/figures/cross_task/README.md
|
| 47 |
c3312e98b8abbbdc488e5dfde77c4110732e8d08abe9ed931bd985ad799ecad8 benchmark/figures/fig10.py
|
| 48 |
3f6b1b82af1619afab8c753cd836040a7353a3226646b8b99fb641617e4ecec7 benchmark/figures/make_more_figures.py
|
| 49 |
+
5f6b1979dcb7ff4ee897659b2f902dd3cc36339987f42f681bff7be5ae06d304 benchmark/figures/make_readout_figures.py
|
| 50 |
078b4f34897e0a1b9bcff66ce21ba0feb28d72c86eaa8cef608f110c48f52f2e benchmark/figures/v128k_compression.csv
|
| 51 |
014da12a435e3404b31470e2642d180dc111eb608fb8513fd993097019698b7a benchmark/figures/v128k_quality_efficiency.csv
|
| 52 |
644e5b7469c0b4e0b63d5ecaa1c0f2fc8e07af8bb5d9e4d3ae5a88166de374f3 benchmark/figures/v128k_quality_storage.png
|
|
Git LFS Details
|
|
Git LFS Details
|
|
Git LFS Details
|
|
Git LFS Details
|
|
Git LFS Details
|
|
Git LFS Details
|
|
Git LFS Details
|
|
@@ -0,0 +1,517 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Figures for the model card: the sealed final test, and the 65-arm field.
|
| 2 |
+
|
| 3 |
+
Run from benchmark/figures/ . Figures 14-17 need only benchmark/full/*.csv, which ships here.
|
| 4 |
+
|
| 5 |
+
Sources, all read-only:
|
| 6 |
+
final test agents/sessions/2026-09-23/S-20260923-final-test-preflight-v1/{RESULTS,PER_LABEL}.csv
|
| 7 |
+
... BASELINE.json
|
| 8 |
+
65-arm field benchmark/full/61_arm_bf16.csv + benchmark/full/taskblind_grid.csv
|
| 9 |
+
|
| 10 |
+
Writes PNGs next to this file.
|
| 11 |
+
"""
|
| 12 |
+
import csv
|
| 13 |
+
import json
|
| 14 |
+
import os
|
| 15 |
+
import re
|
| 16 |
+
import collections
|
| 17 |
+
import math
|
| 18 |
+
import statistics as st
|
| 19 |
+
|
| 20 |
+
import matplotlib
|
| 21 |
+
matplotlib.use("Agg")
|
| 22 |
+
import matplotlib.pyplot as plt
|
| 23 |
+
from matplotlib.lines import Line2D
|
| 24 |
+
from matplotlib.patches import Patch
|
| 25 |
+
from matplotlib.colors import LinearSegmentedColormap, Normalize
|
| 26 |
+
from matplotlib.cm import ScalarMappable
|
| 27 |
+
|
| 28 |
+
# Paths are resolved relative to this file's repository, or overridden by env vars.
|
| 29 |
+
HERE = os.path.dirname(os.path.abspath(__file__))
|
| 30 |
+
REPO = os.path.abspath(os.path.join(HERE, "..", ".."))
|
| 31 |
+
# Final-test inputs are not redistributed in this repository; point SESS at a local copy of
|
| 32 |
+
# agents/sessions/2026-09-23/S-20260923-final-test-preflight-v1/ to rebuild figures 11-13.
|
| 33 |
+
SESS = os.environ.get("FINAL_TEST_DIR", os.path.join(REPO, "final_test")) + os.sep
|
| 34 |
+
BENCH = os.path.join(REPO, "benchmark", "full") + os.sep
|
| 35 |
+
OUT = os.path.join(HERE, "")
|
| 36 |
+
|
| 37 |
+
FLOOR_TEST = json.load(open(SESS + "BASELINE.json"))["always_positive"]["macro_f1"]
|
| 38 |
+
FLOOR_CAL = 0.475627 # all-positive predictor on the opened calibration split
|
| 39 |
+
|
| 40 |
+
INK, MUTED, FAINT = "#1a1a1a", "#6a6a72", "#9a9aa2"
|
| 41 |
+
GRID = "#e6e6ea"
|
| 42 |
+
# Series hues, checked pairwise in OKLab and under deuter/prot/trit simulation:
|
| 43 |
+
# minimum normal-vision dE 18.3, minimum CVD dE 8.6, all >= 30 against white.
|
| 44 |
+
ROOTC, GRIDC, QWENC, ENCC = "#1a5496", "#0d7a52", "#b05512", "#9a9aa2"
|
| 45 |
+
# Status, never a series colour: always carries a dash pattern, an arrow direction,
|
| 46 |
+
# a marker shape or a text label as well.
|
| 47 |
+
DOWN = "#c0392b"
|
| 48 |
+
ACC = QWENC
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def style(ax, xgrid=True, ygrid=False):
|
| 52 |
+
ax.grid(axis="both" if (xgrid and ygrid) else ("x" if xgrid else "y"),
|
| 53 |
+
color=GRID, lw=.8)
|
| 54 |
+
ax.set_axisbelow(True)
|
| 55 |
+
for s in ("top", "right"):
|
| 56 |
+
ax.spines[s].set_visible(False)
|
| 57 |
+
for s in ("bottom", "left"):
|
| 58 |
+
ax.spines[s].set_color("#c9c9cf")
|
| 59 |
+
ax.tick_params(length=0, colors="#4a4a52")
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def footer(fig, text):
|
| 63 |
+
fig.text(.006, .012, text, fontsize=8.6, color=MUTED, ha="left")
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
# ---------------------------------------------------------------- final test
|
| 67 |
+
FT_NAMES = {
|
| 68 |
+
"qwen35-taskblind-base4l-N32768-commonhead": "task-blind 4L N=32,768",
|
| 69 |
+
"qwen35-taskblind-base4l-N65536-commonhead": "task-blind 4L N=65,536",
|
| 70 |
+
"qwen35-taskblind-base4l-N16384-commonhead": "task-blind 4L N=16,384",
|
| 71 |
+
"qwen35-taskblind-base4l-N8192-commonhead": "task-blind 4L N=8,192",
|
| 72 |
+
"qwen35-taskfree-base24l-v248k-commonhead": "historical 24L v248k",
|
| 73 |
+
"qwen35-taskfree-base8l-v248k-commonhead": "historical 8L v248k",
|
| 74 |
+
"qwen35-taskfree-base4l-v248k-commonhead": "historical 4L v248k",
|
| 75 |
+
"qwen35-taskfree-base4l-v128k-commonhead": "historical 4L v128k",
|
| 76 |
+
"roberta-base-commonhead": "RoBERTa-base 12L",
|
| 77 |
+
"roberta-base-6l-commonhead": "RoBERTa-base 6L",
|
| 78 |
+
}
|
| 79 |
+
FT_FAM = {"published": (ROOTC, "published root (N=32,768)"),
|
| 80 |
+
"grid": (GRIDC, "other task-blind grid points"),
|
| 81 |
+
"historical": (ENCC, "historical qwen arms"),
|
| 82 |
+
"roberta": (QWENC, "RoBERTa references")}
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def ft_family(arm):
|
| 86 |
+
if arm == "qwen35-taskblind-base4l-N32768-commonhead":
|
| 87 |
+
return "published"
|
| 88 |
+
if arm.startswith("qwen35-taskblind"):
|
| 89 |
+
return "grid"
|
| 90 |
+
if arm.startswith("roberta"):
|
| 91 |
+
return "roberta"
|
| 92 |
+
return "historical"
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
def ft_arms():
|
| 96 |
+
rows = list(csv.DictReader(open(SESS + "RESULTS.csv")))
|
| 97 |
+
g = collections.defaultdict(list)
|
| 98 |
+
for r in rows:
|
| 99 |
+
g[(r["arm_id"], r["precision"])].append(r)
|
| 100 |
+
arms = []
|
| 101 |
+
for (a, p), rs in g.items():
|
| 102 |
+
cal = [float(x["calibration_macro_f1"]) for x in rs]
|
| 103 |
+
tst = [float(x["test_macro_f1"]) for x in rs]
|
| 104 |
+
arms.append({"label": FT_NAMES[a] + (" Q8_0" if p != "BF16" else ""),
|
| 105 |
+
"fam": ft_family(a), "n": len(rs),
|
| 106 |
+
"cal": st.mean(cal), "test": st.mean(tst),
|
| 107 |
+
"seeds": sorted(tst),
|
| 108 |
+
"sd": st.stdev(tst) if len(tst) > 1 else 0.0})
|
| 109 |
+
arms.sort(key=lambda d: d["test"])
|
| 110 |
+
return arms
|
| 111 |
+
|
| 112 |
+
|
| 113 |
+
def fig_calibration_to_test():
|
| 114 |
+
arms = ft_arms()
|
| 115 |
+
fig, ax = plt.subplots(figsize=(10.6, 6.4))
|
| 116 |
+
fig.patch.set_facecolor("white")
|
| 117 |
+
XMIN, XMAX, XVAL = .468, .646, .624
|
| 118 |
+
for i, d in enumerate(arms):
|
| 119 |
+
c = FT_FAM[d["fam"]][0]
|
| 120 |
+
up = d["test"] >= d["cal"]
|
| 121 |
+
if d["sd"]:
|
| 122 |
+
ax.plot([d["test"] - d["sd"], d["test"] + d["sd"]], [i, i],
|
| 123 |
+
color=c, lw=7, alpha=.20, solid_capstyle="butt", zorder=2)
|
| 124 |
+
ax.annotate("", xy=(d["test"], i), xytext=(d["cal"], i),
|
| 125 |
+
arrowprops=dict(arrowstyle="-|>", lw=2.0,
|
| 126 |
+
color=c if up else DOWN, shrinkA=0, shrinkB=0, alpha=.9))
|
| 127 |
+
ax.plot([d["cal"]], [i], "o", ms=7, mfc="white", mec=c, mew=2.0, zorder=3)
|
| 128 |
+
ax.plot([d["test"]], [i], "o", ms=9.5, color=c, zorder=4,
|
| 129 |
+
mec="white", mew=1.6)
|
| 130 |
+
bold = "bold" if d["fam"] == "published" else "normal"
|
| 131 |
+
ax.text(XVAL, i, f"{d['test']:.4f}", va="center", ha="right",
|
| 132 |
+
fontsize=9.5, color=INK, fontweight=bold)
|
| 133 |
+
dl = d["test"] - d["cal"]
|
| 134 |
+
ax.text(XMAX - .002, i, f"{dl:+.4f}", va="center", ha="right", fontsize=9,
|
| 135 |
+
color=DOWN if dl < 0 else "#4a7a4a", fontweight=bold)
|
| 136 |
+
ax.axvline(FLOOR_TEST, color=DOWN, ls=(0, (5, 3)), lw=1.6, zorder=1)
|
| 137 |
+
ax.text(FLOOR_TEST + .003, -.62, f"all-positive floor {FLOOR_TEST:.4f}",
|
| 138 |
+
va="bottom", ha="left", fontsize=8.8, color=DOWN)
|
| 139 |
+
ax.text(XVAL, len(arms) - .34, "test", ha="right", va="bottom", fontsize=8.6, color=FAINT)
|
| 140 |
+
ax.text(XMAX - .002, len(arms) - .34, "Δ", ha="right", va="bottom",
|
| 141 |
+
fontsize=8.6, color=FAINT)
|
| 142 |
+
ax.set_yticks(range(len(arms)))
|
| 143 |
+
ax.set_yticklabels([d["label"] for d in arms], fontsize=9.5)
|
| 144 |
+
for t, d in zip(ax.get_yticklabels(), arms):
|
| 145 |
+
if d["fam"] == "published":
|
| 146 |
+
t.set_fontweight("bold")
|
| 147 |
+
ax.set_xlabel("macro F1 (14-label fixed denominator)", fontsize=10)
|
| 148 |
+
ax.set_xlim(XMIN, XMAX)
|
| 149 |
+
ax.set_ylim(-.95, len(arms) - .15)
|
| 150 |
+
ax.set_xticks([.48, .50, .52, .54, .56, .58, .60])
|
| 151 |
+
style(ax, xgrid=True)
|
| 152 |
+
ax.spines["left"].set_visible(False)
|
| 153 |
+
handles = [Line2D([], [], marker="o", ls="", color=c, ms=8, label=l)
|
| 154 |
+
for c, l in FT_FAM.values()]
|
| 155 |
+
handles.append(Line2D([], [], color=DOWN, lw=2.2, label="moved down on test"))
|
| 156 |
+
ax.legend(handles=handles, fontsize=8.6, frameon=False, ncol=1,
|
| 157 |
+
loc="center left", bbox_to_anchor=(.005, .30), labelspacing=.55)
|
| 158 |
+
ax.set_title("Calibration → sealed test\n"
|
| 159 |
+
"hollow = calibration mean, filled = test mean, band = sample SD over 3 seeds",
|
| 160 |
+
fontsize=11.5, loc="left", color=INK, pad=12)
|
| 161 |
+
footer(fig, "Nine of eleven arms moved up and the two RoBERTa references moved up the most, "
|
| 162 |
+
"so the direction belongs to the split pair, not to any model.")
|
| 163 |
+
fig.tight_layout(rect=(0, .035, 1, 1))
|
| 164 |
+
fig.savefig(OUT + "11_final_test_calibration_to_test.png", dpi=170, facecolor="white")
|
| 165 |
+
plt.close(fig)
|
| 166 |
+
|
| 167 |
+
|
| 168 |
+
def fig_per_label():
|
| 169 |
+
pl = [r for r in csv.DictReader(open(SESS + "PER_LABEL.csv"))
|
| 170 |
+
if r["job_id"].startswith("qwen35-taskblind-base4l-N32768-commonhead")
|
| 171 |
+
and r["job_id"].endswith("BF16")]
|
| 172 |
+
by = collections.defaultdict(list)
|
| 173 |
+
for r in pl:
|
| 174 |
+
by[r["label"]].append(r)
|
| 175 |
+
num = lambda v: float(v) if v not in ("", "null", None) else None
|
| 176 |
+
pts = []
|
| 177 |
+
for lab, rs in by.items():
|
| 178 |
+
pts.append({"lab": lab.replace("_", " ").replace(",", " / "),
|
| 179 |
+
"sup": int(rs[0]["support"]),
|
| 180 |
+
"f1": st.mean([num(r["f1"]) for r in rs]),
|
| 181 |
+
"p": st.mean([num(r["precision"]) for r in rs]),
|
| 182 |
+
"r": st.mean([num(r["recall"]) for r in rs])})
|
| 183 |
+
pts.sort(key=lambda d: d["f1"])
|
| 184 |
+
ramp = LinearSegmentedColormap.from_list("sup", ["#cfe0f0", "#14406f"])
|
| 185 |
+
norm = Normalize(vmin=min(p["sup"] for p in pts), vmax=max(p["sup"] for p in pts))
|
| 186 |
+
fig, ax = plt.subplots(figsize=(10.2, 6.2))
|
| 187 |
+
fig.patch.set_facecolor("white")
|
| 188 |
+
ax.barh(range(len(pts)), [p["f1"] for p in pts], height=.64,
|
| 189 |
+
color=[ramp(norm(p["sup"])) for p in pts], zorder=3)
|
| 190 |
+
for i, p in enumerate(pts):
|
| 191 |
+
ax.text(p["f1"] - .012, i, f"{p['f1']:.3f}", va="center", ha="right",
|
| 192 |
+
fontsize=8.8, color="white", fontweight="bold", zorder=4)
|
| 193 |
+
ax.text(p["f1"] + .014, i, f"n={p['sup']} P {p['p']:.2f} / R {p['r']:.2f}",
|
| 194 |
+
va="center", ha="left", fontsize=8.4, color="#5a5a62", zorder=4)
|
| 195 |
+
macro = st.mean([p["f1"] for p in pts])
|
| 196 |
+
ax.axvline(macro, color="#14406f", ls=(0, (5, 3)), lw=1.5, zorder=2)
|
| 197 |
+
ax.text(macro + .006, len(pts) - .42, f"macro F1 {macro:.4f}",
|
| 198 |
+
fontsize=9, color="#14406f", ha="left", va="bottom")
|
| 199 |
+
ax.set_yticks(range(len(pts)))
|
| 200 |
+
ax.set_yticklabels([p["lab"] for p in pts], fontsize=9)
|
| 201 |
+
ax.set_xlabel("per-label F1 (3-seed mean)", fontsize=10)
|
| 202 |
+
ax.set_xlim(0, 1.16)
|
| 203 |
+
ax.set_ylim(-.7, len(pts) - .15)
|
| 204 |
+
ax.set_xticks([0, .2, .4, .6, .8, 1.0])
|
| 205 |
+
style(ax, xgrid=True)
|
| 206 |
+
ax.spines["left"].set_visible(False)
|
| 207 |
+
ax.set_title("Published root on the sealed test, per label\n"
|
| 208 |
+
"bar shade = support in the 55 test documents",
|
| 209 |
+
fontsize=11.5, loc="left", color=INK, pad=12)
|
| 210 |
+
cb = fig.colorbar(ScalarMappable(norm=norm, cmap=ramp), ax=ax,
|
| 211 |
+
orientation="horizontal", fraction=.030, pad=.12, aspect=44)
|
| 212 |
+
cb.set_label("label support (documents)", fontsize=8.6, color=MUTED)
|
| 213 |
+
cb.ax.tick_params(labelsize=8, length=0, colors=MUTED)
|
| 214 |
+
cb.outline.set_visible(False)
|
| 215 |
+
footer(fig, "Recall exceeds precision on all fourteen labels: at these thresholds the head "
|
| 216 |
+
"over-predicts. F1 tracks support almost monotonically.")
|
| 217 |
+
fig.tight_layout(rect=(0, .035, 1, 1))
|
| 218 |
+
fig.savefig(OUT + "12_final_test_per_label.png", dpi=170, facecolor="white")
|
| 219 |
+
plt.close(fig)
|
| 220 |
+
|
| 221 |
+
|
| 222 |
+
def fig_seed_spread():
|
| 223 |
+
arms = [a for a in ft_arms() if a["n"] == 3]
|
| 224 |
+
arms.sort(key=lambda d: d["test"])
|
| 225 |
+
fig, ax = plt.subplots(figsize=(10.6, 5.8))
|
| 226 |
+
fig.patch.set_facecolor("white")
|
| 227 |
+
for i, d in enumerate(arms):
|
| 228 |
+
c = FT_FAM[d["fam"]][0]
|
| 229 |
+
ax.plot([min(d["seeds"]), max(d["seeds"])], [i, i], color=c, lw=2, alpha=.35,
|
| 230 |
+
solid_capstyle="round", zorder=2)
|
| 231 |
+
for s in d["seeds"]:
|
| 232 |
+
ax.plot([s], [i], "o", ms=7, color=c, alpha=.85, zorder=3, mec="white", mew=1.2)
|
| 233 |
+
ax.plot([d["test"]], [i], "|", ms=17, color=INK, mew=2.0, zorder=4)
|
| 234 |
+
ax.text(.638, i, f"spread {max(d['seeds']) - min(d['seeds']):.4f}",
|
| 235 |
+
va="center", ha="right", fontsize=8.8,
|
| 236 |
+
color=DOWN if max(d["seeds"]) - min(d["seeds"]) > .05 else MUTED)
|
| 237 |
+
ax.set_yticks(range(len(arms)))
|
| 238 |
+
ax.set_yticklabels([d["label"] for d in arms], fontsize=9.5)
|
| 239 |
+
ax.set_xlabel("test macro F1, one dot per seed (41 / 42 / 43); tick = mean", fontsize=10)
|
| 240 |
+
ax.set_xlim(.478, .642)
|
| 241 |
+
ax.set_ylim(-.7, len(arms) - .3)
|
| 242 |
+
style(ax, xgrid=True)
|
| 243 |
+
ax.spines["left"].set_visible(False)
|
| 244 |
+
ax.set_title("Seed spread swamps the gaps between arms\n"
|
| 245 |
+
"every arm and seed lands inside a 0.103-wide band on 55 documents",
|
| 246 |
+
fontsize=11.5, loc="left", color=INK, pad=12)
|
| 247 |
+
footer(fig, "N=16,384 is the extreme case: one seed at 0.5951 and two near 0.51. "
|
| 248 |
+
"Three seeds give a sample SD, not a confidence interval.")
|
| 249 |
+
fig.tight_layout(rect=(0, .038, 1, 1))
|
| 250 |
+
fig.savefig(OUT + "13_final_test_seed_spread.png", dpi=170, facecolor="white")
|
| 251 |
+
plt.close(fig)
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
# ------------------------------------------------------------- the 65 arms
|
| 255 |
+
BASE_LAYERS = {"bert-base": 12, "roberta-base": 12, "xlmr-base": 12, "deberta-v3-base": 12,
|
| 256 |
+
"mdeberta-v3": 12, "albert-base-v2": 12, "electra-base": 12, "mpnet-base": 12,
|
| 257 |
+
"modernbert-base": 22, "distilbert-base": 6, "mbert": 12,
|
| 258 |
+
"minilm-multilingual": 12}
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def encoder_family(arm_id):
|
| 262 |
+
s = arm_id.replace("-commonhead", "")
|
| 263 |
+
m = re.match(r"^(.*?)-(\d+)l$", s)
|
| 264 |
+
if m and m.group(1) in BASE_LAYERS:
|
| 265 |
+
return m.group(1), int(m.group(2))
|
| 266 |
+
if s in BASE_LAYERS:
|
| 267 |
+
return s, BASE_LAYERS[s]
|
| 268 |
+
return None, None
|
| 269 |
+
|
| 270 |
+
|
| 271 |
+
def field():
|
| 272 |
+
"""61 measured arms plus the 4 task-blind grid points = 65."""
|
| 273 |
+
out = []
|
| 274 |
+
for r in csv.DictReader(open(BENCH + "61_arm_bf16.csv")):
|
| 275 |
+
fam, _ = encoder_family(r["arm_id"])
|
| 276 |
+
out.append({"id": r["arm_id"], "q": float(r["macro_f1_mean"]),
|
| 277 |
+
"sd": float(r["macro_f1_sd"]), "p50": float(r["p50_ms"]),
|
| 278 |
+
"mib": float(r["weight_file_mib"]), "j": float(r["gpu_j_per_doc"]),
|
| 279 |
+
"rss": float(r["peak_rss_mib"]),
|
| 280 |
+
"floor": r["degenerate_at_floor"] == "True",
|
| 281 |
+
"grp": "encoder" if fam else "qwen"})
|
| 282 |
+
for r in csv.DictReader(open(BENCH + "taskblind_grid.csv")):
|
| 283 |
+
n = int(r["grid_point_N"])
|
| 284 |
+
out.append({"id": f"qwen35-taskblind-base4l-N{n}", "q": float(r["macro_f1_mean"]),
|
| 285 |
+
"sd": float(r["macro_f1_sd"]), "p50": float(r["bf16_p50_ms"]),
|
| 286 |
+
"mib": float(r["root_mib"]), "j": float(r["bf16_gpu_j_per_doc"]),
|
| 287 |
+
"rss": float("nan"), "floor": False,
|
| 288 |
+
"grp": "root" if n == 32768 else "grid"})
|
| 289 |
+
return out
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
GRP = {"root": (ROOTC, "this root (N=32,768)"),
|
| 293 |
+
"grid": (GRIDC, "task-blind grid"),
|
| 294 |
+
"qwen": (QWENC, "other Qwen (3.5 and 2.5)"),
|
| 295 |
+
"encoder": (ENCC, "encoder families (10)")}
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
def pareto(rows, key):
|
| 299 |
+
keep = []
|
| 300 |
+
for r in rows:
|
| 301 |
+
if not any(o["q"] >= r["q"] and o[key] <= r[key] and
|
| 302 |
+
(o["q"] > r["q"] or o[key] < r[key]) for o in rows):
|
| 303 |
+
keep.append(r)
|
| 304 |
+
return sorted(keep, key=lambda r: r[key])
|
| 305 |
+
|
| 306 |
+
|
| 307 |
+
def fig_quality_vs_cost():
|
| 308 |
+
F = field()
|
| 309 |
+
fig, axes = plt.subplots(1, 2, figsize=(14.6, 6.2), sharey=True)
|
| 310 |
+
fig.patch.set_facecolor("white")
|
| 311 |
+
for ax, key, xlab, unit in (
|
| 312 |
+
(axes[0], "p50", "document p50 latency (ms, log scale)", "ms"),
|
| 313 |
+
(axes[1], "j", "GPU energy per document (J, log scale)", "J")):
|
| 314 |
+
front = pareto(F, key)
|
| 315 |
+
ax.plot([r[key] for r in front], [r["q"] for r in front],
|
| 316 |
+
color=INK, lw=1.3, alpha=.45, zorder=2, ls=(0, (4, 3)))
|
| 317 |
+
for grp in ("encoder", "qwen", "grid", "root"):
|
| 318 |
+
pts = [r for r in F if r["grp"] == grp]
|
| 319 |
+
if not pts:
|
| 320 |
+
continue
|
| 321 |
+
big = grp == "root"
|
| 322 |
+
ax.scatter([r[key] for r in pts], [r["q"] for r in pts],
|
| 323 |
+
s=150 if big else 46, color=GRP[grp][0], zorder=5 if big else 3,
|
| 324 |
+
edgecolor="white", linewidth=1.6 if big else .9,
|
| 325 |
+
marker="D" if big else "o", alpha=1 if big else .92)
|
| 326 |
+
ax.axhline(FLOOR_CAL, color=DOWN, ls=(0, (5, 3)), lw=1.4, zorder=1)
|
| 327 |
+
ax.set_xscale("log")
|
| 328 |
+
ax.set_xlabel(xlab, fontsize=10)
|
| 329 |
+
style(ax, xgrid=True, ygrid=True)
|
| 330 |
+
labelled = []
|
| 331 |
+
for j, r in enumerate(front):
|
| 332 |
+
if r["grp"] in ("root", "grid"):
|
| 333 |
+
continue
|
| 334 |
+
nm = r["id"].replace("-commonhead", "").replace("qwen35-", "")
|
| 335 |
+
# skip a label that would land on top of one already placed
|
| 336 |
+
if any(abs(math.log10(r[key]) - math.log10(o)) < .17 and abs(r["q"] - q) < .012
|
| 337 |
+
for o, q in labelled):
|
| 338 |
+
continue
|
| 339 |
+
labelled.append((r[key], r["q"]))
|
| 340 |
+
ax.annotate(nm, (r[key], r["q"]), textcoords="offset points",
|
| 341 |
+
xytext=(9, -13 if j % 2 == 0 else 9), fontsize=7.6, color=MUTED)
|
| 342 |
+
rt = [r for r in F if r["grp"] == "root"][0]
|
| 343 |
+
ax.annotate("this root", (rt[key], rt["q"]), textcoords="offset points",
|
| 344 |
+
xytext=(13, 9), fontsize=8.8, color=ROOTC, fontweight="bold")
|
| 345 |
+
axes[0].set_ylabel("macro F1 (3-seed mean)", fontsize=10)
|
| 346 |
+
axes[0].set_ylim(.466, .582)
|
| 347 |
+
axes[0].text(6.1, FLOOR_CAL + .0018, f"all-positive floor {FLOOR_CAL:.4f}",
|
| 348 |
+
fontsize=8.5, color=DOWN, va="bottom")
|
| 349 |
+
handles = [Line2D([], [], marker="D" if k == "root" else "o", ls="", color=c,
|
| 350 |
+
ms=9 if k == "root" else 7, label=l) for k, (c, l) in GRP.items()]
|
| 351 |
+
handles.append(Line2D([], [], color=INK, lw=1.3, ls=(0, (4, 3)), alpha=.45,
|
| 352 |
+
label="Pareto frontier"))
|
| 353 |
+
handles.append(Line2D([], [], color=DOWN, lw=1.4, ls=(0, (5, 3)),
|
| 354 |
+
label="all-positive floor"))
|
| 355 |
+
fig.legend(handles=handles, fontsize=8.8, frameon=False, ncol=6,
|
| 356 |
+
loc="upper left", bbox_to_anchor=(.006, .945), columnspacing=1.5)
|
| 357 |
+
fig.suptitle("65 arms, one frozen protocol: quality barely moves, cost moves by orders of magnitude",
|
| 358 |
+
fontsize=12.5, x=.006, ha="left", y=.975, color=INK)
|
| 359 |
+
footer(fig, "Dashed line is the Pareto frontier. The RoBERTa family owns the cheap half of it; "
|
| 360 |
+
"the Qwen3.5 arms own the top. Every arm lives within 0.093 of a trivial all-positive predictor.")
|
| 361 |
+
fig.tight_layout(rect=(0, .038, 1, .895))
|
| 362 |
+
fig.savefig(OUT + "14_field_quality_vs_cost.png", dpi=170, facecolor="white")
|
| 363 |
+
plt.close(fig)
|
| 364 |
+
|
| 365 |
+
|
| 366 |
+
def fig_metric_spans():
|
| 367 |
+
F = field()
|
| 368 |
+
specs = [("macro F1", [r["q"] for r in F], "", 4),
|
| 369 |
+
("peak RSS", [r["rss"] for r in F if r["rss"] == r["rss"]], "MiB", 0),
|
| 370 |
+
("document p50", [r["p50"] for r in F], "ms", 0),
|
| 371 |
+
("GPU energy / doc", [r["j"] for r in F], "J", 2),
|
| 372 |
+
("weight file", [r["mib"] for r in F], "MiB", 0)]
|
| 373 |
+
rows = []
|
| 374 |
+
for name, vals, unit, dec in specs:
|
| 375 |
+
lo, hi = min(vals), max(vals)
|
| 376 |
+
rows.append((name, lo, hi, hi / lo if lo > 0 else float("inf"), unit, dec))
|
| 377 |
+
rows.sort(key=lambda t: t[3])
|
| 378 |
+
fig, ax = plt.subplots(figsize=(11.0, 4.4))
|
| 379 |
+
fig.patch.set_facecolor("white")
|
| 380 |
+
for i, (name, lo, hi, ratio, unit, dec) in enumerate(rows):
|
| 381 |
+
c = ROOTC if ratio > 5 else QWENC
|
| 382 |
+
ax.plot([1, ratio], [i, i], color=c, lw=9, solid_capstyle="round",
|
| 383 |
+
alpha=.85, zorder=3)
|
| 384 |
+
ax.text(ratio * 1.09, i, f"{ratio:.1f}×", va="center", ha="left",
|
| 385 |
+
fontsize=11, color=c, fontweight="bold")
|
| 386 |
+
ax.text(ratio * 1.09, i - .30, f"{lo:,.{dec}f} → {hi:,.{dec}f} {unit}".strip(),
|
| 387 |
+
va="center", ha="left", fontsize=8.3, color=MUTED)
|
| 388 |
+
ax.set_yticks(range(len(rows)))
|
| 389 |
+
ax.set_yticklabels([r[0] for r in rows], fontsize=10)
|
| 390 |
+
ax.set_xscale("log")
|
| 391 |
+
ax.set_xlim(.92, 260)
|
| 392 |
+
ax.set_ylim(-.7, len(rows) - .3)
|
| 393 |
+
ax.set_xlabel("ratio of the largest arm to the smallest, across all 65 arms (log scale)",
|
| 394 |
+
fontsize=10)
|
| 395 |
+
style(ax, xgrid=True)
|
| 396 |
+
ax.spines["left"].set_visible(False)
|
| 397 |
+
ax.set_title("What actually varies across the field\n"
|
| 398 |
+
"the answers are nearly identical; what they cost to produce is not",
|
| 399 |
+
fontsize=11.5, loc="left", color=INK, pad=12)
|
| 400 |
+
footer(fig, f"The best arm beats a trivial all-positive predictor ({FLOOR_CAL:.4f}) by 0.0930, "
|
| 401 |
+
"and the worst three tie it exactly. Peak RSS is dominated by the harness, not the model.")
|
| 402 |
+
fig.tight_layout(rect=(0, .05, 1, 1))
|
| 403 |
+
fig.savefig(OUT + "15_field_metric_spans.png", dpi=170, facecolor="white")
|
| 404 |
+
plt.close(fig)
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
def fig_depth_effect():
|
| 408 |
+
rows = list(csv.DictReader(open(BENCH + "61_arm_bf16.csv")))
|
| 409 |
+
fams = collections.defaultdict(list)
|
| 410 |
+
for r in rows:
|
| 411 |
+
fam, L = encoder_family(r["arm_id"])
|
| 412 |
+
if fam:
|
| 413 |
+
fams[fam].append((L, float(r["macro_f1_mean"]),
|
| 414 |
+
r["degenerate_at_floor"] == "True"))
|
| 415 |
+
order = sorted(fams, key=lambda k: -max(q for _, q, _ in fams[k]))
|
| 416 |
+
fig, axes = plt.subplots(3, 4, figsize=(14.2, 7.4), sharex=True, sharey=True)
|
| 417 |
+
fig.patch.set_facecolor("white")
|
| 418 |
+
shallow_wins = 0
|
| 419 |
+
for ax, fam in zip(axes.ravel(), order):
|
| 420 |
+
pts = sorted(fams[fam])
|
| 421 |
+
xs = [p[0] for p in pts]
|
| 422 |
+
ys = [p[1] for p in pts]
|
| 423 |
+
deepest = max(xs)
|
| 424 |
+
best_L = max(pts, key=lambda t: t[1])[0]
|
| 425 |
+
win = best_L != deepest
|
| 426 |
+
shallow_wins += win
|
| 427 |
+
c = ROOTC if win else QWENC
|
| 428 |
+
ax.plot(xs, ys, "-", color=c, lw=2, zorder=3, alpha=.9)
|
| 429 |
+
for L, q, fl in pts:
|
| 430 |
+
ax.plot([L], [q], "X" if fl else "o", ms=8.5 if fl else (8 if L == best_L else 6),
|
| 431 |
+
color=DOWN if fl else c, zorder=4, mec="white", mew=1.3)
|
| 432 |
+
ax.axhline(FLOOR_CAL, color=DOWN, ls=(0, (4, 3)), lw=1.1, zorder=1)
|
| 433 |
+
ax.set_title(fam, fontsize=9.5, loc="left", color=INK, pad=4)
|
| 434 |
+
ax.text(.97, .18, "best at %dL" % best_L, transform=ax.transAxes,
|
| 435 |
+
ha="right", va="bottom", fontsize=8.2,
|
| 436 |
+
color=ROOTC if win else MUTED, fontweight="bold" if win else "normal")
|
| 437 |
+
ax.set_xticks([4, 6, 8, 12, 22])
|
| 438 |
+
ax.set_ylim(.468, .566)
|
| 439 |
+
style(ax, xgrid=False, ygrid=True)
|
| 440 |
+
for ax in axes[-1]:
|
| 441 |
+
ax.set_xlabel("layers", fontsize=9)
|
| 442 |
+
for ax in axes[:, 0]:
|
| 443 |
+
ax.set_ylabel("macro F1", fontsize=9)
|
| 444 |
+
fig.suptitle(f"Depth does not reliably buy quality: in {shallow_wins} of {len(order)} encoder "
|
| 445 |
+
f"families the best arm is not the deepest one",
|
| 446 |
+
fontsize=12.5, x=.006, ha="left", y=.978, color=INK)
|
| 447 |
+
handles = [Line2D([], [], color=ROOTC, lw=2.4, label="a shallower variant wins"),
|
| 448 |
+
Line2D([], [], color=QWENC, lw=2.4, label="the deepest variant wins"),
|
| 449 |
+
Line2D([], [], marker="X", ls="", color=DOWN, ms=8,
|
| 450 |
+
label="failed to train: sits exactly on the all-positive floor")]
|
| 451 |
+
fig.legend(handles=handles, fontsize=9, frameon=False, ncol=3,
|
| 452 |
+
loc="lower left", bbox_to_anchor=(.006, .035))
|
| 453 |
+
footer(fig, "Same frozen protocol, three seeds, same split. Each family's own full-depth "
|
| 454 |
+
"checkpoint is the rightmost point.")
|
| 455 |
+
fig.tight_layout(rect=(0, .10, 1, .948))
|
| 456 |
+
fig.savefig(OUT + "16_field_depth_effect.png", dpi=170, facecolor="white")
|
| 457 |
+
plt.close(fig)
|
| 458 |
+
|
| 459 |
+
|
| 460 |
+
def fig_resolution_floor():
|
| 461 |
+
F = sorted(field(), key=lambda r: r["q"])
|
| 462 |
+
med_sd = st.median([r["sd"] for r in F if r["sd"] > 0])
|
| 463 |
+
top = max(r["q"] for r in F)
|
| 464 |
+
band = top - med_sd
|
| 465 |
+
fig, ax = plt.subplots(figsize=(10.4, 9.4))
|
| 466 |
+
fig.patch.set_facecolor("white")
|
| 467 |
+
ax.axvspan(band, top + .004, color=ROOTC, alpha=.08, zorder=1)
|
| 468 |
+
for i, r in enumerate(F):
|
| 469 |
+
c = GRP[r["grp"]][0]
|
| 470 |
+
if r["sd"]:
|
| 471 |
+
ax.plot([r["q"] - r["sd"], r["q"] + r["sd"]], [i, i], color=c, lw=3.4,
|
| 472 |
+
alpha=.30, solid_capstyle="butt", zorder=2)
|
| 473 |
+
if r["floor"]:
|
| 474 |
+
ax.plot([r["q"]], [i], "X", ms=7.5, color=DOWN, zorder=4, mec="white", mew=1.0)
|
| 475 |
+
else:
|
| 476 |
+
ax.plot([r["q"]], [i], "D" if r["grp"] == "root" else "o",
|
| 477 |
+
ms=8 if r["grp"] == "root" else 5.4, color=c,
|
| 478 |
+
zorder=4, mec="white", mew=1.0)
|
| 479 |
+
ax.axvline(FLOOR_CAL, color=DOWN, ls=(0, (5, 3)), lw=1.5, zorder=3)
|
| 480 |
+
ax.text(FLOOR_CAL - .0015, len(F) * .55, f"all-positive floor {FLOOR_CAL:.4f}",
|
| 481 |
+
rotation=90, va="center", ha="right", fontsize=8.6, color=DOWN)
|
| 482 |
+
n_in = sum(1 for r in F if r["q"] >= band)
|
| 483 |
+
ax.set_yticks(range(len(F)))
|
| 484 |
+
ax.set_yticklabels([r["id"].replace("-commonhead", "") for r in F], fontsize=6.9)
|
| 485 |
+
for t, r in zip(ax.get_yticklabels(), F):
|
| 486 |
+
if r["grp"] == "root":
|
| 487 |
+
t.set_fontweight("bold")
|
| 488 |
+
t.set_color(ROOTC)
|
| 489 |
+
ax.set_xlabel("macro F1 (3-seed mean, bar = sample SD)", fontsize=10)
|
| 490 |
+
ax.set_xlim(.466, .607)
|
| 491 |
+
ax.set_ylim(-1.2, len(F) - .3)
|
| 492 |
+
style(ax, xgrid=True)
|
| 493 |
+
ax.spines["left"].set_visible(False)
|
| 494 |
+
ax.set_title("All 65 arms on one axis\n"
|
| 495 |
+
f"shaded band = one median seed-SD ({med_sd:.4f}) below the best arm; "
|
| 496 |
+
f"{n_in} arms fall inside it",
|
| 497 |
+
fontsize=11.5, loc="left", color=INK, pad=12)
|
| 498 |
+
handles = [Line2D([], [], marker="D" if k == "root" else "o", ls="", color=c,
|
| 499 |
+
ms=8 if k == "root" else 6, label=l) for k, (c, l) in GRP.items()]
|
| 500 |
+
handles.append(Line2D([], [], marker="X", ls="", color=DOWN, ms=7.5,
|
| 501 |
+
label="failed to train (on the floor)"))
|
| 502 |
+
ax.legend(handles=handles, fontsize=8.6, frameon=False, loc="upper left",
|
| 503 |
+
bbox_to_anchor=(.012, .995), labelspacing=.5)
|
| 504 |
+
footer(fig, "Three arms — mdeberta-v3-4l, deberta-v3-base-6l, deberta-v3-base-8l — score exactly "
|
| 505 |
+
"the floor. They are reported rather than dropped.")
|
| 506 |
+
fig.tight_layout(rect=(0, .026, 1, 1))
|
| 507 |
+
fig.savefig(OUT + "17_field_resolution_floor.png", dpi=170, facecolor="white")
|
| 508 |
+
plt.close(fig)
|
| 509 |
+
|
| 510 |
+
|
| 511 |
+
if __name__ == "__main__":
|
| 512 |
+
os.makedirs(OUT, exist_ok=True)
|
| 513 |
+
for fn in (fig_calibration_to_test, fig_per_label, fig_seed_spread,
|
| 514 |
+
fig_quality_vs_cost, fig_metric_spans, fig_depth_effect,
|
| 515 |
+
fig_resolution_floor):
|
| 516 |
+
fn()
|
| 517 |
+
print("ok", fn.__name__)
|