cactus-compute / cactus-compute/cactus

Finetuned Needle checkpoint converts to a CQ4 bundle that emits token garbage (stock converts fine)

Open
#767 1 comment 0 reactions 0 assignees View on GitHub
Dominant language
C++
Stars
6k
Forks
501
Avg merge
1d 18h
Merged PRs (30d)
4

Description

Converting a **finetuned** `cactus-compute/needle` checkpoint with `cactus convert --bits 4` produces a
bundle that loads and serves but emits **repeating token garbage** for every prompt. The **stock** Needle
checkpoint converts and serves coherently through the exact same path. I've isolated the fault to the CQ
quantization core (not rounding tolerance, not the remap, not the toolchain, not the fusion passes).

The weights are healthy: they survive naive per-group int4 round-trip at ~0.90 task accuracy and run at
0.938 accuracy at full precision. Only Cactus's CQ4 conversion breaks them.

## Environment

- **`cactus 2.0.1` — the current latest** (Homebrew `cactus-compute/cactus` stable 2.0.1; GitHub latest
release `v2.0.1`, 2026-07-09; PyPI `cactus-compute` 2.0.1). macOS (Apple Silicon), `--backend cpu`.
- Base model: `cactus-compute/needle` (26M encoder-decoder tool-caller), finetuned locally via
`needle finetune` (JAX/Flax)
- Convert env: `pip install cactus-compute` (torch 2.13, transformers 5.5)

> Note: `pip install cactus` grabs an unrelated static-site-generator package (currently 3.3.x) — the
> engine is `cactus-compute` on PyPI / the Homebrew `cactus-compute/cactus` tap. Verified this repro is on
> the newest engine release (2.0.1).

## Reproduction

1. Finetune the stock Needle on any small tool-calling set (mine: a 165-example single-tool routing set,
20 epochs, `lr=3e-5` — the default `finetune_local` LR).
2. Export the finetuned JAX `.pkl` → HF `NeedleForCausalLM` safetensors (my remap validates **228/228**
tensors against the stock HF reference, transpose orientation unambiguous).
3. `cactus convert --bits 4` → clean bundle (114 converted / 114 fallback).
4. `cactus serve --port 8085 --backend cpu`

Then:

```bash
# plain prompt
curl -s localhost:8085/v1/chat/completions -d '{"model":"","messages":[{"role":"user","content":"What is the capital of France? Answer in one word."}],"max_tokens":24}'
# -> content: "virtualvirtualvirtualvirtual..." (finetuned)
# -> content: "" (stock needle-cq4, coherent null — Needle is a tool-caller)

# routing/tool prompt
# finetuned -> "extendedextendeddishdishstrenuous..."
# stock -> well-formed tool call
```

## What I ruled out (isolation)

| Hypothesis | Test | Result |
|---|---|---|
| 4-bit rounding is too lossy for these weights | naive per-group int4 (group=64) round-trip, re-eval | **survives — 0.896** route acc |
| The finetuned weights are broken | full-precision (JAX) eval | **fine — 0.938** route acc |
| Remap / HF export is wrong | validate every tensor vs stock HF ref | **228/228** within tol |
| Toolchain / engine build | convert **stock** through same venv+engine | **coherent** output |
| Fusion passes | reconvert with `--no-fuse-rms-norm --no-fuse-rope --no-fuse-attention --no-fuse-attention-block --no-fuse-add-clipped` | **same garbage** |

The decisive signal: **survives naive int4 but garbles under CQ4** ⟹ the fault is CQ-specific
(the Hessian/rotation-codebook calibration — each bundle carries `hessian_metadata.json`), and it appears
to only be robust for the stock weight distribution. I also swept earlier finetune epochs (less drift from
stock); the least-drifted checkpoint that still learned the task survives naive int4 *better* than the
fully-trained one, yet **still garbles under CQ4** — so it's not simply "how far the finetune drifted."

## Questions for maintainers

1. Is converting a **finetuned** Needle to native CQ4 supported today, or is CQ calibration effectively
pinned to the released stock checkpoint?
2. Can `convert` accept **calibration data** (or a calibration-free / higher-bit path) so finetuned
encoder-decoder models can be quantized robustly?
3. Is there a recommended path to serve a finetuned Needle natively (vs. running the JAX checkpoint
full-precision behind a shim)?

Happy to share the exact repro artifacts (finetuned HF checkpoint, the int4-proxy screen, before/after
serve transcripts).

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the `cactus convert --bits 4` and `cactus serve --backend cpu` path with the finetuned and stock Needle checkpoints. Inspect the CQ quantization core and each bundle's `hessian_metadata.json`, using the reported naive int4 comparison and fusion-disabled conversion as controls. Done means establishing whether finetuned CQ4 conversion is supported and identifying a robust conversion or documented alternative.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp, python, pytorch
Domain
backend, machine-learning, performance
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Needs clarification
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.