thundercode commited on
Commit
267161f
Β·
verified Β·
1 Parent(s): ecb0365

release: add docs/RESEARCH_NOTES.md

Browse files
Files changed (1) hide show
  1. docs/RESEARCH_NOTES.md +86 -46
docs/RESEARCH_NOTES.md CHANGED
@@ -1,7 +1,8 @@
1
  # Research Notes
2
 
3
- Engineering findings, negative results and design decisions that would otherwise be lost. Each was
4
- learned by **probe or execution**, not by assumption, and each is recorded so it is not rediscovered.
 
5
 
6
  **Status tags:** `MEASURED` Β· `RESOLVED` Β· `REJECTED` Β· `OPEN` Β· `ATTEMPTED`.
7
 
@@ -11,32 +12,37 @@ learned by **probe or execution**, not by assumption, and each is recorded so it
11
 
12
  ### F4-1 β€” the MiniLM tokenizer ceiling (`MEASURED`)
13
 
14
- The MiniLM tokenizer's own ceiling is **256** (verified by probe). The project truncates to **128** β€”
15
  a deliberate truncation *well inside* the ceiling, not the model limit. Satellite queries are short;
16
  halving the sequence halves attention cost for no measurable accuracy loss. The encoder **asserts**
17
- `max_length ≀ 256`, because truncating above the ceiling is a silent no-op.
 
 
18
 
19
  ### F4-2 β€” the router needs no GPU (`MEASURED`)
20
 
21
  The encoder is frozen, so embeddings are **cached** and the 50,822-parameter adapter trains on cached
22
- vectors. **Measured on CPU: 20 epochs / 4,096 vectors in 0.28 s.** No GPU required.
 
 
23
 
24
  ### F4-3 β€” splits must be by group (`MEASURED`)
25
 
26
  Splits are by **group** (template / hard-negative family), never by example. Hard-negative families
27
  are placed in the **test** split so their accuracy measures generalisation rather than memorisation.
28
- Splitting by example would leak template variants across the boundary.
29
 
30
  ### F5-1 β€” `AutoModelForVision2Seq` does not exist (`MEASURED`)
31
 
32
- In transformers 5.17.0, `AutoModelForVision2Seq` **does not exist** (not merely deprecated);
33
- `AutoModelForImageTextToText` is present. The loader is resolved by **feature detection**, never
34
- hardcoded.
 
35
 
36
  ### F5-2 β€” the processor cost overrun is ~17Γ—, not 4Γ— (`MEASURED`)
37
 
38
- The processor's default `longest_edge` is **2048**, which upscales 512-px tiles **4Γ—** and then splits
39
- them (`do_image_splitting=True`) into **17 sub-images**:
40
 
41
  | Setting | `pixel_values` | prompt tokens |
42
  |---|---|---|
@@ -44,18 +50,25 @@ them (`do_image_splitting=True`) into **17 sub-images**:
44
  | pinned (`processor_longest_edge: 512`) | `(1, 1, 3, 512, 512)` | β€” |
45
 
46
  The plan estimated a 4Γ— cost overrun; the real figure is **~17Γ—**. The value **must** be set
47
- explicitly on the processor at construction time.
 
48
 
49
  ### F5-3 β€” prompts must go through the chat template (`MEASURED`)
50
 
51
  SmolVLM requires one `<image>` token per image in the prompt. Hand-written prompt strings raise
52
- `ValueError`. Prompts are always built through `processor.apply_chat_template()`.
 
 
 
53
 
54
  ### P7-1 β€” the RemoteCLIP projected dimension is 512 (`MEASURED`)
55
 
56
  The RemoteCLIP ViT-B/32 transformer width is **768**, but `visual.proj` maps to a **projected** dim of
57
- **512**. The grounding head's per-cell feature is `4 Γ— 512 = 2048`, declared in config so
58
- `core/config.py` can validate the head **without importing torch**.
 
 
 
59
 
60
  ### C-1 β€” the availability mask is consumed by the head, not by CROMA (`MEASURED`)
