Commit Graph

6 Commits

Author SHA1 Message Date
woolcoxm e141db047d converter: per-tensor-type mixed-precision control
Split the resident weight classification into 5 sub-types so each can
get different precision:
  sh   = shared expert (highest sensitivity, fires every token)
  o    = o_proj (reconstructs output, biggest attn tensor)
  kvb  = kv_b_proj (reconstructs KV cache on every decode)
  attn = q_a/q_b/kv_a (other attention projections)
  dmlp = dense MLP (first 3 layers)

New args: --shared-bits, --o-bits, --kvb-bits, --attn-bits, --dmlp-bits
Each defaults to ebits (backward compat). When set, the converter applies
that precision to just that tensor type.

Research-backed plan: put the 3 compounding tensors (shared expert, o_proj,
kv_b_proj) at int8 and everything else at grouped int4. Extra RAM cost:
only +5.3 GB (those tensors are small vs the 372 GB expert pool on disk).
2026-07-15 02:32:12 -04:00
woolcoxm 498ab0c20e experiment: group-scaled int4 (fmt=4) — one scale per 128 elements, not per row
Root cause of gibberish output: the int4 quantization uses one F32 scale per
output row (2048 scales for a 2048x6144 matrix). The FP8 source has 128x128
block scales — 48x finer. This destroys reasoning while keeping surface fluency.

Changes:
- Converter: quant_int4_grouped() with --group-size 128 arg. Same nibble
  packing, but one scale per group of 128 elements along the input dim.
- Engine QT struct: added 'gs' field (group size, 0=per-row backward compat)
- Engine qt_from_disk: auto-detects fmt=4 when scale array is O*ceil(I/128)
  elements instead of O. Old per-row models (fmt=2) work unchanged.
- Engine matmul_i4_grouped(): AVX2 kernel that applies per-group scales.
  Accumulator resets at each group boundary: dot(x[grp],w[grp]) * scale[grp].
- Engine matmul_qt_ex: dispatches to grouped kernel for fmt=4 (always exact,
  no IDOT approximation since the point is quality)
- Engine expert_load: both mmap and slab+pread paths detect fmt=4 from
  scale array size and set gs=128
- qt_bytes: fmt=4 reports correct memory including group scales

Backward compatible: existing per-row int4 models work unchanged.
The fused gate+up pair path (matmul_i4_pair) falls back to separate
matmul_qt calls for fmt=4 — minor perf cost, correctness preserved.
2026-07-15 02:05:18 -04:00
Rodolfo Hansen 8395435322 convert: NVFP4 (modelopt e2m1) dequant path for convert_fp8_to_int4.py (#83)
convert_fp8_to_int4.py could ingest FP8-blockscale, bf16 and f32
checkpoints, but not NVFP4 — the format NVIDIA modelopt emits for
REAP-pruned GLM-5.2 (quant_algo=NVFP4). Those expert weights are U8
(two e2m1 nibbles/byte) with a per-16-block FP8 scale sidecar
(.weight_scale) plus a per-tensor F32 global (.weight_scale_2).

Adds dequant_nvfp4(): e2m1 LUT (verified 1:1 against
ml_dtypes.float4_e2m1fn), low-nibble=even / high-nibble=odd unpacking,
per-block scale repeat-interleaved over group_size=16, times the global.
classify() now consumes the .weight_scale/.weight_scale_2/.input_scale
sidecars, and dequant() routes U8 tensors with a scale sidecar to the
NVFP4 path (keys is now required, so a stray U8 can't fall through to it).
Existing FP8/bf16/f32 paths are untouched.

Guards against silent corruption rather than trusting the input:
  * group_size is fixed at 16 and the block-scale column count is
    asserted == ceil(I/16); the old code inferred it from the data
    (I // ncols), which misaligns silently on a padded/swizzled scale
    layout and hard-crashes on a non-multiple-of-16 I — after a multi-GB
    shard download. A partial trailing block is handled by slicing to I.
  * .weight_scale_2 is asserted < 1: modelopt stores the small global and
    MULTIPLIES; llm-compressor/compressed-tensors stores the reciprocal
    (>= 1) and DIVIDES. The two are dtype-identical, so without this a
    compressed-tensors checkpoint would corrupt every tensor by ~gscale^2.

--selftest-nvfp4 (no network) asserts the 16-code LUT, an encode->dequant
round-trip to <1e-9, and a dequant->colibri-int4 requant bound (<0.30;
the inherent ~0.17 is informational, not a precision claim).


Claude-Session: https://claude.ai/code/session_01DS7oc65c5RdA9V99otRCwt

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 14:12:49 +02:00
woolcoxm 789169f8f9 convert: fix converter crashes on Windows (fcntl import, null config) (#185)
Three crash bugs in convert_fp8_to_int4.py, all on the download path:

P3: 'import fcntl' at line 211 is Unix-only — ModuleNotFoundError on Windows.
    The --indir test path returns before reaching it so tests pass, but
    '--repo' on Windows hard-crashes. Guarded the import: try fcntl (Unix),
    fall back to msvcrt.locking (Windows), skip if neither available.

P4: repo_info retry loop had range(999) — up to ~16 hours of retries on a
    bad network, then fell through to line 395 where 'info' was unbound
    (NameError). Capped at 10 retries and added an explicit error + return
    when exhausted. Also added an early return if no safetensors shards are
    found in the repo.

P5: if the shards list was empty (wrong repo, all filtered out), the
    'for i, sh in enumerate(shards)' loop never executed and 'i' was unbound
    at line 460 (NameError). Now caught by the early return from P4.

Co-authored-by: woolcoxm <13604288+woolcoxm@users.noreply.github.com>
2026-07-14 14:11:47 +02:00
Sidd 2416bc9079 Translate user-facing runtime output to English, machine prefixes preserved, + CLI output test (#67, #85)
* feat: standardize runtime output in English

* test: cover English CLI output
2026-07-12 13:38:40 +02:00
ZacharyZcR 13e8f09ffc Organize project tools and local workflows: c/tools, c/scripts, c/tests, root Makefile (flat C core untouched) (#22) 2026-07-10 10:08:39 +02:00