Skip to content

feat: oversize handling, exact <facts>, calibration with real engine timings, llama 5.0.4 - #130

Merged
bernardladenthin merged 18 commits into
mainfrom
claude_20260630_00
Jul 2, 2026
Merged

feat: oversize handling, exact <facts>, calibration with real engine timings, llama 5.0.4#130
bernardladenthin merged 18 commits into
mainfrom
claude_20260630_00

Conversation

@bernardladenthin

Copy link
Copy Markdown
Owner

Summary

  • Oversize handling for files beyond a model's context window — a per-rule <onOversize> strategy (fail (default, hard abort), sample, mapReduce, deterministic). mapReduce chunks at line boundaries and combines via a hierarchical reduce with token-safety headroom (so whole-file <maxChunks>=0 coverage isn't trimmed and dense chunks can't overflow context). Huge files (>1 MB) route to a small, CPU-fast window (Granite 4.0 H-1B default fallback) so per-file time stays bounded (~70 min, not hours). Plus a retry-on-overflow guard on the general single-shot path.
  • Exact, language-agnostic <facts> — deterministic regex counts prepended to every file's body (authoritative numbers the sampled AI prose can't reliably produce), a shared reusable <factDefinitions> registry, curated Java/SQL sets, and honestly-labelled Java structural counts (methods (approx), constructors, field declarations (w/ modifier)) validated in the real java.util.regex engine.
  • ai-index:calibrate goal + <calibration> — a preflight between planOnly and a real run that loads each routed model once and measures per-machine throughput, feeding both the plan and the live per-file ETA. Now reads the engine's own exact per-call timings (chatComplete + ChatResponseParser), matching the server's prompt_per_second / predicted_per_second exactly (verified 417/12 tok/s vs the engine's 416.66/12.48), with a wall-clock estimate only as the mock/no-timings fallback.
  • Dependency + housekeeping — bump net.ladenthin:llama 5.0.3 → 5.0.4; normalize pom.xml comments to pure ASCII (em-dashes, section signs).

Test plan

  • Affected unit / integration tests pass locally (mvn verify: 413 tests, SpotBugs clean, ArchUnit intact, PIT 100% on the gated classes)
  • Real-model native tests pass (-DrunNativeLlamaTests=true): mapReduce+facts end-to-end, and generateWithTimings returns real engine timings that scale with prompt size (50 → 1479 prompt tokens)
  • CI is green on this branch
  • Docs / CHANGELOG updated where applicable (CLAUDE.md, README.md, TODO.md)

Related issues / PRs

Checklist

  • I have read CONTRIBUTING.md and CODE_OF_CONDUCT.md
  • My commits follow Conventional Commits
  • No security-sensitive changes (if there are, I have notified the maintainer privately per SECURITY.md)

bernardladenthin and others added 18 commits June 30, 2026 22:30
Adds a per-rule <onOversize> strategy so a file larger than its routed
model's context window is handled deliberately instead of always aborting:

- fail (default)   hard abort; route oversized files to a bigger window
- sample           feed only the head (trimmed) in one call
- mapReduce        chunk at line boundaries (+overlap), summarize each,
                   combine in a final call; <maxChunks>-capped (head+tail+
                   evenly-spaced representative subset)
- deterministic    model-free body (size, line count, head/tail sample)

New: AiOversizeStrategy (parsing/validation), AiSourceChunker (line-boundary
chunking + representative selection), AiDeterministicSummary (model-free body).
AiFieldGenerationConfig gains onOversize/maxChunks; AiFieldGenerationSupport
branches on the strategy and implements the map-reduce loop; SourceFileIndexer
estimates oversize routes (0 for deterministic, (chunks+1)*window for
mapReduce); AiIndexPlan/GenerateMojo only hard-fail when a still-over-window
file is routed with onOversize=fail. Selector validates onOversize fail-fast.

Quality gates: mvn verify BUILD SUCCESS, SpotBugs clean (scoped IMPROPER_UNICODE
suppression for the ASCII config-token match in fromConfig), PIT 498/498 (100%)
on the gated classes. Docs updated (CLAUDE.md, README.md, TODO.md).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
…file)

