code: def next_plan(
      starting_proposal: List[ShardingOption],
      budget: Optional[int],
      enumerator: Optional[Enumerator],
  ) -> Optional[List[ShardingOption]]:
      if budget is None or enumerator is None:
          return None
  
      def none_to_zero(x: Optional[float]) -> float:
          return x if x is not None else 0.0
  
      proposal = copy.deepcopy(starting_proposal)
      # This is the subset of tables that we can scale
      cache_tables = [
          sharding_option
          for sharding_option in proposal
          if sharding_option.compute_kernel
          == EmbeddingComputeKernel.FUSED_UVM_CACHING.value
          and none_to_zero(
              EmbeddingOffloadScaleupProposer.get_cacheability(
                  sharding_option
              )
          )
          * none_to_zero(
              EmbeddingOffloadScaleupProposer.get_expected_lookups(
                  sharding_option
              )
          )
          * none_to_zero(
              EmbeddingOffloadScaleupProposer.get_load_factor(
                  sharding_option
              )
          )
          > 0
      ]
      # Nothing to scale
      if len(cache_tables) == 0:
          return None
  
      size_model = (
          EmbeddingOffloadScaleupProposer.build_affine_storage_model(
              cache_tables, enumerator
          )
      )
      clfs = torch.tensor(
          [
              EmbeddingOffloadScaleupProposer.get_load_factor(
                  sharding_option
              )
              for sharding_option in cache_tables
          ]
      )
      # cooked_cacheability is cacheability scaled by the expected number of cache
      # lookups.
  
      cooked_cacheability = torch.tensor(
          [
              none_to_zero(
                  EmbeddingOffloadScaleupProposer.get_cacheability(
                      sharding_option
                  )
              )
              * none_to_zero(
                  EmbeddingOffloadScaleupProposer.get_expected_lookups(
                      sharding_option
                  )
              )
              for sharding_option in cache_tables
          ]
      )
      new_clfs = EmbeddingOffloadScaleupProposer.allocate_budget(
          model=size_model,
          clfs=clfs,
          budget=budget,
          allocation_priority=cooked_cacheability,
      )
      # apply new_clfs, promoting tables that made it to 1.0
      for sharding_option, clf in zip(cache_tables, new_clfs):
          clf = clf.item()  # tensor scalar -> scalar
          assert sharding_option.cache_params  # appease pyre
          sharding_option.cache_params.load_factor = clf
          if clf > 0.9999:  # tolerate float roundoff
              assert sharding_option.cache_params  # appease pyre
              sharding_option.cache_params.load_factor = None
              sharding_option.compute_kernel = (
                  EmbeddingComputeKernel.FUSED.value
              )
      # recalculate cost estimates of modified tables
      enumerator.populate_estimates(cache_tables)
      return proposal
  
imports: import copy
  import itertools
  import logging
  from decimal import Decimal
  from typing import cast, Dict, List, Optional, Set, Tuple, Union
  import torch
  from torchrec.distributed.embedding_types import EmbeddingComputeKernel
  from torchrec.distributed.planner.types import Enumerator, Perf, Proposer, ShardingOption, Topology
  from torchrec.distributed.planner.utils import BinarySearchPredicate, bytes_to_gb, prod
label_desc: Use image node_id 6 for calling the EmbeddingOffloadScaleupProposer obj's underlying member method code with example usage: obj.next_plan(starting_proposal, budget, enumerator) and returns: proposal, None, unknown, None