Add trust_remote_code configuration option#31
Conversation
…g models and tokenizers
|
I just saw your comment in another PR:
I have to vehemently disagree, the number of times you have to confirm is making the abliteration process anything else than smooth. Maybe the crashing behavior is unsatisfactory, but nowhere close to having to agree over and over again. |
|
You're right that having to agree every time is a bug. But a command-line flag is not the correct fix. This change creates the following workflow:
The workflow we want is:
So we must find a way to remember the choice made by the user the first time. There is actually an extremely simple fix that does almost the right thing: Just add Edit: Actually, we can have the CLI flag, but if it is set to |
|
The thing I added was actually useful you mean to say the |
…sly `None` so the user wouldn't be asked multiple times
|
I think this is the cleanest way, by setting it to If the model requires custom code, loading the tokenizer would prompt the user to confirm allowing custom code. If the user declines the program will stop. If the user confirms, each subsequent model load will not prompt again. Should I still implement this explicit check for loading the |
…of the global `settings` object.
| ) | ||
|
|
||
| trust_remote_code: bool | None = Field( | ||
| default=None, |
There was a problem hiding this comment.
👍 That's definitely the correct default.
src/heretic/model.py
Outdated
| self.settings.model, | ||
| dtype=dtype, | ||
| device_map=self.settings.device_map, | ||
| trust_remote_code=self.settings.trust_remote_code, |
There was a problem hiding this comment.
This isn't safe in this form because of evaluate_model (see my earlier comment).
src/heretic/model.py
Outdated
| ) | ||
|
|
||
| if self.settings.trust_remote_code is None: | ||
| self.settings.trust_remote_code = True |
There was a problem hiding this comment.
We have to be careful here. Is it possible for the tokenizer to not require remote code, but for the model itself to require it? If so, this is unsafe, because no prompt would appear when loading the tokenizer, and then we flip it to True, loading the main model without a prompt, and the user was never given a choice.
There was a problem hiding this comment.
Makes sense, though I'd think this to not be the case, since they're being loaded from the same repository. In the cases I tested it was always asking for both, but I definitely can't say this is true in every case.
…mote_code` settings during model loading
|
Thanks for taking another look. What do you think about this approach @p-e-w? By the way, I tested your theory and you're definitely correct. If the tokenizer does not have an auto mapping it will not prompt, so the previous iteration would have been unsafe. |
src/heretic/model.py
Outdated
| self.trusted_models = {settings.model: settings.trust_remote_code} | ||
|
|
||
| if self.settings.evaluate_model is not None: | ||
| self.trusted_models[settings.model] = settings.trust_remote_code |
There was a problem hiding this comment.
I don't understand this line. Doesn't it do exactly the same thing as line 52?
There was a problem hiding this comment.
Whoopsie, that should've been the evaluate_model obviously
| self.settings.model, | ||
| dtype=dtype, | ||
| device_map=self.settings.device_map, | ||
| trust_remote_code=self.trusted_models.get(self.settings.model), |
There was a problem hiding this comment.
Not sure about this. If the user explicitly passes --trust-remote-code True, we'd expect this to also extend to the evaluated model, no?
There was a problem hiding this comment.
That's what the other line was meant for, although I've referenced the wrong model name in line 55.
There was a problem hiding this comment.
And settings.model is being overridden in main.py#L192, so with model.py#L55 corrected this should be working as intended.
p-e-w
left a comment
There was a problem hiding this comment.
Because this change is security-critical, I have done a very detailed review to convince myself that it is safe, and I now believe that it is indeed.
| ) | ||
|
|
||
| trust_remote_code: bool | None = Field( | ||
| default=None, |
There was a problem hiding this comment.
✅ Correct default, equivalent to not passing an argument to from_pretrained.
| self.tokenizer: PreTrainedTokenizerBase = AutoTokenizer.from_pretrained( | ||
| settings.model | ||
| settings.model, | ||
| trust_remote_code=settings.trust_remote_code, |
There was a problem hiding this comment.
✅ Completely safe so far, will simply follow the trust_remote_code setting, defaulting to None, which prompts the user.
| self.tokenizer.padding_side = "left" | ||
|
|
||
| self.model = None | ||
| self.trusted_models = {settings.model: settings.trust_remote_code} |
There was a problem hiding this comment.
✅ Completely safe. This just says "use the setting for the model".
| self.trusted_models = {settings.model: settings.trust_remote_code} | ||
|
|
||
| if self.settings.evaluate_model is not None: | ||
| self.trusted_models[settings.evaluate_model] = settings.trust_remote_code |
There was a problem hiding this comment.
✅ Completely safe. This just says "use the setting for the evaluated model".
| settings.model, | ||
| dtype=dtype, | ||
| device_map=settings.device_map, | ||
| trust_remote_code=self.trusted_models.get(settings.model), |
There was a problem hiding this comment.
Looks up the stored trust value. This is safe assuming said value is correct.
| ) | ||
|
|
||
| # If we reach this point and the model requires trust_remote_code, | ||
| # the user must have confirmed it. |
There was a problem hiding this comment.
(or settings.trust_remote_code was set to True)
| # If we reach this point and the model requires trust_remote_code, | ||
| # the user must have confirmed it. | ||
| if self.trusted_models.get(settings.model) is None: | ||
| self.trusted_models[settings.model] = True |
There was a problem hiding this comment.
❗ ✅ This is the critical part, and the comment correctly describes why it works. Declining the prompt for a model requiring trust_remote_code will raise an exception, so at this point, we know that the user has either confirmed the prompt or passed --trust-remote-code True to begin with.
There was a problem hiding this comment.
🟡 Small imperfection: If loading the model fails because of the wrong dtype, this part will not be reached and the user will be prompted again for the next dtype in the cascade, even though they already agreed to trust remote code.
| self.settings.model, | ||
| dtype=dtype, | ||
| device_map=self.settings.device_map, | ||
| trust_remote_code=self.trusted_models.get(self.settings.model), |
There was a problem hiding this comment.
Looks up the stored trust value. This is safe assuming said value is correct. Note that settings.model might have been changed to the value of settings.evaluate_model, but that doesn't make this unsafe as this model path would not have been set to True in trusted_models yet by the user confirming the initial prompt. If settings.trust_remote_code was set to True by the user, the code above would have set this lookup to True, so we won't get a prompt, as desired.
| ) | ||
|
|
||
| if self.trusted_models.get(self.settings.model) is None: | ||
| self.trusted_models[self.settings.model] = True |
There was a problem hiding this comment.
✅ Same logic as above. This currently doesn't do anything, as the evaluate_model is only loaded once, but it completes the trust logic as the user must have agreed to the prompt at this point.
|
Which model(s) requiring |
|
Problem
Some models require running custom code. If we don't specify
trust_remote_code, the process will be interrupted and we'll be asked everytime the tokenizer or model is reloaded, for whether we want to allow it.Solution
Introduce a
--trust-remote-codeflag, which defaults toFalse, and pass it to all.from_pretrained()calls.Result
The process will now run uninterrupted. If we didn't specify the flag and the model needs to run custom code, it will fail when trying to load the model the first time with a error that specifies that remote code must be trusted.
One minor inconvenience is that the flag needs to explicitly needs to be followed with a boolean value. I didn't want to overcomplicate the model arg parsing logic, which in its current iteration prevents us from setting
cli_implicit_flags=Truein theSettingsConfigDict, meaningheretic --trust-remote-code [...]isn't sufficient, instead a boolean value must be passed likeheretic --trust-remote-code true [...].