Prefill is O(n^2) in prompt length, so map-reducing a multi-MB file AT the
big window (granite-bigwindow, ~1.16M-char chunks x maxChunks) was the slowest
possible combination (~6500 min/file in the plan estimate). The fix is the
opposite: chunk with a SMALL window so each prefill is cheap, and cap maxChunks.

- New aiDefinition `granite-4.0-h-tiny-fastchunk` (same Granite model file as the
  big-window preset, contextSize 16384) for the map-reduce of very large files.
- New size-tiered rules huge-java / huge-sql (priority 200, size > ~1 MB) route
  to fastchunk with onOversize=mapReduce, maxChunks=6. The big-window rules drop
  back to a single-pass 275 KB..1 MB band (priority 100); >1 MB no longer
  map-reduces on the 384K window.
- README: route oversized files to a *small* fast model for mapReduce, with the
  O(n^2) rationale; example java-huge now uses gpt-oss-20B-c16k.

Verified with a standalone planOnly repro on a 7.3 MB .java file: routes to
granite-fastchunk (48,175-char chunks), onOversize=mapReduce -> handled,
per-file estimate ~70 min (within the 60-90 min ceiling), BUILD SUCCESS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
…, CPU-fast)

Switches the fastchunk preset (the huge-java/huge-sql onOversize=mapReduce
fallback) from the 7B h-tiny to the smallest Granite 4.0 hybrid, H-1B (~1.5B,
Apache-2.0). Rationale: for the oversize map-reduce path, throughput matters more
than raw quality, and the 1.5B roughly doubles CPU prefill throughput.

Measured on this machine (CPU-only, 16 threads, Q4_K_M) over a 1 MB SQL file,
maxChunks=6:
  - 3B h-micro : 16m39s  (~90 tok/s)
  - 1.5B h-1b  :  8m27s  (~175 tok/s)   <- new default
GPU (RTX 3070) does the same run in ~40s. Per-file time is bounded by maxChunks
(~8.5 min CPU regardless of file size); a 10 MB file summarizes in ~31s on GPU.

Renamed the preset key granite-4.0-h-tiny-fastchunk -> granite-4.0-h-1b-fastchunk
and updated both rule references; model at X:/Modelle/granite-4.0-h-1b-Q4_K_M.gguf
loads on the existing llama 5.0.3 native (granitehybrid arch, no rebuild).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
… overflow)

Two fixes to the onOversize=mapReduce path, both validated end-to-end on a 10 MB
SQL dump with Granite H-1B at the config that previously crashed (cpt=3, unbounded):

1. Token-safety headroom (fixes the "request exceeds context" crash).
   charsPerToken is only an estimate; token-dense content (SQL with many
   digits/punctuation ~2.8 chars/token) tokenized to MORE tokens than the cpt=3
   char budget assumed, so a chunk overflowed the 16384 context and the native
   layer rejected it (crash at chunk 4). mapReduceSummarize now shrinks the chunk
   window by MAP_REDUCE_TOKEN_HEADROOM_PERCENT (20%) before sizing chunks and
   every map/reduce prompt. Re-run: 278 chunks, 0 overflows.

2. Hierarchical reduce (fixes whole-file coverage loss).
   The single reduce concatenated ALL partials into one call and trimmed to the
   window, silently dropping the file's tail (with maxChunks=0 / 220+ partials the
   final summary lost the views). reducePartials now packs partials into
   window-sized batches (<= MAX_REDUCE_FANIN each), reduces each, and recurses
   until one summary remains. Re-run reduced 278 -> 18 -> 2 -> 1 and the final
   summary now includes both the head table AND the tail view.

