mirror of
https://github.com/TheTom/turboquant_plus.git
synced 2026-10-22 02:53:13 -05:00
Compressed history of v0.1.4 → v0.3.2 in one alpha-ship commit so the
framework is testable end-to-end by friends. Detailed per-version
discussion lives in refract/CHANGELOG.md.
What ships
----------
Four scoring axes vs the model's own fp16-KV reference:
- Axis A (Trajectory, v0.1.4) — decode-time token IDs via patched
llama-completion, no detokenize round-trip. Replaces the buggy
v0.1.x GTM as the default.
- Axis B (KLD@D) — corpus KL divergence, bit-exact zero on Metal
when ref==cand. Native MLX implementation in addition to the
llama-perplexity subprocess path.
- Axis C (R-NIAH, v0.2.0) — long-context retrieval at multiple
(length, position) cells. Confidence guard for low base_acc.
- Axis D (PLAD, v0.2.0) — perturbation-locality drift across
typo/case/punct/paraphrase. Skipped-perturbation handling.
Composite = harmonic mean. Bands EXCELLENT/PASS/DEGRADED/FAIL.
Four CLI subcommands:
- score — run REFRACT on a candidate KV config
- selftest — 30s preflight: binaries, flags, model probe
- compare — multi-report side-by-side
- repeatability — run N times, report stdev/range health verdict
Three backends:
- llamacpp (production) subprocess wrappers around llama.cpp
- mlx (production) native via mlx-lm; verified end-to-end
on Qwen2.5-3B-Instruct-4bit
- vllm (skeleton) interface defined; plug-in pointers
in module docstring
Reporting
---------
- Text (ANSI colour, terminal)
- JSON schema refract.report.v0.3.1 with framework_version +
environment metadata (backend, llama_cpp_commit, mlx_lm_version)
- HTML (self-contained single file) with hardware info, model
params, repro command, R-NIAH heatmap, PLAD per-perturbation bars
Discoveries from the matrix runs (all logged in CHANGELOG):
- "secret password" needle wording triggers safety refusals on
instruct models. v0.2.1 switched to neutral "rare paint color"
- Thinking-mode models burn n_predict before answering. v0.3.0 added
-rea off + auto-detect at startup
- Symmetric turbo4/turbo4 is the canonical FAIL config; framework
correctly identifies (composite ~19) on gemma-4-26B-A4B-Q8
Documentation
-------------
- README.md what + alpha banner + "where do I go" navigator
- QUICKSTART.md 0 to first PASS in 30 minutes
- INTERPRETATION.md per-axis "what to do if low" guide
- LEADERBOARD.md cross-model rankings + "ranks the quant, not the
model" disclaimer
- PITFALLS.md known traps from today's matrix runs
- LIMITATIONS.md what v0.3 explicitly does not do
- CHANGELOG.md version history with discovery stories
- MATRIX-RESULTS.md the 7-model matrix log
- examples/ 4 sample JSONs + HTML reports + README
Tests
-----
80 → 82 unit tests, all green:
- 17 trajectory tests (incl. R-NIAH/PLAD impls + 4-axis composite)
- 5 strip-noise pinning tests
- 14 unit tests (math + KVConfig)
- 8 JSON schema tests (incl. v0.3.1 framework_version field)
- 6 KLD regex tests
- 5 corpus-identity tests
- 5 tokenize-to-ids tests
- 4 command-construction tests
Verified
--------
- 7-model v0.3.0 matrix end-to-end (~2h on M5 Max 128GB)
- Llama-4 Scout extras + symmetric-turbo negative control
- MLX backend on Qwen2.5-3B-Instruct-4bit
- selftest on llamacpp (no model) and mlx (with model)
- 2-run repeatability on qwen2.5-1.5b: composite stdev = 0.00 on
Trajectory + KLD (bit-exact reproducibility on cheap axes)
Open follow-ups (v0.4)
----------------------
- vLLM backend implementation (skeleton today)
- T-Call axis (tool-call fidelity) building on v0.3 chat-template
- Bundled corpus + prompts distribution
- `refract leaderboard --in dir/ --out LEADERBOARD.md` auto-gen
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
168 lines
5.7 KiB
Python
168 lines
5.7 KiB
Python
"""llama.cpp backend for REFRACT.
|
|
|
|
Thin adapter over the legacy ``refract.runner`` functions, which already
|
|
implement subprocess wrappers around the four llama.cpp binaries:
|
|
- llama-cli (run_completion)
|
|
- llama-completion (run_completion_trajectory; needs REFRACT v0.1.4 patch)
|
|
- llama-perplexity (run_perplexity_kld[_base])
|
|
- llama-tokenize (tokenize_to_ids)
|
|
|
|
Configurable via env var ``LLAMA_CPP_BIN_DIR`` (default
|
|
``~/local_llms/llama.cpp/build-test/bin``).
|
|
|
|
We delegate to runner.* rather than reimplement so the existing v0.1.x
|
|
regression tests (which monkeypatch ``runner._bin`` and
|
|
``subprocess.run``) continue to work without modification.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import subprocess
|
|
import tempfile
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
from .base import Backend, CompletionResult, KLDResult, TrajectoryResult
|
|
|
|
|
|
class LlamaCppBackend(Backend):
|
|
name = "llamacpp"
|
|
|
|
# ---------------------------------------------------------------- run_completion
|
|
def run_completion(
|
|
self,
|
|
*,
|
|
model: Path,
|
|
prompt: str,
|
|
kv_config_str: str,
|
|
n_predict: int = 128,
|
|
ctx: int = 512,
|
|
n_gpu_layers: int = 99,
|
|
seed: int = 42,
|
|
temperature: float = 0.0,
|
|
timeout: float = 300.0,
|
|
apply_chat_template: bool = True,
|
|
system: Optional[str] = None,
|
|
reasoning: str = "off",
|
|
) -> CompletionResult:
|
|
from ..runner import KVConfig, run_completion as _rc
|
|
kv = KVConfig.parse(kv_config_str)
|
|
text, meta = _rc(
|
|
model=model, prompt=prompt, kv=kv,
|
|
n_predict=n_predict, ctx=ctx, n_gpu_layers=n_gpu_layers,
|
|
seed=seed, temperature=temperature, timeout=timeout,
|
|
apply_chat_template=apply_chat_template,
|
|
system=system, reasoning=reasoning,
|
|
)
|
|
return CompletionResult(text=text, n_tokens=0, metadata=meta)
|
|
|
|
# ---------------------------------------------------------- run_completion_trajectory
|
|
def run_completion_trajectory(
|
|
self,
|
|
*,
|
|
model: Path,
|
|
prompt: str,
|
|
kv_config_str: str,
|
|
n_predict: int = 128,
|
|
ctx: int = 512,
|
|
n_gpu_layers: int = 99,
|
|
seed: int = 42,
|
|
temperature: float = 0.0,
|
|
timeout: float = 300.0,
|
|
apply_chat_template: bool = True,
|
|
system: Optional[str] = None,
|
|
) -> TrajectoryResult:
|
|
from ..runner import KVConfig, run_completion_trajectory as _rct
|
|
kv = KVConfig.parse(kv_config_str)
|
|
token_ids, meta = _rct(
|
|
model=model, prompt=prompt, kv=kv,
|
|
n_predict=n_predict, ctx=ctx, n_gpu_layers=n_gpu_layers,
|
|
seed=seed, temperature=temperature, timeout=timeout,
|
|
apply_chat_template=apply_chat_template, system=system,
|
|
)
|
|
return TrajectoryResult(token_ids=token_ids, metadata=meta)
|
|
|
|
# ---------------------------------------------------------------- run_kld
|
|
def run_kld(
|
|
self,
|
|
*,
|
|
model: Path,
|
|
corpus: Path,
|
|
ref_kv_str: str,
|
|
cand_kv_str: str,
|
|
chunks: int = 32,
|
|
ctx: int = 512,
|
|
n_gpu_layers: int = 99,
|
|
) -> KLDResult:
|
|
from ..runner import (
|
|
KVConfig, run_perplexity_kld, run_perplexity_kld_base,
|
|
corpus_identity, write_corpus_sidecar,
|
|
)
|
|
ref_kv = KVConfig.parse(ref_kv_str)
|
|
cand_kv = KVConfig.parse(cand_kv_str)
|
|
fd, base_path = tempfile.mkstemp(prefix="refract-kldbase-", suffix=".bin")
|
|
os.close(fd)
|
|
os.unlink(base_path)
|
|
base_path_p = Path(base_path)
|
|
try:
|
|
run_perplexity_kld_base(
|
|
model=model, corpus=corpus, kv=ref_kv,
|
|
base_path=base_path_p, chunks=chunks, ctx=ctx,
|
|
n_gpu_layers=n_gpu_layers,
|
|
)
|
|
write_corpus_sidecar(base_path_p, corpus)
|
|
scored = run_perplexity_kld(
|
|
model=model, corpus=corpus, kv=cand_kv,
|
|
base_path=base_path_p, chunks=chunks, ctx=ctx,
|
|
n_gpu_layers=n_gpu_layers,
|
|
)
|
|
return KLDResult(
|
|
mean_kld=scored["mean_kld"],
|
|
ppl=scored.get("ppl"),
|
|
rms_dp_pct=scored.get("rms_dp_pct"),
|
|
same_topp_pct=scored.get("same_topp_pct"),
|
|
chunks=chunks, ctx=ctx,
|
|
metadata={
|
|
"base_path": str(base_path_p),
|
|
"corpus": corpus_identity(corpus),
|
|
},
|
|
)
|
|
finally:
|
|
for p in (base_path_p,):
|
|
try:
|
|
if p.exists():
|
|
p.unlink()
|
|
except OSError:
|
|
pass
|
|
|
|
# ---------------------------------------------------------------- tokenize_to_ids
|
|
def tokenize_to_ids(
|
|
self,
|
|
*,
|
|
model: Path,
|
|
text: str,
|
|
timeout: float = 120.0,
|
|
) -> list[int]:
|
|
from ..runner import tokenize_to_ids as _tti
|
|
return _tti(model=model, text=text, timeout=timeout)
|
|
|
|
# ---------------------------------------------------------------- model_metadata
|
|
def model_metadata(self, *, model: Path) -> dict:
|
|
from ..runner import DEFAULT_BIN_DIR
|
|
commit = None
|
|
try:
|
|
commit = subprocess.run(
|
|
["git", "rev-parse", "--short", "HEAD"],
|
|
cwd=str(DEFAULT_BIN_DIR.parent.parent),
|
|
capture_output=True, text=True, timeout=5,
|
|
).stdout.strip() or None
|
|
except Exception:
|
|
pass
|
|
return {
|
|
"backend": self.name,
|
|
"model": str(model),
|
|
"llama_cpp_bin_dir": str(DEFAULT_BIN_DIR),
|
|
"llama_cpp_commit": commit,
|
|
}
|