diff --git a/README.md b/README.md index 86b83c599a..c3777bd416 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ NNI (Neural Network Intelligence) is a toolkit to help users run automated machi The tool dispatches and runs trial jobs generated by tuning algorithms to search the best neural architecture and/or hyper-parameters in different environments like local machine, remote servers and cloud. -### **NNI [v1.0](https://github.com/Microsoft/nni/blob/master/docs/en_US/Release_v1.0.md) has been released!  ** +### **NNI v1.1 has been released!  **

@@ -211,7 +211,7 @@ Linux and MacOS * Run the following commands in an environment that has `python >= 3.5`, `git` and `wget`. ```bash - git clone -b v1.0 https://github.com/Microsoft/nni.git + git clone -b v1.1 https://github.com/Microsoft/nni.git cd nni source install.sh ``` @@ -221,7 +221,7 @@ Windows * Run the following commands in an environment that has `python >=3.5`, `git` and `PowerShell` ```bash - git clone -b v1.0 https://github.com/Microsoft/nni.git + git clone -b v1.1 https://github.com/Microsoft/nni.git cd nni powershell -ExecutionPolicy Bypass -file install.ps1 ``` @@ -232,12 +232,12 @@ For NNI on Windows, please refer to [NNI on Windows](docs/en_US/Tutorial/NniOnWi **Verify install** -The following example is an experiment built on TensorFlow. Make sure you have **TensorFlow installed** before running it. +The following example is an experiment built on TensorFlow. Make sure you have **TensorFlow 1.x installed** before running it. Note that **currently Tensorflow 2.0 is NOT supported**. * Download the examples via clone the source code. ```bash - git clone -b v1.0 https://github.com/Microsoft/nni.git + git clone -b v1.1 https://github.com/Microsoft/nni.git ``` Linux and MacOS diff --git a/deployment/pypi/setup.py b/deployment/pypi/setup.py index 936f4dc7ff..57328b98f6 100644 --- a/deployment/pypi/setup.py +++ b/deployment/pypi/setup.py @@ -73,7 +73,7 @@ 'requests', 'astor', 'PythonWebHDFS', - 'hyperopt', + 'hyperopt==0.1.2', 'json_tricks', 'numpy', 'scipy', diff --git a/docs/en_US/Compressor/AutoCompression.md b/docs/en_US/Compressor/AutoCompression.md index fc24f17211..424ac9957b 100644 --- a/docs/en_US/Compressor/AutoCompression.md +++ b/docs/en_US/Compressor/AutoCompression.md @@ -1,3 +1,118 @@ # Automatic Model Compression on NNI -TBD. \ No newline at end of file +It's convenient to implement auto model compression with NNI compression and NNI tuners + +## First, model compression with NNI + +You can easily compress a model with NNI compression. Take pruning for example, you can prune a pretrained model with LevelPruner like this + +```python +from nni.compression.torch import LevelPruner +config_list = [{ 'sparsity': 0.8, 'op_types': 'default' }] +pruner = LevelPruner(config_list) +pruner(model) +``` + +```{ 'sparsity': 0.8, 'op_types': 'default' }```means that **all layers with weight will be compressed with the same 0.8 sparsity**. When ```pruner(model)``` called, the model is compressed with masks and after that you can normally fine tune this model and **pruned weights won't be updated** which have been masked. + +## Then, make this automatic + +The previous example manually choosed LevelPruner and pruned all layers with the same sparsity, this is obviously sub-optimal because different layers may have different redundancy. Layer sparsity should be carefully tuned to achieve least model performance degradation and this can be done with NNI tuners. + +The first thing we need to do is to design a search space, here we use a nested search space which contains choosing pruning algorithm and optimizing layer sparsity. + +```json +{ + "prune_method": { + "_type": "choice", + "_value": [ + { + "_name": "agp", + "conv0_sparsity": { + "_type": "uniform", + "_value": [ + 0.1, + 0.9 + ] + }, + "conv1_sparsity": { + "_type": "uniform", + "_value": [ + 0.1, + 0.9 + ] + }, + }, + { + "_name": "level", + "conv0_sparsity": { + "_type": "uniform", + "_value": [ + 0.1, + 0.9 + ] + }, + "conv1_sparsity": { + "_type": "uniform", + "_value": [ + 0.01, + 0.9 + ] + }, + } + ] + } +} +``` + +Then we need to modify our codes for few lines + +```python +import nni +from nni.compression.torch import * +params = nni.get_parameters() +conv0_sparsity = params['prune_method']['conv0_sparsity'] +conv1_sparsity = params['prune_method']['conv1_sparsity'] +# these raw sparsity should be scaled if you need total sparsity constrained +config_list_level = [{ 'sparsity': conv0_sparsity, 'op_name': 'conv0' }, + { 'sparsity': conv1_sparsity, 'op_name': 'conv1' }] +config_list_agp = [{'initial_sparsity': 0, 'final_sparsity': conv0_sparsity, + 'start_epoch': 0, 'end_epoch': 3, + 'frequency': 1,'op_name': 'conv0' }, + {'initial_sparsity': 0, 'final_sparsity': conv1_sparsity, + 'start_epoch': 0, 'end_epoch': 3, + 'frequency': 1,'op_name': 'conv1' },] +PRUNERS = {'level':LevelPruner(config_list_level),'agp':AGP_Pruner(config_list_agp)} +pruner = PRUNERS(params['prune_method']['_name']) +pruner(model) +... # fine tuning +acc = evaluate(model) # evaluation +nni.report_final_results(acc) +``` + +Last, define our task and automatically tuning pruning methods with layers sparsity + +```yaml +authorName: default +experimentName: Auto_Compression +trialConcurrency: 2 +maxExecDuration: 100h +maxTrialNum: 500 +#choice: local, remote, pai +trainingServicePlatform: local +#choice: true, false +useAnnotation: False +searchSpacePath: search_space.json +tuner: + #choice: TPE, Random, Anneal... + builtinTunerName: TPE + classArgs: + #choice: maximize, minimize + optimize_mode: maximize +trial: + command: bash run_prune.sh + codeDir: . + gpuNum: 1 + +``` + diff --git a/docs/en_US/Compressor/Overview.md b/docs/en_US/Compressor/Overview.md index e3b0fb7c13..6fa4777bfb 100644 --- a/docs/en_US/Compressor/Overview.md +++ b/docs/en_US/Compressor/Overview.md @@ -1,14 +1,16 @@ # Compressor + +We are glad to announce the alpha release for model compression toolkit on top of NNI, it's still in the experiment phase which might evolve based on usage feedback. We'd like to invite you to use, feedback and even contribute. + NNI provides an easy-to-use toolkit to help user design and use compression algorithms. It supports Tensorflow and PyTorch with unified interface. For users to compress their models, they only need to add several lines in their code. There are some popular model compression algorithms built-in in NNI. Users could further use NNI's auto tuning power to find the best compressed model, which is detailed in [Auto Model Compression](./AutoCompression.md). On the other hand, users could easily customize their new compression algorithms using NNI's interface, refer to the tutorial [here](#customize-new-compression-algorithms). ## Supported algorithms -We have provided two naive compression algorithms and four popular ones for users, including three pruning algorithms and three quantization algorithms: +We have provided two naive compression algorithms and three popular ones for users, including two pruning algorithms and three quantization algorithms: |Name|Brief Introduction of Algorithm| |---|---| | [Level Pruner](./Pruner.md#level-pruner) | Pruning the specified ratio on each weight based on absolute values of weights | | [AGP Pruner](./Pruner.md#agp-pruner) | Automated gradual pruning (To prune, or not to prune: exploring the efficacy of pruning for model compression) [Reference Paper](https://arxiv.org/abs/1710.01878)| -| [Sensitivity Pruner](./Pruner.md#sensitivity-pruner) | Learning both Weights and Connections for Efficient Neural Networks. [Reference Paper](https://arxiv.org/abs/1506.02626)| | [Naive Quantizer](./Quantizer.md#naive-quantizer) | Quantize weights to default 8 bits | | [QAT Quantizer](./Quantizer.md#qat-quantizer) | Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference. [Reference Paper](http://openaccess.thecvf.com/content_cvpr_2018/papers/Jacob_Quantization_and_Training_CVPR_2018_paper.pdf)| | [DoReFa Quantizer](./Quantizer.md#dorefa-quantizer) | DoReFa-Net: Training Low Bitwidth Convolutional Neural Networks with Low Bitwidth Gradients. [Reference Paper](https://arxiv.org/abs/1606.06160)| diff --git a/docs/en_US/Compressor/Pruner.md b/docs/en_US/Compressor/Pruner.md index c6c74efd8a..c3a2adb720 100644 --- a/docs/en_US/Compressor/Pruner.md +++ b/docs/en_US/Compressor/Pruner.md @@ -48,7 +48,7 @@ from nni.compression.tensorflow import AGP_Pruner config_list = [{ 'initial_sparsity': 0, 'final_sparsity': 0.8, - 'start_epoch': 1, + 'start_epoch': 0, 'end_epoch': 10, 'frequency': 1, 'op_types': 'default' @@ -62,7 +62,7 @@ from nni.compression.torch import AGP_Pruner config_list = [{ 'initial_sparsity': 0, 'final_sparsity': 0.8, - 'start_epoch': 1, + 'start_epoch': 0, 'end_epoch': 10, 'frequency': 1, 'op_types': 'default' @@ -86,47 +86,9 @@ You can view example for more information #### User configuration for AGP Pruner * **initial_sparsity:** This is to specify the sparsity when compressor starts to compress * **final_sparsity:** This is to specify the sparsity when compressor finishes to compress -* **start_epoch:** This is to specify the epoch number when compressor starts to compress +* **start_epoch:** This is to specify the epoch number when compressor starts to compress, default start from epoch 0 * **end_epoch:** This is to specify the epoch number when compressor finishes to compress -* **frequency:** This is to specify every *frequency* number epochs compressor compress once +* **frequency:** This is to specify every *frequency* number epochs compressor compress once, default frequency=1 *** -## Sensitivity Pruner -In [Learning both Weights and Connections for Efficient Neural Networks](https://arxiv.org/abs/1506.02626), author Song Han and provide an algorithm to find the sensitivity of each layer and set the pruning threshold to each layer. - ->We used the sensitivity results to find each layer’s threshold: for example, the smallest threshold was applied to the most sensitive layer, which is the first convolutional layer... The pruning threshold is chosen as a quality parameter multiplied by the standard deviation of a layer’s weights - -### Usage -You can prune weight step by step and reach one target sparsity by Sensitivity Pruner with the code below. - -Tensorflow code -```python -from nni.compression.tensorflow import SensitivityPruner -config_list = [{ 'sparsity':0.8, 'op_types': 'default' }] -pruner = SensitivityPruner(config_list) -pruner(tf.get_default_graph()) -``` -PyTorch code -```python -from nni.compression.torch import SensitivityPruner -config_list = [{ 'sparsity':0.8, 'op_types': 'default' }] -pruner = SensitivityPruner(config_list) -pruner(model) -``` -Like AGP Pruner, you should update mask information every epoch by adding code below - -Tensorflow code -```python -pruner.update_epoch(epoch, sess) -``` -PyTorch code -```python -pruner.update_epoch(epoch) -``` -You can view example for more information - -#### User configuration for Sensitivity Pruner -* **sparsity:** This is to specify the sparsity operations to be compressed to - -*** diff --git a/docs/en_US/TrialExample/RocksdbExamples.md b/docs/en_US/TrialExample/RocksdbExamples.md new file mode 100644 index 0000000000..9423c1e152 --- /dev/null +++ b/docs/en_US/TrialExample/RocksdbExamples.md @@ -0,0 +1,97 @@ +# Tuning RocksDB on NNI + +## Overview + +[RocksDB](https://github.com/facebook/rocksdb) is a popular high performance embedded key-value database used in production systems at various web-scale enterprises including Facebook, Yahoo!, and LinkedIn.. It is a fork of [LevelDB](https://github.com/google/leveldb) by Facebook optimized to exploit many central processing unit (CPU) cores, and make efficient use of fast storage, such as solid-state drives (SSD), for input/output (I/O) bound workloads. + +The performance of RocksDB is highly contingent on its tuning. However, because of the complexity of its underlying technology and a large number of configurable parameters, a good configuration is sometimes hard to obtain. NNI can help to address this issue. NNI supports many kinds of tuning algorithms to search the best configuration of RocksDB, and support many kinds of environments like local machine, remote servers and cloud. + +This example illustrates how to use NNI to search the best configuration of RocksDB for a `fillrandom` benchmark supported by a benchmark tool `db_bench`, which is an official benchmark tool provided by RocksDB itself. Therefore, before running this example, please make sure NNI is installed and [`db_bench`](https://github.com/facebook/rocksdb/wiki/Benchmarking-tools) is in your `PATH`. Please refer to [here](../Tutorial/QuickStart.md) for detailed information about installation and preparing of NNI environment, and [here](https://github.com/facebook/rocksdb/blob/master/INSTALL.md) for compiling RocksDB as well as `db_bench`. + +We also provide a simple script [`db_bench_installation.sh`](../../../examples/trials/systems/rocksdb-fillrandom/db_bench_installation.sh) helping to compile and install `db_bench` as well as its dependencies on Ubuntu. Installing RocksDB on other systems can follow the same procedure. + +*code directory: [`example/trials/systems/rocksdb-fillrandom`](../../../examples/trials/systems/rocksdb-fillrandom)* + +## Experiment setup + +There are mainly three steps to setup an experiment of tuning systems on NNI. Define search space with a `json` file, write a benchmark code, and start NNI experiment by passing a config file to NNI manager. + +### Search Space + +For simplicity, this example tunes three parameters, `write_buffer_size`, `min_write_buffer_num` and `level0_file_num_compaction_trigger`, for writing 16M keys with 20 Bytes of key size and 100 Bytes of value size randomly, based on writing operations per second (OPS). `write_buffer_size` sets the size of a single memtable. Once memtable exceeds this size, it is marked immutable and a new one is created. `min_write_buffer_num` is the minimum number of memtables to be merged before flushing to storage. Once the number of files in level 0 reaches `level0_file_num_compaction_trigger`, level 0 to level 1 compaction is triggered. + +In this example, the search space is specified by a `search_space.json` file as shown below. Detailed explanation of search space could be found [here](../Tutorial/SearchSpaceSpec.md). + +```json +{ + "write_buffer_size": { + "_type": "quniform", + "_value": [2097152, 16777216, 1048576] + }, + "min_write_buffer_number_to_merge": { + "_type": "quniform", + "_value": [2, 16, 1] + }, + "level0_file_num_compaction_trigger": { + "_type": "quniform", + "_value": [2, 16, 1] + } +} +``` + +*code directory: [`example/trials/systems/rocksdb-fillrandom/search_space.json`](../../../examples/trials/systems/rocksdb-fillrandom/search_space.json)* + +### Benchmark code + +Benchmark code should receive a configuration from NNI manager, and report the corresponding benchmark result back. Following NNI APIs are designed for this purpose. In this example, writing operations per second (OPS) is used as a performance metric. Please refer to [here](Trials.md) for detailed information. + +* Use `nni.get_next_parameter()` to get next system configuration. +* Use `nni.report_final_result(metric)` to report the benchmark result. + +*code directory: [`example/trials/systems/rocksdb-fillrandom/main.py`](../../../examples/trials/systems/rocksdb-fillrandom/main.py)* + +### Config file + +One could start a NNI experiment with a config file. A config file for NNI is a `yaml` file usually including experiment settings (`trialConcurrency`, `maxExecDuration`, `maxTrialNum`, `trial gpuNum`, etc.), platform settings (`trainingServicePlatform`, etc.), path settings (`searchSpacePath`, `trial codeDir`, etc.) and tuner settings (`tuner`, `tuner optimize_mode`, etc.). Please refer to [here](../Tutorial/QuickStart.md) for more information. + +Here is an example of tuning RocksDB with SMAC algorithm: + +*code directory: [`example/trials/systems/rocksdb-fillrandom/config_smac.yml`](../../../examples/trials/systems/rocksdb-fillrandom/config_smac.yml)* + +Here is an example of tuning RocksDB with TPE algorithm: + +*code directory: [`example/trials/systems/rocksdb-fillrandom/config_tpe.yml`](../../../examples/trials/systems/rocksdb-fillrandom/config_tpe.yml)* + +Other tuners can be easily adopted in the same way. Please refer to [here](../Tuner/BuiltinTuner.md) for more information. + +Finally, we could enter the example folder and start the experiment using following commands: + +```bash +# tuning RocksDB with SMAC tuner +nnictl create --config ./config_smac.yml +# tuning RocksDB with TPE tuner +nnictl create --config ./config_tpe.yml +``` + +## Experiment results + +We ran these two examples on the same machine with following details: + +* 16 * Intel(R) Xeon(R) CPU E5-2650 v2 @ 2.60GHz +* 465 GB of rotational hard drive with ext4 file system +* 128 GB of RAM +* Kernel version: 4.15.0-58-generic +* NNI version: v1.0-37-g1bd24577 +* RocksDB version: 6.4 +* RocksDB DEBUG_LEVEL: 0 + +The detailed experiment results are shown in the below figure. Horizontal axis is sequential order of trials. Vertical axis is the metric, write OPS in this example. Blue dots represent trials for tuning RocksDB with SMAC tuner, and orange dots stand for trials for tuning RocksDB with TPE tuner. + +![image](../../../examples/trials/systems/rocksdb-fillrandom/plot.png) + +Following table lists the best trials and corresponding parameters and metric obtained by the two tuners. Unsurprisingly, both of them found the same optimal configuration for `fillrandom` benchmark. + +| Tuner | Best trial | Best OPS | write_buffer_size | min_write_buffer_number_to_merge | level0_file_num_compaction_trigger | +| :---: | :--------: | :------: | :---------------: | :------------------------------: | :--------------------------------: | +| SMAC | 255 | 779289 | 2097152 | 7.0 | 7.0 | +| TPE | 169 | 761456 | 2097152 | 7.0 | 7.0 | diff --git a/docs/en_US/TrialExample/Trials.md b/docs/en_US/TrialExample/Trials.md index ccf6f87df1..ce1be47885 100644 --- a/docs/en_US/TrialExample/Trials.md +++ b/docs/en_US/TrialExample/Trials.md @@ -163,3 +163,4 @@ For more information, please refer to [HowToDebug](../Tutorial/HowToDebug.md) * [How to tune Scikit-learn on NNI](SklearnExamples.md) * [Automatic Model Architecture Search for Reading Comprehension.](SquadEvolutionExamples.md) * [Tuning GBDT on NNI](GbdtExample.md) +* [Tuning RocksDB on NNI](RocksdbExamples.md) diff --git a/docs/en_US/conf.py b/docs/en_US/conf.py index ce306245ad..b87f9db8eb 100644 --- a/docs/en_US/conf.py +++ b/docs/en_US/conf.py @@ -28,7 +28,7 @@ # The short X.Y version version = '' # The full version, including alpha/beta/rc tags -release = 'v1.0' +release = 'v1.1' # -- General configuration --------------------------------------------------- diff --git a/docs/zh_CN/TrialExample/RocksdbExamples.md b/docs/zh_CN/TrialExample/RocksdbExamples.md new file mode 100644 index 0000000000..a31d20a85b --- /dev/null +++ b/docs/zh_CN/TrialExample/RocksdbExamples.md @@ -0,0 +1,97 @@ +# 使用 NNI 调优 RocksDB + +## 概述 + +[RocksDB](https://github.com/facebook/rocksdb) 是一种很受欢迎的高性能嵌入式键值数据库,被许多公司,如 Facebook, Yahoo! 和 LinkedIn 等,广泛应用于各种网络规模的产品中。它是 Facebook 在 [LevelDB](https://github.com/google/leveldb) 的基础上,通过充分利用多核心中央处理器和快速存储器(如固态硬盘)的特点,针对IO密集型应用优化而成的。 + +RocksDB 的性能高度依赖于运行参数的调优。然而,由于其底层技术极为复杂,需要调整的参数过多,有时很难找到合适的运行参数。NNI 可以帮助数据库运维工程师解决这个问题。NNI 支持多种自动调参算法,并且支持运行于本地、远程和云端的各种负载。 + +本例展示了如何使用 NNI 搜索 RocksDB 在 `fillrandom` 基准测试中的最佳运行参数,`fillrandom` 基准测试是 RocksDB 官方提供的基准测试工具 `db_bench` 所支持的一种基准测试,因此在运行本例之前请确保您已经安装了 NNI,并且 `db_bench` 在您的 `PATH` 路径中。关于如何安装和准备 NNI 环境,请参考[这里](../Tuner/BuiltinTuner.md),关于如何编译 RocksDB 以及 `db_bench`,请参考[这里](https://github.com/facebook/rocksdb/blob/master/INSTALL.md)。 + +我们还提供了一个简单的脚本 [`db_bench_installation.sh`](../../../examples/trials/systems/rocksdb-fillrandom/db_bench_installation.sh),用来在 Ubuntu 系统上编译和安装 `db_bench` 和相关依赖。在其他系统中的安装也可以参考该脚本实现。 + +*代码目录: [`example/trials/systems/rocksdb-fillrandom`](../../../examples/trials/systems/rocksdb-fillrandom)* + +## 实验配置 + +使用 NNI 进行调优系统主要有三个步骤,分别是,使用一个 `json` 文件定义搜索空间;准备一个基准测试程序;和一个用来启动 NNI 实验的配置文件。 + +### 搜索空间 + +简便起见,本例基于 Rocks_DB 每秒的写入操作数(Operations Per Second, OPS),在随机写入 16M 个键长为 20 字节值长为 100 字节的键值对的情况下,对三个系统运行参数,`write_buffer_size`,`min_write_buffer_num` 和 `level0_file_num_compaction_trigger`,进行了调优。`write_buffer_size` 控制了单个 memtable 的大小。在写入过程中,当 memtable 的大小超过了 `write_buffer_size` 指定的数值,该 memtable 将会被标记为不可变,并创建一个新的 memtable。`min_write_buffer_num` 是在写入(flush)磁盘之前需要合并(merge)的 memtable 的最小数量。一旦 level 0 中的文件数量超过了 `level0_file_num_compaction_trigger` 所指定的数,level 0 向 level 1 的压缩(compaction)将会被触发。 + +搜索空间由如下所示的文件 `search_space.json` 指定。更多关于搜索空间的解释请参考[这里](../Tutorial/SearchSpaceSpec.md)。 + +```json +{ + "write_buffer_size": { + "_type": "quniform", + "_value": [2097152, 16777216, 1048576] + }, + "min_write_buffer_number_to_merge": { + "_type": "quniform", + "_value": [2, 16, 1] + }, + "level0_file_num_compaction_trigger": { + "_type": "quniform", + "_value": [2, 16, 1] + } +} +``` + +*代码目录: [`example/trials/systems/rocksdb-fillrandom/search_space.json`](../../../examples/trials/systems/rocksdb-fillrandom/search_space.json)* + +### 基准测试 + +基准测试程序需要从 NNI manager 接收一个运行参数,并在运行基准测试以后向 NNI manager 汇报基准测试结果。NNI 提供了下面两个 APIs 来完成这些任务。更多关于 NNI trials 的信息请参考[这里](Trials.md)。 + +* 使用 `nni.get_next_parameter()` 从 NNI manager 得到需要测试的系统运行参数。 +* 使用 `nni.report_final_result(metric)` 向 NNI manager 汇报基准测试的结果。 + +*代码目录: [`example/trials/systems/rocksdb-fillrandom/main.py`](../../../examples/trials/systems/rocksdb-fillrandom/main.py)* + +### 配置文件 + +NNI 实验可以通过配置文件来启动。通常而言,NNI 配置文件是一个 `yaml` 文件,通常包含实验设置(`trialConcurrency`,`maxExecDuration`,`maxTrialNum`,`trial gpuNum` 等),运行平台设置(`trainingServicePlatform` 等),路径设置(`searchSpacePath`,`trial codeDir` 等)和 调参器设置(`tuner`,`tuner optimize_mode` 等)。更多关于 NNI 配置文件的信息请参考[这里](../Tutorial/QuickStart.md)。 + +下面是使用 SMAC 算法调优 RocksDB 配置文件的例子: + +*代码目录: [`example/trials/systems/rocksdb-fillrandom/config_smac.yml`](../../../examples/trials/systems/rocksdb-fillrandom/config_smac.yml)* + +下面是使用 TPE 算法调优 RocksDB 配置文件的例子: + +*代码目录: [`example/trials/systems/rocksdb-fillrandom/config_tpe.yml`](../../../examples/trials/systems/rocksdb-fillrandom/config_tpe.yml)* + +其他的调参器可以使用同样的方式应用,更多关于调参器的信息请参考[这里](../Tuner/BuiltinTuner.md)。 + +最后,我们可以进入本例的文件夹内,用下面的命令启动实验: + +```bash +# tuning RocksDB with SMAC tuner +nnictl create --config ./config_smac.yml +# tuning RocksDB with TPE tuner +nnictl create --config ./config_tpe.yml +``` + +## 实验结果 + +我们在同一台机器上运行了这两个实验,相关信息如下: + +* 16 * Intel(R) Xeon(R) CPU E5-2650 v2 @ 2.60GHz +* 465 GB of rotational hard drive with ext4 file system +* 128 GB of RAM +* Kernel version: 4.15.0-58-generic +* NNI version: v1.0-37-g1bd24577 +* RocksDB version: 6.4 +* RocksDB DEBUG_LEVEL: 0 + +具体的实验结果如下图所示。横轴是基准测试的顺序,纵轴是基准测试得到的结果,在本例中是每秒钟写操作的次数。蓝色的圆点代表用 SMAC 调优 RocksDB 得到的基准测试结果,而橘黄色的圆点表示用 TPE 调优得到的基准测试结果。 + +![image](../../../examples/trials/systems/rocksdb-fillrandom/plot.png) + +下面的表格列出了使用两种调参器得到的最好的基准测试结果及相对应的参数。毫不意外,使用这两种调参器在 `fillrandom` 基准测试中搜索得到了相同的最优参数。 + +| Tuner | Best trial | Best OPS | write_buffer_size | min_write_buffer_number_to_merge | level0_file_num_compaction_trigger | +| :---: | :--------: | :------: | :---------------: | :------------------------------: | :--------------------------------: | +| SMAC | 255 | 779289 | 2097152 | 7.0 | 7.0 | +| TPE | 169 | 761456 | 2097152 | 7.0 | 7.0 | diff --git a/docs/zh_CN/TrialExample/Trials.md b/docs/zh_CN/TrialExample/Trials.md index 17285da413..6a2465764f 100644 --- a/docs/zh_CN/TrialExample/Trials.md +++ b/docs/zh_CN/TrialExample/Trials.md @@ -168,4 +168,5 @@ echo $? `date +%s%3N` >/home/user_name/nni/experiments/$experiment_id$/trials/$t * [为 CIFAR 10 分类找到最佳的 optimizer](Cifar10Examples.md) * [如何在 NNI 调优 SciKit-learn 的参数](SklearnExamples.md) * [在阅读理解上使用自动模型架构搜索。](SquadEvolutionExamples.md) -* [如何在 NNI 上调优 GBDT](GbdtExample.md) \ No newline at end of file +* [如何在 NNI 上调优 GBDT](GbdtExample.md) +* [如何在 NNI 上调优 RocksDB](RocksdbExamples.md) \ No newline at end of file diff --git a/docs/zh_CN/conf.py b/docs/zh_CN/conf.py index ce306245ad..b87f9db8eb 100644 --- a/docs/zh_CN/conf.py +++ b/docs/zh_CN/conf.py @@ -28,7 +28,7 @@ # The short X.Y version version = '' # The full version, including alpha/beta/rc tags -release = 'v1.0' +release = 'v1.1' # -- General configuration --------------------------------------------------- diff --git a/examples/model_compress/README.md b/examples/model_compress/README.md new file mode 100644 index 0000000000..c8742a854a --- /dev/null +++ b/examples/model_compress/README.md @@ -0,0 +1,48 @@ +# Run model compression examples + +You can run these examples easily like this, take torch pruning for example + +```bash +python main_torch_pruner.py +``` + +This example uses AGP Pruner. Initiating a pruner needs a user provided configuration which can be provided in two ways: + +- By reading ```configure_example.yaml```, this can make code clean when your configuration is complicated +- Directly config in your codes + +In our example, we simply config model compression in our codes like this + +```python +configure_list = [{ + 'initial_sparsity': 0, + 'final_sparsity': 0.8, + 'start_epoch': 0, + 'end_epoch': 10, + 'frequency': 1, + 'op_type': 'default' +}] +pruner = AGP_Pruner(configure_list) +``` + +When ```pruner(model)``` is called, your model is injected with masks as embedded operations. For example, a layer takes a weight as input, we will insert an operation between the weight and the layer, this operation takes the weight as input and outputs a new weight applied by the mask. Thus, the masks are applied at any time the computation goes through the operations. You can fine-tune your model **without** any modifications. + +```python +for epoch in range(10): + # update_epoch is for pruner to be aware of epochs, so that it could adjust masks during training. + pruner.update_epoch(epoch) + print('# Epoch {} #'.format(epoch)) + train(model, device, train_loader, optimizer) + test(model, device, test_loader) +``` + +When fine tuning finished, pruned weights are all masked and you can get masks like this + +``` +masks = pruner.mask_list +layer_name = xxx +mask = masks[layer_name] +``` + + + diff --git a/examples/model_compress/configure_example.yaml b/examples/model_compress/configure_example.yaml index b8bb59c339..0f856f0349 100644 --- a/examples/model_compress/configure_example.yaml +++ b/examples/model_compress/configure_example.yaml @@ -1,7 +1,7 @@ AGPruner: config: - - start_epoch: 1 + start_epoch: 0 end_epoch: 10 frequency: 1 initial_sparsity: 0.05 diff --git a/examples/model_compress/main_tf_pruner.py b/examples/model_compress/main_tf_pruner.py index b00a01925f..78faf4a1c2 100644 --- a/examples/model_compress/main_tf_pruner.py +++ b/examples/model_compress/main_tf_pruner.py @@ -4,23 +4,26 @@ def weight_variable(shape): - return tf.Variable(tf.truncated_normal(shape, stddev = 0.1)) + return tf.Variable(tf.truncated_normal(shape, stddev=0.1)) + def bias_variable(shape): - return tf.Variable(tf.constant(0.1, shape = shape)) + return tf.Variable(tf.constant(0.1, shape=shape)) + def conv2d(x_input, w_matrix): - return tf.nn.conv2d(x_input, w_matrix, strides = [ 1, 1, 1, 1 ], padding = 'SAME') + return tf.nn.conv2d(x_input, w_matrix, strides=[1, 1, 1, 1], padding='SAME') + def max_pool(x_input, pool_size): - size = [ 1, pool_size, pool_size, 1 ] - return tf.nn.max_pool(x_input, ksize = size, strides = size, padding = 'SAME') + size = [1, pool_size, pool_size, 1] + return tf.nn.max_pool(x_input, ksize=size, strides=size, padding='SAME') class Mnist: def __init__(self): - images = tf.placeholder(tf.float32, [ None, 784 ], name = 'input_x') - labels = tf.placeholder(tf.float32, [ None, 10 ], name = 'input_y') + images = tf.placeholder(tf.float32, [None, 784], name='input_x') + labels = tf.placeholder(tf.float32, [None, 10], name='input_y') keep_prob = tf.placeholder(tf.float32, name='keep_prob') self.images = images @@ -35,35 +38,35 @@ def __init__(self): self.fcw1 = None self.cross = None with tf.name_scope('reshape'): - x_image = tf.reshape(images, [ -1, 28, 28, 1 ]) + x_image = tf.reshape(images, [-1, 28, 28, 1]) with tf.name_scope('conv1'): - w_conv1 = weight_variable([ 5, 5, 1, 32 ]) + w_conv1 = weight_variable([5, 5, 1, 32]) self.w1 = w_conv1 - b_conv1 = bias_variable([ 32 ]) + b_conv1 = bias_variable([32]) self.b1 = b_conv1 h_conv1 = tf.nn.relu(conv2d(x_image, w_conv1) + b_conv1) with tf.name_scope('pool1'): h_pool1 = max_pool(h_conv1, 2) with tf.name_scope('conv2'): - w_conv2 = weight_variable([ 5, 5, 32, 64 ]) - b_conv2 = bias_variable([ 64 ]) + w_conv2 = weight_variable([5, 5, 32, 64]) + b_conv2 = bias_variable([64]) h_conv2 = tf.nn.relu(conv2d(h_pool1, w_conv2) + b_conv2) with tf.name_scope('pool2'): h_pool2 = max_pool(h_conv2, 2) with tf.name_scope('fc1'): - w_fc1 = weight_variable([ 7 * 7 * 64, 1024 ]) + w_fc1 = weight_variable([7 * 7 * 64, 1024]) self.fcw1 = w_fc1 - b_fc1 = bias_variable([ 1024 ]) - h_pool2_flat = tf.reshape(h_pool2, [ -1, 7 * 7 * 64 ]) + b_fc1 = bias_variable([1024]) + h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64]) h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, w_fc1) + b_fc1) with tf.name_scope('dropout'): h_fc1_drop = tf.nn.dropout(h_fc1, 0.5) with tf.name_scope('fc2'): - w_fc2 = weight_variable([ 1024, 10 ]) - b_fc2 = bias_variable([ 10 ]) + w_fc2 = weight_variable([1024, 10]) + b_fc2 = bias_variable([10]) y_conv = tf.matmul(h_fc1_drop, w_fc2) + b_fc2 with tf.name_scope('loss'): - cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels = labels, logits = y_conv)) + cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=labels, logits=y_conv)) self.cross = cross_entropy with tf.name_scope('adam_optimizer'): self.train_step = tf.train.AdamOptimizer(0.0001).minimize(cross_entropy) @@ -75,21 +78,21 @@ def __init__(self): def main(): tf.set_random_seed(0) - data = input_data.read_data_sets('data', one_hot = True) + data = input_data.read_data_sets('data', one_hot=True) model = Mnist() - '''you can change this to SensitivityPruner to implement it - pruner = SensitivityPruner(configure_list) + '''you can change this to LevelPruner to implement it + pruner = LevelPruner(configure_list) ''' configure_list = [{ - 'initial_sparsity': 0, - 'final_sparsity': 0.8, - 'start_epoch': 1, - 'end_epoch': 10, - 'frequency': 1, - 'op_type': 'default' - }] + 'initial_sparsity': 0, + 'final_sparsity': 0.8, + 'start_epoch': 0, + 'end_epoch': 10, + 'frequency': 1, + 'op_type': 'default' + }] pruner = AGP_Pruner(configure_list) # if you want to load from yaml file # configure_file = nni.compressors.tf_compressor._nnimc_tf._tf_default_load_configure_file('configure_example.yaml','AGPruner') @@ -99,28 +102,27 @@ def main(): pruner(tf.get_default_graph()) # you can also use compress(model) or compress_default_graph() for tensorflow compressor # pruner.compress(tf.get_default_graph()) - - + with tf.Session() as sess: sess.run(tf.global_variables_initializer()) for batch_idx in range(2000): + if batch_idx % 10 == 0: + pruner.update_epoch(batch_idx / 10, sess) batch = data.train.next_batch(2000) - model.train_step.run(feed_dict = { + model.train_step.run(feed_dict={ model.images: batch[0], model.labels: batch[1], model.keep_prob: 0.5 }) if batch_idx % 10 == 0: - test_acc = model.accuracy.eval(feed_dict = { + test_acc = model.accuracy.eval(feed_dict={ model.images: data.test.images, model.labels: data.test.labels, model.keep_prob: 1.0 }) - pruner.update_epoch(batch_idx / 10,sess) print('test accuracy', test_acc) - - - test_acc = model.accuracy.eval(feed_dict = { + + test_acc = model.accuracy.eval(feed_dict={ model.images: data.test.images, model.labels: data.test.labels, model.keep_prob: 1.0 diff --git a/examples/model_compress/main_torch_pruner.py b/examples/model_compress/main_torch_pruner.py index b8474a8a00..9fd12d8859 100644 --- a/examples/model_compress/main_torch_pruner.py +++ b/examples/model_compress/main_torch_pruner.py @@ -20,7 +20,7 @@ def forward(self, x): x = x.view(-1, 4 * 4 * 50) x = F.relu(self.fc1(x)) x = self.fc2(x) - return F.log_softmax(x, dim = 1) + return F.log_softmax(x, dim=1) def train(model, device, train_loader, optimizer): @@ -35,6 +35,7 @@ def train(model, device, train_loader, optimizer): if batch_idx % 100 == 0: print('{:2.0f}% Loss {}'.format(100 * batch_idx / len(train_loader), loss.item())) + def test(model, device, test_loader): model.eval() test_loss = 0 @@ -43,52 +44,52 @@ def test(model, device, test_loader): for data, target in test_loader: data, target = data.to(device), target.to(device) output = model(data) - test_loss += F.nll_loss(output, target, reduction = 'sum').item() - pred = output.argmax(dim = 1, keepdim = True) + test_loss += F.nll_loss(output, target, reduction='sum').item() + pred = output.argmax(dim=1, keepdim=True) correct += pred.eq(target.view_as(pred)).sum().item() test_loss /= len(test_loader.dataset) print('Loss: {} Accuracy: {}%)\n'.format( test_loss, 100 * correct / len(test_loader.dataset))) + def main(): torch.manual_seed(0) device = torch.device('cpu') trans = transforms.Compose([transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))]) train_loader = torch.utils.data.DataLoader( - datasets.MNIST('data', train = True, download = True, transform = trans), - batch_size = 64, shuffle = True) + datasets.MNIST('data', train=True, download=True, transform=trans), + batch_size=64, shuffle=True) test_loader = torch.utils.data.DataLoader( - datasets.MNIST('data', train = False, transform = trans), - batch_size = 1000, shuffle = True) + datasets.MNIST('data', train=False, transform=trans), + batch_size=1000, shuffle=True) model = Mnist() - - '''you can change this to SensitivityPruner to implement it - pruner = SensitivityPruner(configure_list) + + '''you can change this to LevelPruner to implement it + pruner = LevelPruner(configure_list) ''' configure_list = [{ - 'initial_sparsity': 0, - 'final_sparsity': 0.8, - 'start_epoch': 1, - 'end_epoch': 10, - 'frequency': 1, - 'op_type': 'default' - }] + 'initial_sparsity': 0, + 'final_sparsity': 0.8, + 'start_epoch': 0, + 'end_epoch': 10, + 'frequency': 1, + 'op_type': 'default' + }] pruner = AGP_Pruner(configure_list) pruner(model) # you can also use compress(model) method # like that pruner.compress(model) - optimizer = torch.optim.SGD(model.parameters(), lr = 0.01, momentum = 0.5) + optimizer = torch.optim.SGD(model.parameters(), lr=0.01, momentum=0.5) for epoch in range(10): + pruner.update_epoch(epoch) print('# Epoch {} #'.format(epoch)) train(model, device, train_loader, optimizer) test(model, device, test_loader) - - pruner.update_epoch(epoch) if __name__ == '__main__': diff --git a/examples/trials/nas_cifar10/README.md b/examples/trials/nas_cifar10/README.md index e6f03e0b58..3c002ab33b 100644 --- a/examples/trials/nas_cifar10/README.md +++ b/examples/trials/nas_cifar10/README.md @@ -1,15 +1,10 @@ - **Run Neural Network Architecture Search in NNI** - === +# Run Neural Architecture Search in NNI Now we have an NAS example [NNI-NAS-Example](https://github.com/Crysple/NNI-NAS-Example) run in NNI using NAS interface from our contributors. We have included its trial code in this folder, and provided example config files to show how to use PPO tuner to tune the trial code. -> Download data +To prepare for the dataset, please run `cd data && . download.sh`. + +Thanks our lovely contributors, and welcome more and more people to join us! -- `cd data && . download.sh` -- `tar xzf cifar-10-python.tar.gz && mv cifar-batches cifar10` - -Thanks our lovely contributors. - -And welcome more and more people to join us! diff --git a/examples/trials/nas_cifar10/data/download.sh b/examples/trials/nas_cifar10/data/download.sh index f00ac25724..08c7256424 100755 --- a/examples/trials/nas_cifar10/data/download.sh +++ b/examples/trials/nas_cifar10/data/download.sh @@ -1 +1,2 @@ wget https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz +tar xzf cifar-10-python.tar.gz && mv cifar-10-batches-py cifar10 \ No newline at end of file diff --git a/examples/trials/sklearn/classification/main.py b/examples/trials/sklearn/classification/main.py index b6b394e5e9..280dc26fb8 100644 --- a/examples/trials/sklearn/classification/main.py +++ b/examples/trials/sklearn/classification/main.py @@ -41,7 +41,7 @@ def get_default_parameters(): '''get default parameters''' params = { 'C': 1.0, - 'keral': 'linear', + 'kernel': 'linear', 'degree': 3, 'gamma': 0.01, 'coef0': 0.01 @@ -52,7 +52,7 @@ def get_model(PARAMS): '''Get model according to parameters''' model = SVC() model.C = PARAMS.get('C') - model.keral = PARAMS.get('keral') + model.kernel = PARAMS.get('kernel') model.degree = PARAMS.get('degree') model.gamma = PARAMS.get('gamma') model.coef0 = PARAMS.get('coef0') diff --git a/examples/trials/sklearn/classification/search_space.json b/examples/trials/sklearn/classification/search_space.json index 1687a93ccc..f63e07bda6 100644 --- a/examples/trials/sklearn/classification/search_space.json +++ b/examples/trials/sklearn/classification/search_space.json @@ -1,6 +1,6 @@ { "C": {"_type":"uniform","_value":[0.1, 1]}, - "keral": {"_type":"choice","_value":["linear", "rbf", "poly", "sigmoid"]}, + "kernel": {"_type":"choice","_value":["linear", "rbf", "poly", "sigmoid"]}, "degree": {"_type":"choice","_value":[1, 2, 3, 4]}, "gamma": {"_type":"uniform","_value":[0.01, 0.1]}, "coef0 ": {"_type":"uniform","_value":[0.01, 0.1]} diff --git a/examples/trials/systems/rocksdb-fillrandom/config_smac.yml b/examples/trials/systems/rocksdb-fillrandom/config_smac.yml new file mode 100644 index 0000000000..dee0d0b334 --- /dev/null +++ b/examples/trials/systems/rocksdb-fillrandom/config_smac.yml @@ -0,0 +1,21 @@ +authorName: default +experimentName: auto_rocksdb_SMAC +trialConcurrency: 1 +maxExecDuration: 12h +maxTrialNum: 256 +#choice: local, remote, pai +trainingServicePlatform: local +searchSpacePath: search_space.json +#choice: true, false +useAnnotation: false +tuner: + #choice: TPE, Random, Anneal, Evolution, BatchTuner, MetisTuner + #SMAC (SMAC should be installed through nnictl) + builtinTunerName: SMAC + classArgs: + #choice: maximize, minimize + optimize_mode: maximize +trial: + command: python3 main.py + codeDir: . + gpuNum: 0 diff --git a/examples/trials/systems/rocksdb-fillrandom/config_tpe.yml b/examples/trials/systems/rocksdb-fillrandom/config_tpe.yml new file mode 100644 index 0000000000..30e1aad011 --- /dev/null +++ b/examples/trials/systems/rocksdb-fillrandom/config_tpe.yml @@ -0,0 +1,21 @@ +authorName: default +experimentName: auto_rocksdb_TPE +trialConcurrency: 1 +maxExecDuration: 12h +maxTrialNum: 256 +#choice: local, remote, pai +trainingServicePlatform: local +searchSpacePath: search_space.json +#choice: true, false +useAnnotation: false +tuner: + #choice: TPE, Random, Anneal, Evolution, BatchTuner, MetisTuner + #SMAC (SMAC should be installed through nnictl) + builtinTunerName: TPE + classArgs: + #choice: maximize, minimize + optimize_mode: maximize +trial: + command: python3 main.py + codeDir: . + gpuNum: 0 diff --git a/examples/trials/systems/rocksdb-fillrandom/db_bench_installation.sh b/examples/trials/systems/rocksdb-fillrandom/db_bench_installation.sh new file mode 100755 index 0000000000..462387cb76 --- /dev/null +++ b/examples/trials/systems/rocksdb-fillrandom/db_bench_installation.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# Install db_bench and its dependencies on Ubuntu + +pushd $PWD 1>/dev/null + +# install snappy +echo "****************** Installing snappy *******************" +sudo apt-get install libsnappy-dev -y + +# install gflag +echo "****************** Installing gflag ********************" +cd /tmp +git clone https://github.com/gflags/gflags.git +cd gflags +git checkout v2.0 +./configure && make && sudo make install + +# install rocksdb +echo "****************** Installing rocksdb ******************" +cd /tmp +git clone https://github.com/facebook/rocksdb.git +cd rocksdb +CPATH=/usr/local/include LIBRARY_PATH=/usr/local/lib DEBUG_LEVEL=0 make db_bench -j7 + +DIR=$HOME/.local/bin/ +if [[ ! -e $DIR ]]; then + mkdir $dir +elif [[ ! -d $DIR ]]; then + echo "$DIR already exists but is not a directory" 1>&2 + exit +fi +mv db_bench $HOME/.local/bin && +echo "Successfully installed rocksed in "$DIR" !" && +echo "Please add "$DIR" to your PATH for runing this example." + +popd 1>/dev/null diff --git a/examples/trials/systems/rocksdb-fillrandom/main.py b/examples/trials/systems/rocksdb-fillrandom/main.py new file mode 100644 index 0000000000..f6aa55dbf1 --- /dev/null +++ b/examples/trials/systems/rocksdb-fillrandom/main.py @@ -0,0 +1,96 @@ +# Copyright (c) Microsoft Corporation +# All rights reserved. +# +# MIT License +# +# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +# documentation files (the "Software"), to deal in the Software without restriction, including without limitation +# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and +# to permit persons to whom the Software is furnished to do so, subject to the following conditions: +# The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING +# BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +# DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +import nni +import subprocess +import logging + +LOG = logging.getLogger('rocksdb-fillrandom') + + +def run(**parameters): + '''Run rocksdb benchmark and return throughput''' + bench_type = parameters['benchmarks'] + # recover args + args = ["--{}={}".format(k, v) for k, v in parameters.items()] + # subprocess communicate + process = subprocess.Popen(['db_bench'] + args, stdout=subprocess.PIPE) + out, err = process.communicate() + # split into lines + lines = out.decode("utf8").splitlines() + + match_lines = [] + for line in lines: + # find the line with matched str + if bench_type not in line: + continue + else: + match_lines.append(line) + break + + results = {} + for line in match_lines: + key, _, value = line.partition(":") + key = key.strip() + value = value.split("op")[1] + results[key] = float(value) + + return results[bench_type] + + +def generate_params(received_params): + '''generate parameters based on received parameters''' + params = { + "benchmarks": "fillrandom", + "threads": 1, + "key_size": 20, + "value_size": 100, + "num": 13107200, + "db": "/tmp/rockdb", + "disable_wal": 1, + "max_background_flushes": 1, + "max_background_compactions": 4, + "write_buffer_size": 67108864, + "max_write_buffer_number": 16, + "min_write_buffer_number_to_merge": 2, + "level0_file_num_compaction_trigger": 2, + "max_bytes_for_level_base": 268435456, + "max_bytes_for_level_multiplier": 10, + "target_file_size_base": 33554432, + "target_file_size_multiplier": 1 + } + + for k, v in received_params.items(): + params[k] = int(v) + + return params + + +if __name__ == "__main__": + try: + # get parameters from tuner + RECEIVED_PARAMS = nni.get_next_parameter() + LOG.debug(RECEIVED_PARAMS) + PARAMS = generate_params(RECEIVED_PARAMS) + LOG.debug(PARAMS) + # run benchmark + throughput = run(**PARAMS) + # report throughput to nni + nni.report_final_result(throughput) + except Exception as exception: + LOG.exception(exception) + raise diff --git a/examples/trials/systems/rocksdb-fillrandom/plot.png b/examples/trials/systems/rocksdb-fillrandom/plot.png new file mode 100644 index 0000000000..075bf7ac77 Binary files /dev/null and b/examples/trials/systems/rocksdb-fillrandom/plot.png differ diff --git a/examples/trials/systems/rocksdb-fillrandom/search_space.json b/examples/trials/systems/rocksdb-fillrandom/search_space.json new file mode 100644 index 0000000000..c433b72233 --- /dev/null +++ b/examples/trials/systems/rocksdb-fillrandom/search_space.json @@ -0,0 +1,14 @@ +{ + "write_buffer_size": { + "_type": "quniform", + "_value": [2097152, 16777216, 1048576] + }, + "min_write_buffer_number_to_merge": { + "_type": "quniform", + "_value": [2, 16, 1] + }, + "level0_file_num_compaction_trigger": { + "_type": "quniform", + "_value": [2, 16, 1] + } +} diff --git a/setup.py b/setup.py index 296b2a8f22..c4734ddc24 100644 --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ def read(fname): python_requires = '>=3.5', install_requires = [ 'astor', - 'hyperopt', + 'hyperopt==0.1.2', 'json_tricks', 'numpy', 'psutil', diff --git a/src/sdk/pynni/nni/compression/tensorflow/builtin_pruners.py b/src/sdk/pynni/nni/compression/tensorflow/builtin_pruners.py index c2b7e4453d..88f00b4d52 100644 --- a/src/sdk/pynni/nni/compression/tensorflow/builtin_pruners.py +++ b/src/sdk/pynni/nni/compression/tensorflow/builtin_pruners.py @@ -2,7 +2,7 @@ import tensorflow as tf from .compressor import Pruner -__all__ = [ 'LevelPruner', 'AGP_Pruner', 'SensitivityPruner' ] +__all__ = ['LevelPruner', 'AGP_Pruner'] _logger = logging.getLogger(__name__) @@ -14,10 +14,18 @@ def __init__(self, config_list): - sparsity """ super().__init__(config_list) + self.mask_list = {} + self.if_init_list = {} - def calc_mask(self, weight, config, **kwargs): - threshold = tf.contrib.distributions.percentile(tf.abs(weight), config['sparsity'] * 100) - return tf.cast(tf.math.greater(tf.abs(weight), threshold), weight.dtype) + def calc_mask(self, weight, config, op_name, **kwargs): + if self.if_init_list.get(op_name, True): + threshold = tf.contrib.distributions.percentile(tf.abs(weight), config['sparsity'] * 100) + mask = tf.cast(tf.math.greater(tf.abs(weight), threshold), weight.dtype) + self.mask_list.update({op_name: mask}) + self.if_init_list.update({op_name: False}) + else: + mask = self.mask_list[op_name] + return mask class AGP_Pruner(Pruner): @@ -29,6 +37,7 @@ class AGP_Pruner(Pruner): Learning of Phones and other Consumer Devices, https://arxiv.org/pdf/1710.01878.pdf """ + def __init__(self, config_list): """ config_list: supported keys: @@ -39,15 +48,25 @@ def __init__(self, config_list): - frequency: if you want update every 2 epoch, you can set it 2 """ super().__init__(config_list) + self.mask_list = {} + self.if_init_list = {} self.now_epoch = tf.Variable(0) self.assign_handler = [] - def calc_mask(self, weight, config, **kwargs): - target_sparsity = self.compute_target_sparsity(config) - threshold = tf.contrib.distributions.percentile(weight, target_sparsity * 100) - # stop gradient in case gradient change the mask - mask = tf.stop_gradient(tf.cast(tf.math.greater(weight, threshold), weight.dtype)) - self.assign_handler.append(tf.assign(weight, weight * mask)) + def calc_mask(self, weight, config, op_name, **kwargs): + start_epoch = config.get('start_epoch', 0) + freq = config.get('frequency', 1) + if self.now_epoch >= start_epoch and self.if_init_list.get(op_name, True) and ( + self.now_epoch - start_epoch) % freq == 0: + target_sparsity = self.compute_target_sparsity(config) + threshold = tf.contrib.distributions.percentile(weight, target_sparsity * 100) + # stop gradient in case gradient change the mask + mask = tf.stop_gradient(tf.cast(tf.math.greater(weight, threshold), weight.dtype)) + self.assign_handler.append(tf.assign(weight, weight * mask)) + self.mask_list.update({op_name: tf.constant(mask)}) + self.if_init_list.update({op_name: False}) + else: + mask = self.mask_list[op_name] return mask def compute_target_sparsity(self, config): @@ -60,51 +79,18 @@ def compute_target_sparsity(self, config): if end_epoch <= start_epoch or initial_sparsity >= final_sparsity: _logger.warning('your end epoch <= start epoch or initial_sparsity >= final_sparsity') return final_sparsity - + now_epoch = tf.minimum(self.now_epoch, tf.constant(end_epoch)) - span = int(((end_epoch - start_epoch-1)//freq)*freq) + span = int(((end_epoch - start_epoch - 1) // freq) * freq) assert span > 0 base = tf.cast(now_epoch - start_epoch, tf.float32) / span - target_sparsity = (final_sparsity + - (initial_sparsity - final_sparsity)* - (tf.pow(1.0 - base, 3))) + target_sparsity = (final_sparsity + + (initial_sparsity - final_sparsity) * + (tf.pow(1.0 - base, 3))) return target_sparsity def update_epoch(self, epoch, sess): sess.run(self.assign_handler) sess.run(tf.assign(self.now_epoch, int(epoch))) - - -class SensitivityPruner(Pruner): - """Use algorithm from "Learning both Weights and Connections for Efficient Neural Networks" - https://arxiv.org/pdf/1506.02626v3.pdf - - I.e.: "The pruning threshold is chosen as a quality parameter multiplied - by the standard deviation of a layers weights." - """ - def __init__(self, config_list): - """ - config_list: supported keys - - sparsity: chosen pruning sparsity - """ - super().__init__(config_list) - self.layer_mask = {} - self.assign_handler = [] - - def calc_mask(self, weight, config, op_name, **kwargs): - target_sparsity = config['sparsity'] * tf.math.reduce_std(weight) - mask = tf.get_variable(op_name + '_mask', initializer=tf.ones(weight.shape), trainable=False) - self.layer_mask[op_name] = mask - - weight_assign_handler = tf.assign(weight, mask*weight) - # use control_dependencies so that weight_assign_handler will be executed before mask_update_handler - with tf.control_dependencies([weight_assign_handler]): - threshold = tf.contrib.distributions.percentile(weight, target_sparsity * 100) - # stop gradient in case gradient change the mask - new_mask = tf.stop_gradient(tf.cast(tf.math.greater(weight, threshold), weight.dtype)) - mask_update_handler = tf.assign(mask, new_mask) - self.assign_handler.append(mask_update_handler) - return mask - - def update_epoch(self, epoch, sess): - sess.run(self.assign_handler) + for k in self.if_init_list.keys(): + self.if_init_list[k] = True diff --git a/src/sdk/pynni/nni/compression/torch/builtin_pruners.py b/src/sdk/pynni/nni/compression/torch/builtin_pruners.py index 858db63a94..0ad09b9135 100644 --- a/src/sdk/pynni/nni/compression/torch/builtin_pruners.py +++ b/src/sdk/pynni/nni/compression/torch/builtin_pruners.py @@ -2,7 +2,7 @@ import torch from .compressor import Pruner -__all__ = [ 'LevelPruner', 'AGP_Pruner', 'SensitivityPruner' ] +__all__ = ['LevelPruner', 'AGP_Pruner'] logger = logging.getLogger('torch pruner') @@ -10,20 +10,29 @@ class LevelPruner(Pruner): """Prune to an exact pruning level specification """ + def __init__(self, config_list): """ config_list: supported keys: - sparsity """ super().__init__(config_list) + self.mask_list = {} + self.if_init_list = {} - def calc_mask(self, weight, config, **kwargs): - w_abs = weight.abs() - k = int(weight.numel() * config['sparsity']) - if k == 0: - return torch.ones(weight.shape) - threshold = torch.topk(w_abs.view(-1), k, largest = False).values.max() - return torch.gt(w_abs, threshold).type(weight.type()) + def calc_mask(self, weight, config, op_name, **kwargs): + if self.if_init_list.get(op_name, True): + w_abs = weight.abs() + k = int(weight.numel() * config['sparsity']) + if k == 0: + return torch.ones(weight.shape).type_as(weight) + threshold = torch.topk(w_abs.view(-1), k, largest=False).values.max() + mask = torch.gt(w_abs, threshold).type_as(weight) + self.mask_list.update({op_name: mask}) + self.if_init_list.update({op_name: False}) + else: + mask = self.mask_list[op_name] + return mask class AGP_Pruner(Pruner): @@ -35,35 +44,44 @@ class AGP_Pruner(Pruner): Learning of Phones and other Consumer Devices, https://arxiv.org/pdf/1710.01878.pdf """ + def __init__(self, config_list): """ config_list: supported keys: - initial_sparsity - final_sparsity: you should make sure initial_sparsity <= final_sparsity - - start_epoch: start epoch numer begin update mask + - start_epoch: start epoch number begin update mask - end_epoch: end epoch number stop update mask, you should make sure start_epoch <= end_epoch - frequency: if you want update every 2 epoch, you can set it 2 """ super().__init__(config_list) self.mask_list = {} - self.now_epoch = 1 + self.now_epoch = 0 + self.if_init_list = {} def calc_mask(self, weight, config, op_name, **kwargs): - mask = self.mask_list.get(op_name, torch.ones(weight.shape)) - target_sparsity = self.compute_target_sparsity(config) - k = int(weight.numel() * target_sparsity) - if k == 0 or target_sparsity >= 1 or target_sparsity <= 0: - return mask - # if we want to generate new mask, we should update weigth first - w_abs = weight.abs()*mask - threshold = torch.topk(w_abs.view(-1), k, largest = False).values.max() - new_mask = torch.gt(w_abs, threshold).type(weight.type()) - self.mask_list[op_name] = new_mask + start_epoch = config.get('start_epoch', 0) + freq = config.get('frequency', 1) + if self.now_epoch >= start_epoch and self.if_init_list.get(op_name, True) and ( + self.now_epoch - start_epoch) % freq == 0: + mask = self.mask_list.get(op_name, torch.ones(weight.shape).type_as(weight)) + target_sparsity = self.compute_target_sparsity(config) + k = int(weight.numel() * target_sparsity) + if k == 0 or target_sparsity >= 1 or target_sparsity <= 0: + return mask + # if we want to generate new mask, we should update weigth first + w_abs = weight.abs() * mask + threshold = torch.topk(w_abs.view(-1), k, largest=False).values.max() + new_mask = torch.gt(w_abs, threshold).type_as(weight) + self.mask_list.update({op_name: new_mask}) + self.if_init_list.update({op_name: False}) + else: + new_mask = self.mask_list.get(op_name, torch.ones(weight.shape).type_as(weight)) return new_mask def compute_target_sparsity(self, config): end_epoch = config.get('end_epoch', 1) - start_epoch = config.get('start_epoch', 1) + start_epoch = config.get('start_epoch', 0) freq = config.get('frequency', 1) final_sparsity = config.get('final_sparsity', 0) initial_sparsity = config.get('initial_sparsity', 0) @@ -74,45 +92,15 @@ def compute_target_sparsity(self, config): if end_epoch <= self.now_epoch: return final_sparsity - span = ((end_epoch - start_epoch-1)//freq)*freq + span = ((end_epoch - start_epoch - 1) // freq) * freq assert span > 0 - target_sparsity = (final_sparsity + - (initial_sparsity - final_sparsity)* - (1.0 - ((self.now_epoch - start_epoch)/span))**3) + target_sparsity = (final_sparsity + + (initial_sparsity - final_sparsity) * + (1.0 - ((self.now_epoch - start_epoch) / span)) ** 3) return target_sparsity def update_epoch(self, epoch): if epoch > 0: self.now_epoch = epoch - - -class SensitivityPruner(Pruner): - """Use algorithm from "Learning both Weights and Connections for Efficient Neural Networks" - https://arxiv.org/pdf/1506.02626v3.pdf - - I.e.: "The pruning threshold is chosen as a quality parameter multiplied - by the standard deviation of a layers weights." - """ - def __init__(self, config_list): - """ - config_list: supported keys: - - sparsity: chosen pruning sparsity - """ - super().__init__(config_list) - self.mask_list = {} - - - def calc_mask(self, weight, config, op_name, **kwargs): - mask = self.mask_list.get(op_name, torch.ones(weight.shape)) - # if we want to generate new mask, we should update weigth first - weight = weight*mask - target_sparsity = config['sparsity'] * torch.std(weight).item() - k = int(weight.numel() * target_sparsity) - if k == 0: - return mask - - w_abs = weight.abs() - threshold = torch.topk(w_abs.view(-1), k, largest = False).values.max() - new_mask = torch.gt(w_abs, threshold).type(weight.type()) - self.mask_list[op_name] = new_mask - return new_mask + for k in self.if_init_list.keys(): + self.if_init_list[k] = True diff --git a/src/sdk/pynni/nni/compression/torch/compressor.py b/src/sdk/pynni/nni/compression/torch/compressor.py index 5d74a464b0..19304d1f7a 100644 --- a/src/sdk/pynni/nni/compression/torch/compressor.py +++ b/src/sdk/pynni/nni/compression/torch/compressor.py @@ -38,25 +38,23 @@ def compress(self, model): if config is not None: self._instrument_layer(layer, config) - def bind_model(self, model): """This method is called when a model is bound to the compressor. Users can optionally overload this method to do model-specific initialization. It is guaranteed that only one model will be bound to each compressor instance. """ pass - + def update_epoch(self, epoch): """if user want to update model every epoch, user can override this method """ pass - + def step(self): """if user want to update model every step, user can override this method """ pass - def _instrument_layer(self, layer, config): raise NotImplementedError() @@ -90,7 +88,6 @@ def calc_mask(self, weight, config, op, op_type, op_name): """ raise NotImplementedError("Pruners must overload calc_mask()") - def _instrument_layer(self, layer, config): # TODO: support multiple weight tensors # create a wrapper forward function to replace the original one @@ -112,7 +109,7 @@ def new_forward(*input): return ret layer.module.forward = new_forward - + class Quantizer(Compressor): """Base quantizer for pytorch quantizer""" @@ -123,7 +120,7 @@ def __init__(self, config_list): def __call__(self, model): self.compress(model) return model - + def quantize_weight(self, weight, config, op, op_type, op_name): """user should know where dequantize goes and implement it in quantize method we now do not provide dequantize method diff --git a/src/sdk/pynni/requirements.txt b/src/sdk/pynni/requirements.txt index 3adfb06cf5..d44ec3a072 100644 --- a/src/sdk/pynni/requirements.txt +++ b/src/sdk/pynni/requirements.txt @@ -4,7 +4,7 @@ json_tricks # hyperopt tuner numpy scipy -hyperopt +hyperopt==0.1.2 # metis tuner sklearn diff --git a/src/sdk/pynni/setup.py b/src/sdk/pynni/setup.py index e7ba29dad2..197b59fb6e 100644 --- a/src/sdk/pynni/setup.py +++ b/src/sdk/pynni/setup.py @@ -32,7 +32,7 @@ def read(fname): python_requires = '>=3.5', install_requires = [ - 'hyperopt', + 'hyperopt==0.1.2', 'json_tricks', 'numpy', 'scipy', diff --git a/src/sdk/pynni/tests/test_compressor.py b/src/sdk/pynni/tests/test_compressor.py index 83735a20a2..d1f4a724cb 100644 --- a/src/sdk/pynni/tests/test_compressor.py +++ b/src/sdk/pynni/tests/test_compressor.py @@ -5,24 +5,28 @@ import nni.compression.tensorflow as tf_compressor import nni.compression.torch as torch_compressor + def weight_variable(shape): - return tf.Variable(tf.truncated_normal(shape, stddev = 0.1)) + return tf.Variable(tf.truncated_normal(shape, stddev=0.1)) + def bias_variable(shape): - return tf.Variable(tf.constant(0.1, shape = shape)) + return tf.Variable(tf.constant(0.1, shape=shape)) + def conv2d(x_input, w_matrix): - return tf.nn.conv2d(x_input, w_matrix, strides = [ 1, 1, 1, 1 ], padding = 'SAME') + return tf.nn.conv2d(x_input, w_matrix, strides=[1, 1, 1, 1], padding='SAME') + def max_pool(x_input, pool_size): - size = [ 1, pool_size, pool_size, 1 ] - return tf.nn.max_pool(x_input, ksize = size, strides = size, padding = 'SAME') + size = [1, pool_size, pool_size, 1] + return tf.nn.max_pool(x_input, ksize=size, strides=size, padding='SAME') class TfMnist: def __init__(self): - images = tf.placeholder(tf.float32, [ None, 784 ], name = 'input_x') - labels = tf.placeholder(tf.float32, [ None, 10 ], name = 'input_y') + images = tf.placeholder(tf.float32, [None, 784], name='input_x') + labels = tf.placeholder(tf.float32, [None, 10], name='input_y') keep_prob = tf.placeholder(tf.float32, name='keep_prob') self.images = images @@ -37,35 +41,35 @@ def __init__(self): self.fcw1 = None self.cross = None with tf.name_scope('reshape'): - x_image = tf.reshape(images, [ -1, 28, 28, 1 ]) + x_image = tf.reshape(images, [-1, 28, 28, 1]) with tf.name_scope('conv1'): - w_conv1 = weight_variable([ 5, 5, 1, 32 ]) + w_conv1 = weight_variable([5, 5, 1, 32]) self.w1 = w_conv1 - b_conv1 = bias_variable([ 32 ]) + b_conv1 = bias_variable([32]) self.b1 = b_conv1 h_conv1 = tf.nn.relu(conv2d(x_image, w_conv1) + b_conv1) with tf.name_scope('pool1'): h_pool1 = max_pool(h_conv1, 2) with tf.name_scope('conv2'): - w_conv2 = weight_variable([ 5, 5, 32, 64 ]) - b_conv2 = bias_variable([ 64 ]) + w_conv2 = weight_variable([5, 5, 32, 64]) + b_conv2 = bias_variable([64]) h_conv2 = tf.nn.relu(conv2d(h_pool1, w_conv2) + b_conv2) with tf.name_scope('pool2'): h_pool2 = max_pool(h_conv2, 2) with tf.name_scope('fc1'): - w_fc1 = weight_variable([ 7 * 7 * 64, 1024 ]) + w_fc1 = weight_variable([7 * 7 * 64, 1024]) self.fcw1 = w_fc1 - b_fc1 = bias_variable([ 1024 ]) - h_pool2_flat = tf.reshape(h_pool2, [ -1, 7 * 7 * 64 ]) + b_fc1 = bias_variable([1024]) + h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64]) h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, w_fc1) + b_fc1) with tf.name_scope('dropout'): h_fc1_drop = tf.nn.dropout(h_fc1, 0.5) with tf.name_scope('fc2'): - w_fc2 = weight_variable([ 1024, 10 ]) - b_fc2 = bias_variable([ 10 ]) + w_fc2 = weight_variable([1024, 10]) + b_fc2 = bias_variable([10]) y_conv = tf.matmul(h_fc1_drop, w_fc2) + b_fc2 with tf.name_scope('loss'): - cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels = labels, logits = y_conv)) + cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=labels, logits=y_conv)) self.cross = cross_entropy with tf.name_scope('adam_optimizer'): self.train_step = tf.train.AdamOptimizer(0.0001).minimize(cross_entropy) @@ -73,6 +77,7 @@ def __init__(self): correct_prediction = tf.equal(tf.argmax(y_conv, 1), tf.argmax(labels, 1)) self.accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32)) + class TorchMnist(torch.nn.Module): def __init__(self): super().__init__() @@ -89,24 +94,24 @@ def forward(self, x): x = x.view(-1, 4 * 4 * 50) x = F.relu(self.fc1(x)) x = self.fc2(x) - return F.log_softmax(x, dim = 1) + return F.log_softmax(x, dim=1) + class CompressorTestCase(TestCase): def test_tf_pruner(self): model = TfMnist() - configure_list = [{'sparsity':0.8, 'op_types':'default'}] + configure_list = [{'sparsity': 0.8, 'op_types': 'default'}] tf_compressor.LevelPruner(configure_list).compress_default_graph() - def test_tf_quantizer(self): model = TfMnist() tf_compressor.NaiveQuantizer([{'op_types': 'default'}]).compress_default_graph() - + def test_torch_pruner(self): model = TorchMnist() - configure_list = [{'sparsity':0.8, 'op_types':'default'}] + configure_list = [{'sparsity': 0.8, 'op_types': 'default'}] torch_compressor.LevelPruner(configure_list).compress(model) - + def test_torch_quantizer(self): model = TorchMnist() torch_compressor.NaiveQuantizer([{'op_types': 'default'}]).compress(model) diff --git a/src/webui/src/components/Modal/Compare.tsx b/src/webui/src/components/Modal/Compare.tsx index 8a7278c966..7ba8f8f7fa 100644 --- a/src/webui/src/components/Modal/Compare.tsx +++ b/src/webui/src/components/Modal/Compare.tsx @@ -125,6 +125,11 @@ class Compare extends React.Component { durationList.push(temp.duration); parameterList.push(temp.description.parameters); }); + let isComplexSearchSpace; + if (parameterList.length > 0) { + isComplexSearchSpace = (typeof parameterList[0][parameterKeys[0]] === 'object') + ? true : false; + } return ( @@ -164,22 +169,26 @@ class Compare extends React.Component { })} { - Object.keys(parameterKeys).map(index => { - return ( - - - { - Object.keys(parameterList).map(key => { - return ( - - ); - }) - } - - ); - }) + isComplexSearchSpace + ? + null + : + Object.keys(parameterKeys).map(index => { + return ( + + + { + Object.keys(parameterList).map(key => { + return ( + + ); + }) + } + + ); + }) }
{parameterKeys[index]} - {parameterList[key][parameterKeys[index]]} -
{parameterKeys[index]} + {parameterList[key][parameterKeys[index]]} +
diff --git a/src/webui/src/components/Modal/ExperimentDrawer.tsx b/src/webui/src/components/Modal/ExperimentDrawer.tsx index 2541811bf3..ba32492773 100644 --- a/src/webui/src/components/Modal/ExperimentDrawer.tsx +++ b/src/webui/src/components/Modal/ExperimentDrawer.tsx @@ -14,6 +14,7 @@ interface ExpDrawerProps { interface ExpDrawerState { experiment: string; + expDrawerHeight: number; } class ExperimentDrawer extends React.Component { @@ -23,7 +24,8 @@ class ExperimentDrawer extends React.Component { super(props); this.state = { - experiment: '' + experiment: '', + expDrawerHeight: window.innerHeight - 48 }; } @@ -69,9 +71,14 @@ class ExperimentDrawer extends React.Component { downFile(experiment, 'experiment.json'); } + onWindowResize = () => { + this.setState(() => ({expDrawerHeight: window.innerHeight - 48})); + } + componentDidMount() { this._isCompareMount = true; this.getExperimentContent(); + window.addEventListener('resize', this.onWindowResize); } componentWillReceiveProps(nextProps: ExpDrawerProps) { @@ -83,12 +90,12 @@ class ExperimentDrawer extends React.Component { componentWillUnmount() { this._isCompareMount = false; + window.removeEventListener('resize', this.onWindowResize); } render() { const { isVisble, closeExpDrawer } = this.props; - const { experiment } = this.state; - const heights: number = window.innerHeight - 48; + const { experiment, expDrawerHeight } = this.state; return ( { onClose={closeExpDrawer} visible={isVisble} width="54%" - height={heights} + height={expDrawerHeight} > -

- + {/* 104: tabHeight(40) + tabMarginBottom(16) + buttonHeight(32) + buttonMarginTop(16) */} +
+
void; activeTab?: string; } interface LogDrawerState { - nniManagerLogStr: string; - dispatcherLogStr: string; + nniManagerLogStr: string | null; + dispatcherLogStr: string | null; isLoading: boolean; - isLoadispatcher: boolean; + logDrawerHeight: number; } class LogDrawer extends React.Component { + private timerId: number | undefined; - public _isLogDrawer: boolean; constructor(props: LogDrawerProps) { super(props); this.state = { - nniManagerLogStr: 'nnimanager', - dispatcherLogStr: 'dispatcher', - isLoading: false, - isLoadispatcher: false + nniManagerLogStr: null, + dispatcherLogStr: null, + isLoading: true, + logDrawerHeight: window.innerHeight - 48 }; } - getNNImanagerLogmessage = () => { - if (this._isLogDrawer === true) { - this.setState({ isLoading: true }, () => { - axios(`${DOWNLOAD_IP}/nnimanager.log`, { - method: 'GET' - }) - .then(res => { - if (res.status === 200) { - setTimeout(() => { this.setNNImanager(res.data); }, 300); - } - }); - }); - } - } - - setDispatcher = (value: string) => { - if (this._isLogDrawer === true) { - this.setState({ isLoadispatcher: false, dispatcherLogStr: value }); - } - } - - setNNImanager = (val: string) => { - if (this._isLogDrawer === true) { - this.setState({ isLoading: false, nniManagerLogStr: val }); - } - } - - getdispatcherLogmessage = () => { - if (this._isLogDrawer === true) { - this.setState({ isLoadispatcher: true }, () => { - axios(`${DOWNLOAD_IP}/dispatcher.log`, { - method: 'GET' - }) - .then(res => { - if (res.status === 200) { - setTimeout(() => { this.setDispatcher(res.data); }, 300); - } - }); - }); - } - } - downloadNNImanager = () => { - const { nniManagerLogStr } = this.state; - downFile(nniManagerLogStr, 'nnimanager.log'); + if (this.state.nniManagerLogStr !== null) { + downFile(this.state.nniManagerLogStr, 'nnimanager.log'); + } } downloadDispatcher = () => { - const { dispatcherLogStr } = this.state; - downFile(dispatcherLogStr, 'dispatcher.log'); + if (this.state.dispatcherLogStr !== null) { + downFile(this.state.dispatcherLogStr, 'dispatcher.log'); + } } dispatcherHTML = () => { return (
Dispatcher Log - +
@@ -101,37 +60,28 @@ class LogDrawer extends React.Component { return (
NNImanager Log - +
); } - componentDidMount() { - this._isLogDrawer = true; - this.getNNImanagerLogmessage(); - this.getdispatcherLogmessage(); + setLogDrawerHeight = () => { + this.setState(() => ({ logDrawerHeight: window.innerHeight - 48 })); } - componentWillReceiveProps(nextProps: LogDrawerProps) { - const { isVisble, activeTab } = nextProps; - if (isVisble === true) { - if (activeTab === 'nnimanager') { - this.getNNImanagerLogmessage(); - } - if (activeTab === 'dispatcher') { - this.getdispatcherLogmessage(); - } - } + async componentDidMount() { + this.refresh(); + window.addEventListener('resize', this.setLogDrawerHeight); } componentWillUnmount() { - this._isLogDrawer = false; + window.clearTimeout(this.timerId); + window.removeEventListener('resize', this.setLogDrawerHeight); } render() { - const { isVisble, closeDrawer, activeTab } = this.props; - const { nniManagerLogStr, dispatcherLogStr, isLoadispatcher, isLoading } = this.state; - const heights: number = window.innerHeight - 48; // padding top and bottom + const { closeDrawer, activeTab } = this.props; + const { nniManagerLogStr, dispatcherLogStr, isLoading, logDrawerHeight } = this.state; return ( { closable={false} destroyOnClose={true} onClose={closeDrawer} - visible={isVisble} + visible={true} width="76%" - height={heights} + height={logDrawerHeight} // className="logDrawer" > -
- +
+ {/* */} {/* */}
- +
@@ -174,7 +132,11 @@ class LogDrawer extends React.Component { {/* */}
- +
@@ -201,6 +163,31 @@ class LogDrawer extends React.Component { ); } + + private refresh = () => { + window.clearTimeout(this.timerId); + const dispatcherPromise = axios.get(`${DOWNLOAD_IP}/dispatcher.log`); + const nniManagerPromise = axios.get(`${DOWNLOAD_IP}/nnimanager.log`); + dispatcherPromise.then(res => { + if (res.status === 200) { + this.setState({ dispatcherLogStr: res.data }); + } + }); + nniManagerPromise.then(res => { + if (res.status === 200) { + this.setState({ nniManagerLogStr: res.data }); + } + }); + Promise.all([dispatcherPromise, nniManagerPromise]).then(() => { + this.setState({ isLoading: false }); + this.timerId = window.setTimeout(this.refresh, 300); + }); + } + + private manualRefresh = () => { + this.setState({ isLoading: true }); + this.refresh(); + } } export default LogDrawer; diff --git a/src/webui/src/components/SlideBar.tsx b/src/webui/src/components/SlideBar.tsx index 1200fb8a7d..32a7430101 100644 --- a/src/webui/src/components/SlideBar.tsx +++ b/src/webui/src/components/SlideBar.tsx @@ -214,7 +214,12 @@ class SlideBar extends React.Component { type="ghost" > - Help + question + Help @@ -329,8 +334,8 @@ class SlideBar extends React.Component { render() { const mobile = ({this.mobileHTML()}); - const tablet = ({this.tabeltHTML()}); - const desktop = ({this.desktopHTML()}); + const tablet = ({this.tabeltHTML()}); + const desktop = ({this.desktopHTML()}); const { isvisibleLogDrawer, activeKey, isvisibleExperimentDrawer } = this.state; return (
@@ -338,11 +343,12 @@ class SlideBar extends React.Component { {tablet} {desktop} {/* the drawer for dispatcher & nnimanager log message */} - + {isvisibleLogDrawer ? ( + + ) : null} this.setState({ whichGraph: activeKey }); } - test = () => { - alert('TableList component was not properly initialized.'); - } - updateSearchFilterType = (value: string) => { // clear input value and re-render table if (this.searchInput !== null) { @@ -167,14 +163,14 @@ class TrialsDetail extends React.Component diff --git a/src/webui/src/components/overview/Progress.tsx b/src/webui/src/components/overview/Progress.tsx index 398fbf2ff7..3145788a6f 100644 --- a/src/webui/src/components/overview/Progress.tsx +++ b/src/webui/src/components/overview/Progress.tsx @@ -184,11 +184,12 @@ class Progressed extends React.Component { {/* learn about click -> default active key is dispatcher. */} - + {isShowLogDrawer ? ( + + ) : null} ); } diff --git a/src/webui/src/components/public-child/MonacoEditor.tsx b/src/webui/src/components/public-child/MonacoEditor.tsx index cf89f01502..7ab1621cad 100644 --- a/src/webui/src/components/public-child/MonacoEditor.tsx +++ b/src/webui/src/components/public-child/MonacoEditor.tsx @@ -6,6 +6,7 @@ import MonacoEditor from 'react-monaco-editor'; interface MonacoEditorProps { content: string; loading: boolean; + height: number; } class MonacoHTML extends React.Component { @@ -25,18 +26,17 @@ class MonacoHTML extends React.Component { } render() { - const { content, loading } = this.props; - const heights: number = window.innerHeight - 48; + const { content, loading, height } = this.props; return (
0); if (better) { - data.push([ trial.sequenceId, trial.accuracy, trial.info.hyperParameters ]); + data.push([ trial.sequenceId, trial.accuracy, trial.description.parameters ]); best = trial; } else { - data.push([ trial.sequenceId, best.accuracy, trial.info.hyperParameters ]); + data.push([ trial.sequenceId, best.accuracy, trial.description.parameters ]); } } diff --git a/src/webui/src/components/trial-detail/Intermediate.tsx b/src/webui/src/components/trial-detail/Intermediate.tsx index 3c24b8f497..1850926cbd 100644 --- a/src/webui/src/components/trial-detail/Intermediate.tsx +++ b/src/webui/src/components/trial-detail/Intermediate.tsx @@ -262,16 +262,10 @@ class Intermediate extends React.Component
{/* style in para.scss */} - {/* filter message */} - Filter - { isFilter ? - + # Intermediate result : null } + {/* filter message */} + Filter + { const showColumn: Array = []; // parameter as table column - const trialMess = TRIALS.getTrial(tableSource[0].id); - const trial = trialMess.description.parameters; - const parameterColumn: Array = Object.keys(trial); const parameterStr: Array = []; - parameterColumn.forEach(value => { - parameterStr.push(`${value} (search space)`); - }); + if (tableSource.length > 0) { + const trialMess = TRIALS.getTrial(tableSource[0].id); + const trial = trialMess.description.parameters; + const parameterColumn: Array = Object.keys(trial); + parameterColumn.forEach(value => { + parameterStr.push(`${value} (search space)`); + }); + } showTitle = COLUMNPro.concat(parameterStr); // only succeed trials have final keys @@ -330,20 +332,35 @@ class TableList extends React.Component { {/* kill job */} - - - + { + flag + ? + + : + + + + } ); }, diff --git a/src/webui/src/static/img/icon/ques.png b/src/webui/src/static/img/icon/ques.png new file mode 100644 index 0000000000..baf4d0a45e Binary files /dev/null and b/src/webui/src/static/img/icon/ques.png differ diff --git a/src/webui/src/static/model/trial.ts b/src/webui/src/static/model/trial.ts index f2a6e13896..58be735404 100644 --- a/src/webui/src/static/model/trial.ts +++ b/src/webui/src/static/model/trial.ts @@ -43,7 +43,7 @@ class Trial implements TableObj { } get sortable(): boolean { - return this.finalAcc !== undefined && !isNaN(this.finalAcc); + return this.metricsInitialized && this.finalAcc !== undefined && !isNaN(this.finalAcc); } /* table obj start */ @@ -132,7 +132,7 @@ class Trial implements TableObj { /* table obj end */ public initialized(): boolean { - return !!(this.infoField && this.metricsInitialized); + return Boolean(this.infoField); } public updateMetrics(metrics: MetricDataRecord[]): boolean { diff --git a/src/webui/src/static/style/button.scss b/src/webui/src/static/style/button.scss index 666036b7f7..1db2a1feec 100644 --- a/src/webui/src/static/style/button.scss +++ b/src/webui/src/static/style/button.scss @@ -6,7 +6,7 @@ Button.tableButton{ border-color: $btnBgcolor; height: 26px; font-size: 14px; - margin-top: 2px; + margin-top: 4px; border-radius: 0; } diff --git a/src/webui/src/static/style/logDrawer.scss b/src/webui/src/static/style/logDrawer.scss index 5694ea2cf4..62da125ef0 100644 --- a/src/webui/src/static/style/logDrawer.scss +++ b/src/webui/src/static/style/logDrawer.scss @@ -25,8 +25,9 @@ height: 100%; } +/* log drawer download & close button's row */ .buttons{ - margin-top: 11px; + margin-top: 16px; .close{ text-align: right; } diff --git a/src/webui/src/static/style/slideBar.scss b/src/webui/src/static/style/slideBar.scss index a07f7e75df..9b6b2ba6df 100644 --- a/src/webui/src/static/style/slideBar.scss +++ b/src/webui/src/static/style/slideBar.scss @@ -21,7 +21,7 @@ $drowHoverBgColor: #e2e2e2; padding-bottom: 14px; .down-icon{ font-size: 20px !important; - padding-right: 2px; + padding-right: 6px; } } @@ -186,6 +186,11 @@ $drowHoverBgColor: #e2e2e2; width: 20px; margin-right: 8px; } + /* ? icon style */ + .question{ + width: 14px; + margin-right: 4px; + } .feedback{ font-size: 16px; margin: 0 20px; diff --git a/src/webui/src/static/style/trialsDetail.scss b/src/webui/src/static/style/trialsDetail.scss index d4a2ca0fbd..badc6018bd 100644 --- a/src/webui/src/static/style/trialsDetail.scss +++ b/src/webui/src/static/style/trialsDetail.scss @@ -15,10 +15,12 @@ .ant-tabs-tab-active{ .panelTitle{ background-color: #999; + /* span{ color: #fff; font-weight: normal; } + */ } } .panelTitle{ diff --git a/tools/nni_cmd/command_utils.py b/tools/nni_cmd/command_utils.py index f8c7a0acfd..a3bcb81965 100644 --- a/tools/nni_cmd/command_utils.py +++ b/tools/nni_cmd/command_utils.py @@ -3,10 +3,11 @@ import os import signal import psutil -from .common_utils import print_error, print_normal, print_warning +from .common_utils import print_error, print_normal, print_warning + def check_output_command(file_path, head=None, tail=None): - '''call check_output command to read content from a file''' + """call check_output command to read content from a file""" if os.path.exists(file_path): if sys.platform == 'win32': cmds = ['powershell.exe', 'type', file_path] @@ -26,8 +27,9 @@ def check_output_command(file_path, head=None, tail=None): print_error('{0} does not exist!'.format(file_path)) exit(1) + def kill_command(pid): - '''kill command''' + """kill command""" if sys.platform == 'win32': process = psutil.Process(pid=pid) process.send_signal(signal.CTRL_BREAK_EVENT) @@ -35,21 +37,35 @@ def kill_command(pid): cmds = ['kill', str(pid)] call(cmds) + def install_package_command(package_name): - '''install python package from pip''' - #TODO refactor python logic - if sys.platform == "win32": - cmds = 'python -m pip install --user {0}'.format(package_name) - else: - cmds = 'python3 -m pip install --user {0}'.format(package_name) - call(cmds, shell=True) + """ + Install python package from pip. + + Parameters + ---------- + package_name: str + The name of package to be installed. + """ + call(_get_pip_install() + [package_name], shell=False) + def install_requirements_command(requirements_path): - '''install requirements.txt''' - cmds = 'cd ' + requirements_path + ' && {0} -m pip install --user -r requirements.txt' - #TODO refactor python logic - if sys.platform == "win32": - cmds = cmds.format('python') - else: - cmds = cmds.format('python3') - call(cmds, shell=True) + """ + Install packages from `requirements.txt` in `requirements_path`. + + Parameters + ---------- + requirements_path: str + Path to the directory that contains `requirements.txt`. + """ + call(_get_pip_install() + ["-r", os.path.join(requirements_path, "requirements.txt")], shell=False) + + +def _get_pip_install(): + python = "python" if sys.platform == "win32" else "python3" + ret = [python, "-m", "pip", "install"] + if "CONDA_DEFAULT_ENV" not in os.environ and "VIRTUAL_ENV" not in os.environ and \ + (sys.platform != "win32" and os.getuid() != 0): # on unix and not running in root + ret.append("--user") # not in virtualenv or conda + return ret