Localized to AiFieldGenerationSupport (indexer orchestration; not PIT-gated),
leaving the global window calculator / plan semantics and their tests untouched.
New test onOversize_mapReduce_unbounded_reducesHierarchicallyInMultipleRounds
asserts a second reduce round occurs. mvn verify green, SpotBugs clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
Adds an orthogonal, language-agnostic <facts> add-on to a routing rule: a list
of {label, pattern} counters. On the oversize path (sample/mapReduce/deterministic)
each counter's regex is counted over the WHOLE source and the results are prepended
to the body as an exact facts line. This fixes the inherent map-reduce blind spot -
no single call sees the whole file, so the sampled AI prose guesses counts ("25
rows"); the facts line carries the authoritative number.

Fully generic - the meaning is entirely in the operator's regex, so the same
mechanism counts SQL "(?m)^INSERT" rows or Java "\bboolean\b" fields or Python
"(?m)^\s*def". Multi-line matching is opt-in via the inline (?m) flag.

- config/AiFactCounter: Maven @parameter POJO {label, pattern}.
- config/AiFactExtractor: pure factsBlock(counters, source) counting non-overlapping
  matches + validate(counters) (fail-fast on missing label/pattern or bad regex).
- AiFieldGenerationConfig gains <facts>; AiFieldGenerationSupport computes the block
  over the full source and prepends it (deterministic/mapReduce inline, sample via
  a prefix applied after generation so blank-output detection still sees raw output).
- AiFieldGenerationSelector.validate rejects malformed fact patterns up front.

Validated end-to-end on GPU (H-1B, 1 MB SQL): body led with
"**Facts (exact, whole file):** INSERT rows: 11396; tables: 2; views: 2".
mvn verify green, SpotBugs clean, PIT 518/518 (100%) incl the two new classes.
Docs + POM huge-sql example updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
Follow-up to the <facts> feature so it serves downstream agents on ALL summaries:

- Facts now apply to every file a rule matches, not only oversize ones. The facts
  block is computed once over the full source and prepended whether the body comes
  from a normal single-shot generation or an oversize strategy. Small files get
  exact structural counts too (the AI could count them, but the facts line is the
  authoritative, greppable number an agent can trust without re-reading the source).
- Curated, robust fact sets added to the real self-test rules:
    java / huge-java : type declarations, public declarations, TODO/FIXME, @OverRide
    sql  / huge-sql  : INSERT rows, tables, views, indexes, functions/procedures
  Patterns are line-anchored / unambiguous (verified against real repo files:
  AiFieldGenerationSelector -> 1 type, 4 public; a sample SQL -> 2/1/1/1/1).

New test facts_prependedOnNonOversizeFile covers the always-on path (small source
that fits the window still gets the facts block). mvn verify green (362 tests,
1 skipped), SpotBugs clean; PIT unaffected (no gated-class logic change).
Docs (CLAUDE.md, README) updated to "every file, not just oversize".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
…eneral path

Implements the two follow-ups surfaced by the facts/mapReduce work.

#3 Shared fact registry (removes the per-rule <facts> duplication).
- config/AiFactDefinition {key, facts}: a named, reusable fact-counter group.
- config/AiFactDefinitionSupport: key-indexed lookup; resolveFactsKeys(rules)
  copies a referenced group's counters onto each rule's facts (factsKey wins
  over inline), so the rest of the pipeline reads getFacts() uniformly.
- AiFieldGenerationConfig gains <factsKey>; GenerateMojo adds <factDefinitions>
  and resolves keys (as a MojoExecutionException on a bad key) before validation.
- Self-test POM: java-facts / sql-facts defined once, referenced by java+huge-java
  and sql+huge-sql via <factsKey> instead of four inline blocks.

#5 Retry-on-overflow (the cpt-estimate safety net beyond map-reduce).
- generateWithOverflowRetry: if the provider rejects a prompt as exceeding the
  context (char-based trim under-counted tokens on dense content), re-trim to a
  smaller window and retry (bounded, message-matched so the indexer stays
  decoupled from the JNI binding); any non-overflow error propagates immediately.
  Now covers the single-shot path (sample / dense-but-fitting file), not just
  map-reduce, which already reserves a token headroom.

Quality gates: mvn verify green, SpotBugs clean (scoped suppressions for the
config-bean/service patterns + the deliberate NPE->MojoExecutionException bridge
and the re-throw in the retry loop), PIT 531/531 (100%) incl the two new classes.
Docs (CLAUDE.md) updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
…n estimate

Adds the preflight+timing pass between planOnly and a real run, so the plan's
time estimate reflects THIS machine (GPU/CPU, quant, context) instead of the
built-in reference-CPU coefficients (which mispredicted ~68 min for a run that
took ~43 s on GPU).

- ai-index:calibrate (CalibrateMojo, thin) loads each distinct routed model once
  (catching bad path / OOM / wrong native early), then AiCalibrationRunner (indexer
  layer) runs a warmup + two sized generations and derives prefill/decode
  throughput, printing a paste-ready <calibration> block per <aiDefinition>.
- Throughput source: prefer the provider's own reported timings; the JNI binding's
  stats path does no prompt-proportional work here, so the runner falls back to a
  wall-clock differential (prefill from the mid->near delta, decode from the near
  residual). Sized sources carry a size-specific first line so the prompt cache
  cannot reuse the larger run's KV and collapse the differential.
- New <calibration> {prefillTokensPerSecond, decodeTokensPerSecond, charsPerToken}
  on <aiDefinition> flows into AiGenerationConfig; AiGenerationTimeEstimator gains
  a calibrated linear model (promptTokens/prefillTps + out/decodeTps), used by
  SourceFileIndexer.classify when a routed model has calibration, else the built-in
  fallback. Provider gains generateWithTimings (default = text + zero rates; mock
  returns deterministic synthetic rates).

Validated on the RTX 3070 (granite-4.0-h-1b-fastchunk): calibrate reported
prefill 6651 tok/s, decode 786 tok/s, 3.0 chars/token. mvn verify green, SpotBugs
clean, PIT 565/565 (100%). Docs (CLAUDE.md) updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
The calibrate work wired per-machine calibration into the plan (SourceFileIndexer
.classify) but left the runtime per-file "estimated ~X" log using the built-in
reference-CPU model, so the live ETA disagreed with the (calibrated) plan.
AiFieldGenerationSupport now applies the same calibration-aware estimate.

mvn verify-affected green, SpotBugs clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
…ded)

