!exception command uses the Exception Bitmap field of VMCS, which is a mask that if you set a special bit on it then every time that an exception generated (on that special IDT entry which we set the mask), it causes vm-exit.
It's clear that only those entries that you want will cause a vm-exit and not all entries.
It works on the first 32 entries of IDT or entries between 0x0 to 0x1f.
All vm-exits are handled in the same way, but page-faults (#PF) are different. In those cases, HyperDbg also the cr2 register too.
!interrupt, on the other hand, is different. There is a bit in pin-based vmx controls, which cause vm-exit on all external-interrupts (starting from 0x20 to 0xff); thus, if you want just an entry above the 0x1f, then all of the external-interrupts cause vm-exit and HyperDbg manages them, so it's substantially slower.
There also other considerations for emulating external-interrupts. For example, the target guest might not be in an interruptible-state (e.g., RFLAG.IF bit is not set), so we have to save the interrupt details somewhere else and wait for a window to open (interrupt-window exiting).
Whenever the guest is in an interruptible-state, it causes vm-exit (because of interrupt-window exiting bit), and we re-inject all the accumulated interrupts.
You should not try to intercept and handle all external-interrupts by yourself; just choose one entry. It is because some interrupts like clock-cycle are at a high-rate, and if you intercept them, then Windows timing-clock will be dead, and you'll end up with a BSOD.