[JAX] Add support for per-layer config custimization and composable configs#2481
[JAX] Add support for per-layer config custimization and composable configs#2481bkowalskiINTEL wants to merge 35 commits into
Conversation
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
for more information, see https://pre-commit.ci
There was a problem hiding this comment.
Pull request overview
Adds JAX quantization support for (1) per-layer filtering via include/exclude patterns and (2) composing multiple quantization configs, while refactoring quantize flow to centralize model wrapping and improve (de)serialization behavior.
Changes:
- Add
include/excludelayer filters toStaticQuantConfigandDynamicQuantConfig, and apply them when generating model info. - Add ComposableConfig support in quantization config JSON serialization/deserialization and in
quantize_model()mapping construction. - Refactor JAX static/dynamic algorithms to operate on per-layer config mappings and move wrapper application into
quantize_model().
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
test/jax/test_config_on_vit.py |
Adds a ViT config-filter demonstration (currently executes at import time). |
neural_compressor/jax/quantization/saving.py |
Adds ComposableConfig (de)serialization and updates deserialization preparation to handle multiple sub-configs and include/exclude. |
neural_compressor/jax/quantization/quantize.py |
Adds ComposableConfig mapping merge logic, enforces static-first algorithm application, and centralizes model wrapping. |
neural_compressor/jax/quantization/config.py |
Introduces include/exclude filtering and switches model info to use layer paths (with filtering). |
neural_compressor/jax/algorithms/static.py |
Updates static quantization to prepare only layers selected by configs_mapping and to support per-layer params. |
neural_compressor/jax/algorithms/dynamic.py |
Updates dynamic quantization to prepare only layers selected by configs_mapping and to support per-layer params. |
examples/jax/keras/gemma/quantization.py |
Updates Gemma quantization example (currently contains an early exit()). |
| def _matches(pattern: str) -> bool: | ||
| if pattern == class_name: | ||
| return True | ||
| return re.search(pattern, layer_id) is not None |
| # Build configs_mapping - handle ComposableConfig by calling sub-configs individually | ||
| if isinstance(quant_config, ComposableConfig): | ||
| configs_mapping = _build_configs_mapping_composable(model, quant_config) | ||
| else: | ||
| model_info = quant_config.get_model_info(model) | ||
| configs_mapping = quant_config.to_config_mapping(model_info=model_info) |
anko-intel
left a comment
There was a problem hiding this comment.
Some comments from offline reviewe
|
|
||
| Returns: | ||
| keras.Model: The quantized model wrapped for inference. | ||
| keras.Model: The quantized model. |
There was a problem hiding this comment.
Previous comment seems to be more accurate
| causal_lm_make_replace_generate_function(model) | ||
|
|
||
| # Execute algorithms - static first to ensure calibration runs on original FP32 model | ||
| algo_order = sorted(algos_mapping.keys(), key=lambda name: (0 if name == STATIC_QUANT else 1)) |
There was a problem hiding this comment.
can we use priority or something like that. I am not sure, but I think I saw such list of algos
| iterate_over_layers(qmodel, operations, filter_function=lambda c: c in static_quant_mapping) | ||
| # Phase 1: Prepare layers and add observers | ||
| for layer in qmodel._flatten_layers(): | ||
| if layer.__class__ not in static_quant_mapping: |
There was a problem hiding this comment.
maybe we can filter if class could be quantied in earlier stage
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
2c557c7 to
c45031b
Compare
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Co-authored-by: Bartosz Kowalski <bartosz.kowalski@intel.com> Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Fixes missing import Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
| def _matches(pattern: str) -> bool: | ||
| if pattern == class_name: | ||
| return True | ||
| return re.search(pattern, layer_id) is not None |
| include (Optional[List[str]]): List of layer class names or path patterns to include. | ||
| When set, only matching layers are quantized. Supports fnmatch patterns. | ||
| exclude (Optional[List[str]]): List of layer class names or path patterns to exclude. | ||
| Matching layers are skipped. Supports fnmatch patterns. |
| and the layer path (regex search). | ||
|
|
||
| Args: | ||
| layer_id: Layer path or name identifier. |
There was a problem hiding this comment.
maybe 2 examples also here
| Args: | ||
| layer_id: Layer path or name identifier. | ||
| class_name: Layer class name (e.g. "Dense"). | ||
| include: If set, layer must match at least one entry. |
| """Check if a layer passes include/exclude filters. | ||
|
|
||
| Each filter entry is matched against both the layer class name (exact match) | ||
| and the layer path (regex search). |
There was a problem hiding this comment.
some more description? only one of include or exclude is optional ? which one is more important/ win
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.
Comments suppressed due to low confidence (1)
neural_compressor/jax/quantization/saving.py:41
- The return-shape description is now inaccurate for composable configs: the function returns
{quantization_type: \"composable\", configs: [...]}(noteconfigs, notconfig). Updating the docstring to reflect the exact keys/shape will avoid confusion for consumers parsing saved artifacts.
def quant_config_to_json_object(quant_config: BaseConfig) -> dict:
"""Serialize a quant config to a JSON-compatible dict with class name.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
20ce594 to
96a2397
Compare
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
| from typing import Callable, Dict, Tuple | ||
|
|
||
| import keras | ||
| from keras_hub.src.models.causal_lm import CausalLM |
|
|
||
| @register_config(framework_name=FRAMEWORK_NAME, algo_name=STATIC_QUANT) | ||
| class StaticQuantConfig(BaseConfig): | ||
| class StaticQuantConfig(JaxBaseConfig): |
| name = STATIC_QUANT | ||
|
|
||
| def __init__( | ||
| self, | ||
| weight_dtype: str = "fp8_e4m3", | ||
| activation_dtype: str = "fp8_e4m3", | ||
| const_scale: bool = False, | ||
| const_weight: bool = False, | ||
| white_list: Optional[List[OP_NAME_OR_MODULE_TYPE]] = DEFAULT_WHITE_LIST, | ||
| ): | ||
| """Init Static quantization config. | ||
|
|
||
| Args: | ||
| weight_dtype (str): Data type for weights, default is "fp8_e4m3". | ||
| activation_dtype (str): Data type for activations, default is "fp8_e4m3". | ||
| const_scale (bool): Whether to use a constant scale factor for quantization. | ||
| const_weight (bool): Whether to use constant quantized weights. | ||
| white_list (list): A list of supported operators of this algorithm. | ||
|
|
||
| Returns: | ||
| None: Initializes the configuration instance. | ||
| """ | ||
| super().__init__(white_list=white_list) | ||
| if not isinstance(weight_dtype, list): | ||
| jnp_weight_dtype = dtype_mapping[weight_dtype] | ||
| jnp_activation_dtype = dtype_mapping[activation_dtype] | ||
| if ( | ||
| jnp.issubdtype(jnp_weight_dtype, jnp.floating) and jnp.issubdtype(jnp_activation_dtype, jnp.integer) | ||
| ) or (jnp.issubdtype(jnp_weight_dtype, jnp.integer) and jnp.issubdtype(jnp_activation_dtype, jnp.floating)): | ||
| raise ValueError("Mixed quantization with floating-point and integer dtypes is not supported.") | ||
| def _get_supported_class_names(self) -> set: |
| def _matches(pattern) -> bool: | ||
| pattern = pattern if isinstance(pattern, str) else pattern.__name__ | ||
| if pattern == class_name: | ||
| return True | ||
| try: | ||
| return re.search(pattern, layer_id) is not None | ||
| except re.error as e: | ||
| raise ValueError(f"Invalid regex pattern {pattern!r} in white_list/exclude filter.") from e |
| const_scale: bool = False, | ||
| const_weight: bool = False, | ||
| white_list: Optional[List[OP_NAME_OR_MODULE_TYPE]] = DEFAULT_WHITE_LIST, | ||
| exclude: Optional[List[str]] = None, |
There was a problem hiding this comment.
Maybe now we should name it black_list, since we use white_list instead of include
| raise ValueError(f"Invalid regex pattern {pattern!r} in white_list/exclude filter.") from e | ||
|
|
||
| white_list = self._white_list | ||
| if white_list != DEFAULT_WHITE_LIST and (white_list is None or not any(_matches(p) for p in white_list)): |
There was a problem hiding this comment.
Make this if statement clearer, it's hard to understand what's going on
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
Signed-off-by: Bartosz Kowalski <bartosz.kowalski@intel.com>
No description provided.