Adds AiFieldGenerationSupportRealModelTest: drives onOversize=mapReduce (chunk ->
hierarchical reduce) plus the exact <facts> block through the REAL llama.cpp JNI
provider and the bundled 135M test model, asserting the body leads with the facts
line and carries a map-reduced summary. Skipped unless -DrunNativeLlamaTests=true
and the model is present (same guard as LlamaCppJniAiGenerationProviderTest).

Verified locally: SmolLM2 loaded, 7090 chars -> 2 chunks -> reduce 2->1, body
"**Facts (exact, whole file):** lines: 400 ...", 3.7s; skips cleanly by default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
…ors/fields)

Adds three regex-based structural facts to the shipped java-facts group, from a
tested design: "methods (approx)", "constructors", and "field declarations
(w/ modifier)". Labels are deliberately honest about the regex heuristic limits:

- methods (approx): ~3% on real code, and includes constructors (subtract the
  constructors fact for the true method count).
- field declarations (w/ modifier): only counts fields with a STRONG modifier
  (public/private/protected/static/transient/volatile) - final-only and
  package-private fields are NOT counted, on purpose, so that `final` LOCAL
  variables in method bodies are not miscounted as fields (that was a +104%
  over-count in testing; excluding final-only swings it to a safe under-count).

Validated in the real java.util.regex engine (JavaStructureFactPatternsTest, 14
cases covering multi-line methods, interface methods, control structures, calls,
lambdas, multi-declarator fields, final locals) and cross-checked against real
OpenJDK source. Patterns are XML-escaped in the POM (< -> &lt;); an effective-pom
round-trip confirms they unescape to the tested patterns. For EXACT structure a
real Java parser is still the only reliable route.

mvn verify green, SpotBugs clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
…decode limit

The "files beyond the big-window preset" item is fully delivered (onOversize with
fail/sample/mapReduce/deterministic, hierarchical reduce, facts, and the real-model
end-to-end smoke test), so it is removed. Replaced with the one genuine open
limitation: ai-index:calibrate's decode rate is a wall-clock approximation
(prefill is solid). Exact-Java-facts-via-parser is intentionally NOT tracked (a
decided non-goal; the shipped regex facts are honestly labelled).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
…ok/s note

#10 follow-up. The wall-clock decode previously assumed the model emitted the full
maxOutputTokens (over-reported). AiCalibrationRunner now measures decode from a
dedicated tiny-prompt probe (its prefill KV is reused from the warmup, so its
wall-clock is ~pure decode) and divides by the ACTUAL generated-text length, not
an assumed budget.

Validated against real GPU server timings and documented the honest cap: because
the binding exposes no per-call token counts, calibrate estimates tokens via
charsPerToken, so the DISPLAYED tok/s can differ from the engine's (measured
~4590 prefill / ~164 decode vs displayed ~6588 / ~893 on granite H-1B, since the
filler is ~4.4 chars/token not 3). This does NOT hurt the plan ETA: the
charsPerToken factor cancels in the prefill term (the plan estimates a file's
tokens with the same cpt), so the estimate stays self-consistent; decode is a
minor contributor for large files. The calibrate output and TODO.md now say the
numbers are hardware-relative plan estimates, not exact engine throughput.

mvn verify green (412 tests), SpotBugs clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
The binding already returns per-call timings on the chat path; chatCompleteText just
discarded them. LlamaCppJniAiGenerationProvider.generateWithTimings now calls
chatComplete(...) and parses the OAI JSON via the binding's ChatResponseParser,
surfacing the model's own prompt_n / predicted_n / prompt_per_second /
predicted_per_second (no charsPerToken estimate, no binding change, no upstream change).

AiCalibrationRunner now runs the decode probe ONLY on the fallback path (the reported
path needs no probe). Verified end-to-end on the real model: calibrate reports 417
prefill / 12 decode tok/s and chars/token 4.41, matching the server's own
416.66 / 12.48 print_timing lines exactly (previously wall-clock-estimated ~6588 / ~893).

- New real-model test asserts generateWithTimings returns non-zero prefill/decode and a
  prompt-token count that scales with prompt size (proves the real generation path).
- Wall-clock + charsPerToken remains only as the mock/no-timings fallback.
- Removed the "hardware-relative estimate, not exact" notes (CalibrateMojo, TODO.md);
  #10 is resolved. Narrowed the override's bogus throws IOException (BED); scoped RCN
  suppression for the defensive getTimings() null-guard.

mvn verify green (413 tests, 3 native skipped), SpotBugs clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
… history)

Per TODO.md's own rule, remove the now-resolved "calibrate exact timings" entry
(#10 is done via e085e8b). Remaining items are all genuinely open (jqwik pin,
@VisibleForTesting audit, null-safety refinement, PIT scope, cross-repo
code-quality) plus the standing LogCaptor note.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
…in comments

Comment prose had em-dashes; normalized to plain ASCII '-'. Byte-level swap of
U+2014 only (spaced "word — word" -> "word - word"); no other bytes/line endings
changed. The 2 section signs (U+00A7 §) are left as-is (not hyphens). POM still
parses (help:effective-pom OK).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
… ASCII

"COMPARISON.md section 11" / "(section 11)" instead of the U+00A7 glyph. pom.xml is
now fully ASCII (no non-ASCII chars remain). POM still parses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
New release on Maven Central (main jar + cuda13-windows-x86-64 classifier both
published). Verified: mvn verify green (413 tests, SpotBugs clean, ArchUnit ok),
and the real-model native tests pass against 5.0.4 -- generation plus the
exact per-call timings path (chatComplete + ChatResponseParser) still work
(prompt-token count scales 50 -> 1479 as expected).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLccGn2dcCk7vqvBCTFWHd
@bernardladenthin
bernardladenthin merged commit bd3bfb5 into main Jul 2, 2026
8 of 10 checks passed
@bernardladenthin
bernardladenthin deleted the claude_20260630_00 branch July 2, 2026 15:42
@claude

claude Bot commented Jul 2, 2026

Copy link
Copy Markdown

Code Review Complete: This PR adds comprehensive oversized file handling with excellent architecture, testing, and documentation. The implementation correctly handles all four strategies (fail/sample/mapReduce/deterministic) with proper token-safety headroom, hierarchical reduce to prevent overflow antipatterns, and context-overflow retries. All 413 tests pass, SpotBugs is clean, PIT achieves 100% coverage on gated classes. Ready to merge.

@sonarqubecloud

sonarqubecloud Bot commented Jul 2, 2026

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant