clarifai 11.3.0rc2__py3-none-any.whl → 11.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (300) hide show
  1. clarifai/__init__.py +1 -1
  2. clarifai/cli/__main__.py +1 -1
  3. clarifai/cli/base.py +144 -136
  4. clarifai/cli/compute_cluster.py +45 -31
  5. clarifai/cli/deployment.py +93 -76
  6. clarifai/cli/model.py +578 -180
  7. clarifai/cli/nodepool.py +100 -82
  8. clarifai/client/__init__.py +12 -2
  9. clarifai/client/app.py +973 -911
  10. clarifai/client/auth/helper.py +345 -342
  11. clarifai/client/auth/register.py +7 -7
  12. clarifai/client/auth/stub.py +107 -106
  13. clarifai/client/base.py +185 -178
  14. clarifai/client/compute_cluster.py +214 -180
  15. clarifai/client/dataset.py +793 -698
  16. clarifai/client/deployment.py +55 -50
  17. clarifai/client/input.py +1223 -1088
  18. clarifai/client/lister.py +47 -45
  19. clarifai/client/model.py +1939 -1717
  20. clarifai/client/model_client.py +525 -502
  21. clarifai/client/module.py +82 -73
  22. clarifai/client/nodepool.py +358 -213
  23. clarifai/client/runner.py +58 -0
  24. clarifai/client/search.py +342 -309
  25. clarifai/client/user.py +419 -414
  26. clarifai/client/workflow.py +294 -274
  27. clarifai/constants/dataset.py +11 -17
  28. clarifai/constants/model.py +8 -2
  29. clarifai/datasets/export/inputs_annotations.py +233 -217
  30. clarifai/datasets/upload/base.py +63 -51
  31. clarifai/datasets/upload/features.py +43 -38
  32. clarifai/datasets/upload/image.py +237 -207
  33. clarifai/datasets/upload/loaders/coco_captions.py +34 -32
  34. clarifai/datasets/upload/loaders/coco_detection.py +72 -65
  35. clarifai/datasets/upload/loaders/imagenet_classification.py +57 -53
  36. clarifai/datasets/upload/loaders/xview_detection.py +274 -132
  37. clarifai/datasets/upload/multimodal.py +55 -46
  38. clarifai/datasets/upload/text.py +55 -47
  39. clarifai/datasets/upload/utils.py +250 -234
  40. clarifai/errors.py +51 -50
  41. clarifai/models/api.py +260 -238
  42. clarifai/modules/css.py +50 -50
  43. clarifai/modules/pages.py +33 -33
  44. clarifai/rag/rag.py +312 -288
  45. clarifai/rag/utils.py +91 -84
  46. clarifai/runners/models/model_builder.py +906 -802
  47. clarifai/runners/models/model_class.py +370 -331
  48. clarifai/runners/models/model_run_locally.py +459 -419
  49. clarifai/runners/models/model_runner.py +170 -162
  50. clarifai/runners/models/model_servicer.py +78 -70
  51. clarifai/runners/server.py +111 -101
  52. clarifai/runners/utils/code_script.py +225 -187
  53. clarifai/runners/utils/const.py +4 -1
  54. clarifai/runners/utils/data_types/__init__.py +12 -0
  55. clarifai/runners/utils/data_types/data_types.py +598 -0
  56. clarifai/runners/utils/data_utils.py +387 -440
  57. clarifai/runners/utils/loader.py +247 -227
  58. clarifai/runners/utils/method_signatures.py +411 -386
  59. clarifai/runners/utils/openai_convertor.py +108 -109
  60. clarifai/runners/utils/serializers.py +175 -179
  61. clarifai/runners/utils/url_fetcher.py +35 -35
  62. clarifai/schema/search.py +56 -63
  63. clarifai/urls/helper.py +125 -102
  64. clarifai/utils/cli.py +129 -123
  65. clarifai/utils/config.py +127 -87
  66. clarifai/utils/constants.py +49 -0
  67. clarifai/utils/evaluation/helpers.py +503 -466
  68. clarifai/utils/evaluation/main.py +431 -393
  69. clarifai/utils/evaluation/testset_annotation_parser.py +154 -144
  70. clarifai/utils/logging.py +324 -306
  71. clarifai/utils/misc.py +60 -56
  72. clarifai/utils/model_train.py +165 -146
  73. clarifai/utils/protobuf.py +126 -103
  74. clarifai/versions.py +3 -1
  75. clarifai/workflows/export.py +48 -50
  76. clarifai/workflows/utils.py +39 -36
  77. clarifai/workflows/validate.py +55 -43
  78. {clarifai-11.3.0rc2.dist-info → clarifai-11.4.0.dist-info}/METADATA +16 -6
  79. clarifai-11.4.0.dist-info/RECORD +109 -0
  80. {clarifai-11.3.0rc2.dist-info → clarifai-11.4.0.dist-info}/WHEEL +1 -1
  81. clarifai/__pycache__/__init__.cpython-310.pyc +0 -0
  82. clarifai/__pycache__/__init__.cpython-311.pyc +0 -0
  83. clarifai/__pycache__/__init__.cpython-39.pyc +0 -0
  84. clarifai/__pycache__/errors.cpython-310.pyc +0 -0
  85. clarifai/__pycache__/errors.cpython-311.pyc +0 -0
  86. clarifai/__pycache__/versions.cpython-310.pyc +0 -0
  87. clarifai/__pycache__/versions.cpython-311.pyc +0 -0
  88. clarifai/cli/__pycache__/__init__.cpython-310.pyc +0 -0
  89. clarifai/cli/__pycache__/__init__.cpython-311.pyc +0 -0
  90. clarifai/cli/__pycache__/base.cpython-310.pyc +0 -0
  91. clarifai/cli/__pycache__/base.cpython-311.pyc +0 -0
  92. clarifai/cli/__pycache__/base_cli.cpython-310.pyc +0 -0
  93. clarifai/cli/__pycache__/compute_cluster.cpython-310.pyc +0 -0
  94. clarifai/cli/__pycache__/compute_cluster.cpython-311.pyc +0 -0
  95. clarifai/cli/__pycache__/deployment.cpython-310.pyc +0 -0
  96. clarifai/cli/__pycache__/deployment.cpython-311.pyc +0 -0
  97. clarifai/cli/__pycache__/model.cpython-310.pyc +0 -0
  98. clarifai/cli/__pycache__/model.cpython-311.pyc +0 -0
  99. clarifai/cli/__pycache__/model_cli.cpython-310.pyc +0 -0
  100. clarifai/cli/__pycache__/nodepool.cpython-310.pyc +0 -0
  101. clarifai/cli/__pycache__/nodepool.cpython-311.pyc +0 -0
  102. clarifai/client/__pycache__/__init__.cpython-310.pyc +0 -0
  103. clarifai/client/__pycache__/__init__.cpython-311.pyc +0 -0
  104. clarifai/client/__pycache__/__init__.cpython-39.pyc +0 -0
  105. clarifai/client/__pycache__/app.cpython-310.pyc +0 -0
  106. clarifai/client/__pycache__/app.cpython-311.pyc +0 -0
  107. clarifai/client/__pycache__/app.cpython-39.pyc +0 -0
  108. clarifai/client/__pycache__/base.cpython-310.pyc +0 -0
  109. clarifai/client/__pycache__/base.cpython-311.pyc +0 -0
  110. clarifai/client/__pycache__/compute_cluster.cpython-310.pyc +0 -0
  111. clarifai/client/__pycache__/compute_cluster.cpython-311.pyc +0 -0
  112. clarifai/client/__pycache__/dataset.cpython-310.pyc +0 -0
  113. clarifai/client/__pycache__/dataset.cpython-311.pyc +0 -0
  114. clarifai/client/__pycache__/deployment.cpython-310.pyc +0 -0
  115. clarifai/client/__pycache__/deployment.cpython-311.pyc +0 -0
  116. clarifai/client/__pycache__/input.cpython-310.pyc +0 -0
  117. clarifai/client/__pycache__/input.cpython-311.pyc +0 -0
  118. clarifai/client/__pycache__/lister.cpython-310.pyc +0 -0
  119. clarifai/client/__pycache__/lister.cpython-311.pyc +0 -0
  120. clarifai/client/__pycache__/model.cpython-310.pyc +0 -0
  121. clarifai/client/__pycache__/model.cpython-311.pyc +0 -0
  122. clarifai/client/__pycache__/module.cpython-310.pyc +0 -0
  123. clarifai/client/__pycache__/module.cpython-311.pyc +0 -0
  124. clarifai/client/__pycache__/nodepool.cpython-310.pyc +0 -0
  125. clarifai/client/__pycache__/nodepool.cpython-311.pyc +0 -0
  126. clarifai/client/__pycache__/search.cpython-310.pyc +0 -0
  127. clarifai/client/__pycache__/search.cpython-311.pyc +0 -0
  128. clarifai/client/__pycache__/user.cpython-310.pyc +0 -0
  129. clarifai/client/__pycache__/user.cpython-311.pyc +0 -0
  130. clarifai/client/__pycache__/workflow.cpython-310.pyc +0 -0
  131. clarifai/client/__pycache__/workflow.cpython-311.pyc +0 -0
  132. clarifai/client/auth/__pycache__/__init__.cpython-310.pyc +0 -0
  133. clarifai/client/auth/__pycache__/__init__.cpython-311.pyc +0 -0
  134. clarifai/client/auth/__pycache__/helper.cpython-310.pyc +0 -0
  135. clarifai/client/auth/__pycache__/helper.cpython-311.pyc +0 -0
  136. clarifai/client/auth/__pycache__/register.cpython-310.pyc +0 -0
  137. clarifai/client/auth/__pycache__/register.cpython-311.pyc +0 -0
  138. clarifai/client/auth/__pycache__/stub.cpython-310.pyc +0 -0
  139. clarifai/client/auth/__pycache__/stub.cpython-311.pyc +0 -0
  140. clarifai/client/cli/__init__.py +0 -0
  141. clarifai/client/cli/__pycache__/__init__.cpython-310.pyc +0 -0
  142. clarifai/client/cli/__pycache__/base_cli.cpython-310.pyc +0 -0
  143. clarifai/client/cli/__pycache__/model_cli.cpython-310.pyc +0 -0
  144. clarifai/client/cli/base_cli.py +0 -88
  145. clarifai/client/cli/model_cli.py +0 -29
  146. clarifai/constants/__pycache__/base.cpython-310.pyc +0 -0
  147. clarifai/constants/__pycache__/base.cpython-311.pyc +0 -0
  148. clarifai/constants/__pycache__/dataset.cpython-310.pyc +0 -0
  149. clarifai/constants/__pycache__/dataset.cpython-311.pyc +0 -0
  150. clarifai/constants/__pycache__/input.cpython-310.pyc +0 -0
  151. clarifai/constants/__pycache__/input.cpython-311.pyc +0 -0
  152. clarifai/constants/__pycache__/model.cpython-310.pyc +0 -0
  153. clarifai/constants/__pycache__/model.cpython-311.pyc +0 -0
  154. clarifai/constants/__pycache__/rag.cpython-310.pyc +0 -0
  155. clarifai/constants/__pycache__/rag.cpython-311.pyc +0 -0
  156. clarifai/constants/__pycache__/search.cpython-310.pyc +0 -0
  157. clarifai/constants/__pycache__/search.cpython-311.pyc +0 -0
  158. clarifai/constants/__pycache__/workflow.cpython-310.pyc +0 -0
  159. clarifai/constants/__pycache__/workflow.cpython-311.pyc +0 -0
  160. clarifai/datasets/__pycache__/__init__.cpython-310.pyc +0 -0
  161. clarifai/datasets/__pycache__/__init__.cpython-311.pyc +0 -0
  162. clarifai/datasets/__pycache__/__init__.cpython-39.pyc +0 -0
  163. clarifai/datasets/export/__pycache__/__init__.cpython-310.pyc +0 -0
  164. clarifai/datasets/export/__pycache__/__init__.cpython-311.pyc +0 -0
  165. clarifai/datasets/export/__pycache__/__init__.cpython-39.pyc +0 -0
  166. clarifai/datasets/export/__pycache__/inputs_annotations.cpython-310.pyc +0 -0
  167. clarifai/datasets/export/__pycache__/inputs_annotations.cpython-311.pyc +0 -0
  168. clarifai/datasets/upload/__pycache__/__init__.cpython-310.pyc +0 -0
  169. clarifai/datasets/upload/__pycache__/__init__.cpython-311.pyc +0 -0
  170. clarifai/datasets/upload/__pycache__/__init__.cpython-39.pyc +0 -0
  171. clarifai/datasets/upload/__pycache__/base.cpython-310.pyc +0 -0
  172. clarifai/datasets/upload/__pycache__/base.cpython-311.pyc +0 -0
  173. clarifai/datasets/upload/__pycache__/features.cpython-310.pyc +0 -0
  174. clarifai/datasets/upload/__pycache__/features.cpython-311.pyc +0 -0
  175. clarifai/datasets/upload/__pycache__/image.cpython-310.pyc +0 -0
  176. clarifai/datasets/upload/__pycache__/image.cpython-311.pyc +0 -0
  177. clarifai/datasets/upload/__pycache__/multimodal.cpython-310.pyc +0 -0
  178. clarifai/datasets/upload/__pycache__/multimodal.cpython-311.pyc +0 -0
  179. clarifai/datasets/upload/__pycache__/text.cpython-310.pyc +0 -0
  180. clarifai/datasets/upload/__pycache__/text.cpython-311.pyc +0 -0
  181. clarifai/datasets/upload/__pycache__/utils.cpython-310.pyc +0 -0
  182. clarifai/datasets/upload/__pycache__/utils.cpython-311.pyc +0 -0
  183. clarifai/datasets/upload/loaders/__pycache__/__init__.cpython-311.pyc +0 -0
  184. clarifai/datasets/upload/loaders/__pycache__/__init__.cpython-39.pyc +0 -0
  185. clarifai/datasets/upload/loaders/__pycache__/coco_detection.cpython-311.pyc +0 -0
  186. clarifai/datasets/upload/loaders/__pycache__/imagenet_classification.cpython-311.pyc +0 -0
  187. clarifai/models/__pycache__/__init__.cpython-39.pyc +0 -0
  188. clarifai/modules/__pycache__/__init__.cpython-39.pyc +0 -0
  189. clarifai/rag/__pycache__/__init__.cpython-310.pyc +0 -0
  190. clarifai/rag/__pycache__/__init__.cpython-311.pyc +0 -0
  191. clarifai/rag/__pycache__/__init__.cpython-39.pyc +0 -0
  192. clarifai/rag/__pycache__/rag.cpython-310.pyc +0 -0
  193. clarifai/rag/__pycache__/rag.cpython-311.pyc +0 -0
  194. clarifai/rag/__pycache__/rag.cpython-39.pyc +0 -0
  195. clarifai/rag/__pycache__/utils.cpython-310.pyc +0 -0
  196. clarifai/rag/__pycache__/utils.cpython-311.pyc +0 -0
  197. clarifai/runners/__pycache__/__init__.cpython-310.pyc +0 -0
  198. clarifai/runners/__pycache__/__init__.cpython-311.pyc +0 -0
  199. clarifai/runners/__pycache__/__init__.cpython-39.pyc +0 -0
  200. clarifai/runners/dockerfile_template/Dockerfile.cpu.template +0 -31
  201. clarifai/runners/dockerfile_template/Dockerfile.cuda.template +0 -42
  202. clarifai/runners/dockerfile_template/Dockerfile.nim +0 -71
  203. clarifai/runners/models/__pycache__/__init__.cpython-310.pyc +0 -0
  204. clarifai/runners/models/__pycache__/__init__.cpython-311.pyc +0 -0
  205. clarifai/runners/models/__pycache__/__init__.cpython-39.pyc +0 -0
  206. clarifai/runners/models/__pycache__/base_typed_model.cpython-310.pyc +0 -0
  207. clarifai/runners/models/__pycache__/base_typed_model.cpython-311.pyc +0 -0
  208. clarifai/runners/models/__pycache__/base_typed_model.cpython-39.pyc +0 -0
  209. clarifai/runners/models/__pycache__/model_builder.cpython-311.pyc +0 -0
  210. clarifai/runners/models/__pycache__/model_class.cpython-310.pyc +0 -0
  211. clarifai/runners/models/__pycache__/model_class.cpython-311.pyc +0 -0
  212. clarifai/runners/models/__pycache__/model_run_locally.cpython-310-pytest-7.1.2.pyc +0 -0
  213. clarifai/runners/models/__pycache__/model_run_locally.cpython-310.pyc +0 -0
  214. clarifai/runners/models/__pycache__/model_run_locally.cpython-311.pyc +0 -0
  215. clarifai/runners/models/__pycache__/model_runner.cpython-310.pyc +0 -0
  216. clarifai/runners/models/__pycache__/model_runner.cpython-311.pyc +0 -0
  217. clarifai/runners/models/__pycache__/model_upload.cpython-310.pyc +0 -0
  218. clarifai/runners/models/base_typed_model.py +0 -238
  219. clarifai/runners/models/model_class_refract.py +0 -80
  220. clarifai/runners/models/model_upload.py +0 -607
  221. clarifai/runners/models/temp.py +0 -25
  222. clarifai/runners/utils/__pycache__/__init__.cpython-310.pyc +0 -0
  223. clarifai/runners/utils/__pycache__/__init__.cpython-311.pyc +0 -0
  224. clarifai/runners/utils/__pycache__/__init__.cpython-38.pyc +0 -0
  225. clarifai/runners/utils/__pycache__/__init__.cpython-39.pyc +0 -0
  226. clarifai/runners/utils/__pycache__/buffered_stream.cpython-310.pyc +0 -0
  227. clarifai/runners/utils/__pycache__/buffered_stream.cpython-38.pyc +0 -0
  228. clarifai/runners/utils/__pycache__/buffered_stream.cpython-39.pyc +0 -0
  229. clarifai/runners/utils/__pycache__/const.cpython-310.pyc +0 -0
  230. clarifai/runners/utils/__pycache__/const.cpython-311.pyc +0 -0
  231. clarifai/runners/utils/__pycache__/constants.cpython-310.pyc +0 -0
  232. clarifai/runners/utils/__pycache__/constants.cpython-38.pyc +0 -0
  233. clarifai/runners/utils/__pycache__/constants.cpython-39.pyc +0 -0
  234. clarifai/runners/utils/__pycache__/data_handler.cpython-310.pyc +0 -0
  235. clarifai/runners/utils/__pycache__/data_handler.cpython-311.pyc +0 -0
  236. clarifai/runners/utils/__pycache__/data_handler.cpython-38.pyc +0 -0
  237. clarifai/runners/utils/__pycache__/data_handler.cpython-39.pyc +0 -0
  238. clarifai/runners/utils/__pycache__/data_utils.cpython-310.pyc +0 -0
  239. clarifai/runners/utils/__pycache__/data_utils.cpython-311.pyc +0 -0
  240. clarifai/runners/utils/__pycache__/data_utils.cpython-38.pyc +0 -0
  241. clarifai/runners/utils/__pycache__/data_utils.cpython-39.pyc +0 -0
  242. clarifai/runners/utils/__pycache__/grpc_server.cpython-310.pyc +0 -0
  243. clarifai/runners/utils/__pycache__/grpc_server.cpython-38.pyc +0 -0
  244. clarifai/runners/utils/__pycache__/grpc_server.cpython-39.pyc +0 -0
  245. clarifai/runners/utils/__pycache__/health.cpython-310.pyc +0 -0
  246. clarifai/runners/utils/__pycache__/health.cpython-38.pyc +0 -0
  247. clarifai/runners/utils/__pycache__/health.cpython-39.pyc +0 -0
  248. clarifai/runners/utils/__pycache__/loader.cpython-310.pyc +0 -0
  249. clarifai/runners/utils/__pycache__/loader.cpython-311.pyc +0 -0
  250. clarifai/runners/utils/__pycache__/logging.cpython-310.pyc +0 -0
  251. clarifai/runners/utils/__pycache__/logging.cpython-38.pyc +0 -0
  252. clarifai/runners/utils/__pycache__/logging.cpython-39.pyc +0 -0
  253. clarifai/runners/utils/__pycache__/stream_source.cpython-310.pyc +0 -0
  254. clarifai/runners/utils/__pycache__/stream_source.cpython-39.pyc +0 -0
  255. clarifai/runners/utils/__pycache__/url_fetcher.cpython-310.pyc +0 -0
  256. clarifai/runners/utils/__pycache__/url_fetcher.cpython-311.pyc +0 -0
  257. clarifai/runners/utils/__pycache__/url_fetcher.cpython-38.pyc +0 -0
  258. clarifai/runners/utils/__pycache__/url_fetcher.cpython-39.pyc +0 -0
  259. clarifai/runners/utils/data_handler.py +0 -231
  260. clarifai/runners/utils/data_handler_refract.py +0 -213
  261. clarifai/runners/utils/data_types.py +0 -469
  262. clarifai/runners/utils/logger.py +0 -0
  263. clarifai/runners/utils/openai_format.py +0 -87
  264. clarifai/schema/__pycache__/search.cpython-310.pyc +0 -0
  265. clarifai/schema/__pycache__/search.cpython-311.pyc +0 -0
  266. clarifai/urls/__pycache__/helper.cpython-310.pyc +0 -0
  267. clarifai/urls/__pycache__/helper.cpython-311.pyc +0 -0
  268. clarifai/utils/__pycache__/__init__.cpython-310.pyc +0 -0
  269. clarifai/utils/__pycache__/__init__.cpython-311.pyc +0 -0
  270. clarifai/utils/__pycache__/__init__.cpython-39.pyc +0 -0
  271. clarifai/utils/__pycache__/cli.cpython-310.pyc +0 -0
  272. clarifai/utils/__pycache__/cli.cpython-311.pyc +0 -0
  273. clarifai/utils/__pycache__/config.cpython-311.pyc +0 -0
  274. clarifai/utils/__pycache__/constants.cpython-310.pyc +0 -0
  275. clarifai/utils/__pycache__/constants.cpython-311.pyc +0 -0
  276. clarifai/utils/__pycache__/logging.cpython-310.pyc +0 -0
  277. clarifai/utils/__pycache__/logging.cpython-311.pyc +0 -0
  278. clarifai/utils/__pycache__/misc.cpython-310.pyc +0 -0
  279. clarifai/utils/__pycache__/misc.cpython-311.pyc +0 -0
  280. clarifai/utils/__pycache__/model_train.cpython-310.pyc +0 -0
  281. clarifai/utils/__pycache__/model_train.cpython-311.pyc +0 -0
  282. clarifai/utils/__pycache__/protobuf.cpython-311.pyc +0 -0
  283. clarifai/utils/evaluation/__pycache__/__init__.cpython-311.pyc +0 -0
  284. clarifai/utils/evaluation/__pycache__/__init__.cpython-39.pyc +0 -0
  285. clarifai/utils/evaluation/__pycache__/helpers.cpython-311.pyc +0 -0
  286. clarifai/utils/evaluation/__pycache__/main.cpython-311.pyc +0 -0
  287. clarifai/utils/evaluation/__pycache__/main.cpython-39.pyc +0 -0
  288. clarifai/workflows/__pycache__/__init__.cpython-310.pyc +0 -0
  289. clarifai/workflows/__pycache__/__init__.cpython-311.pyc +0 -0
  290. clarifai/workflows/__pycache__/__init__.cpython-39.pyc +0 -0
  291. clarifai/workflows/__pycache__/export.cpython-310.pyc +0 -0
  292. clarifai/workflows/__pycache__/export.cpython-311.pyc +0 -0
  293. clarifai/workflows/__pycache__/utils.cpython-310.pyc +0 -0
  294. clarifai/workflows/__pycache__/utils.cpython-311.pyc +0 -0
  295. clarifai/workflows/__pycache__/validate.cpython-310.pyc +0 -0
  296. clarifai/workflows/__pycache__/validate.cpython-311.pyc +0 -0
  297. clarifai-11.3.0rc2.dist-info/RECORD +0 -322
  298. {clarifai-11.3.0rc2.dist-info → clarifai-11.4.0.dist-info}/entry_points.txt +0 -0
  299. {clarifai-11.3.0rc2.dist-info → clarifai-11.4.0.dist-info/licenses}/LICENSE +0 -0
  300. {clarifai-11.3.0rc2.dist-info → clarifai-11.4.0.dist-info}/top_level.txt +0 -0
@@ -11,436 +11,476 @@ import traceback
11
11
  import venv
12
12
 
13
13
  from clarifai_grpc.grpc.api import resources_pb2, service_pb2
14
+
14
15
  from clarifai.runners.models.model_builder import ModelBuilder
15
16
  from clarifai.utils.logging import logger
16
17
 
17
18
 
18
19
  class ModelRunLocally:
20
+ def __init__(self, model_path):
21
+ self.model_path = model_path
22
+ self.requirements_file = os.path.join(self.model_path, "requirements.txt")
23
+
24
+ # ModelBuilder contains multiple useful methods to interact with the model
25
+ self.builder = ModelBuilder(self.model_path, download_validation_only=True)
26
+ self.config = self.builder.config
27
+
28
+ def _requirements_hash(self):
29
+ """Generate a hash of the requirements file."""
30
+ with open(self.requirements_file, "r") as f:
31
+ return hashlib.md5(f.read().encode('utf-8')).hexdigest()
32
+
33
+ def _get_env_executable(self):
34
+ """Get the python executable from the virtual environment."""
35
+ # Depending on the platform, venv scripts are placed in either "Scripts" (Windows) or "bin" (Linux/Mac)
36
+ if platform.system().lower().startswith("win"):
37
+ scripts_folder = "Scripts"
38
+ python_exe = "python.exe"
39
+ pip_exe = "pip.exe"
40
+ else:
41
+ scripts_folder = "bin"
42
+ python_exe = "python"
43
+ pip_exe = "pip"
19
44
 
