7.2.1. Example: timm Model Inference
An application example that retrieves a model from timm and performs inference on the Image (beignets-task-guide.png)
Fig. 7.2 beignets-task-guide.png
Execution Method (resnet50.a1h_in1k)
$ cd /opt/pfn/pfcomp/codegen/MLSDK/examples/
$ ./run_timm.sh --model_name resnet50.a1h_in1k --batch_size 16
Expected Output (resnet50.a1h_in1k)
MNCore2 top-5 classes:
- espresso (967)
- cup (968)
- chocolate sauce, chocolate syrup (960)
- consomme (925)
- eggnog (969)
Torch top-5 classes:
- espresso (967)
- cup (968)
- chocolate sauce, chocolate syrup (960)
- eggnog (969)
- consomme (925)
Execution Method (mobilenetv3_small_050.lamb_in1k)
$ cd /opt/pfn/pfcomp/codegen/MLSDK/examples/
$ ./run_timm.sh --model_name mobilenetv3_small_050.lamb_in1k --batch_size 16
Expected Output (mobilenetv3_small_050.lamb_in1k)
MNCore2 top-5 classes:
- cup (968)
- trifle (927)
- face powder (551)
- ice cream, icecream (928)
- coffee mug (504)
Torch top-5 classes:
- cup (968)
- trifle (927)
- ice cream, icecream (928)
- face powder (551)
- coffee mug (504)
Scripts
1#! /bin/bash
2
3set -eux -o pipefail
4
5EXAMPLE_NAME=run_timm
6VENVDIR=/tmp/${EXAMPLE_NAME}_venv
7
8CURRENT_DIR=$(realpath $(dirname $0))
9CODEGEN_DIR=$(realpath ${CURRENT_DIR}/../../)
10BUILD_DIR=${BUILD_DIR:-${CODEGEN_DIR}/build}
11
12if [[ ! -d ${VENVDIR} ]]; then
13 python3 -m venv --system-site-packages ${VENVDIR}
14 source ${VENVDIR}/bin/activate
19 pip3 install timm==1.0.14 huggingface-hub==0.28.1
20else
21 source ${VENVDIR}/bin/activate
22fi
23
24source "${BUILD_DIR}/codegen_pythonpath.sh"
25
26exec python3 ${CURRENT_DIR}/${EXAMPLE_NAME}.py "$@"
1import argparse
2import os
3from pathlib import Path
4from typing import Any, Optional, Union
5
6import mncore # noqa: F401
7import timm
8import torch
9from mlsdk import (
10 Context,
11 MNCoreSGD,
12 MNDevice,
13 set_buffer_name_in_optimizer,
14 set_tensor_name_in_module,
15 storage,
16)
17from PIL import Image
18
19SAMPLE_IMAGE_PATH = os.path.join(
20 os.path.dirname(__file__), "./datasets/mncore2_chip.png"
21)
22
23
24def escape_path(path: str) -> str:
25 escaped = ""
26 for c in path:
27 if c.isalnum() or c in "_-":
28 escaped += c
29 else:
30 escaped += "_"
31 return escaped
32
33
34def create_model_with_cache(
35 model_name: str, model_cache_dir: Optional[str] = None, **kwargs: Any
36) -> Any:
37 if not model_cache_dir:
38 return timm.create_model(model_name, **kwargs)
39 else:
40 timm_version = "timm_version" + timm.__version__
41 torch_version = "torch_version" + torch.__version__
42 cache_dir = os.path.join(
43 model_cache_dir,
44 escape_path(f"{torch_version}_{timm_version}_{model_name}"),
45 )
46 # Load the model always from the cache to return the same model object always.
47 # This should also create the cache if it does not exist.
48 return timm.create_model(model_name, **kwargs, cache_dir=cache_dir)
49
50
51def imagenet_classes() -> list[str]:
52 script_dir = os.path.dirname(__file__)
53 imagenet_classes_path = os.path.join(script_dir, "imagenet_classes.txt")
54 with open(imagenet_classes_path) as f:
55 return [line.strip() for line in f]
56
57
58def run_inference(
59 model_name: str,
60 batch_size: int,
61 outdir: str,
62 option_json_path: Optional[Path],
63 device_str: str,
64 model_cache_dir: Optional[str],
65) -> None:
66 img = Image.open(SAMPLE_IMAGE_PATH)
67 model = create_model_with_cache(
68 model_name,
69 pretrained=True,
70 model_cache_dir=model_cache_dir,
71 )
72 model = model.eval()
73
74 def infer(input: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]:
75 with torch.no_grad():
76 x = input["images"]
77 return {"out": model(x)}
78
79 data_config = timm.data.resolve_model_data_config(model)
80 transforms = timm.data.create_transform(**data_config, is_training=False)
81 images = transforms(img).unsqueeze(0).expand(batch_size, -1, -1, -1)
82 sample = {"images": images}
83
84 device = MNDevice(device_str)
85 context = Context(device)
86 Context.switch_context(context)
87 context.registry.register("model", model)
88
89 compile_options: dict[str, str] = {}
90 if option_json_path is not None:
91 compile_options = {"option_json": str(option_json_path)}
92
93 compiled_infer = context.compile(
94 infer,
95 sample,
96 storage.path(outdir) / "infer",
97 options=compile_options,
98 )
99 result_as_proxy = compiled_infer(sample)
100 result_on_torch = infer(sample)
101
102 # Tensors obtained via ".cpu()" from TensorProxy exist on GPU in CUDA environments,
103 # so they need to be moved to CPU before the comparison.
104 result = result_as_proxy["out"].cpu()
105 if result.is_cuda:
106 result = result.cpu()
107
108 torch.allclose(result, result_on_torch["out"], atol=1e-5)
109
110 if "in1k" in model_name:
111 classes = imagenet_classes()
112 mncore_top5_classes = torch.topk(result[0], 5).indices.cpu()
113 print("MNCore2 top-5 classes:")
114 for i in mncore_top5_classes:
115 print(f"- {classes[i]} ({i.item()})")
116 torch_top5_classes = torch.topk(result_on_torch["out"][0], 5).indices
117 print("Torch top-5 classes:")
118 for i in torch_top5_classes:
119 print(f"- {classes[i]} ({i.item()})")
120
121
122def run_training_torch_onnx(
123 model_name: str,
124 batch_size: int,
125 outdir: str,
126 option_json_path: Optional[Path],
127 device: str,
128 model_cache_dir: Optional[str],
129) -> None:
130 device = MNDevice(device)
131 context = Context(device)
132 Context.switch_context(context)
133
134 img = Image.open(SAMPLE_IMAGE_PATH)
135
136 model = create_model_with_cache(
137 model_name,
138 pretrained=True,
139 num_classes=1000,
140 model_cache_dir=model_cache_dir,
141 )
142 data_config = timm.data.resolve_model_data_config(model)
143 transforms = timm.data.create_transform(**data_config, is_training=False)
144 images = transforms(img).unsqueeze(0).expand(batch_size, -1, -1, -1)
145 labels = torch.randint(0, 1000, (batch_size,))
146 sample = {"images": images, "labels": labels}
147
148 model = model.train()
149 context.registry.register("model", model)
150 optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
151 context.registry.register("optimizer", optimizer)
152 loss_fn = torch.nn.CrossEntropyLoss()
153
154 def f(inputs: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]:
155 return {"loss": loss_fn(model(inputs["images"]), inputs["labels"])}
156
157 compile_options: dict[str, Union[str, bool]] = {}
158 if option_json_path is not None:
159 compile_options = {"option_json": str(option_json_path)}
160 compile_options["backprop"] = True
161 compiled_f = context.compile(
162 f,
163 sample,
164 storage.path(outdir) / "train_step_torch_onnx",
165 optimizers=[optimizer],
166 options=compile_options,
167 )
168
169 first_loss = compiled_f(sample)["loss"].cpu()
170 for _ in range(10):
171 compiled_f(sample)
172 context.synchronize()
173 last_loss = compiled_f(sample)["loss"].cpu()
174
175 assert last_loss < first_loss
176
177
178def run_training_fx2onnx(
179 model_name: str,
180 batch_size: int,
181 outdir: str,
182 option_json_path: Optional[Path],
183 device_str: str,
184 model_cache_dir: Optional[str],
185) -> None:
186 device = MNDevice(device_str)
187 context = Context(device)
188 Context.switch_context(context)
189
190 img = Image.open(SAMPLE_IMAGE_PATH)
191
192 model = create_model_with_cache(
193 model_name,
194 pretrained=True,
195 num_classes=1000,
196 model_cache_dir=model_cache_dir,
197 )
198 model = model.train()
199 set_tensor_name_in_module(model, "model0")
200 for p in model.parameters():
201 context.register_param(p)
202
203 optimizer = MNCoreSGD(model.parameters(), 0.1, 0.9, 0.0)
204 set_buffer_name_in_optimizer(optimizer, "optimizer0")
205 context.register_optimizer_buffers(optimizer)
206 loss_fn = torch.nn.CrossEntropyLoss()
207
208 def train_step(input: dict[str, torch.Tensor]) -> dict[str, torch.Tensor]:
209 x = input["images"]
210 t = input["labels"]
211 optimizer.zero_grad()
212 y = model(x)
213 loss = loss_fn(y, t)
214 loss.backward()
215 optimizer.step()
216 return {"loss": loss}
217
218 data_config = timm.data.resolve_model_data_config(model)
219 transforms = timm.data.create_transform(**data_config, is_training=False)
220 images = transforms(img).unsqueeze(0).expand(batch_size, -1, -1, -1)
221 labels = torch.randint(0, 1000, (batch_size,))
222 sample = {"images": images, "labels": labels}
223
224 compile_options: dict[str, str] = {}
225 if option_json_path is not None:
226 compile_options = {"option_json": str(option_json_path)}
227
228 compiled_train_step = context.compile(
229 train_step,
230 sample,
231 storage.path(outdir) / "train_step_fx2onnx",
232 options=compile_options,
233 export_kwargs={"use_fx2onnx": True},
234 )
235
236 first_loss = compiled_train_step(sample)["loss"].cpu()
237 for _ in range(10):
238 compiled_train_step(sample)
239 context.synchronize()
240 last_loss = compiled_train_step(sample)["loss"].cpu()
241
242 assert last_loss < first_loss
243
244
245if __name__ == "__main__":
246 parser = argparse.ArgumentParser()
247 parser.add_argument("--batch_size", type=int, default=1, required=True)
248 parser.add_argument("--model_name", type=str)
249 parser.add_argument("--outdir", type=str, default="/tmp/mlsdk_timm")
250 parser.add_argument("--option_json", type=Path, default=None)
251 parser.add_argument("--is_training", action="store_true")
252 parser.add_argument(
253 "--device",
254 type=str,
255 default="mncore2:auto",
256 choices=["mncore2:auto", "pfvm:cpu", "pfvm:cuda"],
257 )
258 parser.add_argument(
259 "--model_cache_dir",
260 type=str,
261 default=None,
262 help="Directory to cache the model weights. "
263 "If not set, weights are always downloaded from the hub. default: None",
264 )
265 args = parser.parse_args()
266
267 outdir = args.outdir
268 if outdir is None:
269 outdir = f"/tmp/MLSDK_codegen_dir_{args.model_name}"
270 if args.is_training:
271 outdir += "_training"
272 else:
273 outdir += "_inference"
274
275 # TODO (akirakawata): Should we make this argument?
276 use_fx2onnx = not bool(
277 int(os.environ.get("MNCORE_USE_LEGACY_ONNX_EXPORTER", False))
278 )
279 if args.is_training:
280 if use_fx2onnx:
281 run_training_fx2onnx(
282 args.model_name,
283 args.batch_size,
284 outdir,
285 args.option_json,
286 args.device,
287 args.model_cache_dir,
288 )
289 else:
290 run_training_torch_onnx(
291 args.model_name,
292 args.batch_size,
293 args.outdir,
294 args.option_json,
295 args.device,
296 args.model_cache_dir,
297 )
298 else:
299 run_inference(
300 args.model_name,
301 args.batch_size,
302 args.outdir,
303 args.option_json,
304 args.device,
305 args.model_cache_dir,
306 )