The problem
With pipeline parallelism, the layers of a model are divided among GPUs (graphics processing units), called stages: stage 0 holds the first layers, stage 1 the next ones, and so on. Each stage can only work on a piece of data after the previous stage has finished with it. To keep the stages busy, a batch is cut into micro-batches that follow each other through the stages, like cars on an assembly line. You will build GPipe's forward schedule and measure how much time the stages spend idle.
The schedule
Time is divided into slots, and every forward pass of one micro-batch on one stage takes one slot. Micro-batch reaches stage at slot : stage 0 starts micro-batch 0 at slot 0, stage 1 can start it only at slot 1, after stage 0 is done with it.
The schedule is a table with one row per stage and one column per slot; it needs slots for the last micro-batch to leave the last stage.
Example. stages, micro-batches: slots.
-1means idle.
stage slot 0 slot 1 slot 2 slot 3 0 0 1 -1 -1 1 -1 0 1 -1 2 -1 -1 0 1 6 of the 12 cells are idle: the stages spend half of the time waiting.
The bubble
The idle cells are the bubble: at the start the pipeline fills (later stages wait), at the end it drains (earlier stages have nothing left). Each stage works for slots out of , so the idle fraction is:
where is the number of stages and the number of micro-batches.
Example. For the table above, , matching the 6 idle cells out of 12. With and it drops to : the bubble shrinks only when there are many more micro-batches than stages.
What to implement
gpipe_forward_schedule(p, m): a NumPy integer array of shape , where entry[s, t]is the micro-batch that stage processes in slot , or-1if the stage is idle.bubble_fraction(schedule): the fraction of all cells that are idle, measured from the schedule.bubble_formula(p, m): the formula above.
Requirements
- Stage starts micro-batch exactly one slot after stage .
bubble_fractioncounts the-1cells; it must agree withbubble_formulafor every and .
Hint
Row of the table is idle slots, then the micro-batches , then idle slots until the end. Fill the array with -1, then write np.arange(m) into the right slice of each row.