Bucketed ring buffer for storing prediction errors on CPU. Two layouts are supported: * **1D (timestep-only)** — when ``num_blocks <= 0``. Buckets are keyed by the diffusion timestep. This is the original SVI behavior. * **2D (position × timestep)** — when ``num_blocks > 0
| 6 | |
| 7 | |
| 8 | class ErrorBuffer: |
| 9 | """Bucketed ring buffer for storing prediction errors on CPU. |
| 10 | |
| 11 | Two layouts are supported: |
| 12 | |
| 13 | * **1D (timestep-only)** — when ``num_blocks <= 0``. Buckets are keyed by |
| 14 | the diffusion timestep. This is the original SVI behavior. |
| 15 | |
| 16 | * **2D (position × timestep)** — when ``num_blocks > 0``. Each entry is |
| 17 | keyed by both the global block position along the sequence and the |
| 18 | timestep. Inject paths can then choose: |
| 19 | - ``sample(pos, t)``: match BOTH position and timestep |
| 20 | (E_vid / E_noise — noise-level dependent errors) |
| 21 | - ``sample_pos_any_t(pos)``: match position, sample uniformly across |
| 22 | timesteps (E_img — position-dependent context corruption that is |
| 23 | agnostic to the current denoising step) |
| 24 | - ``sample_global()``: legacy fallback, samples uniformly everywhere |
| 25 | |
| 26 | The 2D layout encodes the teacher-forcing insight that ``noisy_suffix[i]`` |
| 27 | looks at clean_prefix[0..i] during training but at model rollouts during |
| 28 | inference; storing prediction errors per-position therefore lets later |
| 29 | blocks self-feed larger errors without any manual position ramp. |
| 30 | |
| 31 | **Sharded timestep buckets** (``shard_size > 1``): |
| 32 | Each rank only allocates the timestep buckets it owns |
| 33 | (``t_bucket % shard_size == shard_rank``), reducing per-rank CPU memory |
| 34 | by ~``shard_size`` times. Typically ``shard_rank/shard_size`` are set to |
| 35 | ``sp_rank/sp_size`` so that sharding is per-SP-rank and saving follows |
| 36 | the same per-SP-rank pattern as the 2D position split. On ``add()``, |
| 37 | non-owned buckets are silently skipped; on ``sample()``, non-owned buckets |
| 38 | are remapped to the nearest owned one. |
| 39 | """ |
| 40 | |
| 41 | def __init__( |
| 42 | self, |
| 43 | num_buckets=40, |
| 44 | max_size_per_bucket=50, |
| 45 | num_train_timesteps=1000, |
| 46 | modulate_factor=0.3, |
| 47 | replacement_strategy="random", |
| 48 | num_blocks=0, |
| 49 | global_block_offset=0, |
| 50 | shard_rank=0, |
| 51 | shard_size=1, |
| 52 | ): |
| 53 | self.num_buckets = num_buckets |
| 54 | self.max_size = max_size_per_bucket |
| 55 | self.num_train_timesteps = num_train_timesteps |
| 56 | self.modulate_factor = modulate_factor |
| 57 | self.replacement_strategy = replacement_strategy |
| 58 | self.bucket_width = num_train_timesteps / num_buckets |
| 59 | self.num_blocks = int(num_blocks) if num_blocks else 0 |
| 60 | # ``global_block_offset`` is only used for stats / debug display so |
| 61 | # users can tell which absolute positions of the full sequence this |
| 62 | # buffer covers (the LAST SP rank carries the highest accumulated |
| 63 | # error positions). It does NOT participate in bucket keying. |
| 64 | self.global_block_offset = int(global_block_offset) |
| 65 |
no outgoing calls
no test coverage detected