61
 
@@ -64,21 +77,23 @@ by the **fusion head** (`input_dim = 3 Γ— 768 + 12 + 2 = 2318`), not by the enco
64
 
65
  ### C-6 β€” T4 is SM 7.5, so training uses fp16, not bf16 (`MEASURED`)
66
 
67
- The training precision is **fp16** because the target GPU (T4) is compute capability 7.5. bf16 is
68
- not available there.
69
 
70
  ### C-7 β€” `image_resolution % 8 == 0` (`MEASURED`)
71
 
72
- CROMA requires `image_resolution % 8 == 0`. The native value **120** yields 225 patches.
 
73
 
74
  ### C-8 β€” ZeroGPU does not support `torch.compile` (`MEASURED`)
75
 
76
- `torch.compile` must never be enabled on the (historical) ZeroGPU target.
 
77
 
78
- ### C-9 β€” STANet change hyperparameters are upstream-verified (`MEASURED`)
79
 
80
- Change detection uses STANet-style architecture with upstream-verified hyperparameters (PAM
81
- self-attention, ResNet-18 encoder).
82
 
83
  ---
84
 
@@ -86,27 +101,28 @@ self-attention, ResNet-18 encoder).
86
 
87
  **Question:** should grounding decode at 448 or 224?
88
 
89
- **Answer: 224. 448 was rejected** β€” and the rejection is notable because it was *pre-registered* and
90
- then *confirmed* by a paired test:
91
 
92
- | Comparison (over identical samples, n = 16,159) | 448 vs 224 |
93
  |---|---|
94
  | mean best IoU | **βˆ’0.0147** |
95
  | recall@0.5 | βˆ’0.0022 |
96
  | recall@0.10 | βˆ’0.0699 |
97
  | recall@0.25 | βˆ’0.0243 |
98
  | latency | **1.59Γ—** |
99
- | paired 95 % CI | [βˆ’0.0160, βˆ’0.0134] |
 
100
  | paired t | **βˆ’22.63** |
101
  | 448 better on | 8.5 % of records |
102
  | 448 worse on | **20.9 %** of records |
103
 
104
  448 lost on **every** axis. The pre-registered decision rule and the paired test **agree** on 224.
105
- This is a model of how a resolution decision should be made: declared in advance, then tested.
106
 
107
  ---
108
 
109
- ## 3. The router defect β€” a real bug, found and fixed
110
 
111
  ### 3.1 Symptom
112
 
@@ -121,10 +137,10 @@ Two functions with different information:
121
  - **`interpret()`** β€” produces the console's *reading*; **asset-count-blind** (text only).
122
  - **`chooseTask()`** β€” performs *dispatch*; **asset-count-aware**.
123
 
124
- The defect was in the dispatch path's handling of spatial/lexical cues, so region queries fell
125
- through to the generic VQA specialist. See [`ARCHITECTURE.md`](ARCHITECTURE.md) Β§4.
126
 
127
- ### 3.3 Fix and verification (`RESOLVED`)
128
 
129
  The fix was deployed to `SatQuery-Frontend` and validated by **three independent live passes**:
130
 
@@ -141,18 +157,18 @@ Both defect queries now dispatch to `grounding`:
141
  | Where are the built-up areas in this image? | `run_467ffa406f22` | `grounding` |
142
  | Where is the new airport? | `run_46980ba55c62` | `grounding` |
143
 
144
- 24 live runs, 24 correct dispatches, **0 mock nodes**. Screenshots are in
145
  [`../screenshots/`](../screenshots/).
146
 
147
  ---
148
 
149
- ## 4. The harness false-positive β€” caught before it could lie
150
 
151
  An earlier live-validation harness typed queries with **synthetic CDP key events**, which Chrome
152
  **silently drops when the window lacks OS focus**. The harness therefore dispatched the page's
153
  *default* query and still recorded a "result" β€” a **false pass**.
154
 
