Skip to content

feat(eval): shared perplexity harness with llama.cpp-comparable sliding-window mode - #418

Open
jamesburton wants to merge 14 commits into
kkokosa:mainfrom
jamesburton:issue/231-perplexity-harness
Open

feat(eval): shared perplexity harness with llama.cpp-comparable sliding-window mode#418
jamesburton wants to merge 14 commits into
kkokosa:mainfrom
jamesburton:issue/231-perplexity-harness

Conversation

@jamesburton

@jamesburton jamesburton commented Jul 30, 2026

Copy link
Copy Markdown

Tracked by jamesburton#231 (fork issue tracker) — deliberately not using a Closes keyword, since #231 here is an unrelated issue in this repo. Addresses the blocker recorded in #416: "Every remaining lever changes numerics and none of them can currently be evaluated."

Summary

A shared perplexity harness with a llama.cpp-comparable sliding-window mode, so numerics-changing kernel work has an evaluation gate. Self-contained: abstractions in DotLLM.Core.Evaluation, evaluator in DotLLM.Engine, a TransformerModel adapter, and a dotllm perplexity CLI verb.

What "llama.cpp-comparable" actually means

This turned out to be the substance of the work, so it is pinned down explicitly rather than asserted.

llama.cpp walks the corpus in non-overlapping chunks of n_ctx and scores only the second half of each — the first half is context that is never scored, so the scored ranges have gaps. Generalised to (ContextLength L, Stride S, UnscoredPrefix P): window w covers [w*S, w*S + L) and scores absolute targets [w*S + P, w*S + L). PerplexityOptions.LlamaCppDefault(L) gives S = L, P = L/2.

Advance and scored span are independent, and a single "stride" cannot express this. An earlier revision collapsed them, which produced the same scored-token count over a different token set — a figure that looks comparable and is not. It read 24.88 where llama.cpp read 24.01. Positions also restart at 0 per window, because each chunk is an independent sequence; absolute positions additionally throw on any corpus longer than the model's context.

Design notes

One axis, not two implementations. The ten existing per-test perplexity helpers on our fork diverged for exactly one reason: whether a backend's Forward returns all rows or only the last. CPU returns [seqLen, vocab], so one pass scores every target (O(n)); CUDA returns the final row only, so each target needs a growing-prefix re-prefill (O(n²)). Modelling that as IPerplexityModel.ReturnsAllRows lets one evaluator serve both, and a test asserts the two paths produce an identical number on the same tokens.

The evaluator never loads weights, and the corpus is streamed and tokenized in chunks. On unified-memory parts a large VRAM carve-out leaves host RAM scarce, and perplexity — a long run of full-context prefills rather than a single load — is the workload most punished by holding a second host-side copy.

TeacherForced is preserved verbatim as the existing in-tree methodology. It is ratio-oriented (the signal is an OFF/ON ratio under a <1% gate), not comparable to published figures, and is documented as such.

Verification

Geometry is exact against llama-perplexity build 8683 (d0a6dfeb2) in every configuration tested — window and scored-token counts match: 4/512, 15/1920, 18/2304.

