Skip to content

uniformize kwargs for OneFormer#34547

Open
tibor-reiss wants to merge 17 commits into
huggingface:mainfrom
tibor-reiss:fix-31811-oneformer
Open

uniformize kwargs for OneFormer#34547
tibor-reiss wants to merge 17 commits into
huggingface:mainfrom
tibor-reiss:fix-31811-oneformer

Conversation

@tibor-reiss

@tibor-reiss tibor-reiss commented Oct 31, 2024

Copy link
Copy Markdown
Contributor

Adds uniformized processors for OneFormer following #31911.

Small changes to simplify code.

Additional check via test_processor_oneformer.py that the inputs are the same before and after the changes (with fixed random seeds).

@qubvel @molbap

@tibor-reiss
tibor-reiss force-pushed the fix-31811-oneformer branch 2 times, most recently from cd15539 to fad4111 Compare October 31, 2024 21:12

@molbap molbap left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the initiative! Left a couple initial comments

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Cool! But we should also check that the previous signature works as intended as to not break backwards compatibility - in this case the previous behaviour where arguments were passed positionally should still be supported

@tibor-reiss tibor-reiss Nov 1, 2024

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.

I added *args. I had to add it after images, so the signature will only match after another iteration of deprecation. WDYT?

Comment on lines 111 to 130

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

since this does more than checking the types, I suggest moving the conversion to list out of this function, and rename it explicitly _validate_input_types for instance.

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.

Implemented.

Comment thread src/transformers/models/altclip/processing_altclip.py Outdated

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

so this tokenizes which should be done with self.tokenizer(..., output_kwargs['text_kwargs']), and it seems that it puts pad tokens where the attention mask is 0 - this should be covered by the tokenizer already, would be a nice refactor

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.

I will look into this...

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.

Atm I don't see how this can be simplified because the function is called with different arguments inside __call__, and it is also called in encode_inputs. Any suggestions?

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.

Agreed that it would be best to use self.tokenizer directly, and pass in the other text kwargs as well. For max_length, you can do the following:

  • if the max_length kwarg is not set, set it to max_seq_length or task_seq_length in the two different calls.
  • if it is set, use its value for both task and seq.

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.

