Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions optimum/onnxruntime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"ORTModelForCustomTasks",
"ORTModelForFeatureExtraction",
"ORTModelForImageClassification",
"ORTModelForImageSegmentation",
"ORTModelForMultipleChoice",
"ORTModelForQuestionAnswering",
"ORTModelForSequenceClassification",
Expand Down Expand Up @@ -63,6 +64,7 @@
ORTModelForCustomTasks,
ORTModelForFeatureExtraction,
ORTModelForImageClassification,
ORTModelForImageSegmentation,
ORTModelForMultipleChoice,
ORTModelForQuestionAnswering,
ORTModelForSequenceClassification,
Expand Down
143 changes: 143 additions & 0 deletions optimum/onnxruntime/modeling_ort.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
AutoConfig,
AutoModel,
AutoModelForImageClassification,
AutoModelForImageSegmentation,
AutoModelForMultipleChoice,
AutoModelForQuestionAnswering,
AutoModelForSequenceClassification,
Expand All @@ -34,6 +35,7 @@
from transformers.modeling_outputs import (
BaseModelOutput,
ImageClassifierOutput,
SemanticSegmenterOutput,
ModelOutput,
MultipleChoiceModelOutput,
QuestionAnsweringModelOutput,
Expand Down Expand Up @@ -1421,6 +1423,147 @@ def forward(
return ImageClassifierOutput(logits=logits)


IMAGE_SEGMENTATION_EXAMPLE = r"""
Comment thread
TheoMrc marked this conversation as resolved.
Outdated
Example of image segmentation:
Comment thread
TheoMrc marked this conversation as resolved.
Outdated

```python
>>> import requests
>>> from PIL import Image
>>> from optimum.onnxruntime import {model_class}
>>> from transformers import {processor_class}

>>> url = "http://images.cocodataset.org/val2017/000000039769.jpg"
>>> image = Image.open(requests.get(url, stream=True).raw)

>>> preprocessor = {processor_class}.from_pretrained("{checkpoint}")
>>> model = {model_class}.from_pretrained("{checkpoint}")

>>> inputs = preprocessor(images=image, return_tensors="pt")

>>> outputs = model(**inputs)
>>> logits = outputs.logits
```

Example using `transformers.pipeline`:

```python
>>> import requests
>>> from PIL import Image
>>> from transformers import {processor_class}, pipeline
>>> from optimum.onnxruntime import {model_class}

>>> preprocessor = {processor_class}.from_pretrained("{checkpoint}")
>>> model = {model_class}.from_pretrained("{checkpoint}")
>>> onnx_image_segmenter = pipeline("image-segmentation", model=model, feature_extractor=preprocessor)

>>> url = "http://images.cocodataset.org/val2017/000000039769.jpg"
>>> pred = onnx_image_segmenter(url)
```
"""
# Probably have to modify urls above to an image that can be segmented to the chosen onnx segmentation model
Comment thread
TheoMrc marked this conversation as resolved.
Outdated


@add_start_docstrings(
"""
Onnx Model for image-segmentation tasks.
Comment thread
TheoMrc marked this conversation as resolved.
Outdated
""",
ONNX_MODEL_START_DOCSTRING,
)
class ORTModelForImageSegmentation(ORTModel):
"""
Image Segmentation model for ONNX.
Comment thread
TheoMrc marked this conversation as resolved.
Outdated
"""

export_feature = "image-segmentation"
Comment thread
TheoMrc marked this conversation as resolved.
Outdated
auto_model_class = AutoModelForImageSegmentation

def __init__(self, model=None, config=None, use_io_binding=True, **kwargs):
super().__init__(model, config, use_io_binding, **kwargs)
self.model_outputs = {output_key.name: idx for idx, output_key in enumerate(self.model.get_outputs())}
self.name_to_np_type = TypeHelper.get_io_numpy_type_map(self.model) if self.use_io_binding else None

def prepare_logits_buffer(self, batch_size, output_size):
"""Prepares the buffer of logits with a 2D tensor on shape: (batch_size, config.num_labels, height, width)."""
height, width = output_size
ort_type = TypeHelper.get_output_type(self.model, "logits")
torch_type = TypeHelper.ort_type_to_torch_type(ort_type)

logits_shape = (batch_size, self.config.num_labels, height, width)
logits_buffer = torch.empty(np.prod(logits_shape), dtype=torch_type, device=self.device).contiguous()

return logits_shape, logits_buffer

def prepare_io_binding(
self,
pixel_values: torch.Tensor,
):
io_binding = self.model.io_binding()

# bind pixel values
pixel_values = pixel_values.contiguous()
io_binding.bind_input(
"pixel_values",
pixel_values.device.type,
self.device.index,
self.name_to_np_type["pixel_values"],
tuple(pixel_values.shape),
pixel_values.data_ptr(),
)

output_size = (128, 128) # Need to get proper output size from object or config
# bind logits
logits_shape, logits_buffer = self.prepare_logits_buffer(batch_size=pixel_values.size(0),
output_size=output_size)
io_binding.bind_output(
"logits",
logits_buffer.device.type,
self.device.index,
self.name_to_np_type["logits"],
logits_shape,
logits_buffer.data_ptr(),
)
output_shapes = {"logits": logits_shape}
output_buffers = {"logits": logits_buffer}

return io_binding, output_shapes, output_buffers

@add_start_docstrings_to_model_forward(
ONNX_IMAGE_INPUTS_DOCSTRING.format("batch_size, num_channels, height, width")
+ IMAGE_SEGMENTATION_EXAMPLE.format(
Comment thread
TheoMrc marked this conversation as resolved.
Outdated
processor_class=_FEATURE_EXTRACTOR_FOR_DOC,
model_class="ORTModelForImageSegmentation",
checkpoint="optimum/vit-base-patch16-224", # Probably have to modify to an onnx segmentation model
Comment thread
TheoMrc marked this conversation as resolved.
Outdated
)
)
def forward(
self,
pixel_values: torch.Tensor,
**kwargs,
):
if self.device.type == "cuda" and self.use_io_binding:
io_binding, output_shapes, output_buffers = self.prepare_io_binding(pixel_values)

# run inference with binding & synchronize in case of multiple CUDA streams
io_binding.synchronize_inputs()
self.model.run_with_iobinding(io_binding)
io_binding.synchronize_outputs()

# converts output to namedtuple for pipelines post-processing
return SemanticSegmenterOutput(logits=output_buffers["logits"].view(output_shapes["logits"]))
else:
# converts pytorch inputs into numpy inputs for onnx
onnx_inputs = {
"pixel_values": pixel_values.cpu().detach().numpy(),
}

# run inference
outputs = self.model.run(None, onnx_inputs)
logits = torch.from_numpy(outputs[self.model_outputs["logits"]])

# converts output to namedtuple for pipelines post-processing
return SemanticSegmenterOutput(logits=logits)


CUSTOM_TASKS_EXAMPLE = r"""
Example of custom tasks(e.g. a sentence transformers taking `pooler_output` as output):

Expand Down
112 changes: 112 additions & 0 deletions tests/onnxruntime/test_modeling.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
ORTModelForCustomTasks,
ORTModelForFeatureExtraction,
ORTModelForImageClassification,
ORTModelForImageSegmentation,
ORTModelForMultipleChoice,
ORTModelForQuestionAnswering,
ORTModelForSeq2SeqLM,
Expand Down Expand Up @@ -1277,6 +1278,117 @@ def test_compare_to_io_binding(self, *args, **kwargs):
gc.collect()


class ORTModelForImageSegmentationIntegrationTest(unittest.TestCase):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to be similar to ORTModelForImageClassification, maybe we could abstract all of these test with a base test class, can be done in another PR

SUPPORTED_ARCHITECTURES_WITH_MODEL_ID = {
"vit": "hf-internal-testing/tiny-random-vit", # Probably have to modify to an onnx segmentation model

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to modify

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm
I'm wondering why though ?? Tests are dying on my pc

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VIT doesn't have segmentation model implemented, let's replace it by Segformer

}

def test_load_vanilla_transformers_which_is_not_supported(self):
with self.assertRaises(Exception) as context:
_ = ORTModelForImageSegmentation.from_pretrained(MODEL_NAMES["t5"], from_transformers=True)

self.assertIn("Unrecognized configuration class", str(context.exception))

@parameterized.expand(SUPPORTED_ARCHITECTURES_WITH_MODEL_ID.items())
def test_compare_to_transformers(self, *args, **kwargs):
model_arch, model_id = args
set_seed(SEED)
onnx_model = ORTModelForImageSegmentation.from_pretrained(model_id, from_transformers=True)

self.assertIsInstance(onnx_model.model, onnxruntime.capi.onnxruntime_inference_collection.InferenceSession)
self.assertIsInstance(onnx_model.config, PretrainedConfig)

set_seed(SEED)
trfs_model = AutoModelForImageClassification.from_pretrained(model_id)
Comment thread
TheoMrc marked this conversation as resolved.
Outdated
preprocessor = get_preprocessor(model_id)
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
image = Image.open(requests.get(url, stream=True).raw)
inputs = preprocessor(images=image, return_tensors="pt")
onnx_outputs = onnx_model(**inputs)

self.assertTrue("logits" in onnx_outputs)
self.assertTrue(isinstance(onnx_outputs.logits, torch.Tensor))

with torch.no_grad():
trtfs_outputs = trfs_model(**inputs)

# compare tensor outputs
self.assertTrue(torch.allclose(onnx_outputs.logits, trtfs_outputs.logits, atol=1e-4))

gc.collect()

@parameterized.expand(SUPPORTED_ARCHITECTURES_WITH_MODEL_ID.items())
def test_pipeline_ort_model(self, *args, **kwargs):
model_arch, model_id = args
onnx_model = ORTModelForImageSegmentation.from_pretrained(model_id, from_transformers=True)
preprocessor = get_preprocessor(model_id)
pipe = pipeline("image-classification", model=onnx_model, feature_extractor=preprocessor)
Comment thread
TheoMrc marked this conversation as resolved.
Outdated
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
outputs = pipe(url)

self.assertEqual(pipe.device, onnx_model.device)
self.assertGreaterEqual(outputs[0]["score"], 0.0)
self.assertTrue(isinstance(outputs[0]["label"], str))

gc.collect()

@pytest.mark.run_in_series
def test_pipeline_model_is_none(self):
pipe = pipeline("image-segmentation")
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
outputs = pipe(url)

# compare model output class
self.assertGreaterEqual(outputs[0]["score"], 0.0)
self.assertTrue(isinstance(outputs[0]["label"], str))

@parameterized.expand(SUPPORTED_ARCHITECTURES_WITH_MODEL_ID.items())
@require_torch_gpu
def test_pipeline_on_gpu(self, *args, **kwargs):
model_arch, model_id = args
onnx_model = ORTModelForImageSegmentation.from_pretrained(model_id, from_transformers=True)
preprocessor = get_preprocessor(model_id)
pipe = pipeline("image-classification", model=onnx_model, feature_extractor=preprocessor, device=0)
Comment thread
TheoMrc marked this conversation as resolved.
Outdated
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
outputs = pipe(url)
# check model device
self.assertEqual(pipe.model.device.type.lower(), "cuda")

# compare model output class
self.assertGreaterEqual(outputs[0]["score"], 0.0)
self.assertTrue(isinstance(outputs[0]["label"], str))

gc.collect()

@parameterized.expand(SUPPORTED_ARCHITECTURES_WITH_MODEL_ID.items())
@require_torch_gpu
def test_compare_to_io_binding(self, *args, **kwargs):
model_arch, model_id = args
set_seed(SEED)
onnx_model = ORTModelForImageSegmentation.from_pretrained(
model_id, from_transformers=True, use_io_binding=False
)
set_seed(SEED)
io_model = ORTModelForImageSegmentation.from_pretrained(
model_id, from_transformers=True, use_io_binding=True
)

preprocessor = get_preprocessor(model_id)
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
image = Image.open(requests.get(url, stream=True).raw)
inputs = preprocessor(images=[image] * 2, return_tensors="pt")
onnx_outputs = onnx_model(**inputs)
io_outputs = io_model(**inputs)

self.assertTrue("logits" in io_outputs)
self.assertIsInstance(io_outputs.logits, torch.Tensor)

# compare tensor outputs
self.assertTrue(torch.equal(onnx_outputs.logits, io_outputs.logits))

gc.collect()


class ORTModelForSeq2SeqLMIntegrationTest(unittest.TestCase):
SUPPORTED_ARCHITECTURES = (
"t5",
Expand Down