diff --git a/swh/model/model.py b/swh/model/model.py
--- a/swh/model/model.py
+++ b/swh/model/model.py
@@ -147,18 +147,161 @@
         raise NotImplementedError(f"Type-checking {type_}")
 
 
-def type_validator():
-    """Like attrs_strict.type_validator(), but stricter.
+def generic_type_validator(instance, attribute, value):
+    """validates the type of an attribute value whatever the attribute type"""
+    if not _check_type(attribute.type, value):
+        raise AttributeTypeError(value, attribute)
 
-    It is an attrs validator, which checks attributes have the specified type,
-    using type equality instead of ``isinstance()``, for improved performance
-    """
 
-    def validator(instance, attribute, value):
-        if not _check_type(attribute.type, value):
-            raise AttributeTypeError(value, attribute)
+def _true_validator(instance, attribute, value, expected_type=None):
+    pass
+
+
+def _none_validator(instance, attribute, value, expected_type=None):
+    if value is not None:
+        raise AttributeTypeError(value, attribute)
+
+
+def _origin_type_validator(instance, attribute, value, expected_type=None):
+    # This is functionally equivalent to using just this:
+    #   return isinstance(value, type)
+    # but using type equality before isinstance allows very quick checks
+    # when the exact class is used (which is the overwhelming majority of cases)
+    # while still allowing subclasses to be used.
+    if expected_type is None:
+        expected_type = attribute.type
+    if not (type(value) == expected_type or isinstance(value, expected_type)):
+        raise AttributeTypeError(value, attribute)
+
+
+def _tuple_infinite_validator(instance, attribute, value, expected_type=None):
+    type_ = type(value)
+    if type_ != tuple and not isinstance(value, tuple):
+        raise AttributeTypeError(value, attribute)
+    if expected_type is None:
+        expected_type = attribute.type
+    args = expected_type.__args__
+    # assert len(args) == 2 and args[1] is Ellipsis
+    expected_value_type = args[0]
+    validator = optimized_validator(expected_value_type)
+    try:
+        for i in value:
+            validator(instance, attribute, i, expected_type=expected_value_type)
+    except AttributeTypeError:
+        raise AttributeTypeError(value, attribute) from None
+
+
+def _tuple_bytes_bytes_validator(instance, attribute, value, expected_type=None):
+    type_ = type(value)
+    if type_ != tuple and not isinstance(value, tuple):
+        raise AttributeTypeError(value, attribute)
+    if len(value) != 2:
+        raise AttributeTypeError(value, attribute)
+    if type(value[0]) is not bytes or type(value[1]) is not bytes:
+        raise AttributeTypeError(value, attribute)
+
+
+def _tuple_finite_validator(instance, attribute, value, expected_type=None):
+    # might be useful to optimise the sub-validator tuple, in practice, we only
+    # have [bytes, bytes]
+    type_ = type(value)
+    if type_ != tuple and not isinstance(value, tuple):
+        raise AttributeTypeError(value, attribute)
+    if expected_type is None:
+        expected_type = attribute.type
+    args = expected_type.__args__
+
+    # assert len(args) != 2 or args[1] is Ellipsis
+    if len(args) != len(value):
+        raise AttributeTypeError(value, attribute)
+    try:
+        for item_type, item in zip(args, value):
+            validator = optimized_validator(item_type)
+            validator(instance, attribute, item, expected_type=item_type)
+    except AttributeTypeError:
+        raise AttributeTypeError(value, attribute) from None
+
+
+def _immutable_dict_validator(instance, attribute, value, expected_type=None):
+    value_type = type(value)
+    if value_type != ImmutableDict and not isinstance(value, ImmutableDict):
+        raise AttributeTypeError(value, attribute)
+
+    if expected_type is None:
+        expected_type = attribute.type
+    (expected_key_type, expected_value_type) = expected_type.__args__
+
+    key_validator = optimized_validator(expected_key_type)
+    value_validator = optimized_validator(expected_value_type)
+
+    try:
+        for (item_key, item_value) in value.items():
+            key_validator(
+                instance, attribute, item_key, expected_type=expected_key_type
+            )
+            value_validator(
+                instance, attribute, item_value, expected_type=expected_value_type
+            )
+    except AttributeTypeError:
+        raise AttributeTypeError(value, attribute) from None
+
+
+def optimized_validator(type_):
+    if type_ is object or type_ is Any:
+        return _true_validator
+
+    if type_ is None:
+        return _none_validator
+
+    origin = getattr(type_, "__origin__", None)
 
-    return validator
+    # Non-generic type, check it directly
+    if origin is None:
+        return _origin_type_validator
+
+    # Then, if it's a container, check its items.
+    if origin is tuple:
+        args = type_.__args__
+        if len(args) == 2 and args[1] is Ellipsis:
+            # Infinite tuple
+            return _tuple_infinite_validator
+        elif args == (bytes, bytes):
+            return _tuple_bytes_bytes_validator
+        else:
+            return _tuple_finite_validator
+    elif origin is Union:
+        args = type_.__args__
+        all_validators = tuple((optimized_validator(t), t) for t in args)
+
+        def union_validator(instance, attribute, value, expected_type=None):
+            for (validator, type_) in all_validators:
+                try:
+                    validator(instance, attribute, value, expected_type=type_)
+                except AttributeTypeError:
+                    pass
+                else:
+                    break
+            else:
+                raise AttributeTypeError(value, attribute)
+
+        return union_validator
+    elif origin is ImmutableDict:
+        return _immutable_dict_validator
+    else:
+        # No need to check dict or list. because they are converted to ImmutableDict
+        # and tuple respectively.
+        raise NotImplementedError(f"Type-checking {type_}")
+
+
+def optimize_all_validators(cls, old_fields):
+    """process validators to turn them into a faster version … eventually"""
+    new_fields = []
+    for f in old_fields:
+        if f.validator is generic_type_validator:
+            validator = optimized_validator(f.type)
+            f = f.evolve(validator=validator)
+        new_fields.append(f)
+    return new_fields
 
 
 ModelType = TypeVar("ModelType", bound="BaseModel")
@@ -285,15 +428,15 @@
             )
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class Person(BaseModel):
     """Represents the author/committer of a revision or release."""
 
     object_type: Final = "person"
 
-    fullname = attr.ib(type=bytes, validator=type_validator())
-    name = attr.ib(type=Optional[bytes], validator=type_validator(), eq=False)
-    email = attr.ib(type=Optional[bytes], validator=type_validator(), eq=False)
+    fullname = attr.ib(type=bytes, validator=generic_type_validator)
+    name = attr.ib(type=Optional[bytes], validator=generic_type_validator, eq=False)
+    email = attr.ib(type=Optional[bytes], validator=generic_type_validator, eq=False)
 
     @classmethod
     def from_fullname(cls, fullname: bytes):
@@ -367,14 +510,14 @@
         return super().from_dict(d)
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class Timestamp(BaseModel):
     """Represents a naive timestamp from a VCS."""
 
     object_type: Final = "timestamp"
 
-    seconds = attr.ib(type=int, validator=type_validator())
-    microseconds = attr.ib(type=int, validator=type_validator())
+    seconds = attr.ib(type=int, validator=generic_type_validator)
+    microseconds = attr.ib(type=int, validator=generic_type_validator)
 
     @seconds.validator
     def check_seconds(self, attribute, value):
@@ -389,15 +532,15 @@
             raise ValueError("Microseconds must be in [0, 1000000[.")
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class TimestampWithTimezone(BaseModel):
     """Represents a TZ-aware timestamp from a VCS."""
 
     object_type: Final = "timestamp_with_timezone"
 
-    timestamp = attr.ib(type=Timestamp, validator=type_validator())
+    timestamp = attr.ib(type=Timestamp, validator=generic_type_validator)
 