155
- **Fix:** the current harness **asserts form state before dispatch** (`q_ok`, `obs_ok`, `t0_ok`), and
156
  uses deterministic query entry (`js()` value-set + `type_text()` via CDP `Input.insertText`).
157
 
158
  **Independent check:** the earlier 8/8 run was re-examined and confirmed **not** infected β€” its
@@ -165,9 +181,12 @@ recorded because it is exactly the silent false-positive an evaluation harness m
165
 
166
  `SATQUERY_TRANSPORT=auto` tries the tunnel, then falls through to the forward path on timeout. The
167
  forward path to a **private** repo returns `302` quickly, but the wake step still consumes
168
- `SATQUERY_WAKE_TIMEOUT_S` (120 s) first β€” so a worst-case failed request takes β‰ˆ 249 s
169
- (150 + 120). This is the root shape of the observed transient tunnel gap. Recorded as **OPEN**; a
170
- deployed fix for the `codespace_name` newline on the wake path was authored separately.
 
 
 
171
 
172
  ---
173
 
@@ -175,7 +194,7 @@ deployed fix for the `codespace_name` newline on the wake path was authored sepa
175
 
176
  For *"What changed between the earlier and later image?"* with **one** asset attached, the console
177
  **reads** `change` while dispatch correctly falls back to **`change_vqa`**. This is not a bug: the
178
- reading describes the question's intent, the dispatch respects what can actually be computed with the
179
  assets present. It is documented so it is not mistaken for a defect.
180
 
181
  ---
@@ -184,21 +203,42 @@ assets present. It is documented so it is not mistaken for a defect.
184
 
185
  | Finding | Detail |
186
  |---|---|
187
- | **Dead proxy in the sandbox** | outbound calls need `--noproxy '*'` (curl) or `ProxyHandler({})` (Python). |
 
 
188
  | **pytest is only in the repo venv** | `.venv/Scripts/python.exe`; a bare `pytest` misses it. |
189
- | **Full-suite pytest trips a bulk-delete guard** | sandbox-specific; affects `test_safe_delete_shim`. |
190
  | **Cloudflare `_headers` concatenate** | two matching rules are merged, not overridden; Chromium takes the **first** `max-age`. |
191
  | **Cloudflare 308-redirects `X.html` β†’ `/X`** | reference the extensionless path. |
192
- | **A forwarded Codespace port returns `302`** for a private repo | this is *why* the tunnel exists. |
193
  | **Chrome drops synthetic CDP key events without OS focus** | the harness false-positive (Β§4). |
194
- | **`browser-use` block-buffers stdout** even when redirected | needs explicit line buffering to stream. |
195
 
196
  ---
197
 
198
  ## 8. The BigEarthNet format contradiction (`ATTEMPTED`, reported not resolved)
199
 
200
  The BigEarthNet data format **contradicts the original plan**. This was **reported rather than
201
- silently patched**, because quietly changing the preprocessing would move the frozen config hash. The
202
- BigEarthNet documentation does not specify a percentile stretch; this project nevertheless applies
203
- percentile normalisation (2/98) to match the CROMA contract. That is a **deliberate, documented
204
- choice**, not an upstream fact. See [`DATASETS.md`](DATASETS.md) Β§5.3.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  # Research Notes
2
 
3
+ Engineering findings, negative results, and design decisions that would otherwise be lost. Every one
4
+ was learned by **probe or execution**, not assumption, and every one is recorded so it is not
5
+ rediscovered.
6
 
7
  **Status tags:** `MEASURED` Β· `RESOLVED` Β· `REJECTED` Β· `OPEN` Β· `ATTEMPTED`.
8
 
 
12
 
13
  ### F4-1 β€” the MiniLM tokenizer ceiling (`MEASURED`)
14
 
15
+ The MiniLM tokenizer's own ceiling is **256** (verified by probe). This project truncates to **128** β€”
16
  a deliberate truncation *well inside* the ceiling, not the model limit. Satellite queries are short;
17
  halving the sequence halves attention cost for no measurable accuracy loss. The encoder **asserts**