I tried this (commit 3c7724d), but I don't see yet how it is simpler/more readable :)
Couple of observations:

  • the function _preprocess_text is called both with task_seq_length and max_seq_length, so if I add "max_length" to the text_kwargs dict, I would need to add and then replace in __call__.
  • in order to do something similar to what the attention-mask is doing (padding with 0s), I would need to change self.tokenizer.pad_token_id (and then change it back to what was before, e.g. 49407 (endoftext), because I can't pass in pad_token_id to the tokenizer. I am most probably missing here something, so suggestions welcome.

Comment on lines 75 to 82

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

so the way it should be is that in OneFormerProcessorKwargs, these defaults should be passed to the relevant dictionary, which you can then get back. say

_defaults = {"max_seq_length" = 77,
        "task_seq_length" = 77}

and you can inform the types with a Kwargs typed class

class OneFormerTextKwargs(TextKwargs, total=False):
    max_seq_length: Optional[int]
    task_seq_length: Optional[int]

then your types are correctly informed as well as your defaults and you can use both.

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.

This was a bigger refactor. Let me know what you think...

@ArthurZucker

Copy link
Copy Markdown
Collaborator

Hey! @molbap is off as he needs to rest, cc @yonigozlan can you review this? 🤗

@yonigozlan yonigozlan left a comment

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.

Thanks for working on this!
I mainly have comments about the handling of positional kwargs (like in the SAM PR), and about avoiding the wrapping of text processing inside _preprocess_text

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.

Agreed that it would be best to use self.tokenizer directly, and pass in the other text kwargs as well. For max_length, you can do the following:

  • if the max_length kwarg is not set, set it to max_seq_length or task_seq_length in the two different calls.
  • if it is set, use its value for both task and seq.

Comment on lines 203 to 223

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.

With this refactoring, it looks to me like encode_inputs could be made the same as call while preserving backward compatibility (if the args are handled correctly), so maybe we could just use call here?

@tibor-reiss tibor-reiss Nov 29, 2024

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.

Good catch! Done.

I tried this in commit 027003465, took some time until I realized that there is a big difference in the two calls:

  • encode_inputs is calling OneFormerImageProcessor.encode_inputs
  • __call__ is calling OneFormerImageProcessor.__call__, which will eventually call also OneFormerImageProcessor.encode_inputs, but only after quite some checks and massaging.

So atm I don't think there is a simple and elegant way to replace and would just leave as is. WDYT?

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.

Oh I see, never mind then

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.

Yes, see SAM PR comment #34578 (comment)

Comment on lines 119 to 126

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.

not needed #34578 (comment)

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.

These should be added to the optional_call_args attribute (see udop processor)

@tibor-reiss
tibor-reiss force-pushed the fix-31811-oneformer branch 2 times, most recently from 40cf888 to cbddaf4 Compare November 29, 2024 09:58

@yonigozlan yonigozlan left a comment

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.

There's a breaking change in the init that needs to be fixed. Otherwise it is looking good!
Just like for SAM, you'll also have to add the ProcessorTesterMixin to the processor test class, make sure all the tests pass and override the, if needed, and rebase on main.

Comment on lines 203 to 223

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.

Oh I see, never mind then

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.

Sorry I hadn't caught that before, but removing max_seq_length and task_seq_length from the init looks like a breaking change. We should add them back, and use self.max_seq_length and self.task_seq_length in the processing when the "max_length" kwarg is not defined. No need to add max_seq_length and task_seq_length to OneFormerTextKwargs as they weren't accepted before.

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.

I would say the opposite:

Suggested change
max_length=max_length if max_length is not None else text_kwargs.get("max_length", 77),
max_length=text_kwargs.get("max_length") if text_kwargs.get("max_length") is not None else max_length,

So that the max_length kwarg overrides the default kwarg when it is specified

Comment on lines 32 to 34

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.

No need for that (see comments after)

Comment on lines 107 to 106

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.

No need to redefine the defaults kwargs here in the get, as if they are not specified, they will necessarily be "max_length" and True respectively, you can just have:

Suggested change
padding=text_kwargs.get("padding", "max_length"),
truncation=text_kwargs.get("truncation", True),
padding=text_kwargs.get("padding"),
truncation=text_kwargs.get("truncation"),

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.

I believe this is needed because encode_inputs does not pass in any dict, so text_kwargs will become {}, without default.

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.

Oh yes I see

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

what is weird for me here is taht we don't use all the textkwargs. Why not just pass **text_kwargs? They can be typed as well (maybe just tokenizer kwargs) and you just update them with the args passed!

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.

Suggested change
max_length=output_kwargs["text_kwargs"]["task_seq_length"],
max_length=self.task_seq_length,

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.

Suggested change
max_length=output_kwargs["text_kwargs"]["max_seq_length"],
max_length=self.max_seq_length,

@tibor-reiss
tibor-reiss force-pushed the fix-31811-oneformer branch 2 times, most recently from 9ccaa40 to b579834 Compare December 19, 2024 18:51
@tibor-reiss

Copy link
Copy Markdown
Contributor Author

@yonigozlan I had to repeat some of the tests, e.g. test_structured_kwargs_nested, due to the mandatory task_inputs. It was either this, or making a default for task_inputs, e.g. 'semantic'. I went with the former, because it might be surprising for the users that the 'semantic' is picked as default - currently it raises a ValueError if not specified.

@yonigozlan yonigozlan left a comment

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.

LGTM after putting the kwargs back in the init! Thanks for iterating on this :)

image_processor=None,
tokenizer=None,
max_seq_length: int = 77,
task_seq_length: int = 77,

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.

Nit removing the **kwargs here would also be a breaking change, even if it's not used anywhere.

@yonigozlan

Copy link
Copy Markdown
Contributor

@yonigozlan I had to repeat some of the tests, e.g. test_structured_kwargs_nested, due to the mandatory task_inputs. It was either this, or making a default for task_inputs, e.g. 'semantic'. I went with the former, because it might be surprising for the users that the 'semantic' is picked as default - currently it raises a ValueError if not specified.

Yes I think that's the best way to do it :)