20
- def __init__(self, model_path):
21
- self.model_path = model_path
22
- self.requirements_file = os.path.join(self.model_path, "requirements.txt")
23
-
24
- # ModelBuilder contains multiple useful methods to interact with the model
25
- self.builder = ModelBuilder(self.model_path, download_validation_only=True)
26
- self.config = self.builder.config
27
-
28
- def _requirements_hash(self):
29
- """Generate a hash of the requirements file."""
30
- with open(self.requirements_file, "r") as f:
31
- return hashlib.md5(f.read().encode('utf-8')).hexdigest()
32
-
33
- def _get_env_executable(self):
34
- """Get the python executable from the virtual environment."""
35
- # Depending on the platform, venv scripts are placed in either "Scripts" (Windows) or "bin" (Linux/Mac)
36
- if platform.system().lower().startswith("win"):
37
- scripts_folder = "Scripts"
38
- python_exe = "python.exe"
39
- pip_exe = "pip.exe"
40
- else:
41
- scripts_folder = "bin"
42
- python_exe = "python"
43
- pip_exe = "pip"
45
+ self.python_executable = os.path.join(self.venv_dir, scripts_folder, python_exe)
46
+ self.pip_executable = os.path.join(self.venv_dir, scripts_folder, pip_exe)
44
47
 
