MDMM hardware-aware metrics (FPGA + PACA), tests and docs#46
MDMM hardware-aware metrics (FPGA + PACA), tests and docs#46ArghyaRanjanDas wants to merge 3 commits into
Conversation
Add the two hardware-aware sparsity metrics for MDMM to both the Keras and Torch backends, on dev's per-backend layout: - FPGAAwareSparsityMetric: fraction of zero DSP/BRAM weight groups, modelling FPGA resource packing (rf / precision / bram_width). - PACAPatternMetric: drives conv kernels onto a small set of dominant binary patterns. Pattern selection stays on-graph (keras.ops bit-pack + bincount / native torch.unique, no NumPy round-trip of the weight) and exposes a projection mask applied during fine-tuning through a generic, metric-agnostic hook in MDMM. Data model (MDMMPruningModel): - New metric_type values + flat Optional fields (precision, target_resource, bram_width, num_patterns_to_keep, beta, distance_metric) validated by Pydantic (ge/le + Literal); a model_validator forces PACA -> Equality / target 0. - Fix the reported Pydantic serialization warnings (constraint_type 'Equality' and the discriminated pruning_parameters union): enum fields now default to enum members instead of bare strings, which Pydantic does not coerce on defaults. Also: distance/layout/resource constants centralised in core/constants.py, the get_layer_sparsity float/int dtype fix on the TF backend, and config_mdmm_fpga.yaml + config_mdmm_paca.yaml with mdmm_fpga_config() / mdmm_paca_config() helpers.
- test_mdmm.py / test_mdmm_metrics.py: Keras MDMM, FPGA DSP/BRAM math, PACA pattern selection + projection masking, full phase cycles, Pydantic validation, and a regression test that MDMM configs serialize without Pydantic warnings. - test_torch_mdmm.py: native-torch MDMM + metrics. - run_tests.sh: run the new files under both tensorflow and torch backends.
- reference.md: MDMM metric-type overview + FPGAAwareSparsity and PACAPatternSparsity parameter tables. - getting_started.md: constraint-based MDMM usage with the hardware-aware configs.
|
Add these tests to run_tests.sh |
|
Hi @ArghyaRanjanDas, thank you for your updated PR. I reviewed it successfully and left you couple of comments on what should be changed before we merge it to the dev branch. Let me know if you have any questions regarding it. |
| damping: 1.0 | ||
| use_grad: false | ||
| constraint_lr: 1.0e-3 | ||
| # PACAPatternMetric-specific |
There was a problem hiding this comment.
Clarification about PACAPatternMetric-specific values; are these values result in the best results?
There was a problem hiding this comment.
They were the values (hand-tuned) that worked best for the SmartPixels. Do you think it would be better to scan for the best hyperparameters?
If Yes, Kindly let me know if you also have any dataset in mind, that you would like this to be tuned to.
There was a problem hiding this comment.
I would say, that in this situation it would be nice just to leave the best values based on your point of view. If they are the same, leave them as it's.
|
|
||
| def convert_conv_layout(w, src, dst=CANONICAL_CONV_LAYOUT): | ||
| """Transpose a 4D conv weight from `src` to `dst` layout (no-op if already equal).""" | ||
| if src == dst: |
There was a problem hiding this comment.
why not to make just one condition:
if src =! dst or perm != (0, 1, 2, 3):
and return w in the end instead?
There was a problem hiding this comment.
Yeah sure! So something like this?
def convert_conv_layout(w, src, dst=CANONICAL_CONV_LAYOUT):
perm = _perm(src, dst)
if src != dst or perm != (0, 1, 2, 3):
return ops.transpose(w, perm)
return w
|
|
||
| def _kernel_pattern_distances(kernel_patterns, kernels, dominant_patterns, distance_metric): | ||
| """Distance from every kernel to every dominant pattern. -> (M_kernels, alpha).""" | ||
| tk = ops.cast(kernel_patterns, kernels.dtype) # (M, K) binary support of kernels |
There was a problem hiding this comment.
these namings are slightly confusing
There was a problem hiding this comment.
Ohh okay :)
I will name them something better.
| dot = ops.sum(k_e * projected, axis=-1) | ||
| denom = ops.norm(k_e, axis=-1) * ops.norm(projected, axis=-1) + keras.backend.epsilon() | ||
| return 1.0 - dot / denom | ||
| raise ValueError(f"Unsupported distance metric: {distance_metric!r}") |
There was a problem hiding this comment.
there is no need in value error, as pydantic will raise it:
distance_metric: Optional[Literal["hamming", "valued_hamming", "cosine"]] = Field(default=None)
| _METRIC_REGISTRY = { | ||
| "UnstructuredSparsity": UnstructuredSparsityMetric, | ||
| "StructuredSparsity": StructuredSparsityMetric, | ||
| "FPGAAwareSparsity": FPGAAwareSparsityMetric, |
There was a problem hiding this comment.
as torch and keras mdmm implementations share the same constraints, metric registry and functions ( _compute_hard_mask), why not to reorganise it in the way that modules reuse that shared code?
There was a problem hiding this comment.
I agree with the principle, the backend is split into two. But I think all the other method also does that, and to unify them into one keras backend that would require a considerable amount of repo-wide refactoring?
There was a problem hiding this comment.
I think we should really post-pone this large refactoring, unless we decide to start moving everything to keras.
There was a problem hiding this comment.
Makes sense for now
| PACA_DISTANCE_METRICS = (DISTANCE_HAMMING, DISTANCE_VALUED_HAMMING, DISTANCE_COSINE) | ||
|
|
||
| # FPGAAwareSparsityMetric target hardware resources | ||
| TARGET_RESOURCE_DSP = "DSP" |
There was a problem hiding this comment.
same here, why not to make FPGA_TARGET_RESOURCES_REGISTRY, etc?
| @@ -67,6 +67,8 @@ class ActivationPruningModel(BasePruningModel): | |||
| class MetricType(str, Enum): | |||
There was a problem hiding this comment.
It can be as a general metric class, and these subclasses inherit from it and reimplement some methods
There was a problem hiding this comment.
That also sounds great! Implementing that
| return zero_bram.sum() / float(bram_norms.numel()) | ||
|
|
||
|
|
||
| class PACAPatternMetric: |
There was a problem hiding this comment.
why these metric classes aren't Pydantic?
There was a problem hiding this comment.
This are compute objects as the metric holds live tensor and bound method. So I think we would have to allow arbitrary_types_allowed=True if we want to move to Pydantic, which I think would not be good code policy.
There was a problem hiding this comment.
Also during config parsing, Pydantic is already validating by the time MDMM.build constructs a metric, every value has passed the Literal/ge/le checks in MDMMPruningModel. So validating again inside the class would be double validation.
There was a problem hiding this comment.
Actually, if we do that per-metric config sub-models from the other thread, there will be effectively a Pydantic class per metric where it owns and validates the parameters. And the runtime class will just then consume those validated values.
There was a problem hiding this comment.
Kindly let me know how that sounds to you or if I am missing anything
There was a problem hiding this comment.
I think it wouldn't be a problem if we make a good module structure planning -> we can discuss it via meet
| @@ -0,0 +1,72 @@ | |||
| # file: /src/pquant/configs/config_mdmm_fpga.yaml | |||
There was a problem hiding this comment.
too much redundant parameters in config files
There was a problem hiding this comment.
Sure Happy to trim them. these two were clones from the config_mdmm.yaml to stay consistent and trimming them might make it a bit confusing for the user (ommited = default or ommited = forgotten). Do you think we should trim just these two or all of them? the minimal versions should be ~15 lines
There was a problem hiding this comment.
Maybe we can make separate variation of files, including different mdmm parameters for different settings? All others blocks (training, hpo, quantization) should stay the same within the files.
| pruning_parameters: | ||
| pruning_method: mdmm | ||
| enable_pruning: true | ||
| disable_pruning_for_layers: [] # Disable pruning for these layers, even if enable_pruning is true |
There was a problem hiding this comment.
same, too much redundant parameters
There was a problem hiding this comment.
Same as above
Hi, you might have missed it, they are already added, lines 7–9 of tests/run_tests.sh |
|
Thanks @ArghyaRanjanDas for these modifications. Yes, I have missed |
Summary
FPGAAwareSparsityandPACAPatternMDMM metrics to both the Keras and Torch backends, ported onto dev's per-backend layout. Supersedes MDMM tests and hardware-aware metric functions #37, which was built on the old singlepruning_methods/tree and targetedmain.constraint_typeenum and the discriminatedpruning_parametersunion). Root cause: the MDMM enum fields defaulted to bare strings, and Pydantic does not validate/coerce defaults, so the runtime value stayed astr. They now default to enum members.MDMMPruningModel(ge/le+Literal) and drop the per-metric asserts. PACA auto-forces Equality/target 0 via a model_validator, so the MDMM layer stays metric-agnostic.torch.unique, no NumPy round-trip of the weight); centralise distance/layout/resource constants incore/constants.py; fix theget_layer_sparsityfloat/int dtype bug on the TF backend.config_mdmm_fpga.yaml,config_mdmm_paca.yaml, themdmm_fpga_config()/mdmm_paca_config()helpers, and document the new metric types inreference.mdandgetting_started.md.Test plan
cd tests && pytest test_mdmm.py test_mdmm_metrics.py(Keras / TensorFlow)cd tests && KERAS_BACKEND=torch pytest test_mdmm.py test_mdmm_metrics.py(Keras / Torch)cd tests && KERAS_BACKEND=torch pytest test_torch_mdmm.py(native Torch)config_mdmm_fpga.yamlandconfig_mdmm_paca.yaml, callmodel_dump(), confirm noPydanticSerializationUnexpectedValuewarnings.pytest test_pdp.py test_ap.py.Notes for review
metric_typeis read off the config and resolved via the metric registry, and theMetricType/ConstraintTypeenums already exist. This PR keeps that flat-params + registry approach rather than nested metric models.patterns.pyno-opOIHW->OIHWpermute is replaced with real per-backend layout handling (Keras kernels are HWIO, Torch OIHW), and theconvert_to_numpypattern step is gone.