The BEAM scheduler is preemptive but does not use time slices. Each process gets a work budget measured in reductions and runs until that budget is exhausted (or it blocks, e.g. waiting in a receive), at which point it is preempted and put back on the run queue.
The number: the per-schedule budget is the VM constant CONTEXT_REDS, defined in erl_vm.h. It is currently 4000 reductions (since OTP 20.0). Before OTP 20.0 it was 2000, the figure you still see in older writeups (it was 2000 as far back as R12B). When a process is scheduled in, its fcalls counter is set to CONTEXT_REDS and decremented as it works; when it reaches zero, the process yields.
What a reduction is: roughly one function call. It is a per-process counter normally incremented by one per function call, used as a proxy for amount of work done instead of wall-clock time. BIFs/NIFs and other operations also charge reductions in proportion to the work they do (long-running C functions are expected to bump reductions so they cooperate), but the simple model is one reduction is approximately one function call.
Why reductions, not time: preemptive scheduling at the Erlang level is implemented via cooperative yielding at the C level. The compiler and VM cooperate so each process yields within a bounded amount of work, ensuring no single process monopolizes a scheduler thread and that all processes make progress (soft real-time fairness). A related constant, INPUT_REDUCTIONS (currently 2*CONTEXT_REDS, i.e. 8000), governs how often a scheduler checks for I/O.
In short: roughly 4000 reductions of work (2000 before OTP 20), where a reduction is approximately one function call, not a fixed slice of wall-clock time.
Sources: