This directory contains thin command-line examples for running quantization, evaluation, export, and debugging workflows.
The examples are intentionally small. Real implementation code lives in
tico.quantization.recipes.
tico/quantization/examples/
├── README.md
├── quantize.py # Run a config-driven quantization pipeline
├── evaluate.py # Evaluate an FP model or saved checkpoint
├── export.py # Export artifacts from a model or saved checkpoint
├── inspector.py # Run trace/parity/debug tools
└── configs/ # Reusable recipe presets
Use quantize.py when you want to run a quantization recipe. It loads the
model, builds calibration inputs, runs enabled pipeline stages such as GPTQ,
PTQ, SpinQuant, or CLE, and then optionally evaluates and exports the resulting
model.
Use evaluate.py when you only want to evaluate a floating-point model or an
already saved checkpoint. evaluate.py does not run pipeline stages from
the config. If the config contains enabled stages such as gptq or ptq, they
are ignored by evaluate.py.
Use export.py to export either a floating-point model loaded through its
adapter or an already saved checkpoint. It writes configured artifacts such as
LLaMA and Qwen3-VL staged Circle files and does not run pipeline stages.
Use inspector.py for debug-oriented workflows such as trace, parity, runtime
inspection, and wrapper-level smoke checks.
Summary:
| Command | Runs pipeline stages? |
Builds calibration inputs? | Evaluates? | Exports? |
|---|---|---|---|---|
quantize.py |
Yes | Yes | If evaluation.enabled=true |
If export.enabled=true |
evaluate.py |
No | No | Yes | No |
export.py |
No | No | No | Yes |
inspector.py |
Mode-dependent | Mode-dependent | Debug only | Debug only |
Common usage patterns:
# Quantize LLaMA and save the configured checkpoint.
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/llama_quantize.yaml# Evaluate a saved LLaMA checkpoint without running quantization again.
python -m tico.quantization.examples.evaluate \
--config tico/quantization/examples/configs/llama_eval_suite.yaml \
--checkpoint ./out/llama_quantized/quantized_model.pt# Export per-layer Circle artifacts from a saved LLaMA checkpoint.
python -m tico.quantization.examples.export \
--config tico/quantization/examples/configs/llama_export.yaml \
--checkpoint ./out/llama_quantized/quantized_model.ptUse this three-step loop for the common LLaMA post-quantization benchmark flow.
-
Quantize the model and save the configured checkpoint.
python -m tico.quantization.examples.quantize \ --config tico/quantization/examples/configs/llama_quantize.yaml
-
Evaluate the saved checkpoint with the configured LLM evaluation suite.
python -m tico.quantization.examples.evaluate \ --config tico/quantization/examples/configs/llama_eval_suite.yaml \ --checkpoint ./out/llama_quantized/quantized_model.pt
-
Export per-layer Circle artifacts from the saved checkpoint.
python -m tico.quantization.examples.export \ --config tico/quantization/examples/configs/llama_export.yaml \ --checkpoint ./out/llama_quantized/quantized_model.pt
Gemma4 E2B uses the same config-driven three-step interface. The export preset writes static per-stage Circle files for the vision and text runtime.
-
Quantize Gemma4 E2B and save the checkpoint.
python -m tico.quantization.examples.quantize \ --config tico/quantization/examples/configs/gemma4_quantize.yaml
-
Evaluate either the floating-point model or the saved checkpoint with the Gemma4 LLaVA-Bench judge suite.
python -m tico.quantization.examples.evaluate \ --config tico/quantization/examples/configs/gemma4_eval_suite.yaml \ --checkpoint ./out/gemma4_quantized/quantized_model.pt
-
Export quantized per-stage Circle artifacts.
python -m tico.quantization.examples.export \ --config tico/quantization/examples/configs/gemma4_export.yaml \ --checkpoint ./out/gemma4_quantized/quantized_model.pt
Export the floating-point model with the same static runtime contract:
python -m tico.quantization.examples.export \ --config tico/quantization/examples/configs/gemma4_export.yaml \ --source model \ --device cpu \ --output-dir ./out/gemma4_float/circle_layers
Floating-point files use the
.f32.circlesuffix. Quantized checkpoint files use.q.circle.
quantize.py is the command that executes the recipe pipeline. It runs the
enabled stages under pipeline in the order they appear in the config.
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/llama_quantize.yamlpython -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/qwen3_vl_quantize.yaml \
--model Qwen/Qwen3-VL-2B-InstructExamples:
# Run the default LLaMA benchmark quantization and save a checkpoint.
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/llama_quantize.yaml# Run a smaller calibration smoke without exporting artifacts.
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/llama_quantize.yaml \
--set calibration.n_samples=1 \
--set export.enabled=false# Run quantization and write the checkpoint to another output directory.
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/llama_quantize.yaml \
--output-dir ./out/llamaquantize.py prints a high-level config summary before loading the model. This
helps verify which stages and key quantization specs are active.
Example:
=== Config ===
Model : Maykeye/TinyLLama-v0
Device : cuda
DType : float32
Seed : 42
GPTQ enabled : True
GPTQ lm_head enabled : False
PTQ enabled : True
SpinQuant enabled : True
CLE enabled : False
Activation : int16
Linear weight : uint4
Embedding weight : uint8
LM head weight : uint8
Spin rotation weight : int16
Calibration samples : 128
Calibration seq length: 2048
Max seq length : 2048
Profile : npu_export
Disable the summary when needed:
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/llama_quantize.yaml \
--set runtime.print_config=falsePrint the GPTQ-to-PTQ qparam injection summary with runtime.verbose=true:
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/llama_quantize.yaml \
--set runtime.verbose=trueThe injection summary reports how many GPTQ quantizers were injected into PTQ weight observers:
[GPTQ → PTQ injection summary]
matched : 56
missed : 21
unused : 0
missed modules:
- model.embed_tokens
- model.norm
- model.layers.0.input_layernorm
...
evaluate.py evaluates the model loaded by the adapter, or the model loaded
from --checkpoint when one is provided. It does not prepare, calibrate,
convert, or export the model.
Running this command without --checkpoint evaluates the floating-point model,
because the pipeline section is not executed by evaluate.py:
python -m tico.quantization.examples.evaluate \
--config tico/quantization/examples/configs/llama_eval_suite.yamlTo evaluate a quantized result, pass a saved checkpoint:
python -m tico.quantization.examples.evaluate \
--config tico/quantization/examples/configs/llama_eval_suite.yaml \
--checkpoint ./out/llama_quantized/quantized_model.ptTop-level evaluation target selection is supported through --tasks.
--tasks is an exclusive allow-list. When it is present, only the listed
top-level evaluation targets run, even when other targets are enabled in the
YAML file. The option does not replace benchmark details such as LM-Eval task
names, VQA datasets, MMMU subjects, or sample limits. Configure those details
in YAML or with --set.
Canonical targets are model-family specific:
| Model family | Supported --tasks values |
|---|---|
| LLaMA | lm_eval, ppl |
| Qwen3-VL, Gemma4 | vqa, coco, llava_bench, videomme, mmlu, hellaswag, mmmu, ppl |
Run only the configured LM-Eval suite:
python -m tico.quantization.examples.evaluate \
--config tico/quantization/examples/configs/llama_eval_suite.yaml \
--checkpoint ./out/llama_quantized/quantized_model.pt \
--tasks lm_evalOverride the LM-Eval benchmark details separately:
python -m tico.quantization.examples.evaluate \
--config tico/quantization/examples/configs/llama_eval_suite.yaml \
--checkpoint ./out/llama_quantized/quantized_model.pt \
--tasks lm_eval \
--set evaluation.lm_eval_tasks=mmlu,hellaswagRun only MMMU and text perplexity from a VLM suite:
python -m tico.quantization.examples.evaluate \
--config tico/quantization/examples/configs/qwen3_vl_eval_suite.yaml \
--tasks mmmu,pplRun only the VQA evaluator while selecting its datasets separately:
python -m tico.quantization.examples.evaluate \
--config tico/quantization/examples/configs/qwen3_vl_eval_suite.yaml \
--tasks vqa \
--set 'evaluation.vlm_tasks=[textvqa]'Target names are canonical and have no aliases. For example,
--tasks lm_eval_tasks, --tasks vlm_tasks, and --tasks perplexity are
invalid. Use lm_eval, vqa, and ppl, respectively.
export.py exports configured artifacts from a floating-point model or an
already saved checkpoint. It does not load calibration data or run quantization
stages.
Export the floating-point model on CPU while preserving the NPU-facing per-layer input/output contract:
python -m tico.quantization.examples.export \
--config tico/quantization/examples/configs/llama_export.yaml \
--source model \
--device cpu \
--output-dir ./out/llama_float/circle_layersFloating-point artifacts use the .f32.circle suffix. To export an already
saved quantized checkpoint instead:
python -m tico.quantization.examples.export \
--config tico/quantization/examples/configs/llama_export.yaml \
--checkpoint ./out/llama_quantized/quantized_model.ptQwen3-VL uses the same generic circle_per_layer artifact key. The exporter
keeps the fixed-grid vision model as one prefill stage and emits each text
decoder layer separately for prefill and decode:
python -m tico.quantization.examples.export \
--config tico/quantization/examples/configs/qwen3_vl_export.yaml \
--checkpoint ./out/qwen3_vl_quantized/quantized_model.ptThe fixed values under model_args.vision must match the values used when the
checkpoint was wrapped and quantized. A default prefill/decode export writes:
vision_prefill.q.circle
token_embedding.q.circle
multimodal_embedding_prefill.q.circle
decoder_layer_prefill_<index>.q.circle
deepstack_fusion_<index>.q.circle
decoder_layer_decode_<index>.q.circle
lm_head.q.circle
token_embedding has a dynamic sequence dimension and is shared by prefill
and decode. The runtime owns mRoPE lookup, additive attention-mask
construction, and KV cache storage/update. It passes those tensors to the
exported decoder-layer graphs. Exporting with --source model writes the
same artifact set with the .f32.circle suffix.
llama_export.yaml exports both prefill and decode layer artifacts by default.
Use an override only when decode layer export is not needed:
python -m tico.quantization.examples.export \
--config tico/quantization/examples/configs/llama_export.yaml \
--checkpoint ./out/llama_quantized/quantized_model.pt \
--set export.prefill_decode=falseExport to another directory:
python -m tico.quantization.examples.export \
--config tico/quantization/examples/configs/llama_export.yaml \
--checkpoint ./out/llama_quantized/quantized_model.pt \
--output-dir ./out/llama_layersNote
Pass --tasks llava_bench to run only LLaVA-Bench, even when the selected
YAML preset enables other evaluation targets. Benchmark details under
evaluation.llava_bench remain unchanged.
LLaVA-Bench-in-the-Wild is evaluated through the regular evaluate.py entry
point when a supported VLM config enables evaluation.llava_bench.mode=judge.
This path treats LLaVA-Bench as open-ended natural QA. The generation prompt is
only:
<image>
{question}
For static-runtime parity with max_seq_len=2048, cap the image resolution so
the visual placeholder tokens fit before generation. The example config uses
image_max_pixels: 602112, which is 768 * 28 * 28, and the evaluator
pre-resizes PIL-like images before processor tokenization.
The adapter writes three artifacts:
- answer JSONL containing generated candidate answers
- review JSONL containing per-question judge scores
- summary JSON containing average score, relative score, and win/tie counts
Use the following three-step workflow to measure regression from the floating- point model to a quantized checkpoint.
Run evaluate.py first. This does not run the quantization pipeline; it only
loads the FP model and produces the FP LLaVA-Bench answers and judge summary.
Keep the FP answer JSONL because it is used as the regression baseline in step
3.
python -m tico.quantization.examples.evaluate \
--set evaluation.llava_bench.output.dir=./out/llava_bench_fp \
--set evaluation.llava_bench.output.answers=./out/llava_bench_fp/answers.jsonl \
--set evaluation.llava_bench.output.reviews=./out/llava_bench_fp/reviews.jsonl \
--set evaluation.llava_bench.output.summary=./out/llava_bench_fp/summary.json \
--config tico/quantization/examples/configs/qwen3_vl_eval_suite.yamlRun quantize.py with a config that enables the desired pipeline stages. The
example below exports the quantized checkpoint.
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/qwen3_vl_quantize.yaml \
--set export.output_dir=./out/qwen3_vl_quantizedThis writes the default checkpoint:
./out/qwen3_vl_quantized/quantized_model.pt
Run evaluate.py again with --checkpoint. Leave candidate_answers as
null so the checkpoint generates new candidate answers. Set
baseline_answers to the FP answer JSONL from step 1. The judge then compares:
baseline = FP answers
candidate = quantized-checkpoint answers
python -m tico.quantization.examples.evaluate \
--config tico/quantization/examples/configs/qwen3_vl_eval_suite.yaml \
--checkpoint ./out/qwen3_vl_quantized/quantized_model.pt \
--set evaluation.llava_bench.baseline_answers=./out/llava_bench_fp/answers.jsonl \
--set evaluation.llava_bench.candidate_answers=null \
--set evaluation.llava_bench.baseline_label=qwen3_vl_4b_fp \
--set evaluation.llava_bench.candidate_label=qwen3_vl_4b_ptq \
--set evaluation.llava_bench.output.dir=./out/llava_bench_quant \
--set evaluation.llava_bench.output.answers=./out/llava_bench_quant/answers.jsonl \
--set evaluation.llava_bench.output.reviews=./out/llava_bench_quant/quant_vs_fp.reviews.jsonl \
--set evaluation.llava_bench.output.summary=./out/llava_bench_quant/quant_vs_fp.summary.jsonThe regression signal is the quantized candidate score relative to the FP baseline score in the summary:
candidate_relative_score = mean_candidate_score / mean_baseline_score * 100
A value near 100 means the quantized checkpoint is close to the FP model under the same prompt, image cap, judge model, and sample set.
If candidate_answers points to an existing JSONL file, generation is skipped
and that file is judged. If candidate_answers is null, the currently loaded
model generates candidate answers and writes them to evaluation.llava_bench.output.answers.
Per-sample answer and judge progress is controlled by runtime.show_progress;
set it to false when you want quieter output:
--set runtime.show_progress=falseWhen the judge is not the official GPT-4-style judge, report the judge model ID with the results. The default Llama 3.2 3B judge is intended for inexpensive regression checks, not for leaderboard-compatible LLaVA-Bench reporting.
python -m tico.quantization.examples.inspector \
--config tico/quantization/examples/configs/qwen3_vl_quantize.yaml \
--mode trace \
--interesting-modules model.language_model model.visualpython -m tico.quantization.examples.inspector \
--config tico/quantization/examples/configs/static_llama_runtime.yaml \
--mode static-llama-runtimepython -m tico.quantization.examples.inspector \
--config tico/quantization/examples/configs/tied_embedding_smoke.yaml \
--mode tied-embedding-smokeTrace mode compares a floating-point model with a PTQ-prepared copy of the same model. It is useful for finding the first module where PTQ wrapping, calibration, or fake quantization changes the model output.
Basic Qwen3-VL trace:
python -m tico.quantization.examples.inspector \
--config tico/quantization/examples/configs/qwen3_vl_quantize.yaml \
--mode traceTrace only selected module subtrees:
python -m tico.quantization.examples.inspector \
--config tico/quantization/examples/configs/qwen3_vl_quantize.yaml \
--mode trace \
--interesting-modules model.language_model model.visualBy default, trace mode does not call convert() on the PTQ-prepared model.
This checks FP-vs-PTQ wrapper parity and should normally show very small
differences. To inspect the numerical error introduced by fake quantization,
enable conversion explicitly:
python -m tico.quantization.examples.inspector \
--config tico/quantization/examples/configs/qwen3_vl_quantize.yaml \
--mode trace \
--enable-quantization \
--interesting-modules model.language_model model.visualOutput sections:
=== FP trace ===
[module.name] Tensor(shape=..., dtype=..., mean=..., min=..., max=..., std=...)
=== PTQ trace ===
[module.name] Tensor(shape=..., dtype=..., mean=..., min=..., max=..., std=...)
=== Side-by-side diff ===
module.name: mean|diff|=..., max|diff|=...
--interesting-modules accepts module names or parent module prefixes. When it
is omitted, trace mode records all named modules. Use a small calibration sample
count before tracing large Qwen3-VL models.
Wrapper smoke mode runs module-level sanity checks for quantization wrappers.
It is useful when you want to quickly validate prepare -> calibrate -> convert,
numerical parity, plotting, and optional Circle export for a single wrapped
module.
List available wrapper smoke cases:
python -m tico.quantization.examples.inspector \
--mode wrapper-smoke \
--list-casesExample output:
Available wrapper smoke cases:
nn_linear
nn_conv3d
nn_conv3d_special_case
nn_layernorm
nn_tied_embedding
llama_attention_prefill
llama_attention_decode
llama_mlp
llama_decoder_layer_prefill
llama_decoder_layer_decode
qwen3_vl_text_attention
qwen3_vl_text_mlp
qwen3_vl_text_decoder_layer
qwen3_vl_text_model
qwen3_vl_vision_attention
Run one case:
python -m tico.quantization.examples.inspector \
--config tico/quantization/examples/configs/wrapper_smoke.yaml \
--mode wrapper-smoke \
--case llama_attention_prefillRun one case with Circle export:
python -m tico.quantization.examples.inspector \
--config tico/quantization/examples/configs/wrapper_smoke.yaml \
--mode wrapper-smoke \
--case llama_attention_prefill \
--export circle \
--output-dir ./out/wrapper_smoke \
--strictAll example CLIs accept --set KEY=VALUE for simple dotted overrides:
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/llama_quantize.yaml \
--set runtime.device=cpu \
--set calibration.n_samples=1 \
--set export.enabled=falseValues are parsed as YAML-like scalars when possible:
--set evaluation.enabled=true
--set calibration.n_samples=1
--set model.hf_token=null
--set runtime.dtype=float32List indices are zero-based. This is most useful for toggling entries in the
pipeline list:
pipeline:
- name: spinquant # pipeline.0
enabled: true
- name: cle # pipeline.1
enabled: false
- name: gptq # pipeline.2
enabled: true
- name: ptq # pipeline.3
enabled: truellama_quantize.yaml already enables SpinQuant. Disable it from the command
line with:
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/llama_quantize.yaml \
--set pipeline.0.enabled=falseYou can also override fields inside later stages by index. For example, if the
PTQ stage is pipeline.3, change its SpinQuant rotation weight spec with:
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/llama_quantize.yaml \
--set pipeline.3.spin_rotation_weight=int8Convenience aliases are also available:
--model # overrides model.name_or_path
--device # overrides runtime.device
--output-dir # overrides export.output_dir, quantize.py/export.py onlyWhen GPTQ uses sensitivity-aware MSE, set pipeline.<index>.mse=smse and
configure sensitivity behavior with a nested sensitivity mapping under the
GPTQ stage.
Schema:
pipeline:
- name: gptq
enabled: true
mse: smse
sensitivity:
mode: compute
path: nullSupported modes:
| Mode | Behavior |
|---|---|
compute |
Compute sensitivity for the current run only. path must be null. |
save |
Compute sensitivity, use it for this run, and save it to path. |
load |
Load sensitivity from path and use it for this run. |
cache |
Load from path if it exists; otherwise compute and save it. |
Compute sensitivity without saving it:
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/llama_quantize.yaml \
--set pipeline.2.mse=smse \
--set pipeline.2.sensitivity.mode=computeCompute and save sensitivity:
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/llama_quantize.yaml \
--set pipeline.2.mse=smse \
--set pipeline.2.sensitivity.mode=save \
--set pipeline.2.sensitivity.path=./out/llama/gptq_sensitivity.ptLoad a saved sensitivity file:
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/llama_quantize.yaml \
--set pipeline.2.mse=smse \
--set pipeline.2.sensitivity.mode=load \
--set pipeline.2.sensitivity.path=./out/llama/gptq_sensitivity.ptUse a cache file when available, otherwise compute and save it:
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/llama_quantize.yaml \
--set pipeline.2.mse=smse \
--set pipeline.2.sensitivity.mode=cache \
--set pipeline.2.sensitivity.path=./out/llama/gptq_sensitivity.ptFor qwen3_vl_quantize.yaml, the GPTQ stage is also pipeline.2, so the same
override pattern applies.
Most new examples should be config files, not Python files.
Add a config when:
- the model family already has an adapter
- the algorithm already has a stage
- the workflow is a new combination of existing stages
- the change is only calibration size, dtype, qscheme, evaluation, or export
Examples:
configs/llama_quantize.yaml
configs/llama_eval_suite.yaml
configs/llama_export.yaml
configs/qwen3_vl_quantize.yaml
configs/qwen3_vl_eval_suite.yaml
configs/qwen3_vl_eval_suite_mx_override_polices.yaml
configs/qwen3_vl_export.yaml
Do not add scripts like:
quantize_llama_with_gptq.py
quantize_qwen3_vl_with_gptq.py
quantize_qwen3_vl_ptq_only.py
quantize_vision_attention_debug.py
Use:
python -m tico.quantization.examples.quantize --config configs/<preset>.yaml
python -m tico.quantization.examples.inspector --config configs/<preset>.yaml --mode <mode>A new file under examples/ should be rare. It is acceptable only when it
introduces a genuinely new user-facing top-level action that does not fit any of
these commands:
quantize.py
evaluate.py
inspector.py
Before adding a new example script, check whether the feature should instead be:
| Feature | Preferred location |
|---|---|
| New model family | recipes/adapters/ |
| New algorithm | recipes/stages/ |
| New config combination | examples/configs/ |
| New benchmark | recipes/evaluation/ |
| New export artifact | recipes/export/ |
| New debug trace/parity tool | recipes/debug/ + inspector.py mode |
For a new family such as gemma, do this:
- Add
recipes/adapters/gemma.py. - Register it in
recipes/adapters/__init__.py. - Add
configs/gemma_ptq_only.yaml. - Optionally add
configs/gemma_gptq_ptq.yaml. - Add a smoke test.
- Document any model-specific config fields in
configs/README.md.
Then run:
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/gemma_ptq_only.yamlNo new example script is needed.
For a new algorithm such as awq, do this:
-
Add
recipes/stages/awq.py. -
Register it in
recipes/stages/__init__.py. -
Add the stage to an existing or new config preset:
pipeline: - name: awq enabled: true weight_bits: 4 - name: ptq enabled: true
-
Add a smoke test.
No new example script is needed.
Use these commands for quick sanity checks after changing examples or recipes:
python -m tico.quantization.examples.quantize \
--config tico/quantization/examples/configs/llama_quantize.yaml \
--set calibration.n_samples=1 \
--set calibration.seq_len=64 \
--set calibration.decode_steps=0 \
--set pipeline.spinquant.enabled=false \
--set pipeline.gptq.enabled=false \
--set pipeline.ptq.decode_calibration_steps=0 \
--set evaluation.enabled=false \
--set export.enabled=falsepython -m tico.quantization.examples.inspector \
--config tico/quantization/examples/configs/qwen3_vl_quantize.yaml \
--mode trace \
--set calibration.n_samples=1python -m tico.quantization.examples.inspector \
--config tico/quantization/examples/configs/wrapper_smoke.yaml \
--mode wrapper-smoke \
--case nn_linear \
--no-plot