This is a solution for pydantic v2.
It naturally uses depth first search down the hierarchy of composition and then with the field info found, it builds models with required=False bottom-up towards the top.
Also, it does not use any low level hack that depends on the internal implementation of pydantic objects, many of which are changed or not available in v2.
from pydantic import BaseModel, create_model
class MyModelE(BaseModel):
       f: float
class MyModelA(BaseModel):
   d: str
   e: MyModelE
class MyModel(BaseModel):
   a: MyModelA
   b: int
def is_pydantic_model(obj):
    try:
        return issubclass(obj, BaseModel)
    except TypeError:
        return False
def show_hierarchy(Model: BaseModel, indent: int=0):
    for k, v in Model.model_fields.items():
        print(f'{" "*indent}{Model.__name__}.{k}: '
              f'type={getattr(v.annotation, "__name__", v.annotation)}, '
              f'required={v.is_required()}')
        if is_pydantic_model(v.annotation):
            show_hierarchy(v.annotation, indent+2)
def unset_required(Model: BaseModel, name: str=None) -> BaseModel:
    fields = {}
    for k, v in Model.model_fields.items():
        if is_pydantic_model(v.annotation):
            fields[k] = (unset_required(v.annotation), None) 
        else:
            fields[k] = (v.annotation, None) 
    return create_model(name if name is not None else Model.__name__, **fields)
show_hierarchy(MyModel)
print('')
UpdateModel = unset_required(MyModel, name='UpdateModel')
show_hierarchy(UpdateModel)
This results in
MyModel.a: type=MyModelA, required=True
  MyModelA.d: type=str, required=True
  MyModelA.e: type=MyModelE, required=True
    MyModelE.f: type=float, required=True
MyModel.b: type=int, required=True
UpdateModel.a: type=MyModelA, required=False
  MyModelA.d: type=str, required=False
  MyModelA.e: type=MyModelE, required=False
    MyModelE.f: type=float, required=False
UpdateModel.b: type=int, required=False
It can handle a model where the same sub-model is used multiple times at different levels as well.
class MyModelE(BaseModel):
   f: float
class MyModelA(BaseModel):
   d: str
   e: MyModelE  # MyModelE
class MyModel(BaseModel):
   a: MyModelA
   b: MyModelE  # MyModelE
MyModel.a: type=MyModelA, required=True
  MyModelA.d: type=str, required=True
  MyModelA.e: type=MyModelE, required=True
    MyModelE.f: type=float, required=True # MyModelE
MyModel.b: type=MyModelE, required=True
  MyModelE.f: type=float, required=True   # MyModelE
UpdateModel.a: type=MyModelA, required=False
  MyModelA.d: type=str, required=False
  MyModelA.e: type=MyModelE, required=False
    MyModelE.f: type=float, required=False # MyModelE
UpdateModel.b: type=MyModelE, required=False
  MyModelE.f: type=float, required=False   # MyModelE