-    offset_bytes = attr.ib(type=bytes, validator=type_validator())
+    offset_bytes = attr.ib(type=bytes, validator=generic_type_validator)
     """Raw git representation of the timezone, as an offset from UTC.
     It should follow this format: ``+HHMM`` or ``-HHMM`` (including ``+0000`` and
     ``-0000``).
@@ -586,15 +729,15 @@
         return self._parse_offset_bytes(self.offset_bytes)
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class Origin(HashableObject, BaseModel):
     """Represents a software source: a VCS and an URL."""
 
     object_type: Final = "origin"
 
-    url = attr.ib(type=str, validator=type_validator())
+    url = attr.ib(type=str, validator=generic_type_validator)
 
-    id = attr.ib(type=Sha1Git, validator=type_validator(), default=b"")
+    id = attr.ib(type=Sha1Git, validator=generic_type_validator, default=b"")
 
     def unique_key(self) -> KeyType:
         return {"url": self.url}
@@ -610,18 +753,18 @@
         )
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class OriginVisit(BaseModel):
     """Represents an origin visit with a given type at a given point in time, by a
     SWH loader."""
 
     object_type: Final = "origin_visit"
 
-    origin = attr.ib(type=str, validator=type_validator())
-    date = attr.ib(type=datetime.datetime, validator=type_validator())
-    type = attr.ib(type=str, validator=type_validator())
+    origin = attr.ib(type=str, validator=generic_type_validator)
+    date = attr.ib(type=datetime.datetime, validator=generic_type_validator)
+    type = attr.ib(type=str, validator=generic_type_validator)
     """Should not be set before calling 'origin_visit_add()'."""
-    visit = attr.ib(type=Optional[int], validator=type_validator(), default=None)
+    visit = attr.ib(type=Optional[int], validator=generic_type_validator, default=None)
 
     @date.validator
     def check_date(self, attribute, value):
@@ -641,16 +784,16 @@
         return {"origin": self.origin, "date": str(self.date)}
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class OriginVisitStatus(BaseModel):
     """Represents a visit update of an origin at a given point in time."""
 
     object_type: Final = "origin_visit_status"
 
-    origin = attr.ib(type=str, validator=type_validator())
-    visit = attr.ib(type=int, validator=type_validator())
+    origin = attr.ib(type=str, validator=generic_type_validator)
+    visit = attr.ib(type=int, validator=generic_type_validator)
 
-    date = attr.ib(type=datetime.datetime, validator=type_validator())
+    date = attr.ib(type=datetime.datetime, validator=generic_type_validator)
     status = attr.ib(
         type=str,
         validator=attr.validators.in_(
@@ -658,13 +801,13 @@
         ),
     )
     snapshot = attr.ib(
-        type=Optional[Sha1Git], validator=type_validator(), repr=hash_repr
+        type=Optional[Sha1Git], validator=generic_type_validator, repr=hash_repr
     )
     # Type is optional be to able to use it before adding it to the database model
-    type = attr.ib(type=Optional[str], validator=type_validator(), default=None)
+    type = attr.ib(type=Optional[str], validator=generic_type_validator, default=None)
     metadata = attr.ib(
         type=Optional[ImmutableDict[str, object]],
-        validator=type_validator(),
+        validator=generic_type_validator,
         converter=freeze_optional_dict,
         default=None,
     )
@@ -707,14 +850,14 @@
         return f"ObjectType.{self.name}"
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class SnapshotBranch(BaseModel):
     """Represents one of the branches of a snapshot."""
 
     object_type: Final = "snapshot_branch"
 
-    target = attr.ib(type=bytes, validator=type_validator(), repr=hash_repr)
-    target_type = attr.ib(type=TargetType, validator=type_validator())
+    target = attr.ib(type=bytes, validator=generic_type_validator, repr=hash_repr)
+    target_type = attr.ib(type=TargetType, validator=generic_type_validator)
 
     @target.validator
     def check_target(self, attribute, value):
@@ -729,7 +872,7 @@
         return cls(target=d["target"], target_type=TargetType(d["target_type"]))
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class Snapshot(HashableObject, BaseModel):
     """Represents the full state of an origin at a given point in time."""
 
@@ -737,10 +880,12 @@
 
     branches = attr.ib(
         type=ImmutableDict[bytes, Optional[SnapshotBranch]],
-        validator=type_validator(),
+        validator=generic_type_validator,
         converter=freeze_optional_dict,
     )
-    id = attr.ib(type=Sha1Git, validator=type_validator(), default=b"", repr=hash_repr)
+    id = attr.ib(
+        type=Sha1Git, validator=generic_type_validator, default=b"", repr=hash_repr
+    )
 
     def _compute_hash_from_attributes(self) -> bytes:
         return _compute_hash_from_manifest(
@@ -763,26 +908,34 @@
         return CoreSWHID(object_type=SwhidObjectType.SNAPSHOT, object_id=self.id)
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class Release(HashableObjectWithManifest, BaseModel):
     object_type: Final = "release"
 
-    name = attr.ib(type=bytes, validator=type_validator())
-    message = attr.ib(type=Optional[bytes], validator=type_validator())
-    target = attr.ib(type=Optional[Sha1Git], validator=type_validator(), repr=hash_repr)
-    target_type = attr.ib(type=ObjectType, validator=type_validator())
-    synthetic = attr.ib(type=bool, validator=type_validator())
-    author = attr.ib(type=Optional[Person], validator=type_validator(), default=None)
+    name = attr.ib(type=bytes, validator=generic_type_validator)
+    message = attr.ib(type=Optional[bytes], validator=generic_type_validator)
+    target = attr.ib(
+        type=Optional[Sha1Git], validator=generic_type_validator, repr=hash_repr
+    )
+    target_type = attr.ib(type=ObjectType, validator=generic_type_validator)
+    synthetic = attr.ib(type=bool, validator=generic_type_validator)
+    author = attr.ib(
+        type=Optional[Person], validator=generic_type_validator, default=None
+    )
     date = attr.ib(
-        type=Optional[TimestampWithTimezone], validator=type_validator(), default=None
+        type=Optional[TimestampWithTimezone],
+        validator=generic_type_validator,
+        default=None,
     )
     metadata = attr.ib(
         type=Optional[ImmutableDict[str, object]],
-        validator=type_validator(),
+        validator=generic_type_validator,
         converter=freeze_optional_dict,
         default=None,
     )
-    id = attr.ib(type=Sha1Git, validator=type_validator(), default=b"", repr=hash_repr)
+    id = attr.ib(
+        type=Sha1Git, validator=generic_type_validator, default=b"", repr=hash_repr
+    )
     raw_manifest = attr.ib(type=Optional[bytes], default=None)
 
     def _compute_hash_from_attributes(self) -> bytes:
@@ -839,31 +992,37 @@
     return tuple((k, v) for k, v in value)
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class Revision(HashableObjectWithManifest, BaseModel):
     object_type: Final = "revision"
 
-    message = attr.ib(type=Optional[bytes], validator=type_validator())
-    author = attr.ib(type=Optional[Person], validator=type_validator())
-    committer = attr.ib(type=Optional[Person], validator=type_validator())
-    date = attr.ib(type=Optional[TimestampWithTimezone], validator=type_validator())
+    message = attr.ib(type=Optional[bytes], validator=generic_type_validator)
+    author = attr.ib(type=Optional[Person], validator=generic_type_validator)
+    committer = attr.ib(type=Optional[Person], validator=generic_type_validator)
+    date = attr.ib(
+        type=Optional[TimestampWithTimezone], validator=generic_type_validator
+    )
     committer_date = attr.ib(
-        type=Optional[TimestampWithTimezone], validator=type_validator()
+        type=Optional[TimestampWithTimezone], validator=generic_type_validator
     )
-    type = attr.ib(type=RevisionType, validator=type_validator())
-    directory = attr.ib(type=Sha1Git, validator=type_validator(), repr=hash_repr)
-    synthetic = attr.ib(type=bool, validator=type_validator())
+    type = attr.ib(type=RevisionType, validator=generic_type_validator)
+    directory = attr.ib(type=Sha1Git, validator=generic_type_validator, repr=hash_repr)
+    synthetic = attr.ib(type=bool, validator=generic_type_validator)
     metadata = attr.ib(
         type=Optional[ImmutableDict[str, object]],
-        validator=type_validator(),
+        validator=generic_type_validator,
         converter=freeze_optional_dict,
         default=None,
     )
-    parents = attr.ib(type=Tuple[Sha1Git, ...], validator=type_validator(), default=())
-    id = attr.ib(type=Sha1Git, validator=type_validator(), default=b"", repr=hash_repr)
+    parents = attr.ib(
+        type=Tuple[Sha1Git, ...], validator=generic_type_validator, default=()
+    )
+    id = attr.ib(
+        type=Sha1Git, validator=generic_type_validator, default=b"", repr=hash_repr
+    )
     extra_headers = attr.ib(
         type=Tuple[Tuple[bytes, bytes], ...],
-        validator=type_validator(),
+        validator=generic_type_validator,
         converter=tuplify_extra_headers,
         default=(),
     )
@@ -951,14 +1110,14 @@
 _DIR_ENTRY_TYPES = ["file", "dir", "rev"]
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class DirectoryEntry(BaseModel):
     object_type: Final = "directory_entry"
 
-    name = attr.ib(type=bytes, validator=type_validator())
+    name = attr.ib(type=bytes, validator=generic_type_validator)
     type = attr.ib(type=str, validator=attr.validators.in_(_DIR_ENTRY_TYPES))
-    target = attr.ib(type=Sha1Git, validator=type_validator(), repr=hash_repr)
-    perms = attr.ib(type=int, validator=type_validator(), converter=int, repr=oct)
+    target = attr.ib(type=Sha1Git, validator=generic_type_validator, repr=hash_repr)
+    perms = attr.ib(type=int, validator=generic_type_validator, converter=int, repr=oct)
     """Usually one of the values of `swh.model.from_disk.DentryPerms`."""
 
     @name.validator
@@ -967,12 +1126,14 @@
             raise ValueError(f"{value!r} is not a valid directory entry name.")
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class Directory(HashableObjectWithManifest, BaseModel):
     object_type: Final = "directory"
 
-    entries = attr.ib(type=Tuple[DirectoryEntry, ...], validator=type_validator())
-    id = attr.ib(type=Sha1Git, validator=type_validator(), default=b"", repr=hash_repr)
+    entries = attr.ib(type=Tuple[DirectoryEntry, ...], validator=generic_type_validator)
+    id = attr.ib(
+        type=Sha1Git, validator=generic_type_validator, default=b"", repr=hash_repr
+    )
     raw_manifest = attr.ib(type=Optional[bytes], default=None)
 
     def _compute_hash_from_attributes(self) -> bytes:
@@ -1086,7 +1247,7 @@
         return (True, dir_)
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class BaseContent(BaseModel):
     status = attr.ib(
         type=str, validator=attr.validators.in_(["visible", "hidden", "absent"])
@@ -1122,16 +1283,16 @@
         return {algo: getattr(self, algo) for algo in DEFAULT_ALGORITHMS}
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class Content(BaseContent):
     object_type: Final = "content"
 
-    sha1 = attr.ib(type=bytes, validator=type_validator(), repr=hash_repr)
-    sha1_git = attr.ib(type=Sha1Git, validator=type_validator(), repr=hash_repr)
-    sha256 = attr.ib(type=bytes, validator=type_validator(), repr=hash_repr)
-    blake2s256 = attr.ib(type=bytes, validator=type_validator(), repr=hash_repr)
+    sha1 = attr.ib(type=bytes, validator=generic_type_validator, repr=hash_repr)
+    sha1_git = attr.ib(type=Sha1Git, validator=generic_type_validator, repr=hash_repr)
+    sha256 = attr.ib(type=bytes, validator=generic_type_validator, repr=hash_repr)
+    blake2s256 = attr.ib(type=bytes, validator=generic_type_validator, repr=hash_repr)
 
-    length = attr.ib(type=int, validator=type_validator())
+    length = attr.ib(type=int, validator=generic_type_validator)
 
     status = attr.ib(
         type=str,
@@ -1139,11 +1300,11 @@
         default="visible",
     )
 
-    data = attr.ib(type=Optional[bytes], validator=type_validator(), default=None)
+    data = attr.ib(type=Optional[bytes], validator=generic_type_validator, default=None)
 
     ctime = attr.ib(
         type=Optional[datetime.datetime],
-        validator=type_validator(),
+        validator=generic_type_validator,
         default=None,
         eq=False,
     )
@@ -1205,29 +1366,33 @@
         return CoreSWHID(object_type=SwhidObjectType.CONTENT, object_id=self.sha1_git)
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class SkippedContent(BaseContent):
     object_type: Final = "skipped_content"
 
-    sha1 = attr.ib(type=Optional[bytes], validator=type_validator(), repr=hash_repr)
+    sha1 = attr.ib(
+        type=Optional[bytes], validator=generic_type_validator, repr=hash_repr
+    )
     sha1_git = attr.ib(
-        type=Optional[Sha1Git], validator=type_validator(), repr=hash_repr
+        type=Optional[Sha1Git], validator=generic_type_validator, repr=hash_repr
+    )
+    sha256 = attr.ib(
+        type=Optional[bytes], validator=generic_type_validator, repr=hash_repr
     )
-    sha256 = attr.ib(type=Optional[bytes], validator=type_validator(), repr=hash_repr)
     blake2s256 = attr.ib(
-        type=Optional[bytes], validator=type_validator(), repr=hash_repr
+        type=Optional[bytes], validator=generic_type_validator, repr=hash_repr
     )
 
-    length = attr.ib(type=Optional[int], validator=type_validator())
+    length = attr.ib(type=Optional[int], validator=generic_type_validator)
 
     status = attr.ib(type=str, validator=attr.validators.in_(["absent"]))
-    reason = attr.ib(type=Optional[str], validator=type_validator(), default=None)
+    reason = attr.ib(type=Optional[str], validator=generic_type_validator, default=None)
 
-    origin = attr.ib(type=Optional[str], validator=type_validator(), default=None)
+    origin = attr.ib(type=Optional[str], validator=generic_type_validator, default=None)
 
     ctime = attr.ib(
         type=Optional[datetime.datetime],
-        validator=type_validator(),
+        validator=generic_type_validator,
         default=None,
         eq=False,
     )
@@ -1298,19 +1463,19 @@
         return f"MetadataAuthorityType.{self.name}"
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class MetadataAuthority(BaseModel):
     """Represents an entity that provides metadata about an origin or
     software artifact."""
 
     object_type: Final = "metadata_authority"
 
-    type = attr.ib(type=MetadataAuthorityType, validator=type_validator())
-    url = attr.ib(type=str, validator=type_validator())
+    type = attr.ib(type=MetadataAuthorityType, validator=generic_type_validator)
+    url = attr.ib(type=str, validator=generic_type_validator)
     metadata = attr.ib(
         type=Optional[ImmutableDict[str, Any]],
         default=None,
-        validator=type_validator(),
+        validator=generic_type_validator,
         converter=freeze_optional_dict,
     )
 
@@ -1332,19 +1497,19 @@
         return {"type": self.type.value, "url": self.url}
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class MetadataFetcher(BaseModel):
     """Represents a software component used to fetch metadata from a metadata
     authority, and ingest them into the Software Heritage archive."""
 
     object_type: Final = "metadata_fetcher"
 
-    name = attr.ib(type=str, validator=type_validator())
-    version = attr.ib(type=str, validator=type_validator())
+    name = attr.ib(type=str, validator=generic_type_validator)
+    version = attr.ib(type=str, validator=generic_type_validator)
     metadata = attr.ib(
         type=Optional[ImmutableDict[str, Any]],
         default=None,
-        validator=type_validator(),
+        validator=generic_type_validator,
         converter=freeze_optional_dict,
     )
 
@@ -1369,40 +1534,42 @@
     return value.astimezone(datetime.timezone.utc).replace(microsecond=0)
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class RawExtrinsicMetadata(HashableObject, BaseModel):
     object_type: Final = "raw_extrinsic_metadata"
 
     # target object
-    target = attr.ib(type=ExtendedSWHID, validator=type_validator())
+    target = attr.ib(type=ExtendedSWHID, validator=generic_type_validator)
 
     # source
     discovery_date = attr.ib(type=datetime.datetime, converter=normalize_discovery_date)
-    authority = attr.ib(type=MetadataAuthority, validator=type_validator())
-    fetcher = attr.ib(type=MetadataFetcher, validator=type_validator())
+    authority = attr.ib(type=MetadataAuthority, validator=generic_type_validator)
+    fetcher = attr.ib(type=MetadataFetcher, validator=generic_type_validator)
 
     # the metadata itself
-    format = attr.ib(type=str, validator=type_validator())
-    metadata = attr.ib(type=bytes, validator=type_validator())
+    format = attr.ib(type=str, validator=generic_type_validator)
+    metadata = attr.ib(type=bytes, validator=generic_type_validator)
 
     # context
-    origin = attr.ib(type=Optional[str], default=None, validator=type_validator())
-    visit = attr.ib(type=Optional[int], default=None, validator=type_validator())
+    origin = attr.ib(type=Optional[str], default=None, validator=generic_type_validator)
+    visit = attr.ib(type=Optional[int], default=None, validator=generic_type_validator)
     snapshot = attr.ib(
-        type=Optional[CoreSWHID], default=None, validator=type_validator()
+        type=Optional[CoreSWHID], default=None, validator=generic_type_validator
     )
     release = attr.ib(
-        type=Optional[CoreSWHID], default=None, validator=type_validator()
+        type=Optional[CoreSWHID], default=None, validator=generic_type_validator
     )
     revision = attr.ib(
-        type=Optional[CoreSWHID], default=None, validator=type_validator()
+        type=Optional[CoreSWHID], default=None, validator=generic_type_validator
     )
-    path = attr.ib(type=Optional[bytes], default=None, validator=type_validator())
+    path = attr.ib(type=Optional[bytes], default=None, validator=generic_type_validator)
     directory = attr.ib(
-        type=Optional[CoreSWHID], default=None, validator=type_validator()
+        type=Optional[CoreSWHID], default=None, validator=generic_type_validator
     )
 
-    id = attr.ib(type=Sha1Git, validator=type_validator(), default=b"", repr=hash_repr)
+    id = attr.ib(
+        type=Sha1Git, validator=generic_type_validator, default=b"", repr=hash_repr
+    )
 
     def _compute_hash_from_attributes(self) -> bytes:
         return _compute_hash_from_manifest(
@@ -1592,16 +1759,18 @@
         )
 
 
-@attr.s(frozen=True, slots=True)
+@attr.s(frozen=True, slots=True, field_transformer=optimize_all_validators)
 class ExtID(HashableObject, BaseModel):
     object_type: Final = "extid"
 
-    extid_type = attr.ib(type=str, validator=type_validator())
-    extid = attr.ib(type=bytes, validator=type_validator())
-    target = attr.ib(type=CoreSWHID, validator=type_validator())
-    extid_version = attr.ib(type=int, validator=type_validator(), default=0)
+    extid_type = attr.ib(type=str, validator=generic_type_validator)
+    extid = attr.ib(type=bytes, validator=generic_type_validator)
+    target = attr.ib(type=CoreSWHID, validator=generic_type_validator)
+    extid_version = attr.ib(type=int, validator=generic_type_validator, default=0)
 
-    id = attr.ib(type=Sha1Git, validator=type_validator(), default=b"", repr=hash_repr)
+    id = attr.ib(
+        type=Sha1Git, validator=generic_type_validator, default=b"", repr=hash_repr
+    )
 
     @classmethod
     def from_dict(cls, d):
diff --git a/swh/model/tests/test_model.py b/swh/model/tests/test_model.py
--- a/swh/model/tests/test_model.py
+++ b/swh/model/tests/test_model.py
@@ -43,7 +43,8 @@
     TargetType,
     Timestamp,
     TimestampWithTimezone,
-    type_validator,
+    generic_type_validator,
+    optimized_validator,
 )
 import swh.model.swhids
 from swh.model.swhids import CoreSWHID, ExtendedSWHID, ObjectType
@@ -275,8 +276,34 @@
         for value in values
     ],
 )
-def test_type_validator_valid(type_, value):
-    type_validator()(None, attr.ib(type=type_), value)
+def test_generic_type_validator_valid(type_, value):
+    generic_type_validator(None, attr.ib(type=type_), value)
+
+
+@pytest.mark.parametrize(
+    "type_,value",
+    [
+        pytest.param(type_, value, id=f"type={type_}, value={value}")
+        for (type_, values, _) in _TYPE_VALIDATOR_PARAMETERS
+        for value in values
+    ],
+)
+def test_optimized_type_validator_valid(type_, value):
+    validator = optimized_validator(type_)
+    validator(None, attr.ib(type=type_), value)
+
+
+@pytest.mark.parametrize(
+    "type_,value",
+    [
+        pytest.param(type_, value, id=f"type={type_}, value={value}")
+        for (type_, _, values) in _TYPE_VALIDATOR_PARAMETERS
+        for value in values
+    ],
+)
+def test_generic_type_validator_invalid(type_, value):
+    with pytest.raises(AttributeTypeError):
+        generic_type_validator(None, attr.ib(type=type_), value)
 
 
 @pytest.mark.parametrize(
@@ -287,9 +314,10 @@
         for value in values
     ],
 )
-def test_type_validator_invalid(type_, value):
+def test_optimized_type_validator_invalid(type_, value):
+    validator = optimized_validator(type_)
     with pytest.raises(AttributeTypeError):
-        type_validator()(None, attr.ib(type=type_), value)
+        validator(None, attr.ib(type=type_), value)
 
 
 @pytest.mark.parametrize("object_type, objects", TEST_OBJECTS.items())