45
- self.python_executable = os.path.join(self.venv_dir, scripts_folder, python_exe)
46
- self.pip_executable = os.path.join(self.venv_dir, scripts_folder, pip_exe)
48
+ return self.python_executable, self.pip_executable
47
49
 
48
- return self.python_executable, self.pip_executable
50
+ def create_temp_venv(self):
51
+ """Create a temporary virtual environment."""
52
+ requirements_hash = self._requirements_hash()
49
53
 
50
- def create_temp_venv(self):
51
- """Create a temporary virtual environment."""
52
- requirements_hash = self._requirements_hash()
54
+ temp_dir = os.path.join(tempfile.gettempdir(), str(requirements_hash))
55
+ venv_dir = os.path.join(temp_dir, "venv")
53
56
 
54
- temp_dir = os.path.join(tempfile.gettempdir(), str(requirements_hash))
55
- venv_dir = os.path.join(temp_dir, "venv")
57
+ if os.path.exists(temp_dir):
58
+ logger.info(f"Using previous virtual environment at {temp_dir}")
59
+ use_existing_venv = True
60
+ else:
61
+ logger.info("Creating temporary virtual environment...")
62
+ use_existing_venv = False
63
+ venv.create(venv_dir, with_pip=True)
64
+ logger.info(f"Created temporary virtual environment at {venv_dir}")
56
65
 