18
+ `max_length ≀ 256`, because truncating above the ceiling is a **silent no-op**.
19
+
20
+ **Consequence:** `router.max_length: 128` with an assertion, rather than a comment.
21
 
22
  ### F4-2 β€” the router needs no GPU (`MEASURED`)
23
 
24
  The encoder is frozen, so embeddings are **cached** and the 50,822-parameter adapter trains on cached
25
+ vectors. **Measured on CPU: 20 epochs / 4,096 vectors in 0.28 s.**
26
+
27
+ **Consequence:** the router can be retrained during development without a GPU.
28
 
29
  ### F4-3 β€” splits must be by group (`MEASURED`)
30
 
31
  Splits are by **group** (template / hard-negative family), never by example. Hard-negative families
32
  are placed in the **test** split so their accuracy measures generalisation rather than memorisation.
33
+ Splitting by example would leak template variants across the boundary and inflate the score.
34
 
35
  ### F5-1 β€” `AutoModelForVision2Seq` does not exist (`MEASURED`)
36
 
37
+ In transformers 5.17.0, `AutoModelForVision2Seq` **does not exist** (it is not merely deprecated);
38
+ `AutoModelForImageTextToText` is present.
39
+
40
+ **Consequence:** the loader is resolved by **feature detection**, never hardcoded to one class name.
41
 
42
  ### F5-2 β€” the processor cost overrun is ~17Γ—, not 4Γ— (`MEASURED`)
43
 
44
+ The processor's default `longest_edge` is **2048**, which upscales a 512-px tile **4Γ—** and then
45
+ splits it (`do_image_splitting=True`) into sub-images:
46
 
47
  | Setting | `pixel_values` | prompt tokens |
48
  |---|---|---|
 
50
  | pinned (`processor_longest_edge: 512`) | `(1, 1, 3, 512, 512)` | β€” |
51
 
52
  The plan estimated a 4Γ— cost overrun; the real figure is **~17Γ—**. The value **must** be set
53
+ explicitly on the processor at construction time, and `core/config.py` now **enforces**
54
+ `processor_longest_edge <= image.tile_size` so the pin is a control rather than a comment.
55
 
56
  ### F5-3 β€” prompts must go through the chat template (`MEASURED`)
57
 
58
  SmolVLM requires one `<image>` token per image in the prompt. Hand-written prompt strings raise
59
+ `ValueError`.
60
+
61
+ **Consequence:** prompts are always built through `processor.apply_chat_template()`, enforced by the
62
+ config key `vlm.prompt_must_use_chat_template: true`.
63
 
64
  ### P7-1 β€” the RemoteCLIP projected dimension is 512 (`MEASURED`)
65
 
66
  The RemoteCLIP ViT-B/32 transformer width is **768**, but `visual.proj` maps to a **projected** dim of
67
+ **512**. The grounding head's per-cell feature is therefore `4 Γ— 512 = 2048`.
68
+
69
+ **Consequence:** `grounding.encoder_projected_dim: 512` is declared in config so `core/config.py` can
70
+ validate the head **without importing torch**, and `specialists/grounding/remoteclip.py` asserts the
71
+ same value against the real model at load time.
72
 
73
  ### C-1 β€” the availability mask is consumed by the head, not by CROMA (`MEASURED`)
74
 
 
77
 
78
  ### C-6 β€” T4 is SM 7.5, so training uses fp16, not bf16 (`MEASURED`)
79
 
80
+ `training.precision: fp16` because the target GPU (T4) is compute capability 7.5; bf16 is unavailable
81
+ there. The loader validates the value is one of `fp16|bf16|fp32`.
82
 
83
  ### C-7 β€” `image_resolution % 8 == 0` (`MEASURED`)
84
 
85
+ CROMA requires `image_resolution % 8 == 0`. The native value **120** yields 225 patches. Enforced at
86
+ config load.
87
 
88
  ### C-8 β€” ZeroGPU does not support `torch.compile` (`MEASURED`)
89
 
90
+ `torch.compile` must never be enabled on the (historical) ZeroGPU target. Enforced: the loader
91
+ **fails startup** if `deployment.torch_compile` is true.
92
 
93
+ ### C-9 β€” STANet hyperparameters are upstream-verified (`MEASURED`)
94
 
95
+ Change detection uses a STANet-style architecture with upstream-verified hyperparameters: ResNet-18
96
+ encoder, **PAM** self-attention mode, tile 256, threshold 0.50, BCE 0.5 + Dice 0.5.
97
 
98
  ---
99
 
 
101
 
102
  **Question:** should grounding decode at 448 or 224?
103
 
104
+ **Answer: 224. 448 was rejected** β€” notable because the rejection was *pre-registered* and then
105
+ *confirmed* by a paired test over identical samples (n = 16,159):
106
 
107
+ | Comparison (448 vs 224) | Value |
108
  |---|---|
109
  | mean best IoU | **βˆ’0.0147** |
110
  | recall@0.5 | βˆ’0.0022 |
111
  | recall@0.10 | βˆ’0.0699 |
112
  | recall@0.25 | βˆ’0.0243 |
113
  | latency | **1.59Γ—** |
114
+ | paired mean difference | βˆ’0.0147 |
115
+ | paired 95 % CI | **[βˆ’0.0160, βˆ’0.0134]** |
116
  | paired t | **βˆ’22.63** |
117
  | 448 better on | 8.5 % of records |
118
  | 448 worse on | **20.9 %** of records |
119
 
120
  448 lost on **every** axis. The pre-registered decision rule and the paired test **agree** on 224.
121
+ This is the model for how a resolution decision should be made: declared in advance, then tested.
122
 
123
  ---
124
 
125
+ ## 3. The router defect β€” a real bug, found and fixed (`RESOLVED`)
126
 
127
  ### 3.1 Symptom
128
 
 
137
  - **`interpret()`** β€” produces the console's *reading*; **asset-count-blind** (text only).
138
  - **`chooseTask()`** β€” performs *dispatch*; **asset-count-aware**.
139
 
140
+ The defect was in the dispatch path's handling of spatial/lexical cues, so region queries fell through
141
+ to the generic VQA specialist.
142
 
143
+ ### 3.3 Fix and verification
144
 
145
  The fix was deployed to `SatQuery-Frontend` and validated by **three independent live passes**:
146
 
 
157
  | Where are the built-up areas in this image? | `run_467ffa406f22` | `grounding` |
158
  | Where is the new airport? | `run_46980ba55c62` | `grounding` |
159
 
160
+ **24 live runs, 24 correct dispatches, 0 mock nodes.** Screenshots are in
161
  [`../screenshots/`](../screenshots/).
162
 
163
  ---
164
 
165
+ ## 4. The harness false-positive β€” caught before it could lie (`RESOLVED`)
166
 
167
  An earlier live-validation harness typed queries with **synthetic CDP key events**, which Chrome
168
  **silently drops when the window lacks OS focus**. The harness therefore dispatched the page's
169
  *default* query and still recorded a "result" β€” a **false pass**.
170
 
171
+ **Fix:** the current harness **asserts form state before dispatch** (`q_ok`, `obs_ok`, `t0_ok`) and
172
  uses deterministic query entry (`js()` value-set + `type_text()` via CDP `Input.insertText`).
173
 
174
  **Independent check:** the earlier 8/8 run was re-examined and confirmed **not** infected β€” its
 
181
 
182
  `SATQUERY_TRANSPORT=auto` tries the tunnel, then falls through to the forward path on timeout. The
183
  forward path to a **private** repo returns `302` quickly, but the wake step still consumes
184
+ `SATQUERY_WAKE_TIMEOUT_S` (120 s) first β€” so a worst-case failed request takes β‰ˆ **249 s**
185
+ (150 + 120). This is the root shape of the observed transient tunnel gap.
186
+
187
+ A patch adding `forward_unavailable` (503) and `upstream_timeout` (504) codes plus the
188
+ `codespace_name` `.strip()` fix was authored and verified (`py_compile` clean). **Status: OPEN β€” the
189
+ patch is prepared but NOT deployed.**
190
 
191
  ---
192
 
 
194
 
195
  For *"What changed between the earlier and later image?"* with **one** asset attached, the console
196
  **reads** `change` while dispatch correctly falls back to **`change_vqa`**. This is not a bug: the
197
+ reading describes the question's intent; the dispatch respects what can actually be computed with the
198
  assets present. It is documented so it is not mistaken for a defect.
199
 
200
  ---
 
203
 
204
  | Finding | Detail |
205
  |---|---|
206
+ | **Dead proxy in the authoring sandbox** | outbound calls need `--noproxy '*'` (curl) or `ProxyHandler({})` (Python). |
207
+ | **The sandbox proxy is slow for uploads** | `huggingface_hub` uploads stalled at ~51 kB/s through `http_proxy=127.0.0.1:58294`; the fix is to unset `http_proxy`/`https_proxy` and set `no_proxy='*'`. |
208
+ | **`hf_hub_download` returned an EMPTY file** | sha256 `e3b0c442…` (the empty-content hash), which produced a **false FAIL** for all six artifacts. The honest check is a direct HTTPS download with `ProxyHandler({})`. |
209
  | **pytest is only in the repo venv** | `.venv/Scripts/python.exe`; a bare `pytest` misses it. |
210
+ | **The full test suite trips a bulk-delete guard** | sandbox-specific; affects `test_safe_delete_shim`. |
211
  | **Cloudflare `_headers` concatenate** | two matching rules are merged, not overridden; Chromium takes the **first** `max-age`. |
212
  | **Cloudflare 308-redirects `X.html` β†’ `/X`** | reference the extensionless path. |
213
+ | **A forwarded Codespace port returns `302`** | for a private repo β€” this is *why* the tunnel exists. |
214
  | **Chrome drops synthetic CDP key events without OS focus** | the harness false-positive (Β§4). |
215
+ | **`browser-use` block-buffers stdout** | even when redirected; needs explicit line buffering to stream. |
216
 
217
  ---
218
 
219
  ## 8. The BigEarthNet format contradiction (`ATTEMPTED`, reported not resolved)
220
 
221
  The BigEarthNet data format **contradicts the original plan**. This was **reported rather than
222
+ silently patched**, because quietly changing the preprocessing would move the frozen config hash.
223
+
224
+ Two facts matter:
225
+
226
+ 1. The BigEarthNet documentation β€” its uses, mentions, or endorsements β€” does **not** specify a
227
+ percentile stretch. This project nevertheless applies percentile normalisation (2/98) for optical
228
+ inputs to match the CROMA contract. That is a **deliberate, documented choice**, not an upstream
229
+ fact.
230
+ 2. The local subset is **100 % single-label**, against the official 1–11 multi-label scheme, so
231
+ metrics computed on it are **not comparable** to published multi-label numbers.
232
+
233
+ ---
234
+
235
+ ## 9. Where the evidence lives
236
+
237
+ | Topic | Evidence |
238
+ |---|---|
239
+ | Findings F4-1…F5-3, C-1…C-9, P7-1 | `docs/PHASE*.md`, `docs/ARCHITECTURE_*.md`, `docs/CROMA_NORMALISATION_UPSTREAM_EVIDENCE.md` |
240
+ | The 448-vs-224 paired test | `docs/PHASE7_RESOLUTION_DECISION.md` |
241
+ | The VLM rejection | `docs/PHASE6_RUN1_REJECTION_DIAGNOSIS.md`, `artifacts/vlm/phase6_closure.json` |
242
+ | Router defect + 3 live passes | `.workbuddy-ai/scratch/live_validation/` (`run_output.txt`, `run_final2.txt`, `run_final3.txt`) |
243
+ | The undeployed B-07 patch | session scratch `fix-b07-forward-unavailable.patch` |
244
+ | Live validation harness | `.workbuddy-ai/scratch/run_all_postfix2.harness`, `recompute_verdicts.py` |