aimet_onnx.experimental.spinquant¶
Top level APIs
- aimet_onnx.experimental.spinquant.apply_spinquant(model, visual_model=None, embedding=None, *, enable_r1=True, enable_r2=False, enable_r3=False, topology=None)[source]¶
Apply SpinQuant rotation transforms to an ONNX transformer model.
SpinQuant applies orthogonal Hadamard rotations to model weights to reduce quantization error. This function modifies the ONNX model(s) in-place by:
Reading the backbone’s decoder-stack structure (block boundaries, role map, hidden size) off
topology, and analyzing the optional visual encoder (PatchMerger output projection).Validating every selected rotation pass against the analysis.
Applying every selected pass in order (R1 before R2 before R3).
Serializing the result back onto the caller’s
ModelProtos.
The rotations are performed on an
onnx_ircopy of the graph and written back only once every pass has succeeded, so a failure part-way through leaves the caller’s model untouched rather than half-rotated. (Theembeddingtensor is the exception: it is atorch.Tensorrotated in place.)Must be called on the float ONNX model BEFORE creating a
QuantizationSimModel. The rotation modifies float weight initializers (R1 / R2) and may insert new nodes (R3); build the sim on the rotated graph and runcompute_encodingsafterward so quantizer scales are calibrated on the rotated weights.- Supported architectures:
LLaMA, Qwen2, Qwen3, Phi3 (backbone only)
Qwen2.5-VL, Qwen3-VL (backbone + visual)
- Parameters:
model (
ModelProto) – backbone.onnx ModelProto. Mutated in-place.visual_model (
Optional[ModelProto]) – Optional visual.onnx ModelProto (VLM only). Mutated in-place.embedding (
Optional[Tensor]) – Optionaltorch.Tensorof shape[vocab, hidden]loaded fromembedding.pth(VLM only). Rotated in-place with R_L.enable_r1 (
bool) – IfTrue(default), apply the R1 (residual-stream) rotation.enable_r2 (
bool) – IfTrue, apply the R2 (per-head) rotation. Defaults toFalse. Not supported on architectures with fused QKV projections (e.g. Phi3).enable_r3 (
bool) – IfTrue, apply the R3 online Hadamard rotation on Q and K paths into each block’s QK^T MatMul. Defaults toFalse. Inserts newMatMulnodes into the ONNX graph (does not mutate existing weights). MHA only — not supported on fused QKV or per-head split exports. The K-side rotation is placed upstream of the past-keyConcatso K values entering the KV cache are already rotated; the model’spresent_keyoutput then carries rotated K (cache convention is self-consistent across steps).topology (
Optional[LlmTopology]) – Decoder-stack topology ofmodel, fromanalyze_llm_topology(). Every rotation is placed by it — which linears read from and write to the residual stream (R1), which are the V/O projections (R2), which are the Q/K edges into QKᵀ (R3) — because discovering model structure belongs tollm_topology, not to SpinQuant. Analyze the samemodelthis call rotates. Passing it explicitly is also the only way to override the analysis: this function would otherwise analyze withactive_norms_per_block=2and the default role patterns, which no argument here can change. Optional today: when omitted the topology is analyzed internally frommodeland a warning is raised. Passing it explicitly is recommended, and may become required in a future release.
- Raises:
ValueError – If no rotation is enabled, if block detection or role classification fails, if
topologydoes not describemodel, or if any expected weight is missing / has the wrong shape.- Return type:
None
Example (LLM):
topology = analyze_llm_topology(model) apply_spinquant(model, topology=topology) sim = QuantizationSimModel(model) # built on the rotated graph sim.compute_encodings(calibration_data)
Example (VLM):
embedding = torch.load("embedding.pth") # torch.Tensor [vocab, hidden] topology = analyze_llm_topology(backbone_model) apply_spinquant( backbone_model, visual_model=visual_model, embedding=embedding, topology=topology, ) torch.save(embedding, "embedding.pth") # overwrite with rotated weights backbone_sim = QuantizationSimModel(backbone_model) visual_sim = QuantizationSimModel(visual_model) backbone_sim.compute_encodings(backbone_calibration_data) visual_sim.compute_encodings(visual_calibration_data)
Model topology
SpinQuant places every rotation using an LlmTopology, which describes the structure of the
decoder stack: which linears read from and write to the residual stream (R1), which are the V and O
projections (R2), and which Q/K edges feed each QKᵀ MatMul (R3). Analyze the model, then pass the
result as topology:
topology = analyze_llm_topology(onnx_model)
apply_spinquant(onnx_model, topology=topology)
sim = QuantizationSimModel(onnx_model, ...) # built on the rotated graph
- aimet_onnx.experimental.llm_topology.analyze_llm_topology(model, active_norms_per_block=2, expected_num_blocks=None, role_patterns=None, ir_model=None)[source]¶
Analyze
modelend-to-end and return a name-basedLlmTopology.Runs the whole pipeline on a private onnx_ir copy of
model: strip quantizers, fuse RMSNorms, detect active norms and block boundaries, build the per-block topology, and inferhidden_size/head_dim.- Parameters:
model (
ModelProto) – ONNX ModelProto to analyze. Not mutated.active_norms_per_block (
int) – Active norms per decoder block (seeget_decoder_block_boundaries()). Defaults to 2.expected_num_blocks (
Optional[int]) – If given, validated against the detected count.role_patterns (
Optional[Dict[LinearRole,Pattern]]) – Optional module-name → role override (seeclassify_linear_role()).ir_model (
Optional[Model]) – Pre-built analysis IR formodel— as returned bybuild_analysis_ir(), i.e. quantizer-stripped and RMSNorm-fused. Built here whenNone. Pass one only to avoid a secondfrom_protoof a large model when the caller already holds it; a faithful (unfused) IR will not detect norms and must not be passed.
- Return type:
- Returns:
LlmTopology with block/backbone roles,
active_norms,hidden_sizeandhead_dimpopulated.head_dimisNonewhen the export exposes nopast_valuegraph input to derive it from.
- class aimet_onnx.experimental.llm_topology.LlmTopology(embed_tokens=<factory>, lm_head=<factory>, blocks=<factory>, past_key_input_names=<factory>, past_key_output_names=<factory>, past_value_input_names=<factory>, past_value_output_names=<factory>, active_norms=None, hidden_size=None, head_dim=None)[source]¶
Topology of an ONNX decoder-stack model: blocks + backbone-level roles + dims.
- Parameters:
embed_tokens (
List[str]) – Names of the token-embedding ``Gather``(s) that produce the initial residual-stream activations.lm_head (
List[str]) – Names of the vocabulary-projection linear(s) downstream of the final norm.blocks (
List[BlockTopology]) – Per-decoder-block topology in topological order.past_key_input_names (
List[str]) – Rawpast_key_*graph inputs in declaration order, collected tolerantly (empty for prefill-only exports without a KV-cache). Pairing these toblocksand validating that their count matches the block count are the consumer’s responsibility (e.g. R3) — R1-only and prefill-only flows do not require KV-cache inputs.past_key_output_names (
List[str]) – Key-cache graph outputs in declaration order.past_value_input_names (
List[str]) – Value-cache graph inputs in declaration order.past_value_output_names (
List[str]) – Value-cache graph outputs in declaration order.active_norms (
Optional[List[ActiveNorm]]) – Active norms in topological order used to build the topology.hidden_size (
Optional[int]) – Residual-stream hidden dimension (Noneif not inferred;analyze_llm_topology()fills it).head_dim (
Optional[int]) – Per-head dimension (Nonewhen it could not be derived, e.g. an export without KV-cache inputs).