@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@ArthurZucker ArthurZucker left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks, not sure I get why encode inputs does not pass kwargs, while call does !

Comment on lines 107 to 106

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

what is weird for me here is taht we don't use all the textkwargs. Why not just pass **text_kwargs? They can be typed as well (maybe just tokenizer kwargs) and you just update them with the args passed!

Comment on lines +207 to +213
text_inputs = [
self._preprocess_text(
texts,
max_length=self.max_seq_length,
text_kwargs=output_kwargs["text_kwargs"],
).unsqueeze(0)
for texts in encoded_inputs.text_inputs

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

let's od a for loop it's more redable. Also this is now almost exactly the same as encode_inputs why don't we call that funciton instead / merge both?

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.

Added the for loop

@tibor-reiss

Copy link
Copy Markdown
Contributor Author

Hi @ArthurZucker,

Thanks, not sure I get why encode inputs does not pass kwargs, while call does !
I don't understand this comment. encode_inputs has kwargs, and is passed e.g. here: encoded_inputs = self.image_processor.encode_inputs(images, task_inputs, segmentation_maps, **kwargs). Could you please clarify?

Regarding _preprocess_text and text_kwargs: I rewrote it a little bit differently, let me know if it's more readable. The difficulty comes from several things, namely:

  • encode_inputs is similar, but not the same as __call__, see also uniformize kwargs for OneFormer #34547 (comment)
  • max_length can overwrite self.max_seq_length and self.task_seq_length
  • backwards compatibility
    I think once we remove encode_inputs, the whole code gets simpler and can be again refactored.

Comment on lines +130 to +131
if not all(task in ["semantic", "instance", "panoptic"] for task in task_inputs):
raise ValueError("task_inputs must be semantic, instance, or panoptic.")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

typing with choice should allow us not to have to do this

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.

Done

Comment on lines +127 to +128
if not isinstance(task_inputs, List) or not task_inputs:
raise TypeError("task_inputs should be a string or a list of strings.")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

typing is enough IMO we should not do this

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.

Done

Comment on lines +122 to +125
if task_inputs is None:
raise ValueError("You have to specify the task_inputs. Found None.")
elif images is None:
raise ValueError("You have to specify the images. Found None.")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this would be the only thing left and we can do something like if task_inputs ^ images: you have to specifiy one of task_inputs or images

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.

Both are needed, so I have put it into one line. wdyt?

if hasattr(encoded_inputs, "text_inputs"):
texts_list = encoded_inputs.text_inputs
task_token_inputs = []
for task in task_inputs:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

if task_inputs is None this will fail

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.

Yes, would fail. But it is already caught in _validate_input_types before.


for texts in encoded_inputs.text_inputs:
text_inputs.append(
self._preprocess_text(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

might be worth updating _preprocess_text because the tokenizer supports directly passing batches of text.
Also, task_inputs are written all the time so there is no case when this is not run no?

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.

Sorry, but I don't understand neither part of the comment here.

This part is only run if text_inputs is part of encoded_inputs - see the if two lines before. Not connected to task_inputs.

Once above is clarified, could you please specify in more detail how to update _preprocess_text? I have the feeling that this will again become simpler once we drop encode_inputs...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

_preprocess_text could only be called once, as the tokenizer.__call__ fonction supports list of list of text

@tibor-reiss
tibor-reiss force-pushed the fix-31811-oneformer branch from 0f7b044 to 77601ed Compare April 27, 2025 07:12
@tibor-reiss

Copy link
Copy Markdown
Contributor Author

Rebased

@qubvel

qubvel commented May 5, 2025

Copy link
Copy Markdown
Contributor

Hey @tibor-reiss, please run make modified_only_fixup to fix style issues and make CI green, thank you!

@qubvel

qubvel commented May 8, 2025

Copy link
Copy Markdown
Contributor

run-slow: oneformer

@github-actions

github-actions Bot commented May 8, 2025

Copy link
Copy Markdown
Contributor

This comment contains run-slow, running the specified jobs: This comment contains run-slow, running the specified jobs:

models: ['models/oneformer']
quantizations: [] ...

@ArthurZucker
ArthurZucker removed their request for review July 7, 2025 11:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants