Note that, if you're worried about the optimization being missed, the panic branch is arguably better than the ? branch -- the compiler know that blocks that panic are #[cold], which LLVM uses to make the branches going to the panics be unlikely, and thus give better branch prediction and instruction cache usage for the non-panicking case.
Not to mention that the map version will have an accurate size_hint, which the filter_map will not, so avoiding the panicking path might make your performance worse by causing extra Vec reallocations later.
(But, realistically, it's not going to fail to optimize this. The whole point of chunks_exact is that it makes it obvious to LLVM how long the chunks are, and thus simplifies bounds checks and other checks like this.)
Something useful to know is that anything of the form "call this to get the next element" can be converted into an iterator using std::iter::from_fn().
let items = [1, 2, 3, 4, 5].into_iter();
let iter = std::iter::from_fn(|| items.next_chunk::<2>().ok());