def _greedy_by_size_memory_planning(
    sorted_graph: List[Tensor], tensor_usage_records: List[TensorUsageRecord]
):
    """
    based on the greedy-by-size algorithm for offset calculation described in
    the following paper:
        Yury Pisarchyk, Juhyun Lee,
        Efficient Memory Management for Deep Neural Net Inference,
        https://arxiv.org/abs/2001.03288
    """
    # sort tensor usage records in non-increasing order by their sizes
    sorted_tensor_usage_records = sorted(
        tensor_usage_records, key=lambda r: r.size, reverse=True
    )

    max_blob = 0
    # For tensors that have been assigned, we keep their tensor usage records
    # in increasing order by memory offsets
    sorted_assigned_records = []
    for tensor_record in sorted_tensor_usage_records:
        tensor, first_op_idx, last_op_idx, size = tensor_record
        prev_offset = 0
        best_offset = None
        smallest_gap = pow(2, 63) - 1
        # Iterate through tensors that have been allocated.
        # For those whose usage intervals intersect with that of current
        # tensor, we try to find the smallest valid memory gap between such two
        # allocated tensors, which is big enough to hold current tensor.
        # If such a gap is found, we will place current tensor in the gap.
        for a_record in sorted_assigned_records:
            a_tensor, a_first_op_idx, a_last_op_idx, a_size = a_record
            max_first_op_idx = max(first_op_idx, a_first_op_idx)
            min_last_op_idx = min(last_op_idx, a_last_op_idx)
            # current tensor overlaps with this assigned tensor
            if max_first_op_idx <= min_last_op_idx:
                a_offset = a_tensor._attrs["offset"]
                gap = a_offset - prev_offset
                if size <= gap < smallest_gap:
                    smallest_gap = gap
                    best_offset = prev_offset
                prev_offset = max(prev_offset, a_offset + a_size)
        # If we can't find a valid memory gap between two allocated tensors,
        # we put current tensor to the rightmost tensor whose usage interval
        # intersects with that of the current tensor.
        if best_offset is None:
            best_offset = prev_offset
        tensor._attrs["offset"] = best_offset
        max_blob = max(max_blob, best_offset + size)

        # bisect from Python <=3.9 doesn't have the key parameter
        sorted_offsets = [r.tensor._attrs["offset"] for r in sorted_assigned_records]
        in_pos = bisect.bisect_right(
            sorted_offsets, tensor_record.tensor._attrs["offset"]
        )
        sorted_assigned_records.insert(in_pos, tensor_record)

    # now we assign blobs for weights and inputs
    constant_offset = 0
    for node in sorted_graph:
        if (
            node._attrs["data"] is not None
            or node._attrs["constant_folding_output_idx"] is not None
        ):
            node._attrs["offset"] = constant_offset
            constant_offset += node.size_bytes(alignment=64)

    # assign offsets to tensor views
    # this step must happen after weights and inputs are assigned so that views
    # of inputs are properly handled
    assign_offsets_to_views_and_outputs(sorted_graph)

    workspace = _compute_workspace(sorted_graph)

    # make sure we've covered the entire graph
    return (max_blob, constant_offset, workspace)

    based on the greedy-by-size algorithm for offset calculation described in
    the following paper:
        Yury Pisarchyk, Juhyun Lee,
        Efficient Memory Management for Deep Neural Net Inference,
        https://arxiv.org/abs/2001.03288
    
python/aitemplate/compiler/transform/memory_planning.py