1# Copyright 2020-2021 Huawei Technologies Co., Ltd 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14# ============================================================================ 15"""Provide random seed api.""" 16from __future__ import absolute_import 17 18import numpy as np 19from mindspore import _checkparam as Validator 20 21# constants 22DEFAULT_GRAPH_SEED = 87654321 23_MAXINT32 = 2**31 - 1 24keyConstant = [3528531795, 2654435769, 3449720151, 3144134277] 25 26# set global RNG seed 27_GLOBAL_SEED = None 28_KERNEL_SEED = {} 29 30 31def _reset_op_seed(): 32 """ 33 Reset op seeds in the kernel's dictionary. 34 """ 35 for (kernel_name, op_seed) in _KERNEL_SEED: 36 _KERNEL_SEED[(kernel_name, op_seed)] = op_seed 37 38 39def set_seed(seed): 40 """ 41 Set global seed. 42 43 Note: 44 - The global seed is used by numpy.random, mindspore.common.Initializer and 45 mindspore.nn.probability.distribution. 46 47 - If global seed is not set, these packages will use their own default seed independently, numpy.random and 48 mindspore.common.Initializer will choose a random seed, mindspore.nn.probability.distribution will use zero. 49 50 - Seed set by numpy.random.seed() only used by numpy.random, while seed set by this API will also used by 51 numpy.random, so just set all seed by this API is recommended. 52 53 - In semi_auto_parallel/auto_parallel mode, when using set_seed, weights with same shape and same sharding 54 strategy in the same device would be initialized to the same result, otherwise, they would be initialized to 55 the different result. 56 57 Args: 58 seed (int): The seed to be set. 59 60 Raises: 61 ValueError: If seed is invalid (< 0). 62 TypeError: If seed isn't an int. 63 64 Examples: 65 >>> import numpy as np 66 >>> import mindspore as ms 67 >>> from mindspore import Tensor, set_seed, Parameter, ops 68 >>> from mindspore.common.initializer import initializer 69 >>> # Note: (1) Please make sure the code is running in PYNATIVE MODE; 70 >>> # (2) Because Composite-level ops need parameters to be Tensors, for below examples, 71 >>> # when using ops.uniform operator, minval and maxval are initialised as: 72 >>> minval = Tensor(1.0, ms.float32) 73 >>> maxval = Tensor(2.0, ms.float32) 74 >>> 75 >>> # 1. If global seed is not set, numpy.random and initializer will choose a random seed: 76 >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A1 77 >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A2 78 >>> w1 = Parameter(initializer("uniform", [2, 2], ms.float32), name="w1") # W1 79 >>> w1 = Parameter(initializer("uniform", [2, 2], ms.float32), name="w1") # W2 80 >>> # Rerun the program will get different results: 81 >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A3 82 >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A4 83 >>> w1 = Parameter(initializer("uniform", [2, 2], ms.float32), name="w1") # W3 84 >>> w1 = Parameter(initializer("uniform", [2, 2], ms.float32), name="w1") # W4 85 >>> 86 >>> # 2. If global seed is set, numpy.random and initializer will use it: 87 >>> set_seed(1234) 88 >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A1 89 >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A2 90 >>> w1 = Parameter(initializer("uniform", [2, 2], ms.float32), name="w1") # W1 91 >>> w1 = Parameter(initializer("uniform", [2, 2], ms.float32), name="w1") # W2 92 >>> # Rerun the program will get the same results: 93 >>> set_seed(1234) 94 >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A1 95 >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A2 96 >>> w1 = Parameter(initializer("uniform", [2, 2], ms.float32), name="w1") # W1 97 >>> w1 = Parameter(initializer("uniform", [2, 2], ms.float32), name="w1") # W2 98 >>> 99 >>> # 3. If neither global seed nor op seed is set, mindspore.ops.function.random_func and 100 >>> # mindspore.nn.probability.distribution will choose a random seed: 101 >>> c1 = ops.uniform((1, 4), minval, maxval) # C1 102 >>> c2 = ops.uniform((1, 4), minval, maxval) # C2 103 >>> # Rerun the program will get different results: 104 >>> c1 = ops.uniform((1, 4), minval, maxval) # C3 105 >>> c2 = ops.uniform((1, 4), minval, maxval) # C4 106 >>> 107 >>> # 4. If global seed is set, but op seed is not set, mindspore.ops.function.random_func and 108 >>> # mindspore.nn.probability.distribution will calculate a seed according to global seed and 109 >>> # default op seed. Each call will change the default op seed, thus each call get different 110 >>> # results. 111 >>> set_seed(1234) 112 >>> c1 = ops.uniform((1, 4), minval, maxval) # C1 113 >>> c2 = ops.uniform((1, 4), minval, maxval) # C2 114 >>> # Rerun the program will get the same results: 115 >>> set_seed(1234) 116 >>> c1 = ops.uniform((1, 4), minval, maxval) # C1 117 >>> c2 = ops.uniform((1, 4), minval, maxval) # C2 118 >>> 119 >>> # 5. If both global seed and op seed are set, mindspore.ops.function.random_func and 120 >>> # mindspore.nn.probability.distribution will calculate a seed according to global seed and 121 >>> # op seed counter. Each call will change the op seed counter, thus each call get different 122 >>> # results. 123 >>> set_seed(1234) 124 >>> c1 = ops.uniform((1, 4), minval, maxval, seed=2) # C1 125 >>> c2 = ops.uniform((1, 4), minval, maxval, seed=2) # C2 126 >>> # Rerun the program will get the same results: 127 >>> set_seed(1234) 128 >>> c1 = ops.uniform((1, 4), minval, maxval, seed=2) # C1 129 >>> c2 = ops.uniform((1, 4), minval, maxval, seed=2) # C2 130 >>> 131 >>> # 6. If op seed is set but global seed is not set, 0 will be used as global seed. Then 132 >>> # mindspore.ops.function.random_func and mindspore.nn.probability.distribution act as in 133 >>> # condition 5. 134 >>> c1 = ops.uniform((1, 4), minval, maxval, seed=2) # C1 135 >>> c2 = ops.uniform((1, 4), minval, maxval, seed=2) # C2 136 >>> # Rerun the program will get the different results: 137 >>> c1 = ops.uniform((1, 4), minval, maxval, seed=2) # C1 138 >>> c2 = ops.uniform((1, 4), minval, maxval, seed=2) # C2 139 >>> 140 >>> # 7. Recall set_seed() in the program will reset numpy seed and op seed counter of 141 >>> # mindspore.ops.function.random_func and mindspore.nn.probability.distribution. 142 >>> set_seed(1234) 143 >>> np_1 = np.random.normal(0, 1, [1]).astype(np.float32) # A1 144 >>> c1 = ops.uniform((1, 4), minval, maxval, seed=2) # C1 145 >>> set_seed(1234) 146 >>> np_2 = np.random.normal(0, 1, [1]).astype(np.float32) # still get A1 147 >>> c2 = ops.uniform((1, 4), minval, maxval, seed=2) # still get C1 148 """ 149 if not isinstance(seed, int): 150 raise TypeError("The argument 'seed' must be type of int, but got {}.".format(type(seed))) 151 Validator.check_non_negative_int(seed, "seed", "global_seed") 152 import mindspore.dataset as de 153 np.random.seed(seed) 154 de.config.set_seed(seed) 155 _reset_op_seed() 156 global _GLOBAL_SEED 157 _GLOBAL_SEED = seed 158 159 160def get_seed(): 161 """ 162 Get global seed. 163 164 Returns: 165 Integer. The global seed. 166 167 Examples: 168 >>> import mindspore as ms 169 >>> ms.set_seed(1234) 170 >>> seed = ms.get_seed() 171 >>> print(seed) 172 1234 173 """ 174 return _GLOBAL_SEED 175 176 177def _truncate_seed(seed): 178 """ 179 Truncate the seed with MAXINT32. 180 181 Args: 182 seed (int): The seed to be truncated. 183 184 Returns: 185 Integer. The seed with MAXINT32. 186 """ 187 return seed % _MAXINT32 188 189 190def _update_seeds(op_seed, kernel_name): 191 """ 192 Update the seed every time when the op seed is called. 193 194 Args: 195 op_seed (int): The op seed to be updated. 196 kernel_name (string): The random op kernel. 197 """ 198 global _KERNEL_SEED 199 if op_seed is not None: 200 _KERNEL_SEED[(kernel_name, op_seed)] = _KERNEL_SEED.get((kernel_name, op_seed)) + \ 201 (keyConstant[0] ^ keyConstant[2]) 202 203 204def _get_op_seed(op_seed, kernel_name): 205 """ 206 Get op seed which is relating to the specific kernel. 207 If the seed does not exist, add it into the kernel's dictionary. 208 209 Args: 210 op_seed (int): The op seed to be updated. 211 kernel_name (string): The random op kernel. 212 """ 213 if (kernel_name, op_seed) not in _KERNEL_SEED: 214 _KERNEL_SEED[(kernel_name, op_seed)] = op_seed 215 return _KERNEL_SEED[(kernel_name, op_seed)] 216 217 218def _get_graph_seed(op_seed, kernel_name): 219 """ 220 Get the graph-level seed. 221 Graph-level seed is used as a global variable, that can be used in different ops in case op-level seed is not set. 222 If op-level seed is 0, use graph-level seed; if graph-level seed is also 0, the system would generate a 223 random seed. 224 225 Note: 226 For each seed, either op-seed or graph-seed, a random sequence will be generated relating to this seed. 227 So, the state of the seed regarding to this op should be recorded. 228 A simple illustration should be: 229 If a random op is called twice within one program, the two results should be different: 230 minval = Tensor(1.0, mstype.float32) 231 maxval = Tensor(2.0, mstype.float32) 232 print(C.uniform((1, 4), minval, maxval, seed=1)) # generates 'A1' 233 print(C.uniform((1, 4), minval, maxval, seed=1)) # generates 'A2' 234 If the same program runs again, it repeat the results: 235 print(C.uniform((1, 4), minval, maxval, seed=1)) # generates 'A1' 236 print(C.uniform((1, 4), minval, maxval, seed=1)) # generates 'A2' 237 238 Returns: 239 Integer. The current graph-level seed. 240 241 Examples: 242 >>> print(_get_graph_seed(0, 'normal')) 243 (0, 0) 244 """ 245 global_seed = get_seed() 246 if global_seed == 0: 247 global_seed = DEFAULT_GRAPH_SEED 248 elif global_seed is None: 249 global_seed = 0 250 if op_seed is None: 251 op_seed = 0 252 # neither global seed or op seed is set, return (0, 0) to let kernel choose random seed. 253 if global_seed == 0 and op_seed == 0: 254 seeds = 0, 0 255 else: 256 Validator.check_non_negative_int(op_seed, "seed", kernel_name) 257 temp_seed = _get_op_seed(op_seed, kernel_name) 258 seeds = _truncate_seed(global_seed), _truncate_seed(temp_seed) 259 _update_seeds(op_seed, kernel_name) 260 return seeds 261