save.py 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. # copyright (c) 2020 PaddlePaddle Authors. All Rights Reserved.
  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. import six
  15. import os
  16. import errno
  17. import warnings
  18. import six
  19. import numpy as np
  20. import paddle
  21. from paddle.fluid import layers
  22. from paddle.fluid import core
  23. from paddle.fluid import unique_name
  24. from paddle.fluid.executor import global_scope
  25. from paddle.fluid.compiler import CompiledProgram
  26. from paddle.fluid.framework import Program, Parameter, default_main_program, default_startup_program, Variable, \
  27. program_guard
  28. __all__ = ["save_mask_inference_model"]
  29. def _get_valid_program(main_program):
  30. if main_program is None:
  31. main_program = default_main_program()
  32. elif isinstance(main_program, CompiledProgram):
  33. main_program = main_program._program
  34. if main_program is None:
  35. raise TypeError("program should be as Program type or None")
  36. warnings.warn(
  37. "The input is a CompiledProgram, this is not recommended.")
  38. if not isinstance(main_program, Program):
  39. raise TypeError("program should be as Program type or None")
  40. return main_program
  41. def prepend_feed_ops(inference_program,
  42. feed_target_names,
  43. feed_holder_name='feed'):
  44. if len(feed_target_names) == 0:
  45. return
  46. global_block = inference_program.global_block()
  47. feed_var = global_block.create_var(
  48. name=feed_holder_name,
  49. type=core.VarDesc.VarType.FEED_MINIBATCH,
  50. persistable=True)
  51. for i, name in enumerate(feed_target_names):
  52. out = global_block.var(name)
  53. global_block._prepend_op(
  54. type='feed',
  55. inputs={'X': [feed_var]},
  56. outputs={'Out': [out]},
  57. attrs={'col': i})
  58. def append_fetch_ops(inference_program,
  59. fetch_target_names,
  60. fetch_holder_name='fetch'):
  61. global_block = inference_program.global_block()
  62. fetch_var = global_block.create_var(
  63. name=fetch_holder_name,
  64. type=core.VarDesc.VarType.FETCH_LIST,
  65. persistable=True)
  66. for i, name in enumerate(fetch_target_names):
  67. global_block.append_op(
  68. type='fetch',
  69. inputs={'X': [name]},
  70. outputs={'Out': [fetch_var]},
  71. attrs={'col': i})
  72. def _clone_var_in_block_(block, var):
  73. assert isinstance(var, Variable)
  74. if var.desc.type() == core.VarDesc.VarType.LOD_TENSOR:
  75. return block.create_var(
  76. name=var.name,
  77. shape=var.shape,
  78. dtype=var.dtype,
  79. type=var.type,
  80. lod_level=var.lod_level,
  81. persistable=True)
  82. else:
  83. return block.create_var(
  84. name=var.name,
  85. shape=var.shape,
  86. dtype=var.dtype,
  87. type=var.type,
  88. persistable=True)
  89. def save_vars(executor,
  90. dirname,
  91. main_program=None,
  92. vars=None,
  93. predicate=None,
  94. filename=None):
  95. """
  96. This API saves specific variables in the `Program` to files.
  97. There are two ways to specify the variables to be saved: set variables in
  98. a list and assign it to the `vars`, or use the `predicate` function to select
  99. variables that make `predicate(variable) == True`. The first way has a higher priority.
  100. The `dirname` is used to specify the folder where to save variables.
  101. If you prefer to save variables in separate files in the `dirname` floder,
  102. do not set `filename`. If you prefer to save all variables in a single file,
  103. use `filename` to specify it.
  104. Args:
  105. executor(Executor): The executor to run for saving variables.
  106. dirname(str, optional): The folder where to save variables.
  107. When you need to save the parameter to the memory, set it to None.
  108. main_program(Program, optional): The program whose variables will be saved.
  109. If it is None, the default main program will
  110. be used automatically.
  111. Default: None
  112. vars(list[Variable], optional): The list contains all variables to be saved.
  113. Default: None
  114. predicate(function, optional): The function selects the variables that make
  115. `predicate(variable) == True`.
  116. Default: None
  117. filename(str, optional): If you prefer to save all variables in a single file,
  118. use `filename` to specify it. Otherwise, let `filename` be None.
  119. Default: None
  120. Returns:
  121. str: When saving parameters to a file, returns None.
  122. When saving parameters to memory, returns a binary string containing parameters.
  123. Raises:
  124. TypeError: If `main_program` is not an instance of Program nor None.
  125. Examples:
  126. .. code-block:: python
  127. import paddle.fluid as fluid
  128. main_prog = fluid.Program()
  129. startup_prog = fluid.Program()
  130. with fluid.program_guard(main_prog, startup_prog):
  131. data = fluid.layers.data(name="img", shape=[64, 784], append_batch_size=False)
  132. w = fluid.layers.create_parameter(shape=[784, 200], dtype='float32', name='fc_w')
  133. b = fluid.layers.create_parameter(shape=[200], dtype='float32', name='fc_b')
  134. hidden_w = fluid.layers.matmul(x=data, y=w)
  135. hidden_b = fluid.layers.elementwise_add(hidden_w, b)
  136. place = fluid.CPUPlace()
  137. exe = fluid.Executor(place)
  138. exe.run(startup_prog)
  139. # The first usage: use `vars` to set the saved variables.
  140. var_list = [w, b]
  141. path = "./my_paddle_vars"
  142. fluid.io.save_vars(executor=exe, dirname=path, vars=var_list,
  143. filename="vars_file")
  144. # w and b will be save in a file named "var_file".
  145. # The second usage: use `predicate` to select the saved variable.
  146. def name_has_fc(var):
  147. res = "fc" in var.name
  148. return res
  149. param_path = "./my_paddle_model"
  150. fluid.io.save_vars(executor=exe, dirname=param_path, main_program=main_prog, vars=None, predicate = name_has_fc)
  151. # all variables whose names contain "fc " are saved.
  152. """
  153. save_to_memory = False
  154. if dirname is None and filename is None:
  155. save_to_memory = True
  156. main_program = _get_valid_program(main_program)
  157. if vars is None:
  158. return save_vars(
  159. executor,
  160. main_program=main_program,
  161. dirname=dirname,
  162. vars=list(filter(predicate, main_program.list_vars())),
  163. filename=filename)
  164. else:
  165. params_var_name = unique_name.generate("saved_params")
  166. # give warning when there is no var in model
  167. if len(list(vars)) == 0:
  168. warnings.warn(
  169. "no variable in your model, please ensure there are any variables in your model to save"
  170. )
  171. return None
  172. save_program = Program()
  173. save_block = save_program.global_block()
  174. save_var_map = {}
  175. for each_var in vars:
  176. # NOTE: don't save the variable which type is RAW
  177. if each_var.type == core.VarDesc.VarType.RAW:
  178. continue
  179. new_var = _clone_var_in_block_(save_block, each_var)
  180. if filename is None and save_to_memory is False:
  181. save_file_path = os.path.join(
  182. os.path.normpath(dirname), new_var.name)
  183. save_block.append_op(
  184. type='save',
  185. inputs={'X': [new_var]},
  186. outputs={},
  187. attrs={'file_path': os.path.normpath(save_file_path)})
  188. else:
  189. save_var_map[new_var.name] = new_var
  190. if filename is not None or save_to_memory:
  191. save_var_list = []
  192. for name in sorted(save_var_map.keys()):
  193. save_var_list.append(save_var_map[name])
  194. save_path = str()
  195. if save_to_memory is False:
  196. save_path = os.path.join(os.path.normpath(dirname), filename)
  197. saved_params = save_block.create_var(
  198. type=core.VarDesc.VarType.RAW, name=params_var_name)
  199. saved_params.desc.set_persistable(True)
  200. save_block.append_op(
  201. type='save_combine',
  202. inputs={'X': save_var_list},
  203. outputs={'Y': saved_params},
  204. attrs={
  205. 'file_path': save_path,
  206. 'save_to_memory': save_to_memory
  207. })
  208. #NOTE(zhiqiu): save op will add variable kLookupTablePath in save_program.desc,
  209. # which leads to diff on save_program and its desc. Call _sync_with_cpp
  210. # to keep consistency.
  211. save_program._sync_with_cpp()
  212. executor.run(save_program)
  213. if save_to_memory:
  214. return global_scope().find_var(params_var_name).get_bytes()
  215. def _save_distributed_persistables(executor, dirname, main_program):
  216. """
  217. save_persistables for distributed training.
  218. the method will do things listed below:
  219. 1.save part of persistable variables on trainer.
  220. 2.receive "remote prefetch variables" from parameter servers and merge them.
  221. 3.save "distributed lookup table" on parameter servers.
  222. 4.receive "optimizer variables" from parameter servers and merge them.
  223. Args:
  224. executor(Executor): The executor to run for saving parameters.
  225. dirname(str): The saving directory path.
  226. main_program(Program): The program whose parameters will be
  227. saved. the main_program must be the trainer_program
  228. get after transpiler.
  229. Returns:
  230. None
  231. Examples:
  232. .. code-block:: python
  233. import paddle.fluid as fluid
  234. exe = fluid.Executor(fluid.CPUPlace())
  235. param_path = "./my_paddle_model"
  236. t = distribute_transpiler.DistributeTranspiler()
  237. t.transpile(...)
  238. train_program = t.get_trainer_program()
  239. _save_distributed_persistables(executor=exe, dirname=param_path, main_program=train_program)
  240. """
  241. def __save_remote_params(executor, dirname, remote_params_map):
  242. """
  243. recive params on pserver through rpc.
  244. if the params are be sliced, will concat them to one, then save it.
  245. """
  246. if not remote_params_map:
  247. return
  248. prog = Program()
  249. block = prog.global_block()
  250. # recv optimize vars from pserver
  251. for name, remote_params in remote_params_map.items():
  252. origin = remote_params[0].origin
  253. is_slice = remote_params[0].is_slice
  254. slices = [None] * len(remote_params)
  255. slice_varnames = [None] * len(remote_params)
  256. remote_varnames = [None] * len(remote_params)
  257. endpoints = [None] * len(remote_params)
  258. for idx, optimizer in enumerate(remote_params):
  259. block_id = optimizer.block_id
  260. slice = optimizer.slice
  261. endpoint = optimizer.endpoint
  262. index = block_id if is_slice else idx
  263. slices[index] = slice
  264. slice_varnames[index] = "{}.slice.{}".format(slice.name, idx)
  265. remote_varnames[index] = slice.name
  266. endpoints[index] = endpoint
  267. slice_shapes = []
  268. for slice in slices:
  269. tmp = [str(dim) for dim in slice.shape]
  270. slice_shapes.append(",".join(tmp))
  271. block.append_op(
  272. type='recv_save',
  273. attrs={
  274. "trainer_id": 0,
  275. "shape": origin.shape,
  276. "slice_shapes": slice_shapes,
  277. "slice_varnames": slice_varnames,
  278. "remote_varnames": remote_varnames,
  279. "endpoints": endpoints,
  280. "file_path": os.path.join(dirname, origin.name)
  281. })
  282. executor.run(prog)
  283. def is_persistable(var):
  284. """
  285. Check whether the given variable is persistable.
  286. Args:
  287. var(Variable): The variable to be checked.
  288. Returns:
  289. bool: True if the given `var` is persistable
  290. False if not.
  291. Examples:
  292. .. code-block:: python
  293. import paddle.fluid as fluid
  294. param = fluid.default_main_program().global_block().var('fc.b')
  295. res = fluid.io.is_persistable(param)
  296. """
  297. if var.desc.type() == core.VarDesc.VarType.FEED_MINIBATCH or \
  298. var.desc.type() == core.VarDesc.VarType.FETCH_LIST or \
  299. var.desc.type() == core.VarDesc.VarType.READER:
  300. return False
  301. return var.persistable
  302. def save_persistables(executor, dirname, main_program=None, filename=None):
  303. """
  304. This operator saves all persistable variables from :code:`main_program` to
  305. the folder :code:`dirname` or file :code:`filename`. You can refer to
  306. :ref:`api_guide_model_save_reader_en` for more details. And then
  307. saves these persistables variables to the folder :code:`dirname` or file
  308. :code:`filename`.
  309. The :code:`dirname` is used to specify the folder where persistable variables
  310. are going to be saved. If you would like to save variables in separate
  311. files, set :code:`filename` None; if you would like to save all variables in a
  312. single file, use :code:`filename` to specify the file name.
  313. Args:
  314. executor(Executor): The executor to run for saving persistable variables.
  315. You can refer to :ref:`api_guide_executor_en` for
  316. more details.
  317. dirname(str, optional): The saving directory path.
  318. When you need to save the parameter to the memory, set it to None.
  319. main_program(Program, optional): The program whose persistbale variables will
  320. be saved. You can refer to
  321. :ref:`api_guide_Program_en` for more details.
  322. If it is None, the default main program will
  323. be used.
  324. Default: None.
  325. filename(str, optional): The file to save all variables. If you prefer to
  326. save variables in different files, set it to None.
  327. Default: None.
  328. Returns:
  329. str: When saving parameters to a file, returns None.
  330. When saving parameters to memory, returns a binary string containing parameters.
  331. Examples:
  332. .. code-block:: python
  333. import paddle.fluid as fluid
  334. dir_path = "./my_paddle_model"
  335. file_name = "persistables"
  336. image = fluid.data(name='img', shape=[None, 28, 28], dtype='float32')
  337. label = fluid.data(name='label', shape=[None, 1], dtype='int64')
  338. feeder = fluid.DataFeeder(feed_list=[image, label], place=fluid.CPUPlace())
  339. predict = fluid.layers.fc(input=image, size=10, act='softmax')
  340. loss = fluid.layers.cross_entropy(input=predict, label=label)
  341. avg_loss = fluid.layers.mean(loss)
  342. exe = fluid.Executor(fluid.CPUPlace())
  343. exe.run(fluid.default_startup_program())
  344. fluid.io.save_persistables(executor=exe, dirname=dir_path, filename=file_name)
  345. # The persistables variables weights and bias in the fc layer of the network
  346. # are going to be saved in the same file named "persistables" in the path
  347. # "./my_paddle_model"
  348. """
  349. if main_program and main_program._is_distributed:
  350. return _save_distributed_persistables(
  351. executor, dirname=dirname, main_program=main_program)
  352. else:
  353. return save_vars(
  354. executor,
  355. dirname=dirname,
  356. main_program=main_program,
  357. vars=None,
  358. predicate=is_persistable,
  359. filename=filename)
  360. def save_mask_inference_model(dirname,
  361. feeded_var_names,
  362. target_vars,
  363. executor,
  364. main_program=None,
  365. model_filename=None,
  366. params_filename=None,
  367. export_for_deployment=True,
  368. program_only=False):
  369. """
  370. Prune the given `main_program` to build a new program especially for inference,
  371. and then save it and all related parameters to given `dirname` .
  372. If you just want to save parameters of your trained model, please use the
  373. :ref:`api_fluid_io_save_params` . You can refer to :ref:`api_guide_model_save_reader_en`
  374. for more details.
  375. Note:
  376. The :code:`dirname` is used to specify the folder where inference model
  377. structure and parameters are going to be saved. If you would like to save params of
  378. Program in separate files, set `params_filename` None; if you would like to save all
  379. params of Program in a single file, use `params_filename` to specify the file name.
  380. Args:
  381. dirname(str): The directory path to save the inference model.
  382. feeded_var_names(list[str]): list of string. Names of variables that need to be feeded
  383. data during inference.
  384. target_vars(list[Variable]): list of Variable. Variables from which we can get
  385. inference results.
  386. executor(Executor): The executor that saves the inference model. You can refer
  387. to :ref:`api_guide_executor_en` for more details.
  388. main_program(Program, optional): The original program, which will be pruned to
  389. build the inference model. If is setted None,
  390. the global default :code:`_main_program_` will be used.
  391. Default: None.
  392. model_filename(str, optional): The name of file to save the inference program
  393. itself. If is setted None, a default filename
  394. :code:`__model__` will be used.
  395. params_filename(str, optional): The name of file to save all related parameters.
  396. If it is setted None, parameters will be saved
  397. in separate files .
  398. export_for_deployment(bool): If True, programs are modified to only support
  399. direct inference deployment. Otherwise,
  400. more information will be stored for flexible
  401. optimization and re-training. Currently, only
  402. True is supported.
  403. Default: True.
  404. program_only(bool, optional): If True, It will save inference program only, and do not
  405. save params of Program.
  406. Default: False.
  407. Returns:
  408. The fetch variables' name list
  409. Return Type:
  410. list
  411. Raises:
  412. ValueError: If `feed_var_names` is not a list of basestring, an exception is thrown.
  413. ValueError: If `target_vars` is not a list of Variable, an exception is thrown.
  414. Examples:
  415. .. code-block:: python
  416. import paddle.fluid as fluid
  417. path = "./infer_model"
  418. # User defined network, here a softmax regresssion example
  419. image = fluid.data(name='img', shape=[None, 28, 28], dtype='float32')
  420. label = fluid.data(name='label', shape=[None, 1], dtype='int64')
  421. feeder = fluid.DataFeeder(feed_list=[image, label], place=fluid.CPUPlace())
  422. predict = fluid.layers.fc(input=image, size=10, act='softmax')
  423. loss = fluid.layers.cross_entropy(input=predict, label=label)
  424. avg_loss = fluid.layers.mean(loss)
  425. exe = fluid.Executor(fluid.CPUPlace())
  426. exe.run(fluid.default_startup_program())
  427. # Feed data and train process
  428. # Save inference model. Note we don't save label and loss in this example
  429. fluid.io.save_inference_model(dirname=path,
  430. feeded_var_names=['img'],
  431. target_vars=[predict],
  432. executor=exe)
  433. # In this example, the save_inference_mode inference will prune the default
  434. # main program according to the network's input node (img) and output node(predict).
  435. # The pruned inference program is going to be saved in the "./infer_model/__model__"
  436. # and parameters are going to be saved in separate files under folder
  437. # "./infer_model".
  438. """
  439. if isinstance(feeded_var_names, six.string_types):
  440. feeded_var_names = [feeded_var_names]
  441. elif export_for_deployment:
  442. if len(feeded_var_names) > 0:
  443. # TODO(paddle-dev): polish these code blocks
  444. if not (bool(feeded_var_names) and all(
  445. isinstance(name, six.string_types)
  446. for name in feeded_var_names)):
  447. raise ValueError("'feed_var_names' should be a list of str.")
  448. if isinstance(target_vars, Variable):
  449. target_vars = [target_vars]
  450. elif export_for_deployment:
  451. if not (bool(target_vars) and
  452. all(isinstance(var, Variable) for var in target_vars)):
  453. raise ValueError("'target_vars' should be a list of Variable.")
  454. main_program = _get_valid_program(main_program)
  455. # remind user to set auc_states to zeros if the program contains auc op
  456. all_ops = main_program.global_block().ops
  457. for op in all_ops:
  458. if op.type == 'auc':
  459. warnings.warn(
  460. "please ensure that you have set the auc states to zeros before saving inference model"
  461. )
  462. break
  463. # fix the bug that the activation op's output as target will be pruned.
  464. # will affect the inference performance.
  465. # TODO(Superjomn) add an IR pass to remove 1-scale op.
  466. with program_guard(main_program):
  467. uniq_target_vars = []
  468. for i, var in enumerate(target_vars):
  469. if isinstance(var, Variable):
  470. var = layers.scale(
  471. var, 1., name="save_infer_model/scale_{}".format(i))
  472. uniq_target_vars.append(var)
  473. target_vars = uniq_target_vars
  474. target_var_name_list = [var.name for var in target_vars]
  475. # when a pserver and a trainer running on the same machine, mkdir may conflict
  476. save_dirname = dirname
  477. try:
  478. save_dirname = os.path.normpath(dirname)
  479. os.makedirs(save_dirname)
  480. except OSError as e:
  481. if e.errno != errno.EEXIST:
  482. raise
  483. if model_filename is not None:
  484. model_basename = os.path.basename(model_filename)
  485. else:
  486. model_basename = "__model__"
  487. model_basename = os.path.join(save_dirname, model_basename)
  488. # When export_for_deployment is true, we modify the program online so that
  489. # it can only be loaded for inference directly. If it's false, the whole
  490. # original program and related meta are saved so that future usage can be
  491. # more flexible.
  492. origin_program = main_program.clone()
  493. if export_for_deployment:
  494. main_program = main_program.clone()
  495. global_block = main_program.global_block()
  496. need_to_remove_op_index = []
  497. for i, op in enumerate(global_block.ops):
  498. op.desc.set_is_target(False)
  499. if op.type == "feed" or op.type == "fetch":
  500. need_to_remove_op_index.append(i)
  501. for index in need_to_remove_op_index[::-1]:
  502. global_block._remove_op(index)
  503. main_program.desc.flush()
  504. main_program = main_program._prune_with_input(
  505. feeded_var_names=feeded_var_names, targets=target_vars)
  506. main_program = main_program._inference_optimize(prune_read_op=True)
  507. fetch_var_names = [v.name for v in target_vars]
  508. prepend_feed_ops(main_program, feeded_var_names)
  509. append_fetch_ops(main_program, fetch_var_names)
  510. main_program.desc._set_version()
  511. paddle.fluid.core.save_op_compatible_info(main_program.desc)
  512. with open(model_basename, "wb") as f:
  513. f.write(main_program.desc.serialize_to_string())
  514. else:
  515. # TODO(panyx0718): Save more information so that it can also be used
  516. # for training and more flexible post-processing.
  517. with open(model_basename + ".main_program", "wb") as f:
  518. f.write(main_program.desc.serialize_to_string())
  519. if program_only:
  520. warnings.warn(
  521. "save_inference_model specified the param `program_only` to True, It will not save params of Program."
  522. )
  523. return target_var_name_list
  524. main_program._copy_dist_param_info_from(origin_program)
  525. if params_filename is not None:
  526. params_filename = os.path.basename(params_filename)
  527. save_persistables(executor, save_dirname, main_program, params_filename)
  528. return target_var_name_list