57
- if os.path.exists(temp_dir):
58
- logger.info(f"Using previous virtual environment at {temp_dir}")
59
- use_existing_venv = True
60
- else:
61
- logger.info("Creating temporary virtual environment...")
62
- use_existing_venv = False
63
- venv.create(venv_dir, with_pip=True)
64
- logger.info(f"Created temporary virtual environment at {venv_dir}")
65
-
66
- self.venv_dir = venv_dir
67
- self.temp_dir = temp_dir
68
- self.python_executable, self.pip_executable = self._get_env_executable()
69
-
70
- return use_existing_venv
71
-
72
- def install_requirements(self):
73
- """Install the dependencies from requirements.txt and Clarifai."""
74
- _, pip_executable = self._get_env_executable()
75
- try:
76
- logger.info(
77
- f"Installing requirements from {self.requirements_file}... in the virtual environment {self.venv_dir}"
78
- )
79
- subprocess.check_call([pip_executable, "install", "-r", self.requirements_file])
80
- logger.info("Installing Clarifai package...")
81
- subprocess.check_call([pip_executable, "install", "clarifai"])
82
- logger.info("Requirements installed successfully!")
83
- except subprocess.CalledProcessError as e:
84
- logger.error(f"Error installing requirements: {e}")
85
- self.clean_up()
86
- sys.exit(1)
87
-
88
- def _build_request(self):
89
- """Create a mock inference request for testing the model."""
90
-
91
- model_version_proto = self.builder.get_model_version_proto()
92
- model_version_proto.id = "model_version"
93
-
94
- return service_pb2.PostModelOutputsRequest(
95
- model=resources_pb2.Model(model_version=model_version_proto),
96
- inputs=[
97
- resources_pb2.Input(data=resources_pb2.Data(
98
- text=resources_pb2.Text(raw="How many people live in new york?"),
99
- image=resources_pb2.Image(url="https://samples.clarifai.com/metro-north.jpg"),
100
- audio=resources_pb2.Audio(url="https://samples.clarifai.com/GoodMorning.wav"),
101
- video=resources_pb2.Video(url="https://samples.clarifai.com/beer.mp4"),
102
- ))
103
- ],
66
+ self.venv_dir = venv_dir
67
+ self.temp_dir = temp_dir
68
+ self.python_executable, self.pip_executable = self._get_env_executable()
69
+
70
+ return use_existing_venv
71
+
72
+ def install_requirements(self):
73
+ """Install the dependencies from requirements.txt and Clarifai."""
74
+ _, pip_executable = self._get_env_executable()
75
+ try:
76
+ logger.info(
77
+ f"Installing requirements from {self.requirements_file}... in the virtual environment {self.venv_dir}"
78
+ )
79
+ subprocess.check_call([pip_executable, "install", "-r", self.requirements_file])
80
+ logger.info("Installing Clarifai package...")
81
+ subprocess.check_call([pip_executable, "install", "clarifai"])
82
+ logger.info("Requirements installed successfully!")
83
+ except subprocess.CalledProcessError as e:
84
+ logger.error(f"Error installing requirements: {e}")
85
+ self.clean_up()
86
+ sys.exit(1)
87
+
88
+ def _build_request(self):
89
+ """Create a mock inference request for testing the model."""
90
+
91
+ model_version_proto = self.builder.get_model_version_proto()
92
+ model_version_proto.id = "model_version"
93
+
94
+ return service_pb2.PostModelOutputsRequest(
95
+ model=resources_pb2.Model(model_version=model_version_proto),
96
+ inputs=[
97
+ resources_pb2.Input(
98
+ data=resources_pb2.Data(
99
+ text=resources_pb2.Text(raw="How many people live in new york?"),
100
+ image=resources_pb2.Image(
101
+ url="https://samples.clarifai.com/metro-north.jpg"
102
+ ),
103
+ audio=resources_pb2.Audio(
104
+ url="https://samples.clarifai.com/GoodMorning.wav"
105
+ ),
106
+ video=resources_pb2.Video(url="https://samples.clarifai.com/beer.mp4"),
107
+ )
108
+ )
109
+ ],
110
+ )
111
+
112
+ def _run_test(self):
113
+ """Test the model locally by making a prediction."""
114
+ # Create the model
115
+ model = self.builder.create_model_instance()
116
+ # call its test method, if it has one
117
+ if hasattr(model, "test"):
118
+ try:
119
+ model.test()
120
+ logger.info("Model tested successfully!")
121
+ except Exception as e:
122
+ logger.error(f"Error occurred while testing the model: {e}")
123
+ traceback.print_exc()
124
+
125
+ def test_model(self):
126
+ """Test the model by running it locally in the virtual environment."""
127
+
128
+ import_path = repr(os.path.dirname(os.path.abspath(__file__)))
129
+ model_path = repr(self.model_path)
130
+
131
+ command_string = (
132
+ f"import sys; "
133
+ f"sys.path.append({import_path}); "
134
+ f"from model_run_locally import ModelRunLocally; "
135
+ f"ModelRunLocally({model_path})._run_test()"
136
+ )
137
+
138
+ command = [self.python_executable, "-c", command_string]
139
+ process = None
140
+ try:
141
+ logger.info("Testing the model locally...")
142
+ process = subprocess.Popen(command)
143
+ # Wait for the process to complete
144
+ process.wait()
145
+ if process.returncode == 0:
146
+ logger.info("Model tested successfully!")
147
+ if process.returncode != 0:
148
+ raise subprocess.CalledProcessError(process.returncode, command)
149
+ except subprocess.CalledProcessError as e:
150
+ logger.error(f"Error testing the model: {e}")
151
+ sys.exit(1)
152
+ except Exception as e:
153
+ logger.error(f"Unexpected error: {e}")
154
+ sys.exit(1)
155
+ finally:
156
+ # After the function runs, check if the process is still running
157
+ if process and process.poll() is None:
158
+ logger.info("Process is still running. Terminating process.")
159
+ process.terminate()
160
+ try:
161
+ process.wait(timeout=5)
162
+ except subprocess.TimeoutExpired:
163
+ logger.info("Process did not terminate gracefully. Killing process.")
164
+ # Kill the process if it doesn't terminate after 5 seconds
165
+ process.kill()
166
+
167
+ # run the model server
168
+ def run_model_server(self, port=8080):
169
+ """Run the Clarifai Runners's model server."""
170
+
171
+ command = [
172
+ self.python_executable,
173
+ "-m",
174
+ "clarifai.runners.server",
175
+ "--model_path",
176
+ self.model_path,
177
+ "--grpc",
178
+ "--port",
179
+ str(port),
180
+ ]
181
+ try:
182
+ logger.info(
183
+ f"Starting model server at localhost:{port} with the model at {self.model_path}..."
184
+ )
185
+ subprocess.check_call(command)
186
+ logger.info("Model server started successfully and running at localhost:{port}")
187
+ except subprocess.CalledProcessError as e:
188
+ logger.error(f"Error running model server: {e}")
189
+ self.clean_up()
190
+ sys.exit(1)
191
+
192
+ def _docker_hash(self):
193
+ """Generate a hash of the combined requirements file and Dockefile"""
194
+ with open(self.requirements_file, "r") as f:
195
+ requirements_hash = hashlib.md5(f.read().encode('utf-8')).hexdigest()
196
+ with open(os.path.join(self.model_path, "Dockerfile"), "r") as f:
197
+ dockerfile_hash = hashlib.md5(f.read().encode('utf-8')).hexdigest()
198
+
199
+ return hashlib.md5(f"{requirements_hash}{dockerfile_hash}".encode('utf-8')).hexdigest()
200
+
201
+ def is_docker_installed(self):
202
+ """Checks if Docker is installed on the system."""
203
+ try:
204
+ logger.info("Checking if Docker is installed...")
205
+ subprocess.run(["docker", "--version"], check=True)
206
+ return True
207
+ except subprocess.CalledProcessError:
208
+ logger.error(
209
+ "Docker is not installed! Please install Docker to run the model in a container."
210
+ )
211
+ return False
212
+
213
+ def build_docker_image(
214
+ self,
215
+ image_name="model_image",
216
+ ):
217
+ """Build the docker image using the Dockerfile in the model directory."""
218
+ try:
219
+ logger.info(f"Building docker image from Dockerfile in {self.model_path}...")
220
+
221
+ # since we don't want to copy the model directory into the container, we need to modify the Dockerfile and comment out the COPY instruction
222
+ dockerfile_path = os.path.join(self.model_path, "Dockerfile")
223
+ # Read the Dockerfile
224
+ with open(dockerfile_path, 'r') as file:
225
+ lines = file.readlines()
226
+
227
+ # Comment out the COPY instruction that copies the current folder
228
+ modified_lines = []
229
+ for line in lines:
230
+ if 'COPY' in line and '/home/nonroot/main' in line:
231
+ modified_lines.append(f'# {line}')
232
+ elif 'download-checkpoints' in line and '/home/nonroot/main' in line:
233
+ modified_lines.append(f'# {line}')
234
+ else:
235
+ modified_lines.append(line)
236
+
237
+ # Create a temporary directory to store the modified Dockerfile
238
+ with tempfile.TemporaryDirectory() as temp_dir:
239
+ temp_dockerfile_path = os.path.join(temp_dir, "Dockerfile.temp")
240
+
241
+ # Write the modified Dockerfile to the temporary file
242
+ with open(temp_dockerfile_path, 'w') as file:
243
+ file.writelines(modified_lines)
244
+
245
+ # Build the Docker image using the temporary Dockerfile
246
+ subprocess.check_call(
247
+ [
248
+ 'docker',
249
+ 'build',
250
+ '-t',
251
+ image_name,
252
+ '-f',
253
+ temp_dockerfile_path,
254
+ self.model_path,
255
+ ]
256
+ )
257
+ logger.info(f"Docker image '{image_name}' built successfully!")
258
+ except subprocess.CalledProcessError as e:
259
+ logger.info(f"Error occurred while building the Docker image: {e}")
260
+ sys.exit(1)
261
+
262
+ def docker_image_exists(self, image_name):
263
+ """Check if the Docker image exists."""
264
+ try:
265
+ logger.info(f"Checking if Docker image '{image_name}' exists...")
266
+ subprocess.run(["docker", "inspect", image_name], check=True)
267
+ logger.info(f"Docker image '{image_name}' exists!")
268
+ return True
269
+ except subprocess.CalledProcessError:
270
+ logger.info(f"Docker image '{image_name}' does not exist!")
271
+ return False
272
+
273
+ def _gpu_is_available(self):
274
+ """
275
+ Checks if nvidia-smi is available, indicating a GPU is likely accessible.
276
+ """
277
+ return shutil.which("nvidia-smi") is not None
278
+
279
+ def run_docker_container(
280
+ self, image_name, container_name="clarifai-model-container", port=8080, env_vars=None
281
+ ):
282
+ """Runs a Docker container from the specified image."""
283
+ try:
284
+ logger.info(
285
+ f"Running Docker container '{container_name}' from image '{image_name}'..."
286
+ )
287
+ # Base docker run command
288
+ cmd = ["docker", "run", "--name", container_name, '--rm', "--network", "host"]
289
+ if self._gpu_is_available():
290
+ cmd.extend(["--gpus", "all"])
291
+ # Add volume mappings
292
+ cmd.extend(["-v", f"{self.model_path}:/home/nonroot/main"])
293
+ # Add environment variables
294
+ if env_vars:
295
+ for key, value in env_vars.items():
296
+ cmd.extend(["-e", f"{key}={value}"])
297
+ # Add the image name
298
+ cmd.append(image_name)
299
+ # update the CMD to run the server
300
+ cmd.extend(["--model_path", "/home/nonroot/main", "--grpc", "--port", str(port)])
301
+ # Run the container
302
+ process = subprocess.Popen(
303
+ cmd,
304
+ )
305
+ logger.info(
306
+ f"Docker container '{container_name}' is running successfully! access the model at http://localhost:{port}"
307
+ )
308
+
309
+ # Function to handle Ctrl+C (SIGINT) gracefully
310
+ def signal_handler(sig, frame):
311
+ logger.info(f"Stopping Docker container '{container_name}'...")
312
+ subprocess.run(["docker", "stop", container_name], check=True)
313
+ process.terminate()
314
+ logger.info(f"Docker container '{container_name}' stopped successfully!")
315
+ time.sleep(1)
316
+ sys.exit(0)
317
+
318
+ # Register the signal handler for SIGINT (Ctrl+C)
319
+ signal.signal(signal.SIGINT, signal_handler)
320
+ # Wait for the process to finish (keeps the container running until it's stopped)
321
+ process.wait()
322
+ except subprocess.CalledProcessError as e:
323
+ logger.info(f"Error occurred while running the Docker container: {e}")
324
+ sys.exit(1)
325
+ except Exception as e:
326
+ logger.info(f"Error occurred while running the Docker container: {e}")
327
+ sys.exit(1)
328
+
329
+ def test_model_container(
330
+ self, image_name, container_name="clarifai-model-container", env_vars=None
331
+ ):
332
+ """Test the model inside the Docker container."""
333
+ try:
334
+ logger.info("Testing the model inside the Docker container...")
335
+ # Base docker run command
336
+ cmd = ["docker", "run", "--name", container_name, '--rm', "--network", "host"]
337
+ if self._gpu_is_available():
338
+ cmd.extend(["--gpus", "all"])
339
+ # update the entrypoint for testing the model
340
+ cmd.extend(["--entrypoint", "python"])
341
+ # Add volume mappings
342
+ cmd.extend(["-v", f"{self.model_path}:/home/nonroot/main"])
343
+ # Add environment variables
344
+ if env_vars:
345
+ for key, value in env_vars.items():
346
+ cmd.extend(["-e", f"{key}={value}"])
347
+ # Add the image name
348
+ cmd.append(image_name)
349
+ # update the CMD to test the model inside the container
350
+ cmd.extend(
351
+ [
352
+ "-c",
353
+ "from clarifai.runners.models.model_run_locally import ModelRunLocally; ModelRunLocally('/home/nonroot/main')._run_test()",
354
+ ]
355
+ )
356
+ # Run the container
357
+ subprocess.check_call(cmd)
358
+ logger.info("Model tested successfully!")
359
+ except subprocess.CalledProcessError as e:
360
+ logger.error(f"Error testing the model inside the Docker container: {e}")
361
+ sys.exit(1)
362
+
363
+ def container_exists(self, container_name="clarifai-model-container"):
364
+ """Check if the Docker container exists."""
365
+ try:
366
+ # Run docker ps -a to list all containers (running and stopped)
367
+ result = subprocess.run(
368
+ [
369
+ "docker",
370
+ "ps",
371
+ "-a",
372
+ "--filter",
373
+ f"name={container_name}",
374
+ "--format",
375
+ "{{.Names}}",
376
+ ],
377
+ check=True,
378
+ capture_output=True,
379
+ text=True,
380
+ )
381
+ # If the container name is returned, it exists
382
+ if result.stdout.strip() == container_name:
383
+ logger.info(f"Docker container '{container_name}' exists.")
384
+ return True
385
+ else:
386
+ return False
387
+ except subprocess.CalledProcessError as e:
388
+ logger.error(f"Error occurred while checking if container exists: {e}")
389
+ return False
390
+
391
+ def stop_docker_container(self, container_name="clarifai-model-container"):
392
+ """Stop the Docker container if it's running."""
393
+ try:
394
+ # Check if the container is running
395
+ result = subprocess.run(
396
+ ["docker", "ps", "--filter", f"name={container_name}", "--format", "{{.Names}}"],
397
+ check=True,
398
+ capture_output=True,
399
+ text=True,
400
+ )
401
+ if result.stdout.strip() == container_name:
402
+ logger.info(f"Docker container '{container_name}' is running. Stopping it...")
403
+ subprocess.run(["docker", "stop", container_name], check=True)
404
+ logger.info(f"Docker container '{container_name}' stopped successfully!")
405
+ except subprocess.CalledProcessError as e:
406
+ logger.error(f"Error occurred while stopping the Docker container: {e}")
407
+
408
+ def remove_docker_container(self, container_name="clarifai-model-container"):
409
+ """Remove the Docker container."""
410
+ try:
411
+ logger.info(f"Removing Docker container '{container_name}'...")
412
+ subprocess.run(["docker", "rm", container_name], check=True)
413
+ logger.info(f"Docker container '{container_name}' removed successfully!")
414
+ except subprocess.CalledProcessError as e:
415
+ logger.error(f"Error occurred while removing the Docker container: {e}")
416
+
417
+ def remove_docker_image(self, image_name):
418
+ """Remove the Docker image."""
419
+ try:
420
+ logger.info(f"Removing Docker image '{image_name}'...")
421
+ subprocess.run(["docker", "rmi", image_name], check=True)
422
+ logger.info(f"Docker image '{image_name}' removed successfully!")
423
+ except subprocess.CalledProcessError as e:
424
+ logger.error(f"Error occurred while removing the Docker image: {e}")
425
+
426
+ def clean_up(self):
427
+ """Clean up the temporary virtual environment."""
428
+ if os.path.exists(self.temp_dir):
429
+ logger.info("Cleaning up temporary virtual environment...")
430
+ shutil.rmtree(self.temp_dir)
431
+
432
+
433
+ def main(
434
+ model_path,
435
+ run_model_server=False,
436
+ inside_container=False,
437
+ port=8080,
438
+ keep_env=False,
439
+ keep_image=False,
440
+ skip_dockerfile: bool = False,
441
+ ):
442
+ manager = ModelRunLocally(model_path)
443
+ # get whatever stage is in config.yaml to force download now
444
+ # also always write to where upload/build wants to, not the /tmp folder that runtime stage uses
445
+ _, _, _, when, _, _ = manager.builder._validate_config_checkpoints()
446
+ manager.builder.download_checkpoints(
447
+ stage=when, checkpoint_path_override=manager.builder.checkpoint_path
104
448
  )
449
+ if inside_container:
450
+ if not manager.is_docker_installed():
451
+ sys.exit(1)
452
+ if not skip_dockerfile:
453
+ manager.builder.create_dockerfile()
454
+ image_tag = manager._docker_hash()
455
+ model_id = manager.config['model']['id'].lower()
456
+ # must be in lowercase
457
+ image_name = f"{model_id}:{image_tag}"
458
+ container_name = model_id
459
+ if not manager.docker_image_exists(image_name):
460
+ manager.build_docker_image(image_name=image_name)
461
+ try:
462
+ if run_model_server:
463
+ manager.run_docker_container(
464
+ image_name=image_name, container_name=container_name, port=port
465
+ )
466
+ else:
467
+ manager.test_model_container(image_name=image_name, container_name=container_name)
468
+ finally:
469
+ if manager.container_exists(container_name):
470
+ manager.stop_docker_container(container_name)
471
+ manager.remove_docker_container(container_name=container_name)
472
+ if not keep_image:
473
+ manager.remove_docker_image(image_name)
105
474
 
106
- def _run_test(self):
107
- """Test the model locally by making a prediction."""
108
- # Create the model
109
- model = self.builder.create_model_instance()
110
- # call its test method, if it has one
111
- if hasattr(model, "test"):
112
- try:
113
- model.test()
114
- logger.info("Model tested successfully!")
115
- except Exception as e:
116
- logger.error(f"Error occurred while testing the model: {e}")
117
- traceback.print_exc()
118
-
119
- def test_model(self):
120
- """Test the model by running it locally in the virtual environment."""
121
-
122
- import_path = repr(os.path.dirname(os.path.abspath(__file__)))
123
- model_path = repr(self.model_path)
124
-
125
- command_string = (f"import sys; "
126
- f"sys.path.append({import_path}); "
127
- f"from model_run_locally import ModelRunLocally; "
128
- f"ModelRunLocally({model_path})._run_test()")
129
-
130
- command = [self.python_executable, "-c", command_string]
131
- process = None
132
- try:
133
- logger.info("Testing the model locally...")
134
- process = subprocess.Popen(command)
135
- # Wait for the process to complete
136
- process.wait()
137
- if process.returncode == 0:
138
- logger.info("Model tested successfully!")
139
- if process.returncode != 0:
140
- raise subprocess.CalledProcessError(process.returncode, command)
141
- except subprocess.CalledProcessError as e:
142
- logger.error(f"Error testing the model: {e}")
143
- sys.exit(1)
144
- except Exception as e:
145
- logger.error(f"Unexpected error: {e}")
146
- sys.exit(1)
147
- finally:
148
- # After the function runs, check if the process is still running
149
- if process and process.poll() is None:
150
- logger.info("Process is still running. Terminating process.")
151
- process.terminate()
475
+ else:
152
476
  try:
153
- process.wait(timeout=5)
154
- except subprocess.TimeoutExpired:
155
- logger.info("Process did not terminate gracefully. Killing process.")
156
- # Kill the process if it doesn't terminate after 5 seconds
157
- process.kill()
158
-
159
- # run the model server
160
- def run_model_server(self, port=8080):
161
- """Run the Clarifai Runners's model server."""
162
-
163
- command = [
164
- self.python_executable, "-m", "clarifai.runners.server", "--model_path", self.model_path,
165
- "--grpc", "--port",
166
- str(port)
167
- ]
168
- try:
169
- logger.info(
170
- f"Starting model server at localhost:{port} with the model at {self.model_path}...")
171
- subprocess.check_call(command)
172
- logger.info("Model server started successfully and running at localhost:{port}")
173
- except subprocess.CalledProcessError as e:
174
- logger.error(f"Error running model server: {e}")
175
- self.clean_up()
176
- sys.exit(1)
177
-
178
- def _docker_hash(self):
179
- """Generate a hash of the combined requirements file and Dockefile"""
180
- with open(self.requirements_file, "r") as f:
181
- requirements_hash = hashlib.md5(f.read().encode('utf-8')).hexdigest()
182
- with open(os.path.join(self.model_path, "Dockerfile"), "r") as f:
183
- dockerfile_hash = hashlib.md5(f.read().encode('utf-8')).hexdigest()
184
-
185
- return hashlib.md5(f"{requirements_hash}{dockerfile_hash}".encode('utf-8')).hexdigest()
186
-
187
- def is_docker_installed(self):
188
- """Checks if Docker is installed on the system."""
189
- try:
190
- logger.info("Checking if Docker is installed...")
191
- subprocess.run(["docker", "--version"], check=True)
192
- return True
193
- except subprocess.CalledProcessError:
194
- logger.error(
195
- "Docker is not installed! Please install Docker to run the model in a container.")
196
- return False
197
-
198
- def build_docker_image(
199
- self,
200
- image_name="model_image",
201
- ):
202
- """Build the docker image using the Dockerfile in the model directory."""
203
- try:
204
- logger.info(f"Building docker image from Dockerfile in {self.model_path}...")
205
-
206
- # since we don't want to copy the model directory into the container, we need to modify the Dockerfile and comment out the COPY instruction
207
- dockerfile_path = os.path.join(self.model_path, "Dockerfile")
208
- # Read the Dockerfile
209
- with open(dockerfile_path, 'r') as file:
210
- lines = file.readlines()
211
-
212
- # Comment out the COPY instruction that copies the current folder
213
- modified_lines = []
214
- for line in lines:
215
- if 'COPY' in line and '/home/nonroot/main' in line:
216
- modified_lines.append(f'# {line}')
217
- elif 'download-checkpoints' in line and '/home/nonroot/main' in line:
218
- modified_lines.append(f'# {line}')
219
- else:
220
- modified_lines.append(line)
221
-
222
- # Create a temporary directory to store the modified Dockerfile
223
- with tempfile.TemporaryDirectory() as temp_dir:
224
- temp_dockerfile_path = os.path.join(temp_dir, "Dockerfile.temp")
225
-
226
- # Write the modified Dockerfile to the temporary file
227
- with open(temp_dockerfile_path, 'w') as file:
228
- file.writelines(modified_lines)
229
-
230
- # Build the Docker image using the temporary Dockerfile
231
- subprocess.check_call(
232
- ['docker', 'build', '-t', image_name, '-f', temp_dockerfile_path, self.model_path])
233
- logger.info(f"Docker image '{image_name}' built successfully!")
234
- except subprocess.CalledProcessError as e:
235
- logger.info(f"Error occurred while building the Docker image: {e}")
236
- sys.exit(1)
237
-
238
- def docker_image_exists(self, image_name):
239
- """Check if the Docker image exists."""
240
- try:
241
- logger.info(f"Checking if Docker image '{image_name}' exists...")
242
- subprocess.run(["docker", "inspect", image_name], check=True)
243
- logger.info(f"Docker image '{image_name}' exists!")
244
- return True
245
- except subprocess.CalledProcessError:
246
- logger.info(f"Docker image '{image_name}' does not exist!")
247
- return False
248
-
249
- def _gpu_is_available(self):
250
- """
251
- Checks if nvidia-smi is available, indicating a GPU is likely accessible.
252
- """
253
- return shutil.which("nvidia-smi") is not None
254
-
255
- def run_docker_container(self,
256
- image_name,
257
- container_name="clarifai-model-container",
258
- port=8080,
259
- env_vars=None):
260
- """Runs a Docker container from the specified image."""
261
- try:
262
- logger.info(f"Running Docker container '{container_name}' from image '{image_name}'...")
263
- # Base docker run command
264
- cmd = ["docker", "run", "--name", container_name, '--rm', "--network", "host"]
265
- if self._gpu_is_available():
266
- cmd.extend(["--gpus", "all"])
267
- # Add volume mappings
268
- cmd.extend(["-v", f"{self.model_path}:/home/nonroot/main"])
269
- # Add environment variables
270
- if env_vars:
271
- for key, value in env_vars.items():
272
- cmd.extend(["-e", f"{key}={value}"])
273
- # Add the image name
274
- cmd.append(image_name)
275
- # update the CMD to run the server
276
- cmd.extend(["--model_path", "/home/nonroot/main", "--grpc", "--port", str(port)])
277
- # Run the container
278
- process = subprocess.Popen(cmd,)
279
- logger.info(
280
- f"Docker container '{container_name}' is running successfully! access the model at http://localhost:{port}"
281
- )
282
-
283
- # Function to handle Ctrl+C (SIGINT) gracefully
284
- def signal_handler(sig, frame):
285
- logger.info(f"Stopping Docker container '{container_name}'...")
286
- subprocess.run(["docker", "stop", container_name], check=True)
287
- process.terminate()
288
- logger.info(f"Docker container '{container_name}' stopped successfully!")
289
- time.sleep(1)
290
- sys.exit(0)
291
-
292
- # Register the signal handler for SIGINT (Ctrl+C)
293
- signal.signal(signal.SIGINT, signal_handler)
294
- # Wait for the process to finish (keeps the container running until it's stopped)
295
- process.wait()
296
- except subprocess.CalledProcessError as e:
297
- logger.info(f"Error occurred while running the Docker container: {e}")
298
- sys.exit(1)
299
- except Exception as e:
300
- logger.info(f"Error occurred while running the Docker container: {e}")
301
- sys.exit(1)
302
-
303
- def test_model_container(self,
304
- image_name,
305
- container_name="clarifai-model-container",
306
- env_vars=None):
307
- """Test the model inside the Docker container."""
308
- try:
309
- logger.info("Testing the model inside the Docker container...")
310
- # Base docker run command
311
- cmd = ["docker", "run", "--name", container_name, '--rm', "--network", "host"]
312
- if self._gpu_is_available():
313
- cmd.extend(["--gpus", "all"])
314
- # update the entrypoint for testing the model
315
- cmd.extend(["--entrypoint", "python"])
316
- # Add volume mappings
317
- cmd.extend(["-v", f"{self.model_path}:/home/nonroot/main"])
318
- # Add environment variables
319
- if env_vars:
320
- for key, value in env_vars.items():
321
- cmd.extend(["-e", f"{key}={value}"])
322
- # Add the image name
323
- cmd.append(image_name)
324
- # update the CMD to test the model inside the container
325
- cmd.extend([
326
- "-c",
327
- "from clarifai.runners.models.model_run_locally import ModelRunLocally; ModelRunLocally('/home/nonroot/main')._run_test()"
328
- ])
329
- # Run the container
330
- subprocess.check_call(cmd)
331
- logger.info("Model tested successfully!")
332
- except subprocess.CalledProcessError as e:
333
- logger.error(f"Error testing the model inside the Docker container: {e}")
334
- sys.exit(1)
335
-
336
- def container_exists(self, container_name="clarifai-model-container"):
337
- """Check if the Docker container exists."""
338
- try:
339
- # Run docker ps -a to list all containers (running and stopped)
340
- result = subprocess.run(
341
- ["docker", "ps", "-a", "--filter", f"name={container_name}", "--format", "{{.Names}}"],
342
- check=True,
343
- capture_output=True,
344
- text=True)
345
- # If the container name is returned, it exists
346
- if result.stdout.strip() == container_name:
347
- logger.info(f"Docker container '{container_name}' exists.")
348
- return True
349
- else:
350
- return False
351
- except subprocess.CalledProcessError as e:
352
- logger.error(f"Error occurred while checking if container exists: {e}")
353
- return False
354
-
355
- def stop_docker_container(self, container_name="clarifai-model-container"):
356
- """Stop the Docker container if it's running."""
357
- try:
358
- # Check if the container is running
359
- result = subprocess.run(
360
- ["docker", "ps", "--filter", f"name={container_name}", "--format", "{{.Names}}"],
361
- check=True,
362
- capture_output=True,
363
- text=True)
364
- if result.stdout.strip() == container_name:
365
- logger.info(f"Docker container '{container_name}' is running. Stopping it...")
366
- subprocess.run(["docker", "stop", container_name], check=True)
367
- logger.info(f"Docker container '{container_name}' stopped successfully!")
368
- except subprocess.CalledProcessError as e:
369
- logger.error(f"Error occurred while stopping the Docker container: {e}")
370
-
371
- def remove_docker_container(self, container_name="clarifai-model-container"):
372
- """Remove the Docker container."""
373
- try:
374
- logger.info(f"Removing Docker container '{container_name}'...")
375
- subprocess.run(["docker", "rm", container_name], check=True)
376
- logger.info(f"Docker container '{container_name}' removed successfully!")
377
- except subprocess.CalledProcessError as e:
378
- logger.error(f"Error occurred while removing the Docker container: {e}")
379
-
380
- def remove_docker_image(self, image_name):
381
- """Remove the Docker image."""
382
- try:
383
- logger.info(f"Removing Docker image '{image_name}'...")
384
- subprocess.run(["docker", "rmi", image_name], check=True)
385
- logger.info(f"Docker image '{image_name}' removed successfully!")
386
- except subprocess.CalledProcessError as e:
387
- logger.error(f"Error occurred while removing the Docker image: {e}")
388
-
389
- def clean_up(self):
390
- """Clean up the temporary virtual environment."""
391
- if os.path.exists(self.temp_dir):
392
- logger.info("Cleaning up temporary virtual environment...")
393
- shutil.rmtree(self.temp_dir)
394
-
395
-
396
- def main(model_path,
397
- run_model_server=False,
398
- inside_container=False,
399
- port=8080,
400
- keep_env=False,
401
- keep_image=False,
402
- skip_dockerfile: bool = False):
403
-
404
- manager = ModelRunLocally(model_path)
405
- # get whatever stage is in config.yaml to force download now
406
- # also always write to where upload/build wants to, not the /tmp folder that runtime stage uses
407
- _, _, _, when, _, _ = manager.builder._validate_config_checkpoints()
408
- manager.builder.download_checkpoints(
409
- stage=when, checkpoint_path_override=manager.builder.checkpoint_path)
410
- if inside_container:
411
- if not manager.is_docker_installed():
412
- sys.exit(1)
413
- if not skip_dockerfile:
414
- manager.builder.create_dockerfile()
415
- image_tag = manager._docker_hash()
416
- model_id = manager.config['model']['id'].lower()
417
- # must be in lowercase
418
- image_name = f"{model_id}:{image_tag}"
419
- container_name = model_id
420
- if not manager.docker_image_exists(image_name):
421
- manager.build_docker_image(image_name=image_name)
422
- try:
423
- if run_model_server:
424
- manager.run_docker_container(
425
- image_name=image_name, container_name=container_name, port=port)
426
- else:
427
- manager.test_model_container(image_name=image_name, container_name=container_name)
428
- finally:
429
- if manager.container_exists(container_name):
430
- manager.stop_docker_container(container_name)
431
- manager.remove_docker_container(container_name=container_name)
432
- if not keep_image:
433
- manager.remove_docker_image(image_name)
434
-
435
- else:
436
- try:
437
- use_existing_env = manager.create_temp_venv()
438
- if not use_existing_env:
439
- manager.install_requirements()
440
- if run_model_server:
441
- manager.run_model_server(port)
442
- else:
443
- manager.test_model()
444
- finally:
445
- if not keep_env:
446
- manager.clean_up()
477
+ use_existing_env = manager.create_temp_venv()
478
+ if not use_existing_env:
479
+ manager.install_requirements()
480
+ if run_model_server:
481
+ manager.run_model_server(port)
482
+ else:
483
+ manager.test_model()
484
+ finally:
485
+ if not keep_env:
486
+ manager.clean_up()