nvidia-modelopt 0.35.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- modelopt/__init__.py +20 -0
- modelopt/deploy/__init__.py +16 -0
- modelopt/deploy/llm/__init__.py +30 -0
- modelopt/deploy/llm/generate.py +345 -0
- modelopt/onnx/__init__.py +34 -0
- modelopt/onnx/autocast/__init__.py +19 -0
- modelopt/onnx/autocast/__main__.py +187 -0
- modelopt/onnx/autocast/convert.py +202 -0
- modelopt/onnx/autocast/graphsanitizer.py +422 -0
- modelopt/onnx/autocast/logging_config.py +78 -0
- modelopt/onnx/autocast/nodeclassifier.py +416 -0
- modelopt/onnx/autocast/precisionconverter.py +1032 -0
- modelopt/onnx/autocast/referencerunner.py +159 -0
- modelopt/onnx/autocast/utils.py +117 -0
- modelopt/onnx/llm_export_utils/__init__.py +16 -0
- modelopt/onnx/llm_export_utils/export_utils.py +162 -0
- modelopt/onnx/llm_export_utils/quantization_utils.py +122 -0
- modelopt/onnx/llm_export_utils/surgeon_utils.py +120 -0
- modelopt/onnx/logging_config.py +77 -0
- modelopt/onnx/op_types.py +300 -0
- modelopt/onnx/quantization/__init__.py +20 -0
- modelopt/onnx/quantization/__main__.py +291 -0
- modelopt/onnx/quantization/calib_utils.py +178 -0
- modelopt/onnx/quantization/extensions.py +35 -0
- modelopt/onnx/quantization/fp8.py +375 -0
- modelopt/onnx/quantization/graph_utils.py +1479 -0
- modelopt/onnx/quantization/gs_patching.py +112 -0
- modelopt/onnx/quantization/int4.py +1347 -0
- modelopt/onnx/quantization/int8.py +283 -0
- modelopt/onnx/quantization/operators.py +121 -0
- modelopt/onnx/quantization/ort_patching.py +1766 -0
- modelopt/onnx/quantization/ort_utils.py +346 -0
- modelopt/onnx/quantization/partitioning.py +439 -0
- modelopt/onnx/quantization/qdq_utils.py +1349 -0
- modelopt/onnx/quantization/quant_utils.py +283 -0
- modelopt/onnx/quantization/quantize.py +528 -0
- modelopt/onnx/quantization/src/modelopt_round_and_pack_ext.cpp +127 -0
- modelopt/onnx/trt_utils.py +424 -0
- modelopt/onnx/utils.py +753 -0
- modelopt/torch/__init__.py +41 -0
- modelopt/torch/_deploy/__init__.py +23 -0
- modelopt/torch/_deploy/_runtime/__init__.py +29 -0
- modelopt/torch/_deploy/_runtime/common.py +72 -0
- modelopt/torch/_deploy/_runtime/ort_client.py +193 -0
- modelopt/torch/_deploy/_runtime/registry.py +83 -0
- modelopt/torch/_deploy/_runtime/runtime_client.py +154 -0
- modelopt/torch/_deploy/_runtime/tensorrt/constants.py +108 -0
- modelopt/torch/_deploy/_runtime/tensorrt/engine_builder.py +324 -0
- modelopt/torch/_deploy/_runtime/tensorrt/hw_param_config.py +51 -0
- modelopt/torch/_deploy/_runtime/tensorrt/layerwise_profiling.py +178 -0
- modelopt/torch/_deploy/_runtime/tensorrt/parse_trtexec_log.py +151 -0
- modelopt/torch/_deploy/_runtime/tensorrt/tensorrt_utils.py +200 -0
- modelopt/torch/_deploy/_runtime/trt_client.py +219 -0
- modelopt/torch/_deploy/compilation.py +142 -0
- modelopt/torch/_deploy/device_model.py +157 -0
- modelopt/torch/_deploy/profiling.py +193 -0
- modelopt/torch/_deploy/utils/__init__.py +18 -0
- modelopt/torch/_deploy/utils/onnx_optimizer.py +120 -0
- modelopt/torch/_deploy/utils/onnx_utils.py +58 -0
- modelopt/torch/_deploy/utils/torch_onnx.py +593 -0
- modelopt/torch/distill/__init__.py +28 -0
- modelopt/torch/distill/config.py +130 -0
- modelopt/torch/distill/distillation.py +84 -0
- modelopt/torch/distill/distillation_model.py +318 -0
- modelopt/torch/distill/loss_balancers.py +140 -0
- modelopt/torch/distill/losses.py +275 -0
- modelopt/torch/distill/mode.py +223 -0
- modelopt/torch/distill/plugins/__init__.py +24 -0
- modelopt/torch/distill/plugins/huggingface.py +110 -0
- modelopt/torch/distill/plugins/megatron.py +476 -0
- modelopt/torch/distill/registry.py +23 -0
- modelopt/torch/export/__init__.py +24 -0
- modelopt/torch/export/convert_hf_config.py +117 -0
- modelopt/torch/export/distribute.py +300 -0
- modelopt/torch/export/hf_config_map.py +84 -0
- modelopt/torch/export/layer_utils.py +1893 -0
- modelopt/torch/export/mcore_config_map.py +25 -0
- modelopt/torch/export/model_config.py +623 -0
- modelopt/torch/export/model_config_export.py +552 -0
- modelopt/torch/export/model_config_utils.py +392 -0
- modelopt/torch/export/model_utils.py +71 -0
- modelopt/torch/export/plugins/__init__.py +21 -0
- modelopt/torch/export/plugins/mcore_common.py +67 -0
- modelopt/torch/export/plugins/mcore_custom.py +530 -0
- modelopt/torch/export/plugins/mcore_deepseek.py +143 -0
- modelopt/torch/export/plugins/mcore_gptoss.py +71 -0
- modelopt/torch/export/plugins/mcore_llama.py +164 -0
- modelopt/torch/export/plugins/mcore_nemotron.py +90 -0
- modelopt/torch/export/plugins/mcore_qwen.py +99 -0
- modelopt/torch/export/plugins/megatron_importer.py +647 -0
- modelopt/torch/export/postprocess.py +847 -0
- modelopt/torch/export/quant_utils.py +1069 -0
- modelopt/torch/export/tensorrt_llm_type.py +42 -0
- modelopt/torch/export/tensorrt_llm_utils.py +405 -0
- modelopt/torch/export/transformer_engine.py +94 -0
- modelopt/torch/export/unified_export_hf.py +537 -0
- modelopt/torch/export/unified_export_megatron.py +1164 -0
- modelopt/torch/nas/__init__.py +27 -0
- modelopt/torch/nas/algorithms.py +856 -0
- modelopt/torch/nas/autonas.py +808 -0
- modelopt/torch/nas/conversion.py +119 -0
- modelopt/torch/nas/hparams/__init__.py +19 -0
- modelopt/torch/nas/hparams/concat.py +319 -0
- modelopt/torch/nas/hparams/container.py +48 -0
- modelopt/torch/nas/modules/__init__.py +21 -0
- modelopt/torch/nas/modules/container.py +99 -0
- modelopt/torch/nas/modules/conv.py +254 -0
- modelopt/torch/nas/modules/linear.py +76 -0
- modelopt/torch/nas/modules/norm.py +193 -0
- modelopt/torch/nas/modules/utils.py +61 -0
- modelopt/torch/nas/patch.py +261 -0
- modelopt/torch/nas/plugins/__init__.py +29 -0
- modelopt/torch/nas/plugins/megatron.py +1497 -0
- modelopt/torch/nas/plugins/torch.py +71 -0
- modelopt/torch/nas/plugins/transformer_engine.py +42 -0
- modelopt/torch/nas/plugins/transformers.py +210 -0
- modelopt/torch/nas/registry.py +23 -0
- modelopt/torch/nas/search_space.py +260 -0
- modelopt/torch/nas/traced_hp.py +149 -0
- modelopt/torch/nas/utils.py +408 -0
- modelopt/torch/opt/__init__.py +41 -0
- modelopt/torch/opt/_hooks.py +98 -0
- modelopt/torch/opt/config.py +383 -0
- modelopt/torch/opt/conversion.py +620 -0
- modelopt/torch/opt/dynamic.py +1367 -0
- modelopt/torch/opt/hparam.py +275 -0
- modelopt/torch/opt/mode.py +353 -0
- modelopt/torch/opt/plugins/__init__.py +38 -0
- modelopt/torch/opt/plugins/diffusers.py +40 -0
- modelopt/torch/opt/plugins/huggingface.py +162 -0
- modelopt/torch/opt/plugins/mcore_dist_checkpointing.py +211 -0
- modelopt/torch/opt/plugins/megatron.py +142 -0
- modelopt/torch/opt/plugins/peft.py +108 -0
- modelopt/torch/opt/plugins/transformers.py +160 -0
- modelopt/torch/opt/searcher.py +363 -0
- modelopt/torch/opt/utils.py +125 -0
- modelopt/torch/prune/__init__.py +26 -0
- modelopt/torch/prune/fastnas.py +376 -0
- modelopt/torch/prune/gradnas.py +349 -0
- modelopt/torch/prune/plugins/__init__.py +24 -0
- modelopt/torch/prune/plugins/mcore_minitron.py +263 -0
- modelopt/torch/prune/plugins/transformers.py +41 -0
- modelopt/torch/prune/pruning.py +211 -0
- modelopt/torch/quantization/__init__.py +26 -0
- modelopt/torch/quantization/algorithms.py +671 -0
- modelopt/torch/quantization/backends/__init__.py +20 -0
- modelopt/torch/quantization/backends/fp8_per_tensor_gemm.py +204 -0
- modelopt/torch/quantization/backends/gemm_registry.py +156 -0
- modelopt/torch/quantization/backends/nvfp4_gemm.py +201 -0
- modelopt/torch/quantization/backends/utils.py +28 -0
- modelopt/torch/quantization/calib/__init__.py +25 -0
- modelopt/torch/quantization/calib/bias.py +172 -0
- modelopt/torch/quantization/calib/calibrator.py +63 -0
- modelopt/torch/quantization/calib/histogram.py +434 -0
- modelopt/torch/quantization/calib/max.py +110 -0
- modelopt/torch/quantization/compress.py +227 -0
- modelopt/torch/quantization/config.py +1180 -0
- modelopt/torch/quantization/conversion.py +389 -0
- modelopt/torch/quantization/export_onnx.py +639 -0
- modelopt/torch/quantization/extensions.py +89 -0
- modelopt/torch/quantization/mode.py +428 -0
- modelopt/torch/quantization/model_calib.py +949 -0
- modelopt/torch/quantization/model_quant.py +477 -0
- modelopt/torch/quantization/nn/__init__.py +26 -0
- modelopt/torch/quantization/nn/functional.py +109 -0
- modelopt/torch/quantization/nn/modules/__init__.py +16 -0
- modelopt/torch/quantization/nn/modules/quant_activations.py +22 -0
- modelopt/torch/quantization/nn/modules/quant_batchnorm.py +24 -0
- modelopt/torch/quantization/nn/modules/quant_conv.py +123 -0
- modelopt/torch/quantization/nn/modules/quant_instancenorm.py +39 -0
- modelopt/torch/quantization/nn/modules/quant_linear.py +258 -0
- modelopt/torch/quantization/nn/modules/quant_module.py +211 -0
- modelopt/torch/quantization/nn/modules/quant_pooling.py +99 -0
- modelopt/torch/quantization/nn/modules/quant_rnn.py +527 -0
- modelopt/torch/quantization/nn/modules/tensor_quantizer.py +1293 -0
- modelopt/torch/quantization/plugins/__init__.py +73 -0
- modelopt/torch/quantization/plugins/accelerate.py +214 -0
- modelopt/torch/quantization/plugins/apex.py +52 -0
- modelopt/torch/quantization/plugins/attention.py +312 -0
- modelopt/torch/quantization/plugins/custom.py +192 -0
- modelopt/torch/quantization/plugins/diffusers.py +237 -0
- modelopt/torch/quantization/plugins/fairscale.py +45 -0
- modelopt/torch/quantization/plugins/huggingface.py +736 -0
- modelopt/torch/quantization/plugins/megatron.py +462 -0
- modelopt/torch/quantization/plugins/peft.py +148 -0
- modelopt/torch/quantization/plugins/transformer_engine.py +60 -0
- modelopt/torch/quantization/plugins/transformers.py +52 -0
- modelopt/torch/quantization/plugins/transformers_trainer.py +426 -0
- modelopt/torch/quantization/plugins/trl.py +27 -0
- modelopt/torch/quantization/plugins/vllm.py +199 -0
- modelopt/torch/quantization/qtensor/__init__.py +24 -0
- modelopt/torch/quantization/qtensor/base_qtensor.py +370 -0
- modelopt/torch/quantization/qtensor/fp8_tensor.py +151 -0
- modelopt/torch/quantization/qtensor/int4_tensor.py +130 -0
- modelopt/torch/quantization/qtensor/int8_tensor.py +124 -0
- modelopt/torch/quantization/qtensor/mxfp4_tensor.py +144 -0
- modelopt/torch/quantization/qtensor/nf4_tensor.py +199 -0
- modelopt/torch/quantization/qtensor/nvfp4_tensor.py +295 -0
- modelopt/torch/quantization/src/tensor_quant.cpp +77 -0
- modelopt/torch/quantization/src/tensor_quant.h +69 -0
- modelopt/torch/quantization/src/tensor_quant_fp8.cpp +48 -0
- modelopt/torch/quantization/src/tensor_quant_gpu.cu +361 -0
- modelopt/torch/quantization/src/tensor_quant_gpu_fp8.cu +122 -0
- modelopt/torch/quantization/src/tensor_quant_mx.cu +411 -0
- modelopt/torch/quantization/src/tensor_quant_mx.h +247 -0
- modelopt/torch/quantization/tensor_quant.py +816 -0
- modelopt/torch/quantization/triton/__init__.py +36 -0
- modelopt/torch/quantization/triton/fp4_kernel.py +303 -0
- modelopt/torch/quantization/utils.py +443 -0
- modelopt/torch/sparsity/__init__.py +19 -0
- modelopt/torch/sparsity/config.py +51 -0
- modelopt/torch/sparsity/magnitude.py +148 -0
- modelopt/torch/sparsity/mode.py +203 -0
- modelopt/torch/sparsity/module.py +87 -0
- modelopt/torch/sparsity/plugins/__init__.py +27 -0
- modelopt/torch/sparsity/plugins/megatron.py +93 -0
- modelopt/torch/sparsity/searcher.py +84 -0
- modelopt/torch/sparsity/sparsegpt.py +276 -0
- modelopt/torch/sparsity/sparsification.py +123 -0
- modelopt/torch/speculative/__init__.py +20 -0
- modelopt/torch/speculative/config.py +100 -0
- modelopt/torch/speculative/eagle/__init__.py +20 -0
- modelopt/torch/speculative/eagle/conversion.py +67 -0
- modelopt/torch/speculative/eagle/default_config.py +50 -0
- modelopt/torch/speculative/eagle/eagle_model.py +51 -0
- modelopt/torch/speculative/eagle/utils.py +91 -0
- modelopt/torch/speculative/medusa/__init__.py +19 -0
- modelopt/torch/speculative/medusa/conversion.py +59 -0
- modelopt/torch/speculative/medusa/medusa_model.py +32 -0
- modelopt/torch/speculative/mode.py +86 -0
- modelopt/torch/speculative/plugins/__init__.py +33 -0
- modelopt/torch/speculative/plugins/megatron_eagle.py +2119 -0
- modelopt/torch/speculative/plugins/megatron_medusa.py +312 -0
- modelopt/torch/speculative/plugins/transformers.py +1138 -0
- modelopt/torch/speculative/speculative_decoding.py +59 -0
- modelopt/torch/speculative/utils.py +364 -0
- modelopt/torch/trace/__init__.py +25 -0
- modelopt/torch/trace/analyzer.py +1368 -0
- modelopt/torch/trace/modules/__init__.py +19 -0
- modelopt/torch/trace/modules/concat.py +384 -0
- modelopt/torch/trace/modules/nn.py +153 -0
- modelopt/torch/trace/plugins/__init__.py +34 -0
- modelopt/torch/trace/plugins/megatron.py +36 -0
- modelopt/torch/trace/plugins/transformers.py +58 -0
- modelopt/torch/trace/symbols.py +545 -0
- modelopt/torch/trace/tracer.py +331 -0
- modelopt/torch/utils/__init__.py +28 -0
- modelopt/torch/utils/_pytree.py +133 -0
- modelopt/torch/utils/cpp_extension.py +91 -0
- modelopt/torch/utils/dataset_utils.py +484 -0
- modelopt/torch/utils/distributed.py +311 -0
- modelopt/torch/utils/graph.py +123 -0
- modelopt/torch/utils/image_processor.py +112 -0
- modelopt/torch/utils/import_utils.py +35 -0
- modelopt/torch/utils/list.py +54 -0
- modelopt/torch/utils/logging.py +127 -0
- modelopt/torch/utils/memory_monitor.py +146 -0
- modelopt/torch/utils/network.py +658 -0
- modelopt/torch/utils/perf.py +174 -0
- modelopt/torch/utils/plugins/__init__.py +27 -0
- modelopt/torch/utils/plugins/megatron_generate.py +299 -0
- modelopt/torch/utils/plugins/megatron_mmlu.py +152 -0
- modelopt/torch/utils/plugins/megatron_preprocess_data.py +200 -0
- modelopt/torch/utils/random.py +163 -0
- modelopt/torch/utils/speech_dataset_utils.py +134 -0
- modelopt/torch/utils/tensor.py +87 -0
- modelopt/torch/utils/vlm_dataset_utils.py +110 -0
- nvidia_modelopt-0.35.0.dist-info/METADATA +171 -0
- nvidia_modelopt-0.35.0.dist-info/RECORD +272 -0
- nvidia_modelopt-0.35.0.dist-info/WHEEL +5 -0
- nvidia_modelopt-0.35.0.dist-info/licenses/LICENSE +14 -0
- nvidia_modelopt-0.35.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
2
|
+
# SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
"""Entrypoints for AutoNAS mode."""
|
|
17
|
+
|
|
18
|
+
import copy
|
|
19
|
+
import hashlib
|
|
20
|
+
import json
|
|
21
|
+
import warnings
|
|
22
|
+
from abc import ABC, abstractmethod
|
|
23
|
+
from collections import defaultdict, deque
|
|
24
|
+
from functools import partial
|
|
25
|
+
from typing import Any
|
|
26
|
+
|
|
27
|
+
import torch
|
|
28
|
+
import torch.nn as nn
|
|
29
|
+
import tqdm
|
|
30
|
+
from pydantic import create_model
|
|
31
|
+
from torch.nn.modules.batchnorm import _BatchNorm
|
|
32
|
+
|
|
33
|
+
from modelopt.torch.opt.config import (
|
|
34
|
+
ModeloptBaseConfig,
|
|
35
|
+
ModeloptField,
|
|
36
|
+
get_kwargs_for_create_model_with_rules,
|
|
37
|
+
)
|
|
38
|
+
from modelopt.torch.opt.conversion import ApplyModeError, ModelLikeModule
|
|
39
|
+
from modelopt.torch.opt.mode import (
|
|
40
|
+
ConvertEntrypoint,
|
|
41
|
+
ConvertReturnType,
|
|
42
|
+
MetadataDict,
|
|
43
|
+
ModeDescriptor,
|
|
44
|
+
RestoreEntrypoint,
|
|
45
|
+
UpdateEntrypoint,
|
|
46
|
+
)
|
|
47
|
+
from modelopt.torch.opt.searcher import BaseSearcher, ForwardLoop, SearchConfig, SearchStateDict
|
|
48
|
+
from modelopt.torch.opt.utils import is_configurable
|
|
49
|
+
from modelopt.torch.utils import (
|
|
50
|
+
compare_dict,
|
|
51
|
+
get_model_attributes,
|
|
52
|
+
is_channels_last,
|
|
53
|
+
num2hrb,
|
|
54
|
+
random,
|
|
55
|
+
run_forward_loop,
|
|
56
|
+
stats,
|
|
57
|
+
torch_detach,
|
|
58
|
+
torch_to,
|
|
59
|
+
unwrap_model,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
from .algorithms import ConstraintsFunc, get_constraints_func
|
|
63
|
+
from .conversion import NASModeRegistry
|
|
64
|
+
from .patch import PatchData, PatchManager, _modelopt_eval_recursion_guard, prep_for_eval
|
|
65
|
+
from .registry import DMRegistry
|
|
66
|
+
from .search_space import SearchSpace, generate_search_space
|
|
67
|
+
from .utils import MODELOPT_BN_CALIB_ITERS, MODELOPT_QUEUE_MAXLEN, get_subnet_config, sample, select
|
|
68
|
+
|
|
69
|
+
__all__ = [
|
|
70
|
+
"AutoNASConfig",
|
|
71
|
+
"AutoNASModeDescriptor",
|
|
72
|
+
"AutoNASPatchManager",
|
|
73
|
+
"EvolveSearcher",
|
|
74
|
+
"ExportConfig",
|
|
75
|
+
"ExportModeDescriptor",
|
|
76
|
+
"IterativeSearcher",
|
|
77
|
+
"RandomSearcher",
|
|
78
|
+
"convert_autonas_searchspace",
|
|
79
|
+
"convert_searchspace",
|
|
80
|
+
"export_searchspace",
|
|
81
|
+
"restore_autonas_searchspace",
|
|
82
|
+
"restore_export",
|
|
83
|
+
"restore_searchspace",
|
|
84
|
+
"update_autonas_metadata",
|
|
85
|
+
]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _get_ratio_list():
|
|
89
|
+
return (0.5, 0.67, 1.0)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _conv_config():
|
|
93
|
+
return {
|
|
94
|
+
"channels_ratio": _get_ratio_list(),
|
|
95
|
+
"kernel_size": (),
|
|
96
|
+
"channel_divisor": 32,
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _norm_lin_config():
|
|
101
|
+
return {
|
|
102
|
+
"features_ratio": _get_ratio_list(),
|
|
103
|
+
"feature_divisor": 32,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
AutoNASConfig: type[ModeloptBaseConfig] = create_model(
|
|
108
|
+
"AutoNASConfig",
|
|
109
|
+
**get_kwargs_for_create_model_with_rules(
|
|
110
|
+
registry=DMRegistry,
|
|
111
|
+
default_rules={
|
|
112
|
+
"nn.Conv1d": _conv_config(),
|
|
113
|
+
"nn.Conv2d": _conv_config(),
|
|
114
|
+
"nn.Conv3d": _conv_config(),
|
|
115
|
+
"nn.ConvTranspose1d": _conv_config(),
|
|
116
|
+
"nn.ConvTranspose2d": _conv_config(),
|
|
117
|
+
"nn.ConvTranspose3d": _conv_config(),
|
|
118
|
+
"nn.Linear": _norm_lin_config(),
|
|
119
|
+
"nn.BatchNorm1d": _norm_lin_config(),
|
|
120
|
+
"nn.BatchNorm2d": _norm_lin_config(),
|
|
121
|
+
"nn.BatchNorm3d": _norm_lin_config(),
|
|
122
|
+
"nn.SyncBatchNorm": _norm_lin_config(),
|
|
123
|
+
"nn.InstanceNorm1d": _norm_lin_config(),
|
|
124
|
+
"nn.InstanceNorm2d": _norm_lin_config(),
|
|
125
|
+
"nn.InstanceNorm3d": _norm_lin_config(),
|
|
126
|
+
"nn.LayerNorm": _norm_lin_config(),
|
|
127
|
+
"nn.GroupNorm": {k: v for k, v in _conv_config().items() if k != "kernel_size"},
|
|
128
|
+
"nn.Sequential": {"min_depth": 0},
|
|
129
|
+
},
|
|
130
|
+
doc='Configuration for the ``"autonas"`` mode.',
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class ExportConfig(ModeloptBaseConfig):
|
|
136
|
+
"""Configuration for the export mode.
|
|
137
|
+
|
|
138
|
+
This mode is used to export a model after NAS search.
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
strict: bool = ModeloptField(
|
|
142
|
+
default=True,
|
|
143
|
+
title="Strict export",
|
|
144
|
+
description="Enforces that the subnet configuration must exactly match during export.",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
calib: bool = ModeloptField(
|
|
148
|
+
default=False,
|
|
149
|
+
title="Calibration",
|
|
150
|
+
description="Whether to calibrate the subnet before exporting.",
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class AutoNASPatchManager(PatchManager):
|
|
155
|
+
"""A class to handle the monkey patching of the model for automode."""
|
|
156
|
+
|
|
157
|
+
def _get_default_patch_data(self) -> PatchData:
|
|
158
|
+
"""Return the default patch data of the model."""
|
|
159
|
+
return {
|
|
160
|
+
"queue": deque(maxlen=MODELOPT_QUEUE_MAXLEN), # queue for reset_bn data batches cache
|
|
161
|
+
"fill": False, # force push to queue if set to True
|
|
162
|
+
"count": 0, # internal count for number of forward passes
|
|
163
|
+
"train_count_since_sample": MODELOPT_BN_CALIB_ITERS, # step count since last sample
|
|
164
|
+
"autosampled": False, # indicator whether current sub-net was auto-sampled
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
def _hook_post_eval(self, forward_loop: ForwardLoop | None = None) -> None:
|
|
168
|
+
"""Optional hook that is called after eval() (or train(False)) to calibrate the model."""
|
|
169
|
+
# get the model and patch_data reference
|
|
170
|
+
model = self._model
|
|
171
|
+
patch_data = self.patch_data
|
|
172
|
+
|
|
173
|
+
# automode-related attributes
|
|
174
|
+
fill = patch_data["fill"]
|
|
175
|
+
|
|
176
|
+
# BN calibration related in autonas/fastnas mode
|
|
177
|
+
if is_configurable(model):
|
|
178
|
+
# indicates that model is being trained in which case we want max sub-net for eval
|
|
179
|
+
if patch_data["autosampled"]:
|
|
180
|
+
sample(model, sample_func=max)
|
|
181
|
+
|
|
182
|
+
# check current count since last sample operation
|
|
183
|
+
count_since_sample = patch_data["train_count_since_sample"]
|
|
184
|
+
|
|
185
|
+
# logic to determine whether and how to calibrate BN statistics
|
|
186
|
+
queue = patch_data["queue"]
|
|
187
|
+
if forward_loop is None and count_since_sample < MODELOPT_BN_CALIB_ITERS:
|
|
188
|
+
if len(queue) < queue.maxlen and self._is_modelopt_queue_needed(model):
|
|
189
|
+
warnings.warn(
|
|
190
|
+
"modelopt data queue not filled! Inference results can be inaccurate."
|
|
191
|
+
)
|
|
192
|
+
else:
|
|
193
|
+
forward_loop = partial(
|
|
194
|
+
run_forward_loop,
|
|
195
|
+
data_loader=queue,
|
|
196
|
+
collect_func=lambda x: x,
|
|
197
|
+
)
|
|
198
|
+
elif forward_loop is not None:
|
|
199
|
+
# in this case we want to fill up the queue since new data is provided.
|
|
200
|
+
patch_data["fill"] = True
|
|
201
|
+
|
|
202
|
+
# call reset_bn in eval mode
|
|
203
|
+
if forward_loop is not None:
|
|
204
|
+
with _modelopt_eval_recursion_guard(model):
|
|
205
|
+
self._reset_bn(model, forward_loop)
|
|
206
|
+
patch_data["train_count_since_sample"] = MODELOPT_BN_CALIB_ITERS
|
|
207
|
+
|
|
208
|
+
# resetting modelopt_fill
|
|
209
|
+
patch_data["fill"] = fill
|
|
210
|
+
|
|
211
|
+
@staticmethod
|
|
212
|
+
def _is_modelopt_queue_needed(model) -> bool:
|
|
213
|
+
return any(isinstance(m, _BatchNorm) and m.track_running_stats for m in model.modules())
|
|
214
|
+
|
|
215
|
+
@classmethod
|
|
216
|
+
def _reset_bn(cls, model: nn.Module, forward_loop: ForwardLoop):
|
|
217
|
+
"""Calibrate BN statistics by the provided data loader.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
model: The model to be calibrated
|
|
221
|
+
forward_loop: A ``Callable`` that takes a model as input and runs a pre-defined forward
|
|
222
|
+
loop on it using data that is suitable for BN calibration.
|
|
223
|
+
"""
|
|
224
|
+
# skip if there is no batch normalization layer in the network that
|
|
225
|
+
# requires tracking running stats
|
|
226
|
+
if not cls._is_modelopt_queue_needed(model):
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
is_training = model.training
|
|
230
|
+
momentum = {}
|
|
231
|
+
|
|
232
|
+
# modify all batch norms to track cumulative moving average
|
|
233
|
+
for name, m in model.named_modules():
|
|
234
|
+
if isinstance(m, _BatchNorm):
|
|
235
|
+
m.reset_running_stats()
|
|
236
|
+
m.train()
|
|
237
|
+
momentum[name] = m.momentum
|
|
238
|
+
m.momentum = None
|
|
239
|
+
|
|
240
|
+
# do some forward passes to track BN statistics
|
|
241
|
+
with torch.no_grad():
|
|
242
|
+
forward_loop(model)
|
|
243
|
+
|
|
244
|
+
# reset network into its original state
|
|
245
|
+
model.train(is_training)
|
|
246
|
+
for name, m in model.named_modules():
|
|
247
|
+
if name in momentum:
|
|
248
|
+
m.momentum = momentum[name]
|
|
249
|
+
|
|
250
|
+
def _hook_pre_sample(self) -> None:
|
|
251
|
+
"""Optional hook to be called before sample-related operations (sample & select)."""
|
|
252
|
+
patch_data = self.patch_data_or_empty
|
|
253
|
+
patch_data["train_count_since_sample"] = 0
|
|
254
|
+
patch_data["autosampled"] = False
|
|
255
|
+
|
|
256
|
+
@property
|
|
257
|
+
def sample_during_training(self) -> bool:
|
|
258
|
+
"""Indicates whether we should sample a new subnet during training."""
|
|
259
|
+
return True
|
|
260
|
+
|
|
261
|
+
def _hook_pre_forward(self, *args, **kwargs) -> None:
|
|
262
|
+
"""Optional hook that is called before the original forward function is called."""
|
|
263
|
+
patch_data = self.patch_data
|
|
264
|
+
mod = self._model
|
|
265
|
+
if not (mod.training or patch_data["fill"]):
|
|
266
|
+
return
|
|
267
|
+
|
|
268
|
+
# run sample step and update metadata related to sampling
|
|
269
|
+
def process_data(data):
|
|
270
|
+
return torch_to(torch_detach(data), device="cpu", non_blocking=True)
|
|
271
|
+
|
|
272
|
+
if mod.training or patch_data["fill"]:
|
|
273
|
+
# sample new model configuration in training mode and indicate it was auto-sampled
|
|
274
|
+
if mod.training:
|
|
275
|
+
if self.sample_during_training:
|
|
276
|
+
sample(mod)
|
|
277
|
+
patch_data["autosampled"] = True
|
|
278
|
+
patch_data["train_count_since_sample"] += 1 # update train mode count
|
|
279
|
+
# push detached CPU copy of data to queue (once full only update periodically)
|
|
280
|
+
queue = patch_data["queue"]
|
|
281
|
+
if len(queue) < queue.maxlen or patch_data["count"] % 100 == 0 or patch_data["fill"]:
|
|
282
|
+
queue.append((*process_data(args), process_data(kwargs)))
|
|
283
|
+
patch_data["count"] += 1
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class IterativeSearcher(BaseSearcher, ABC):
|
|
287
|
+
"""Base class for iterative search algorithms."""
|
|
288
|
+
|
|
289
|
+
iter_num: int
|
|
290
|
+
num_satisfied: int
|
|
291
|
+
constraints_func: ConstraintsFunc
|
|
292
|
+
candidate: dict[str, Any]
|
|
293
|
+
best: SearchStateDict
|
|
294
|
+
samples: dict[str, Any]
|
|
295
|
+
history: dict[str, Any]
|
|
296
|
+
best_history: dict[str, Any]
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def default_search_config(self) -> SearchConfig:
|
|
300
|
+
"""Get the default config for the searcher."""
|
|
301
|
+
return {
|
|
302
|
+
**super().default_search_config,
|
|
303
|
+
"num_iters": 5000,
|
|
304
|
+
"max_iter_data_loader": 50, # plenty to calibrate BN statistics
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def default_state_dict(self) -> SearchStateDict:
|
|
309
|
+
"""Return default state dict."""
|
|
310
|
+
return {
|
|
311
|
+
"iter_num": 0,
|
|
312
|
+
"num_satisfied": 0,
|
|
313
|
+
"candidate": {},
|
|
314
|
+
"best": {"metric": -float("inf"), "constraints": None},
|
|
315
|
+
"samples": {},
|
|
316
|
+
"history": {"metric": [], "constraints": defaultdict(list)},
|
|
317
|
+
"best_history": {"iter_num": [], "candidates": []},
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
def sanitize_search_config(self, config: SearchConfig | None) -> SearchConfig:
|
|
321
|
+
"""Sanitize the search config dict."""
|
|
322
|
+
config = super().sanitize_search_config(config)
|
|
323
|
+
assert config["score_func"] is not None, "Please provide `score_func`!"
|
|
324
|
+
return config
|
|
325
|
+
|
|
326
|
+
def _sample(self) -> dict[str, Any]:
|
|
327
|
+
return {"config": sample(self.model)}
|
|
328
|
+
|
|
329
|
+
@abstractmethod
|
|
330
|
+
def sample(self) -> dict[str, Any]:
|
|
331
|
+
"""Sample and select new sub-net configuration and return configuration."""
|
|
332
|
+
raise NotImplementedError
|
|
333
|
+
|
|
334
|
+
def before_search(self) -> None:
|
|
335
|
+
"""Ensure that the model is actually configurable and ready for eval."""
|
|
336
|
+
super().before_search()
|
|
337
|
+
|
|
338
|
+
# Initialize the constraints functor
|
|
339
|
+
self.constraints_func = get_constraints_func(
|
|
340
|
+
self.model, self.constraints, self.dummy_input, deployment=self.deployment
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
# Set the model to max subnet and put model into eval mode with potential calibaration
|
|
344
|
+
sample(self.model, max)
|
|
345
|
+
prep_for_eval(self.model, self.forward_loop)
|
|
346
|
+
|
|
347
|
+
# check for configurability of the models; otherwise we don't need to run the search.
|
|
348
|
+
if not is_configurable(self.model):
|
|
349
|
+
warnings.warn(
|
|
350
|
+
"Provided model does not contain configurable hparams with multiple choices! "
|
|
351
|
+
"Running only one iteration."
|
|
352
|
+
)
|
|
353
|
+
self.config["num_iters"] = 1
|
|
354
|
+
|
|
355
|
+
# do a sanity check and profile the constraints
|
|
356
|
+
# This way we also fill up the interpolation table for latency with min, centroid, max!
|
|
357
|
+
from .algorithms import profile # TODO: hack until we refactor files
|
|
358
|
+
|
|
359
|
+
profile(
|
|
360
|
+
self.model,
|
|
361
|
+
constraints=self.constraints_func,
|
|
362
|
+
strict=True,
|
|
363
|
+
verbose=self.config["verbose"],
|
|
364
|
+
use_centroid=True,
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
def run_search(self) -> None:
|
|
368
|
+
"""Run iterative search loop."""
|
|
369
|
+
num_iters = self.config["num_iters"]
|
|
370
|
+
verbose = self.config["verbose"]
|
|
371
|
+
|
|
372
|
+
# run search loop
|
|
373
|
+
if verbose:
|
|
374
|
+
pbar = tqdm.trange(
|
|
375
|
+
self.iter_num,
|
|
376
|
+
num_iters,
|
|
377
|
+
initial=self.iter_num,
|
|
378
|
+
total=num_iters,
|
|
379
|
+
position=0,
|
|
380
|
+
leave=True,
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
for self.iter_num in range(self.iter_num, num_iters):
|
|
384
|
+
self.before_step()
|
|
385
|
+
self.run_step()
|
|
386
|
+
self.after_step()
|
|
387
|
+
|
|
388
|
+
if verbose:
|
|
389
|
+
info = {
|
|
390
|
+
"num_satisfied": self.num_satisfied,
|
|
391
|
+
"metric": self.candidate["metric"],
|
|
392
|
+
"constraints": self.candidate["constraints"],
|
|
393
|
+
"best_subnet_metric": self.best["metric"],
|
|
394
|
+
"best_subnet_constraints": self.best["constraints"],
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
# display the full stats only once a while
|
|
398
|
+
if len(self.history["metric"]) == 100:
|
|
399
|
+
info["metric/stats"] = stats(self.history["metric"])
|
|
400
|
+
info["constraints/stats"] = {
|
|
401
|
+
name: stats(vals) for name, vals in self.history["constraints"].items()
|
|
402
|
+
}
|
|
403
|
+
self.history["metric"].clear()
|
|
404
|
+
self.history["constraints"].clear()
|
|
405
|
+
|
|
406
|
+
def _recursive_format(obj, fmt):
|
|
407
|
+
if isinstance(obj, float):
|
|
408
|
+
return num2hrb(obj)
|
|
409
|
+
if isinstance(obj, dict):
|
|
410
|
+
return {k: _recursive_format(v, fmt) for k, v in obj.items()}
|
|
411
|
+
return obj
|
|
412
|
+
|
|
413
|
+
pbar.update()
|
|
414
|
+
info = _recursive_format(info, fmt="{:.4g}")
|
|
415
|
+
pbar.set_description(f"[num_satisfied] = {info['num_satisfied']}")
|
|
416
|
+
|
|
417
|
+
if self.early_stop():
|
|
418
|
+
break
|
|
419
|
+
|
|
420
|
+
if verbose:
|
|
421
|
+
pbar.close()
|
|
422
|
+
print(f"[best_subnet_constraints] = {info['best_subnet_constraints']}")
|
|
423
|
+
|
|
424
|
+
def after_search(self) -> None:
|
|
425
|
+
"""Select best model."""
|
|
426
|
+
super().after_search()
|
|
427
|
+
select(self.model, self.best["config"])
|
|
428
|
+
# if self.has_score:
|
|
429
|
+
# final_score = self.eval_score(silent=False)
|
|
430
|
+
# print(f"Final score for the searched model: {final_score}")
|
|
431
|
+
|
|
432
|
+
def before_step(self) -> None:
|
|
433
|
+
"""Run before each iterative step."""
|
|
434
|
+
|
|
435
|
+
def run_step(self) -> None:
|
|
436
|
+
"""The main routine of each iterative step."""
|
|
437
|
+
# sample and select the candidate
|
|
438
|
+
self.candidate = self.sample()
|
|
439
|
+
|
|
440
|
+
# obtain the independent config and hparams
|
|
441
|
+
# TODO: consider whether we want to eventually bring back "active_config" from omnimizer
|
|
442
|
+
# to improve the efficiency of the search and avoid redundant sampling. This was used to
|
|
443
|
+
# avoid testing two architectures that only differed in modules that were not active, i.e.,
|
|
444
|
+
# due to reduced depth.
|
|
445
|
+
independent_config = self._configurable_config(self.model)
|
|
446
|
+
|
|
447
|
+
# serialize and hash the candidate
|
|
448
|
+
buffer = json.dumps({"config": independent_config}, sort_keys=True)
|
|
449
|
+
ckey = hashlib.sha256(buffer.encode()).hexdigest()
|
|
450
|
+
|
|
451
|
+
if ckey not in self.samples:
|
|
452
|
+
# check constraints
|
|
453
|
+
self.candidate["is_satisfied"], self.candidate["constraints"] = self.constraints_func()
|
|
454
|
+
|
|
455
|
+
# evaluate the metric
|
|
456
|
+
if self.candidate["is_satisfied"]:
|
|
457
|
+
self.candidate["metric"] = self.eval_score()
|
|
458
|
+
else:
|
|
459
|
+
self.candidate["metric"] = -float("inf")
|
|
460
|
+
|
|
461
|
+
self.samples[ckey] = copy.deepcopy(self.candidate)
|
|
462
|
+
else:
|
|
463
|
+
self.candidate = copy.deepcopy(self.samples[ckey])
|
|
464
|
+
|
|
465
|
+
self.num_satisfied += int(self.candidate["is_satisfied"])
|
|
466
|
+
is_best = self.candidate["metric"] >= self.best["metric"] or self.iter_num == 0
|
|
467
|
+
self.candidate["is_best"] = is_best
|
|
468
|
+
|
|
469
|
+
# update the stats if satisfied
|
|
470
|
+
if self.candidate["is_satisfied"]:
|
|
471
|
+
self.history["metric"].append(self.candidate["metric"])
|
|
472
|
+
for name, val in self.candidate["constraints"].items():
|
|
473
|
+
self.history["constraints"][name].append(val)
|
|
474
|
+
|
|
475
|
+
# update the best if necessary
|
|
476
|
+
if is_best:
|
|
477
|
+
self.best = copy.deepcopy(self.candidate)
|
|
478
|
+
self.best_history["iter_num"].append(self.iter_num)
|
|
479
|
+
self.best_history["candidates"].append(self.best)
|
|
480
|
+
|
|
481
|
+
# save the state dict
|
|
482
|
+
if self.iter_num % 100 == 0 or self.iter_num == self.config["num_iters"] - 1 or is_best:
|
|
483
|
+
self.save_search_checkpoint()
|
|
484
|
+
|
|
485
|
+
def after_step(self) -> None:
|
|
486
|
+
"""Run after each iterative step."""
|
|
487
|
+
|
|
488
|
+
def early_stop(self) -> bool:
|
|
489
|
+
"""Check if we should early stop the search if possible."""
|
|
490
|
+
return False
|
|
491
|
+
|
|
492
|
+
def _configurable_config(self, model) -> dict[str, Any]:
|
|
493
|
+
"""Returns the config dict of the configurable hyperparameters of the model."""
|
|
494
|
+
return get_subnet_config(model, configurable=True)
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
class RandomSearcher(IterativeSearcher):
|
|
498
|
+
"""An iterative searcher that samples subnets randomly."""
|
|
499
|
+
|
|
500
|
+
def sample(self) -> dict[str, Any]:
|
|
501
|
+
"""Random sample new subset during each steo."""
|
|
502
|
+
return self._sample()
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
class EvolveSearcher(IterativeSearcher):
|
|
506
|
+
"""An iterative searcher that uses an evolutionary algorithm to optimize the subnet config."""
|
|
507
|
+
|
|
508
|
+
population: list[dict[str, Any]]
|
|
509
|
+
candidates: list[dict[str, Any]]
|
|
510
|
+
|
|
511
|
+
@property
|
|
512
|
+
def default_search_config(self) -> SearchConfig:
|
|
513
|
+
"""Default search config contains additional algorithm parameters."""
|
|
514
|
+
return {
|
|
515
|
+
**super().default_search_config,
|
|
516
|
+
"population_size": 100,
|
|
517
|
+
"candidate_size": 25,
|
|
518
|
+
"mutation_prob": 0.1,
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
@property
|
|
522
|
+
def default_state_dict(self) -> SearchStateDict:
|
|
523
|
+
"""Return default state dict."""
|
|
524
|
+
return {
|
|
525
|
+
**super().default_state_dict,
|
|
526
|
+
"population": [],
|
|
527
|
+
"candidates": [],
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
def _mutate(self, input: dict[str, Any]) -> dict[str, Any]:
|
|
531
|
+
output = self._sample()
|
|
532
|
+
# only considers independent hparams
|
|
533
|
+
output["config"] = self._configurable_config(self.model)
|
|
534
|
+
for var in output: # pylint: disable=C0206
|
|
535
|
+
for name in output[var]:
|
|
536
|
+
if random.random() > self.config["mutation_prob"]:
|
|
537
|
+
output[var][name] = input[var][name]
|
|
538
|
+
# returns reduced, independent config
|
|
539
|
+
return output
|
|
540
|
+
|
|
541
|
+
def _crossover(self, inputs: list[dict[str, Any]]) -> dict[str, Any]:
|
|
542
|
+
output = {"config": {}}
|
|
543
|
+
# only considers independent hparams
|
|
544
|
+
for name in self._configurable_config(self.model):
|
|
545
|
+
output["config"][name] = random.choice(inputs)["config"][name]
|
|
546
|
+
# returns reduced, independent config
|
|
547
|
+
return output
|
|
548
|
+
|
|
549
|
+
def sample(self) -> dict[str, Any]:
|
|
550
|
+
"""Sampling a new subnet involves random sampling, mutation, and crossover."""
|
|
551
|
+
if not self.candidates:
|
|
552
|
+
return self._sample()
|
|
553
|
+
if len(self.population) < self.config["population_size"] // 2:
|
|
554
|
+
output = self._mutate(random.choice(self.candidates))
|
|
555
|
+
else:
|
|
556
|
+
output = self._crossover(random.sample(self.candidates, 2))
|
|
557
|
+
# returns full config
|
|
558
|
+
select(self.model, output["config"])
|
|
559
|
+
output["config"] = get_subnet_config(self.model)
|
|
560
|
+
return output
|
|
561
|
+
|
|
562
|
+
def before_search(self) -> None:
|
|
563
|
+
"""Set the lower bound of the constraints to 0.85 * upper bound before search."""
|
|
564
|
+
super().before_search()
|
|
565
|
+
self.constraints_func.set_rel_lower_bounds(rel_lower=0.85)
|
|
566
|
+
|
|
567
|
+
def before_step(self) -> None:
|
|
568
|
+
"""Update candidates and population before each iterative step."""
|
|
569
|
+
if len(self.population) >= self.config["population_size"]:
|
|
570
|
+
all_candidates = sorted(self.population, key=lambda x: x["metric"], reverse=True)
|
|
571
|
+
self.candidates = all_candidates[: self.config["candidate_size"]]
|
|
572
|
+
self.population = []
|
|
573
|
+
|
|
574
|
+
def after_step(self) -> None:
|
|
575
|
+
"""Update population after each iterative step."""
|
|
576
|
+
if self.candidate["is_satisfied"]:
|
|
577
|
+
self.population.append(copy.deepcopy(self.candidate))
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
def convert_searchspace(
|
|
581
|
+
model: nn.Module, config: ModeloptBaseConfig, patch_manager_type: type[PatchManager]
|
|
582
|
+
) -> ConvertReturnType:
|
|
583
|
+
"""Convert given model into a search space."""
|
|
584
|
+
# initialize the true module if necessary
|
|
585
|
+
model = model.init_modellike() if isinstance(model, ModelLikeModule) else model
|
|
586
|
+
|
|
587
|
+
# check if the model is in channels-last format
|
|
588
|
+
# NOTE: not supported because we have not implemented channel sorting for channels-last format
|
|
589
|
+
if is_channels_last(model):
|
|
590
|
+
raise RuntimeError(f"Channels-last format in {type(model).__name__} is not supported!")
|
|
591
|
+
|
|
592
|
+
# put together metadata before starting to modify model
|
|
593
|
+
metadata = {"model_attributes": get_model_attributes(model)}
|
|
594
|
+
|
|
595
|
+
# now convert the model to a search space
|
|
596
|
+
search_space = generate_search_space(model, rules=config.model_dump())
|
|
597
|
+
|
|
598
|
+
# sanity check if any module has been converted
|
|
599
|
+
if not search_space.is_configurable():
|
|
600
|
+
raise ApplyModeError(
|
|
601
|
+
"The model does not contain any configurable hyperparameters! Please check the"
|
|
602
|
+
" documentation for modules and config and how to get a configurable model."
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
# activate the max subnet that can be used for subsequent tasks like training
|
|
606
|
+
# (note it should be activated already but we do it here again to be sure)
|
|
607
|
+
sample(model, max)
|
|
608
|
+
|
|
609
|
+
# search space requires a patch
|
|
610
|
+
patch_manager_type(model).patch()
|
|
611
|
+
|
|
612
|
+
# get current config store in metadata
|
|
613
|
+
metadata["subnet_config"] = get_subnet_config(model)
|
|
614
|
+
|
|
615
|
+
# return converted model as well as metadata
|
|
616
|
+
return model, metadata
|
|
617
|
+
|
|
618
|
+
|
|
619
|
+
def convert_autonas_searchspace(model: nn.Module, config: ModeloptBaseConfig) -> ConvertReturnType:
|
|
620
|
+
"""Convert search space for AutoNAS mode with correct patch manager."""
|
|
621
|
+
return convert_searchspace(model, config, AutoNASPatchManager)
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def restore_autonas_searchspace(
|
|
625
|
+
model: nn.Module, config: ModeloptBaseConfig, metadata: MetadataDict
|
|
626
|
+
) -> nn.Module:
|
|
627
|
+
"""Restore search space for AutoNAS mode with correct patch manager."""
|
|
628
|
+
return restore_searchspace(model, config, metadata, AutoNASPatchManager)
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def restore_searchspace(
|
|
632
|
+
model: nn.Module,
|
|
633
|
+
config: ModeloptBaseConfig,
|
|
634
|
+
metadata: MetadataDict,
|
|
635
|
+
patch_manager: type[PatchManager],
|
|
636
|
+
) -> nn.Module:
|
|
637
|
+
"""Restore a search space from the given model."""
|
|
638
|
+
# retrieve metadata model attributes
|
|
639
|
+
model_attributes = metadata["model_attributes"]
|
|
640
|
+
|
|
641
|
+
# initialize the true module if necessary
|
|
642
|
+
model = model.init_modellike() if isinstance(model, ModelLikeModule) else model
|
|
643
|
+
|
|
644
|
+
# set up train/eval mode accordingly
|
|
645
|
+
is_training = model.training
|
|
646
|
+
model.train(model_attributes["training"])
|
|
647
|
+
|
|
648
|
+
# run regular convert entrypoint
|
|
649
|
+
model, metadata_new = convert_searchspace(model, config, patch_manager)
|
|
650
|
+
|
|
651
|
+
# compare new model attributes with provided model attributes
|
|
652
|
+
new_attributes = metadata_new["model_attributes"]
|
|
653
|
+
unmatched_keys = compare_dict(model_attributes, new_attributes)
|
|
654
|
+
if len(unmatched_keys) > 0:
|
|
655
|
+
error_msg = ["Following keys in your model do not match the checkpoint:"]
|
|
656
|
+
error_msg += [
|
|
657
|
+
f"{k}: Original={model_attributes.get(k, 'N/A')}; New={new_attributes.get(k, 'N/A')}"
|
|
658
|
+
for k in unmatched_keys
|
|
659
|
+
]
|
|
660
|
+
raise ApplyModeError("\n\t".join(error_msg))
|
|
661
|
+
|
|
662
|
+
# ensure model is back in mode it started out in
|
|
663
|
+
model.train(is_training)
|
|
664
|
+
|
|
665
|
+
# select subnet config stored in metadata
|
|
666
|
+
select(model, metadata["subnet_config"])
|
|
667
|
+
|
|
668
|
+
# return converted model
|
|
669
|
+
return model
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def update_autonas_metadata(
|
|
673
|
+
model: nn.Module, config: ModeloptBaseConfig, metadata: MetadataDict
|
|
674
|
+
) -> None:
|
|
675
|
+
"""Update subnet config to current subnet config of model."""
|
|
676
|
+
metadata["subnet_config"] = get_subnet_config(model)
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
def export_searchspace(model: nn.Module, config: ExportConfig) -> ConvertReturnType:
|
|
680
|
+
"""Export a subnet configuration of the search space to a regular model."""
|
|
681
|
+
# sanity check to avoid DP/DDP here in the entrypoint
|
|
682
|
+
model = unwrap_model(model, raise_error=True)
|
|
683
|
+
|
|
684
|
+
# store config from model if we can find it for a future convert/restore process
|
|
685
|
+
subnet_config = get_subnet_config(model)
|
|
686
|
+
|
|
687
|
+
# Check for patching and calibration
|
|
688
|
+
if PatchManager.is_patched(model):
|
|
689
|
+
manager = PatchManager.get_manager(model)
|
|
690
|
+
if config.calib:
|
|
691
|
+
manager.call_post_eval()
|
|
692
|
+
manager.unpatch()
|
|
693
|
+
|
|
694
|
+
# export model in-place
|
|
695
|
+
model = SearchSpace(model).export()
|
|
696
|
+
|
|
697
|
+
# construct metadata
|
|
698
|
+
metadata = {
|
|
699
|
+
"subnet_config": subnet_config,
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
return model, metadata
|
|
703
|
+
|
|
704
|
+
|
|
705
|
+
def restore_export(model: nn.Module, config: ExportConfig, metadata: MetadataDict) -> nn.Module:
|
|
706
|
+
"""Restore & export the subnet configuration of the search space to a regular model."""
|
|
707
|
+
# select subnet config provided in metadata
|
|
708
|
+
select(model, metadata["subnet_config"], strict=config["strict"])
|
|
709
|
+
|
|
710
|
+
# run export
|
|
711
|
+
model, metadata_new = export_searchspace(model, config)
|
|
712
|
+
|
|
713
|
+
# double check metadata
|
|
714
|
+
unmatched_keys = compare_dict(metadata, metadata_new)
|
|
715
|
+
if unmatched_keys:
|
|
716
|
+
raise ApplyModeError(f"Unmatched metadata={unmatched_keys}!")
|
|
717
|
+
|
|
718
|
+
return model
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
@NASModeRegistry.register_mode
|
|
722
|
+
class AutoNASModeDescriptor(ModeDescriptor):
|
|
723
|
+
"""Class to describe the ``"autonas"`` mode.
|
|
724
|
+
|
|
725
|
+
The properties of this mode can be inspected via the source code.
|
|
726
|
+
"""
|
|
727
|
+
|
|
728
|
+
@property
|
|
729
|
+
def name(self) -> str:
|
|
730
|
+
"""Returns the value (str representation) of the mode."""
|
|
731
|
+
return "autonas"
|
|
732
|
+
|
|
733
|
+
@property
|
|
734
|
+
def config_class(self) -> type[ModeloptBaseConfig]:
|
|
735
|
+
"""Specifies the config class for the mode."""
|
|
736
|
+
return AutoNASConfig
|
|
737
|
+
|
|
738
|
+
@property
|
|
739
|
+
def next_modes(self) -> set[str] | None:
|
|
740
|
+
"""Modes that must immediately follow this mode."""
|
|
741
|
+
return {"export", "kd_loss", "quantize", "sparse_magnitude", "sparse_gpt"}
|
|
742
|
+
|
|
743
|
+
@property
|
|
744
|
+
def export_mode(self) -> str | None:
|
|
745
|
+
"""The mode that corresponds to the export mode of this mode."""
|
|
746
|
+
return "export"
|
|
747
|
+
|
|
748
|
+
@property
|
|
749
|
+
def search_algorithm(self) -> type[BaseSearcher]:
|
|
750
|
+
"""Specifies the search algorithm to use for this mode (if any)."""
|
|
751
|
+
return EvolveSearcher
|
|
752
|
+
|
|
753
|
+
@property
|
|
754
|
+
def convert(self) -> ConvertEntrypoint:
|
|
755
|
+
"""The mode's entrypoint for converting a model."""
|
|
756
|
+
return convert_autonas_searchspace
|
|
757
|
+
|
|
758
|
+
@property
|
|
759
|
+
def restore(self) -> RestoreEntrypoint:
|
|
760
|
+
"""The mode's entrypoint for restoring a model."""
|
|
761
|
+
return restore_autonas_searchspace
|
|
762
|
+
|
|
763
|
+
@property
|
|
764
|
+
def update_for_save(self) -> UpdateEntrypoint:
|
|
765
|
+
"""The mode's entrypoint for updating the models state before saving."""
|
|
766
|
+
return update_autonas_metadata
|
|
767
|
+
|
|
768
|
+
@property
|
|
769
|
+
def update_for_new_mode(self) -> UpdateEntrypoint:
|
|
770
|
+
"""The mode's entrypoint for updating the models state before new mode."""
|
|
771
|
+
return update_autonas_metadata
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
@NASModeRegistry.register_mode
|
|
775
|
+
class ExportModeDescriptor(ModeDescriptor):
|
|
776
|
+
"""Class to describe the ``"export"`` mode.
|
|
777
|
+
|
|
778
|
+
The properties of this mode can be inspected via the source code.
|
|
779
|
+
"""
|
|
780
|
+
|
|
781
|
+
@property
|
|
782
|
+
def name(self) -> str:
|
|
783
|
+
"""Returns the value (str representation) of the mode."""
|
|
784
|
+
return "export"
|
|
785
|
+
|
|
786
|
+
@property
|
|
787
|
+
def config_class(self) -> type[ModeloptBaseConfig]:
|
|
788
|
+
"""Specifies the config class for the mode."""
|
|
789
|
+
return ExportConfig
|
|
790
|
+
|
|
791
|
+
@property
|
|
792
|
+
def is_export_mode(self) -> bool:
|
|
793
|
+
"""Whether the mode is an export mode.
|
|
794
|
+
|
|
795
|
+
Returns:
|
|
796
|
+
True if the mode is an export mode, False otherwise. Defaults to False.
|
|
797
|
+
"""
|
|
798
|
+
return True
|
|
799
|
+
|
|
800
|
+
@property
|
|
801
|
+
def convert(self) -> ConvertEntrypoint:
|
|
802
|
+
"""The mode's entrypoint for converting a model."""
|
|
803
|
+
return export_searchspace
|
|
804
|
+
|
|
805
|
+
@property
|
|
806
|
+
def restore(self) -> RestoreEntrypoint:
|
|
807
|
+
"""The mode's entrypoint for restoring a model."""
|
|
808
|
+
return restore_export
|