There may be room to optimize further, but I get a pretty big pickup specializing just on the names of the NamedTuple
# original implementation
julia> @btime cvt($state)
2.078 μs (38 allocations: 2.31 KiB)
cvt(a::NamedTuple{N, T}) where {N, T} = NamedTuple{N}(cvt.(values(a)))
julia> @btime cvt($state)
126.710 ns (5 allocations: 352 bytes)