def __init__(self, width: IntLike, height: IntLike, channel_mode: ChannelModeEnum, begin: int = _T_UNKNOWN, end: int = _T_UNKNOWN, name: str = None): super(WebGLAllocation, self).__init__(size=width * height * ChannelMode.elements_per_pixel(channel_mode), offset=-1, begin=begin, end=end, name=name) self.width = width self.height = height self.channel_mode = channel_mode
def __init__(self, base: Variable): if base.has_attribute(TextureShape): raise ValueError( f"\'TextureShape\' attribute has been already registered to {base}." ) MAX_TEXTURE_SIZE = config.WEBGL_MAX_TEXTURE_SIZE super(TextureShape, self).__init__(base) spacial_size = base.size // ChannelMode.elements_per_pixel(base) self.width = MAX_TEXTURE_SIZE if spacial_size > MAX_TEXTURE_SIZE else spacial_size # type: int self.height = ( spacial_size + MAX_TEXTURE_SIZE - 1 ) // MAX_TEXTURE_SIZE if spacial_size > MAX_TEXTURE_SIZE else 1 # type: int
def _get_allocations(graph: Graph, operators: List[Operator], variables: List[Variable]) -> WebGLAllocationDict: T_LAST = len(operators) allocations = {} # type: WebGLAllocationDict retain_count = {v: 0 for v in variables} # type: Dict[Variable, int] allocated = set() # type: Set[Variable] for v in traverse.filter_nodes(variables, ConstantVariable): # type: ConstantVariable # Constant variable cannot be released height, width = TextureShape.get(v) width = (width + ChannelMode.elements_per_pixel(v) - 1) // ChannelMode.elements_per_pixel(v) allocations[v] = WebGLAllocation(width=width, height=height, channel_mode=ChannelMode.get(v), begin=0, end=T_LAST, name=v.name) allocated.add(v) for v in graph.inputs: # Input variable cannot be released height, width = TextureShape.get(v) width = (width + ChannelMode.elements_per_pixel(v) - 1) // ChannelMode.elements_per_pixel(v) allocations[v] = WebGLAllocation(width=width, height=height, channel_mode=ChannelMode.get(v), begin=0, end=T_LAST, name=v.name) allocated.add(v) for v in graph.outputs: # Output variable cannot be released, but it's not needed to be allocated from the begin height, width = TextureShape.get(v) width = (width + ChannelMode.elements_per_pixel(v) - 1) // ChannelMode.elements_per_pixel(v) allocations[v] = WebGLAllocation(width=width, height=height, channel_mode=ChannelMode.get(v), begin=_T_UNKNOWN, end=T_LAST, name=v.name) allocated.add(v) for t, op in enumerate(operators): for v in op.outputs.values(): if v in allocated: # Allocation object is already created (output variable, etc.) if allocations[v].begin == _T_UNKNOWN: allocations[v].begin = t else: # Create new allocation object height, width = TextureShape.get(v) width = (width + ChannelMode.elements_per_pixel(v) - 1) // ChannelMode.elements_per_pixel(v) allocations[v] = WebGLAllocation(width=width, height=height, channel_mode=ChannelMode.get(v), begin=t, end=_T_UNKNOWN, name=v.name) retain_count[v] = len(v.input_to) allocated.add(v) for v in op.inputs.values(): if v not in allocated: # Allocate height, width = TextureShape.get(v) width = (width + ChannelMode.elements_per_pixel(v) - 1) // ChannelMode.elements_per_pixel(v) allocations[v] = WebGLAllocation(width=width, height=height, channel_mode=ChannelMode.get(v), begin=t, end=_T_UNKNOWN, name=v.name) retain_count[v] = len(v.input_to) allocated.add(v) if allocations[v].end != _T_UNKNOWN: # Release timing is already determined (input, output, or constant variable). continue # Release input variable retain_count[v] -= 1 if retain_count[v] == 0: # `t + 1` means that `v` will be released *AFTER* `op` will be finished. allocations[v].end = t + 1 return allocations
def _choose_split_axis(v: Variable) -> Axis: """ For too-large texture `v`, choose one axis which is the best one to reduce texture size by splitting `v` in that axis. Args: v: Variable, whose size is too large (= this variable has :code:`SplitTarget` attribute) Returns: axis """ ops = list(v.input_to) if v.output_from is not None: ops += [v.output_from] splittable_axes = list(v.order.axes) for op in ops: _op_splittable_axes = _listup_splittable_axis( v, op) + [attr.axis for attr in op.get_attribute(Tensorwise)] for a in list(splittable_axes): if a not in _op_splittable_axes: splittable_axes.remove(a) if len(splittable_axes) == 0: raise ValueError("No axis is splittable") # Calculate the size of a side of texture which will be changed when each axis is split # # ex) OrderNC, N=512, C=2048, texture(width=2048, height=512) # => If axis `N` is split, then height will be changed => N: 512 (=height) # If axis `C` is split, then width will be changed => C: 2048 (=width) # # ex) OrderNCHW, N=1, C=512, H=13, W=13, texture(width=2048, height=43) # => TexW == W*H*(partial of C) texture width consists of axis W, H and C. # TexH == (partial of C)*N texture height consists of axis C and N. # => N cannot be split => N: -1 # C is related both width and height. In this case, use large one. => C: 2048 # H is included in width => H: 2048 # W is also included in width => W: 2048 axis_corresponding_texture_size = AxisKeyDict() element_per_pixel = ChannelMode.elements_per_pixel(v) tex_h, tex_w = TextureShape.get(v) tex_w = (tex_w + element_per_pixel - 1) // element_per_pixel for a in v.order.axes: if v.shape_dict[a] == 1: # This axis cannot be split axis_corresponding_texture_size[a] = -1 elif v.stride_dict[a] >= tex_w * element_per_pixel: axis_corresponding_texture_size[a] = tex_h elif v.stride_dict[a] * v.shape_dict[a] >= tex_w * element_per_pixel: axis_corresponding_texture_size[a] = max(tex_h, tex_w) else: axis_corresponding_texture_size[a] = tex_w splittable_axes.sort(key=lambda a: axis_corresponding_texture_size[a], reverse=True) target_axis = splittable_axes[0] console.debug( f"===========================================================================" ) console.debug(f"{v}") console.debug(f" original order: {v.order}") console.debug(f" original shape: {v.shape}") console.debug(f" texture shape: {TextureShape.get(v)}") console.debug(f"") console.debug(f" splittable axis: {splittable_axes}") console.debug(f" split axis: {target_axis}") console.debug(f"") console.debug(f" related operators:") for related_op in ops: console.debug( f"---------------------------------------------------------------------------" ) traverse.dump_op(related_op) console.debug(f"") if axis_corresponding_texture_size[target_axis] <= 0: raise NotImplementedError( f"Variable is too large to handle in WebGL backend: {v}") return target_axis
def texture_shape(v: Variable): height, width = TextureShape.get(v) elements_per_pixel = ChannelMode.elements_per_pixel(v) width = (width + elements_per_pixel - 1) // elements_per_pixel return height, width, elements_per_pixel