Let me attempt an answer. Calling @code_warntype on Val(1) treats Val as the “main” function and 1 as the “input”. Technically, the Val function is not type stable. However, inside another function when using Val(N) where N is a compile-time constant, e.g. a type parameter or the length of a tuple, inter-procedural constant propagation (https://github.com/JuliaLang/julia/pull/24362) kicks in making Val(N) infer nicely. Note that the input to the function Val when tested directly with @code_warntype is not treated as a constant in the above case, hence the type instability.
I’m definitely not an expert in this area, but I think that, at least in recent julia versions, precisely thanks to constant propagation, using Val is less of a necessity.
For example, the following infers just fine:
julia> using Test
julia> f(v::AbstractArray{T, N}) where {T, N} = ntuple(log, N)
f (generic function with 1 method)
julia> @inferred f([1 2; 3 4])
(0.0, 0.6931471805599453)
So I’m not completely sure that the performance tips here to use Val{N}() for this scenario are still relevant. (Of course I could be wrong and there could be more complex scenarios in which using Val matters.)