Conversation
BlueCrescent
left a comment
There was a problem hiding this comment.
Overall LGTM.
Should we also explicitly allow seeding for the "model_initialized" component?
It will probably inherit the random state from the model_raw component but it seems a bit risky to me to assume that (also in the future) no other interaction with the random state happens between these two components (though, probably, only interactions that are asymmetrical between the ranks would be problematic). In particular, since we cannot guarantee the order in which the components are build, something like a dataloader component might even re-seed the random state.
le1nux
left a comment
There was a problem hiding this comment.
I checked the seeding (not the test) and from my understanding the changes do not provide the expected results (also what @BlueCrescent was hinting towards).
When we seed the raw model, the model weights are indeed deterministic at instantiation time. However, we also have model weight initialization which runs afterwards and would just override the weights / seeding.
Additionally, passing device_mesh to the model is coupling two components that should normally not know anything about each other.
I think we have to integrate the seeding to the weight initializer component and can also pass in the device_mesh there.
Yes that makes sense. I moved the seeding to the model initialization component |
See #426 (comment) |
le1nux
left a comment
There was a problem hiding this comment.
Generally good state.
Left a couple of comments.
My main concern is the global setting of the seed. A generator object might be favorable.
| """NNModel class to define a base model.""" | ||
|
|
||
| def __init__(self, seed: int = None, weight_decay_groups: Optional[WeightDecayGroups] = None): | ||
| def __init__(self, seed: Optional[int] = None, weight_decay_groups: Optional[WeightDecayGroups] = None): |
There was a problem hiding this comment.
| def __init__(self, seed: Optional[int] = None, weight_decay_groups: Optional[WeightDecayGroups] = None): | |
| def __init__(self, seed: int | None = None, weight_decay_groups: Optional[WeightDecayGroups] = None): |
There was a problem hiding this comment.
Do we even want to allow setting the seed here?
Could torch.manual_seed below have side effects with the new weight init implementation?
There was a problem hiding this comment.
Probably it could have side effects, e.g. default submodule initialization, random ops and the ambient global RNG state for unrelated code. Also it is mostly redundant since we now use a local generator for weight initialization. I would suggest to remove it.
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
…tialization Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
le1nux
left a comment
There was a problem hiding this comment.
Looks good to me! Nice work :)
The comment regarding the per-device Generator we should discuss, what makes most sense here.
I would add one last test, which checks that two models instantiated with the same config file (with a specified seed), should have 100% matching parameter weights. I'd keep that one simple (no advanced sharding like TP or PP. only FSDP).
| from transformers.utils.generic import check_model_inputs | ||
| except ImportError: | ||
|
|
||
| def check_model_inputs(func: Callable) -> Callable: |
There was a problem hiding this comment.
was this removed in transormers?
If it is part of a legacy API I think we should also remove this on our end.
What do you think @BlueCrescent? I think you added it, right?
There was a problem hiding this comment.
The function was removed in transformers version 5.2. In our pyproject.yaml we specify the requirement "transformers>=4.57.4,<5.0.0", so I used an unsupported transformers version here. Should we remove it just to be on the safe side?
There was a problem hiding this comment.
Yes, I think, we should tackle the transformers 5.0.0+ support soon anyways.
| self.seed = torch.initial_seed() if seed is None else seed | ||
| self._generators: dict[str, torch.Generator] = {} | ||
|
|
||
| def _get_generator(self, parameter: torch.Tensor) -> torch.Generator: |
There was a problem hiding this comment.
a few things are not clear to me.
- Do we actually have the case, where in a single process tensors are sitting on different GPUs?
- if 1. is the case, then we can end up with tensors that are initialized identically, since we create multiple generators from the same seed.
I'm not sure what the best way to solve this ... also seems to me that the Pytorch API regarding Generators is kinda limited.
There was a problem hiding this comment.
Since we start a single process for each rank via torchrun, this shouldn't happen, right? Or do I miss something?
Co-authored-by: Max Lübbering <2804731+le1nux@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
…tiple devices per rank Co-authored-by: Copilot <copilot@github.com>
le1nux
left a comment
There was a problem hiding this comment.
Minor comment. Otherwise all looks good to me! :)
Nice work! 👍
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <copilot@github.com>
What does this PR do?
This PR gives a unique model seed for each pp rank, such that parameters are initialized differently across ranks.
General Changes
Breaking Changes
Checklist before submitting final PR
python tests/tests.py)CHANGELOG_DEV.md)