Absolute figures, SmolLM-135M Q8_0 on wikitext-2 at -c 256, with byte-exact tokenization (requires the fix in #417):

slice this harness llama.cpp Δ
4 chunks 25.0548 25.8113 −2.93%
15 chunks 23.7864 24.0137 −0.95%
18 chunks 25.0649 25.9366 −3.36%

Unit tests: 18 covering both modes, both backend shapes, window tiling, per-window position reset, row/target mapping, and a test asserting the llama.cpp-default and contiguous-tiling schemes score different token sets — they produce the same count, so a count check cannot distinguish them.

Known limitation, stated plainly

The residual above is not closed. It is consistently negative across slices (this harness scores slightly better than llama.cpp), which is the signature of a systematic forward-pass difference rather than a harness defect: geometry is exact, and the token streams are now byte-identical.

Ruled out by measurement, not argument: window geometry; token-stream content (0 mismatches over 3904 tokens after #417); the --max-tokens cap; and per-chunk BOS substitution. The remaining untested hypothesis is batching — llama.cpp evaluates chunks with n_seq=8, batch_size=2048, while this harness runs one window per forward.

So: trustworthy as a relative gate today (identical geometry, identical tokens, deterministic), and not yet something to quote as an absolute figure against published numbers. I would rather ship that distinction explicitly than imply a comparability I have not demonstrated.

Diagnostics included

--per-window, --dump-tokens and --tokens-file separate tokenization from scoring from geometry. They are not decoration — feeding llama.cpp's exact ids back through the scorer is what localized the original discrepancy, and diffing token streams is what found #417.

Follow-up

The ten duplicated per-test helpers live only on our fork's dev, so migrating them onto this harness — gated on producing numerically identical results — is a separate change and not part of this PR.

jamesburton and others added 12 commits July 30, 2026 17:32
Adds the DotLLM.Core.Evaluation abstractions and the design document for a
shared perplexity harness, addressing the blocker recorded in upstream #416:
every remaining numerics-changing CPU lever is ungated because no harness exists.

The contract models the one axis the existing per-test helpers actually diverged
on -- IPerplexityModel.ReturnsAllRows -- so a single evaluator can replace both
the O(n) all-rows CPU shape and the O(n^2) growing-prefix last-row-only shape
without changing any number either currently produces.

The evaluator deliberately never loads weights and takes an already-constructed
model, and the corpus is streamed rather than materialized. Both are constraints
from the planned iGPU follow-on: on unified-memory parts a large VRAM carve-out
leaves host RAM scarce, and paying for the model twice is the failure mode.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Nine TDD tasks. Pins down what "llama.cpp-comparable" actually means before
any code: window w covers [w*S, w*S+L) and scores absolute targets in
[w*S+L-S, w*S+L), so scored ranges tile the corpus exactly and every scored
token carries L-S tokens of context. S = L/2 reproduces llama.cpp's default.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…indows (#231)

Records every window passed to Forward so evaluator tests can assert on window
tiling and on the O(n) vs O(n^2) strategy split, not just on the resulting
perplexity number.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ends (#231)

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ends (#231)

Produces a numerically identical result to the all-rows path on the same
tokens, which is what makes migrating the existing per-test helpers onto this
harness a meaningful gate rather than an aspiration.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Window w covers [w*S, w*S+L) and scores absolute targets in [w*S+L-S, w*S+L),
so scored ranges tile the corpus exactly and every scored token carries L-S
tokens of context. S = L/2 reproduces llama.cpp's default of scoring the second
half of each chunk.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reports window geometry and scored-token count alongside the figure: a
perplexity without them is not comparable to anything, which is the failure
this harness exists to prevent.

Verified end-to-end on SmolLM-135M Q8_0 over wikitext-2 (ctx 256, stride 128,
2048 tokens): ppl 24.8800, 15 windows, 1920 scored -- matching the expected
tiling arithmetic exactly.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…sitions (#231)

Validating against llama.cpp build 8683 refuted two assumptions baked into the
original design.

1. Advance and scored span are INDEPENDENT. llama.cpp advances by the full
   window yet scores only its second half, so its scored ranges have gaps. The
   single-stride model produced the same scored-token COUNT over a different
   token SET -- 24.88 vs llama.cpp's 24.01, a figure that looked comparable and
   was not. PerplexityOptions now carries UnscoredPrefix, and
   LlamaCppDefault() names the faithful configuration.

2. Positions restart at 0 per window. llama.cpp evaluates each chunk as an
   independent sequence; absolute positions ran past the model's max sequence
   length and threw on any corpus longer than it.

Also fixes CorpusReader dropping the separating space when cutting a chunk:
GPT-2-style BPE encodes a leading space into the following token, so dropping
it silently changes the token stream versus a single-pass tokenization.

Geometry now matches llama.cpp exactly (15 windows, 1920 scored). A +3.46%
residual remains and is NOT yet explained -- see the next commit's notes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds PerplexityEvaluator.WindowObserver and the CLI's --per-window,
--dump-tokens and --tokens-file. These exist because an aggregate perplexity
cannot localize a disagreement: differencing per-window figures against another
implementation, and feeding that implementation's exact token ids back through
our scorer, separates tokenization from scoring from geometry.

That separation immediately paid for itself -- see #237: diffing token streams
against llama.cpp on wikitext-2 found 30 mismatches in 3904 tokens, all the
same repeated pair, which is a real pre-tokenizer bug rather than anything
about perplexity.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 30, 2026 20:51

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a shared perplexity evaluation harness to dotLLM, including a llama.cpp-comparable sliding-window mode, so numerics-changing kernel work can be gated by a repeatable evaluator rather than ad-hoc per-test helpers.

Changes:

  • Introduces DotLLM.Core.Evaluation contracts (IPerplexityModel, PerplexityOptions/Result, modes) and an engine-side PerplexityEvaluator with both teacher-forced and sliding-window scoring.
  • Adds streaming corpus tokenization (CorpusReader) and a TransformerModel adapter (TransformerPerplexityModel) so evaluation can run without re-loading weights.
  • Exposes the harness via a new dotllm perplexity CLI verb and adds unit tests covering evaluator geometry and stable log-prob scoring.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/DotLLM.Tests.Unit/Evaluation/PerplexityEvaluatorTests.cs Unit coverage for teacher-forced vs sliding-window behavior, window tiling, and row/target mapping.
tests/DotLLM.Tests.Unit/Evaluation/LogProbTests.cs Verifies numerical stability and invariants of log-softmax target log-probability.
tests/DotLLM.Tests.Unit/Evaluation/FakePerplexityModel.cs Deterministic IPerplexityModel test double recording forward calls/windows.
tests/DotLLM.Tests.Unit/Evaluation/CorpusReaderTests.cs Validates streamed tokenization ordering, max-tokens cap, and chunk-boundary handling.
src/DotLLM.Models/Evaluation/TransformerPerplexityModel.cs Adapter from TransformerModel to IPerplexityModel for evaluation.
src/DotLLM.Engine/Evaluation/PerplexityEvaluator.cs Core evaluator implementing teacher-forced and sliding-window perplexity computation.
src/DotLLM.Engine/Evaluation/LogProb.cs Numerically stable log-softmax (log P(target)) helper used by the evaluator.
src/DotLLM.Engine/Evaluation/CorpusReader.cs Streaming corpus reader that tokenizes in chunks without loading whole corpus text.
src/DotLLM.Core/Evaluation/PerplexityResult.cs Defines modes/options/result data structures and llama.cpp-default preset.
src/DotLLM.Core/Evaluation/IPerplexityModel.cs Defines the minimal model surface required for perplexity scoring.
src/DotLLM.Cli/Program.cs Registers the new perplexity CLI command.
src/DotLLM.Cli/Commands/PerplexityCommand.cs Implements dotllm perplexity: load → stream/tokenize or read ids → score → report.
docs/superpowers/specs/2026-07-30-perplexity-harness-design.md Design spec documenting geometry/constraints and rationale.
docs/superpowers/plans/2026-07-30-perplexity-harness.md Detailed implementation plan and acceptance/verification steps.
Comments suppressed due to low confidence (1)

src/DotLLM.Cli/Commands/PerplexityCommand.cs:95

  • The command currently requires --corpus even when --tokens-file is provided, which makes the documented "read pre-tokenized ids instead of tokenizing --corpus" path unusable. Validation should require either --corpus or --tokens-file, and check existence of the chosen input.
        if (string.IsNullOrWhiteSpace(settings.Corpus))
        {
            AnsiConsole.MarkupLine("[red]--corpus is required.[/]");
            return 1;
        }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +46 to +47
int cut = pending.LastIndexOf(' ');
if (cut < 0) continue; // no safe split point yet; keep accumulating
Comment on lines +20 to +23
/// Defaults to <see cref="PerplexityMode.SlidingWindow"/> with <c>stride = context / 2</c>, which
/// reproduces llama.cpp's <c>--perplexity</c> methodology, so the reported figure is directly
/// comparable to published numbers for the same model, corpus, context and stride.
/// </remarks>
Comment on lines +48 to +51
/// <param name="positions">
/// Absolute position ids, one per token. Passed explicitly rather than derived, so a sliding
/// window can score at its true absolute positions instead of restarting at zero.
/// </param>
Comment on lines +9 to +14
/// <summary>
/// Teacher-forced scoring over one window per stride step, each scored from a single forward
/// pass where the backend permits it. The established in-tree methodology (the "G1 precedent"
/// referenced by the CUDA prefill harnesses); preserved so existing quality gates keep their
/// meaning after consolidation.
/// </summary>
… bar (#231)

Validating the harness against llama.cpp on wikitext-2 turned up three defects,
all in the part that claims comparability with published figures.

1. LlamaCppDefault scored one target too many. llama.cpp sets first = n_ctx/2 and
   then accumulates `count += n_ctx - first - 1`, scoring targets (n_ctx/2, n_ctx)
   — the token AT n_ctx/2 is context, never a target. We scored it too, giving
   n_ctx/2 targets per chunk against llama.cpp's n_ctx/2 - 1: the same name over a
   different token set, which is exactly the failure mode the mode exists to avoid.

2. No error bar. llama.cpp prints "PPL = 18.8440 +/- 0.14515" and the +/- is
   load-bearing: on a 2,286-token corpus this model's figure carries ~6.5%, so a
   3% "discrepancy" against another implementation is noise. Without it the harness
   invites chasing phantoms — it cost a full investigation to learn that. Computed
   by Welford rather than E[x^2]-E[x]^2, whose cancellation returns ~1e-6 for a
   constant NLL where the true variance is 0.

3. --tokens-file still demanded --corpus, so the one flag that separates a
   tokenizer difference from a scoring one could not be used. It now replaces
   --corpus, and accepts the JSON-array form reference tools print.

Validated on wikitext-2 test (SmolLM-135M Q8_0, n_ctx=512, 589 chunks,
150,195 scored tokens both sides):

  llama.cpp b8683      18.8440 +/- 0.14515
  ours, llama's ids    18.8911   (+0.25%, 0.32 sigma)
  ours, end-to-end     18.8950   (+0.27%, 0.35 sigma)

Tokenizer agreement is exact over the corpus: 0 mismatches in 301,948 ids
(requires the #237 pre-tokenizer fix; without it, 44 mismatches).
@jamesburton

Copy link
Copy Markdown
Author

Validated against llama.cpp on wikitext-2 — and three defects fixed in the process

The harness now reproduces llama.cpp to well within its own error bar. Getting there
turned up three real defects, all in the part that claims comparability.

Result

SmolLM-135M Q8_0, wikitext-2 test, n_ctx=512, 589 chunks, 150,195 scored tokens on both sides:

perplexity vs llama.cpp
llama.cpp b8683 (d0a6dfeb2) 18.8440 ± 0.14515
ours, fed llama.cpp's exact token ids 18.8911 ± 0.14543 +0.25% (0.23σ)
ours, end-to-end (own tokenizer) 18.8950 +0.27%

The error bars agree to 0.2% — an independent check of the new statistic against
llama.cpp's own on identical data. Tokenizer agreement over the corpus is exact:
0 mismatches in 301,948 ids.

What was wrong

1. LlamaCppDefault scored one target too many. llama.cpp sets first = n_ctx/2,
then accumulates count += n_ctx - first - 1 (perplexity.cpp:629),
scoring targets (n_ctx/2, n_ctx) — the token at n_ctx/2 is context, never a target.
We scored it as well, giving n_ctx/2 targets per chunk against llama.cpp's n_ctx/2 - 1:
the same name over a different token set, which is precisely what this mode exists to prevent.

2. No error bar. This one matters more than it looks. Before the fix I measured a
consistent ~3% gap on a 20k-char corpus and spent a long time hunting a forward-pass bug.
There was none — on that corpus llama.cpp's own figure is 25.9366 ± 1.67994, i.e. ±6.5%,
so a 3% difference was never evidence of anything. A harness that reports a bare number
invites exactly that mistake. It now prints PPL +/- err in llama.cpp's format.

Computed by Welford rather than the textbook E[x²] - E[x]²: the per-token NLL of a
converged model clusters tightly about a few nats, so those two terms agree to most of
their significant digits and their difference is largely rounding. On a constant NLL the
closed form returns ~1e-6 where the true variance is 0. Welford is exact there and agrees
with llama.cpp everywhere else.

3. --tokens-file still demanded --corpus, so the one flag that separates a tokenizer
difference from a scoring difference could not be used at all. It now replaces --corpus
and accepts the JSON-array form reference tools print. This flag did the load-bearing work
above: feeding llama.cpp's exact ids is what proved the +0.25% is not tokenization.

What the harness does and does not guarantee

  • Relative comparisons are sound. Identical geometry, identical tokens, deterministic
    (three identical runs → bit-identical per-window output). This is what gates a kernel change.
  • Absolute figures are comparable to published llama.cpp numbers in sliding-window mode,
    to within the error bar, provided model, corpus, context and unscored prefix all match.
  • The residual +0.25% is sub-σ and not separately attributable; it is consistent with ordinary
    numerical differences (accumulation order, SIMD kernels, activation-quantization detail).

Note on ordering

End-to-end agreement depends on #417 — without that pre-tokenizer fix the same corpus
gives 44 token mismatches against llama.cpp. The scoring path itself is independent of it,
as the --tokens-file row shows.

Reproducing

llama-perplexity -m SmolLM-135M.Q8_0.gguf -f wiki.test.raw -c 512 -ngl 0
dotllm perplexity SmolLM-135M.Q8_0.gguf -f wiki.test.raw -c 512

…space-run starts (#231)

From review feedback. Five of the six points were documentation that had drifted from
the implementation — bad anywhere, worse here, since this harness's entire value is that
its stated geometry is exactly what it measures.

- IPerplexityModel.Forward described `positions` as absolute and invited implementers to
  rely on that. Sliding-window has passed window-relative positions restarting at 0 since
  e303be4 (llama.cpp chunk semantics, and what lets a corpus longer than the model's max
  sequence length be scored at all). Contract now says so.
- PerplexityMode.TeacherForced claimed "one window per stride step"; it scores one clamped
  window and ignores Stride/UnscoredPrefix. Documented as deliberate — the mode reproduces
  the pre-existing harnesses bit for bit, and giving it geometry would move the numbers
  their gates were calibrated against.
- The CLI's remarks said stride = context/2. The default is stride = context with unscored
  prefix = context/2 + 1. Also corrected --unscored-prefix's own help text, which still
  said context/2 after ca8a92b changed it.

The sixth was behavioural and is fixed: chunks were cut at the last whitespace *character*,
which can land inside a run of them. A GPT-2-style pre-tokenizer treats a whitespace run as
one unit, so splitting one yields a different stream than tokenizing the file in one pass.
Cuts now land at the run's START, so the whole run moves into the carry.

Verified against whole-file tokenization on wikitext-2 (1.29 MB, 301,948 tokens) at chunk
sizes 997 / 4096 / 8192 / 65536 — identical streams every time. The smallest exercises
~1,290 boundaries; the previously-reported validation crossed only ~20, which was thin
cover for a boundary bug.

Tests: whitespace runs of spaces, mixed space/tab/newline, newlines-only and CRLF all
assert no segment ends in whitespace. The whitespace-free case is asserted too, since it
has no safe cut and is therefore read whole — a stated trade (correctness over bounded
memory) rather than an accident.
@jamesburton

Copy link
Copy Markdown
Author

All six addressed in c00068a — five were documentation that had drifted from the implementation,
and one was behavioural.

CorpusReader whitespace (behavioural, fixed). Good catch, and it was worse than the docstring
mismatch it looked like. Cutting at the last whitespace character can land inside a run of them,
and a GPT-2-style pre-tokenizer treats a whitespace run as one unit — so splitting one yields a
different stream than tokenizing the file in one pass. Cuts now land at the run's start, so the
whole run moves into the carry.

I checked whether it actually bit before changing anything. It did not, but my prior evidence was
thinner than I had realised: the wikitext-2 validation I reported crosses only ~20 chunk boundaries
(1.29 MB at 64 KiB). Re-run against whole-file tokenization at 997 / 4096 / 8192 / 65536 — the
smallest exercising ~1,290 boundaries — all four produce identical streams, before and after.
So the fix is a robustness one, now covered by tests over runs of spaces, mixed space/tab/newline,
newlines-only and CRLF.

The unbounded-carry half is real and I have documented it rather than papered over it: with no
whitespace there is no safe cut, so the corpus is read whole. Flushing at an arbitrary character
would bound the memory but change the token stream, which is the one thing this harness must never
do silently. Asserted in a test so it is a stated trade.

IPerplexityModel.positions (fixed). You are right and this was actively misleading — the doc
invited implementers to rely on absolute positions, and sliding-window has passed window-relative
ones restarting at 0 since e303be4. Beyond matching llama.cpp's chunk semantics, that is what lets
a corpus longer than MaxContextLength be scored at all; absolute positions overflow the model's
limit on window two. Contract now says so explicitly.

PerplexityMode.TeacherForced (fixed, docs). Correct — it ignores Stride and
UnscoredPrefix. I documented rather than implemented, deliberately: that mode exists to reproduce
the pre-existing CUDA prefill harnesses bit for bit, and giving it window geometry would move the
numbers their quality gates were calibrated against. Now stated as a deliberate constraint with the
reason, and it points at SlidingWindow for walking a corpus.

CLI defaults (fixed, both sites). The remarks said stride = context/2; the default is
stride = context with unscored prefix = context/2 + 1. The --unscored-prefix help text was
also stale — it still said context/2 after ca8a92b corrected the off-by-one against llama.cpp's
count += n_ctx - first - 1. Both now match the code.

Evaluation tests 26/26.

@jamesburton
jamesburton deleted the issue/231-perplexity-harness branch July 31, 2026 10:15
jamesburton added a commit to jamesburton/dotLLM that referenced this pull request Jul 31, 2026
…418)

Ports upstream PR kkokosa#418 onto dev so the dev-only work (BitNet/I2_S,
PQ2_0, Vulkan) has an evaluation gate to build on.

Contract in DotLLM.Core.Evaluation, evaluator in DotLLM.Engine, TransformerModel
adapter, and a `dotllm perplexity` CLI verb. Two modes: TeacherForced (the
existing in-tree "G1 precedent" methodology, ratio-oriented, preserved verbatim)
and SlidingWindow (llama.cpp-comparable, absolute-value oriented).

The evaluator selects its strategy from IPerplexityModel.ReturnsAllRows -- the
single axis the ten duplicated per-test helpers on this branch actually diverged
on -- so one implementation serves both the O(n) all-rows CPU shape and the
O(n^2) growing-prefix last-row-only CUDA shape. A test asserts the two produce an
identical number on the same tokens, which is what makes migrating those helpers
a meaningful gate rather than an aspiration.

Advance and scored span are independent: llama.cpp advances by the full window
yet scores only its second half, so PerplexityOptions carries UnscoredPrefix and
LlamaCppDefault() names the faithful configuration. Positions restart at 0 per
window, since each chunk is an independent sequence.

The evaluator never loads weights and the corpus is streamed -- deliberate, for
the planned iGPU follow-up: on UMA a large VRAM carve-out leaves host RAM scarce
and perplexity is a long run of full-context prefills, so a second host-side copy
of the weights is the failure mode to avoid.

Geometry verified exact against llama-perplexity build 8683 (15 windows / 1920
scored tokens on SmolLM-135M Q8_0 over wikitext-2 at -c 256). A consistently
negative ~1-3.4% residual against llama.cpp's absolute figure remains open and is
documented on the PR; geometry and token streams are both exact, so it is a
forward-pass difference, not a harness defect.

Evaluation tests: 18 passed, 0 failed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
jamesburton referenced this pull request in jamesburton/dotLLM Jul 31, 2026
Reconciles a genuine divergence: while this session merged issue/231's
perplexity harness directly from its fork branch, a concurrent session on
Strix independently ported the SAME feature back from upstream (kkokosa/
dotLLM#418, meaning it was also contributed upstream), plus a separate new
feature never on origin/dev before now -- issue #237's pre-tokenizer
pipeline (BpeTokenizer.cs/Gpt2TiktokenEncoding.cs/TiktokenPreTokenizer.cs,
StarCoder/SmolLM family support).

Reconciled via git bundle (no push access from Strix to transfer the
diverged history directly -- HTTPS credential store isn't available
non-interactively over the SSH+PowerShell channel this session uses).
Only one real conflict: Program.cs's perplexity command example (kept the
--stride variant -- verified PerplexityCommand.Settings actually has a
Stride option, so the simpler upstream-ported example was the stale one).
Everything else merged clean -- the #231 content itself was byte-identical
between both paths (confirms both ports landed the same end state).

Full DotLLM.Tokenizers/Cli build clean; pre-tokenizer test suite (95
tests) passes with zero failures.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@jamesburton
jamesburton restored the issue/231-perplexity-harness branch July 31, 2026 13:33
@jamesburton jamesburton reopened this Jul 31, 2026
@jamesburton

Copy link
Copy Markdown
Author

Apologies for the close/reopen noise — that was accidental on our side, not a withdrawal.

The branch was deleted while tidying up after porting this work into our fork's integration branch,
and GitHub closes a PR when its head branch goes. Restored to the same commit (c00068a7); nothing
was rewritten and the diff is unchanged.

Still open for review, and no action needed from you.

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.

2 participants