Skip to content

Commit 093ca47

Browse files
committed
ocaml: re-tune to exp_nodes + depth 2 (Rust config); add --weights flag
A larger LLM-judge sweep on a nonna+deps corpus (nonna, vendor/opengrep, opam libs; n=93 sonnet-judged, vs the earlier n=37 single-lib pass) flips the OCaml tuning: exp_nodes at depth 2 is decisive — spearman(jaccard, overall) 0.592 (base) -> 0.787 (+exp) -> 0.846 (+exp, depth 2). string/field and per-tag weight tweaks were flat-to-negative on top of exp. exp_nodes separates OCaml's variant-dispatch twins (big match-mappers) that otherwise collapse to near-identical blobs. The config now matches Rust (same ML-family expression/match shape), and is robust: ~0.84 on BOTH corpora, where the old string+field config scored 0.88 on generic libs but only 0.59 here. Also adds a --weights name=val runtime flag (signature.ml dfg_weight is now override-aware) so per-tag weights are sweepable like --with/--iters; the sweep confirmed defaults win, so no weight change is encoded.
1 parent b149b2a commit 093ca47

3 files changed

Lines changed: 39 additions & 13 deletions

File tree

nonna/cli/main.ml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,17 @@ let rec strip_profile acc = function
233233
| "--iters" :: v :: rest ->
234234
Dfg.iterations_override := Some (int_of_string v);
235235
strip_profile acc rest
236+
| "--weights" :: v :: rest ->
237+
(* per-DFG-tag weight overrides for tuning sweeps, e.g.
238+
--weights construct=1.3,control=1.2 (tag names per Dfg.tag_name) *)
239+
String.split_on_char ',' v
240+
|> List.iter (fun kv ->
241+
match String.split_on_char '=' kv with
242+
| [ name; w ] -> Signature.set_weight name (float_of_string w)
243+
| _ ->
244+
prerr_endline ("bad --weights entry: " ^ kv);
245+
exit 1);
246+
strip_profile acc rest
236247
| "--with" :: v :: rest ->
237248
(* fold channels into the BASE hashes (ablation studies) *)
238249
String.split_on_char ',' v

nonna/features/dfg.ml

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ let iters_for (lang : Lang.t option) : int =
7272
Tuned via LLM-judge correlation on a Chrome (net/) corpus, 2026-06-15
7373
(n=62 sonnet-judged pairs): spearman(jaccard, judge overall)
7474
0.859 (depth0/no-exp) -> 0.879 (+exp) -> 0.890 (+exp, depth2). *)
75-
| Some (Lang.Rust | Lang.Python | Lang.Python2 | Lang.Python3 | Lang.Cpp) ->
75+
| Some
76+
( Lang.Rust | Lang.Python | Lang.Python2 | Lang.Python3 | Lang.Cpp
77+
| Lang.Ocaml ) ->
7678
2 (* exp-node features specialize per round *)
7779
| _ -> 1)
7880

@@ -196,17 +198,19 @@ let base_cfg_for (lang : Lang.t) : cfg =
196198
Chrome net/ corpus (2026-06-15, n=62): +exp_nodes lifted spearman 0.859->0.879
197199
(0.890 with depth-2 iters). call_names measured flat (0.859) — left off. *)
198200
| Lang.Cpp -> { b with string_values = true; field_names = true; exp_nodes = true }
199-
(* OCaml lands on the C config, NOT the Rust/Python one: record/module field
200-
access (Dot offsets) and format/label strings are API identity, while
201-
exp_nodes — decomposed expression graphs that win for Rust/Python/Cpp —
202-
HURT here. Tuned via LLM-judge correlation on a 5-lib corpus (base,
203-
containers, dune, re, yojson; 2026-06-30, n=37 sonnet-judged pairs):
204-
spearman(jaccard, overall) 0.873 (base) -> 0.884 (+string/field);
205-
+exp_nodes measured 0.844, iters-2 neutral. Corroborated on 732 mined
206-
pairs: samefile FPR halved (0.027 -> 0.014), positive recall unchanged.
207-
(OCaml IL itself required fixing opengrep's AST_to_IL — without it every
208-
OCaml body collapsed to NTodo; see vendor/opengrep patches.) *)
209-
| Lang.Ocaml -> { b with string_values = true; field_names = true }
201+
(* OCaml takes the Rust config (exp_nodes, depth 2 — see iters_for): it is the
202+
same ML-family expression/match-oriented shape, and decomposed expression
203+
graphs are what separate OCaml's ubiquitous variant-dispatch twins (big
204+
`match`-mapper functions that collapse to near-identical blobs without it).
205+
Re-tuned via LLM-judge correlation on a nonna + deps corpus (nonna,
206+
vendor/opengrep, opam libs; 2026-06-30, n=93 sonnet-judged pairs):
207+
spearman(jaccard, overall) 0.592 (base) -> 0.787 (+exp) -> 0.846 (+exp,
208+
depth 2). string/field and per-tag weight tweaks were flat-to-negative on
209+
top of exp. (An earlier n=37 single-lib pass had picked the C config; the
210+
larger judge corrected it — exp wins decisively at scale.)
211+
(OCaml IL itself is recovered by units.ml's normalize_ocaml AST pre-pass;
212+
without it every OCaml body collapses to NTodo.) *)
213+
| Lang.Ocaml -> { b with exp_nodes = true }
210214
(* exp-nodes sweeps (2026-06): decomposed expression graphs at depth 2
211215
win the evolved kind on Rust (MRR 0.723->0.766, r@5 0.812->0.859) AND
212216
Python (0.929->0.950, r@5 0.964->0.986), everything else flat — an

nonna/features/signature.ml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,25 @@ type t = {
1010

1111
(* Per-source weights (v1 defaults: dfg 1.0, call surface 0.8, consts 0.5,
1212
structural 0.3). DFG features are weighted per tag. *)
13-
let dfg_weight (tag : Dfg.tag) : float =
13+
let default_dfg_weight (tag : Dfg.tag) : float =
1414
match tag with
1515
| Dfg.BinOp | Dfg.UnOp | Dfg.Call | Dfg.Field | Dfg.Index | Dfg.Construct
1616
| Dfg.Control ->
1717
1.0
1818
| Dfg.ConstString | Dfg.ConstOther -> 0.8
1919
| Dfg.MacroBag -> 0.8
2020

21+
(* Runtime per-tag weight overrides (--weights, for tuning sweeps), keyed by
22+
Dfg.tag_name. Empty = the defaults above. *)
23+
let weight_overrides : (string, float) Hashtbl.t = Hashtbl.create 8
24+
let set_weight (name : string) (w : float) : unit =
25+
Hashtbl.replace weight_overrides name w
26+
27+
let dfg_weight (tag : Dfg.tag) : float =
28+
match Hashtbl.find_opt weight_overrides (Dfg.tag_name tag) with
29+
| Some w -> w
30+
| None -> default_dfg_weight tag
31+
2132
let misc_weight = 0.8
2233
let structural_weight = 0.3
2334

0 commit comments

Comments
 (0)