It's mainly a legacy of the python 2 days, and fear of breaking backward compatibility.
But also, we need to avoid mixing str and bytes objects internally to the template functions. So if we kept everything as str, we'd need to decode any bytes objects that came in. But we know that ultimately anything we produce in a template is going out on the wire as bytes, so we'd potentially do a redundant decode/encode pair. So "encode everything to bytes" seems like a better default policy than "decode everything to str".
If everything is str already, that's no big deal, but given Tornado's historical bias towards bytes, it seems likely that we'd have to make more far-reaching changes than 10 LOC in template.py to make a change like this without performance regressions.
-Ben