Repository navigation
fix(quant): calibrate int8 and AWQ low-memory runs on every sample, not one per batch - #388
Merged
yghstill merged 3 commits intoOct 31, 2026
Merged
Conversation
…ot one per batch
The calibration-setup step exists three times and only the fp8 copy is right:
fp8/fp8.py:78 nsamples = len(dataloader) * dataloader.batch_size
int8/int8.py:77 nsamples = len(dataloader) <- drops all but the last batch
awq/awq.py:125 nsamples = len(dataloader) <- same
Catcher stores one entry per sample (see the docstring on _capture_samples:
"Splits a batched input into per-sample tensors"), so len(captured_inputs) is
len(dataloader) * batch_size. But int8/awq size self.inps with len(dataloader)
and copy with min(len(captured_inputs), self.inps.shape[0]), so every sample
past the first batch is discarded before an observer sees it: with 96 samples
at batch_size 8 the int8 and AWQ activation scales are calibrated on 12 of them,
while print_info("nsamples:12") reports the truncated number as if it were the
requested one. The fp8 copy has measured the full set since Tencent#29.
Sizing the buffers by the real capture count makes the three modules agree and
lets the observer see every sample. At batch_size 1 -- every low_memory config
shipped in configs/ -- the two expressions are equal, so this is a no-op for the
existing recipes and only changes runs that ask for batched calibration.
INT8.low_memory_run and AWQ.run size self.inps from nsamples and then copy the inputs Catcher captured into it, capped by min(len(captured_inputs), self.inps.shape[0]). Sizing the buffer from len(dataloader) - one row per batch - therefore keeps only that many samples and silently drops the rest, so the low-memory calibration saw a fraction of the corpus while reporting success. Drives both real methods with a stubbed model: 3 batches of 8 means the buffer must be 24 rows with every captured sample copied into it, not 3. Only the modules under test are loaded, with their imports stubbed, following tests/test_dataloader.py.
yghstill
previously approved these changes
Oct 14, 2026
Collaborator
|
@MincongZhou Please refacing code format: pre-commit install
pre-commit run --all-files |
Contributor
Author
|
Applied the repo's own pre-commit config: The diff is whitespace-only in Heads-up: the two workflows on the new head ( |
yghstill
approved these changes
Oct 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The calibration-setup step that sizes the captured-activation buffer exists three times in this package, and only the fp8 copy got it right:
angelslim/compressor/quant/modules/fp8/fp8.py:78len(dataloader) * dataloader.batch_sizeangelslim/compressor/quant/modules/int8/int8.py:77len(dataloader)angelslim/compressor/quant/modules/awq/awq.py:125len(dataloader)Catcherstores one entry per sample — its own docstring on_capture_samplessays "Splits a batched input into per-sample tensors", and the loop isfor i in range(inp.shape[0]): ... captured_inputs.append(sample). Solen(captured_inputs)islen(dataloader) * batch_size.int8/awqthen allocateself.inpswith the smallerlen(dataloader)and copy withwhich silently discards every sample past the first batch, before any observer sees it. Two consequences:
1/batch_sizeof the requested data.print_info(f"nsamples:{nsamples}")reports the truncated number, so the run log states the wrong calibration-set size — the only signal the user gets is a number that looks intentional.Reproduction
Executed against the real
Catcherfrom this repo (96-sample dataset,batch_size=8, solen(dataloader)=12):The same input yields 12 calibrated samples on int8/AWQ and 96 on fp8 today.
Provenance
git logon the two files says this is a migration leftover, not an intentional difference:int8/int8.pywas last touched by fix(quant): migrate int8/fp8/awq/lepto_fp8 to new Catcher API (regression from #318) #352 "migrate int8/fp8/awq/lepto_fp8 to new Catcher API" — the commit that moved these modules from a batched capture toCatcher's per-sample capture (regression from fix(gptq): fix Hessian computation, variable-length sequence support, and layer output type handling #318), and that kept the pre-migrationlen(dataloader).fp8/fp8.pyreceived the* dataloader.batch_sizeform earlier, in add DeekSeek and Kimi-K2; support per-tensor and w4a8; support batch calibration in fp8 low_memory_run; support multiple nodes #29 "support batch calibration in fp8 low_memory_run".So the fix is to make int8 and AWQ agree with fp8.
Scope
nsamplesis used only as an allocation length and as amin()bound, so this cannot index out of range. Atbatch_size = 1the two expressions are equal — and everylow_memory: trueconfig shipped underconfigs/usesbatch_size: 1— so this is a no-op for the existing recipes and only changes runs that ask for batched calibration, which is exactly the case that is broken today.dataloader.batch_sizeis already dereferenced unconditionally by the fp8 copy.Validation
python -m py_compileon both files: OK..pre-commit-config.yaml:black --line-length 99 --check→2 files would be left unchangedisort --profile=black --check-only→ cleanflake8 --ignore=E203,E704,W503,W504 --max-line-length 99→ exit 0Catcherclass imported from this repository.Not run: an end-to-end PTQ to compare the resulting quantized accuracy (needs model weights, a real calibration dataset and a GPU). The claim here is limited to what the code does: the buffer is sized for one batch instead of the whole capture set, and the surplus is dropped before the observer loop.
Competition
No issue or PR mentions this.
gh api search/issuesforint8 low_memory→ 0 results; fornsamples→ 2 (the fp8 fix #29 and an unrelated #318). The 4 currently open PRs (#351, #346, #319, #305) touch other areas. The one open issue in this neighbourhood (#48) is about FP8 low-memory and W4A8 and does not mention sample counting.