feat(quantization): scoped calibration pipelines via algo_cfg [prototype] - #2292
Draft
Fridah-nv wants to merge 2 commits into
Draft
feat(quantization): scoped calibration pipelines via algo_cfg [prototype]#2292Fridah-nv wants to merge 2 commits into
algo_cfg [prototype]#2292Fridah-nv wants to merge 2 commits into
Conversation
Prototype of the flexible-calibration design: assign an ordered calibration
pipeline per scope instead of one model-wide `algorithm`.
config = {
"quant_cfg": [...],
"algo_cfg": [
{"module_name": "*self_attn*", "cfg": ["awq_lite", "mse"]},
{"module_name": "*mlp*", "cfg": ["max", {"method": "gptq"}]},
{"quantizer_name": "*input_quantizer", "cfg": ["max"]},
],
"algorithm": "max", # fallback for anything no entry matches
}
`compile_algo_cfg` lowers the config into ordered scoped stages, reading the
model's structure to resolve globs and validate but mutating nothing. The new
`calibration_plan` mode executes those stages through the existing calibration
functions, gated by a `should_process` write-mask, and records one mode.
`algorithm` lowers through the same path as its all-`"*"` case, so there is no
second engine; with no `algo_cfg` the old path and its saved state are
untouched.
Two upstream bugs found while making pipelines actually sequence, both
reproducible on today's un-scoped `algorithm=[...]` list:
- `_mse_calibrate_weights` never restored the search calibrator it installs, so
any stage after `mse` crashed (`algorithm=['max','mse','max']` -> TypeError).
Now restored in a `finally`.
- `awq_lite` called `enable_stats_collection(model)` directly, so the write-mask
had to reach helper calls inside an algorithm, not just its module loop.
Validation rejects a config before anything runs: unknown algorithm, empty
scope, role mismatch, fusible siblings split across pipelines, a stage whose
every write is overwritten before being read, and repeating an algorithm whose
own output violates its precondition. The last two are what make
`awq_lite -> mse -> awq_lite` wrong.
Signed-off-by: Fridah-nv <201670829+Fridah-nv@users.noreply.github.com>
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
Contributor
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #2292 +/- ##
==========================================
+ Coverage 79.05% 79.14% +0.08%
==========================================
Files 525 526 +1
Lines 61106 61457 +351
==========================================
+ Hits 48308 48640 +332
- Misses 12798 12817 +19
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…lare it `derive_handoff` reports what state earlier stages already produced; most algorithms have no knob to act on that. Handing `skip_max_init` to one of them made the stage config raise `extra_forbidden`, so every chain ending in `awq_clip` (which also consumes a prior stage's amax) failed to build. Found by sweeping all 121 ordered algorithm pairs through compile-then-run and cross-checking each outcome against the declared capability table: the five `* -> awq_clip` chains crashed on the scoped path while working on the legacy `algorithm=[...]` path, which located the bug in the executor rather than in any algorithm. Signed-off-by: Fridah-nv <201670829+Fridah-nv@users.noreply.github.com>
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueComment |
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.
What does this PR do?
Type of change: new feature (prototype — draft, not for merge as-is)
Adds an opt-in
algo_cfgkey that assigns an ordered calibration pipeline per scope, instead of a single model-widealgorithm. This expresses two things the current surface cannot: different algorithms for different parts of the model (parallel), and an ordered pipeline on the same targets where each stage consumes the previous one's mutated weights/scales (sequential).Usage
An
algo_cfgentry has the same{<selector>, "cfg": ...}shape as aquant_cfgentry:quant_cfgentries carry quantizer attributes,algo_cfgentries carry the ordered algorithms. Exactly one selector per entry —module_name(module/weight-level algorithms, role implied by the algorithm) orquantizer_name(when the role must be chosen explicitly, e.g.maxon inputs only).What changed
algo_cfg.py(new) — the compile half.compile_algo_cfg(config, model)lowersalgo_cfg+algorithminto an ordered list ofAlgoStages. It is pure: it reads the quantized model's structure (quantizer/linear names) to resolve globs and validate, but mutates nothing, runs no forward and touches no data. So bad configs fail before any expensive calibration, it is testable without running a model, and the plan is a pure function of(config, structure)— hence identical on every rank, which is what keeps predicate scoping from desynchronizing collectives.ALGO_CAPABILITIES— a small declared table per algorithm (granularity,role,requires,produces,requires_absent, forward traits).derive_handoff()— a stage whose inputs an earlier stage already produced is told to skip its own initialization (skip_max_init), derived from the declared capabilities rather than a hard-coded algorithm pair.plan_hash()— excludes provenance, so equivalent plans written two ways hash the same; intended as the rank-identical-plan assert.mode.py— the execute half. Acalibration_planmode whoseconvertcompiles the plan, then runs each stage in order through the existingwrapped_calib_funcwith ashould_processwrite-mask built from the stage's scope. Records one mode (per-stage modes would bloat state); restore is the generic quantizer-state snapshot, unchanged.config.py—AlgoCfgEntry,CalibrationPlanConfig,QuantizeConfig.algo_cfg/.strict,MseCalibConfig.skip_max_init;need_calibrationconsidersalgo_cfg.model_quant.py—calibrate(..., algo_cfg=, strict=);quantizepasses them through.model_calib.py—should_processwrite-mask threaded into the module-iteration points ofmax/mse/awq/awq_clip/gptq/smoothquant. DefaultNonemeans "whole model", i.e. today's behaviour. The mask gates writes only and never toggles enable-state, so the activations search-based algorithms see are unchanged.algorithmlowers through the same path as its all-"*"case, so there is no second engine. With noalgo_cfgthe old path, its numerics and its saved state are untouched.Two upstream bugs fixed along the way
Both reproduce on today's un-scoped
algorithm=[...]list — they are not artifacts of the scoped plan, but they block sequencing, which is the point of this change.msecrashed._mse_calibrate_weightsassignedweight_quantizer._calibratorto its search calibrator and never restored it, so the next stage that collects stats re-entered a spent calibrator:algorithm=['max','mse','max']→TypeError: unsupported operand type(s) for *: 'NoneType' and 'Tensor'. Now restored in afinally. The structural fix is still to make the per-tensor strategy config-selected rather than swapping the object.awq_litecalibrated the whole model regardless of scope, because it callsenable_stats_collection(model)/finish_stats_collection(model)directly. The write-mask has to reach helper calls inside an algorithm, not just its top-level module loop — worth knowing for any similar work.Testing
tests/unit/torch/quantization/test_algo_cfg.py— 25 new tests: lowering, plan-hash equivalence, every validation rule, derived handoff, write-mask, enable-state untouched, single recorded mode, and "a stage can followmse". ~1s.pytest tests/unit/torch/quantization/ --ignore=.../plugins→ 827 passed, 1 skipped, unchanged frommain. (plugins/does not collect in my env:test_diffusers_wan_conv3d.pyfails to import diffusers — pre-existing, unrelated.)algorithm="max"and the equivalentalgo_cfgproduce bit-identical amax on every weight quantizer, and compile to the same plan hash.Not covered: distributed (no multi-GPU available — the rank-identical-plan property is structural but untested; a 2-GPU TP/EP test is still needed), shared-forward batching across independent stages,
auto_quantizeper-layer algorithm, and GPU-only algorithms (gptq/svdquantcompile and validate but were not executed).Before your PR is "Ready for review"
algo_cfgis opt-in; without it the old path, numerics and saved state are unchanged.CONTRIBUTING.md: N/A — no copied code, no new dependencies.test_algo_cfg.py(25 tests).Additional Information
Draft on purpose. The config surface (
algo_cfg, per-entrycfg) and how much of the capability contract belongs in the first cut are what I would most like feedback on before polishing this for merge.