Because free still exists and there is no “allocate this number of bytes of memory; I promise to free it with a call to free_size” call, you can’t have a malloc implementation that doesn’t track block sizes.
I think the only thing free_size adds is robustness. Allocators can check the passed in size with what they know and abort the program if they do not match. That can thwart some security issues.